Skip to content

Commit 0e74535

Browse files
committed
ffmpeg, python3: include binary
1 parent 6494ac1 commit 0e74535

File tree

7 files changed

+509
-66
lines changed

7 files changed

+509
-66
lines changed

doc/source/apis.rst

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,61 @@ unless you blacklist it. Use it in your app like this::
7272
from android.permissions import request_permissions, Permission
7373
request_permissions([Permission.WRITE_EXTERNAL_STORAGE])
7474

75-
The available permissions are listed here:
75+
The available permissions are listed here:/^^^
7676

7777
https://developer.android.com/reference/android/Manifest.permission
7878

7979

8080
Other common tasks
8181
------------------
8282

83+
Running executables
84+
~~~~~~~~~~~~~~~~~~~
85+
86+
Android restricts executing files from application data directories.
87+
``python-for-android`` works around this by creating symbolic links to a
88+
small set of supported executables inside an internal executable
89+
directory that is added to ``PATH``.
90+
91+
During startup, ``start.c`` (compiled into ``libmain.so``) scans loaded
92+
native libraries and creates symlinks for any library matching::
93+
94+
lib<binary_name>bin.so
95+
96+
Each matching library is exposed at runtime as::
97+
98+
<binary_name>
99+
100+
For example::
101+
102+
libffmpegbin.so -> ffmpeg
103+
104+
Recipe developers may expose additional executables by renaming them
105+
using the same naming convention.
106+
107+
The following example performs a minimal FFmpeg sanity check using an
108+
in-memory test source and discards the output::
109+
110+
import subprocess
111+
112+
subprocess.run(
113+
["ffmpeg", "-f", "lavfi", "-i", "testsrc", "-t", "1", "-f", "null", "-"],
114+
check=True
115+
)
116+
117+
This verifies that ``ffmpeg`` is available and executable on the device.
118+
Ensure ``ffmpeg`` is included in your ``requirements``.
119+
120+
If video encoding is required, the following codec options must also be
121+
enabled in the build configuration:
122+
123+
- ``av_codecs``
124+
- ``libx264``
125+
126+
Without these, FFmpeg may be present but lack the required codec support.
127+
128+
See also: `APK native library execution restrictions <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>`_
129+
83130
Dismissing the splash screen
84131
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
85132

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/bootstraps/common/build/jni/application/src/start.c

Lines changed: 155 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include <stdlib.h>
1010
#include <unistd.h>
1111
#include <dirent.h>
12+
#include <time.h>
13+
#include <dlfcn.h>
14+
#include <libgen.h>
1215
#include <jni.h>
1316
#include <sys/stat.h>
1417
#include <sys/types.h>
@@ -30,8 +33,13 @@
3033

3134
#define ENTRYPOINT_MAXLEN 128
3235
#define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x))
33-
#define LOGP(x) LOG("python", (x))
3436
#define P4A_MIN_VER 11
37+
static void LOGP(const char *fmt, ...) {
38+
va_list args;
39+
va_start(args, fmt);
40+
__android_log_vprint(ANDROID_LOG_INFO, "python", fmt, args);
41+
va_end(args);
42+
}
3543

3644
static PyObject *androidembed_log(PyObject *self, PyObject *args) {
3745
char *logstr = NULL;
@@ -69,14 +77,125 @@ int dir_exists(char *filename) {
6977
}
7078

7179
int file_exists(const char *filename) {
72-
FILE *file;
73-
if ((file = fopen(filename, "r"))) {
74-
fclose(file);
75-
return 1;
76-
}
77-
return 0;
80+
return access(filename, F_OK) == 0;
81+
}
82+
83+
static void get_dirname(const char *path, char *dir, size_t size) {
84+
strncpy(dir, path, size - 1);
85+
dir[size - 1] = '\0';
86+
char *last_slash = strrchr(dir, '/');
87+
if (last_slash) *last_slash = '\0';
88+
else dir[0] = '\0';
89+
}
90+
91+
// strip "lib" prefix and "bin.so" suffix
92+
static void get_exe_name(const char *filename, char *out, size_t size) {
93+
size_t len = strlen(filename);
94+
if (len < 7) { // too short to be valid
95+
strncpy(out, filename, size - 1);
96+
out[size - 1] = '\0';
97+
return;
98+
}
99+
100+
const char *start = filename;
101+
if (strncmp(filename, "lib", 3) == 0) start += 3;
102+
size_t start_len = strlen(start);
103+
104+
if (start_len > 6) {
105+
size_t copy_len = start_len - 6; // remove "bin.so"
106+
if (copy_len >= size) copy_len = size - 1;
107+
strncpy(out, start, copy_len);
108+
out[copy_len] = '\0';
109+
} else {
110+
strncpy(out, start, size - 1);
111+
out[size - 1] = '\0';
112+
}
113+
}
114+
115+
char *setup_symlinks() {
116+
Dl_info info;
117+
char lib_path[512];
118+
char *interpreter = NULL;
119+
120+
if (!(dladdr((void*)setup_symlinks, &info) && info.dli_fname)) {
121+
LOGP("symlinking failed: failed to get libdir");
122+
return interpreter;
123+
}
124+
125+
strncpy(lib_path, info.dli_fname, sizeof(lib_path) - 1);
126+
lib_path[sizeof(lib_path) - 1] = '\0';
127+
128+
char native_lib_dir[512];
129+
get_dirname(lib_path, native_lib_dir, sizeof(native_lib_dir));
130+
if (native_lib_dir[0] == '\0') {
131+
LOGP("symlinking failed: could not determine lib directory");
132+
return interpreter;
133+
}
134+
135+
const char *files_dir_env = getenv("ANDROID_APP_PATH");
136+
char bin_dir[512];
137+
138+
snprintf(bin_dir, sizeof(bin_dir), "%s/.bin", files_dir_env);
139+
if (mkdir(bin_dir, 0755) != 0 && errno != EEXIST) {
140+
LOGP("Failed to create .bin directory");
141+
return interpreter;
142+
}
143+
144+
DIR *dir = opendir(native_lib_dir);
145+
if (!dir) {
146+
LOGP("Failed to open native lib dir");
147+
return interpreter;
148+
}
149+
150+
struct dirent *entry;
151+
while ((entry = readdir(dir)) != NULL) {
152+
const char *name = entry->d_name;
153+
size_t len = strlen(name);
154+
155+
if (len < 7) continue;
156+
if (strcmp(name + len - 6, "bin.so") != 0) continue; // only bin.so at end
157+
158+
// get cleaned executable name
159+
char exe_name[128];
160+
get_exe_name(name, exe_name, sizeof(exe_name));
161+
162+
char src[512], dst[512];
163+
snprintf(src, sizeof(src), "%s/%s", native_lib_dir, name);
164+
snprintf(dst, sizeof(dst), "%s/%s", bin_dir, exe_name);
165+
166+
// interpreter found?
167+
if (strcmp(exe_name, "python") == 0) {
168+
interpreter = strdup(dst);
169+
}
170+
171+
struct stat st;
172+
if (lstat(dst, &st) == 0) continue; // already exists
173+
if (symlink(src, dst) == 0) {
174+
LOGP("symlink: %s -> %s", name, exe_name);
175+
} else {
176+
LOGP("Symlink failed");
177+
}
178+
}
179+
180+
closedir(dir);
181+
182+
// append bin_dir to PATH
183+
const char *old_path = getenv("PATH");
184+
char new_path[1024];
185+
if (old_path && strlen(old_path) > 0) {
186+
snprintf(new_path, sizeof(new_path), "%s:%s", old_path, bin_dir);
187+
} else {
188+
snprintf(new_path, sizeof(new_path), "%s", bin_dir);
189+
}
190+
setenv("PATH", new_path, 1);
191+
192+
// set lib path
193+
setenv("LD_LIBRARY_PATH", native_lib_dir, 1);
194+
195+
return interpreter;
78196
}
79197

198+
80199
/* int main(int argc, char **argv) { */
81200
int main(int argc, char *argv[]) {
82201

@@ -87,7 +206,10 @@ int main(int argc, char *argv[]) {
87206
int ret = 0;
88207
FILE *fd;
89208

90-
LOGP("Initializing Python for Android");
209+
LOGP("======== Initializing P4A ==========");
210+
211+
struct timespec start, end;
212+
clock_gettime(CLOCK_MONOTONIC, &start);
91213

92214
// Set a couple of built-in environment vars:
93215
setenv("P4A_BOOTSTRAP", bootstrap_name, 1); // env var to identify p4a to applications
@@ -147,10 +269,11 @@ int main(int argc, char *argv[]) {
147269
LOGP("Warning: no p4a_env_vars.txt found / failed to open!");
148270
}
149271

150-
LOGP("Changing directory to the one provided by ANDROID_ARGUMENT");
151-
LOGP(env_argument);
272+
LOGP("Changing directory to '%s'", env_argument);
152273
chdir(env_argument);
153274

275+
char *interpreter = setup_symlinks();
276+
154277
#if PY_MAJOR_VERSION < 3
155278
Py_NoSiteFlag=1;
156279
#endif
@@ -257,12 +380,20 @@ int main(int argc, char *argv[]) {
257380
"sys.path.append('%s/site-packages')",
258381
python_bundle_dir);
259382

260-
PyRun_SimpleString("import sys\n"
261-
"sys.argv = ['notaninterpreterreally']\n"
262-
"from os.path import realpath, join, dirname");
383+
PyRun_SimpleString("import sys, os\n"
384+
"from os.path import realpath, join, dirname");
385+
386+
char buf_exec[512];
387+
char buf_argv[512];
388+
snprintf(buf_exec, sizeof(buf_exec), "sys.executable = '%s'\n", interpreter);
389+
snprintf(buf_argv, sizeof(buf_argv), "sys.argv = ['%s']\n", interpreter);
390+
PyRun_SimpleString(buf_exec);
391+
PyRun_SimpleString(buf_argv);
392+
263393
PyRun_SimpleString(add_site_packages_dir);
264394
/* "sys.path.append(join(dirname(realpath(__file__)), 'site-packages'))") */
265395
PyRun_SimpleString("sys.path = ['.'] + sys.path");
396+
PyRun_SimpleString("os.environ['PYTHONPATH'] = ':'.join(sys.path)");
266397
}
267398

268399
PyRun_SimpleString(
@@ -280,23 +411,12 @@ int main(int argc, char *argv[]) {
280411
" androidembed.log(l.replace('\\x00', ''))\n"
281412
" self.__buffer = lines[-1]\n"
282413
"sys.stdout = sys.stderr = LogFile()\n"
283-
"print('Android path', sys.path)\n"
284-
"# import os\n"
285-
"# print('os.environ is', os.environ)\n"
286414
"print('Android kivy bootstrap done. __name__ is', __name__)");
287415

288416
#if PY_MAJOR_VERSION < 3
289417
PyRun_SimpleString("import site; print site.getsitepackages()\n");
290418
#endif
291419

292-
LOGP("AND: Ran string");
293-
294-
/* run it !
295-
*/
296-
LOGP("Run user program, change dir and execute entrypoint");
297-
298-
/* Get the entrypoint, search the .pyc then .py
299-
*/
300420
char *dot = strrchr(env_entrypoint, '.');
301421
char *ext = ".pyc";
302422
if (dot <= 0) {
@@ -346,6 +466,14 @@ int main(int argc, char *argv[]) {
346466
return -1;
347467
}
348468

469+
clock_gettime(CLOCK_MONOTONIC, &end);
470+
double elapsed =
471+
(end.tv_sec - start.tv_sec) +
472+
(end.tv_nsec - start.tv_nsec) / 1e9;
473+
474+
475+
LOGP("======= Initialized in %.3fs =======", elapsed);
476+
349477
/* run python !
350478
*/
351479
ret = PyRun_SimpleFile(fd, entrypoint);
@@ -361,34 +489,16 @@ int main(int argc, char *argv[]) {
361489

362490
LOGP("Python for android ended.");
363491

364-
/* Shut down: since regular shutdown causes issues sometimes
365-
(seems to be an incomplete shutdown breaking next launch)
366-
we'll use sys.exit(ret) to shutdown, since that one works.
367-
368-
Reference discussion:
369-
370-
https://github.com/kivy/kivy/pull/6107#issue-246120816
371-
*/
372-
char terminatecmd[256];
373-
snprintf(
374-
terminatecmd, sizeof(terminatecmd),
375-
"import sys; sys.exit(%d)\n", ret
376-
);
377-
PyRun_SimpleString(terminatecmd);
378-
379-
/* This should never actually be reached, but we'll leave the clean-up
380-
* here just to be safe.
381-
*/
382492
#if PY_MAJOR_VERSION < 3
383493
Py_Finalize();
384494
LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful.");
385495
#else
386-
if (Py_FinalizeEx() != 0) // properly check success on Python 3
496+
if (Py_FinalizeEx() != 0) { // properly check success on Python 3
387497
LOGP("Unexpectedly reached Py_FinalizeEx(), and got error!");
388-
else
389-
LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful.");
498+
}
390499
#endif
391500

501+
exit(ret);
392502
return ret;
393503
}
394504

0 commit comments

Comments
 (0)