From eda94ad61338d70e027c22e6bd2802b92a20a8cb Mon Sep 17 00:00:00 2001 From: Taner Sener Date: Sun, 7 Nov 2021 14:10:34 +0000 Subject: [PATCH] introduce a method to generate saf protocol urls using a custom open mode, fixes #167 --- .../src/main/cpp/ffmpegkit.c | 36 +++-- .../arthenica/ffmpegkit/FFmpegKitConfig.java | 138 ++++++++++++++---- scripts/android/ffmpeg.sh | 2 +- tools/protocols/libavformat_file.c | 62 +++----- tools/protocols/libavutil_file.c | 19 ++- tools/protocols/libavutil_file.h | 12 +- 6 files changed, 180 insertions(+), 89 deletions(-) diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c b/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c index 789da5f..86d5da6 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c @@ -80,8 +80,11 @@ static jmethodID logMethod; /** Global reference of statistics redirection method in Java */ static jmethodID statisticsMethod; -/** Global reference of closeParcelFileDescriptor method in Java */ -static jmethodID closeParcelFileDescriptorMethod; +/** Global reference of safOpen method in Java */ +static jmethodID safOpenMethod; + +/** Global reference of safClose method in Java */ +static jmethodID safCloseMethod; /** Global reference of String class in Java */ static jclass stringClass; @@ -561,13 +564,21 @@ void *callbackThreadFunction() { } /** - * Used by fd and saf protocols; is expected to be called from a Java thread, therefore we don't need attach/detach + * Used by saf protocol; is expected to be called from a Java thread, therefore we don't need attach/detach */ -int close_parcel_file_descriptor(int fd) { +int saf_open(int safId) { JNIEnv *env = NULL; (*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6); - (*env)->CallStaticVoidMethod(env, configClass, closeParcelFileDescriptorMethod, fd); - return 0; + return (*env)->CallStaticIntMethod(env, configClass, safOpenMethod, safId); +} + +/** + * Used by saf protocol; is expected to be called from a Java thread, therefore we don't need attach/detach + */ +int saf_close(int fd) { + JNIEnv *env = NULL; + (*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6); + return (*env)->CallStaticIntMethod(env, configClass, safCloseMethod, fd); } /** @@ -615,9 +626,15 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_FALSE; } - closeParcelFileDescriptorMethod = (*env)->GetStaticMethodID(env, localConfigClass, "closeParcelFileDescriptor", "(I)V"); + safOpenMethod = (*env)->GetStaticMethodID(env, localConfigClass, "safOpen", "(I)I"); if (logMethod == NULL) { - LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "closeParcelFileDescriptor"); + LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "safOpen"); + return JNI_FALSE; + } + + safCloseMethod = (*env)->GetStaticMethodID(env, localConfigClass, "safClose", "(I)I"); + if (logMethod == NULL) { + LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "safClose"); return JNI_FALSE; } @@ -645,7 +662,8 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { redirectionEnabled = 0; - av_set_fd_close(close_parcel_file_descriptor); + av_set_saf_open(saf_open); + av_set_saf_close(saf_close); return JNI_VERSION_1_6; } diff --git a/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/FFmpegKitConfig.java b/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/FFmpegKitConfig.java index e0f0549..03b6bc4 100644 --- a/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/FFmpegKitConfig.java +++ b/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/FFmpegKitConfig.java @@ -19,6 +19,8 @@ package com.arthenica.ffmpegkit; +import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; @@ -44,7 +46,7 @@ import java.util.StringTokenizer; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** @@ -52,6 +54,45 @@ import java.util.concurrent.atomic.AtomicReference; */ public class FFmpegKitConfig { + static class SAFProtocolUrl { + private final Integer safId; + private final Uri uri; + private final String openMode; + private final ContentResolver contentResolver; + private ParcelFileDescriptor parcelFileDescriptor; + + public SAFProtocolUrl(final Integer safId, final Uri uri, final String openMode, final ContentResolver contentResolver) { + this.safId = safId; + this.uri = uri; + this.openMode = openMode; + this.contentResolver = contentResolver; + } + + public Integer getSafId() { + return safId; + } + + public Uri getUri() { + return uri; + } + + public String getOpenMode() { + return openMode; + } + + public ContentResolver getContentResolver() { + return contentResolver; + } + + public void setParcelFileDescriptor(final ParcelFileDescriptor parcelFileDescriptor) { + this.parcelFileDescriptor = parcelFileDescriptor; + } + + public ParcelFileDescriptor getParcelFileDescriptor() { + return parcelFileDescriptor; + } + } + /** * The tag used for logging. */ @@ -63,9 +104,9 @@ public class FFmpegKitConfig { static final String FFMPEG_KIT_NAMED_PIPE_PREFIX = "fk_pipe_"; /** - * Generates ids for named ffmpeg kit pipes. + * Generates ids for named ffmpeg kit pipes and saf protocol urls. */ - private static final AtomicLong pipeIndexGenerator; + private static final AtomicInteger uniqueIdGenerator; private static Level activeLogLevel; @@ -82,7 +123,8 @@ public class FFmpegKitConfig { private static LogCallback globalLogCallbackFunction; private static StatisticsCallback globalStatisticsCallbackFunction; private static ExecuteCallback globalExecuteCallbackFunction; - private static final SparseArray pfdMap; + private static final SparseArray safIdMap; + private static final SparseArray safFileDescriptorMap; private static LogRedirectionStrategy globalLogRedirectionStrategy; static { @@ -102,7 +144,7 @@ public class FFmpegKitConfig { android.util.Log.i(FFmpegKitConfig.TAG, String.format("Loaded ffmpeg-kit-%s-%s-%s-%s.", NativeLoader.loadPackageName(), NativeLoader.loadAbi(), NativeLoader.loadVersion(), NativeLoader.loadBuildDate())); - pipeIndexGenerator = new AtomicLong(1); + uniqueIdGenerator = new AtomicInteger(1); /* NATIVE LOG LEVEL IS RECEIVED ONLY ON STARTUP */ activeLogLevel = Level.from(NativeLoader.loadLogLevel()); @@ -125,7 +167,8 @@ public class FFmpegKitConfig { globalStatisticsCallbackFunction = null; globalExecuteCallbackFunction = null; - pfdMap = new SparseArray<>(); + safIdMap = new SparseArray<>(); + safFileDescriptorMap = new SparseArray<>(); globalLogRedirectionStrategy = LogRedirectionStrategy.PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED; NativeLoader.enableRedirection(); @@ -458,7 +501,7 @@ public class FFmpegKitConfig { } } - final String newFFmpegPipePath = MessageFormat.format("{0}{1}{2}{3}", pipesDir, File.separator, FFMPEG_KIT_NAMED_PIPE_PREFIX, pipeIndexGenerator.getAndIncrement()); + final String newFFmpegPipePath = MessageFormat.format("{0}{1}{2}{3}", pipesDir, File.separator, FFMPEG_KIT_NAMED_PIPE_PREFIX, uniqueIdGenerator.getAndIncrement()); // FIRST CLOSE OLD PIPES WITH THE SAME NAME closeFFmpegPipe(newFFmpegPipePath); @@ -833,13 +876,16 @@ public class FFmpegKitConfig { /** *

Converts the given Structured Access Framework Uri ("content:…") into an - * input/output url that can be used in FFmpeg and FFprobe commands. + * SAF protocol url that can be used in FFmpeg and FFprobe commands. * *

Requires API Level >= 19. On older API levels it returns an empty url. * + * @param context application context + * @param uri SAF uri + * @param openMode file mode to use as defined in {@link ContentProvider#openFile ContentProvider.openFile} * @return input/output url that can be passed to FFmpegKit or FFprobeKit */ - private static String getSafParameter(final Context context, final Uri uri, final String openMode) { + public static String getSafParameter(final Context context, final Uri uri, final String openMode) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { android.util.Log.i(TAG, String.format("getSafParameter is not supported on API Level %d", Build.VERSION.SDK_INT)); return ""; @@ -855,27 +901,20 @@ public class FFmpegKitConfig { throw t; } - final int fd; - try { - ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, openMode); - fd = parcelFileDescriptor.getFd(); - pfdMap.put(fd, parcelFileDescriptor); - } catch (final Throwable t) { - android.util.Log.e(TAG, String.format("Failed to obtain %s parcelFileDescriptor for %s.%s", openMode, uri.toString(), Exceptions.getStackTraceString(t))); - throw new IllegalArgumentException(String.format("Failed to obtain %s parcelFileDescriptor for %s.", openMode, uri.toString()), t); - } + final int safId = uniqueIdGenerator.getAndIncrement(); + safIdMap.put(safId, new SAFProtocolUrl(safId, uri, openMode, context.getContentResolver())); - return "saf:" + fd + "." + FFmpegKitConfig.extractExtensionFromSafDisplayName(displayName); + return "saf:" + safId + "." + FFmpegKitConfig.extractExtensionFromSafDisplayName(displayName); } /** *

Converts the given Structured Access Framework Uri ("content:…") into an - * input url that can be used in FFmpeg and FFprobe commands. + * SAF protocol url that can be used in FFmpeg and FFprobe commands. * *

Requires API Level ≥ 19. On older API levels it returns an empty url. * * @param context application context - * @param uri saf uri + * @param uri SAF uri * @return input url that can be passed to FFmpegKit or FFprobeKit */ public static String getSafParameterForRead(final Context context, final Uri uri) { @@ -884,12 +923,12 @@ public class FFmpegKitConfig { /** *

Converts the given Structured Access Framework Uri ("content:…") into an - * output url that can be used in FFmpeg and FFprobe commands. + * SAF protocol url that can be used in FFmpeg and FFprobe commands. * *

Requires API Level ≥ 19. On older API levels it returns an empty url. * * @param context application context - * @param uri saf uri + * @param uri SAF uri * @return output url that can be passed to FFmpegKit or FFprobeKit */ public static String getSafParameterForWrite(final Context context, final Uri uri) { @@ -897,20 +936,57 @@ public class FFmpegKitConfig { } /** - * Called by saf_wrapper from native library to close a parcel file descriptor. + * Called from native library to open an SAF protocol url. * - * @param fd parcel file descriptor created for a saf uri + * @param safId SAF id part of an SAF protocol url + * @return file descriptor created for this SAF id or 0 if an error occurs */ - private static void closeParcelFileDescriptor(final int fd) { + private static int safOpen(final int safId) { try { - ParcelFileDescriptor pfd = pfdMap.get(fd); - if (pfd != null) { - pfd.close(); - pfdMap.delete(fd); + SAFProtocolUrl safUrl = safIdMap.get(safId); + if (safUrl != null) { + final ParcelFileDescriptor parcelFileDescriptor = safUrl.getContentResolver().openFileDescriptor(safUrl.getUri(), safUrl.getOpenMode()); + safUrl.setParcelFileDescriptor(parcelFileDescriptor); + final int fd = parcelFileDescriptor.getFd(); + safFileDescriptorMap.put(fd, safUrl); + return fd; + } else { + android.util.Log.e(TAG, String.format("SAF id %d not found.", safId)); } } catch (final Throwable t) { - android.util.Log.e(TAG, String.format("Failed to close file descriptor: %d.%s", fd, Exceptions.getStackTraceString(t))); + android.util.Log.e(TAG, String.format("Failed to open SAF id: %d.%s", safId, Exceptions.getStackTraceString(t))); } + + return 0; + } + + /** + * Called from native library to close a file descriptor created for a SAF protocol url. + * + * @param fileDescriptor file descriptor that belongs to a SAF protocol url + * @return 1 if the given file descriptor is closed successfully, 0 if an error occurs + */ + private static int safClose(final int fileDescriptor) { + try { + final SAFProtocolUrl safProtocolUrl = safFileDescriptorMap.get(fileDescriptor); + if (safProtocolUrl != null) { + ParcelFileDescriptor parcelFileDescriptor = safProtocolUrl.getParcelFileDescriptor(); + if (parcelFileDescriptor != null) { + safFileDescriptorMap.delete(fileDescriptor); + safIdMap.delete(safProtocolUrl.getSafId()); + parcelFileDescriptor.close(); + return 1; + } else { + android.util.Log.e(TAG, String.format("ParcelFileDescriptor for SAF fd %d not found.", fileDescriptor)); + } + } else { + android.util.Log.e(TAG, String.format("SAF fd %d not found.", fileDescriptor)); + } + } catch (final Throwable t) { + android.util.Log.e(TAG, String.format("Failed to close SAF fd: %d.%s", fileDescriptor, Exceptions.getStackTraceString(t))); + } + + return 0; } /** diff --git a/scripts/android/ffmpeg.sh b/scripts/android/ffmpeg.sh index 2025d69..c04b9e9 100755 --- a/scripts/android/ffmpeg.sh +++ b/scripts/android/ffmpeg.sh @@ -380,7 +380,7 @@ else cat ../../tools/protocols/libavutil_file.h >> libavutil/file.h cat ../../tools/protocols/libavutil_file.c >> libavutil/file.c awk '{gsub(/ff_file_protocol;/,"ff_file_protocol;\nextern const URLProtocol ff_saf_protocol;")}1' libavformat/protocols.c > libavformat/protocols.c.tmp - awk '{gsub(/ff_file_protocol;/,"ff_file_protocol;\nextern const URLProtocol ff_fd_protocol;")}1' libavformat/protocols.c.tmp > libavformat/protocols.c + cat libavformat/protocols.c.tmp > libavformat/protocols.c echo -e "\nINFO: Enabled custom ffmpeg-kit protocols\n" 1>>"${BASEDIR}"/build.log 2>&1 fi diff --git a/tools/protocols/libavformat_file.c b/tools/protocols/libavformat_file.c index 6f068d4..ccd07d9 100644 --- a/tools/protocols/libavformat_file.c +++ b/tools/protocols/libavformat_file.c @@ -38,26 +38,35 @@ static int64_t fd_seek(URLContext *h, int64_t pos, int whence) static int fd_open(URLContext *h, const char *filename, int flags) { FileContext *c = h->priv_data; - int fd; + int saf_id; struct stat st; char *final; char *saveptr = NULL; - char *fd_string = NULL; + char *saf_id_string = NULL; char filename_backup[128]; - av_strstart(filename, "fd:", &filename); av_strstart(filename, "saf:", &filename); av_strlcpy(filename_backup, filename, FFMIN(sizeof(filename), sizeof(filename_backup))); - fd_string = av_strtok(filename_backup, ".", &saveptr); + saf_id_string = av_strtok(filename_backup, ".", &saveptr); - fd = strtol(fd_string, &final, 10); - if ((fd_string == final) || *final ) { - fd = -1; + saf_id = strtol(saf_id_string, &final, 10); + if ((saf_id_string == final) || *final ) { + saf_id = -1; } - c->fd = fd; + saf_open_function custom_saf_open = av_get_saf_open(); + if (custom_saf_open != NULL) { + int rc = custom_saf_open(saf_id); + if (rc) { + c->fd = rc; + } else { + c->fd = saf_id; + } + } else { + c->fd = saf_id; + } - h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode); + h->is_streamed = !fstat(saf_id, &st) && S_ISFIFO(st.st_mode); /* Buffer writes more than the default 32k to improve throughput especially * with networked file systems */ @@ -74,7 +83,6 @@ static int fd_check(URLContext *h, int mask) { int ret = 0; const char *filename = h->filename; - av_strstart(filename, "fd:", &filename); av_strstart(filename, "saf:", &filename); { @@ -109,7 +117,6 @@ static int fd_delete(URLContext *h) #if HAVE_UNISTD_H int ret; const char *filename = h->filename; - av_strstart(filename, "fd:", &filename); av_strstart(filename, "saf:", &filename); ret = rmdir(filename); @@ -132,9 +139,7 @@ static int fd_move(URLContext *h_src, URLContext *h_dst) { const char *filename_src = h_src->filename; const char *filename_dst = h_dst->filename; - av_strstart(filename_src, "fd:", &filename_src); av_strstart(filename_src, "saf:", &filename_src); - av_strstart(filename_dst, "fd:", &filename_dst); av_strstart(filename_dst, "saf:", &filename_dst); if (rename(filename_src, filename_dst) < 0) @@ -147,11 +152,11 @@ static int fd_close(URLContext *h) { FileContext *c = h->priv_data; - fd_close_function custom_fd_close = av_get_fd_close(); - if (custom_fd_close != NULL) { - return custom_fd_close(c->fd); + saf_close_function custom_saf_close = av_get_saf_close(); + if (custom_saf_close != NULL) { + return custom_saf_close(c->fd); } else { - return close(c->fd); + return 0; } } @@ -162,13 +167,6 @@ static const AVClass saf_class = { .version = LIBAVUTIL_VERSION_INT, }; -static const AVClass fd_class = { - .class_name = "fd", - .item_name = av_default_item_name, - .option = file_options, - .version = LIBAVUTIL_VERSION_INT, -}; - const URLProtocol ff_saf_protocol = { .name = "saf", .url_open = fd_open, @@ -184,19 +182,3 @@ const URLProtocol ff_saf_protocol = { .priv_data_class = &saf_class, .default_whitelist = "saf,crypto,data" }; - -const URLProtocol ff_fd_protocol = { - .name = "fd", - .url_open = fd_open, - .url_read = file_read, - .url_write = file_write, - .url_seek = fd_seek, - .url_close = fd_close, - .url_get_file_handle = file_get_handle, - .url_check = fd_check, - .url_delete = fd_delete, - .url_move = fd_move, - .priv_data_size = sizeof(FileContext), - .priv_data_class = &fd_class, - .default_whitelist = "fd,crypto,data" -}; diff --git a/tools/protocols/libavutil_file.c b/tools/protocols/libavutil_file.c index 7192efb..8430a1d 100644 --- a/tools/protocols/libavutil_file.c +++ b/tools/protocols/libavutil_file.c @@ -17,12 +17,21 @@ * along with FFmpegKit. If not, see . */ -static fd_close_function _fd_close_function = NULL; +static saf_open_function _saf_open_function = NULL; +static saf_close_function _saf_close_function = NULL; -fd_close_function av_get_fd_close() { - return _fd_close_function; +saf_open_function av_get_saf_open() { + return _saf_open_function; } -void av_set_fd_close(fd_close_function close_function) { - _fd_close_function = close_function; +saf_close_function av_get_saf_close() { + return _saf_close_function; +} + +void av_set_saf_open(saf_open_function open_function) { + _saf_open_function = open_function; +} + +void av_set_saf_close(saf_close_function close_function) { + _saf_close_function = close_function; } diff --git a/tools/protocols/libavutil_file.h b/tools/protocols/libavutil_file.h index 7fa12ec..897290b 100644 --- a/tools/protocols/libavutil_file.h +++ b/tools/protocols/libavutil_file.h @@ -20,10 +20,16 @@ #ifndef AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H #define AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H -typedef int (*fd_close_function)(int); +typedef int (*saf_open_function)(int); -fd_close_function av_get_fd_close(void); +typedef int (*saf_close_function)(int); -void av_set_fd_close(fd_close_function); +saf_open_function av_get_saf_open(void); + +saf_close_function av_get_saf_close(void); + +void av_set_saf_open(saf_open_function); + +void av_set_saf_close(saf_close_function); #endif /* AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H */