Skip to content

Commit

Permalink
introduce a method to generate saf protocol urls using a custom open …
Browse files Browse the repository at this point in the history
…mode, fixes #167
  • Loading branch information
tanersener committed Nov 7, 2021
1 parent 83d1fc6 commit eda94ad
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 89 deletions.
36 changes: 27 additions & 9 deletions android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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", "safOpen");
return JNI_FALSE;
}

safCloseMethod = (*env)->GetStaticMethodID(env, localConfigClass, "safClose", "(I)I");
if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "closeParcelFileDescriptor");
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "safClose");
return JNI_FALSE;
}

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,14 +46,53 @@
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;

/**
* <p>Configuration class of <code>FFmpegKit</code> library.
*/
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.
*/
Expand All @@ -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;

Expand All @@ -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 {
Expand All @@ -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());
Expand All @@ -125,7 +167,8 @@ protected boolean removeEldestEntry(Map.Entry<Long, Session> eldest) {
globalStatisticsCallbackFunction = null;
globalExecuteCallbackFunction = null;

pfdMap = new SparseArray<>();
safIdMap = new SparseArray<>();
safFileDescriptorMap = new SparseArray<>();
globalLogRedirectionStrategy = LogRedirectionStrategy.PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED;

NativeLoader.enableRedirection();
Expand Down Expand Up @@ -458,7 +501,7 @@ public static String registerNewFFmpegPipe(final Context context) {
}
}

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);
Expand Down Expand Up @@ -833,13 +876,16 @@ static String extractExtensionFromSafDisplayName(final String safDisplayName) {

/**
* <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 "";
Expand All @@ -855,27 +901,20 @@ private static String getSafParameter(final Context context, final Uri uri, fina
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) {
Expand All @@ -884,33 +923,70 @@ public static String getSafParameterForRead(final Context context, final Uri uri

/**
* <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) {
return getSafParameter(context, uri, "w");
}

/**
* Called by saf_wrapper from native library to close a parcel file descriptor.
* Called from native library to open an SAF protocol url.
*
* @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 int safOpen(final int safId) {
try {
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 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 fd parcel file descriptor created for a saf uri
* @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 void closeParcelFileDescriptor(final int fd) {
private static int safClose(final int fileDescriptor) {
try {
ParcelFileDescriptor pfd = pfdMap.get(fd);
if (pfd != null) {
pfd.close();
pfdMap.delete(fd);
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 file descriptor: %d.%s", fd, Exceptions.getStackTraceString(t)));
android.util.Log.e(TAG, String.format("Failed to close SAF fd: %d.%s", fileDescriptor, Exceptions.getStackTraceString(t)));
}

return 0;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion scripts/android/ffmpeg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading

0 comments on commit eda94ad

Please # to comment.