introduce a method to generate saf protocol urls using a custom open mode, fixes #167
This commit is contained in:
parent
83d1fc6e72
commit
eda94ad613
@ -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;
|
||||
}
|
||||
|
@ -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 ≥ 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 ≥ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user