From c356b059c742a8a6f42eacfbf0c3983a308867a6 Mon Sep 17 00:00:00 2001 From: Taner Sener Date: Fri, 14 May 2021 22:48:53 +0100 Subject: [PATCH] implement custom ffmpeg-kit protocols for android, fixes #39 --- android.sh | 3 + .../src/main/cpp/ffmpegkit.c | 8 +- .../src/main/cpp/fftools_cmdutils.h | 2 - .../src/main/cpp/saf_wrapper.c | 139 ------------ .../src/main/cpp/saf_wrapper.h | 46 ---- .../arthenica/ffmpegkit/FFmpegKitConfig.java | 28 ++- .../ffmpegkit/FFmpegKitConfigTest.java | 19 ++ android/jni/Android.mk | 2 +- scripts/android/ffmpeg.sh | 19 ++ scripts/apple/ffmpeg.sh | 3 + scripts/function-android.sh | 2 +- scripts/function.sh | 3 + tools/protocols/libavformat_file.c | 202 ++++++++++++++++++ tools/protocols/libavutil_file.c | 28 +++ tools/protocols/libavutil_file.h | 29 +++ 15 files changed, 332 insertions(+), 201 deletions(-) delete mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/saf_wrapper.c delete mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/saf_wrapper.h create mode 100644 tools/protocols/libavformat_file.c create mode 100644 tools/protocols/libavutil_file.c create mode 100644 tools/protocols/libavutil_file.h diff --git a/android.sh b/android.sh index 787fd25..0b1c84d 100755 --- a/android.sh +++ b/android.sh @@ -121,6 +121,9 @@ while [ ! $# -eq 0 ]; do export API=${API_LEVEL} ;; + --no-ffmpeg-kit-protocols) + export NO_FFMPEG_KIT_PROTOCOLS="1" + ;; *) print_unknown_option "$1" ;; diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c b/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c index c99b141..1a28f12 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c @@ -25,6 +25,7 @@ #include "config.h" #include "libavcodec/jni.h" #include "libavutil/bprint.h" +#include "libavutil/file.h" #include "fftools_ffmpeg.h" #include "ffmpegkit.h" #include "ffprobekit.h" @@ -560,12 +561,13 @@ void *callbackThreadFunction() { } /** - * Used by saf_wrapper; is expected to be called from a Java thread, therefore we don't need attach/detach + * Used by fd and saf protocols; is expected to be called from a Java thread, therefore we don't need attach/detach */ -void closeParcelFileDescriptor(int fd) { +int close_parcel_file_descriptor(int fd) { JNIEnv *env = NULL; (*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6); (*env)->CallStaticVoidMethod(env, configClass, closeParcelFileDescriptorMethod, fd); + return 0; } /** @@ -643,6 +645,8 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { redirectionEnabled = 0; + av_set_fd_close(close_parcel_file_descriptor); + return JNI_VERSION_1_6; } diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_cmdutils.h b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_cmdutils.h index 6ec4425..fe952a5 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_cmdutils.h +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_cmdutils.h @@ -51,8 +51,6 @@ #include "libavformat/avformat.h" #include "libswscale/swscale.h" -#include "saf_wrapper.h" - #ifdef _WIN32 #undef main /* We don't want SDL to override our main() */ #endif diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/saf_wrapper.c b/android/ffmpeg-kit-android-lib/src/main/cpp/saf_wrapper.c deleted file mode 100644 index 772cf08..0000000 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/saf_wrapper.c +++ /dev/null @@ -1,139 +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 . - */ - -#include -#include -#include - -#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 */