introduce a method to generate saf protocol urls using a custom open mode, fixes #167

This commit is contained in:
Taner Sener 2021-11-07 14:10:34 +00:00
parent 83d1fc6e72
commit eda94ad613
6 changed files with 180 additions and 89 deletions

View File

@ -80,8 +80,11 @@ static jmethodID logMethod;
/** Global reference of statistics redirection method in Java */ /** Global reference of statistics redirection method in Java */
static jmethodID statisticsMethod; static jmethodID statisticsMethod;
/** Global reference of closeParcelFileDescriptor method in Java */ /** Global reference of safOpen method in Java */
static jmethodID closeParcelFileDescriptorMethod; static jmethodID safOpenMethod;
/** Global reference of safClose method in Java */
static jmethodID safCloseMethod;
/** Global reference of String class in Java */ /** Global reference of String class in Java */
static jclass stringClass; 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; JNIEnv *env = NULL;
(*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6); (*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
(*env)->CallStaticVoidMethod(env, configClass, closeParcelFileDescriptorMethod, fd); return (*env)->CallStaticIntMethod(env, configClass, safOpenMethod, safId);
return 0; }
/**
* 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; return JNI_FALSE;
} }
closeParcelFileDescriptorMethod = (*env)->GetStaticMethodID(env, localConfigClass, "closeParcelFileDescriptor", "(I)V"); safOpenMethod = (*env)->GetStaticMethodID(env, localConfigClass, "safOpen", "(I)I");
if (logMethod == NULL) { 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; return JNI_FALSE;
} }
@ -645,7 +662,8 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
redirectionEnabled = 0; 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; return JNI_VERSION_1_6;
} }

View File

@ -19,6 +19,8 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
@ -44,7 +46,7 @@ import java.util.StringTokenizer;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
@ -52,6 +54,45 @@ import java.util.concurrent.atomic.AtomicReference;
*/ */
public class FFmpegKitConfig { 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. * The tag used for logging.
*/ */
@ -63,9 +104,9 @@ public class FFmpegKitConfig {
static final String FFMPEG_KIT_NAMED_PIPE_PREFIX = "fk_pipe_"; 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; private static Level activeLogLevel;
@ -82,7 +123,8 @@ public class FFmpegKitConfig {
private static LogCallback globalLogCallbackFunction; private static LogCallback globalLogCallbackFunction;
private static StatisticsCallback globalStatisticsCallbackFunction; private static StatisticsCallback globalStatisticsCallbackFunction;
private static ExecuteCallback globalExecuteCallbackFunction; private static ExecuteCallback globalExecuteCallbackFunction;
private static final SparseArray<ParcelFileDescriptor> pfdMap; private static final SparseArray<SAFProtocolUrl> safIdMap;
private static final SparseArray<SAFProtocolUrl> safFileDescriptorMap;
private static LogRedirectionStrategy globalLogRedirectionStrategy; private static LogRedirectionStrategy globalLogRedirectionStrategy;
static { 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())); 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 */ /* NATIVE LOG LEVEL IS RECEIVED ONLY ON STARTUP */
activeLogLevel = Level.from(NativeLoader.loadLogLevel()); activeLogLevel = Level.from(NativeLoader.loadLogLevel());
@ -125,7 +167,8 @@ public class FFmpegKitConfig {
globalStatisticsCallbackFunction = null; globalStatisticsCallbackFunction = null;
globalExecuteCallbackFunction = null; globalExecuteCallbackFunction = null;
pfdMap = new SparseArray<>(); safIdMap = new SparseArray<>();
safFileDescriptorMap = new SparseArray<>();
globalLogRedirectionStrategy = LogRedirectionStrategy.PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED; globalLogRedirectionStrategy = LogRedirectionStrategy.PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED;
NativeLoader.enableRedirection(); 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 // FIRST CLOSE OLD PIPES WITH THE SAME NAME
closeFFmpegPipe(newFFmpegPipePath); closeFFmpegPipe(newFFmpegPipePath);
@ -833,13 +876,16 @@ public class FFmpegKitConfig {
/** /**
* <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an * <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) 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.
* *
* <p>Requires API Level >= 19. On older API levels it returns an empty url. * <p>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 * @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) { 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)); android.util.Log.i(TAG, String.format("getSafParameter is not supported on API Level %d", Build.VERSION.SDK_INT));
return ""; return "";
@ -855,27 +901,20 @@ public class FFmpegKitConfig {
throw t; throw t;
} }
final int fd; final int safId = uniqueIdGenerator.getAndIncrement();
try { safIdMap.put(safId, new SAFProtocolUrl(safId, uri, openMode, context.getContentResolver()));
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);
}
return "saf:" + fd + "." + FFmpegKitConfig.extractExtensionFromSafDisplayName(displayName); return "saf:" + safId + "." + FFmpegKitConfig.extractExtensionFromSafDisplayName(displayName);
} }
/** /**
* <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an * <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) 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.
* *
* <p>Requires API Level &ge; 19. On older API levels it returns an empty url. * <p>Requires API Level &ge; 19. On older API levels it returns an empty url.
* *
* @param context application context * @param context application context
* @param uri saf uri * @param uri SAF uri
* @return input url that can be passed to FFmpegKit or FFprobeKit * @return input url that can be passed to FFmpegKit or FFprobeKit
*/ */
public static String getSafParameterForRead(final Context context, final Uri uri) { public static String getSafParameterForRead(final Context context, final Uri uri) {
@ -884,12 +923,12 @@ public class FFmpegKitConfig {
/** /**
* <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an * <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) 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.
* *
* <p>Requires API Level &ge; 19. On older API levels it returns an empty url. * <p>Requires API Level &ge; 19. On older API levels it returns an empty url.
* *
* @param context application context * @param context application context
* @param uri saf uri * @param uri SAF uri
* @return output url that can be passed to FFmpegKit or FFprobeKit * @return output url that can be passed to FFmpegKit or FFprobeKit
*/ */
public static String getSafParameterForWrite(final Context context, final Uri uri) { 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 { try {
ParcelFileDescriptor pfd = pfdMap.get(fd); SAFProtocolUrl safUrl = safIdMap.get(safId);
if (pfd != null) { if (safUrl != null) {
pfd.close(); final ParcelFileDescriptor parcelFileDescriptor = safUrl.getContentResolver().openFileDescriptor(safUrl.getUri(), safUrl.getOpenMode());
pfdMap.delete(fd); 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) { } 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;
} }
/** /**

View File

@ -380,7 +380,7 @@ else
cat ../../tools/protocols/libavutil_file.h >> libavutil/file.h cat ../../tools/protocols/libavutil_file.h >> libavutil/file.h
cat ../../tools/protocols/libavutil_file.c >> libavutil/file.c 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_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 echo -e "\nINFO: Enabled custom ffmpeg-kit protocols\n" 1>>"${BASEDIR}"/build.log 2>&1
fi fi

View File

@ -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) static int fd_open(URLContext *h, const char *filename, int flags)
{ {
FileContext *c = h->priv_data; FileContext *c = h->priv_data;
int fd; int saf_id;
struct stat st; struct stat st;
char *final; char *final;
char *saveptr = NULL; char *saveptr = NULL;
char *fd_string = NULL; char *saf_id_string = NULL;
char filename_backup[128]; char filename_backup[128];
av_strstart(filename, "fd:", &filename);
av_strstart(filename, "saf:", &filename); av_strstart(filename, "saf:", &filename);
av_strlcpy(filename_backup, filename, FFMIN(sizeof(filename), sizeof(filename_backup))); 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); saf_id = strtol(saf_id_string, &final, 10);
if ((fd_string == final) || *final ) { if ((saf_id_string == final) || *final ) {
fd = -1; 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 /* Buffer writes more than the default 32k to improve throughput especially
* with networked file systems */ * with networked file systems */
@ -74,7 +83,6 @@ static int fd_check(URLContext *h, int mask)
{ {
int ret = 0; int ret = 0;
const char *filename = h->filename; const char *filename = h->filename;
av_strstart(filename, "fd:", &filename);
av_strstart(filename, "saf:", &filename); av_strstart(filename, "saf:", &filename);
{ {
@ -109,7 +117,6 @@ static int fd_delete(URLContext *h)
#if HAVE_UNISTD_H #if HAVE_UNISTD_H
int ret; int ret;
const char *filename = h->filename; const char *filename = h->filename;
av_strstart(filename, "fd:", &filename);
av_strstart(filename, "saf:", &filename); av_strstart(filename, "saf:", &filename);
ret = rmdir(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_src = h_src->filename;
const char *filename_dst = h_dst->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_src, "saf:", &filename_src);
av_strstart(filename_dst, "fd:", &filename_dst);
av_strstart(filename_dst, "saf:", &filename_dst); av_strstart(filename_dst, "saf:", &filename_dst);
if (rename(filename_src, filename_dst) < 0) if (rename(filename_src, filename_dst) < 0)
@ -147,11 +152,11 @@ static int fd_close(URLContext *h)
{ {
FileContext *c = h->priv_data; FileContext *c = h->priv_data;
fd_close_function custom_fd_close = av_get_fd_close(); saf_close_function custom_saf_close = av_get_saf_close();
if (custom_fd_close != NULL) { if (custom_saf_close != NULL) {
return custom_fd_close(c->fd); return custom_saf_close(c->fd);
} else { } else {
return close(c->fd); return 0;
} }
} }
@ -162,13 +167,6 @@ static const AVClass saf_class = {
.version = LIBAVUTIL_VERSION_INT, .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 = { const URLProtocol ff_saf_protocol = {
.name = "saf", .name = "saf",
.url_open = fd_open, .url_open = fd_open,
@ -184,19 +182,3 @@ const URLProtocol ff_saf_protocol = {
.priv_data_class = &saf_class, .priv_data_class = &saf_class,
.default_whitelist = "saf,crypto,data" .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"
};

View File

@ -17,12 +17,21 @@
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>. * along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/ */
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() { saf_open_function av_get_saf_open() {
return _fd_close_function; return _saf_open_function;
} }
void av_set_fd_close(fd_close_function close_function) { saf_close_function av_get_saf_close() {
_fd_close_function = close_function; 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;
} }

View File

@ -20,10 +20,16 @@
#ifndef AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H #ifndef AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H
#define 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 */ #endif /* AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H */