implement custom ffmpeg-kit protocols for android, fixes #39

This commit is contained in:
Taner Sener 2021-05-14 22:48:53 +01:00
parent de101cf731
commit c356b059c7
15 changed files with 332 additions and 201 deletions

View File

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

View File

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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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";
}
}
/**
* <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.
@ -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 {
* <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) {
@ -894,7 +902,7 @@ public class FFmpegKitConfig {
* <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) {

View File

@ -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<String> externalLibraryList) {
boolean speex = externalLibraryList.contains("speex");
boolean fribidi = externalLibraryList.contains("fribidi");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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"
};

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 */