Skip to content

Commit 88c6be8

Browse files
committed
ffmpeg, python3: include binary
1 parent 6494ac1 commit 88c6be8

6 files changed

Lines changed: 341 additions & 20 deletions

File tree

pythonforandroid/androidndk.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ def llvm_readelf(self):
6666
def llvm_strip(self):
6767
return f"{self.llvm_binutils_prefix}strip"
6868

69+
@property
70+
def llvm_nm(self):
71+
return f"{self.llvm_binutils_prefix}nm"
72+
6973
@property
7074
def sysroot(self):
7175
return os.path.join(self.llvm_prebuilt_dir, "sysroot")

pythonforandroid/recipes/android/src/android/_android.pyx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,43 @@ class AndroidBrowser(object):
280280
import webbrowser
281281
webbrowser.register('android', AndroidBrowser)
282282

283+
# Native android executable support
284+
# Ref:
285+
# https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/apps/processes/app-data-file-execute-restrictions.md#apk-native-library
286+
287+
import os
288+
import sys
289+
from os.path import join, isdir, islink, isfile
290+
291+
_EXECUTABLES = {
292+
'python': 'libpythonbin.so',
293+
'python3': 'libpythonbin.so',
294+
'ffmpeg': 'libffmpegbin.so',
295+
}
296+
297+
app_info = mActivity.getApplicationInfo()
298+
native_lib_dir = app_info.nativeLibraryDir
299+
files_dir = mActivity.getFilesDir().getAbsolutePath()
300+
bin_dir = join(files_dir, 'app', '.bin')
301+
302+
if not isdir(bin_dir):
303+
os.makedirs(bin_dir)
304+
305+
for exe, exe_lib in _EXECUTABLES.items():
306+
_exe = join(bin_dir, exe)
307+
_exe_lib = join(native_lib_dir, exe_lib)
308+
if isfile(_exe_lib) and not islink(_exe):
309+
try:
310+
os.symlink(_exe_lib, _exe)
311+
print(f'Symlink executable: {exe_lib} -> {exe}')
312+
except Exception as e:
313+
print(f'Symlink failed for {exe_lib} -> {exe}')
314+
print(e)
315+
316+
os.environ['LD_LIBRARY_PATH'] = native_lib_dir
317+
os.environ['PATH'] = bin_dir
318+
os.environ['PYTHONPATH'] = ":".join(sys.path)
319+
sys.executable = join(bin_dir, 'python')
283320

284321
def start_service(title="Background Service",
285322
description="", arg="",

pythonforandroid/recipes/ffmpeg/__init__.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
from pythonforandroid.toolchain import Recipe, current_directory, shprint
22
from os.path import exists, join, realpath
33
import sh
4+
from multiprocessing import cpu_count
45

56

67
class FFMpegRecipe(Recipe):
7-
version = 'n6.1.2'
8+
version = '8.0.1'
89
# Moved to github.com instead of ffmpeg.org to improve download speed
9-
url = 'https://github.com/FFmpeg/FFmpeg/archive/{version}.zip'
10-
depends = ['sdl2'] # Need this to build correct recipe order
10+
url = 'https://www.ffmpeg.org/releases/ffmpeg-{version}.tar.xz'
11+
depends = [('sdl2', 'sdl3')] # Need this to build correct recipe order
1112
opts_depends = ['openssl', 'ffpyplayer_codecs', 'av_codecs']
12-
patches = ['patches/configure.patch']
13+
patches = ['patches/configure.patch', 'patches/backport-Android15-MediaCodec-fix.patch']
14+
_libs = [
15+
"libavcodec.so",
16+
"libavfilter.so",
17+
"libavutil.so",
18+
"libswscale.so",
19+
"libavdevice.so",
20+
"libavformat.so",
21+
"libswresample.so",
22+
"libffmpegbin.so",
23+
]
24+
built_libraries = dict.fromkeys(_libs, "./lib")
1325

1426
def should_build(self, arch):
1527
build_dir = self.get_build_dir(arch.arch)
@@ -36,15 +48,15 @@ def build_arch(self, arch):
3648

3749
if 'openssl' in self.ctx.recipe_build_order:
3850
flags += [
51+
'--enable-version3',
3952
'--enable-openssl',
4053
'--enable-nonfree',
4154
'--enable-protocol=https,tls_openssl',
4255
]
4356
build_dir = Recipe.get_recipe(
4457
'openssl', self.ctx).get_build_dir(arch.arch)
45-
cflags += ['-I' + build_dir + '/include/',
46-
'-DOPENSSL_API_COMPAT=0x10002000L']
47-
ldflags += ['-L' + build_dir]
58+
cflags += ['-I' + build_dir + '/include/']
59+
ldflags += ['-L' + build_dir, '-lssl', '-lcrypto']
4860

4961
codecs_opts = {"ffpyplayer_codecs", "av_codecs"}
5062
if codecs_opts.intersection(self.ctx.recipe_build_order):
@@ -98,9 +110,8 @@ def build_arch(self, arch):
98110
'--disable-symver',
99111
]
100112

101-
# disable binaries / doc
113+
# disable doc
102114
flags += [
103-
'--disable-programs',
104115
'--disable-doc',
105116
]
106117

@@ -131,13 +142,15 @@ def build_arch(self, arch):
131142
'--cross-prefix={}-'.format(arch.target),
132143
'--arch={}'.format(arch_flag),
133144
'--strip={}'.format(self.ctx.ndk.llvm_strip),
145+
'--nm={}'.format(self.ctx.ndk.llvm_nm),
134146
'--sysroot={}'.format(self.ctx.ndk.sysroot),
135147
'--enable-neon',
136148
'--prefix={}'.format(realpath('.')),
137149
]
138150

139151
if arch_flag == 'arm':
140152
cflags += [
153+
'-Wno-error=incompatible-pointer-types',
141154
'-mfpu=vfpv3-d16',
142155
'-mfloat-abi=softfp',
143156
'-fPIC',
@@ -148,11 +161,9 @@ def build_arch(self, arch):
148161

149162
configure = sh.Command('./configure')
150163
shprint(configure, *flags, _env=env)
151-
shprint(sh.make, '-j4', _env=env)
164+
shprint(sh.make, '-j', f"{cpu_count()}", _env=env)
152165
shprint(sh.make, 'install', _env=env)
153-
# copy libs:
154-
sh.cp('-a', sh.glob('./lib/lib*.so'),
155-
self.ctx.get_libs_dir(arch.arch))
166+
shprint(sh.cp, "ffmpeg", "./lib/libffmpegbin.so")
156167

157168

158169
recipe = FFMpegRecipe()
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
From 4531cc1b325b50a77fa22981f44a25fbce025a5e Mon Sep 17 00:00:00 2001
2+
From: Dmitrii Okunev <xaionaro@dx.center>
3+
Date: Sat, 22 Nov 2025 19:57:19 +0000
4+
Subject: [PATCH] fftools: Fix MediaCodec on Android15+
5+
6+
On Android15+ MediaCodec HAL backend was switched from HIDL to AIDL.
7+
As a result, MediaCodec operations started to hang, see:
8+
9+
https://trac.ffmpeg.org/ticket/11363
10+
https://github.com/termux/termux-packages/issues/21264
11+
https://issuetracker.google.com/issues/382831999
12+
13+
To fix that it is necessary to initialize binder thread pool.
14+
15+
Signed-off-by: Dmitrii Okunev <xaionaro@dx.center>
16+
---
17+
compat/android/binder.c | 114 ++++++++++++++++++++++++++++++++++++++++
18+
compat/android/binder.h | 31 +++++++++++
19+
configure | 3 +-
20+
fftools/Makefile | 1 +
21+
fftools/ffmpeg.c | 7 +++
22+
5 files changed, 155 insertions(+), 1 deletion(-)
23+
create mode 100644 compat/android/binder.c
24+
create mode 100644 compat/android/binder.h
25+
26+
diff --git a/compat/android/binder.c b/compat/android/binder.c
27+
new file mode 100644
28+
index 0000000000..a214d977cc
29+
--- /dev/null
30+
+++ b/compat/android/binder.c
31+
@@ -0,0 +1,114 @@
32+
+/*
33+
+ * Android Binder handler
34+
+ *
35+
+ * Copyright (c) 2025 Dmitrii Okunev
36+
+ *
37+
+ * This file is part of FFmpeg.
38+
+ *
39+
+ * FFmpeg is free software; you can redistribute it and/or
40+
+ * modify it under the terms of the GNU Lesser General Public
41+
+ * License as published by the Free Software Foundation; either
42+
+ * version 2.1 of the License, or (at your option) any later version.
43+
+ *
44+
+ * FFmpeg is distributed in the hope that it will be useful,
45+
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
46+
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47+
+ * Lesser General Public License for more details.
48+
+ *
49+
+ * You should have received a copy of the GNU Lesser General Public
50+
+ * License along with FFmpeg; if not, write to the Free Software
51+
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
52+
+ */
53+
+
54+
+
55+
+#if defined(__ANDROID__)
56+
+
57+
+#include <dlfcn.h>
58+
+#include <stdint.h>
59+
+#include <stdlib.h>
60+
+
61+
+#include "libavutil/log.h"
62+
+#include "binder.h"
63+
+
64+
+#define THREAD_POOL_SIZE 1
65+
+
66+
+static void *dlopen_libbinder_ndk(void)
67+
+{
68+
+ /*
69+
+ * libbinder_ndk.so often does not contain the functions we need, so making
70+
+ * this dependency optional, thus using dlopen/dlsym instead of linking.
71+
+ *
72+
+ * See also: https://source.android.com/docs/core/architecture/aidl/aidl-backends
73+
+ */
74+
+
75+
+ void *h = dlopen("libbinder_ndk.so", RTLD_NOW | RTLD_LOCAL);
76+
+ if (h != NULL)
77+
+ return h;
78+
+
79+
+ av_log(NULL, AV_LOG_WARNING,
80+
+ "android/binder: unable to load libbinder_ndk.so: '%s'; skipping binder threadpool init (MediaCodec likely won't work)\n",
81+
+ dlerror());
82+
+ return NULL;
83+
+}
84+
+
85+
+static void android_binder_threadpool_init(void)
86+
+{
87+
+ typedef int (*set_thread_pool_max_fn)(uint32_t);
88+
+ typedef void (*start_thread_pool_fn)(void);
89+
+
90+
+ set_thread_pool_max_fn set_thread_pool_max = NULL;
91+
+ start_thread_pool_fn start_thread_pool = NULL;
92+
+
93+
+ void *h = dlopen_libbinder_ndk();
94+
+ if (h == NULL)
95+
+ return;
96+
+
97+
+ unsigned thead_pool_size = THREAD_POOL_SIZE;
98+
+
99+
+ set_thread_pool_max =
100+
+ (set_thread_pool_max_fn) dlsym(h,
101+
+ "ABinderProcess_setThreadPoolMaxThreadCount");
102+
+ start_thread_pool =
103+
+ (start_thread_pool_fn) dlsym(h, "ABinderProcess_startThreadPool");
104+
+
105+
+ if (start_thread_pool == NULL) {
106+
+ av_log(NULL, AV_LOG_WARNING,
107+
+ "android/binder: ABinderProcess_startThreadPool not found; skipping threadpool init (MediaCodec likely won't work)\n");
108+
+ return;
109+
+ }
110+
+
111+
+ if (set_thread_pool_max != NULL) {
112+
+ int ok = set_thread_pool_max(thead_pool_size);
113+
+ av_log(NULL, AV_LOG_DEBUG,
114+
+ "android/binder: ABinderProcess_setThreadPoolMaxThreadCount(%u) => %s\n",
115+
+ thead_pool_size, ok ? "ok" : "fail");
116+
+ } else {
117+
+ av_log(NULL, AV_LOG_DEBUG,
118+
+ "android/binder: ABinderProcess_setThreadPoolMaxThreadCount is unavailable; using the library default\n");
119+
+ }
120+
+
121+
+ start_thread_pool();
122+
+ av_log(NULL, AV_LOG_DEBUG,
123+
+ "android/binder: ABinderProcess_startThreadPool() called\n");
124+
+}
125+
+
126+
+void android_binder_threadpool_init_if_required(void)
127+
+{
128+
+#if __ANDROID_API__ >= 24
129+
+ if (android_get_device_api_level() < 35) {
130+
+ // the issue with the thread pool was introduced in Android 15 (API 35)
131+
+ av_log(NULL, AV_LOG_DEBUG,
132+
+ "android/binder: API<35, thus no need to initialize a thread pool\n");
133+
+ return;
134+
+ }
135+
+ android_binder_threadpool_init();
136+
+#else
137+
+ // android_get_device_api_level was introduced in API 24, so we cannot use it
138+
+ // to detect the API level in API<24. For simplicity we just assume
139+
+ // libbinder_ndk.so on the system running this code would have API level < 35;
140+
+ av_log(NULL, AV_LOG_DEBUG,
141+
+ "android/binder: is built with API<24, assuming this is not Android 15+\n");
142+
+#endif
143+
+}
144+
+
145+
+#endif /* __ANDROID__ */
146+
diff --git a/compat/android/binder.h b/compat/android/binder.h
147+
new file mode 100644
148+
index 0000000000..2b1ca53fe8
149+
--- /dev/null
150+
+++ b/compat/android/binder.h
151+
@@ -0,0 +1,31 @@
152+
+/*
153+
+ * Android Binder handler
154+
+ *
155+
+ * Copyright (c) 2025 Dmitrii Okunev
156+
+ *
157+
+ * This file is part of FFmpeg.
158+
+ *
159+
+ * FFmpeg is free software; you can redistribute it and/or
160+
+ * modify it under the terms of the GNU Lesser General Public
161+
+ * License as published by the Free Software Foundation; either
162+
+ * version 2.1 of the License, or (at your option) any later version.
163+
+ *
164+
+ * FFmpeg is distributed in the hope that it will be useful,
165+
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
166+
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
167+
+ * Lesser General Public License for more details.
168+
+ *
169+
+ * You should have received a copy of the GNU Lesser General Public
170+
+ * License along with FFmpeg; if not, write to the Free Software
171+
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
172+
+ */
173+
+
174+
+#ifndef COMPAT_ANDROID_BINDER_H
175+
+#define COMPAT_ANDROID_BINDER_H
176+
+
177+
+/**
178+
+ * Initialize Android Binder thread pool.
179+
+ */
180+
+void android_binder_threadpool_init_if_required(void);
181+
+
182+
+#endif // COMPAT_ANDROID_BINDER_H
183+
diff --git a/configure b/configure
184+
index af125bc115..098fe12f39 100755
185+
--- a/configure
186+
+++ b/configure
187+
@@ -7312,7 +7312,8 @@ enabled mbedtls && { check_pkg_config mbedtls mbedtls mbedtls/x509_crt
188+
check_pkg_config mbedtls mbedtls mbedtls/ssl.h mbedtls_ssl_init ||
189+
check_lib mbedtls mbedtls/ssl.h mbedtls_ssl_init -lmbedtls -lmbedx509 -lmbedcrypto ||
190+
die "ERROR: mbedTLS not found"; }
191+
-enabled mediacodec && { enabled jni || die "ERROR: mediacodec requires --enable-jni"; }
192+
+enabled mediacodec && { enabled jni || die "ERROR: mediacodec requires --enable-jni"; } &&
193+
+ add_compat android/binder.o
194+
enabled mmal && { check_lib mmal interface/mmal/mmal.h mmal_port_connect -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host ||
195+
{ ! enabled cross_compile &&
196+
add_cflags -isystem/opt/vc/include/ -isystem/opt/vc/include/interface/vmcs_host/linux -isystem/opt/vc/include/interface/vcos/pthreads -fgnu89-inline &&
197+
diff --git a/fftools/Makefile b/fftools/Makefile
198+
index bdb44fc5ce..01b16fa8f4 100644
199+
--- a/fftools/Makefile
200+
+++ b/fftools/Makefile
201+
@@ -51,6 +51,7 @@ OBJS-ffprobe += \
202+
fftools/textformat/tw_buffer.o \
203+
fftools/textformat/tw_stdout.o \
204+
205+
+OBJS-ffmpeg += $(COMPAT_OBJS:%=compat/%)
206+
OBJS-ffplay += fftools/ffplay_renderer.o
207+
208+
define DOFFTOOL
209+
diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
210+
index 444d027c15..c2c85d46bd 100644
211+
--- a/fftools/ffmpeg.c
212+
+++ b/fftools/ffmpeg.c
213+
@@ -78,6 +78,9 @@
214+
#include "libavdevice/avdevice.h"
215+
216+
#include "cmdutils.h"
217+
+#if CONFIG_MEDIACODEC
218+
+#include "compat/android/binder.h"
219+
+#endif
220+
#include "ffmpeg.h"
221+
#include "ffmpeg_sched.h"
222+
#include "ffmpeg_utils.h"
223+
@@ -1019,6 +1022,10 @@ int main(int argc, char **argv)
224+
goto finish;
225+
}
226+
227+
+#if CONFIG_MEDIACODEC
228+
+ android_binder_threadpool_init_if_required();
229+
+#endif
230+
+
231+
current_time = ti = get_benchmark_time_stamps();
232+
ret = transcode(sch);
233+
if (ret >= 0 && do_benchmark) {
234+
--
235+
2.49.1
236+

0 commit comments

Comments
 (0)