From d4c222353c6eb211830612971657629286c494bc Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Thu, 1 Jan 2026 20:21:57 +0530 Subject: [PATCH] `ffmpeg`, `python3`: include binary --- pythonforandroid/androidndk.py | 4 + .../recipes/android/src/android/_android.pyx | 37 +++ pythonforandroid/recipes/ffmpeg/__init__.py | 37 ++- .../backport-Android15-MediaCodec-fix.patch | 236 ++++++++++++++++++ .../recipes/ffmpeg/patches/configure.patch | 42 +++- pythonforandroid/recipes/python3/__init__.py | 4 + 6 files changed, 340 insertions(+), 20 deletions(-) create mode 100644 pythonforandroid/recipes/ffmpeg/patches/backport-Android15-MediaCodec-fix.patch diff --git a/pythonforandroid/androidndk.py b/pythonforandroid/androidndk.py index 83cb355740..7cd26f4c4e 100644 --- a/pythonforandroid/androidndk.py +++ b/pythonforandroid/androidndk.py @@ -66,6 +66,10 @@ def llvm_readelf(self): def llvm_strip(self): return f"{self.llvm_binutils_prefix}strip" + @property + def llvm_nm(self): + return f"{self.llvm_binutils_prefix}nm" + @property def sysroot(self): return os.path.join(self.llvm_prebuilt_dir, "sysroot") diff --git a/pythonforandroid/recipes/android/src/android/_android.pyx b/pythonforandroid/recipes/android/src/android/_android.pyx index 1d6e65a161..3643e722bb 100644 --- a/pythonforandroid/recipes/android/src/android/_android.pyx +++ b/pythonforandroid/recipes/android/src/android/_android.pyx @@ -280,6 +280,43 @@ class AndroidBrowser(object): import webbrowser webbrowser.register('android', AndroidBrowser) +# Native android executable support +# Ref: +# 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 + +import os +import sys +from os.path import join, isdir, islink, isfile + +_EXECUTABLES = { + 'python': 'libpythonbin.so', + 'python3': 'libpythonbin.so', + 'ffmpeg': 'libffmpegbin.so', +} + +app_info = mActivity.getApplicationInfo() +native_lib_dir = app_info.nativeLibraryDir +files_dir = mActivity.getFilesDir().getAbsolutePath() +bin_dir = join(files_dir, 'app', '.bin') + +if not isdir(bin_dir): + os.makedirs(bin_dir) + +for exe, exe_lib in _EXECUTABLES.items(): + _exe = join(bin_dir, exe) + _exe_lib = join(native_lib_dir, exe_lib) + if isfile(_exe_lib) and not islink(_exe): + try: + os.symlink(_exe_lib, _exe) + print(f'Symlink executable: {exe_lib} -> {exe}') + except Exception as e: + print(f'Symlink failed for {exe_lib} -> {exe}') + print(e) + +os.environ['LD_LIBRARY_PATH'] = native_lib_dir +os.environ['PATH'] = bin_dir +os.environ['PYTHONPATH'] = ":".join(sys.path) +sys.executable = join(bin_dir, 'python') def start_service(title="Background Service", description="", arg="", diff --git a/pythonforandroid/recipes/ffmpeg/__init__.py b/pythonforandroid/recipes/ffmpeg/__init__.py index f7134b3384..38c4ff4660 100644 --- a/pythonforandroid/recipes/ffmpeg/__init__.py +++ b/pythonforandroid/recipes/ffmpeg/__init__.py @@ -1,15 +1,27 @@ from pythonforandroid.toolchain import Recipe, current_directory, shprint from os.path import exists, join, realpath import sh +from multiprocessing import cpu_count class FFMpegRecipe(Recipe): - version = 'n6.1.2' + version = '8.0.1' # Moved to github.com instead of ffmpeg.org to improve download speed - url = 'https://github.com/FFmpeg/FFmpeg/archive/{version}.zip' - depends = ['sdl2'] # Need this to build correct recipe order + url = 'https://www.ffmpeg.org/releases/ffmpeg-{version}.tar.xz' + depends = [('sdl2', 'sdl3')] # Need this to build correct recipe order opts_depends = ['openssl', 'ffpyplayer_codecs', 'av_codecs'] - patches = ['patches/configure.patch'] + patches = ['patches/configure.patch', 'patches/backport-Android15-MediaCodec-fix.patch'] + _libs = [ + "libavcodec.so", + "libavfilter.so", + "libavutil.so", + "libswscale.so", + "libavdevice.so", + "libavformat.so", + "libswresample.so", + "libffmpegbin.so", + ] + built_libraries = dict.fromkeys(_libs, "./lib") def should_build(self, arch): build_dir = self.get_build_dir(arch.arch) @@ -36,15 +48,15 @@ def build_arch(self, arch): if 'openssl' in self.ctx.recipe_build_order: flags += [ + '--enable-version3', '--enable-openssl', '--enable-nonfree', '--enable-protocol=https,tls_openssl', ] build_dir = Recipe.get_recipe( 'openssl', self.ctx).get_build_dir(arch.arch) - cflags += ['-I' + build_dir + '/include/', - '-DOPENSSL_API_COMPAT=0x10002000L'] - ldflags += ['-L' + build_dir] + cflags += ['-I' + build_dir + '/include/'] + ldflags += ['-L' + build_dir, '-lssl', '-lcrypto'] codecs_opts = {"ffpyplayer_codecs", "av_codecs"} if codecs_opts.intersection(self.ctx.recipe_build_order): @@ -98,9 +110,8 @@ def build_arch(self, arch): '--disable-symver', ] - # disable binaries / doc + # disable doc flags += [ - '--disable-programs', '--disable-doc', ] @@ -131,6 +142,7 @@ def build_arch(self, arch): '--cross-prefix={}-'.format(arch.target), '--arch={}'.format(arch_flag), '--strip={}'.format(self.ctx.ndk.llvm_strip), + '--nm={}'.format(self.ctx.ndk.llvm_nm), '--sysroot={}'.format(self.ctx.ndk.sysroot), '--enable-neon', '--prefix={}'.format(realpath('.')), @@ -138,6 +150,7 @@ def build_arch(self, arch): if arch_flag == 'arm': cflags += [ + '-Wno-error=incompatible-pointer-types', '-mfpu=vfpv3-d16', '-mfloat-abi=softfp', '-fPIC', @@ -148,11 +161,9 @@ def build_arch(self, arch): configure = sh.Command('./configure') shprint(configure, *flags, _env=env) - shprint(sh.make, '-j4', _env=env) + shprint(sh.make, '-j', f"{cpu_count()}", _env=env) shprint(sh.make, 'install', _env=env) - # copy libs: - sh.cp('-a', sh.glob('./lib/lib*.so'), - self.ctx.get_libs_dir(arch.arch)) + shprint(sh.cp, "ffmpeg", "./lib/libffmpegbin.so") recipe = FFMpegRecipe() diff --git a/pythonforandroid/recipes/ffmpeg/patches/backport-Android15-MediaCodec-fix.patch b/pythonforandroid/recipes/ffmpeg/patches/backport-Android15-MediaCodec-fix.patch new file mode 100644 index 0000000000..87a556dfc2 --- /dev/null +++ b/pythonforandroid/recipes/ffmpeg/patches/backport-Android15-MediaCodec-fix.patch @@ -0,0 +1,236 @@ +From 4531cc1b325b50a77fa22981f44a25fbce025a5e Mon Sep 17 00:00:00 2001 +From: Dmitrii Okunev +Date: Sat, 22 Nov 2025 19:57:19 +0000 +Subject: [PATCH] fftools: Fix MediaCodec on Android15+ + +On Android15+ MediaCodec HAL backend was switched from HIDL to AIDL. +As a result, MediaCodec operations started to hang, see: + + https://trac.ffmpeg.org/ticket/11363 + https://github.com/termux/termux-packages/issues/21264 + https://issuetracker.google.com/issues/382831999 + +To fix that it is necessary to initialize binder thread pool. + +Signed-off-by: Dmitrii Okunev +--- + compat/android/binder.c | 114 ++++++++++++++++++++++++++++++++++++++++ + compat/android/binder.h | 31 +++++++++++ + configure | 3 +- + fftools/Makefile | 1 + + fftools/ffmpeg.c | 7 +++ + 5 files changed, 155 insertions(+), 1 deletion(-) + create mode 100644 compat/android/binder.c + create mode 100644 compat/android/binder.h + +diff --git a/compat/android/binder.c b/compat/android/binder.c +new file mode 100644 +index 0000000000..a214d977cc +--- /dev/null ++++ b/compat/android/binder.c +@@ -0,0 +1,114 @@ ++/* ++ * Android Binder handler ++ * ++ * Copyright (c) 2025 Dmitrii Okunev ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++ ++#if defined(__ANDROID__) ++ ++#include ++#include ++#include ++ ++#include "libavutil/log.h" ++#include "binder.h" ++ ++#define THREAD_POOL_SIZE 1 ++ ++static void *dlopen_libbinder_ndk(void) ++{ ++ /* ++ * libbinder_ndk.so often does not contain the functions we need, so making ++ * this dependency optional, thus using dlopen/dlsym instead of linking. ++ * ++ * See also: https://source.android.com/docs/core/architecture/aidl/aidl-backends ++ */ ++ ++ void *h = dlopen("libbinder_ndk.so", RTLD_NOW | RTLD_LOCAL); ++ if (h != NULL) ++ return h; ++ ++ av_log(NULL, AV_LOG_WARNING, ++ "android/binder: unable to load libbinder_ndk.so: '%s'; skipping binder threadpool init (MediaCodec likely won't work)\n", ++ dlerror()); ++ return NULL; ++} ++ ++static void android_binder_threadpool_init(void) ++{ ++ typedef int (*set_thread_pool_max_fn)(uint32_t); ++ typedef void (*start_thread_pool_fn)(void); ++ ++ set_thread_pool_max_fn set_thread_pool_max = NULL; ++ start_thread_pool_fn start_thread_pool = NULL; ++ ++ void *h = dlopen_libbinder_ndk(); ++ if (h == NULL) ++ return; ++ ++ unsigned thead_pool_size = THREAD_POOL_SIZE; ++ ++ set_thread_pool_max = ++ (set_thread_pool_max_fn) dlsym(h, ++ "ABinderProcess_setThreadPoolMaxThreadCount"); ++ start_thread_pool = ++ (start_thread_pool_fn) dlsym(h, "ABinderProcess_startThreadPool"); ++ ++ if (start_thread_pool == NULL) { ++ av_log(NULL, AV_LOG_WARNING, ++ "android/binder: ABinderProcess_startThreadPool not found; skipping threadpool init (MediaCodec likely won't work)\n"); ++ return; ++ } ++ ++ if (set_thread_pool_max != NULL) { ++ int ok = set_thread_pool_max(thead_pool_size); ++ av_log(NULL, AV_LOG_DEBUG, ++ "android/binder: ABinderProcess_setThreadPoolMaxThreadCount(%u) => %s\n", ++ thead_pool_size, ok ? "ok" : "fail"); ++ } else { ++ av_log(NULL, AV_LOG_DEBUG, ++ "android/binder: ABinderProcess_setThreadPoolMaxThreadCount is unavailable; using the library default\n"); ++ } ++ ++ start_thread_pool(); ++ av_log(NULL, AV_LOG_DEBUG, ++ "android/binder: ABinderProcess_startThreadPool() called\n"); ++} ++ ++void android_binder_threadpool_init_if_required(void) ++{ ++#if __ANDROID_API__ >= 24 ++ if (android_get_device_api_level() < 35) { ++ // the issue with the thread pool was introduced in Android 15 (API 35) ++ av_log(NULL, AV_LOG_DEBUG, ++ "android/binder: API<35, thus no need to initialize a thread pool\n"); ++ return; ++ } ++ android_binder_threadpool_init(); ++#else ++ // android_get_device_api_level was introduced in API 24, so we cannot use it ++ // to detect the API level in API<24. For simplicity we just assume ++ // libbinder_ndk.so on the system running this code would have API level < 35; ++ av_log(NULL, AV_LOG_DEBUG, ++ "android/binder: is built with API<24, assuming this is not Android 15+\n"); ++#endif ++} ++ ++#endif /* __ANDROID__ */ +diff --git a/compat/android/binder.h b/compat/android/binder.h +new file mode 100644 +index 0000000000..2b1ca53fe8 +--- /dev/null ++++ b/compat/android/binder.h +@@ -0,0 +1,31 @@ ++/* ++ * Android Binder handler ++ * ++ * Copyright (c) 2025 Dmitrii Okunev ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#ifndef COMPAT_ANDROID_BINDER_H ++#define COMPAT_ANDROID_BINDER_H ++ ++/** ++ * Initialize Android Binder thread pool. ++ */ ++void android_binder_threadpool_init_if_required(void); ++ ++#endif // COMPAT_ANDROID_BINDER_H +diff --git a/configure b/configure +index af125bc115..098fe12f39 100755 +--- a/configure ++++ b/configure +@@ -7312,7 +7312,8 @@ enabled mbedtls && { check_pkg_config mbedtls mbedtls mbedtls/x509_crt + check_pkg_config mbedtls mbedtls mbedtls/ssl.h mbedtls_ssl_init || + check_lib mbedtls mbedtls/ssl.h mbedtls_ssl_init -lmbedtls -lmbedx509 -lmbedcrypto || + die "ERROR: mbedTLS not found"; } +-enabled mediacodec && { enabled jni || die "ERROR: mediacodec requires --enable-jni"; } ++enabled mediacodec && { enabled jni || die "ERROR: mediacodec requires --enable-jni"; } && ++ add_compat android/binder.o + enabled mmal && { check_lib mmal interface/mmal/mmal.h mmal_port_connect -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host || + { ! enabled cross_compile && + add_cflags -isystem/opt/vc/include/ -isystem/opt/vc/include/interface/vmcs_host/linux -isystem/opt/vc/include/interface/vcos/pthreads -fgnu89-inline && +diff --git a/fftools/Makefile b/fftools/Makefile +index bdb44fc5ce..01b16fa8f4 100644 +--- a/fftools/Makefile ++++ b/fftools/Makefile +@@ -51,6 +51,7 @@ OBJS-ffprobe += \ + fftools/textformat/tw_buffer.o \ + fftools/textformat/tw_stdout.o \ + ++OBJS-ffmpeg += $(COMPAT_OBJS:%=compat/%) + OBJS-ffplay += fftools/ffplay_renderer.o + + define DOFFTOOL +diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c +index 444d027c15..c2c85d46bd 100644 +--- a/fftools/ffmpeg.c ++++ b/fftools/ffmpeg.c +@@ -78,6 +78,9 @@ + #include "libavdevice/avdevice.h" + + #include "cmdutils.h" ++#if CONFIG_MEDIACODEC ++#include "compat/android/binder.h" ++#endif + #include "ffmpeg.h" + #include "ffmpeg_sched.h" + #include "ffmpeg_utils.h" +@@ -1019,6 +1022,10 @@ int main(int argc, char **argv) + goto finish; + } + ++#if CONFIG_MEDIACODEC ++ android_binder_threadpool_init_if_required(); ++#endif ++ + current_time = ti = get_benchmark_time_stamps(); + ret = transcode(sch); + if (ret >= 0 && do_benchmark) { +-- +2.49.1 + diff --git a/pythonforandroid/recipes/ffmpeg/patches/configure.patch b/pythonforandroid/recipes/ffmpeg/patches/configure.patch index e274359cb7..504a79cad9 100644 --- a/pythonforandroid/recipes/ffmpeg/patches/configure.patch +++ b/pythonforandroid/recipes/ffmpeg/patches/configure.patch @@ -1,8 +1,6 @@ -diff --git a/configure b/configure -index 5af693c954..d1d0a4f0a2 100755 ---- a/configure -+++ b/configure -@@ -6800,7 +6800,7 @@ enabled librsvg && require_pkg_config librsvg librsvg-2.0 librsvg-2.0/ +--- ffmpeg-8.0/configure 2025-08-22 14:54:23.000000000 +0530 ++++ ffmpeg-8.0.mod/configure 2026-01-03 13:40:42.264702438 +0530 +@@ -7141,7 +7141,7 @@ enabled librtmp && require_pkg_config librtmp librtmp librtmp/rtmp.h RTMP_Socket enabled librubberband && require_pkg_config librubberband "rubberband >= 1.8.1" rubberband/rubberband-c.h rubberband_new -lstdc++ && append librubberband_extralibs "-lstdc++" enabled libshaderc && require_pkg_config spirv_compiler "shaderc >= 2019.1" shaderc/shaderc.h shaderc_compiler_initialize @@ -11,12 +9,42 @@ index 5af693c954..d1d0a4f0a2 100755 enabled libsmbclient && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init || require libsmbclient libsmbclient.h smbc_init -lsmbclient; } enabled libsnappy && require libsnappy snappy-c.h snappy_compress -lsnappy -lstdc++ -@@ -6850,7 +6850,7 @@ enabled libvpx && { +@@ -7195,7 +7195,7 @@ enabled libwebp && { enabled libwebp_encoder && require_pkg_config libwebp "libwebp >= 0.2.0" webp/encode.h WebPGetEncoderVersion enabled libwebp_anim_encoder && check_pkg_config libwebp_anim_encoder "libwebpmux >= 0.4.0" webp/mux.h WebPAnimEncoderOptionsInit; } -enabled libx264 && require_pkg_config libx264 x264 "stdint.h x264.h" x264_encoder_encode && +enabled libx264 && require "x264" "stdint.h x264.h" x264_encoder_encode && - require_cpp_condition libx264 x264.h "X264_BUILD >= 122" && { + require_cpp_condition libx264 x264.h "X264_BUILD >= 155" && { [ "$toolchain" != "msvc" ] || require_cpp_condition libx264 x264.h "X264_BUILD >= 158"; } && +@@ -7258,13 +7258,7 @@ + enabled omx && require_headers OMX_Core.h && \ + warn "The OpenMAX encoders are deprecated and will be removed in future versions" + +-enabled openssl && { { check_pkg_config openssl "openssl >= 3.0.0" openssl/ssl.h OPENSSL_init_ssl && +- { enabled gplv3 || ! enabled gpl || enabled nonfree || die "ERROR: OpenSSL >=3.0.0 requires --enable-version3"; }; } || +- { enabled gpl && ! enabled nonfree && die "ERROR: OpenSSL <3.0.0 is incompatible with the gpl"; } || +- check_pkg_config openssl "openssl >= 1.1.0" openssl/ssl.h OPENSSL_init_ssl || +- check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto || +- check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto -lws2_32 -lgdi32 || +- die "ERROR: openssl (>= 1.1.0) not found"; } ++enabled openssl && true + enabled pocketsphinx && require_pkg_config pocketsphinx pocketsphinx pocketsphinx/pocketsphinx.h ps_init + enabled rkmpp && { require_pkg_config rkmpp rockchip_mpp rockchip/rk_mpi.h mpp_create && + require_pkg_config rockchip_mpp "rockchip_mpp >= 1.3.7" rockchip/rk_mpi.h mpp_create && +@@ -7273,14 +7267,6 @@ + } + enabled vapoursynth && require_headers "vapoursynth/VSScript4.h vapoursynth/VapourSynth4.h" + +-enabled openssl && { +- enabled whip_muxer && { +- $pkg_config --exists --print-errors "openssl >= 1.0.1k" || +- require_pkg_config openssl "openssl >= 1.0.1k" openssl/ssl.h SSL_library_init || +- require_pkg_config openssl "openssl >= 1.0.1k" openssl/ssl.h OPENSSL_init_ssl +- } +-} +- + + if enabled gcrypt; then + GCRYPT_CONFIG="${cross_prefix}libgcrypt-config" diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py index fb69b9a366..754d08cde3 100644 --- a/pythonforandroid/recipes/python3/__init__.py +++ b/pythonforandroid/recipes/python3/__init__.py @@ -183,6 +183,8 @@ class Python3Recipe(TargetPythonRecipe): disable_gil = False '''python3.13 experimental free-threading build''' + built_libraries = {"libpythonbin.so": "./android-build/"} + def __init__(self, *args, **kwargs): self._ctx = None super().__init__(*args, **kwargs) @@ -364,6 +366,8 @@ def build_arch(self, arch): 'INSTSONAME={lib_name}'.format(lib_name=self._libpython), _env=env ) + # rename executable + sh.cp('python', 'libpythonbin.so') # TODO: Look into passing the path to pyconfig.h in a # better way, although this is probably acceptable