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 */
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;
}

View File

@ -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<ParcelFileDescriptor> pfdMap;
private static final SparseArray<SAFProtocolUrl> safIdMap;
private static final SparseArray<SAFProtocolUrl> 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 {
/**
* <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.
*
* @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);
}
/**
* <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.
*
* @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 {
/**
* <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.
*
* @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;
}
/**

View File

@ -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

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)
{
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"
};

View File

@ -17,12 +17,21 @@
* 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() {
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;
}

View File

@ -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 */