-
-#include "config.h"
-#include "libavformat/avformat.h"
-#include "libavutil/avstring.h"
-
-#include "saf_wrapper.h"
-
-/** JNI wrapper in ffmpegkit.c */
-void closeParcelFileDescriptor(int fd);
-
-// in these wrappers, we call the original functions, so we remove the shadow defines
-#undef avio_closep
-#undef avformat_close_input
-#undef avio_open
-#undef avio_open2
-#undef avformat_open_input
-
-static int fd_read_packet(void* opaque, uint8_t* buf, int buf_size) {
- return read(*(int*)opaque, buf, buf_size);
-}
-
-static int fd_write_packet(void* opaque, uint8_t* buf, int buf_size) {
- return write(*(int*)opaque, buf, buf_size);
-}
-
-static int64_t fd_seek(void *opaque, int64_t offset, int whence) {
- int *fd = opaque;
-
- if (*fd < 0) {
- return AVERROR(EINVAL);
- }
-
- int64_t ret;
- if (whence == AVSEEK_SIZE) {
- struct stat st;
- ret = fstat(*fd, &st);
- return ret < 0 ? AVERROR(errno) : (S_ISFIFO(st.st_mode) ? 0 : st.st_size);
- }
-
- ret = lseek(*fd, offset, whence);
-
- return ret < 0 ? AVERROR(errno) : ret;
-}
-
-/*
- * returns NULL if the filename is not of expected format (e.g. 'saf:72/video.md4')
- */
-static AVIOContext *create_fd_avio_context(const char *filename, int flags) {
- int fd = -1;
- const char* fd_ptr = NULL;
-
- if (av_strstart(filename, "saf:", &fd_ptr)) {
- char *final;
- fd = strtol(fd_ptr, &final, 10);
- if (fd_ptr == final) { /* No digits found */
- fd = -1;
- }
- }
-
- if (fd >= 0) {
- int *opaque = av_mallocz(sizeof(int));
- *opaque = fd;
- int write_flag = flags & AVIO_FLAG_WRITE ? 1 : 0;
- return avio_alloc_context(av_malloc(4096), 4096, write_flag, opaque, fd_read_packet, write_flag ? fd_write_packet : NULL, fd_seek);
- }
-
- return NULL;
-}
-
-static void close_fd_avio_context(AVIOContext *ctx) {
- if (ctx) {
- int *fd = ctx->opaque;
- if (fd_seek((void*)fd, 0, AVSEEK_SIZE) >= 0) {
- closeParcelFileDescriptor(*fd);
- av_freep(&fd);
- ctx->opaque = NULL;
- }
- }
-}
-
-int android_avformat_open_input(AVFormatContext **ps, const char *filename,
- ff_const59 AVInputFormat *fmt, AVDictionary **options) {
- if (!(*ps) && !(*ps = avformat_alloc_context()))
- return AVERROR(ENOMEM);
-
- (*ps)->pb = create_fd_avio_context(filename, AVIO_FLAG_READ);
-
- return avformat_open_input(ps, filename, fmt, options);
-}
-
-int android_avio_open2(AVIOContext **s, const char *filename, int flags,
- const AVIOInterruptCB *int_cb, AVDictionary **options) {
- AVIOContext *fd_context = create_fd_avio_context(filename, flags);
-
- if (fd_context) {
- *s = fd_context;
- return 0;
- }
-
- return avio_open2(s, filename, flags, int_cb, options);
-}
-
-int android_avio_open(AVIOContext **s, const char *url, int flags) {
- return android_avio_open2(s, url, flags, NULL, NULL);
-}
-
-int android_avio_closep(AVIOContext **s) {
- close_fd_avio_context(*s);
- return avio_closep(s);
-}
-
-void android_avformat_close_input(AVFormatContext **ps) {
- if (*ps && (*ps)->pb) {
- close_fd_avio_context((*ps)->pb);
- }
- avformat_close_input(ps);
-}
diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/saf_wrapper.h b/android/ffmpeg-kit-android-lib/src/main/cpp/saf_wrapper.h
deleted file mode 100644
index e934d56..0000000
--- a/android/ffmpeg-kit-android-lib/src/main/cpp/saf_wrapper.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2020-2021 Taner Sener
- *
- * This file is part of FFmpegKit.
- *
- * FFmpegKit is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * FFmpegKit is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with FFmpegKit. If not, see .
- */
-
-#ifndef FFMPEG_KIT_SAF_WRAPPER_H
-#define FFMPEG_KIT_SAF_WRAPPER_H
-
-/*
- * These wrappers are intended to be used instead of the ffmpeg apis.
- * You don't even need to change the source to call them.
- * Instead, we redefine the public api names so that the wrapper be used.
- */
-
-int android_avio_closep(AVIOContext **s);
-#define avio_closep android_avio_closep
-
-void android_avformat_close_input(AVFormatContext **s);
-#define avformat_close_input android_avformat_close_input
-
-int android_avio_open(AVIOContext **s, const char *url, int flags);
-#define avio_open android_avio_open
-
-int android_avio_open2(AVIOContext **s, const char *url, int flags,
- const AVIOInterruptCB *int_cb, AVDictionary **options);
-#define avio_open2 android_avio_open2
-
-int android_avformat_open_input(AVFormatContext **ps, const char *filename,
- ff_const59 AVInputFormat *fmt, AVDictionary **options);
-#define avformat_open_input android_avformat_open_input
-
-#endif //FFMPEG_KIT_SAF_WRAPPER_H
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 daf13ee..b953f12 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
@@ -40,6 +40,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.StringTokenizer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -831,6 +832,20 @@ public class FFmpegKitConfig {
}
}
+ static String extractExtensionFromSafDisplayName(final String safDisplayName) {
+ String rawExtension = safDisplayName;
+ if (safDisplayName.lastIndexOf(".") >= 0) {
+ rawExtension = safDisplayName.substring(safDisplayName.lastIndexOf("."));
+ }
+ try {
+ // workaround for https://issuetracker.google.com/issues/162440528: ANDROID_CREATE_DOCUMENT generating file names like "transcode.mp3 (2)"
+ return new StringTokenizer(rawExtension, " .").nextToken();
+ } catch (final Exception e) {
+ android.util.Log.w(TAG, String.format("Failed to extract extension from saf display name: %s.%s", safDisplayName, Exceptions.getStackTraceString(e)));
+ return "raw";
+ }
+ }
+
/**
* Converts the given Structured Access Framework Uri ("content:…"
) into an
* input/output url that can be used in FFmpeg and FFprobe commands.
@@ -863,14 +878,7 @@ public class FFmpegKitConfig {
android.util.Log.e(TAG, String.format("Failed to obtain %s parcelFileDescriptor for %s.%s", openMode, uri.toString(), Exceptions.getStackTraceString(t)));
}
- // workaround for https://issuetracker.google.com/issues/162440528: ANDROID_CREATE_DOCUMENT generating file names like "transcode.mp3 (2)"
- if (displayName.lastIndexOf('.') > 0 && displayName.lastIndexOf(' ') > displayName.lastIndexOf('.')) {
- String extension = displayName.substring(displayName.lastIndexOf('.'), displayName.lastIndexOf(' '));
- displayName += extension;
- }
- // spaces can break argument list parsing, see https://github.com/alexcohn/mobile-ffmpeg/pull/1#issuecomment-688643836
- final char NBSP = (char) 0xa0;
- return "saf:" + fd + "/" + displayName.replace(' ', NBSP);
+ return "saf:" + fd + "." + FFmpegKitConfig.extractExtensionFromSafDisplayName(displayName);
}
/**
@@ -880,7 +888,7 @@ public class FFmpegKitConfig {
*
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) {
@@ -894,7 +902,7 @@ public class FFmpegKitConfig {
*
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) {
diff --git a/android/ffmpeg-kit-android-lib/src/test/java/com/arthenica/ffmpegkit/FFmpegKitConfigTest.java b/android/ffmpeg-kit-android-lib/src/test/java/com/arthenica/ffmpegkit/FFmpegKitConfigTest.java
index 3c641d1..9381238 100644
--- a/android/ffmpeg-kit-android-lib/src/test/java/com/arthenica/ffmpegkit/FFmpegKitConfigTest.java
+++ b/android/ffmpeg-kit-android-lib/src/test/java/com/arthenica/ffmpegkit/FFmpegKitConfigTest.java
@@ -32,6 +32,10 @@ import java.util.List;
*/
public class FFmpegKitConfigTest {
+ static {
+ System.setProperty("enable.ffmpeg.kit.test.mode", "true");
+ }
+
private static final String externalLibrariesCommandOutput = " configuration:\n" +
" --cross-prefix=i686-linux-android-\n" +
" --sysroot=/Users/taner/Library/Android/sdk/ndk-bundle/toolchains/ffmpeg-kit-i686/sysroot\n" +
@@ -152,6 +156,21 @@ public class FFmpegKitConfigTest {
Assert.assertEquals("https-gpl", listToPackageName(Arrays.asList("gnutls", "xvidcore")));
}
+ @Test
+ public void extractExtensionFromSafDisplayName() {
+ String extension = FFmpegKitConfig.extractExtensionFromSafDisplayName("video.mp4 (2)");
+ Assert.assertEquals("mp4", extension);
+
+ extension = FFmpegKitConfig.extractExtensionFromSafDisplayName("video file name.mp3 (2)");
+ Assert.assertEquals("mp3", extension);
+
+ extension = FFmpegKitConfig.extractExtensionFromSafDisplayName("file.mp4");
+ Assert.assertEquals("mp4", extension);
+
+ extension = FFmpegKitConfig.extractExtensionFromSafDisplayName("file name.mp4");
+ Assert.assertEquals("mp4", extension);
+ }
+
private String listToPackageName(final List externalLibraryList) {
boolean speex = externalLibraryList.contains("speex");
boolean fribidi = externalLibraryList.contains("fribidi");
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 2b1cc7e..ed37c47 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -63,7 +63,7 @@ include $(BUILD_SHARED_LIBRARY)
$(call import-module, cpu-features)
-MY_SRC_FILES := ffmpegkit.c ffprobekit.c ffmpegkit_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c saf_wrapper.c
+MY_SRC_FILES := ffmpegkit.c ffprobekit.c ffmpegkit_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
ifeq ($(TARGET_PLATFORM),android-16)
MY_SRC_FILES += android_lts_support.c
diff --git a/scripts/android/ffmpeg.sh b/scripts/android/ffmpeg.sh
index c8dd207..1ee0277 100755
--- a/scripts/android/ffmpeg.sh
+++ b/scripts/android/ffmpeg.sh
@@ -350,6 +350,12 @@ export CFLAGS="${HIGH_PRIORITY_INCLUDES} ${CFLAGS}"
ulimit -n 2048 1>>"${BASEDIR}"/build.log 2>&1
########################### CUSTOMIZATIONS #######################
+cd "${BASEDIR}" 1>>"${BASEDIR}"/build.log 2>&1 || exit 1
+git checkout android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c 1>>"${BASEDIR}"/build.log 2>&1
+cd "${BASEDIR}"/src/"${LIB_NAME}" 1>>"${BASEDIR}"/build.log 2>&1 || exit 1
+git checkout libavformat/file.c 1>>"${BASEDIR}"/build.log 2>&1
+git checkout libavformat/protocols.c 1>>"${BASEDIR}"/build.log 2>&1
+git checkout libavutil 1>>"${BASEDIR}"/build.log 2>&1
# 1. Use thread local log levels
${SED_INLINE} 's/static int av_log_level/__thread int av_log_level/g' "${BASEDIR}"/src/"${LIB_NAME}"/libavutil/log.c 1>>"${BASEDIR}"/build.log 2>&1 || exit 1
@@ -358,6 +364,19 @@ ${SED_INLINE} 's/static int av_log_level/__thread int av_log_level/g' "${BASEDIR
FFMPEG_VERSION="v$(get_user_friendly_ffmpeg_version)"
${SED_INLINE} "s/\$version/$FFMPEG_VERSION/g" "${BASEDIR}"/src/"${LIB_NAME}"/ffbuild/version.sh 1>>"${BASEDIR}"/build.log 2>&1 || exit 1
+# 3. Enable ffmpeg-kit protocols
+if [[ ${NO_FFMPEG_KIT_PROTOCOLS} == "1" ]]; then
+ ${SED_INLINE} "s/ av_set_fd_close/\/\/av_set_fd_close/g" "${BASEDIR}"/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c 1>>"${BASEDIR}"/build.log 2>&1
+ echo -e "\nINFO: Disabled custom ffmpeg-kit protocols\n" 1>>"${BASEDIR}"/build.log 2>&1
+else
+ cat ../../tools/protocols/libavformat_file.c >> libavformat/file.c
+ 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
+ echo -e "\nINFO: Enabled custom ffmpeg-kit protocols\n" 1>>"${BASEDIR}"/build.log 2>&1
+fi
+
###################################################################
./configure \
diff --git a/scripts/apple/ffmpeg.sh b/scripts/apple/ffmpeg.sh
index 0d43ae7..0287335 100755
--- a/scripts/apple/ffmpeg.sh
+++ b/scripts/apple/ffmpeg.sh
@@ -423,6 +423,9 @@ if [[ -z ${NO_WORKSPACE_CLEANUP_ffmpeg} ]]; then
fi
########################### CUSTOMIZATIONS #######################
+git checkout libavformat/file.c 1>>"${BASEDIR}"/build.log 2>&1
+git checkout libavformat/protocols.c 1>>"${BASEDIR}"/build.log 2>&1
+git checkout libavutil 1>>"${BASEDIR}"/build.log 2>&1
# 1. Workaround to prevent adding of -mdynamic-no-pic flag
${SED_INLINE} 's/check_cflags -mdynamic-no-pic && add_asflags -mdynamic-no-pic;/check_cflags -mdynamic-no-pic;/g' ./configure 1>>"${BASEDIR}"/build.log 2>&1 || exit 1
diff --git a/scripts/function-android.sh b/scripts/function-android.sh
index 632eec7..90ff9d5 100755
--- a/scripts/function-android.sh
+++ b/scripts/function-android.sh
@@ -31,7 +31,7 @@ under the prebuilt folder.\n"
echo -e "Usage: ./$COMMAND [OPTION]... [VAR=VALUE]...\n"
echo -e "Specify environment variables as VARIABLE=VALUE to override default build options.\n"
- display_help_options " -l, --lts\t\t\tbuild lts packages to support API 16+ devices" " --api-level=api\t\toverride Android api level [${API}]"
+ display_help_options " -l, --lts\t\t\tbuild lts packages to support API 16+ devices" " --api-level=api\t\toverride Android api level" " --no-ffmpeg-kit-protocols\tdisable custom ffmpeg-kit protocols (fd, saf)"
display_help_licensing
echo -e "Architectures:"
diff --git a/scripts/function.sh b/scripts/function.sh
index d5aa899..18ca390 100755
--- a/scripts/function.sh
+++ b/scripts/function.sh
@@ -584,6 +584,9 @@ display_help_options() {
if [ -n "$2" ]; then
echo -e "$2"
fi
+ if [ -n "$3" ]; then
+ echo -e "$3"
+ fi
echo -e ""
}
diff --git a/tools/protocols/libavformat_file.c b/tools/protocols/libavformat_file.c
new file mode 100644
index 0000000..6f068d4
--- /dev/null
+++ b/tools/protocols/libavformat_file.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpegKit. If not, see .
+ */
+
+#include "libavutil/file.h"
+
+static int64_t fd_seek(URLContext *h, int64_t pos, int whence)
+{
+ FileContext *c = h->priv_data;
+ int64_t ret;
+
+ if (whence == AVSEEK_SIZE) {
+ struct stat st;
+ ret = fstat(c->fd, &st);
+ return ret < 0 ? AVERROR(errno) : (S_ISFIFO(st.st_mode) ? 0 : st.st_size);
+ }
+
+ ret = lseek(c->fd, pos, whence);
+
+ return ret < 0 ? AVERROR(errno) : ret;
+}
+
+static int fd_open(URLContext *h, const char *filename, int flags)
+{
+ FileContext *c = h->priv_data;
+ int fd;
+ struct stat st;
+ char *final;
+ char *saveptr = NULL;
+ char *fd_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);
+
+ fd = strtol(fd_string, &final, 10);
+ if ((fd_string == final) || *final ) {
+ fd = -1;
+ }
+
+ c->fd = fd;
+
+ h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode);
+
+ /* Buffer writes more than the default 32k to improve throughput especially
+ * with networked file systems */
+ if (!h->is_streamed && flags & AVIO_FLAG_WRITE)
+ h->min_packet_size = h->max_packet_size = 262144;
+
+ if (c->seekable >= 0)
+ h->is_streamed = !c->seekable;
+
+ return 0;
+}
+
+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);
+
+ {
+#if HAVE_ACCESS && defined(R_OK)
+ if (access(filename, F_OK) < 0)
+ return AVERROR(errno);
+ if (mask&AVIO_FLAG_READ)
+ if (access(filename, R_OK) >= 0)
+ ret |= AVIO_FLAG_READ;
+ if (mask&AVIO_FLAG_WRITE)
+ if (access(filename, W_OK) >= 0)
+ ret |= AVIO_FLAG_WRITE;
+#else
+ struct stat st;
+# ifndef _WIN32
+ ret = stat(filename, &st);
+# else
+ ret = win32_stat(filename, &st);
+# endif
+ if (ret < 0)
+ return AVERROR(errno);
+
+ ret |= st.st_mode&S_IRUSR ? mask&AVIO_FLAG_READ : 0;
+ ret |= st.st_mode&S_IWUSR ? mask&AVIO_FLAG_WRITE : 0;
+#endif
+ }
+ return ret;
+}
+
+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);
+ if (ret < 0 && (errno == ENOTDIR
+# ifdef _WIN32
+ || errno == EINVAL
+# endif
+ ))
+ ret = unlink(filename);
+ if (ret < 0)
+ return AVERROR(errno);
+
+ return ret;
+#else
+ return AVERROR(ENOSYS);
+#endif /* HAVE_UNISTD_H */
+}
+
+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)
+ return AVERROR(errno);
+
+ return 0;
+}
+
+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);
+ } else {
+ return close(c->fd);
+ }
+}
+
+static const AVClass saf_class = {
+ .class_name = "saf",
+ .item_name = av_default_item_name,
+ .option = file_options,
+ .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,
+ .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 = &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
new file mode 100644
index 0000000..7192efb
--- /dev/null
+++ b/tools/protocols/libavutil_file.c
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpegKit. If not, see .
+ */
+
+static fd_close_function _fd_close_function = NULL;
+
+fd_close_function av_get_fd_close() {
+ return _fd_close_function;
+}
+
+void av_set_fd_close(fd_close_function close_function) {
+ _fd_close_function = close_function;
+}
diff --git a/tools/protocols/libavutil_file.h b/tools/protocols/libavutil_file.h
new file mode 100644
index 0000000..7fa12ec
--- /dev/null
+++ b/tools/protocols/libavutil_file.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpegKit. If not, see .
+ */
+
+#ifndef AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H
+#define AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H
+
+typedef int (*fd_close_function)(int);
+
+fd_close_function av_get_fd_close(void);
+
+void av_set_fd_close(fd_close_function);
+
+#endif /* AVUTIL_FILE_FFMPEG_KIT_PROTOCOLS_H */