From 50b13ca2e1e280f355845331435b91d10a8abc52 Mon Sep 17 00:00:00 2001 From: "Sue N. Cooper" Date: Thu, 3 Aug 2023 08:32:35 +0300 Subject: [PATCH] upgrade ffmpeg to v6.0 --- android/README.md | 2 +- android/ffmpeg-kit-android-lib/Doxyfile | 2 +- android/ffmpeg-kit-android-lib/build.gradle | 8 +- .../ffmpeg-kit-android-lib/consumer-rules.pro | 2 +- .../src/main/cpp/ffmpegkit.c | 8 +- .../src/main/cpp/ffmpegkit.h | 2 +- .../src/main/cpp/fftools_cmdutils.c | 46 +- .../src/main/cpp/fftools_cmdutils.h | 25 +- .../src/main/cpp/fftools_ffmpeg.c | 2768 +++++++---------- .../src/main/cpp/fftools_ffmpeg.h | 370 ++- .../src/main/cpp/fftools_ffmpeg_demux.c | 1148 +++++++ .../src/main/cpp/fftools_ffmpeg_filter.c | 287 +- .../src/main/cpp/fftools_ffmpeg_hw.c | 27 +- .../src/main/cpp/fftools_ffmpeg_mux.c | 680 +++- .../src/main/cpp/fftools_ffmpeg_mux.h | 165 + .../src/main/cpp/fftools_ffmpeg_mux_init.c | 2414 ++++++++++++++ .../src/main/cpp/fftools_ffmpeg_opt.c | 2692 ++-------------- .../src/main/cpp/fftools_ffprobe.c | 119 +- .../src/main/cpp/fftools_objpool.c | 145 + .../src/main/cpp/fftools_objpool.h | 50 + .../src/main/cpp/fftools_opt_common.c | 9 +- .../src/main/cpp/fftools_sync_queue.c | 462 +++ .../src/main/cpp/fftools_sync_queue.h | 122 + .../src/main/cpp/fftools_thread_queue.c | 259 ++ .../src/main/cpp/fftools_thread_queue.h | 94 + .../arthenica/ffmpegkit/FFmpegKitConfig.java | 2 +- .../com/arthenica/ffmpegkit/NativeLoader.java | 2 +- .../com/arthenica/ffmpegkit/Statistics.java | 8 +- android/jni/Android.mk | 2 +- apple/Doxyfile | 2 +- apple/README.md | 6 +- apple/configure.ac | 4 +- apple/src/FFmpegKitConfig.m | 14 +- apple/src/Makefile.am | 13 +- apple/src/Makefile.in | 77 +- apple/src/Statistics.h | 4 +- apple/src/Statistics.m | 6 +- apple/src/fftools_cmdutils.c | 44 +- apple/src/fftools_cmdutils.h | 23 +- apple/src/fftools_ffmpeg.c | 2766 +++++++--------- apple/src/fftools_ffmpeg.h | 368 ++- apple/src/fftools_ffmpeg_demux.c | 1148 +++++++ apple/src/fftools_ffmpeg_filter.c | 287 +- apple/src/fftools_ffmpeg_hw.c | 25 +- apple/src/fftools_ffmpeg_mux.c | 677 +++- apple/src/fftools_ffmpeg_mux.h | 165 + apple/src/fftools_ffmpeg_mux_init.c | 2414 ++++++++++++++ apple/src/fftools_ffmpeg_opt.c | 2690 ++-------------- apple/src/fftools_ffprobe.c | 117 +- apple/src/fftools_objpool.c | 145 + apple/src/fftools_objpool.h | 50 + apple/src/fftools_opt_common.c | 9 +- apple/src/fftools_sync_queue.c | 462 +++ apple/src/fftools_sync_queue.h | 122 + apple/src/fftools_thread_queue.c | 259 ++ apple/src/fftools_thread_queue.h | 94 + linux/Doxyfile | 2 +- linux/configure.ac | 7 +- linux/src/FFmpegKit.cpp | 5 +- linux/src/FFmpegKitConfig.cpp | 16 +- linux/src/FFmpegKitConfig.h | 2 +- linux/src/FFprobeKit.cpp | 1 - linux/src/Makefile.am | 13 +- linux/src/Makefile.in | 77 +- linux/src/Statistics.cpp | 4 +- linux/src/Statistics.h | 6 +- linux/src/fftools_cmdutils.c | 46 +- linux/src/fftools_cmdutils.h | 25 +- linux/src/fftools_ffmpeg.c | 2766 +++++++--------- linux/src/fftools_ffmpeg.h | 370 ++- linux/src/fftools_ffmpeg_demux.c | 1148 +++++++ linux/src/fftools_ffmpeg_filter.c | 287 +- linux/src/fftools_ffmpeg_hw.c | 27 +- linux/src/fftools_ffmpeg_mux.c | 680 +++- linux/src/fftools_ffmpeg_mux.h | 165 + linux/src/fftools_ffmpeg_mux_init.c | 2414 ++++++++++++++ linux/src/fftools_ffmpeg_opt.c | 2690 ++-------------- linux/src/fftools_ffprobe.c | 119 +- linux/src/fftools_objpool.c | 145 + linux/src/fftools_objpool.h | 50 + linux/src/fftools_opt_common.c | 9 +- linux/src/fftools_sync_queue.c | 462 +++ linux/src/fftools_sync_queue.h | 122 + linux/src/fftools_thread_queue.c | 259 ++ linux/src/fftools_thread_queue.h | 94 + scripts/android/libiconv.sh | 2 +- scripts/source.sh | 4 +- tools/android/build.gradle | 8 +- tools/android/build.lts.gradle | 8 +- tools/protocols/libavformat_file.c | 24 +- 90 files changed, 22557 insertions(+), 13411 deletions(-) create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_demux.c create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux.h create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux_init.c create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_objpool.c create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_objpool.h create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_sync_queue.c create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_sync_queue.h create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_thread_queue.c create mode 100644 android/ffmpeg-kit-android-lib/src/main/cpp/fftools_thread_queue.h create mode 100644 apple/src/fftools_ffmpeg_demux.c create mode 100644 apple/src/fftools_ffmpeg_mux.h create mode 100644 apple/src/fftools_ffmpeg_mux_init.c create mode 100644 apple/src/fftools_objpool.c create mode 100644 apple/src/fftools_objpool.h create mode 100644 apple/src/fftools_sync_queue.c create mode 100644 apple/src/fftools_sync_queue.h create mode 100644 apple/src/fftools_thread_queue.c create mode 100644 apple/src/fftools_thread_queue.h create mode 100644 linux/src/fftools_ffmpeg_demux.c create mode 100644 linux/src/fftools_ffmpeg_mux.h create mode 100644 linux/src/fftools_ffmpeg_mux_init.c create mode 100644 linux/src/fftools_objpool.c create mode 100644 linux/src/fftools_objpool.h create mode 100644 linux/src/fftools_sync_queue.c create mode 100644 linux/src/fftools_sync_queue.h create mode 100644 linux/src/fftools_thread_queue.c create mode 100644 linux/src/fftools_thread_queue.h diff --git a/android/README.md b/android/README.md index 40db041..c86b502 100644 --- a/android/README.md +++ b/android/README.md @@ -77,7 +77,7 @@ All libraries created by `android.sh` can be found under the `prebuilt` director } dependencies { - implementation 'com.arthenica:ffmpeg-kit-full:5.1' + implementation 'com.arthenica:ffmpeg-kit-full:6.0' } ``` diff --git a/android/ffmpeg-kit-android-lib/Doxyfile b/android/ffmpeg-kit-android-lib/Doxyfile index ad2f799..ef4d4f1 100644 --- a/android/ffmpeg-kit-android-lib/Doxyfile +++ b/android/ffmpeg-kit-android-lib/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "FFmpegKit Android API" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 5.1 +PROJECT_NUMBER = 6.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/android/ffmpeg-kit-android-lib/build.gradle b/android/ffmpeg-kit-android-lib/build.gradle index cf35c13..2917a43 100644 --- a/android/ffmpeg-kit-android-lib/build.gradle +++ b/android/ffmpeg-kit-android-lib/build.gradle @@ -2,14 +2,14 @@ apply plugin: 'com.android.library' android { namespace 'com.arthenica.ffmpegkit' - compileSdk 31 + compileSdk 33 ndkVersion "22.1.7171670" defaultConfig { minSdk 24 - targetSdk 31 - versionCode 240510 - versionName "5.1" + targetSdk 33 + versionCode 240600 + versionName "6.0" project.archivesBaseName = "ffmpeg-kit" consumerProguardFiles "consumer-rules.pro" } diff --git a/android/ffmpeg-kit-android-lib/consumer-rules.pro b/android/ffmpeg-kit-android-lib/consumer-rules.pro index ac4153e..2810109 100644 --- a/android/ffmpeg-kit-android-lib/consumer-rules.pro +++ b/android/ffmpeg-kit-android-lib/consumer-rules.pro @@ -1,7 +1,7 @@ -keep class com.arthenica.ffmpegkit.FFmpegKitConfig { native ; void log(long, int, byte[]); - void statistics(long, int, float, float, long , int, double, double); + void statistics(long, int, float, float, long , double, double, double); int safOpen(int); int safClose(int); } 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 585659d..dddcf93 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c @@ -45,7 +45,7 @@ struct CallbackData { float statisticsFps; // statistics fps float statisticsQuality; // statistics quality int64_t statisticsSize; // statistics size - int statisticsTime; // statistics time + double statisticsTime; // statistics time double statisticsBitrate; // statistics bitrate double statisticsSpeed; // statistics speed @@ -312,7 +312,7 @@ void logCallbackDataAdd(int level, AVBPrint *data) { /** * Adds statistics data to the end of callback data list. */ -void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) { +void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, double time, double bitrate, double speed) { // CREATE DATA STRUCT FIRST struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData)); @@ -491,7 +491,7 @@ void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, v * @param bitrate output bit rate in kbits/s * @param speed processing speed = processed duration / operation duration */ -void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) { +void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, double time, double bitrate, double speed) { statisticsCallbackDataAdd(frameNumber, fps, quality, size, time, bitrate, speed); } @@ -644,7 +644,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_FALSE; } - statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(JIFFJIDD)V"); + statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(JIFFJDDD)V"); if (statisticsMethod == NULL) { LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "statistics"); return JNI_FALSE; diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.h b/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.h index eb7300c..dde9b19 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.h +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.h @@ -27,7 +27,7 @@ #include "libavutil/ffversion.h" /** Library version string */ -#define FFMPEG_KIT_VERSION "5.1" +#define FFMPEG_KIT_VERSION "6.0" /** Defines tag used for Android logging. */ #define LIB_NAME "ffmpeg-kit" diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_cmdutils.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_cmdutils.c index a2d0116..168abab 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_cmdutils.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_cmdutils.c @@ -1,7 +1,8 @@ /* * Various utilities for command line tools * Copyright (c) 2000-2003 Fabrice Bellard - * Copyright (c) 2018 Taner Sener + * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -25,6 +26,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -129,11 +136,18 @@ void register_exit(void (*cb)(int ret)) program_exit = cb; } +void report_and_exit(int ret) +{ + av_log(NULL, AV_LOG_FATAL, "%s\n", av_err2str(ret)); + exit_program(AVUNERROR(ret)); +} + void exit_program(int ret) { if (program_exit) program_exit(ret); + // FFmpegKit // exit disabled and replaced with longjmp, exit value stored in longjmp_value // exit(ret); longjmp_value = ret; @@ -696,7 +710,7 @@ static void init_parse_context(OptionParseContext *octx, octx->nb_groups = nb_groups; octx->groups = av_calloc(octx->nb_groups, sizeof(*octx->groups)); if (!octx->groups) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); for (i = 0; i < octx->nb_groups; i++) octx->groups[i].group_def = &groups[i]; @@ -843,12 +857,7 @@ do { \ void print_error(const char *filename, int err) { - char errbuf[128]; - const char *errbuf_ptr = errbuf; - - if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) - errbuf_ptr = strerror(AVUNERROR(err)); - av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, errbuf_ptr); + av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, av_err2str(err)); } int read_yesno(void) @@ -971,7 +980,7 @@ AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, break; } - while ((t = av_dict_get(opts, "", t, AV_DICT_IGNORE_SUFFIX))) { + while ((t = av_dict_iterate(opts, t))) { const AVClass *priv_class; char *p = strchr(t->key, ':'); @@ -1009,11 +1018,8 @@ AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, if (!s->nb_streams) return NULL; opts = av_calloc(s->nb_streams, sizeof(*opts)); - if (!opts) { - av_log(NULL, AV_LOG_ERROR, - "Could not alloc memory for stream options.\n"); - exit_program(1); - } + if (!opts) + report_and_exit(AVERROR(ENOMEM)); for (i = 0; i < s->nb_streams; i++) opts[i] = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id, s, s->streams[i], NULL); @@ -1028,10 +1034,8 @@ void *grow_array(void *array, int elem_size, int *size, int new_size) } if (*size < new_size) { uint8_t *tmp = av_realloc_array(array, new_size, elem_size); - if (!tmp) { - av_log(NULL, AV_LOG_ERROR, "Could not alloc buffer.\n"); - exit_program(1); - } + if (!tmp) + report_and_exit(AVERROR(ENOMEM)); memset(tmp + *size*elem_size, 0, (new_size-*size) * elem_size); *size = new_size; return tmp; @@ -1044,10 +1048,8 @@ void *allocate_array_elem(void *ptr, size_t elem_size, int *nb_elems) void *new_elem; if (!(new_elem = av_mallocz(elem_size)) || - av_dynarray_add_nofree(ptr, nb_elems, new_elem) < 0) { - av_log(NULL, AV_LOG_ERROR, "Could not alloc buffer.\n"); - exit_program(1); - } + av_dynarray_add_nofree(ptr, nb_elems, new_elem) < 0) + report_and_exit(AVERROR(ENOMEM)); return new_elem; } 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 7d305ee..b925cf0 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 @@ -1,7 +1,8 @@ /* * Various utilities for command line tools * copyright (c) 2003 Fabrice Bellard - * copyright (c) 2018 Taner Sener + * copyright (c) 2018-2022 Taner Sener + * copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -25,6 +26,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -95,6 +102,17 @@ extern __thread int find_stream_info; */ void register_exit(void (*cb)(int ret)); +/** + * Reports an error corresponding to the provided + * AVERROR code and calls exit_program() with the + * corresponding POSIX error code. + * @note ret must be an AVERROR-value of a POSIX error code + * (i.e. AVERROR(EFOO) and not AVERROR_FOO). + * library functions can return both, so call this only + * with AVERROR(EFOO) of your own. + */ +void report_and_exit(int ret) av_noreturn; + /** * Wraps exit with a program-specific cleanup routine. */ @@ -232,11 +250,6 @@ void show_help_children(const AVClass *clazz, int flags); void show_help_default_ffmpeg(const char *opt, const char *arg); void show_help_default_ffprobe(const char *opt, const char *arg); -/** - * Generic -h handler common to all fftools. - */ -int show_help(void *optctx, const char *opt, const char *arg); - /** * Parse the command line arguments. * diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg.c index 1138fa4..731128f 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2000-2003 Fabrice Bellard - * Copyright (c) 2018 Taner Sener + * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -29,6 +30,15 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - vstats_file, received_sigterm and received_nb_signals updated as thread-local + * - forward_report method signature updated + * - time field in report_callback/forward_report/set_report_callback updated as double + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -160,19 +170,18 @@ #include "fftools_ffmpeg.h" #include "fftools_cmdutils.h" +#include "fftools_sync_queue.h" #include "libavutil/avassert.h" -static FILE *vstats_file; +static __thread FILE *vstats_file; -const char *const forced_keyframes_const_names[] = { - "n", - "n_forced", - "prev_forced_n", - "prev_forced_t", - "t", - NULL -}; +// optionally attached as opaque_ref to decoded AVFrames +typedef struct FrameData { + uint64_t idx; + int64_t pts; + AVRational tb; +} FrameData; typedef struct BenchmarkTimeStamps { int64_t real_usec; @@ -180,6 +189,7 @@ typedef struct BenchmarkTimeStamps { int64_t sys_usec; } BenchmarkTimeStamps; +static int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt); static BenchmarkTimeStamps get_benchmark_time_stamps(void); static int64_t getmaxrss(void); static int ifilter_has_all_input_formats(FilterGraph *fg); @@ -190,20 +200,12 @@ __thread int64_t nb_frames_drop = 0; __thread int64_t decode_error_stat[2]; __thread unsigned nb_output_dumped = 0; -__thread int want_sdp = 1; - __thread BenchmarkTimeStamps current_time; __thread AVIOContext *progress_avio = NULL; -__thread uint8_t *subtitle_out; - -__thread InputStream **input_streams = NULL; -__thread int nb_input_streams = 0; __thread InputFile **input_files = NULL; __thread int nb_input_files = 0; -__thread OutputStream **output_streams = NULL; -__thread int nb_output_streams = 0; __thread OutputFile **output_files = NULL; __thread int nb_output_files = 0; @@ -215,7 +217,7 @@ __thread int64_t keyboard_last_time = 0; __thread int first_report = 1; __thread int qp_histogram[52]; -void (*report_callback)(int, float, float, int64_t, int, double, double) = NULL; +void (*report_callback)(int, float, float, int64_t, double, double, double) = NULL; extern int opt_map(void *optctx, const char *opt, const char *arg); extern int opt_map_channel(void *optctx, const char *opt, const char *arg); @@ -263,7 +265,6 @@ extern int opt_data_codec(void *optctx, const char *opt, const char *arg); extern int opt_init_hw_device(void *optctx, const char *opt, const char *arg); extern int opt_filter_hw_device(void *optctx, const char *opt, const char *arg); extern int opt_filter_threads(void *optctx, const char *opt, const char *arg); -extern __thread int input_sync; extern __thread int file_overwrite; extern __thread int no_file_overwrite; extern __thread int do_psnr; @@ -278,10 +279,6 @@ __thread struct termios oldtty; __thread int restore_tty; #endif -#if HAVE_THREADS -static void free_input_threads(void); -#endif - extern volatile int handleSIGQUIT; extern volatile int handleSIGINT; extern volatile int handleSIGTERM; @@ -388,7 +385,7 @@ void sub2video_update(InputStream *ist, int64_t heartbeat_pts, AVSubtitle *sub) num_rects = 0; } if (sub2video_get_blank_frame(ist) < 0) { - av_log(ist->dec_ctx, AV_LOG_ERROR, + av_log(NULL, AV_LOG_ERROR, "Impossible to get a blank canvas.\n"); return; } @@ -412,7 +409,7 @@ static void sub2video_heartbeat(InputStream *ist, int64_t pts) video frames could be accumulating in the filter graph while a filter (possibly overlay) is desperately waiting for a subtitle frame. */ for (i = 0; i < infile->nb_streams; i++) { - InputStream *ist2 = input_streams[infile->ist_index + i]; + InputStream *ist2 = infile->streams[i]; if (!ist2->sub2video.frame) continue; /* subtitles seem to be usually muxed ahead of other streams; @@ -463,20 +460,32 @@ void term_exit(void) term_exit_sigsafe(); } -static volatile int received_sigterm = 0; -static volatile int received_nb_signals = 0; +__thread int received_sigterm = 0; +__thread int received_nb_signals = 0; __thread atomic_int transcode_init_done = ATOMIC_VAR_INIT(0); __thread int ffmpeg_exited = 0; __thread int main_ffmpeg_return_code = 0; __thread int64_t copy_ts_first_pts = AV_NOPTS_VALUE; extern __thread int longjmp_value; +extern __thread int want_sdp; +struct EncStatsFile; +extern __thread struct EncStatsFile *enc_stats_files; +extern __thread int nb_enc_stats_files; static void sigterm_handler(int sig) { + // int ret; received_sigterm = sig; received_nb_signals++; term_exit_sigsafe(); + // FFmpegKit - Hard Exit Disabled + // if(received_nb_signals > 3) { + // ret = write(2/*STDERR_FILENO*/, "Received > 3 system signals, hard exiting\n", + // strlen("Received > 3 system signals, hard exiting\n")); + // if (ret < 0) { /* Do nothing */ }; + // exit(123); + // } } #if HAVE_SETCONSOLECTRLHANDLER @@ -695,72 +704,12 @@ static void ffmpeg_cleanup(int ret) } av_freep(&filtergraphs); - av_freep(&subtitle_out); - /* close files */ for (i = 0; i < nb_output_files; i++) of_close(&output_files[i]); - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!ost) - continue; - - av_bsf_free(&ost->bsf_ctx); - - av_frame_free(&ost->filtered_frame); - av_frame_free(&ost->last_frame); - av_packet_free(&ost->pkt); - av_dict_free(&ost->encoder_opts); - - av_freep(&ost->forced_keyframes); - av_expr_free(ost->forced_keyframes_pexpr); - av_freep(&ost->avfilter); - av_freep(&ost->logfile_prefix); - - av_freep(&ost->audio_channels_map); - ost->audio_channels_mapped = 0; - - av_dict_free(&ost->sws_dict); - av_dict_free(&ost->swr_opts); - - avcodec_free_context(&ost->enc_ctx); - avcodec_parameters_free(&ost->ref_par); - - if (ost->muxing_queue) { - AVPacket *pkt; - while (av_fifo_read(ost->muxing_queue, &pkt, 1) >= 0) - av_packet_free(&pkt); - av_fifo_freep2(&ost->muxing_queue); - } - - av_freep(&output_streams[i]); - } -#if HAVE_THREADS - free_input_threads(); -#endif - for (i = 0; i < nb_input_files; i++) { - avformat_close_input(&input_files[i]->ctx); - av_packet_free(&input_files[i]->pkt); - av_freep(&input_files[i]); - } - for (i = 0; i < nb_input_streams; i++) { - InputStream *ist = input_streams[i]; - - av_frame_free(&ist->decoded_frame); - av_packet_free(&ist->pkt); - av_dict_free(&ist->decoder_opts); - avsubtitle_free(&ist->prev_sub.subtitle); - av_frame_free(&ist->sub2video.frame); - av_freep(&ist->filters); - av_freep(&ist->hwaccel_device); - av_freep(&ist->dts_buffer); - - avcodec_free_context(&ist->dec_ctx); - - av_freep(&input_streams[i]); - } + for (i = 0; i < nb_input_files; i++) + ifile_close(&input_files[i]); if (vstats_file) { if (fclose(vstats_file)) @@ -769,11 +718,11 @@ static void ffmpeg_cleanup(int ret) av_err2str(AVERROR(errno))); } av_freep(&vstats_filename); + of_enc_stats_close(); + av_freep(&filter_nbthreads); - av_freep(&input_streams); av_freep(&input_files); - av_freep(&output_streams); av_freep(&output_files); uninit_opts(); @@ -792,11 +741,45 @@ static void ffmpeg_cleanup(int ret) ffmpeg_exited = 1; } +/* iterate over all output streams in all output files; + * pass NULL to start iteration */ +static OutputStream *ost_iter(OutputStream *prev) +{ + int of_idx = prev ? prev->file_index : 0; + int ost_idx = prev ? prev->index + 1 : 0; + + for (; of_idx < nb_output_files; of_idx++) { + OutputFile *of = output_files[of_idx]; + if (ost_idx < of->nb_streams) + return of->streams[ost_idx]; + + ost_idx = 0; + } + + return NULL; +} + +InputStream *ist_iter(InputStream *prev) +{ + int if_idx = prev ? prev->file_index : 0; + int ist_idx = prev ? prev->st->index + 1 : 0; + + for (; if_idx < nb_input_files; if_idx++) { + InputFile *f = input_files[if_idx]; + if (ist_idx < f->nb_streams) + return f->streams[ist_idx]; + + ist_idx = 0; + } + + return NULL; +} + void remove_avoptions(AVDictionary **a, AVDictionary *b) { const AVDictionaryEntry *t = NULL; - while ((t = av_dict_get(b, "", t, AV_DICT_IGNORE_SUFFIX))) { + while ((t = av_dict_iterate(b, t))) { av_dict_set(a, t->key, NULL, AV_DICT_MATCH_CASE); } } @@ -839,59 +822,18 @@ static void update_benchmark(const char *fmt, ...) static void close_output_stream(OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - AVRational time_base = ost->stream_copy ? ost->mux_timebase : ost->enc_ctx->time_base; - ost->finished |= ENCODER_FINISHED; - if (of->shortest) { - int64_t end = av_rescale_q(ost->sync_opts - ost->first_pts, time_base, AV_TIME_BASE_Q); - of->recording_time = FFMIN(of->recording_time, end); - } -} -/* - * Send a single packet to the output, applying any bitstream filters - * associated with the output stream. This may result in any number - * of packets actually being written, depending on what bitstream - * filters are applied. The supplied packet is consumed and will be - * blank (as if newly-allocated) when this function returns. - * - * If eof is set, instead indicate EOF to all bitstream filters and - * therefore flush any delayed packets to the output. A blank packet - * must be supplied in this case. - */ -static void output_packet(OutputFile *of, AVPacket *pkt, - OutputStream *ost, int eof) -{ - int ret = 0; - - /* apply the output bitstream filters */ - if (ost->bsf_ctx) { - ret = av_bsf_send_packet(ost->bsf_ctx, eof ? NULL : pkt); - if (ret < 0) - goto finish; - while ((ret = av_bsf_receive_packet(ost->bsf_ctx, pkt)) >= 0) - of_write_packet(of, pkt, ost, 0); - if (ret == AVERROR(EAGAIN)) - ret = 0; - } else if (!eof) - of_write_packet(of, pkt, ost, 0); - -finish: - if (ret < 0 && ret != AVERROR_EOF) { - av_log(NULL, AV_LOG_ERROR, "Error applying bitstream filters to an output " - "packet for stream #%d:%d.\n", ost->file_index, ost->index); - if(exit_on_error) - exit_program(1); - } + if (ost->sq_idx_encode >= 0) + sq_send(of->sq_encode, ost->sq_idx_encode, SQFRAME(NULL)); } -static int check_recording_time(OutputStream *ost) +static int check_recording_time(OutputStream *ost, int64_t ts, AVRational tb) { OutputFile *of = output_files[ost->file_index]; if (of->recording_time != INT64_MAX && - av_compare_ts(ost->sync_opts - ost->first_pts, ost->enc_ctx->time_base, of->recording_time, - AV_TIME_BASE_Q) >= 0) { + av_compare_ts(ts, tb, of->recording_time, AV_TIME_BASE_Q) >= 0) { close_output_stream(ost); return 0; } @@ -902,38 +844,36 @@ static double adjust_frame_pts_to_encoder_tb(OutputFile *of, OutputStream *ost, AVFrame *frame) { double float_pts = AV_NOPTS_VALUE; // this is identical to frame.pts but with higher precision - AVCodecContext *enc = ost->enc_ctx; - if (!frame || frame->pts == AV_NOPTS_VALUE || - !enc || !ost->filter || !ost->filter->graph->graph) - goto early_exit; + const int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? + 0 : of->start_time; - { - AVFilterContext *filter = ost->filter->filter; + AVCodecContext *const enc = ost->enc_ctx; - int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; - AVRational filter_tb = av_buffersink_get_time_base(filter); - AVRational tb = enc->time_base; - int extra_bits = av_clip(29 - av_log2(tb.den), 0, 16); - - tb.den <<= extra_bits; - float_pts = - av_rescale_q(frame->pts, filter_tb, tb) - - av_rescale_q(start_time, AV_TIME_BASE_Q, tb); - float_pts /= 1 << extra_bits; - // avoid exact midoints to reduce the chance of rounding differences, this can be removed in case the fps code is changed to work with integers - float_pts += FFSIGN(float_pts) * 1.0 / (1<<17); + AVRational tb = enc->time_base; + AVRational filter_tb = frame->time_base; + const int extra_bits = av_clip(29 - av_log2(tb.den), 0, 16); - frame->pts = - av_rescale_q(frame->pts, filter_tb, enc->time_base) - - av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); - } + if (frame->pts == AV_NOPTS_VALUE) + goto early_exit; + + tb.den <<= extra_bits; + float_pts = av_rescale_q(frame->pts, filter_tb, tb) - + av_rescale_q(start_time, AV_TIME_BASE_Q, tb); + float_pts /= 1 << extra_bits; + // avoid exact midoints to reduce the chance of rounding differences, this + // can be removed in case the fps code is changed to work with integers + float_pts += FFSIGN(float_pts) * 1.0 / (1<<17); + + frame->pts = av_rescale_q(frame->pts, filter_tb, enc->time_base) - + av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); + frame->time_base = enc->time_base; early_exit: if (debug_ts) { av_log(NULL, AV_LOG_INFO, "filter -> pts:%s pts_time:%s exact:%f time_base:%d/%d\n", frame ? av_ts2str(frame->pts) : "NULL", - frame ? av_ts2timestr(frame->pts, &enc->time_base) : "NULL", + (enc && frame) ? av_ts2timestr(frame->pts, &enc->time_base) : "NULL", float_pts, enc ? enc->time_base.num : -1, enc ? enc->time_base.den : -1); @@ -956,8 +896,8 @@ static int init_output_stream_wrapper(OutputStream *ost, AVFrame *frame, ret = init_output_stream(ost, frame, error, sizeof(error)); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n", - ost->file_index, ost->index, error); + av_log(ost, AV_LOG_ERROR, "Error initializing output stream: %s\n", + error); if (fatal) exit_program(1); @@ -1015,17 +955,83 @@ static void update_video_stats(OutputStream *ost, const AVPacket *pkt, int write fprintf(vstats_file,"f_size= %6d ", pkt->size); /* compute pts value */ - ti1 = pkt->dts * av_q2d(ost->mux_timebase); + ti1 = pkt->dts * av_q2d(pkt->time_base); if (ti1 < 0.01) ti1 = 0.01; bitrate = (pkt->size * 8) / av_q2d(enc->time_base) / 1000.0; - avg_bitrate = (double)(ost->data_size * 8) / ti1 / 1000.0; + avg_bitrate = (double)(ost->data_size_enc * 8) / ti1 / 1000.0; fprintf(vstats_file, "s_size= %8.0fkB time= %0.3f br= %7.1fkbits/s avg_br= %7.1fkbits/s ", - (double)ost->data_size / 1024, ti1, bitrate, avg_bitrate); + (double)ost->data_size_enc / 1024, ti1, bitrate, avg_bitrate); fprintf(vstats_file, "type= %c\n", av_get_picture_type_char(ost->pict_type)); } +void enc_stats_write(OutputStream *ost, EncStats *es, + const AVFrame *frame, const AVPacket *pkt, + uint64_t frame_num) +{ + AVIOContext *io = es->io; + AVRational tb = frame ? frame->time_base : pkt->time_base; + int64_t pts = frame ? frame->pts : pkt->pts; + + AVRational tbi = (AVRational){ 0, 1}; + int64_t ptsi = INT64_MAX; + + const FrameData *fd; + + if ((frame && frame->opaque_ref) || (pkt && pkt->opaque_ref)) { + fd = (const FrameData*)(frame ? frame->opaque_ref->data : pkt->opaque_ref->data); + tbi = fd->tb; + ptsi = fd->pts; + } + + for (size_t i = 0; i < es->nb_components; i++) { + const EncStatsComponent *c = &es->components[i]; + + switch (c->type) { + case ENC_STATS_LITERAL: avio_write (io, c->str, c->str_len); continue; + case ENC_STATS_FILE_IDX: avio_printf(io, "%d", ost->file_index); continue; + case ENC_STATS_STREAM_IDX: avio_printf(io, "%d", ost->index); continue; + case ENC_STATS_TIMEBASE: avio_printf(io, "%d/%d", tb.num, tb.den); continue; + case ENC_STATS_TIMEBASE_IN: avio_printf(io, "%d/%d", tbi.num, tbi.den); continue; + case ENC_STATS_PTS: avio_printf(io, "%"PRId64, pts); continue; + case ENC_STATS_PTS_IN: avio_printf(io, "%"PRId64, ptsi); continue; + case ENC_STATS_PTS_TIME: avio_printf(io, "%g", pts * av_q2d(tb)); continue; + case ENC_STATS_PTS_TIME_IN: avio_printf(io, "%g", ptsi == INT64_MAX ? + INFINITY : ptsi * av_q2d(tbi)); continue; + case ENC_STATS_FRAME_NUM: avio_printf(io, "%"PRIu64, frame_num); continue; + case ENC_STATS_FRAME_NUM_IN: avio_printf(io, "%"PRIu64, fd ? fd->idx : -1); continue; + } + + if (frame) { + switch (c->type) { + case ENC_STATS_SAMPLE_NUM: avio_printf(io, "%"PRIu64, ost->samples_encoded); continue; + case ENC_STATS_NB_SAMPLES: avio_printf(io, "%d", frame->nb_samples); continue; + default: av_assert0(0); + } + } else { + switch (c->type) { + case ENC_STATS_DTS: avio_printf(io, "%"PRId64, pkt->dts); continue; + case ENC_STATS_DTS_TIME: avio_printf(io, "%g", pkt->dts * av_q2d(tb)); continue; + case ENC_STATS_PKT_SIZE: avio_printf(io, "%d", pkt->size); continue; + case ENC_STATS_BITRATE: { + double duration = FFMAX(pkt->duration, 1) * av_q2d(tb); + avio_printf(io, "%g", 8.0 * pkt->size / duration); + continue; + } + case ENC_STATS_AVG_BITRATE: { + double duration = pkt->dts * av_q2d(tb); + avio_printf(io, "%g", duration > 0 ? 8.0 * ost->data_size_enc / duration : -1.); + continue; + } + default: av_assert0(0); + } + } + } + avio_w8(io, '\n'); + avio_flush(io); +} + static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) { AVCodecContext *enc = ost->enc_ctx; @@ -1035,10 +1041,15 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) int ret; if (frame) { + if (ost->enc_stats_pre.io) + enc_stats_write(ost, &ost->enc_stats_pre, frame, NULL, + ost->frames_encoded); + ost->frames_encoded++; + ost->samples_encoded += frame->nb_samples; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder <- type:%s " + av_log(ost, AV_LOG_INFO, "encoder <- type:%s " "frame_pts:%s frame_pts_time:%s time_base:%d/%d\n", type_desc, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &enc->time_base), @@ -1050,7 +1061,7 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) ret = avcodec_send_frame(enc, frame); if (ret < 0 && !(ret == AVERROR_EOF && !frame)) { - av_log(NULL, AV_LOG_ERROR, "Error submitting %s frame to the encoder\n", + av_log(ost, AV_LOG_ERROR, "Error submitting %s frame to the encoder\n", type_desc); return ret; } @@ -1060,6 +1071,8 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) update_benchmark("%s_%s %d.%d", action, type_desc, ost->file_index, ost->index); + pkt->time_base = enc->time_base; + /* if two pass, output log on success and EOF */ if ((ret >= 0 || ret == AVERROR_EOF) && ost->logfile && enc->stats_out) fprintf(ost->logfile, "%s", enc->stats_out); @@ -1068,15 +1081,21 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_assert0(frame); // should never happen during flushing return 0; } else if (ret == AVERROR_EOF) { - output_packet(of, pkt, ost, 1); + of_output_packet(of, pkt, ost, 1); return ret; } else if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "%s encoding failed\n", type_desc); + av_log(ost, AV_LOG_ERROR, "%s encoding failed\n", type_desc); return ret; } + if (enc->codec_type == AVMEDIA_TYPE_VIDEO) + update_video_stats(ost, pkt, !!vstats_filename); + if (ost->enc_stats_post.io) + enc_stats_write(ost, &ost->enc_stats_post, NULL, pkt, + ost->packets_encoded); + if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder -> type:%s " + av_log(ost, AV_LOG_INFO, "encoder -> type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s " "duration:%s duration_time:%s\n", type_desc, @@ -1085,10 +1104,11 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base)); } - av_packet_rescale_ts(pkt, enc->time_base, ost->mux_timebase); + av_packet_rescale_ts(pkt, pkt->time_base, ost->mux_timebase); + pkt->time_base = ost->mux_timebase; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder -> type:%s " + av_log(ost, AV_LOG_INFO, "encoder -> type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s " "duration:%s duration_time:%s\n", type_desc, @@ -1097,34 +1117,92 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base)); } - if (enc->codec_type == AVMEDIA_TYPE_VIDEO) - update_video_stats(ost, pkt, !!vstats_filename); + if ((ret = trigger_fix_sub_duration_heartbeat(ost, pkt)) < 0) { + av_log(NULL, AV_LOG_ERROR, + "Subtitle heartbeat logic failed in %s! (%s)\n", + __func__, av_err2str(ret)); + exit_program(1); + } + + ost->data_size_enc += pkt->size; ost->packets_encoded++; - output_packet(of, pkt, ost, 0); + of_output_packet(of, pkt, ost, 0); } av_assert0(0); } +static int submit_encode_frame(OutputFile *of, OutputStream *ost, + AVFrame *frame) +{ + int ret; + + if (ost->sq_idx_encode < 0) + return encode_frame(of, ost, frame); + + if (frame) { + ret = av_frame_ref(ost->sq_frame, frame); + if (ret < 0) + return ret; + frame = ost->sq_frame; + } + + ret = sq_send(of->sq_encode, ost->sq_idx_encode, + SQFRAME(frame)); + if (ret < 0) { + if (frame) + av_frame_unref(frame); + if (ret != AVERROR_EOF) + return ret; + } + + while (1) { + AVFrame *enc_frame = ost->sq_frame; + + ret = sq_receive(of->sq_encode, ost->sq_idx_encode, + SQFRAME(enc_frame)); + if (ret == AVERROR_EOF) { + enc_frame = NULL; + } else if (ret < 0) { + return (ret == AVERROR(EAGAIN)) ? 0 : ret; + } + + ret = encode_frame(of, ost, enc_frame); + if (enc_frame) + av_frame_unref(enc_frame); + if (ret < 0) { + if (ret == AVERROR_EOF) + close_output_stream(ost); + return ret; + } + } +} + static void do_audio_out(OutputFile *of, OutputStream *ost, AVFrame *frame) { + AVCodecContext *enc = ost->enc_ctx; int ret; - adjust_frame_pts_to_encoder_tb(of, ost, frame); + if (frame->pts == AV_NOPTS_VALUE) + frame->pts = ost->next_pts; + else { + int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; + frame->pts = + av_rescale_q(frame->pts, frame->time_base, enc->time_base) - + av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); + } + frame->time_base = enc->time_base; - if (!check_recording_time(ost)) + if (!check_recording_time(ost, frame->pts, frame->time_base)) return; - if (frame->pts == AV_NOPTS_VALUE || audio_sync_method < 0) - frame->pts = ost->sync_opts; - ost->sync_opts = frame->pts + frame->nb_samples; - ost->samples_encoded += frame->nb_samples; + ost->next_pts = frame->pts + frame->nb_samples; - ret = encode_frame(of, ost, frame); - if (ret < 0) + ret = submit_encode_frame(of, ost, frame); + if (ret < 0 && ret != AVERROR_EOF) exit_program(1); } @@ -1133,13 +1211,13 @@ static void do_subtitle_out(OutputFile *of, AVSubtitle *sub) { int subtitle_out_max_size = 1024 * 1024; - int subtitle_out_size, nb, i; + int subtitle_out_size, nb, i, ret; AVCodecContext *enc; AVPacket *pkt = ost->pkt; int64_t pts; if (sub->pts == AV_NOPTS_VALUE) { - av_log(NULL, AV_LOG_ERROR, "Subtitle packets must have a pts\n"); + av_log(ost, AV_LOG_ERROR, "Subtitle packets must have a pts\n"); if (exit_on_error) exit_program(1); return; @@ -1147,14 +1225,6 @@ static void do_subtitle_out(OutputFile *of, enc = ost->enc_ctx; - if (!subtitle_out) { - subtitle_out = av_malloc(subtitle_out_max_size); - if (!subtitle_out) { - av_log(NULL, AV_LOG_FATAL, "Failed to allocate subtitle_out\n"); - exit_program(1); - } - } - /* Note: DVB subtitle need one packet to draw them and one other packet to clear them */ /* XXX: signal it in the codec context ? */ @@ -1170,10 +1240,13 @@ static void do_subtitle_out(OutputFile *of, for (i = 0; i < nb; i++) { unsigned save_num_rects = sub->num_rects; - ost->sync_opts = av_rescale_q(pts, AV_TIME_BASE_Q, enc->time_base); - if (!check_recording_time(ost)) + if (!check_recording_time(ost, pts, AV_TIME_BASE_Q)) return; + ret = av_new_packet(pkt, subtitle_out_max_size); + if (ret < 0) + report_and_exit(AVERROR(ENOMEM)); + sub->pts = pts; // start_display_time is required to be 0 sub->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, AV_TIME_BASE_Q); @@ -1184,33 +1257,156 @@ static void do_subtitle_out(OutputFile *of, ost->frames_encoded++; - subtitle_out_size = avcodec_encode_subtitle(enc, subtitle_out, - subtitle_out_max_size, sub); + subtitle_out_size = avcodec_encode_subtitle(enc, pkt->data, pkt->size, sub); if (i == 1) sub->num_rects = save_num_rects; if (subtitle_out_size < 0) { - av_log(NULL, AV_LOG_FATAL, "Subtitle encoding failed\n"); + av_log(ost, AV_LOG_FATAL, "Subtitle encoding failed\n"); exit_program(1); } - av_packet_unref(pkt); - pkt->data = subtitle_out; - pkt->size = subtitle_out_size; - pkt->pts = av_rescale_q(sub->pts, AV_TIME_BASE_Q, ost->mux_timebase); - pkt->duration = av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + av_shrink_packet(pkt, subtitle_out_size); + pkt->time_base = ost->mux_timebase; + pkt->pts = av_rescale_q(sub->pts, AV_TIME_BASE_Q, pkt->time_base); + pkt->duration = av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, pkt->time_base); if (enc->codec_id == AV_CODEC_ID_DVB_SUBTITLE) { /* XXX: the pts correction is handled here. Maybe handling it in the codec would be better */ if (i == 0) - pkt->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + pkt->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, pkt->time_base); else - pkt->pts += av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + pkt->pts += av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, pkt->time_base); } pkt->dts = pkt->pts; - output_packet(of, pkt, ost, 0); + + of_output_packet(of, pkt, ost, 0); + } +} + +/* Convert frame timestamps to the encoder timebase and decide how many times + * should this (and possibly previous) frame be repeated in order to conform to + * desired target framerate (if any). + */ +static void video_sync_process(OutputFile *of, OutputStream *ost, + AVFrame *next_picture, double duration, + int64_t *nb_frames, int64_t *nb_frames_prev) +{ + double delta0, delta; + + double sync_ipts = adjust_frame_pts_to_encoder_tb(of, ost, next_picture); + /* delta0 is the "drift" between the input frame (next_picture) and + * where it would fall in the output. */ + delta0 = sync_ipts - ost->next_pts; + delta = delta0 + duration; + + // tracks the number of times the PREVIOUS frame should be duplicated, + // mostly for variable framerate (VFR) + *nb_frames_prev = 0; + /* by default, we output a single frame */ + *nb_frames = 1; + + if (delta0 < 0 && + delta > 0 && + ost->vsync_method != VSYNC_PASSTHROUGH && + ost->vsync_method != VSYNC_DROP) { + if (delta0 < -0.6) { + av_log(ost, AV_LOG_VERBOSE, "Past duration %f too large\n", -delta0); + } else + av_log(ost, AV_LOG_DEBUG, "Clipping frame in rate conversion by %f\n", -delta0); + sync_ipts = ost->next_pts; + duration += delta0; + delta0 = 0; + } + + switch (ost->vsync_method) { + case VSYNC_VSCFR: + if (ost->vsync_frame_number == 0 && delta0 >= 0.5) { + av_log(ost, AV_LOG_DEBUG, "Not duplicating %d initial frames\n", (int)lrintf(delta0)); + delta = duration; + delta0 = 0; + ost->next_pts = llrint(sync_ipts); + } + case VSYNC_CFR: + // FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c + if (frame_drop_threshold && delta < frame_drop_threshold && ost->vsync_frame_number) { + *nb_frames = 0; + } else if (delta < -1.1) + *nb_frames = 0; + else if (delta > 1.1) { + *nb_frames = llrintf(delta); + if (delta0 > 1.1) + *nb_frames_prev = llrintf(delta0 - 0.6); + } + next_picture->duration = 1; + break; + case VSYNC_VFR: + if (delta <= -0.6) + *nb_frames = 0; + else if (delta > 0.6) + ost->next_pts = llrint(sync_ipts); + next_picture->duration = duration; + break; + case VSYNC_DROP: + case VSYNC_PASSTHROUGH: + next_picture->duration = duration; + ost->next_pts = llrint(sync_ipts); + break; + default: + av_assert0(0); } } +enum AVPictureType forced_kf_apply(void *logctx, KeyframeForceCtx *kf, + AVRational tb, const AVFrame *in_picture, + int dup_idx) +{ + double pts_time; + + if (kf->ref_pts == AV_NOPTS_VALUE) + kf->ref_pts = in_picture->pts; + + pts_time = (in_picture->pts - kf->ref_pts) * av_q2d(tb); + if (kf->index < kf->nb_pts && + av_compare_ts(in_picture->pts, tb, kf->pts[kf->index], AV_TIME_BASE_Q) >= 0) { + kf->index++; + goto force_keyframe; + } else if (kf->pexpr) { + double res; + kf->expr_const_values[FKF_T] = pts_time; + res = av_expr_eval(kf->pexpr, + kf->expr_const_values, NULL); + ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n", + kf->expr_const_values[FKF_N], + kf->expr_const_values[FKF_N_FORCED], + kf->expr_const_values[FKF_PREV_FORCED_N], + kf->expr_const_values[FKF_T], + kf->expr_const_values[FKF_PREV_FORCED_T], + res); + + kf->expr_const_values[FKF_N] += 1; + + if (res) { + kf->expr_const_values[FKF_PREV_FORCED_N] = kf->expr_const_values[FKF_N] - 1; + kf->expr_const_values[FKF_PREV_FORCED_T] = kf->expr_const_values[FKF_T]; + kf->expr_const_values[FKF_N_FORCED] += 1; + goto force_keyframe; + } + } else if (kf->type == KF_FORCE_SOURCE && + in_picture->key_frame == 1 && !dup_idx) { + goto force_keyframe; + } else if (kf->type == KF_FORCE_SOURCE_NO_DROP && !dup_idx) { + kf->dropped_keyframe = 0; + if ((in_picture->key_frame == 1) || kf->dropped_keyframe) + goto force_keyframe; + } + + return AV_PICTURE_TYPE_NONE; + +force_keyframe: + av_log(logctx, AV_LOG_DEBUG, "Forced keyframe at time %f\n", pts_time); + return AV_PICTURE_TYPE_I; +} + /* May modify/reset next_picture */ static void do_video_out(OutputFile *of, OutputStream *ost, @@ -1219,18 +1415,12 @@ static void do_video_out(OutputFile *of, int ret; AVCodecContext *enc = ost->enc_ctx; AVRational frame_rate; - int64_t nb_frames, nb0_frames, i; - double delta, delta0; + int64_t nb_frames, nb_frames_prev, i; double duration = 0; - double sync_ipts = AV_NOPTS_VALUE; - InputStream *ist = NULL; + InputStream *ist = ost->ist; AVFilterContext *filter = ost->filter->filter; init_output_stream_wrapper(ost, next_picture, 1); - sync_ipts = adjust_frame_pts_to_encoder_tb(of, ost, next_picture); - - if (ost->source_index >= 0) - ist = input_streams[ost->source_index]; frame_rate = av_buffersink_get_frame_rate(filter); if (frame_rate.num > 0 && frame_rate.den > 0) @@ -1244,113 +1434,52 @@ static void do_video_out(OutputFile *of, (nb_filtergraphs == 0 || !filtergraphs[0]->graph_desc) && next_picture && ist && - lrintf(next_picture->pkt_duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)) > 0) { - duration = lrintf(next_picture->pkt_duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)); + lrintf(next_picture->duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)) > 0) { + duration = lrintf(next_picture->duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)); } if (!next_picture) { //end, flushing - nb0_frames = nb_frames = mid_pred(ost->last_nb0_frames[0], - ost->last_nb0_frames[1], - ost->last_nb0_frames[2]); + nb_frames_prev = nb_frames = mid_pred(ost->last_nb0_frames[0], + ost->last_nb0_frames[1], + ost->last_nb0_frames[2]); } else { - delta0 = sync_ipts - ost->sync_opts; // delta0 is the "drift" between the input frame (next_picture) and where it would fall in the output. - delta = delta0 + duration; - - /* by default, we output a single frame */ - nb0_frames = 0; // tracks the number of times the PREVIOUS frame should be duplicated, mostly for variable framerate (VFR) - nb_frames = 1; - - if (delta0 < 0 && - delta > 0 && - ost->vsync_method != VSYNC_PASSTHROUGH && - ost->vsync_method != VSYNC_DROP) { - if (delta0 < -0.6) { - av_log(NULL, AV_LOG_VERBOSE, "Past duration %f too large\n", -delta0); - } else - av_log(NULL, AV_LOG_DEBUG, "Clipping frame in rate conversion by %f\n", -delta0); - sync_ipts = ost->sync_opts; - duration += delta0; - delta0 = 0; - } - - switch (ost->vsync_method) { - case VSYNC_VSCFR: - if (ost->frame_number == 0 && delta0 >= 0.5) { - av_log(NULL, AV_LOG_DEBUG, "Not duplicating %d initial frames\n", (int)lrintf(delta0)); - delta = duration; - delta0 = 0; - ost->sync_opts = llrint(sync_ipts); - } - case VSYNC_CFR: - // FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c - if (frame_drop_threshold && delta < frame_drop_threshold && ost->frame_number) { - nb_frames = 0; - } else if (delta < -1.1) - nb_frames = 0; - else if (delta > 1.1) { - nb_frames = llrintf(delta); - if (delta0 > 1.1) - nb0_frames = llrintf(delta0 - 0.6); - } - break; - case VSYNC_VFR: - if (delta <= -0.6) - nb_frames = 0; - else if (delta > 0.6) - ost->sync_opts = llrint(sync_ipts); - break; - case VSYNC_DROP: - case VSYNC_PASSTHROUGH: - ost->sync_opts = llrint(sync_ipts); - break; - default: - av_assert0(0); - } + video_sync_process(of, ost, next_picture, duration, + &nb_frames, &nb_frames_prev); } - /* - * For video, number of frames in == number of packets out. - * But there may be reordering, so we can't throw away frames on encoder - * flush, we need to limit them here, before they go into encoder. - */ - nb_frames = FFMIN(nb_frames, ost->max_frames - ost->frame_number); - nb0_frames = FFMIN(nb0_frames, nb_frames); - memmove(ost->last_nb0_frames + 1, ost->last_nb0_frames, sizeof(ost->last_nb0_frames[0]) * (FF_ARRAY_ELEMS(ost->last_nb0_frames) - 1)); - ost->last_nb0_frames[0] = nb0_frames; + ost->last_nb0_frames[0] = nb_frames_prev; - if (nb0_frames == 0 && ost->last_dropped) { + if (nb_frames_prev == 0 && ost->last_dropped) { nb_frames_drop++; - av_log(NULL, AV_LOG_VERBOSE, - "*** dropping frame %"PRId64" from stream %d at ts %"PRId64"\n", - ost->frame_number, ost->st->index, ost->last_frame->pts); + av_log(ost, AV_LOG_VERBOSE, + "*** dropping frame %"PRId64" at ts %"PRId64"\n", + ost->vsync_frame_number, ost->last_frame->pts); } - if (nb_frames > (nb0_frames && ost->last_dropped) + (nb_frames > nb0_frames)) { + if (nb_frames > (nb_frames_prev && ost->last_dropped) + (nb_frames > nb_frames_prev)) { if (nb_frames > dts_error_threshold * 30) { - av_log(NULL, AV_LOG_ERROR, "%"PRId64" frame duplication too large, skipping\n", nb_frames - 1); + av_log(ost, AV_LOG_ERROR, "%"PRId64" frame duplication too large, skipping\n", nb_frames - 1); nb_frames_drop++; return; } - nb_frames_dup += nb_frames - (nb0_frames && ost->last_dropped) - (nb_frames > nb0_frames); - av_log(NULL, AV_LOG_VERBOSE, "*** %"PRId64" dup!\n", nb_frames - 1); + nb_frames_dup += nb_frames - (nb_frames_prev && ost->last_dropped) - (nb_frames > nb_frames_prev); + av_log(ost, AV_LOG_VERBOSE, "*** %"PRId64" dup!\n", nb_frames - 1); if (nb_frames_dup > dup_warning) { - av_log(NULL, AV_LOG_WARNING, "More than %"PRIu64" frames duplicated\n", dup_warning); + av_log(ost, AV_LOG_WARNING, "More than %"PRIu64" frames duplicated\n", dup_warning); dup_warning *= 10; } } - ost->last_dropped = nb_frames == nb0_frames && next_picture; - ost->dropped_keyframe = ost->last_dropped && next_picture && next_picture->key_frame; + ost->last_dropped = nb_frames == nb_frames_prev && next_picture; + ost->kf.dropped_keyframe = ost->last_dropped && next_picture && next_picture->key_frame; /* duplicates frame if needed */ for (i = 0; i < nb_frames; i++) { AVFrame *in_picture; - int forced_keyframe = 0; - double pts_time; - if (i < nb0_frames && ost->last_frame->buf[0]) { + if (i < nb_frames_prev && ost->last_frame->buf[0]) { in_picture = ost->last_frame; } else in_picture = next_picture; @@ -1358,69 +1487,22 @@ static void do_video_out(OutputFile *of, if (!in_picture) return; - in_picture->pts = ost->sync_opts; + in_picture->pts = ost->next_pts; - if (!check_recording_time(ost)) + if (!check_recording_time(ost, in_picture->pts, ost->enc_ctx->time_base)) return; in_picture->quality = enc->global_quality; - in_picture->pict_type = 0; - - if (ost->forced_kf_ref_pts == AV_NOPTS_VALUE && - in_picture->pts != AV_NOPTS_VALUE) - ost->forced_kf_ref_pts = in_picture->pts; - - pts_time = in_picture->pts != AV_NOPTS_VALUE ? - (in_picture->pts - ost->forced_kf_ref_pts) * av_q2d(enc->time_base) : NAN; - if (ost->forced_kf_index < ost->forced_kf_count && - in_picture->pts >= ost->forced_kf_pts[ost->forced_kf_index]) { - ost->forced_kf_index++; - forced_keyframe = 1; - } else if (ost->forced_keyframes_pexpr) { - double res; - ost->forced_keyframes_expr_const_values[FKF_T] = pts_time; - res = av_expr_eval(ost->forced_keyframes_pexpr, - ost->forced_keyframes_expr_const_values, NULL); - ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n", - ost->forced_keyframes_expr_const_values[FKF_N], - ost->forced_keyframes_expr_const_values[FKF_N_FORCED], - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N], - ost->forced_keyframes_expr_const_values[FKF_T], - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T], - res); - if (res) { - forced_keyframe = 1; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N] = - ost->forced_keyframes_expr_const_values[FKF_N]; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T] = - ost->forced_keyframes_expr_const_values[FKF_T]; - ost->forced_keyframes_expr_const_values[FKF_N_FORCED] += 1; - } + in_picture->pict_type = forced_kf_apply(ost, &ost->kf, enc->time_base, in_picture, i); - ost->forced_keyframes_expr_const_values[FKF_N] += 1; - } else if ( ost->forced_keyframes - && !strncmp(ost->forced_keyframes, "source", 6) - && in_picture->key_frame==1 - && !i) { - forced_keyframe = 1; - } else if ( ost->forced_keyframes - && !strncmp(ost->forced_keyframes, "source_no_drop", 14) - && !i) { - forced_keyframe = (in_picture->key_frame == 1) || ost->dropped_keyframe; - ost->dropped_keyframe = 0; - } - - if (forced_keyframe) { - in_picture->pict_type = AV_PICTURE_TYPE_I; - av_log(NULL, AV_LOG_DEBUG, "Forced keyframe at time %f\n", pts_time); - } - - ret = encode_frame(of, ost, in_picture); - if (ret < 0) + ret = submit_encode_frame(of, ost, in_picture); + if (ret == AVERROR_EOF) + break; + else if (ret < 0) exit_program(1); - ost->sync_opts++; - ost->frame_number++; + ost->next_pts++; + ost->vsync_frame_number++; } av_frame_unref(ost->last_frame); @@ -1428,19 +1510,6 @@ static void do_video_out(OutputFile *of, av_frame_move_ref(ost->last_frame, next_picture); } -static void finish_output_stream(OutputStream *ost) -{ - OutputFile *of = output_files[ost->file_index]; - AVRational time_base = ost->stream_copy ? ost->mux_timebase : ost->enc_ctx->time_base; - - ost->finished = ENCODER_FINISHED | MUXER_FINISHED; - - if (of->shortest) { - int64_t end = av_rescale_q(ost->sync_opts - ost->first_pts, time_base, AV_TIME_BASE_Q); - of->recording_time = FFMIN(of->recording_time, end); - } -} - /** * Get and encode new output from any of the filtergraphs, without causing * activity. @@ -1450,11 +1519,9 @@ static void finish_output_stream(OutputStream *ost) static int reap_filters(int flush) { AVFrame *filtered_frame = NULL; - int i; /* Reap all buffers present in the buffer sinks */ - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { OutputFile *of = output_files[ost->file_index]; AVFilterContext *filter; AVCodecContext *enc = ost->enc_ctx; @@ -1496,6 +1563,19 @@ static int reap_filters(int flush) continue; } + if (filtered_frame->pts != AV_NOPTS_VALUE) { + AVRational tb = av_buffersink_get_time_base(filter); + ost->last_filter_pts = av_rescale_q(filtered_frame->pts, tb, + AV_TIME_BASE_Q); + filtered_frame->time_base = tb; + + if (debug_ts) + av_log(NULL, AV_LOG_INFO, "filter_raw -> pts:%s pts_time:%s time_base:%d/%d\n", + av_ts2str(filtered_frame->pts), + av_ts2timestr(filtered_frame->pts, &tb), + tb.num, tb.den); + } + switch (av_buffersink_get_type(filter)) { case AVMEDIA_TYPE_VIDEO: if (!ost->frame_aspect_ratio.num) @@ -1532,19 +1612,21 @@ static void print_final_stats(int64_t total_size) float percent = -1.0; int i, j; int pass1_used = 1; - char percentString[50] = {0}; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - switch (ost->enc_ctx->codec_type) { - case AVMEDIA_TYPE_VIDEO: video_size += ost->data_size; break; - case AVMEDIA_TYPE_AUDIO: audio_size += ost->data_size; break; - case AVMEDIA_TYPE_SUBTITLE: subtitle_size += ost->data_size; break; - default: other_size += ost->data_size; break; + + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + AVCodecParameters *par = ost->st->codecpar; + const uint64_t s = ost->data_size_mux; + + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: video_size += s; break; + case AVMEDIA_TYPE_AUDIO: audio_size += s; break; + case AVMEDIA_TYPE_SUBTITLE: subtitle_size += s; break; + default: other_size += s; break; } - extra_size += ost->enc_ctx->extradata_size; - data_size += ost->data_size; - if ( (ost->enc_ctx->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2)) + extra_size += par->extradata_size; + data_size += s; + if (ost->enc_ctx && + (ost->enc_ctx->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2)) != AV_CODEC_FLAG_PASS1) pass1_used = 0; } @@ -1552,18 +1634,17 @@ static void print_final_stats(int64_t total_size) if (data_size && total_size>0 && total_size >= data_size) percent = 100.0 * (total_size - data_size) / data_size; - if (percent >= 0.0) - sprintf(percentString, "%f%%", percent); - else - sprintf(percentString, "unknown"); - - av_log(NULL, AV_LOG_INFO, "video:%1.0fkB audio:%1.0fkB subtitle:%1.0fkB other streams:%1.0fkB global headers:%1.0fkB muxing overhead: %s\n", + av_log(NULL, AV_LOG_INFO, "video:%1.0fkB audio:%1.0fkB subtitle:%1.0fkB other streams:%1.0fkB global headers:%1.0fkB muxing overhead: ", video_size / 1024.0, audio_size / 1024.0, subtitle_size / 1024.0, other_size / 1024.0, - extra_size / 1024.0, - percentString); + extra_size / 1024.0); + if (percent >= 0.0) + av_log(NULL, AV_LOG_INFO, "%f%%", percent); + else + av_log(NULL, AV_LOG_INFO, "unknown"); + av_log(NULL, AV_LOG_INFO, "\n"); /* print verbose per-stream stats */ for (i = 0; i < nb_input_files; i++) { @@ -1574,8 +1655,8 @@ static void print_final_stats(int64_t total_size) i, f->ctx->url); for (j = 0; j < f->nb_streams; j++) { - InputStream *ist = input_streams[f->ist_index + j]; - enum AVMediaType type = ist->dec_ctx->codec_type; + InputStream *ist = f->streams[j]; + enum AVMediaType type = ist->par->codec_type; total_size += ist->data_size; total_packets += ist->nb_packets; @@ -1605,18 +1686,18 @@ static void print_final_stats(int64_t total_size) uint64_t total_packets = 0, total_size = 0; av_log(NULL, AV_LOG_VERBOSE, "Output file #%d (%s):\n", - i, of->ctx->url); + i, of->url); - for (j = 0; j < of->ctx->nb_streams; j++) { - OutputStream *ost = output_streams[of->ost_index + j]; - enum AVMediaType type = ost->enc_ctx->codec_type; + for (j = 0; j < of->nb_streams; j++) { + OutputStream *ost = of->streams[j]; + enum AVMediaType type = ost->st->codecpar->codec_type; - total_size += ost->data_size; - total_packets += ost->packets_written; + total_size += ost->data_size_mux; + total_packets += atomic_load(&ost->packets_written); av_log(NULL, AV_LOG_VERBOSE, " Output stream #%d:%d (%s): ", i, j, av_get_media_type_string(type)); - if (ost->encoding_needed) { + if (ost->enc_ctx) { av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" frames encoded", ost->frames_encoded); if (type == AVMEDIA_TYPE_AUDIO) @@ -1625,7 +1706,7 @@ static void print_final_stats(int64_t total_size) } av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" packets muxed (%"PRIu64" bytes); ", - ost->packets_written, ost->data_size); + atomic_load(&ost->packets_written), ost->data_size_mux); av_log(NULL, AV_LOG_VERBOSE, "\n"); } @@ -1643,83 +1724,19 @@ static void print_final_stats(int64_t total_size) } } -static void forward_report(int is_last_report, int64_t timer_start, int64_t cur_time) +static void forward_report(uint64_t frame_number, float fps, float quality, int64_t total_size, int64_t seconds, int64_t microseconds, double bitrate, double speed) { - AVFormatContext *oc = NULL; - AVCodecContext *enc = NULL; - OutputStream *ost = NULL; - int64_t pts = INT64_MIN + 1; - int vid, i; - - int64_t frame_number = 0; - float fps = 0; - float quality = 0; - int64_t total_size = 0; - int seconds = 0; - double bitrate = 0.0; - double speed = 0.0; - - float t = (cur_time-timer_start) / 1000000.0; - - oc = output_files[0]->ctx; - - // 1. calculate size - total_size = avio_size(oc->pb); - if (total_size <= 0) { - total_size = avio_tell(oc->pb); - } - - vid = 0; - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - enc = ost->enc_ctx; - - if (!ost->stream_copy) { - - // 2. extract quality - quality = ost->quality / (float) FF_QP2LAMBDA; - } - - if (!vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { - - // 3. extract frame number - frame_number = ost->frame_number; - - // 4. calculate fps - fps = t > 1 ? frame_number / t : 0; - } - - // 5. calculate time - if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) - pts = FFMAX(pts, av_rescale_q(av_stream_get_end_pts(ost->st), - ost->st->time_base, AV_TIME_BASE_Q)); - - vid = 1; - } - - // 6. calculate time, with microseconds to milliseconds conversion - seconds = FFABS(pts) / 1000; - - // 7. calculating kbit/s value - bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; - - // 9. calculate processing speed = processed stream duration/operation duration - speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; - // FORWARD DATA if (report_callback != NULL) { - report_callback(frame_number, fps, quality, total_size, seconds, bitrate, speed); + report_callback(frame_number, fps, quality, total_size, ((double)seconds*1000) + ((double)microseconds)/1000, bitrate, speed); } } static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time) { AVBPrint buf, buf_script; - OutputStream *ost; - AVFormatContext *oc; - int64_t total_size; - AVCodecContext *enc; - int vid, i; + int64_t total_size = of_filesize(output_files[0]); + int vid; double bitrate; double speed; int64_t pts = INT64_MIN + 1; @@ -1728,6 +1745,15 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti int ret; float t; + // FFmpegKit field declarations + int local_print_stats = 1; + uint64_t frame_number = 0; + float fps = 0; + float q = 0; + + if (!print_stats && !is_last_report && !progress_avio) + local_print_stats = 0; + if (!is_last_report) { if (last_time == -1) { last_time = cur_time; @@ -1738,58 +1764,48 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti last_time = cur_time; } - forward_report(is_last_report, timer_start, cur_time); - - if (!print_stats && !is_last_report && !progress_avio) - return; - t = (cur_time-timer_start) / 1000000.0; - - oc = output_files[0]->ctx; - - total_size = avio_size(oc->pb); - if (total_size <= 0) // FIXME improve avio_size() so it works with non seekable output too - total_size = avio_tell(oc->pb); - vid = 0; - av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); - av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC); - for (i = 0; i < nb_output_streams; i++) { - float q = -1; - ost = output_streams[i]; - enc = ost->enc_ctx; - if (!ost->stream_copy) - q = ost->quality / (float) FF_QP2LAMBDA; - - if (vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { + if (local_print_stats) { + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); + av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC); + } + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + const AVCodecContext * const enc = ost->enc_ctx; + q = enc ? ost->quality / (float) FF_QP2LAMBDA : -1; + + if (local_print_stats && vid && ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { av_bprintf(&buf, "q=%2.1f ", q); av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", ost->file_index, ost->index, q); } - if (!vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { - float fps; - int64_t frame_number = ost->frame_number; - + if (!vid && ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + frame_number = atomic_load(&ost->packets_written); fps = t > 1 ? frame_number / t : 0; - av_bprintf(&buf, "frame=%5"PRId64" fps=%3.*f q=%3.1f ", - frame_number, fps < 9.95, fps, q); - av_bprintf(&buf_script, "frame=%"PRId64"\n", frame_number); - av_bprintf(&buf_script, "fps=%.2f\n", fps); - av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", - ost->file_index, ost->index, q); - if (is_last_report) + if (local_print_stats) { + av_bprintf(&buf, "frame=%5"PRId64" fps=%3.*f q=%3.1f ", + frame_number, fps < 9.95, fps, q); + av_bprintf(&buf_script, "frame=%"PRId64"\n", frame_number); + av_bprintf(&buf_script, "fps=%.2f\n", fps); + av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", + ost->file_index, ost->index, q); + } + if (local_print_stats && is_last_report) av_bprintf(&buf, "L"); if (qp_hist) { int j; int qp = lrintf(q); if (qp >= 0 && qp < FF_ARRAY_ELEMS(qp_histogram)) qp_histogram[qp]++; - for (j = 0; j < 32; j++) - av_bprintf(&buf, "%X", av_log2(qp_histogram[j] + 1)); + if (local_print_stats) { + for (j = 0; j < 32; j++) + av_bprintf(&buf, "%X", av_log2(qp_histogram[j] + 1)); + } } - if ((enc->flags & AV_CODEC_FLAG_PSNR) && (ost->pict_type != AV_PICTURE_TYPE_NONE || is_last_report)) { + if (local_print_stats && enc && (enc->flags & AV_CODEC_FLAG_PSNR) && + (ost->pict_type != AV_PICTURE_TYPE_NONE || is_last_report)) { int j; double error, error_sum = 0; double scale, scale_sum = 0; @@ -1821,9 +1837,8 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti vid = 1; } /* compute min output value */ - if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) { - pts = FFMAX(pts, av_rescale_q(av_stream_get_end_pts(ost->st), - ost->st->time_base, AV_TIME_BASE_Q)); + if (ost->last_mux_dts != AV_NOPTS_VALUE) { + pts = FFMAX(pts, ost->last_mux_dts); if (copy_ts) { if (copy_ts_first_pts == AV_NOPTS_VALUE && pts > 1) copy_ts_first_pts = pts; @@ -1847,77 +1862,82 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; - if (total_size < 0) av_bprintf(&buf, "size=N/A time="); - else av_bprintf(&buf, "size=%8.0fkB time=", total_size / 1024.0); - if (pts == AV_NOPTS_VALUE) { - av_bprintf(&buf, "N/A "); - } else { - av_bprintf(&buf, "%s%02d:%02d:%02d.%02d ", - hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE); - } + // FFmpegKit forward report + forward_report(frame_number, fps, q, total_size, secs, us, bitrate, speed); - if (bitrate < 0) { - av_bprintf(&buf, "bitrate=N/A"); - av_bprintf(&buf_script, "bitrate=N/A\n"); - }else{ - av_bprintf(&buf, "bitrate=%6.1fkbits/s", bitrate); - av_bprintf(&buf_script, "bitrate=%6.1fkbits/s\n", bitrate); - } + if (local_print_stats) { + if (total_size < 0) av_bprintf(&buf, "size=N/A time="); + else av_bprintf(&buf, "size=%8.0fkB time=", total_size / 1024.0); + if (pts == AV_NOPTS_VALUE) { + av_bprintf(&buf, "N/A "); + } else { + av_bprintf(&buf, "%s%02d:%02d:%02d.%02d ", + hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE); + } - if (total_size < 0) av_bprintf(&buf_script, "total_size=N/A\n"); - else av_bprintf(&buf_script, "total_size=%"PRId64"\n", total_size); - if (pts == AV_NOPTS_VALUE) { - av_bprintf(&buf_script, "out_time_us=N/A\n"); - av_bprintf(&buf_script, "out_time_ms=N/A\n"); - av_bprintf(&buf_script, "out_time=N/A\n"); - } else { - av_bprintf(&buf_script, "out_time_us=%"PRId64"\n", pts); - av_bprintf(&buf_script, "out_time_ms=%"PRId64"\n", pts); - av_bprintf(&buf_script, "out_time=%s%02d:%02d:%02d.%06d\n", - hours_sign, hours, mins, secs, us); - } + if (bitrate < 0) { + av_bprintf(&buf, "bitrate=N/A"); + av_bprintf(&buf_script, "bitrate=N/A\n"); + }else{ + av_bprintf(&buf, "bitrate=%6.1fkbits/s", bitrate); + av_bprintf(&buf_script, "bitrate=%6.1fkbits/s\n", bitrate); + } - if (nb_frames_dup || nb_frames_drop) - av_bprintf(&buf, " dup=%"PRId64" drop=%"PRId64, nb_frames_dup, nb_frames_drop); - av_bprintf(&buf_script, "dup_frames=%"PRId64"\n", nb_frames_dup); - av_bprintf(&buf_script, "drop_frames=%"PRId64"\n", nb_frames_drop); + if (total_size < 0) av_bprintf(&buf_script, "total_size=N/A\n"); + else av_bprintf(&buf_script, "total_size=%"PRId64"\n", total_size); + if (pts == AV_NOPTS_VALUE) { + av_bprintf(&buf_script, "out_time_us=N/A\n"); + av_bprintf(&buf_script, "out_time_ms=N/A\n"); + av_bprintf(&buf_script, "out_time=N/A\n"); + } else { + av_bprintf(&buf_script, "out_time_us=%"PRId64"\n", pts); + av_bprintf(&buf_script, "out_time_ms=%"PRId64"\n", pts); + av_bprintf(&buf_script, "out_time=%s%02d:%02d:%02d.%06d\n", + hours_sign, hours, mins, secs, us); + } - if (speed < 0) { - av_bprintf(&buf, " speed=N/A"); - av_bprintf(&buf_script, "speed=N/A\n"); - } else { - av_bprintf(&buf, " speed=%4.3gx", speed); - av_bprintf(&buf_script, "speed=%4.3gx\n", speed); - } + if (nb_frames_dup || nb_frames_drop) + av_bprintf(&buf, " dup=%"PRId64" drop=%"PRId64, nb_frames_dup, nb_frames_drop); + av_bprintf(&buf_script, "dup_frames=%"PRId64"\n", nb_frames_dup); + av_bprintf(&buf_script, "drop_frames=%"PRId64"\n", nb_frames_drop); - if (print_stats || is_last_report) { - const char end = is_last_report ? '\n' : '\r'; - if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) { - av_log(NULL, AV_LOG_STDERR, "%s %c", buf.str, end); - } else - av_log(NULL, AV_LOG_INFO, "%s %c", buf.str, end); - } - av_bprint_finalize(&buf, NULL); - - if (progress_avio) { - av_bprintf(&buf_script, "progress=%s\n", - is_last_report ? "end" : "continue"); - avio_write(progress_avio, buf_script.str, - FFMIN(buf_script.len, buf_script.size - 1)); - avio_flush(progress_avio); - av_bprint_finalize(&buf_script, NULL); - if (is_last_report) { - if ((ret = avio_closep(&progress_avio)) < 0) - av_log(NULL, AV_LOG_ERROR, - "Error closing progress log, loss of information possible: %s\n", av_err2str(ret)); + if (speed < 0) { + av_bprintf(&buf, " speed=N/A"); + av_bprintf(&buf_script, "speed=N/A\n"); + } else { + av_bprintf(&buf, " speed=%4.3gx", speed); + av_bprintf(&buf_script, "speed=%4.3gx\n", speed); } - } - first_report = 0; - - if (is_last_report) - print_final_stats(total_size); -} + if (print_stats || is_last_report) { + const char end = is_last_report ? '\n' : '\r'; + if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) { + av_log(NULL, AV_LOG_STDERR, "%s %c", buf.str, end); + } else + av_log(NULL, AV_LOG_INFO, "%s %c", buf.str, end); + } + av_bprint_finalize(&buf, NULL); + + if (progress_avio) { + av_bprintf(&buf_script, "progress=%s\n", + is_last_report ? "end" : "continue"); + avio_write(progress_avio, buf_script.str, + FFMIN(buf_script.len, buf_script.size - 1)); + avio_flush(progress_avio); + av_bprint_finalize(&buf_script, NULL); + if (is_last_report) { + if ((ret = avio_closep(&progress_avio)) < 0) + av_log(NULL, AV_LOG_ERROR, + "Error closing progress log, loss of information possible: %s\n", av_err2str(ret)); + } + } + + first_report = 0; + + if (is_last_report) + print_final_stats(total_size); + } +} static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par) { @@ -1939,14 +1959,19 @@ static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParamet static void flush_encoders(void) { - int i, ret; + int ret; + + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + OutputFile *of = output_files[ost->file_index]; + if (ost->sq_idx_encode >= 0) + sq_send(of->sq_encode, ost->sq_idx_encode, SQFRAME(NULL)); + } - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { AVCodecContext *enc = ost->enc_ctx; OutputFile *of = output_files[ost->file_index]; - if (!ost->encoding_needed) + if (!enc) continue; // Try to enable encoding with no input frames. @@ -1954,17 +1979,16 @@ static void flush_encoders(void) if (!ost->initialized) { FilterGraph *fg = ost->filter->graph; - av_log(NULL, AV_LOG_WARNING, - "Finishing stream %d:%d without any data written to it.\n", - ost->file_index, ost->st->index); + av_log(ost, AV_LOG_WARNING, + "Finishing stream without any data written to it.\n"); if (ost->filter && !fg->graph) { int x; for (x = 0; x < fg->nb_inputs; x++) { InputFilter *ifilter = fg->inputs[x]; if (ifilter->format < 0 && - ifilter_parameters_from_codecpar(ifilter, ifilter->ist->st->codecpar) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error copying paramerets from input stream\n"); + ifilter_parameters_from_codecpar(ifilter, ifilter->ist->par) < 0) { + av_log(ost, AV_LOG_ERROR, "Error copying paramerets from input stream\n"); exit_program(1); } } @@ -1974,11 +1998,11 @@ static void flush_encoders(void) ret = configure_filtergraph(fg); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error configuring filter graph\n"); + av_log(ost, AV_LOG_ERROR, "Error configuring filter graph\n"); exit_program(1); } - finish_output_stream(ost); + of_output_packet(of, ost->pkt, ost, 1); } init_output_stream_wrapper(ost, NULL, 1); @@ -1987,7 +2011,7 @@ static void flush_encoders(void) if (enc->codec_type != AVMEDIA_TYPE_VIDEO && enc->codec_type != AVMEDIA_TYPE_AUDIO) continue; - ret = encode_frame(of, ost, NULL); + ret = submit_encode_frame(of, ost, NULL); if (ret != AVERROR_EOF) exit_program(1); } @@ -1999,9 +2023,8 @@ static void flush_encoders(void) static int check_output_constraints(InputStream *ist, OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - int ist_index = input_files[ist->file_index]->ist_index + ist->st->index; - if (ost->source_index != ist_index) + if (ost->ist != ist) return 0; if (ost->finished & MUXER_FINISHED) @@ -2024,7 +2047,7 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p av_packet_unref(opkt); // EOF: flush output bitstream filters. if (!pkt) { - output_packet(of, opkt, ost, 1); + of_output_packet(of, opkt, ost, 1); return; } @@ -2033,12 +2056,9 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p return; if (!ost->streamcopy_started && !ost->copy_prior_start) { - int64_t comp_start = start_time; - if (copy_ts && f->start_time != AV_NOPTS_VALUE) - comp_start = FFMAX(start_time, f->start_time + f->ts_offset); if (pkt->pts == AV_NOPTS_VALUE ? - ist->pts < comp_start : - pkt->pts < av_rescale_q(comp_start, AV_TIME_BASE_Q, ist->st->time_base)) + ist->pts < ost->ts_copy_start : + pkt->pts < av_rescale_q(ost->ts_copy_start, AV_TIME_BASE_Q, ist->st->time_base)) return; } @@ -2052,7 +2072,7 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p start_time = 0; if (copy_ts) { start_time += f->start_time != AV_NOPTS_VALUE ? f->start_time : 0; - start_time += start_at_zero ? 0 : f->ctx->start_time; + start_time += start_at_zero ? 0 : f->start_time_effective; } if (ist->pts >= f->recording_time + start_time) { close_output_stream(ost); @@ -2063,52 +2083,43 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p if (av_packet_ref(opkt, pkt) < 0) exit_program(1); + opkt->time_base = ost->mux_timebase; + if (pkt->pts != AV_NOPTS_VALUE) - opkt->pts = av_rescale_q(pkt->pts, ist->st->time_base, ost->mux_timebase) - ost_tb_start_time; + opkt->pts = av_rescale_q(pkt->pts, ist->st->time_base, opkt->time_base) - ost_tb_start_time; if (pkt->dts == AV_NOPTS_VALUE) { - opkt->dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ost->mux_timebase); + opkt->dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, opkt->time_base); } else if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - int duration = av_get_audio_frame_duration(ist->dec_ctx, pkt->size); + int duration = av_get_audio_frame_duration2(ist->par, pkt->size); if(!duration) - duration = ist->dec_ctx->frame_size; + duration = ist->par->frame_size; opkt->dts = av_rescale_delta(ist->st->time_base, pkt->dts, - (AVRational){1, ist->dec_ctx->sample_rate}, duration, - &ist->filter_in_rescale_delta_last, ost->mux_timebase); + (AVRational){1, ist->par->sample_rate}, duration, + &ist->filter_in_rescale_delta_last, opkt->time_base); /* dts will be set immediately afterwards to what pts is now */ opkt->pts = opkt->dts - ost_tb_start_time; } else - opkt->dts = av_rescale_q(pkt->dts, ist->st->time_base, ost->mux_timebase); + opkt->dts = av_rescale_q(pkt->dts, ist->st->time_base, opkt->time_base); opkt->dts -= ost_tb_start_time; - opkt->duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->mux_timebase); + opkt->duration = av_rescale_q(pkt->duration, ist->st->time_base, opkt->time_base); - ost->sync_opts += opkt->duration; + { + int ret = trigger_fix_sub_duration_heartbeat(ost, pkt); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "Subtitle heartbeat logic failed in %s! (%s)\n", + __func__, av_err2str(ret)); + exit_program(1); + } + } - output_packet(of, opkt, ost, 0); + of_output_packet(of, opkt, ost, 0); ost->streamcopy_started = 1; } -int guess_input_channel_layout(InputStream *ist) -{ - AVCodecContext *dec = ist->dec_ctx; - - if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) { - char layout_name[256]; - - if (dec->ch_layout.nb_channels > ist->guess_layout_max) - return 0; - av_channel_layout_default(&dec->ch_layout, dec->ch_layout.nb_channels); - if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) - return 0; - av_channel_layout_describe(&dec->ch_layout, layout_name, sizeof(layout_name)); - av_log(NULL, AV_LOG_WARNING, "Guessed Channel Layout for Input Stream " - "#%d.%d : %s\n", ist->file_index, ist->st->index, layout_name); - } - return 1; -} - static void check_decode_result(InputStream *ist, int *got_output, int ret) { if (*got_output || ret<0) @@ -2152,7 +2163,7 @@ static int ifilter_send_frame(InputFilter *ifilter, AVFrame *frame, int keep_ref /* determine if the parameters for this input changed */ need_reinit = ifilter->format != frame->format; - switch (ifilter->ist->st->codecpar->codec_type) { + switch (ifilter->ist->par->codec_type) { case AVMEDIA_TYPE_AUDIO: need_reinit |= ifilter->sample_rate != frame->sample_rate || av_channel_layout_compare(&ifilter->ch_layout, &frame->ch_layout); @@ -2236,7 +2247,7 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts) } else { // the filtergraph was never configured if (ifilter->format < 0) { - ret = ifilter_parameters_from_codecpar(ifilter, ifilter->ist->st->codecpar); + ret = ifilter_parameters_from_codecpar(ifilter, ifilter->ist->par); if (ret < 0) return ret; } @@ -2253,7 +2264,8 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts) // There is the following difference: if you got a frame, you must call // it again with pkt=NULL. pkt==NULL is treated differently from pkt->size==0 // (pkt==NULL means get more output, pkt->size==0 is a flush/drain packet) -static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt) +static int decode(InputStream *ist, AVCodecContext *avctx, + AVFrame *frame, int *got_frame, AVPacket *pkt) { int ret; @@ -2270,8 +2282,24 @@ static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacke ret = avcodec_receive_frame(avctx, frame); if (ret < 0 && ret != AVERROR(EAGAIN)) return ret; - if (ret >= 0) + if (ret >= 0) { + if (ist->want_frame_data) { + FrameData *fd; + + av_assert0(!frame->opaque_ref); + frame->opaque_ref = av_buffer_allocz(sizeof(*fd)); + if (!frame->opaque_ref) { + av_frame_unref(frame); + return AVERROR(ENOMEM); + } + fd = (FrameData*)frame->opaque_ref->data; + fd->pts = frame->pts; + fd->tb = avctx->pkt_timebase; + fd->idx = avctx->frame_num - 1; + } + *got_frame = 1; + } return 0; } @@ -2303,16 +2331,11 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, AVRational decoded_frame_tb; update_benchmark(NULL); - ret = decode(avctx, decoded_frame, got_output, pkt); + ret = decode(ist, avctx, decoded_frame, got_output, pkt); update_benchmark("decode_audio %d.%d", ist->file_index, ist->st->index); if (ret < 0) *decode_failed = 1; - if (ret >= 0 && avctx->sample_rate <= 0) { - av_log(avctx, AV_LOG_ERROR, "Sample rate %d invalid\n", avctx->sample_rate); - ret = AVERROR_INVALIDDATA; - } - if (ret != AVERROR_EOF) check_decode_result(ist, got_output, ret); @@ -2325,9 +2348,9 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, /* increment next_dts to use for the case where the input stream does not have timestamps or there are multiple frames in the packet */ ist->next_pts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / - avctx->sample_rate; + decoded_frame->sample_rate; ist->next_dts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / - avctx->sample_rate; + decoded_frame->sample_rate; if (decoded_frame->pts != AV_NOPTS_VALUE) { decoded_frame_tb = ist->st->time_base; @@ -2345,8 +2368,10 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, ist->prev_pkt_pts = pkt->pts; if (decoded_frame->pts != AV_NOPTS_VALUE) decoded_frame->pts = av_rescale_delta(decoded_frame_tb, decoded_frame->pts, - (AVRational){1, avctx->sample_rate}, decoded_frame->nb_samples, &ist->filter_in_rescale_delta_last, - (AVRational){1, avctx->sample_rate}); + (AVRational){1, decoded_frame->sample_rate}, + decoded_frame->nb_samples, + &ist->filter_in_rescale_delta_last, + (AVRational){1, decoded_frame->sample_rate}); ist->nb_samples = decoded_frame->nb_samples; err = send_frame_to_filters(ist, decoded_frame); @@ -2385,16 +2410,16 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ } update_benchmark(NULL); - ret = decode(ist->dec_ctx, decoded_frame, got_output, pkt); + ret = decode(ist, ist->dec_ctx, decoded_frame, got_output, pkt); update_benchmark("decode_video %d.%d", ist->file_index, ist->st->index); if (ret < 0) *decode_failed = 1; // The following line may be required in some cases where there is no parser // or the parser does not has_b_frames correctly - if (ist->st->codecpar->video_delay < ist->dec_ctx->has_b_frames) { + if (ist->par->video_delay < ist->dec_ctx->has_b_frames) { if (ist->dec_ctx->codec_id == AV_CODEC_ID_H264) { - ist->st->codecpar->video_delay = ist->dec_ctx->has_b_frames; + ist->par->video_delay = ist->dec_ctx->has_b_frames; } else av_log(ist->dec_ctx, AV_LOG_WARNING, "video_delay is larger in decoder than demuxer %d > %d.\n" @@ -2402,7 +2427,7 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ "of this file to https://streams.videolan.org/upload/ " "and contact the ffmpeg-devel mailing list. (ffmpeg-devel@ffmpeg.org)\n", ist->dec_ctx->has_b_frames, - ist->st->codecpar->video_delay); + ist->par->video_delay); } if (ret != AVERROR_EOF) @@ -2435,10 +2460,9 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ if (err < 0) goto fail; } - ist->hwaccel_retrieved_pix_fmt = decoded_frame->format; best_effort_timestamp= decoded_frame->best_effort_timestamp; - *duration_pts = decoded_frame->pkt_duration; + *duration_pts = decoded_frame->duration; if (ist->framerate.num) best_effort_timestamp = ist->cfr_next_pts++; @@ -2479,30 +2503,18 @@ fail: return err < 0 ? err : ret; } -static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, - int *decode_failed) +static int process_subtitle(InputStream *ist, AVSubtitle *subtitle, int *got_output) { - AVSubtitle subtitle; + int ret = 0; int free_sub = 1; - int i, ret = avcodec_decode_subtitle2(ist->dec_ctx, - &subtitle, got_output, pkt); - - check_decode_result(NULL, got_output, ret); - - if (ret < 0 || !*got_output) { - *decode_failed = 1; - if (!pkt->size) - sub2video_flush(ist); - return ret; - } if (ist->fix_sub_duration) { int end = 1; if (ist->prev_sub.got_output) { - end = av_rescale(subtitle.pts - ist->prev_sub.subtitle.pts, + end = av_rescale(subtitle->pts - ist->prev_sub.subtitle.pts, 1000, AV_TIME_BASE); if (end < ist->prev_sub.subtitle.end_display_time) { - av_log(ist->dec_ctx, AV_LOG_DEBUG, + av_log(NULL, AV_LOG_DEBUG, "Subtitle duration reduced from %"PRId32" to %d%s\n", ist->prev_sub.subtitle.end_display_time, end, end <= 0 ? ", dropping it" : ""); @@ -2511,7 +2523,7 @@ static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, } FFSWAP(int, *got_output, ist->prev_sub.got_output); FFSWAP(int, ret, ist->prev_sub.ret); - FFSWAP(AVSubtitle, subtitle, ist->prev_sub.subtitle); + FFSWAP(AVSubtitle, *subtitle, ist->prev_sub.subtitle); if (end <= 0) goto out; } @@ -2520,40 +2532,187 @@ static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, return ret; if (ist->sub2video.frame) { - sub2video_update(ist, INT64_MIN, &subtitle); + sub2video_update(ist, INT64_MIN, subtitle); } else if (ist->nb_filters) { if (!ist->sub2video.sub_queue) ist->sub2video.sub_queue = av_fifo_alloc2(8, sizeof(AVSubtitle), AV_FIFO_FLAG_AUTO_GROW); if (!ist->sub2video.sub_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); - ret = av_fifo_write(ist->sub2video.sub_queue, &subtitle, 1); + ret = av_fifo_write(ist->sub2video.sub_queue, subtitle, 1); if (ret < 0) exit_program(1); free_sub = 0; } - if (!subtitle.num_rects) + if (!subtitle->num_rects) goto out; - ist->frames_decoded++; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!check_output_constraints(ist, ost) || !ost->encoding_needed - || ost->enc->type != AVMEDIA_TYPE_SUBTITLE) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (!check_output_constraints(ist, ost) || !ost->enc_ctx + || ost->enc_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE) continue; - do_subtitle_out(output_files[ost->file_index], ost, &subtitle); + do_subtitle_out(output_files[ost->file_index], ost, subtitle); } out: if (free_sub) - avsubtitle_free(&subtitle); + avsubtitle_free(subtitle); + return ret; +} + +static int copy_av_subtitle(AVSubtitle *dst, AVSubtitle *src) +{ + int ret = AVERROR_BUG; + AVSubtitle tmp = { + .format = src->format, + .start_display_time = src->start_display_time, + .end_display_time = src->end_display_time, + .num_rects = 0, + .rects = NULL, + .pts = src->pts + }; + + if (!src->num_rects) + goto success; + + if (!(tmp.rects = av_calloc(src->num_rects, sizeof(*tmp.rects)))) + return AVERROR(ENOMEM); + + for (int i = 0; i < src->num_rects; i++) { + AVSubtitleRect *src_rect = src->rects[i]; + AVSubtitleRect *dst_rect; + + if (!(dst_rect = tmp.rects[i] = av_mallocz(sizeof(*tmp.rects[0])))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + tmp.num_rects++; + + dst_rect->type = src_rect->type; + dst_rect->flags = src_rect->flags; + + dst_rect->x = src_rect->x; + dst_rect->y = src_rect->y; + dst_rect->w = src_rect->w; + dst_rect->h = src_rect->h; + dst_rect->nb_colors = src_rect->nb_colors; + + if (src_rect->text) + if (!(dst_rect->text = av_strdup(src_rect->text))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + if (src_rect->ass) + if (!(dst_rect->ass = av_strdup(src_rect->ass))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + for (int j = 0; j < 4; j++) { + // SUBTITLE_BITMAP images are special in the sense that they + // are like PAL8 images. first pointer to data, second to + // palette. This makes the size calculation match this. + size_t buf_size = src_rect->type == SUBTITLE_BITMAP && j == 1 ? + AVPALETTE_SIZE : + src_rect->h * src_rect->linesize[j]; + + if (!src_rect->data[j]) + continue; + + if (!(dst_rect->data[j] = av_memdup(src_rect->data[j], buf_size))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + dst_rect->linesize[j] = src_rect->linesize[j]; + } + } + +success: + *dst = tmp; + + return 0; + +cleanup: + avsubtitle_free(&tmp); + return ret; } +static int fix_sub_duration_heartbeat(InputStream *ist, int64_t signal_pts) +{ + int ret = AVERROR_BUG; + int got_output = 1; + AVSubtitle *prev_subtitle = &ist->prev_sub.subtitle; + AVSubtitle subtitle; + + if (!ist->fix_sub_duration || !prev_subtitle->num_rects || + signal_pts <= prev_subtitle->pts) + return 0; + + if ((ret = copy_av_subtitle(&subtitle, prev_subtitle)) < 0) + return ret; + + subtitle.pts = signal_pts; + + return process_subtitle(ist, &subtitle, &got_output); +} + +static int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt) +{ + OutputFile *of = output_files[ost->file_index]; + int64_t signal_pts = av_rescale_q(pkt->pts, pkt->time_base, + AV_TIME_BASE_Q); + + if (!ost->fix_sub_duration_heartbeat || !(pkt->flags & AV_PKT_FLAG_KEY)) + // we are only interested in heartbeats on streams configured, and + // only on random access points. + return 0; + + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *iter_ost = of->streams[i]; + InputStream *ist = iter_ost->ist; + int ret = AVERROR_BUG; + + if (iter_ost == ost || !ist || !ist->decoding_needed || + ist->dec_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE) + // We wish to skip the stream that causes the heartbeat, + // output streams without an input stream, streams not decoded + // (as fix_sub_duration is only done for decoded subtitles) as + // well as non-subtitle streams. + continue; + + if ((ret = fix_sub_duration_heartbeat(ist, signal_pts)) < 0) + return ret; + } + + return 0; +} + +static int transcode_subtitles(InputStream *ist, const AVPacket *pkt, + int *got_output, int *decode_failed) +{ + AVSubtitle subtitle; + int ret = avcodec_decode_subtitle2(ist->dec_ctx, + &subtitle, got_output, pkt); + + check_decode_result(NULL, got_output, ret); + + if (ret < 0 || !*got_output) { + *decode_failed = 1; + if (!pkt->size) + sub2video_flush(ist); + return ret; + } + + ist->frames_decoded++; + + return process_subtitle(ist, &subtitle, got_output); +} + static int send_filter_eof(InputStream *ist) { int i, ret; @@ -2572,7 +2731,8 @@ static int send_filter_eof(InputStream *ist) /* pkt = NULL means EOF (needed to flush decoder buffers) */ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof) { - int ret = 0, i; + const AVCodecParameters *par = ist->par; + int ret = 0; int repeating = 0; int eof_reached = 0; @@ -2604,7 +2764,7 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo if (pkt && pkt->dts != AV_NOPTS_VALUE) { ist->next_dts = ist->dts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); - if (ist->dec_ctx->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) + if (par->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) ist->next_pts = ist->pts = ist->dts; } @@ -2618,7 +2778,7 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo ist->pts = ist->next_pts; ist->dts = ist->next_dts; - switch (ist->dec_ctx->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: ret = decode_audio (ist, repeating ? NULL : avpkt, &got_output, &decode_failed); @@ -2631,7 +2791,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo if (pkt && pkt->duration) { duration_dts = av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } else if(ist->dec_ctx->framerate.num != 0 && ist->dec_ctx->framerate.den != 0) { - int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict+1 : ist->dec_ctx->ticks_per_frame; + int ticks = ist->last_pkt_repeat_pict >= 0 ? + ist->last_pkt_repeat_pict + 1 : + ist->dec_ctx->ticks_per_frame; duration_dts = ((int64_t)AV_TIME_BASE * ist->dec_ctx->framerate.den * ticks) / ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; @@ -2715,12 +2877,12 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo /* handle stream copy */ if (!ist->decoding_needed && pkt) { ist->dts = ist->next_dts; - switch (ist->dec_ctx->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: av_assert1(pkt->duration >= 0); - if (ist->dec_ctx->sample_rate) { - ist->next_dts += ((int64_t)AV_TIME_BASE * ist->dec_ctx->frame_size) / - ist->dec_ctx->sample_rate; + if (par->sample_rate) { + ist->next_dts += ((int64_t)AV_TIME_BASE * par->frame_size) / + par->sample_rate; } else { ist->next_dts += av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } @@ -2734,7 +2896,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo } else if (pkt->duration) { ist->next_dts += av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } else if(ist->dec_ctx->framerate.num != 0) { - int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict + 1 : ist->dec_ctx->ticks_per_frame; + int ticks = ist->last_pkt_repeat_pict >= 0 ? + ist->last_pkt_repeat_pict + 1 : + ist->dec_ctx->ticks_per_frame; ist->next_dts += ((int64_t)AV_TIME_BASE * ist->dec_ctx->framerate.den * ticks) / ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; @@ -2746,10 +2910,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo } else if (!ist->decoding_needed) eof_reached = 1; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!check_output_constraints(ist, ost) || ost->encoding_needed) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (!check_output_constraints(ist, ost) || ost->enc_ctx || + (!pkt && no_eof)) continue; do_streamcopy(ist, ost, pkt); @@ -2807,10 +2970,9 @@ static enum AVPixelFormat get_format(AVCodecContext *s, const enum AVPixelFormat return *p; } -static int init_input_stream(int ist_index, char *error, int error_len) +static int init_input_stream(InputStream *ist, char *error, int error_len) { int ret; - InputStream *ist = input_streams[ist_index]; if (ist->decoding_needed) { const AVCodec *codec = ist->dec; @@ -2822,11 +2984,6 @@ static int init_input_stream(int ist_index, char *error, int error_len) ist->dec_ctx->opaque = ist; ist->dec_ctx->get_format = get_format; -#if LIBAVCODEC_VERSION_MAJOR < 60 - AV_NOWARN_DEPRECATED({ - ist->dec_ctx->thread_safe_callbacks = 1; - }) -#endif if (ist->dec_ctx->codec_id == AV_CODEC_ID_DVB_SUBTITLE && (ist->decoding_needed & DECODING_FOR_OST)) { @@ -2872,71 +3029,37 @@ static int init_input_stream(int ist_index, char *error, int error_len) return 0; } -static InputStream *get_input_stream(OutputStream *ost) -{ - if (ost->source_index >= 0) - return input_streams[ost->source_index]; - return NULL; -} - -static int compare_int64(const void *a, const void *b) -{ - return FFDIFFSIGN(*(const int64_t *)a, *(const int64_t *)b); -} - -static int init_output_bsfs(OutputStream *ost) -{ - AVBSFContext *ctx = ost->bsf_ctx; - int ret; - - if (!ctx) - return 0; - - ret = avcodec_parameters_copy(ctx->par_in, ost->st->codecpar); - if (ret < 0) - return ret; - - ctx->time_base_in = ost->st->time_base; - - ret = av_bsf_init(ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing bitstream filter: %s\n", - ctx->filter->name); - return ret; - } - - ret = avcodec_parameters_copy(ost->st->codecpar, ctx->par_out); - if (ret < 0) - return ret; - ost->st->time_base = ctx->time_base_out; - - return 0; -} - static int init_output_stream_streamcopy(OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - InputStream *ist = get_input_stream(ost); - AVCodecParameters *par_dst = ost->st->codecpar; - AVCodecParameters *par_src = ost->ref_par; + InputStream *ist = ost->ist; + InputFile *ifile = input_files[ist->file_index]; + AVCodecParameters *par = ost->st->codecpar; + AVCodecContext *codec_ctx; AVRational sar; int i, ret; - uint32_t codec_tag = par_dst->codec_tag; + uint32_t codec_tag = par->codec_tag; av_assert0(ist && !ost->filter); - ret = avcodec_parameters_to_context(ost->enc_ctx, ist->st->codecpar); + codec_ctx = avcodec_alloc_context3(NULL); + if (!codec_ctx) + return AVERROR(ENOMEM); + + ret = avcodec_parameters_to_context(codec_ctx, ist->par); if (ret >= 0) - ret = av_opt_set_dict(ost->enc_ctx, &ost->encoder_opts); + ret = av_opt_set_dict(codec_ctx, &ost->encoder_opts); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error setting up codec context options.\n"); + avcodec_free_context(&codec_ctx); return ret; } - ret = avcodec_parameters_from_context(par_src, ost->enc_ctx); + ret = avcodec_parameters_from_context(par, codec_ctx); + avcodec_free_context(&codec_ctx); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error getting reference codec parameters.\n"); return ret; } @@ -2944,16 +3067,12 @@ static int init_output_stream_streamcopy(OutputStream *ost) if (!codec_tag) { unsigned int codec_tag_tmp; if (!of->format->codec_tag || - av_codec_get_id (of->format->codec_tag, par_src->codec_tag) == par_src->codec_id || - !av_codec_get_tag2(of->format->codec_tag, par_src->codec_id, &codec_tag_tmp)) - codec_tag = par_src->codec_tag; + av_codec_get_id (of->format->codec_tag, par->codec_tag) == par->codec_id || + !av_codec_get_tag2(of->format->codec_tag, par->codec_id, &codec_tag_tmp)) + codec_tag = par->codec_tag; } - ret = avcodec_parameters_copy(par_dst, par_src); - if (ret < 0) - return ret; - - par_dst->codec_tag = codec_tag; + par->codec_tag = codec_tag; if (!ost->frame_rate.num) ost->frame_rate = ist->framerate; @@ -2979,6 +3098,15 @@ static int init_output_stream_streamcopy(OutputStream *ost) if (ost->st->duration <= 0 && ist->st->duration > 0) ost->st->duration = av_rescale_q(ist->st->duration, ist->st->time_base, ost->st->time_base); + if (!ost->copy_prior_start) { + ost->ts_copy_start = (of->start_time == AV_NOPTS_VALUE) ? + 0 : of->start_time; + if (copy_ts && ifile->start_time != AV_NOPTS_VALUE) { + ost->ts_copy_start = FFMAX(ost->ts_copy_start, + ifile->start_time + ifile->ts_offset); + } + } + if (ist->st->nb_side_data) { for (i = 0; i < ist->st->nb_side_data; i++) { const AVPacketSideData *sd_src = &ist->st->side_data[i]; @@ -2991,37 +3119,36 @@ static int init_output_stream_streamcopy(OutputStream *ost) } } +#if FFMPEG_ROTATION_METADATA if (ost->rotate_overridden) { uint8_t *sd = av_stream_new_side_data(ost->st, AV_PKT_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); if (sd) av_display_rotation_set((int32_t *)sd, -ost->rotate_override_value); } +#endif - switch (par_dst->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: - if (audio_volume != 256) { - av_log(NULL, AV_LOG_FATAL, "-acodec copy and -vol are incompatible (frames are not decoded)\n"); - exit_program(1); - } - if((par_dst->block_align == 1 || par_dst->block_align == 1152 || par_dst->block_align == 576) && par_dst->codec_id == AV_CODEC_ID_MP3) - par_dst->block_align= 0; - if(par_dst->codec_id == AV_CODEC_ID_AC3) - par_dst->block_align= 0; + if ((par->block_align == 1 || par->block_align == 1152 || par->block_align == 576) && + par->codec_id == AV_CODEC_ID_MP3) + par->block_align = 0; + if (par->codec_id == AV_CODEC_ID_AC3) + par->block_align = 0; break; case AVMEDIA_TYPE_VIDEO: if (ost->frame_aspect_ratio.num) { // overridden by the -aspect cli option sar = av_mul_q(ost->frame_aspect_ratio, - (AVRational){ par_dst->height, par_dst->width }); - av_log(NULL, AV_LOG_WARNING, "Overriding aspect ratio " + (AVRational){ par->height, par->width }); + av_log(ost, AV_LOG_WARNING, "Overriding aspect ratio " "with stream copy may produce invalid files\n"); } else if (ist->st->sample_aspect_ratio.num) sar = ist->st->sample_aspect_ratio; else - sar = par_src->sample_aspect_ratio; - ost->st->sample_aspect_ratio = par_dst->sample_aspect_ratio = sar; + sar = par->sample_aspect_ratio; + ost->st->sample_aspect_ratio = par->sample_aspect_ratio = sar; ost->st->avg_frame_rate = ist->st->avg_frame_rate; ost->st->r_frame_rate = ist->st->r_frame_rate; break; @@ -3034,113 +3161,31 @@ static int init_output_stream_streamcopy(OutputStream *ost) static void set_encoder_id(OutputFile *of, OutputStream *ost) { - const AVDictionaryEntry *e; - + const char *cname = ost->enc_ctx->codec->name; uint8_t *encoder_string; int encoder_string_len; - int format_flags = 0; - int codec_flags = ost->enc_ctx->flags; if (av_dict_get(ost->st->metadata, "encoder", NULL, 0)) return; - e = av_dict_get(of->opts, "fflags", NULL, 0); - if (e) { - const AVOption *o = av_opt_find(of->ctx, "fflags", NULL, 0, 0); - if (!o) - return; - av_opt_eval_flags(of->ctx, o, e->value, &format_flags); - } - e = av_dict_get(ost->encoder_opts, "flags", NULL, 0); - if (e) { - const AVOption *o = av_opt_find(ost->enc_ctx, "flags", NULL, 0, 0); - if (!o) - return; - av_opt_eval_flags(ost->enc_ctx, o, e->value, &codec_flags); - } - - encoder_string_len = sizeof(LIBAVCODEC_IDENT) + strlen(ost->enc->name) + 2; + encoder_string_len = sizeof(LIBAVCODEC_IDENT) + strlen(cname) + 2; encoder_string = av_mallocz(encoder_string_len); if (!encoder_string) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); - if (!(format_flags & AVFMT_FLAG_BITEXACT) && !(codec_flags & AV_CODEC_FLAG_BITEXACT)) + if (!of->bitexact && !ost->bitexact) av_strlcpy(encoder_string, LIBAVCODEC_IDENT " ", encoder_string_len); else av_strlcpy(encoder_string, "Lavc ", encoder_string_len); - av_strlcat(encoder_string, ost->enc->name, encoder_string_len); + av_strlcat(encoder_string, cname, encoder_string_len); av_dict_set(&ost->st->metadata, "encoder", encoder_string, AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_OVERWRITE); } -static void parse_forced_key_frames(char *kf, OutputStream *ost, - AVCodecContext *avctx) -{ - char *p; - int n = 1, i, size, index = 0; - int64_t t, *pts; - - for (p = kf; *p; p++) - if (*p == ',') - n++; - size = n; - pts = av_malloc_array(size, sizeof(*pts)); - if (!pts) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate forced key frames array.\n"); - exit_program(1); - } - - p = kf; - for (i = 0; i < n; i++) { - char *next = strchr(p, ','); - - if (next) - *next++ = 0; - - if (!memcmp(p, "chapters", 8)) { - - AVFormatContext *avf = output_files[ost->file_index]->ctx; - int j; - - if (avf->nb_chapters > INT_MAX - size || - !(pts = av_realloc_f(pts, size += avf->nb_chapters - 1, - sizeof(*pts)))) { - av_log(NULL, AV_LOG_FATAL, - "Could not allocate forced key frames array.\n"); - exit_program(1); - } - t = p[8] ? parse_time_or_die("force_key_frames", p + 8, 1) : 0; - t = av_rescale_q(t, AV_TIME_BASE_Q, avctx->time_base); - - for (j = 0; j < avf->nb_chapters; j++) { - AVChapter *c = avf->chapters[j]; - av_assert1(index < size); - pts[index++] = av_rescale_q(c->start, c->time_base, - avctx->time_base) + t; - } - - } else { - - t = parse_time_or_die("force_key_frames", p, 1); - av_assert1(index < size); - pts[index++] = av_rescale_q(t, AV_TIME_BASE_Q, avctx->time_base); - - } - - p = next; - } - - av_assert0(index == size); - qsort(pts, size, sizeof(*pts), compare_int64); - ost->forced_kf_count = size; - ost->forced_kf_pts = pts; -} - static void init_encoder_time_base(OutputStream *ost, AVRational default_time_base) { - InputStream *ist = get_input_stream(ost); + InputStream *ist = ost->ist; AVCodecContext *enc_ctx = ost->enc_ctx; - AVFormatContext *oc; if (ost->enc_timebase.num > 0) { enc_ctx->time_base = ost->enc_timebase; @@ -3153,8 +3198,8 @@ static void init_encoder_time_base(OutputStream *ost, AVRational default_time_ba return; } - oc = output_files[ost->file_index]->ctx; - av_log(oc, AV_LOG_WARNING, "Input stream data not available, using default time base\n"); + av_log(ost, AV_LOG_WARNING, + "Input stream data not available, using default time base\n"); } enc_ctx->time_base = default_time_base; @@ -3162,11 +3207,10 @@ static void init_encoder_time_base(OutputStream *ost, AVRational default_time_ba static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) { - InputStream *ist = get_input_stream(ost); + InputStream *ist = ost->ist; AVCodecContext *enc_ctx = ost->enc_ctx; AVCodecContext *dec_ctx = NULL; OutputFile *of = output_files[ost->file_index]; - AVFormatContext *oc = of->ctx; int ret; set_encoder_id(output_files[ost->file_index], ost); @@ -3178,14 +3222,13 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) if (enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { if (!ost->frame_rate.num) ost->frame_rate = av_buffersink_get_frame_rate(ost->filter->filter); - if (ist && !ost->frame_rate.num && !ost->max_frame_rate.num) { + if (!ost->frame_rate.num && !ost->max_frame_rate.num) { ost->frame_rate = (AVRational){25, 1}; - av_log(NULL, AV_LOG_WARNING, + av_log(ost, AV_LOG_WARNING, "No information " "about the input framerate is available. Falling " - "back to a default value of 25fps for output stream #%d:%d. Use the -r option " - "if you want a different framerate.\n", - ost->file_index, ost->index); + "back to a default value of 25fps. Use the -r option " + "if you want a different framerate.\n"); } if (ost->max_frame_rate.num && @@ -3193,9 +3236,9 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) !ost->frame_rate.den)) ost->frame_rate = ost->max_frame_rate; - if (ost->enc->supported_framerates && !ost->force_fps) { - int idx = av_find_nearest_q_idx(ost->frame_rate, ost->enc->supported_framerates); - ost->frame_rate = ost->enc->supported_framerates[idx]; + if (enc_ctx->codec->supported_framerates && !ost->force_fps) { + int idx = av_find_nearest_q_idx(ost->frame_rate, enc_ctx->codec->supported_framerates); + ost->frame_rate = enc_ctx->codec->supported_framerates[idx]; } // reduce frame rate for mpeg4 to be within the spec limits if (enc_ctx->codec_id == AV_CODEC_ID_MPEG4) { @@ -3229,9 +3272,9 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) if ( av_q2d(enc_ctx->time_base) < 0.001 && ost->vsync_method != VSYNC_PASSTHROUGH && (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR || (ost->vsync_method == VSYNC_AUTO && !(of->format->flags & AVFMT_VARIABLE_FPS)))){ - av_log(oc, AV_LOG_WARNING, "Frame rate very high for a muxer not efficiently supporting it.\n" - "Please consider specifying a lower framerate, a different muxer or " - "setting vsync/fps_mode to vfr\n"); + av_log(ost, AV_LOG_WARNING, "Frame rate very high for a muxer not efficiently supporting it.\n" + "Please consider specifying a lower framerate, a different muxer or " + "setting vsync/fps_mode to vfr\n"); } enc_ctx->width = av_buffersink_get_w(ost->filter->filter); @@ -3283,33 +3326,41 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) enc_ctx->field_order = AV_FIELD_TT; } - if (ost->forced_keyframes) { - if (!strncmp(ost->forced_keyframes, "expr:", 5)) { - ret = av_expr_parse(&ost->forced_keyframes_pexpr, ost->forced_keyframes+5, - forced_keyframes_const_names, NULL, NULL, NULL, NULL, 0, NULL); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, - "Invalid force_key_frames expression '%s'\n", ost->forced_keyframes+5); - return ret; - } - ost->forced_keyframes_expr_const_values[FKF_N] = 0; - ost->forced_keyframes_expr_const_values[FKF_N_FORCED] = 0; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N] = NAN; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T] = NAN; - - // Don't parse the 'forced_keyframes' in case of 'keep-source-keyframes', - // parse it only for static kf timings - } else if(strncmp(ost->forced_keyframes, "source", 6)) { - parse_forced_key_frames(ost->forced_keyframes, ost, ost->enc_ctx); - } - } break; case AVMEDIA_TYPE_SUBTITLE: enc_ctx->time_base = AV_TIME_BASE_Q; if (!enc_ctx->width) { - enc_ctx->width = input_streams[ost->source_index]->st->codecpar->width; - enc_ctx->height = input_streams[ost->source_index]->st->codecpar->height; + enc_ctx->width = ost->ist->par->width; + enc_ctx->height = ost->ist->par->height; + } + if (dec_ctx && dec_ctx->subtitle_header) { + /* ASS code assumes this buffer is null terminated so add extra byte. */ + ost->enc_ctx->subtitle_header = av_mallocz(dec_ctx->subtitle_header_size + 1); + if (!ost->enc_ctx->subtitle_header) + return AVERROR(ENOMEM); + memcpy(ost->enc_ctx->subtitle_header, dec_ctx->subtitle_header, + dec_ctx->subtitle_header_size); + ost->enc_ctx->subtitle_header_size = dec_ctx->subtitle_header_size; + } + if (ist && ist->dec->type == AVMEDIA_TYPE_SUBTITLE && + enc_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { + int input_props = 0, output_props = 0; + AVCodecDescriptor const *input_descriptor = + avcodec_descriptor_get(ist->dec->id); + AVCodecDescriptor const *output_descriptor = + avcodec_descriptor_get(ost->enc_ctx->codec_id); + if (input_descriptor) + input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (output_descriptor) + output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (input_props && output_props && input_props != output_props) { + av_log(ost, AV_LOG_ERROR, + "Subtitle encoding currently only possible from text to text " + "or bitmap to bitmap"); + return AVERROR_INVALIDDATA; + } } + break; case AVMEDIA_TYPE_DATA: break; @@ -3318,6 +3369,12 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) break; } + if (ost->bitexact) + enc_ctx->flags |= AV_CODEC_FLAG_BITEXACT; + + if (ost->sq_idx_encode >= 0) + sq_set_tb(of->sq_encode, ost->sq_idx_encode, enc_ctx->time_base); + ost->mux_timebase = enc_ctx->time_base; return 0; @@ -3328,28 +3385,23 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, { int ret = 0; - if (ost->encoding_needed) { - const AVCodec *codec = ost->enc; - AVCodecContext *dec = NULL; - InputStream *ist; + if (ost->enc_ctx) { + const AVCodec *codec = ost->enc_ctx->codec; + InputStream *ist = ost->ist; ret = init_output_stream_encode(ost, frame); if (ret < 0) return ret; - if ((ist = get_input_stream(ost))) - dec = ist->dec_ctx; - if (dec && dec->subtitle_header) { - /* ASS code assumes this buffer is null terminated so add extra byte. */ - ost->enc_ctx->subtitle_header = av_mallocz(dec->subtitle_header_size + 1); - if (!ost->enc_ctx->subtitle_header) - return AVERROR(ENOMEM); - memcpy(ost->enc_ctx->subtitle_header, dec->subtitle_header, dec->subtitle_header_size); - ost->enc_ctx->subtitle_header_size = dec->subtitle_header_size; - } if (!av_dict_get(ost->encoder_opts, "threads", NULL, 0)) av_dict_set(&ost->encoder_opts, "threads", "auto", 0); + if (codec->capabilities & AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE) { + ret = av_dict_set(&ost->encoder_opts, "flags", "+copy_opaque", AV_DICT_MULTIKEY); + if (ret < 0) + return ret; + } + ret = hw_device_setup_for_encode(ost); if (ret < 0) { snprintf(error, error_len, "Device setup failed for " @@ -3358,24 +3410,6 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, return ret; } - if (ist && ist->dec->type == AVMEDIA_TYPE_SUBTITLE && ost->enc->type == AVMEDIA_TYPE_SUBTITLE) { - int input_props = 0, output_props = 0; - AVCodecDescriptor const *input_descriptor = - avcodec_descriptor_get(dec->codec_id); - AVCodecDescriptor const *output_descriptor = - avcodec_descriptor_get(ost->enc_ctx->codec_id); - if (input_descriptor) - input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (output_descriptor) - output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (input_props && output_props && input_props != output_props) { - snprintf(error, error_len, - "Subtitle encoding currently only possible from text to text " - "or bitmap to bitmap"); - return AVERROR_INVALIDDATA; - } - } - if ((ret = avcodec_open2(ost->enc_ctx, codec, &ost->encoder_opts)) < 0) { if (ret == AVERROR_EXPERIMENTAL) abort_codec_experimental(codec, 1); @@ -3385,19 +3419,19 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, ost->file_index, ost->index); return ret; } - if (ost->enc->type == AVMEDIA_TYPE_AUDIO && - !(ost->enc->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + if (codec->type == AVMEDIA_TYPE_AUDIO && + !(codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) av_buffersink_set_frame_size(ost->filter->filter, ost->enc_ctx->frame_size); assert_avoptions(ost->encoder_opts); if (ost->enc_ctx->bit_rate && ost->enc_ctx->bit_rate < 1000 && ost->enc_ctx->codec_id != AV_CODEC_ID_CODEC2 /* don't complain about 700 bit/s modes */) - av_log(NULL, AV_LOG_WARNING, "The bitrate parameter is set too low." - " It takes bits/s as argument, not kbits/s\n"); + av_log(ost, AV_LOG_WARNING, "The bitrate parameter is set too low." + " It takes bits/s as argument, not kbits/s\n"); ret = avcodec_parameters_from_context(ost->st->codecpar, ost->enc_ctx); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error initializing the output stream codec context.\n"); exit_program(1); } @@ -3433,7 +3467,7 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, return AVERROR(ENOMEM); memcpy(dst, sd->data, sd->size); if (ist->autorotate && sd->type == AV_PKT_DATA_DISPLAYMATRIX) - av_display_rotation_set((uint32_t *)dst, 0); + av_display_rotation_set((int32_t *)dst, 0); } } } @@ -3445,83 +3479,36 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, // copy estimated duration as a hint to the muxer if (ost->st->duration <= 0 && ist && ist->st->duration > 0) ost->st->duration = av_rescale_q(ist->st->duration, ist->st->time_base, ost->st->time_base); - } else if (ost->stream_copy) { + } else if (ost->ist) { ret = init_output_stream_streamcopy(ost); if (ret < 0) return ret; } - /* initialize bitstream filters for the output stream - * needs to be done here, because the codec id for streamcopy is not - * known until now */ - ret = init_output_bsfs(ost); - if (ret < 0) - return ret; - - ost->initialized = 1; - - ret = of_check_init(output_files[ost->file_index]); + ret = of_stream_init(output_files[ost->file_index], ost); if (ret < 0) return ret; return ret; } -static void report_new_stream(int input_index, AVPacket *pkt) -{ - InputFile *file = input_files[input_index]; - AVStream *st = file->ctx->streams[pkt->stream_index]; - - if (pkt->stream_index < file->nb_streams_warn) - return; - av_log(file->ctx, AV_LOG_WARNING, - "New %s stream %d:%d at pos:%"PRId64" and DTS:%ss\n", - av_get_media_type_string(st->codecpar->codec_type), - input_index, pkt->stream_index, - pkt->pos, av_ts2timestr(pkt->dts, &st->time_base)); - file->nb_streams_warn = pkt->stream_index + 1; -} - static int transcode_init(void) { - int ret = 0, i, j, k; - AVFormatContext *oc; - OutputStream *ost; - InputStream *ist; + int ret = 0; char error[1024] = {0}; - for (i = 0; i < nb_filtergraphs; i++) { - FilterGraph *fg = filtergraphs[i]; - for (j = 0; j < fg->nb_outputs; j++) { - OutputFilter *ofilter = fg->outputs[j]; - if (!ofilter->ost || ofilter->ost->source_index >= 0) - continue; - if (fg->nb_inputs != 1) - continue; - for (k = nb_input_streams-1; k >= 0 ; k--) - if (fg->inputs[0]->ist == input_streams[k]) - break; - ofilter->ost->source_index = k; - } - } - /* init framerate emulation */ - for (i = 0; i < nb_input_files; i++) { + for (int i = 0; i < nb_input_files; i++) { InputFile *ifile = input_files[i]; if (ifile->readrate || ifile->rate_emu) - for (j = 0; j < ifile->nb_streams; j++) - input_streams[j + ifile->ist_index]->start = av_gettime_relative(); + for (int j = 0; j < ifile->nb_streams; j++) + ifile->streams[j]->start = av_gettime_relative(); } /* init input streams */ - for (i = 0; i < nb_input_streams; i++) - if ((ret = init_input_stream(i, error, sizeof(error))) < 0) { - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - avcodec_close(ost->enc_ctx); - } + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + if ((ret = init_input_stream(ist, error, sizeof(error))) < 0) goto dump_format; - } /* * initialize stream copy and subtitle/data streams. @@ -3532,26 +3519,26 @@ static int transcode_init(void) * to be configured with the correct audio frame size, which is only * known after the encoder is initialized. */ - for (i = 0; i < nb_output_streams; i++) { - if (!output_streams[i]->stream_copy && - (output_streams[i]->enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - output_streams[i]->enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO)) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->enc_ctx && + (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)) continue; - ret = init_output_stream_wrapper(output_streams[i], NULL, 0); + ret = init_output_stream_wrapper(ost, NULL, 0); if (ret < 0) goto dump_format; } /* discard unused programs */ - for (i = 0; i < nb_input_files; i++) { + for (int i = 0; i < nb_input_files; i++) { InputFile *ifile = input_files[i]; - for (j = 0; j < ifile->ctx->nb_programs; j++) { + for (int j = 0; j < ifile->ctx->nb_programs; j++) { AVProgram *p = ifile->ctx->programs[j]; int discard = AVDISCARD_ALL; - for (k = 0; k < p->nb_stream_indexes; k++) - if (!input_streams[ifile->ist_index + p->stream_index[k]]->discard) { + for (int k = 0; k < p->nb_stream_indexes; k++) + if (!ifile->streams[p->stream_index[k]]->discard) { discard = AVDISCARD_DEFAULT; break; } @@ -3559,23 +3546,11 @@ static int transcode_init(void) } } - /* write headers for files with no streams */ - for (i = 0; i < nb_output_files; i++) { - oc = output_files[i]->ctx; - if (output_files[i]->format->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) { - ret = of_check_init(output_files[i]); - if (ret < 0) - goto dump_format; - } - } - dump_format: /* dump the stream mapping */ av_log(NULL, AV_LOG_INFO, "Stream mapping:\n"); - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; - - for (j = 0; j < ist->nb_filters; j++) { + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { + for (int j = 0; j < ist->nb_filters; j++) { if (!filtergraph_is_simple(ist->filters[j]->graph)) { av_log(NULL, AV_LOG_INFO, " Stream #%d:%d (%s) -> %s", ist->file_index, ist->st->index, ist->dec ? ist->dec->name : "?", @@ -3587,9 +3562,7 @@ static int transcode_init(void) } } - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { if (ost->attachment_filename) { /* an attached file */ av_log(NULL, AV_LOG_INFO, " File %s -> Stream #%d:%d\n", @@ -3604,24 +3577,18 @@ static int transcode_init(void) av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index); av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)\n", ost->file_index, - ost->index, ost->enc ? ost->enc->name : "?"); + ost->index, ost->enc_ctx->codec->name); continue; } av_log(NULL, AV_LOG_INFO, " Stream #%d:%d -> #%d:%d", - input_streams[ost->source_index]->file_index, - input_streams[ost->source_index]->st->index, + ost->ist->file_index, + ost->ist->st->index, ost->file_index, ost->index); - if (ost->sync_ist != input_streams[ost->source_index]) - av_log(NULL, AV_LOG_INFO, " [sync #%d:%d]", - ost->sync_ist->file_index, - ost->sync_ist->st->index); - if (ost->stream_copy) - av_log(NULL, AV_LOG_INFO, " (copy)"); - else { - const AVCodec *in_codec = input_streams[ost->source_index]->dec; - const AVCodec *out_codec = ost->enc; + if (ost->enc_ctx) { + const AVCodec *in_codec = ost->ist->dec; + const AVCodec *out_codec = ost->enc_ctx->codec; const char *decoder_name = "?"; const char *in_codec_name = "?"; const char *encoder_name = "?"; @@ -3649,7 +3616,8 @@ static int transcode_init(void) av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))", in_codec_name, decoder_name, out_codec_name, encoder_name); - } + } else + av_log(NULL, AV_LOG_INFO, " (copy)"); av_log(NULL, AV_LOG_INFO, "\n"); } @@ -3666,22 +3634,9 @@ static int transcode_init(void) /* Return 1 if there remain streams where more output is wanted, 0 otherwise. */ static int need_output(void) { - int i; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - OutputFile *of = output_files[ost->file_index]; - AVFormatContext *os = output_files[ost->file_index]->ctx; - - if (ost->finished || - (os->pb && avio_tell(os->pb) >= of->limit_filesize)) - continue; - if (ost->frame_number >= ost->max_frames) { - int j; - for (j = 0; j < of->ctx->nb_streams; j++) - close_output_stream(output_streams[of->ost_index + j]); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->finished) continue; - } return 1; } @@ -3696,19 +3651,22 @@ static int need_output(void) */ static OutputStream *choose_output(void) { - int i; int64_t opts_min = INT64_MAX; OutputStream *ost_min = NULL; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - int64_t opts = ost->last_mux_dts == AV_NOPTS_VALUE ? INT64_MIN : - av_rescale_q(ost->last_mux_dts, ost->st->time_base, - AV_TIME_BASE_Q); - if (ost->last_mux_dts == AV_NOPTS_VALUE) - av_log(NULL, AV_LOG_DEBUG, - "cur_dts is invalid st:%d (%d) [init:%d i_done:%d finish:%d] (this is harmless if it occurs once at the start per stream)\n", - ost->st->index, ost->st->id, ost->initialized, ost->inputs_done, ost->finished); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + int64_t opts; + + if (ost->filter && ost->last_filter_pts != AV_NOPTS_VALUE) { + opts = ost->last_filter_pts; + } else { + opts = ost->last_mux_dts == AV_NOPTS_VALUE ? + INT64_MIN : ost->last_mux_dts; + if (ost->last_mux_dts == AV_NOPTS_VALUE) + av_log(ost, AV_LOG_DEBUG, + "cur_dts is invalid [init:%d i_done:%d finish:%d] (this is harmless if it occurs once at the start per stream)\n", + ost->initialized, ost->inputs_done, ost->finished); + } if (!ost->initialized && !ost->inputs_done) return ost->unavailable ? NULL : ost; @@ -3751,15 +3709,6 @@ static int check_keyboard_interaction(int64_t cur_time) if (key == '+') av_log_set_level(av_log_get_level()+10); if (key == '-') av_log_set_level(av_log_get_level()-10); if (key == 's') qp_hist ^= 1; - if (key == 'h'){ - if (do_hex_dump){ - do_hex_dump = do_pkt_dump = 0; - } else if(do_pkt_dump){ - do_hex_dump = 1; - } else - do_pkt_dump = 1; - av_log_set_level(AV_LOG_DEBUG); - } if (key == 'c' || key == 'C'){ char buf[4096], target[64], command[256], arg[256] = {0}; double time; @@ -3803,7 +3752,11 @@ static int check_keyboard_interaction(int64_t cur_time) if (key == 'd' || key == 'D'){ int debug=0; if(key == 'D') { - debug = input_streams[0]->dec_ctx->debug << 1; + InputStream *ist = ist_iter(NULL); + + if (ist) + debug = ist->dec_ctx->debug << 1; + if(!debug) debug = 1; while (debug & FF_DEBUG_DCT_COEFF) //unsupported, would just crash debug += debug; @@ -3821,12 +3774,11 @@ static int check_keyboard_interaction(int64_t cur_time) if (k <= 0 || sscanf(buf, "%d", &debug)!=1) av_log(NULL, AV_LOG_STDERR,"error parsing debug value\n"); } - for(i=0;idec_ctx->debug = debug; - } - for(i=0;ienc_ctx->debug = debug; + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + ist->dec_ctx->debug = debug; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->enc_ctx) + ost->enc_ctx->debug = debug; } if(debug) av_log_set_level(AV_LOG_DEBUG); av_log(NULL, AV_LOG_STDERR,"debug=%d\n", debug); @@ -3847,160 +3799,10 @@ static int check_keyboard_interaction(int64_t cur_time) return 0; } -#if HAVE_THREADS -static void *input_thread(void *arg) -{ - InputFile *f = arg; - AVPacket *pkt = f->pkt, *queue_pkt; - unsigned flags = f->non_blocking ? AV_THREAD_MESSAGE_NONBLOCK : 0; - int ret = 0; - - while (1) { - ret = av_read_frame(f->ctx, pkt); - - if (ret == AVERROR(EAGAIN)) { - av_usleep(10000); - continue; - } - if (ret < 0) { - av_thread_message_queue_set_err_recv(f->in_thread_queue, ret); - break; - } - queue_pkt = av_packet_alloc(); - if (!queue_pkt) { - av_packet_unref(pkt); - av_thread_message_queue_set_err_recv(f->in_thread_queue, AVERROR(ENOMEM)); - break; - } - av_packet_move_ref(queue_pkt, pkt); - ret = av_thread_message_queue_send(f->in_thread_queue, &queue_pkt, flags); - if (flags && ret == AVERROR(EAGAIN)) { - flags = 0; - ret = av_thread_message_queue_send(f->in_thread_queue, &queue_pkt, flags); - av_log(f->ctx, AV_LOG_WARNING, - "Thread message queue blocking; consider raising the " - "thread_queue_size option (current value: %d)\n", - f->thread_queue_size); - } - if (ret < 0) { - if (ret != AVERROR_EOF) - av_log(f->ctx, AV_LOG_ERROR, - "Unable to send packet to main thread: %s\n", - av_err2str(ret)); - av_packet_free(&queue_pkt); - av_thread_message_queue_set_err_recv(f->in_thread_queue, ret); - break; - } - } - - return NULL; -} - -static void free_input_thread(int i) -{ - InputFile *f = input_files[i]; - AVPacket *pkt; - - if (!f || !f->in_thread_queue) - return; - av_thread_message_queue_set_err_send(f->in_thread_queue, AVERROR_EOF); - while (av_thread_message_queue_recv(f->in_thread_queue, &pkt, 0) >= 0) - av_packet_free(&pkt); - - pthread_join(f->thread, NULL); - f->joined = 1; - av_thread_message_queue_free(&f->in_thread_queue); -} - -static void free_input_threads(void) -{ - int i; - - for (i = 0; i < nb_input_files; i++) - free_input_thread(i); -} - -static int init_input_thread(int i) -{ - int ret; - InputFile *f = input_files[i]; - - if (f->thread_queue_size < 0) - f->thread_queue_size = (nb_input_files > 1 ? 8 : 0); - if (!f->thread_queue_size) - return 0; - - if (f->ctx->pb ? !f->ctx->pb->seekable : - strcmp(f->ctx->iformat->name, "lavfi")) - f->non_blocking = 1; - ret = av_thread_message_queue_alloc(&f->in_thread_queue, - f->thread_queue_size, sizeof(f->pkt)); - if (ret < 0) - return ret; - - if ((ret = pthread_create(&f->thread, NULL, input_thread, f))) { - av_log(NULL, AV_LOG_ERROR, "pthread_create failed: %s. Try to increase `ulimit -v` or decrease `ulimit -s`.\n", strerror(ret)); - av_thread_message_queue_free(&f->in_thread_queue); - return AVERROR(ret); - } - - return 0; -} - -static int init_input_threads(void) -{ - int i, ret; - - for (i = 0; i < nb_input_files; i++) { - ret = init_input_thread(i); - if (ret < 0) - return ret; - } - return 0; -} - -static int get_input_packet_mt(InputFile *f, AVPacket **pkt) -{ - return av_thread_message_queue_recv(f->in_thread_queue, pkt, - f->non_blocking ? - AV_THREAD_MESSAGE_NONBLOCK : 0); -} -#endif - -static int get_input_packet(InputFile *f, AVPacket **pkt) -{ - if (f->readrate || f->rate_emu) { - int i; - int64_t file_start = copy_ts * ( - (f->ctx->start_time != AV_NOPTS_VALUE ? f->ctx->start_time * !start_at_zero : 0) + - (f->start_time != AV_NOPTS_VALUE ? f->start_time : 0) - ); - float scale = f->rate_emu ? 1.0 : f->readrate; - for (i = 0; i < f->nb_streams; i++) { - InputStream *ist = input_streams[f->ist_index + i]; - int64_t stream_ts_offset, pts, now; - if (!ist->nb_packets || (ist->decoding_needed && !ist->got_output)) continue; - stream_ts_offset = FFMAX(ist->first_dts != AV_NOPTS_VALUE ? ist->first_dts : 0, file_start); - pts = av_rescale(ist->dts, 1000000, AV_TIME_BASE); - now = (av_gettime_relative() - ist->start) * scale + stream_ts_offset; - if (pts > now) - return AVERROR(EAGAIN); - } - } - -#if HAVE_THREADS - if (f->thread_queue_size) - return get_input_packet_mt(f, pkt); -#endif - *pkt = f->pkt; - return av_read_frame(f->ctx, *pkt); -} - static int got_eagain(void) { - int i; - for (i = 0; i < nb_output_streams; i++) - if (output_streams[i]->unavailable) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) + if (ost->unavailable) return 1; return 0; } @@ -4010,87 +3812,123 @@ static void reset_eagain(void) int i; for (i = 0; i < nb_input_files; i++) input_files[i]->eagain = 0; - for (i = 0; i < nb_output_streams; i++) - output_streams[i]->unavailable = 0; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) + ost->unavailable = 0; } -// set duration to max(tmp, duration) in a proper time base and return duration's time_base -static AVRational duration_max(int64_t tmp, int64_t *duration, AVRational tmp_time_base, - AVRational time_base) +static void decode_flush(InputFile *ifile) { - int ret; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int ret; - if (!*duration) { - *duration = tmp; - return tmp_time_base; - } - - ret = av_compare_ts(*duration, time_base, tmp, tmp_time_base); - if (ret < 0) { - *duration = tmp; - return tmp_time_base; - } + if (!ist->processing_needed) + continue; - return time_base; -} + do { + ret = process_input_packet(ist, NULL, 1); + } while (ret > 0); -static int seek_to_start(InputFile *ifile, AVFormatContext *is) -{ - InputStream *ist; - AVCodecContext *avctx; - int i, ret, has_audio = 0; - int64_t duration = 0; + if (ist->decoding_needed) { + /* report last frame duration to the demuxer thread */ + if (ist->par->codec_type == AVMEDIA_TYPE_AUDIO) { + LastFrameDuration dur; - ret = avformat_seek_file(is, -1, INT64_MIN, is->start_time, is->start_time, 0); - if (ret < 0) - return ret; + dur.stream_idx = i; + dur.duration = av_rescale_q(ist->nb_samples, + (AVRational){ 1, ist->dec_ctx->sample_rate}, + ist->st->time_base); - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; + av_thread_message_queue_send(ifile->audio_duration_queue, &dur, 0); + } - /* duration is the length of the last frame in a stream - * when audio stream is present we don't care about - * last video frame length because it's not defined exactly */ - if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && ist->nb_samples) - has_audio = 1; + avcodec_flush_buffers(ist->dec_ctx); + } } +} - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; +static void ts_discontinuity_detect(InputFile *ifile, InputStream *ist, + AVPacket *pkt) +{ + const int fmt_is_discont = ifile->ctx->iformat->flags & AVFMT_TS_DISCONT; + int disable_discontinuity_correction = copy_ts; + int64_t pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, + AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); - if (has_audio) { - if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && ist->nb_samples) { - AVRational sample_rate = {1, avctx->sample_rate}; + if (copy_ts && ist->next_dts != AV_NOPTS_VALUE && + fmt_is_discont && ist->st->pts_wrap_bits < 60) { + int64_t wrap_dts = av_rescale_q_rnd(pkt->dts + (1LL<st->pts_wrap_bits), + ist->st->time_base, AV_TIME_BASE_Q, + AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + if (FFABS(wrap_dts - ist->next_dts) < FFABS(pkt_dts - ist->next_dts)/10) + disable_discontinuity_correction = 0; + } - duration = av_rescale_q(ist->nb_samples, sample_rate, ist->st->time_base); - } else { - continue; + if (ist->next_dts != AV_NOPTS_VALUE && !disable_discontinuity_correction) { + int64_t delta = pkt_dts - ist->next_dts; + if (fmt_is_discont) { + if (FFABS(delta) > 1LL * dts_delta_threshold * AV_TIME_BASE || + pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)) { + ifile->ts_offset_discont -= delta; + av_log(NULL, AV_LOG_DEBUG, + "timestamp discontinuity for stream #%d:%d " + "(id=%d, type=%s): %"PRId64", new offset= %"PRId64"\n", + ist->file_index, ist->st->index, ist->st->id, + av_get_media_type_string(ist->par->codec_type), + delta, ifile->ts_offset_discont); + pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); } } else { - if (ist->framerate.num) { - duration = av_rescale_q(1, av_inv_q(ist->framerate), ist->st->time_base); - } else if (ist->st->avg_frame_rate.num) { - duration = av_rescale_q(1, av_inv_q(ist->st->avg_frame_rate), ist->st->time_base); - } else { - duration = 1; + if (FFABS(delta) > 1LL * dts_error_threshold * AV_TIME_BASE) { + av_log(NULL, AV_LOG_WARNING, "DTS %"PRId64", next:%"PRId64" st:%d invalid dropping\n", pkt->dts, ist->next_dts, pkt->stream_index); + pkt->dts = AV_NOPTS_VALUE; } + if (pkt->pts != AV_NOPTS_VALUE){ + int64_t pkt_pts = av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); + delta = pkt_pts - ist->next_dts; + if (FFABS(delta) > 1LL * dts_error_threshold * AV_TIME_BASE) { + av_log(NULL, AV_LOG_WARNING, "PTS %"PRId64", next:%"PRId64" invalid dropping st:%d\n", pkt->pts, ist->next_dts, pkt->stream_index); + pkt->pts = AV_NOPTS_VALUE; + } + } + } + } else if (ist->next_dts == AV_NOPTS_VALUE && !copy_ts && + fmt_is_discont && ifile->last_ts != AV_NOPTS_VALUE) { + int64_t delta = pkt_dts - ifile->last_ts; + if (FFABS(delta) > 1LL * dts_delta_threshold * AV_TIME_BASE) { + ifile->ts_offset_discont -= delta; + av_log(NULL, AV_LOG_DEBUG, + "Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n", + delta, ifile->ts_offset_discont); + pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); } - if (!ifile->duration) - ifile->time_base = ist->st->time_base; - /* the total duration of the stream, max_pts - min_pts is - * the duration of the stream without the last frame */ - if (ist->max_pts > ist->min_pts && ist->max_pts - (uint64_t)ist->min_pts < INT64_MAX - duration) - duration += ist->max_pts - ist->min_pts; - ifile->time_base = duration_max(duration, &ifile->duration, ist->st->time_base, - ifile->time_base); } - if (ifile->loop > 0) - ifile->loop--; + ifile->last_ts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); +} + +static void ts_discontinuity_process(InputFile *ifile, InputStream *ist, + AVPacket *pkt) +{ + int64_t offset = av_rescale_q(ifile->ts_offset_discont, AV_TIME_BASE_Q, + ist->st->time_base); - return ret; + // apply previously-detected timestamp-discontinuity offset + // (to all streams, not just audio/video) + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += offset; + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += offset; + + // detect timestamp discontinuities for audio/video + if ((ist->par->codec_type == AVMEDIA_TYPE_VIDEO || + ist->par->codec_type == AVMEDIA_TYPE_AUDIO) && + pkt->dts != AV_NOPTS_VALUE) + ts_discontinuity_detect(ifile, ist, pkt); } /* @@ -4106,48 +3944,19 @@ static int process_input(int file_index) AVFormatContext *is; InputStream *ist; AVPacket *pkt; - int ret, thread_ret, i, j; - int64_t duration; - int64_t pkt_dts; - int disable_discontinuity_correction = copy_ts; + int ret, i; is = ifile->ctx; - ret = get_input_packet(ifile, &pkt); + ret = ifile_get_packet(ifile, &pkt); if (ret == AVERROR(EAGAIN)) { ifile->eagain = 1; return ret; } - if (ret < 0 && ifile->loop) { - AVCodecContext *avctx; - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; - if (ist->processing_needed) { - ret = process_input_packet(ist, NULL, 1); - if (ret>0) - return 0; - if (ist->decoding_needed) - avcodec_flush_buffers(avctx); - } - } -#if HAVE_THREADS - free_input_thread(file_index); -#endif - ret = seek_to_start(ifile, is); -#if HAVE_THREADS - thread_ret = init_input_thread(file_index); - if (thread_ret < 0) - return thread_ret; -#endif - if (ret < 0) - av_log(NULL, AV_LOG_WARNING, "Seek to start failed.\n"); - else - ret = get_input_packet(ifile, &pkt); - if (ret == AVERROR(EAGAIN)) { - ifile->eagain = 1; - return ret; - } + if (ret == 1) { + /* the input file is looped: flush the decoders */ + decode_flush(ifile); + return AVERROR(EAGAIN); } if (ret < 0) { if (ret != AVERROR_EOF) { @@ -4157,7 +3966,7 @@ static int process_input(int file_index) } for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; + ist = ifile->streams[i]; if (ist->processing_needed) { ret = process_input_packet(ist, NULL, 0); if (ret>0) @@ -4165,12 +3974,12 @@ static int process_input(int file_index) } /* mark all outputs that don't go through lavfi as finished */ - for (j = 0; j < nb_output_streams; j++) { - OutputStream *ost = output_streams[j]; - - if (ost->source_index == ifile->ist_index + i && - (ost->stream_copy || ost->enc->type == AVMEDIA_TYPE_SUBTITLE)) - finish_output_stream(ost); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->ist == ist && + (!ost->enc_ctx || ost->enc_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE)) { + OutputFile *of = output_files[ost->file_index]; + of_output_packet(of, ost->pkt, ost, 1); + } } } @@ -4180,18 +3989,7 @@ static int process_input(int file_index) reset_eagain(); - if (do_pkt_dump) { - av_pkt_dump_log2(NULL, AV_LOG_INFO, pkt, do_hex_dump, - is->streams[pkt->stream_index]); - } - /* the following test is needed in case new streams appear - dynamically in stream : we ignore them */ - if (pkt->stream_index >= ifile->nb_streams) { - report_new_stream(file_index, pkt); - goto discard_packet; - } - - ist = input_streams[ifile->ist_index + pkt->stream_index]; + ist = ifile->streams[pkt->stream_index]; ist->data_size += pkt->size; ist->nb_packets++; @@ -4199,61 +3997,6 @@ static int process_input(int file_index) if (ist->discard) goto discard_packet; - if (pkt->flags & AV_PKT_FLAG_CORRUPT) { - av_log(NULL, exit_on_error ? AV_LOG_FATAL : AV_LOG_WARNING, - "%s: corrupt input packet in stream %d\n", is->url, pkt->stream_index); - if (exit_on_error) - exit_program(1); - } - - if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "demuxer -> ist_index:%d type:%s " - "next_dts:%s next_dts_time:%s next_pts:%s next_pts_time:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", - ifile->ist_index + pkt->stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), - av_ts2str(ist->next_dts), av_ts2timestr(ist->next_dts, &AV_TIME_BASE_Q), - av_ts2str(ist->next_pts), av_ts2timestr(ist->next_pts, &AV_TIME_BASE_Q), - av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), - av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), - av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base), - av_ts2str(input_files[ist->file_index]->ts_offset), - av_ts2timestr(input_files[ist->file_index]->ts_offset, &AV_TIME_BASE_Q)); - } - - if(!ist->wrap_correction_done && is->start_time != AV_NOPTS_VALUE && ist->st->pts_wrap_bits < 64){ - int64_t stime, stime2; - // Correcting starttime based on the enabled streams - // FIXME this ideally should be done before the first use of starttime but we do not know which are the enabled streams at that point. - // so we instead do it here as part of discontinuity handling - if ( ist->next_dts == AV_NOPTS_VALUE - && ifile->ts_offset == -is->start_time - && (is->iformat->flags & AVFMT_TS_DISCONT)) { - int64_t new_start_time = INT64_MAX; - for (i=0; inb_streams; i++) { - AVStream *st = is->streams[i]; - if(st->discard == AVDISCARD_ALL || st->start_time == AV_NOPTS_VALUE) - continue; - new_start_time = FFMIN(new_start_time, av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q)); - } - if (new_start_time > is->start_time) { - av_log(is, AV_LOG_VERBOSE, "Correcting start time by %"PRId64"\n", new_start_time - is->start_time); - ifile->ts_offset = -new_start_time; - } - } - - stime = av_rescale_q(is->start_time, AV_TIME_BASE_Q, ist->st->time_base); - stime2= stime + (1ULL<st->pts_wrap_bits); - ist->wrap_correction_done = 1; - - if(stime2 > stime && pkt->dts != AV_NOPTS_VALUE && pkt->dts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { - pkt->dts -= 1ULL<st->pts_wrap_bits; - ist->wrap_correction_done = 0; - } - if(stime2 > stime && pkt->pts != AV_NOPTS_VALUE && pkt->pts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { - pkt->pts -= 1ULL<st->pts_wrap_bits; - ist->wrap_correction_done = 0; - } - } - /* add the stream-global side data to the first packet */ if (ist->nb_packets == 1) { for (i = 0; i < ist->st->nb_side_data; i++) { @@ -4268,105 +4011,19 @@ static int process_input(int file_index) dst_data = av_packet_new_side_data(pkt, src_sd->type, src_sd->size); if (!dst_data) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); memcpy(dst_data, src_sd->data, src_sd->size); } } - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); - - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts *= ist->ts_scale; - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts *= ist->ts_scale; - - pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && - pkt_dts != AV_NOPTS_VALUE && ist->next_dts == AV_NOPTS_VALUE && !copy_ts - && (is->iformat->flags & AVFMT_TS_DISCONT) && ifile->last_ts != AV_NOPTS_VALUE) { - int64_t delta = pkt_dts - ifile->last_ts; - if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || - delta > 1LL*dts_delta_threshold*AV_TIME_BASE){ - ifile->ts_offset -= delta; - av_log(NULL, AV_LOG_DEBUG, - "Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n", - delta, ifile->ts_offset); - pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - } - } - - duration = av_rescale_q(ifile->duration, ifile->time_base, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) { - pkt->pts += duration; - ist->max_pts = FFMAX(pkt->pts, ist->max_pts); - ist->min_pts = FFMIN(pkt->pts, ist->min_pts); - } - - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts += duration; - - pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - - if (copy_ts && pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && - (is->iformat->flags & AVFMT_TS_DISCONT) && ist->st->pts_wrap_bits < 60) { - int64_t wrap_dts = av_rescale_q_rnd(pkt->dts + (1LL<st->pts_wrap_bits), - ist->st->time_base, AV_TIME_BASE_Q, - AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - if (FFABS(wrap_dts - ist->next_dts) < FFABS(pkt_dts - ist->next_dts)/10) - disable_discontinuity_correction = 0; - } - - if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && - pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && - !disable_discontinuity_correction) { - int64_t delta = pkt_dts - ist->next_dts; - if (is->iformat->flags & AVFMT_TS_DISCONT) { - if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || - delta > 1LL*dts_delta_threshold*AV_TIME_BASE || - pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)) { - ifile->ts_offset -= delta; - av_log(NULL, AV_LOG_DEBUG, - "timestamp discontinuity for stream #%d:%d " - "(id=%d, type=%s): %"PRId64", new offset= %"PRId64"\n", - ist->file_index, ist->st->index, ist->st->id, - av_get_media_type_string(ist->dec_ctx->codec_type), - delta, ifile->ts_offset); - pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - } - } else { - if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || - delta > 1LL*dts_error_threshold*AV_TIME_BASE) { - av_log(NULL, AV_LOG_WARNING, "DTS %"PRId64", next:%"PRId64" st:%d invalid dropping\n", pkt->dts, ist->next_dts, pkt->stream_index); - pkt->dts = AV_NOPTS_VALUE; - } - if (pkt->pts != AV_NOPTS_VALUE){ - int64_t pkt_pts = av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); - delta = pkt_pts - ist->next_dts; - if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || - delta > 1LL*dts_error_threshold*AV_TIME_BASE) { - av_log(NULL, AV_LOG_WARNING, "PTS %"PRId64", next:%"PRId64" invalid dropping st:%d\n", pkt->pts, ist->next_dts, pkt->stream_index); - pkt->pts = AV_NOPTS_VALUE; - } - } - } - } - - if (pkt->dts != AV_NOPTS_VALUE) - ifile->last_ts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); + // detect and try to correct for timestamp discontinuities + ts_discontinuity_process(ifile, ist, pkt); if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", - ifile->ist_index + pkt->stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), + av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", + ifile->index, pkt->stream_index, + av_get_media_type_string(ist->par->codec_type), av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base), @@ -4379,12 +4036,7 @@ static int process_input(int file_index) process_input_packet(ist, pkt, 0); discard_packet: -#if HAVE_THREADS - if (ifile->thread_queue_size) - av_packet_free(&pkt); - else -#endif - av_packet_unref(pkt); + av_packet_free(&pkt); return 0; } @@ -4512,8 +4164,8 @@ static int transcode_step(void) return 0; } } else { - av_assert0(ost->source_index >= 0); - ist = input_streams[ost->source_index]; + ist = ost->ist; + av_assert0(ist); } ret = process_input(ist->file_index); @@ -4535,8 +4187,6 @@ static int transcode_step(void) static int transcode(void) { int ret, i; - AVFormatContext *os; - OutputStream *ost; InputStream *ist; int64_t timer_start; int64_t total_packets_written = 0; @@ -4551,11 +4201,6 @@ static int transcode(void) timer_start = av_gettime_relative(); -#if HAVE_THREADS - if ((ret = init_input_threads()) < 0) - goto fail; -#endif - while (!received_sigterm && !cancelRequested(globalSessionId)) { int64_t cur_time= av_gettime_relative(); @@ -4579,13 +4224,9 @@ static int transcode(void) /* dump report by using the output first video and audio streams */ print_report(0, timer_start, cur_time); } -#if HAVE_THREADS - free_input_threads(); -#endif /* at the end of stream, we must flush the decoder buffers */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; + for (ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { if (!input_files[ist->file_index]->eof_reached) { process_input_packet(ist, NULL, 0); } @@ -4604,30 +4245,13 @@ static int transcode(void) /* dump report by using the first video and audio streams */ print_report(1, timer_start, av_gettime_relative()); - /* close the output files */ - for (i = 0; i < nb_output_files; i++) { - os = output_files[i]->ctx; - if (os && os->oformat && !(os->oformat->flags & AVFMT_NOFILE)) { - if ((ret = avio_closep(&os->pb)) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error closing file %s: %s\n", os->url, av_err2str(ret)); - if (exit_on_error) - exit_program(1); - } - } - } - - /* dump report by using the first video and audio streams */ - print_report(1, timer_start, av_gettime_relative()); - /* close each encoder */ - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - if (ost->encoding_needed) { - av_freep(&ost->enc_ctx->stats_in); - } - total_packets_written += ost->packets_written; - if (!ost->packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM)) { - av_log(NULL, AV_LOG_FATAL, "Empty output on stream %d.\n", i); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + uint64_t packets_written; + packets_written = atomic_load(&ost->packets_written); + total_packets_written += packets_written; + if (!packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM)) { + av_log(ost, AV_LOG_FATAL, "Empty output\n"); exit_program(1); } } @@ -4637,46 +4261,12 @@ static int transcode(void) exit_program(1); } - /* close each decoder */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; - if (ist->decoding_needed) { - avcodec_close(ist->dec_ctx); - if (ist->hwaccel_uninit) - ist->hwaccel_uninit(ist->dec_ctx); - } - } - hw_device_free_all(); /* finished ! */ ret = 0; fail: -#if HAVE_THREADS - free_input_threads(); -#endif - - if (output_streams) { - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - if (ost) { - if (ost->logfile) { - if (fclose(ost->logfile)) - av_log(NULL, AV_LOG_ERROR, - "Error closing logfile, loss of information possible: %s\n", - av_err2str(AVERROR(errno))); - ost->logfile = NULL; - } - av_freep(&ost->forced_kf_pts); - av_freep(&ost->apad); - av_freep(&ost->disposition); - av_dict_free(&ost->encoder_opts); - av_dict_free(&ost->sws_dict); - av_dict_free(&ost->swr_opts); - } - } - } return ret; } @@ -4725,29 +4315,31 @@ static int64_t getmaxrss(void) } void ffmpeg_var_cleanup() { - main_ffmpeg_return_code = 0; - longjmp_value = 0; received_sigterm = 0; received_nb_signals = 0; + transcode_init_done = ATOMIC_VAR_INIT(0); ffmpeg_exited = 0; + main_ffmpeg_return_code = 0; copy_ts_first_pts = AV_NOPTS_VALUE; + longjmp_value = 0; + want_sdp = 1; + enc_stats_files = NULL; + nb_enc_stats_files = 0; + + vstats_file = NULL; nb_frames_dup = 0; dup_warning = 1000; nb_frames_drop = 0; + decode_error_stat[0] = 0; + decode_error_stat[1] = 0; nb_output_dumped = 0; - want_sdp = 1; - progress_avio = NULL; - input_streams = NULL; - nb_input_streams = 0; input_files = NULL; nb_input_files = 0; - output_streams = NULL; - nb_output_streams = 0; output_files = NULL; nb_output_files = 0; @@ -4757,9 +4349,12 @@ void ffmpeg_var_cleanup() { last_time = -1; keyboard_last_time = 0; first_report = 1; + for(int i = 0; i < FF_ARRAY_ELEMS(qp_histogram); i++) { + qp_histogram[i] = 0; + } } -void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double)) +void set_report_callback(void (*callback)(int, float, float, int64_t, double, double, double)) { report_callback = callback; } @@ -4848,8 +4443,10 @@ int ffmpeg_execute(int argc, char **argv) OPT_OUTPUT, { .func_arg = opt_map }, "set input stream mapping", "[-]input_file_id[:stream_specifier][,sync_file_id[:stream_specifier]]" }, + #if FFMPEG_OPT_MAP_CHANNEL { "map_channel", HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_map_channel }, - "map an audio channel from one stream to another", "file.stream.channel[:syncfile.syncstream]" }, + "map an audio channel from one stream to another (deprecated)", "file.stream.channel[:syncfile.syncstream]" }, + #endif { "map_metadata", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(metadata_map) }, "set metadata information of outfile from infile", @@ -4922,8 +4519,6 @@ int ffmpeg_execute(int argc, char **argv) "set video sync method globally; deprecated, use -fps_mode", "" }, { "frame_drop_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &frame_drop_threshold }, "frame drop threshold", "" }, - { "async", HAS_ARG | OPT_INT | OPT_EXPERT, { &audio_sync_method }, - "audio sync method", "" }, { "adrift_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &audio_drift_threshold }, "audio drift threshold", "threshold" }, { "copyts", OPT_BOOL | OPT_EXPERT, { ©_ts }, @@ -4935,6 +4530,8 @@ int ffmpeg_execute(int argc, char **argv) { "shortest", OPT_BOOL | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(shortest) }, "finish encoding within shortest input" }, + { "shortest_buf_duration", HAS_ARG | OPT_FLOAT | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(shortest_buf_duration) }, + "maximum buffering duration (in seconds) for the -shortest option" }, { "bitexact", OPT_BOOL | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT | OPT_INPUT, { .off = OFFSET(bitexact) }, "bitexact mode" }, @@ -5007,15 +4604,28 @@ int ffmpeg_execute(int argc, char **argv) { "disposition", OPT_STRING | HAS_ARG | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(disposition) }, "disposition", "" }, - { "thread_queue_size", HAS_ARG | OPT_INT | OPT_OFFSET | OPT_EXPERT | OPT_INPUT, + { "thread_queue_size", HAS_ARG | OPT_INT | OPT_OFFSET | OPT_EXPERT | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(thread_queue_size) }, "set the maximum number of queued packets from the demuxer" }, - { "find_stream_info", OPT_BOOL | OPT_PERFILE | OPT_INPUT | OPT_EXPERT, { &find_stream_info }, + { "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT | OPT_OFFSET, { .off = OFFSET(find_stream_info) }, "read and decode the streams to fill missing information with heuristics" }, { "bits_per_raw_sample", OPT_INT | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(bits_per_raw_sample) }, "set the number of bits per raw sample", "number" }, + { "stats_enc_pre", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_pre) }, + "write encoding stats before encoding" }, + { "stats_enc_post", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_post) }, + "write encoding stats after encoding" }, + { "stats_mux_pre", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(mux_stats) }, + "write packets stats before muxing" }, + { "stats_enc_pre_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_pre_fmt) }, + "format of the stats written with -stats_enc_pre" }, + { "stats_enc_post_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_post_fmt) }, + "format of the stats written with -stats_enc_post" }, + { "stats_mux_pre_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(mux_stats_fmt) }, + "format of the stats written with -stats_mux_pre" }, + /* video options */ { "vframes", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_video_frames }, "set the number of video frames to output", "number" }, @@ -5034,6 +4644,16 @@ int ffmpeg_execute(int argc, char **argv) { "pix_fmt", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(frame_pix_fmts) }, "set pixel format", "format" }, + { "display_rotation", OPT_VIDEO | HAS_ARG | OPT_DOUBLE | OPT_SPEC | + OPT_INPUT, { .off = OFFSET(display_rotations) }, + "set pure counter-clockwise rotation in degrees for stream(s)", + "angle" }, + { "display_hflip", OPT_VIDEO | OPT_BOOL | OPT_SPEC | OPT_INPUT, { .off = OFFSET(display_hflips) }, + "set display horizontal flip for stream(s) " + "(overrides any display rotation if it is not set)"}, + { "display_vflip", OPT_VIDEO | OPT_BOOL | OPT_SPEC | OPT_INPUT, { .off = OFFSET(display_vflips) }, + "set display vertical flip for stream(s) " + "(overrides any display rotation if it is not set)"}, { "vn", OPT_VIDEO | OPT_BOOL | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT,{ .off = OFFSET(video_disable) }, "disable video" }, { "rc_override", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | @@ -5049,8 +4669,10 @@ int ffmpeg_execute(int argc, char **argv) { "passlogfile", OPT_VIDEO | HAS_ARG | OPT_STRING | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(passlogfiles) }, "select two pass log file name prefix", "prefix" }, + #if FFMPEG_OPT_PSNR { "psnr", OPT_VIDEO | OPT_BOOL | OPT_EXPERT, { &do_psnr }, - "calculate PSNR of compressed frames" }, + "calculate PSNR of compressed frames (deprecated, use -flags +psnr)" }, + #endif { "vstats", OPT_VIDEO | OPT_EXPERT , { .func_arg = opt_vstats }, "dump video coding statistics to file" }, { "vstats_file", OPT_VIDEO | HAS_ARG | OPT_EXPERT , { .func_arg = opt_vstats_file }, @@ -5088,8 +4710,6 @@ int ffmpeg_execute(int argc, char **argv) { "force_key_frames", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(forced_key_frames) }, "force key frames at specified timestamps", "timestamps" }, - { "ab", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, - "audio bitrate (please use -b:a)", "bitrate" }, { "b", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, "video bitrate (please use -b:v)", "bitrate" }, { "hwaccel", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | @@ -5109,6 +4729,11 @@ int ffmpeg_execute(int argc, char **argv) { "autoscale", HAS_ARG | OPT_BOOL | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT, { .off = OFFSET(autoscale) }, "automatically insert a scale filter at the end of the filter graph" }, + { "fix_sub_duration_heartbeat", OPT_VIDEO | OPT_BOOL | OPT_EXPERT | + OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(fix_sub_duration_heartbeat) }, + "set this video output stream to be a heartbeat stream for " + "fix_sub_duration, according to which subtitles should be split at " + "random access points" }, /* audio options */ { "aframes", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_audio_frames }, @@ -5126,11 +4751,11 @@ int ffmpeg_execute(int argc, char **argv) { "acodec", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_audio_codec }, "force audio codec ('copy' to copy stream)", "codec" }, + { "ab", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, + "audio bitrate (please use -b:a)", "bitrate" }, { "atag", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_old2new }, "force audio tag/fourcc", "fourcc/tag" }, - { "vol", OPT_AUDIO | HAS_ARG | OPT_INT, { &audio_volume }, - "change audio volume (256=normal)" , "volume" }, { "sample_fmt", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_STRING | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(sample_fmts) }, "set sample format", "format" }, @@ -5220,7 +4845,7 @@ int ffmpeg_execute(int argc, char **argv) ffmpeg_options = options; - int i, ret; + int ret; BenchmarkTimeStamps ti; int savedCode = setjmp(ex_buf__); @@ -5259,11 +4884,6 @@ int ffmpeg_execute(int argc, char **argv) exit_program(1); } - for (i = 0; i < nb_output_files; i++) { - if (strcmp(output_files[i]->format->name, "rtp")) - want_sdp = 0; - } - current_time = ti = get_benchmark_time_stamps(); if (transcode() < 0) exit_program(1); diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg.h b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg.h index 653a9d9..66b254f 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg.h +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg.h @@ -1,6 +1,7 @@ /* * This file is part of FFmpeg. - * Copyright (c) 2018 Taner Sener + * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,6 +23,16 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - WARN_MULTIPLE_OPT_USAGE, MATCH_PER_STREAM_OPT, MATCH_PER_TYPE_OPT, SPECIFIER_OPT_FMT declarations migrated to + * ffmpeg_mux.h + * - "class" member field renamed as clazz + * - time field in set_report_callback updated as double + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -59,11 +70,13 @@ #include "config.h" +#include #include #include #include #include "fftools_cmdutils.h" +#include "fftools_sync_queue.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" @@ -85,6 +98,12 @@ #include "libswresample/swresample.h" +// deprecated features +#define FFMPEG_OPT_PSNR 1 +#define FFMPEG_OPT_MAP_CHANNEL 1 +#define FFMPEG_OPT_MAP_SYNC 1 +#define FFMPEG_ROTATION_METADATA 1 + enum VideoSyncMethod { VSYNC_AUTO = -1, VSYNC_PASSTHROUGH, @@ -113,15 +132,15 @@ typedef struct StreamMap { int disabled; /* 1 is this mapping is disabled by a negative map */ int file_index; int stream_index; - int sync_file_index; - int sync_stream_index; char *linklabel; /* name of an output link, for mapping lavfi outputs */ } StreamMap; +#if FFMPEG_OPT_MAP_CHANNEL typedef struct { int file_idx, stream_idx, channel_idx; // input int ofile_idx, ostream_idx; // output } AudioChannelMap; +#endif typedef struct OptionsContext { OptionGroup *g; @@ -157,6 +176,7 @@ typedef struct OptionsContext { int accurate_seek; int thread_queue_size; int input_sync_ref; + int find_stream_info; SpecifierOpt *ts_scale; int nb_ts_scale; @@ -174,11 +194,10 @@ typedef struct OptionsContext { /* output options */ StreamMap *stream_maps; int nb_stream_maps; +#if FFMPEG_OPT_MAP_CHANNEL AudioChannelMap *audio_channel_maps; /* one info entry per -map_channel */ int nb_audio_channel_maps; /* number of (valid) -map_channel settings */ - int metadata_global_manual; - int metadata_streams_manual; - int metadata_chapters_manual; +#endif const char **attachments; int nb_attachments; @@ -186,9 +205,10 @@ typedef struct OptionsContext { int64_t recording_time; int64_t stop_time; - uint64_t limit_filesize; + int64_t limit_filesize; float mux_preload; float mux_max_delay; + float shortest_buf_duration; int shortest; int bitexact; @@ -221,6 +241,12 @@ typedef struct OptionsContext { int nb_force_fps; SpecifierOpt *frame_aspect_ratios; int nb_frame_aspect_ratios; + SpecifierOpt *display_rotations; + int nb_display_rotations; + SpecifierOpt *display_hflips; + int nb_display_hflips; + SpecifierOpt *display_vflips; + int nb_display_vflips; SpecifierOpt *rc_overrides; int nb_rc_overrides; SpecifierOpt *intra_matrices; @@ -247,6 +273,8 @@ typedef struct OptionsContext { int nb_reinit_filters; SpecifierOpt *fix_sub_duration; int nb_fix_sub_duration; + SpecifierOpt *fix_sub_duration_heartbeat; + int nb_fix_sub_duration_heartbeat; SpecifierOpt *canvas_sizes; int nb_canvas_sizes; SpecifierOpt *pass; @@ -275,6 +303,18 @@ typedef struct OptionsContext { int nb_autoscale; SpecifierOpt *bits_per_raw_sample; int nb_bits_per_raw_sample; + SpecifierOpt *enc_stats_pre; + int nb_enc_stats_pre; + SpecifierOpt *enc_stats_post; + int nb_enc_stats_post; + SpecifierOpt *mux_stats; + int nb_mux_stats; + SpecifierOpt *enc_stats_pre_fmt; + int nb_enc_stats_pre_fmt; + SpecifierOpt *enc_stats_post_fmt; + int nb_enc_stats_post_fmt; + SpecifierOpt *mux_stats_fmt; + int nb_mux_stats_fmt; } OptionsContext; typedef struct InputFilter { @@ -350,12 +390,22 @@ typedef struct InputStream { #define DECODING_FOR_OST 1 #define DECODING_FOR_FILTER 2 int processing_needed; /* non zero if the packets must be processed */ + // should attach FrameData as opaque_ref after decoding + int want_frame_data; + /** + * Codec parameters - to be used by the decoding/streamcopy code. + * st->codecpar should not be accessed, because it may be modified + * concurrently by the demuxing thread. + */ + AVCodecParameters *par; AVCodecContext *dec_ctx; const AVCodec *dec; AVFrame *decoded_frame; AVPacket *pkt; + AVRational framerate_guessed; + int64_t prev_pkt_pts; int64_t start; /* time when read started */ /* predicted dts of the next packet read for this stream or (when there are @@ -368,6 +418,12 @@ typedef struct InputStream { int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units) int wrap_correction_done; + // the value of AVCodecParserContext.repeat_pict from the AVStream parser + // for the last packet returned from ifile_get_packet() + // -1 if unknown + // FIXME: this is a hack, the avstream parser should not be used + int last_pkt_repeat_pict; + int64_t filter_in_rescale_delta_last; int64_t min_pts; /* pts with the smallest value in a current stream */ @@ -417,12 +473,8 @@ typedef struct InputStream { char *hwaccel_device; enum AVPixelFormat hwaccel_output_format; - /* hwaccel context */ - void *hwaccel_ctx; - void (*hwaccel_uninit)(AVCodecContext *s); int (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame); enum AVPixelFormat hwaccel_pix_fmt; - enum AVPixelFormat hwaccel_retrieved_pix_fmt; /* stats */ // combined size of all the packets read @@ -439,38 +491,46 @@ typedef struct InputStream { int got_output; } InputStream; +typedef struct LastFrameDuration { + int stream_idx; + int64_t duration; +} LastFrameDuration; + typedef struct InputFile { + int index; + AVFormatContext *ctx; int eof_reached; /* true if eof reached */ int eagain; /* true if last read attempt returned EAGAIN */ - int ist_index; /* index of first stream in input_streams */ - int loop; /* set number of times input stream should be looped */ - int64_t duration; /* actual duration of the longest stream in a file - at the moment when looping happens */ - AVRational time_base; /* time base of the duration */ int64_t input_ts_offset; int input_sync_ref; - + /** + * Effective format start time based on enabled streams. + */ + int64_t start_time_effective; int64_t ts_offset; + /** + * Extra timestamp offset added by discontinuity handling. + */ + int64_t ts_offset_discont; int64_t last_ts; int64_t start_time; /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */ int64_t recording_time; - int nb_streams; /* number of stream that ffmpeg is aware of; may be different - from ctx.nb_streams if new streams appear during av_read_frame() */ - int nb_streams_warn; /* number of streams that the user was warned of */ + + /* streams that ffmpeg is aware of; + * there may be extra streams in ctx that are not mapped to an InputStream + * if new streams appear dynamically during demuxing */ + InputStream **streams; + int nb_streams; + int rate_emu; float readrate; int accurate_seek; - AVPacket *pkt; - -#if HAVE_THREADS - AVThreadMessageQueue *in_thread_queue; - pthread_t thread; /* thread reading from this file */ - int non_blocking; /* reading packets from the thread should not block */ - int joined; /* the thread has been joined */ - int thread_queue_size; /* maximum number of queued packets */ -#endif + /* when looping the input file, this queue is used by decoders to report + * the last frame duration back to the demuxer thread */ + AVThreadMessageQueue *audio_duration_queue; + int audio_duration_queue_size; } InputFile; enum forced_keyframes_const { @@ -485,6 +545,41 @@ enum forced_keyframes_const { #define ABORT_ON_FLAG_EMPTY_OUTPUT (1 << 0) #define ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM (1 << 1) +enum EncStatsType { + ENC_STATS_LITERAL = 0, + ENC_STATS_FILE_IDX, + ENC_STATS_STREAM_IDX, + ENC_STATS_FRAME_NUM, + ENC_STATS_FRAME_NUM_IN, + ENC_STATS_TIMEBASE, + ENC_STATS_TIMEBASE_IN, + ENC_STATS_PTS, + ENC_STATS_PTS_TIME, + ENC_STATS_PTS_IN, + ENC_STATS_PTS_TIME_IN, + ENC_STATS_DTS, + ENC_STATS_DTS_TIME, + ENC_STATS_SAMPLE_NUM, + ENC_STATS_NB_SAMPLES, + ENC_STATS_PKT_SIZE, + ENC_STATS_BITRATE, + ENC_STATS_AVG_BITRATE, +}; + +typedef struct EncStatsComponent { + enum EncStatsType type; + + uint8_t *str; + size_t str_len; +} EncStatsComponent; + +typedef struct EncStats { + EncStatsComponent *components; + int nb_components; + + AVIOContext *io; +} EncStats; + extern const char *const forced_keyframes_const_names[]; typedef enum { @@ -492,68 +587,92 @@ typedef enum { MUXER_FINISHED = 2, } OSTFinished ; +enum { + KF_FORCE_SOURCE = 1, + KF_FORCE_SOURCE_NO_DROP = 2, +}; + +typedef struct KeyframeForceCtx { + int type; + + int64_t ref_pts; + + // timestamps of the forced keyframes, in AV_TIME_BASE_Q + int64_t *pts; + int nb_pts; + int index; + + AVExpr *pexpr; + double expr_const_values[FKF_NB]; + + int dropped_keyframe; +} KeyframeForceCtx; + typedef struct OutputStream { + const AVClass *clazz; + int file_index; /* file index */ int index; /* stream index in the output file */ - int source_index; /* InputStream index */ + + /* input stream that is the source for this output stream; + * may be NULL for streams with no well-defined source, e.g. + * attachments or outputs from complex filtergraphs */ + InputStream *ist; + AVStream *st; /* stream in the output file */ - int encoding_needed; /* true if encoding needed for this stream */ - int64_t frame_number; - /* input pts and corresponding output pts - for A/V sync */ - struct InputStream *sync_ist; /* input stream to sync against */ - int64_t sync_opts; /* output frame counter, could be changed to some true timestamp */ // FIXME look at frame_number - /* pts of the first frame encoded for this stream, used for limiting - * recording time */ - int64_t first_pts; - /* dts of the last packet sent to the muxer */ + /* number of frames emitted by the video-encoding sync code */ + int64_t vsync_frame_number; + /* predicted pts of the next frame to be encoded + * audio/video encoding only */ + int64_t next_pts; + /* dts of the last packet sent to the muxing queue, in AV_TIME_BASE_Q */ int64_t last_mux_dts; + /* pts of the last frame received from the filters, in AV_TIME_BASE_Q */ + int64_t last_filter_pts; + + // timestamp from which the streamcopied streams should start, + // in AV_TIME_BASE_Q; + // everything before it should be discarded + int64_t ts_copy_start; + // the timebase of the packets sent to the muxer AVRational mux_timebase; AVRational enc_timebase; - AVBSFContext *bsf_ctx; - AVCodecContext *enc_ctx; - AVCodecParameters *ref_par; /* associated input codec parameters with encoders options applied */ - const AVCodec *enc; - int64_t max_frames; AVFrame *filtered_frame; AVFrame *last_frame; + AVFrame *sq_frame; AVPacket *pkt; int64_t last_dropped; int64_t last_nb0_frames[3]; - void *hwaccel_ctx; - /* video only */ AVRational frame_rate; AVRational max_frame_rate; enum VideoSyncMethod vsync_method; int is_cfr; - const char *fps_mode; int force_fps; int top_field_first; +#if FFMPEG_ROTATION_METADATA int rotate_overridden; +#endif int autoscale; + int bitexact; int bits_per_raw_sample; +#if FFMPEG_ROTATION_METADATA double rotate_override_value; +#endif AVRational frame_aspect_ratio; - /* forced key frames */ - int64_t forced_kf_ref_pts; - int64_t *forced_kf_pts; - int forced_kf_count; - int forced_kf_index; - char *forced_keyframes; - AVExpr *forced_keyframes_pexpr; - double forced_keyframes_expr_const_values[FKF_NB]; - int dropped_keyframe; + KeyframeForceCtx kf; /* audio only */ +#if FFMPEG_OPT_MAP_CHANNEL int *audio_channels_map; /* list of the channels id to pick from the source stream */ int audio_channels_mapped; /* number of channels in audio_channels_map */ +#endif char *logfile_prefix; FILE *logfile; @@ -569,7 +688,6 @@ typedef struct OutputStream { char *apad; OSTFinished finished; /* no more packets should be written for this stream */ int unavailable; /* true if the steram is unavailable (possibly temporarily) */ - int stream_copy; // init_output_stream() has been called for this stream // The encoder and the bitstream filters have been initialized and the stream @@ -582,15 +700,16 @@ typedef struct OutputStream { int streamcopy_started; int copy_initial_nonkeyframes; int copy_prior_start; - char *disposition; int keep_pix_fmt; /* stats */ - // combined size of all the packets written - uint64_t data_size; + // combined size of all the packets sent to the muxer + uint64_t data_size_mux; + // combined size of all the packets received from the encoder + uint64_t data_size_enc; // number of packets send to the muxer - uint64_t packets_written; + atomic_uint_least64_t packets_written; // number of frames/samples sent to the encoder uint64_t frames_encoded; uint64_t samples_encoded; @@ -600,51 +719,48 @@ typedef struct OutputStream { /* packet quality factor */ int quality; - int max_muxing_queue_size; - - /* the packets are buffered here until the muxer is ready to be initialized */ - AVFifo *muxing_queue; - - /* - * The size of the AVPackets' buffers in queue. - * Updated when a packet is either pushed or pulled from the queue. - */ - size_t muxing_queue_data_size; - - /* Threshold after which max_muxing_queue_size will be in effect */ - size_t muxing_queue_data_threshold; - /* packet picture type */ int pict_type; /* frame encode sum of squared error values */ int64_t error[4]; + + int sq_idx_encode; + int sq_idx_mux; + + EncStats enc_stats_pre; + EncStats enc_stats_post; + + /* + * bool on whether this stream should be utilized for splitting + * subtitles utilizing fix_sub_duration at random access points. + */ + unsigned int fix_sub_duration_heartbeat; } OutputStream; typedef struct OutputFile { + const AVClass *clazz; + int index; const AVOutputFormat *format; + const char *url; + + OutputStream **streams; + int nb_streams; + + SyncQueue *sq_encode; - AVFormatContext *ctx; - AVDictionary *opts; - int ost_index; /* index of the first stream in output_streams */ int64_t recording_time; ///< desired length of the resulting file in microseconds == AV_TIME_BASE units int64_t start_time; ///< start time in microseconds == AV_TIME_BASE units - uint64_t limit_filesize; /* filesize limit expressed in bytes */ int shortest; - - int header_written; + int bitexact; } OutputFile; -extern __thread InputStream **input_streams; -extern __thread int nb_input_streams; extern __thread InputFile **input_files; extern __thread int nb_input_files; -extern __thread OutputStream **output_streams; -extern __thread int nb_output_streams; extern __thread OutputFile **output_files; extern __thread int nb_output_files; @@ -658,13 +774,10 @@ extern __thread float audio_drift_threshold; extern __thread float dts_delta_threshold; extern __thread float dts_error_threshold; -extern __thread int audio_volume; -extern __thread int audio_sync_method; extern __thread enum VideoSyncMethod video_sync_method; extern __thread float frame_drop_threshold; extern __thread int do_benchmark; extern __thread int do_benchmark_all; -extern __thread int do_deinterlace; extern __thread int do_hex_dump; extern __thread int do_pkt_dump; extern __thread int copy_ts; @@ -677,7 +790,6 @@ extern __thread int print_stats; extern __thread int64_t stats_period; extern __thread int qp_hist; extern __thread int stdin_interaction; -extern __thread int frame_bits_per_raw_sample; extern __thread AVIOContext *progress_avio; extern __thread float max_error_rate; @@ -688,15 +800,20 @@ extern __thread int auto_conversion_filters; extern __thread const AVIOInterruptCB int_cb; -#if CONFIG_QSV -extern __thread char *qsv_device; -#endif extern __thread HWDevice *filter_hw_device; -extern __thread int want_sdp; extern __thread unsigned nb_output_dumped; extern __thread int main_ffmpeg_return_code; +extern __thread int ignore_unknown_streams; +extern __thread int copy_unknown_streams; + +extern __thread int recast_media; + +#if FFMPEG_OPT_PSNR +extern __thread int do_psnr; +#endif + void term_init(void); void term_exit(void); @@ -705,7 +822,12 @@ void show_usage(void); void remove_avoptions(AVDictionary **a, AVDictionary *b); void assert_avoptions(AVDictionary *m); -int guess_input_channel_layout(InputStream *ist); +void assert_file_overwrite(const char *filename); +char *file_read(const char *filename); +AVDictionary *strip_specifiers(const AVDictionary *dict); +const AVCodec *find_codec_or_die(void *logctx, const char *name, + enum AVMediaType type, int encoder); +int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_idx, int is_global); int configure_filtergraph(FilterGraph *fg); void check_filter_outputs(void); @@ -719,8 +841,9 @@ int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame); int ffmpeg_parse_options(int argc, char **argv); -int videotoolbox_init(AVCodecContext *s); -int qsv_init(AVCodecContext *s); +void enc_stats_write(OutputStream *ost, EncStats *es, + const AVFrame *frame, const AVPacket *pkt, + uint64_t frame_num); HWDevice *hw_device_get_by_name(const char *name); int hw_device_init_from_string(const char *arg, HWDevice **dev); @@ -732,15 +855,58 @@ int hw_device_setup_for_filter(FilterGraph *fg); int hwaccel_decode_init(AVCodecContext *avctx); -/* open the muxer when all the streams are initialized */ -int of_check_init(OutputFile *of); +/* + * Initialize muxing state for the given stream, should be called + * after the codec/streamcopy setup has been done. + * + * Open the muxer once all the streams have been initialized. + */ +int of_stream_init(OutputFile *of, OutputStream *ost); int of_write_trailer(OutputFile *of); +int of_open(const OptionsContext *o, const char *filename); void of_close(OutputFile **pof); -void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, - int unqueue); +void of_enc_stats_close(void); + +/* + * Send a single packet to the output, applying any bitstream filters + * associated with the output stream. This may result in any number + * of packets actually being written, depending on what bitstream + * filters are applied. The supplied packet is consumed and will be + * blank (as if newly-allocated) when this function returns. + * + * If eof is set, instead indicate EOF to all bitstream filters and + * therefore flush any delayed packets to the output. A blank packet + * must be supplied in this case. + */ +void of_output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof); +int64_t of_filesize(OutputFile *of); + +int ifile_open(const OptionsContext *o, const char *filename); +void ifile_close(InputFile **f); + +/** + * Get next input packet from the demuxer. + * + * @param pkt the packet is written here when this function returns 0 + * @return + * - 0 when a packet has been read successfully + * - 1 when stream end was reached, but the stream is looped; + * caller should flush decoders and read from this demuxer again + * - a negative error code on failure + */ +int ifile_get_packet(InputFile *f, AVPacket **pkt); + +/* iterate over all input streams in all input files; + * pass NULL to start iteration */ +InputStream *ist_iter(InputStream *prev); + +extern const char * const opt_name_codec_names[]; +extern const char * const opt_name_codec_tags[]; +extern const char * const opt_name_frame_rates[]; +extern const char * const opt_name_top_field_first[]; -void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double)); +void set_report_callback(void (*callback)(int, float, float, int64_t, double, double, double)); void cancel_operation(long id); #endif /* FFTOOLS_FFMPEG_H */ diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_demux.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_demux.c new file mode 100644 index 0000000..de246da --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_demux.c @@ -0,0 +1,1148 @@ +/* + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_demux.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools_ prefix added to the file name + * - fftools_ffmpeg_mux.h include added + */ + +#include +#include + +#include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/display.h" +#include "libavutil/error.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" +#include "libavutil/time.h" +#include "libavutil/timestamp.h" +#include "libavutil/thread.h" +#include "libavutil/threadmessage.h" + +#include "libavcodec/packet.h" + +#include "libavformat/avformat.h" + +static const char *const opt_name_discard[] = {"discard", NULL}; +static const char *const opt_name_reinit_filters[] = {"reinit_filter", NULL}; +static const char *const opt_name_fix_sub_duration[] = {"fix_sub_duration", NULL}; +static const char *const opt_name_canvas_sizes[] = {"canvas_size", NULL}; +static const char *const opt_name_guess_layout_max[] = {"guess_layout_max", NULL}; +static const char *const opt_name_ts_scale[] = {"itsscale", NULL}; +static const char *const opt_name_hwaccels[] = {"hwaccel", NULL}; +static const char *const opt_name_hwaccel_devices[] = {"hwaccel_device", NULL}; +static const char *const opt_name_hwaccel_output_formats[] = {"hwaccel_output_format", NULL}; +static const char *const opt_name_autorotate[] = {"autorotate", NULL}; +static const char *const opt_name_display_rotations[] = {"display_rotation", NULL}; +static const char *const opt_name_display_hflips[] = {"display_hflip", NULL}; +static const char *const opt_name_display_vflips[] = {"display_vflip", NULL}; + +typedef struct Demuxer { + InputFile f; + + /* number of times input stream should be looped */ + int loop; + /* actual duration of the longest stream in a file at the moment when + * looping happens */ + int64_t duration; + /* time base of the duration */ + AVRational time_base; + + /* number of streams that the user was warned of */ + int nb_streams_warn; + + AVThreadMessageQueue *in_thread_queue; + int thread_queue_size; + pthread_t thread; + int non_blocking; +} Demuxer; + +typedef struct DemuxMsg { + AVPacket *pkt; + int looping; + + // repeat_pict from the demuxer-internal parser + int repeat_pict; +} DemuxMsg; + +static Demuxer *demuxer_from_ifile(InputFile *f) +{ + return (Demuxer*)f; +} + +static void report_new_stream(Demuxer *d, const AVPacket *pkt) +{ + AVStream *st = d->f.ctx->streams[pkt->stream_index]; + + if (pkt->stream_index < d->nb_streams_warn) + return; + av_log(NULL, AV_LOG_WARNING, + "New %s stream %d:%d at pos:%"PRId64" and DTS:%ss\n", + av_get_media_type_string(st->codecpar->codec_type), + d->f.index, pkt->stream_index, + pkt->pos, av_ts2timestr(pkt->dts, &st->time_base)); + d->nb_streams_warn = pkt->stream_index + 1; +} + +static void ifile_duration_update(Demuxer *d, InputStream *ist, + int64_t last_duration) +{ + /* the total duration of the stream, max_pts - min_pts is + * the duration of the stream without the last frame */ + if (ist->max_pts > ist->min_pts && + ist->max_pts - (uint64_t)ist->min_pts < INT64_MAX - last_duration) + last_duration += ist->max_pts - ist->min_pts; + + if (!d->duration || + av_compare_ts(d->duration, d->time_base, + last_duration, ist->st->time_base) < 0) { + d->duration = last_duration; + d->time_base = ist->st->time_base; + } +} + +static int seek_to_start(Demuxer *d) +{ + InputFile *ifile = &d->f; + AVFormatContext *is = ifile->ctx; + InputStream *ist; + int ret; + + ret = avformat_seek_file(is, -1, INT64_MIN, is->start_time, is->start_time, 0); + if (ret < 0) + return ret; + + if (ifile->audio_duration_queue_size) { + /* duration is the length of the last frame in a stream + * when audio stream is present we don't care about + * last video frame length because it's not defined exactly */ + int got_durations = 0; + + while (got_durations < ifile->audio_duration_queue_size) { + LastFrameDuration dur; + ret = av_thread_message_queue_recv(ifile->audio_duration_queue, &dur, 0); + if (ret < 0) + return ret; + got_durations++; + + ist = ifile->streams[dur.stream_idx]; + ifile_duration_update(d, ist, dur.duration); + } + } else { + for (int i = 0; i < ifile->nb_streams; i++) { + int64_t duration = 0; + ist = ifile->streams[i]; + + if (ist->framerate.num) { + duration = av_rescale_q(1, av_inv_q(ist->framerate), ist->st->time_base); + } else if (ist->st->avg_frame_rate.num) { + duration = av_rescale_q(1, av_inv_q(ist->st->avg_frame_rate), ist->st->time_base); + } else { + duration = 1; + } + + ifile_duration_update(d, ist, duration); + } + } + + if (d->loop > 0) + d->loop--; + + return ret; +} + +static void ts_fixup(Demuxer *d, AVPacket *pkt, int *repeat_pict) +{ + InputFile *ifile = &d->f; + InputStream *ist = ifile->streams[pkt->stream_index]; + const int64_t start_time = ifile->start_time_effective; + int64_t duration; + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "demuxer -> ist_index:%d:%d type:%s " + "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s\n", + ifile->index, pkt->stream_index, + av_get_media_type_string(ist->st->codecpar->codec_type), + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), + av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base)); + } + + if (!ist->wrap_correction_done && start_time != AV_NOPTS_VALUE && + ist->st->pts_wrap_bits < 64) { + int64_t stime, stime2; + + stime = av_rescale_q(start_time, AV_TIME_BASE_Q, ist->st->time_base); + stime2= stime + (1ULL<st->pts_wrap_bits); + ist->wrap_correction_done = 1; + + if(stime2 > stime && pkt->dts != AV_NOPTS_VALUE && pkt->dts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { + pkt->dts -= 1ULL<st->pts_wrap_bits; + ist->wrap_correction_done = 0; + } + if(stime2 > stime && pkt->pts != AV_NOPTS_VALUE && pkt->pts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { + pkt->pts -= 1ULL<st->pts_wrap_bits; + ist->wrap_correction_done = 0; + } + } + + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); + + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts *= ist->ts_scale; + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts *= ist->ts_scale; + + duration = av_rescale_q(d->duration, d->time_base, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) { + pkt->pts += duration; + ist->max_pts = FFMAX(pkt->pts, ist->max_pts); + ist->min_pts = FFMIN(pkt->pts, ist->min_pts); + } + + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += duration; + + *repeat_pict = -1; + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + av_stream_get_parser(ist->st)) + *repeat_pict = av_stream_get_parser(ist->st)->repeat_pict; +} + +static void thread_set_name(InputFile *f) +{ + char name[16]; + snprintf(name, sizeof(name), "dmx%d:%s", f->index, f->ctx->iformat->name); + ff_thread_setname(name); +} + +static void *input_thread(void *arg) +{ + Demuxer *d = arg; + InputFile *f = &d->f; + AVPacket *pkt; + unsigned flags = d->non_blocking ? AV_THREAD_MESSAGE_NONBLOCK : 0; + int ret = 0; + + pkt = av_packet_alloc(); + if (!pkt) { + ret = AVERROR(ENOMEM); + goto finish; + } + + thread_set_name(f); + + while (1) { + DemuxMsg msg = { NULL }; + + ret = av_read_frame(f->ctx, pkt); + + if (ret == AVERROR(EAGAIN)) { + av_usleep(10000); + continue; + } + if (ret < 0) { + if (d->loop) { + /* signal looping to the consumer thread */ + msg.looping = 1; + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, 0); + if (ret >= 0) + ret = seek_to_start(d); + if (ret >= 0) + continue; + + /* fallthrough to the error path */ + } + + if (ret == AVERROR_EOF) + av_log(NULL, AV_LOG_VERBOSE, "EOF in input file %d\n", f->index); + else + av_log(NULL, AV_LOG_ERROR, "Error demuxing input file %d: %s\n", + f->index, av_err2str(ret)); + + break; + } + + if (do_pkt_dump) { + av_pkt_dump_log2(NULL, AV_LOG_INFO, pkt, do_hex_dump, + f->ctx->streams[pkt->stream_index]); + } + + /* the following test is needed in case new streams appear + dynamically in stream : we ignore them */ + if (pkt->stream_index >= f->nb_streams) { + report_new_stream(d, pkt); + av_packet_unref(pkt); + continue; + } + + if (pkt->flags & AV_PKT_FLAG_CORRUPT) { + av_log(NULL, exit_on_error ? AV_LOG_FATAL : AV_LOG_WARNING, + "%s: corrupt input packet in stream %d\n", + f->ctx->url, pkt->stream_index); + if (exit_on_error) { + av_packet_unref(pkt); + ret = AVERROR_INVALIDDATA; + break; + } + } + + ts_fixup(d, pkt, &msg.repeat_pict); + + msg.pkt = av_packet_alloc(); + if (!msg.pkt) { + av_packet_unref(pkt); + ret = AVERROR(ENOMEM); + break; + } + av_packet_move_ref(msg.pkt, pkt); + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, flags); + if (flags && ret == AVERROR(EAGAIN)) { + flags = 0; + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, flags); + av_log(f->ctx, AV_LOG_WARNING, + "Thread message queue blocking; consider raising the " + "thread_queue_size option (current value: %d)\n", + d->thread_queue_size); + } + if (ret < 0) { + if (ret != AVERROR_EOF) + av_log(f->ctx, AV_LOG_ERROR, + "Unable to send packet to main thread: %s\n", + av_err2str(ret)); + av_packet_free(&msg.pkt); + break; + } + } + +finish: + av_assert0(ret < 0); + av_thread_message_queue_set_err_recv(d->in_thread_queue, ret); + + av_packet_free(&pkt); + + av_log(NULL, AV_LOG_VERBOSE, "Terminating demuxer thread %d\n", f->index); + + return NULL; +} + +static void thread_stop(Demuxer *d) +{ + InputFile *f = &d->f; + DemuxMsg msg; + + if (!d->in_thread_queue) + return; + av_thread_message_queue_set_err_send(d->in_thread_queue, AVERROR_EOF); + while (av_thread_message_queue_recv(d->in_thread_queue, &msg, 0) >= 0) + av_packet_free(&msg.pkt); + + pthread_join(d->thread, NULL); + av_thread_message_queue_free(&d->in_thread_queue); + av_thread_message_queue_free(&f->audio_duration_queue); +} + +static int thread_start(Demuxer *d) +{ + int ret; + InputFile *f = &d->f; + + if (d->thread_queue_size <= 0) + d->thread_queue_size = (nb_input_files > 1 ? 8 : 1); + + if (nb_input_files > 1 && + (f->ctx->pb ? !f->ctx->pb->seekable : + strcmp(f->ctx->iformat->name, "lavfi"))) + d->non_blocking = 1; + ret = av_thread_message_queue_alloc(&d->in_thread_queue, + d->thread_queue_size, sizeof(DemuxMsg)); + if (ret < 0) + return ret; + + if (d->loop) { + int nb_audio_dec = 0; + + for (int i = 0; i < f->nb_streams; i++) { + InputStream *ist = f->streams[i]; + nb_audio_dec += !!(ist->decoding_needed && + ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO); + } + + if (nb_audio_dec) { + ret = av_thread_message_queue_alloc(&f->audio_duration_queue, + nb_audio_dec, sizeof(LastFrameDuration)); + if (ret < 0) + goto fail; + f->audio_duration_queue_size = nb_audio_dec; + } + } + + if ((ret = pthread_create(&d->thread, NULL, input_thread, d))) { + av_log(NULL, AV_LOG_ERROR, "pthread_create failed: %s. Try to increase `ulimit -v` or decrease `ulimit -s`.\n", strerror(ret)); + ret = AVERROR(ret); + goto fail; + } + + return 0; +fail: + av_thread_message_queue_free(&d->in_thread_queue); + return ret; +} + +int ifile_get_packet(InputFile *f, AVPacket **pkt) +{ + Demuxer *d = demuxer_from_ifile(f); + InputStream *ist; + DemuxMsg msg; + int ret; + + if (!d->in_thread_queue) { + ret = thread_start(d); + if (ret < 0) + return ret; + } + + if (f->readrate || f->rate_emu) { + int i; + int64_t file_start = copy_ts * ( + (f->start_time_effective != AV_NOPTS_VALUE ? f->start_time_effective * !start_at_zero : 0) + + (f->start_time != AV_NOPTS_VALUE ? f->start_time : 0) + ); + float scale = f->rate_emu ? 1.0 : f->readrate; + for (i = 0; i < f->nb_streams; i++) { + InputStream *ist = f->streams[i]; + int64_t stream_ts_offset, pts, now; + if (!ist->nb_packets || (ist->decoding_needed && !ist->got_output)) continue; + stream_ts_offset = FFMAX(ist->first_dts != AV_NOPTS_VALUE ? ist->first_dts : 0, file_start); + pts = av_rescale(ist->dts, 1000000, AV_TIME_BASE); + now = (av_gettime_relative() - ist->start) * scale + stream_ts_offset; + if (pts > now) + return AVERROR(EAGAIN); + } + } + + ret = av_thread_message_queue_recv(d->in_thread_queue, &msg, + d->non_blocking ? + AV_THREAD_MESSAGE_NONBLOCK : 0); + if (ret < 0) + return ret; + if (msg.looping) + return 1; + + ist = f->streams[msg.pkt->stream_index]; + ist->last_pkt_repeat_pict = msg.repeat_pict; + + *pkt = msg.pkt; + return 0; +} + +static void ist_free(InputStream **pist) +{ + InputStream *ist = *pist; + + if (!ist) + return; + + av_frame_free(&ist->decoded_frame); + av_packet_free(&ist->pkt); + av_dict_free(&ist->decoder_opts); + avsubtitle_free(&ist->prev_sub.subtitle); + av_frame_free(&ist->sub2video.frame); + av_freep(&ist->filters); + av_freep(&ist->hwaccel_device); + av_freep(&ist->dts_buffer); + + avcodec_free_context(&ist->dec_ctx); + avcodec_parameters_free(&ist->par); + + av_freep(pist); +} + +void ifile_close(InputFile **pf) +{ + InputFile *f = *pf; + Demuxer *d = demuxer_from_ifile(f); + + if (!f) + return; + + thread_stop(d); + + for (int i = 0; i < f->nb_streams; i++) + ist_free(&f->streams[i]); + av_freep(&f->streams); + + avformat_close_input(&f->ctx); + + av_freep(pf); +} + +static const AVCodec *choose_decoder(const OptionsContext *o, AVFormatContext *s, AVStream *st, + enum HWAccelID hwaccel_id, enum AVHWDeviceType hwaccel_device_type) + +{ + char *codec_name = NULL; + + MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st); + if (codec_name) { + const AVCodec *codec = find_codec_or_die(NULL, codec_name, st->codecpar->codec_type, 0); + st->codecpar->codec_id = codec->id; + if (recast_media && st->codecpar->codec_type != codec->type) + st->codecpar->codec_type = codec->type; + return codec; + } else { + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + hwaccel_id == HWACCEL_GENERIC && + hwaccel_device_type != AV_HWDEVICE_TYPE_NONE) { + const AVCodec *c; + void *i = NULL; + + while ((c = av_codec_iterate(&i))) { + const AVCodecHWConfig *config; + + if (c->id != st->codecpar->codec_id || + !av_codec_is_decoder(c)) + continue; + + for (int j = 0; (config = avcodec_get_hw_config(c, j)); j++) { + if (config->device_type == hwaccel_device_type) { + av_log(NULL, AV_LOG_VERBOSE, "Selecting decoder '%s' because of requested hwaccel method %s\n", + c->name, av_hwdevice_get_type_name(hwaccel_device_type)); + return c; + } + } + } + } + + return avcodec_find_decoder(st->codecpar->codec_id); + } +} + +static int guess_input_channel_layout(InputStream *ist) +{ + AVCodecContext *dec = ist->dec_ctx; + + if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) { + char layout_name[256]; + + if (dec->ch_layout.nb_channels > ist->guess_layout_max) + return 0; + av_channel_layout_default(&dec->ch_layout, dec->ch_layout.nb_channels); + if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) + return 0; + av_channel_layout_describe(&dec->ch_layout, layout_name, sizeof(layout_name)); + av_log(NULL, AV_LOG_WARNING, "Guessed Channel Layout for Input Stream " + "#%d.%d : %s\n", ist->file_index, ist->st->index, layout_name); + } + return 1; +} + +static void add_display_matrix_to_stream(const OptionsContext *o, + AVFormatContext *ctx, AVStream *st) +{ + double rotation = DBL_MAX; + int hflip = -1, vflip = -1; + int hflip_set = 0, vflip_set = 0, rotation_set = 0; + int32_t *buf; + + MATCH_PER_STREAM_OPT(display_rotations, dbl, rotation, ctx, st); + MATCH_PER_STREAM_OPT(display_hflips, i, hflip, ctx, st); + MATCH_PER_STREAM_OPT(display_vflips, i, vflip, ctx, st); + + rotation_set = rotation != DBL_MAX; + hflip_set = hflip != -1; + vflip_set = vflip != -1; + + if (!rotation_set && !hflip_set && !vflip_set) + return; + + buf = (int32_t *)av_stream_new_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); + if (!buf) { + av_log(NULL, AV_LOG_FATAL, "Failed to generate a display matrix!\n"); + exit_program(1); + } + + av_display_rotation_set(buf, + rotation_set ? -(rotation) : -0.0f); + + av_display_matrix_flip(buf, + hflip_set ? hflip : 0, + vflip_set ? vflip : 0); +} + +/* Add all the streams from the given input file to the demuxer */ +static void add_input_streams(const OptionsContext *o, Demuxer *d) +{ + InputFile *f = &d->f; + AVFormatContext *ic = f->ctx; + int i, ret; + + for (i = 0; i < ic->nb_streams; i++) { + AVStream *st = ic->streams[i]; + AVCodecParameters *par = st->codecpar; + InputStream *ist; + char *framerate = NULL, *hwaccel_device = NULL; + const char *hwaccel = NULL; + char *hwaccel_output_format = NULL; + char *codec_tag = NULL; + char *next; + char *discard_str = NULL; + const AVClass *cc = avcodec_get_class(); + const AVOption *discard_opt = av_opt_find(&cc, "skip_frame", NULL, + 0, AV_OPT_SEARCH_FAKE_OBJ); + + ist = ALLOC_ARRAY_ELEM(f->streams, f->nb_streams); + ist->st = st; + ist->file_index = f->index; + ist->discard = 1; + st->discard = AVDISCARD_ALL; + ist->nb_samples = 0; + ist->first_dts = AV_NOPTS_VALUE; + ist->min_pts = INT64_MAX; + ist->max_pts = INT64_MIN; + + ist->ts_scale = 1.0; + MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st); + + ist->autorotate = 1; + MATCH_PER_STREAM_OPT(autorotate, i, ist->autorotate, ic, st); + + MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st); + if (codec_tag) { + uint32_t tag = strtol(codec_tag, &next, 0); + if (*next) + tag = AV_RL32(codec_tag); + st->codecpar->codec_tag = tag; + } + + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + add_display_matrix_to_stream(o, ic, st); + + MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st); + MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, + hwaccel_output_format, ic, st); + + if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "cuvid")) { + av_log(NULL, AV_LOG_WARNING, + "WARNING: defaulting hwaccel_output_format to cuda for compatibility " + "with old commandlines. This behaviour is DEPRECATED and will be removed " + "in the future. Please explicitly set \"-hwaccel_output_format cuda\".\n"); + ist->hwaccel_output_format = AV_PIX_FMT_CUDA; + } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "qsv")) { + av_log(NULL, AV_LOG_WARNING, + "WARNING: defaulting hwaccel_output_format to qsv for compatibility " + "with old commandlines. This behaviour is DEPRECATED and will be removed " + "in the future. Please explicitly set \"-hwaccel_output_format qsv\".\n"); + ist->hwaccel_output_format = AV_PIX_FMT_QSV; + } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "mediacodec")) { + // There is no real AVHWFrameContext implementation. Set + // hwaccel_output_format to avoid av_hwframe_transfer_data error. + ist->hwaccel_output_format = AV_PIX_FMT_MEDIACODEC; + } else if (hwaccel_output_format) { + ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format); + if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) { + av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output " + "format: %s", hwaccel_output_format); + } + } else { + ist->hwaccel_output_format = AV_PIX_FMT_NONE; + } + + if (hwaccel) { + // The NVDEC hwaccels use a CUDA device, so remap the name here. + if (!strcmp(hwaccel, "nvdec") || !strcmp(hwaccel, "cuvid")) + hwaccel = "cuda"; + + if (!strcmp(hwaccel, "none")) + ist->hwaccel_id = HWACCEL_NONE; + else if (!strcmp(hwaccel, "auto")) + ist->hwaccel_id = HWACCEL_AUTO; + else { + enum AVHWDeviceType type = av_hwdevice_find_type_by_name(hwaccel); + if (type != AV_HWDEVICE_TYPE_NONE) { + ist->hwaccel_id = HWACCEL_GENERIC; + ist->hwaccel_device_type = type; + } + + if (!ist->hwaccel_id) { + av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n", + hwaccel); + av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: "); + type = AV_HWDEVICE_TYPE_NONE; + while ((type = av_hwdevice_iterate_types(type)) != + AV_HWDEVICE_TYPE_NONE) + av_log(NULL, AV_LOG_FATAL, "%s ", + av_hwdevice_get_type_name(type)); + av_log(NULL, AV_LOG_FATAL, "\n"); + exit_program(1); + } + } + } + + MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st); + if (hwaccel_device) { + ist->hwaccel_device = av_strdup(hwaccel_device); + if (!ist->hwaccel_device) + report_and_exit(AVERROR(ENOMEM)); + } + + ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE; + } + + ist->dec = choose_decoder(o, ic, st, ist->hwaccel_id, ist->hwaccel_device_type); + ist->decoder_opts = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ic, st, ist->dec); + + ist->reinit_filters = -1; + MATCH_PER_STREAM_OPT(reinit_filters, i, ist->reinit_filters, ic, st); + + MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st); + ist->user_set_discard = AVDISCARD_NONE; + + if ((o->video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) || + (o->audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || + (o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) || + (o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA)) + ist->user_set_discard = AVDISCARD_ALL; + + if (discard_str && av_opt_eval_int(&cc, discard_opt, discard_str, &ist->user_set_discard) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing discard %s.\n", + discard_str); + exit_program(1); + } + + ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE; + ist->prev_pkt_pts = AV_NOPTS_VALUE; + + ist->dec_ctx = avcodec_alloc_context3(ist->dec); + if (!ist->dec_ctx) + report_and_exit(AVERROR(ENOMEM)); + + ret = avcodec_parameters_to_context(ist->dec_ctx, par); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); + exit_program(1); + } + + ist->decoded_frame = av_frame_alloc(); + if (!ist->decoded_frame) + report_and_exit(AVERROR(ENOMEM)); + + ist->pkt = av_packet_alloc(); + if (!ist->pkt) + report_and_exit(AVERROR(ENOMEM)); + + if (o->bitexact) + ist->dec_ctx->flags |= AV_CODEC_FLAG_BITEXACT; + + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: + // avformat_find_stream_info() doesn't set this for us anymore. + ist->dec_ctx->framerate = st->avg_frame_rate; + + MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st); + if (framerate && av_parse_video_rate(&ist->framerate, + framerate) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing framerate %s.\n", + framerate); + exit_program(1); + } + + ist->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st); + + ist->framerate_guessed = av_guess_frame_rate(ic, st, NULL); + + break; + case AVMEDIA_TYPE_AUDIO: + ist->guess_layout_max = INT_MAX; + MATCH_PER_STREAM_OPT(guess_layout_max, i, ist->guess_layout_max, ic, st); + guess_input_channel_layout(ist); + break; + case AVMEDIA_TYPE_DATA: + case AVMEDIA_TYPE_SUBTITLE: { + char *canvas_size = NULL; + MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st); + MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st); + if (canvas_size && + av_parse_video_size(&ist->dec_ctx->width, &ist->dec_ctx->height, canvas_size) < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid canvas size: %s.\n", canvas_size); + exit_program(1); + } + break; + } + case AVMEDIA_TYPE_ATTACHMENT: + case AVMEDIA_TYPE_UNKNOWN: + break; + default: + abort(); + } + + ist->par = avcodec_parameters_alloc(); + if (!ist->par) + report_and_exit(AVERROR(ENOMEM)); + + ret = avcodec_parameters_from_context(ist->par, ist->dec_ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); + exit_program(1); + } + } +} + +static void dump_attachment(AVStream *st, const char *filename) +{ + int ret; + AVIOContext *out = NULL; + const AVDictionaryEntry *e; + + if (!st->codecpar->extradata_size) { + av_log(NULL, AV_LOG_WARNING, "No extradata to dump in stream #%d:%d.\n", + nb_input_files - 1, st->index); + return; + } + if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) + filename = e->value; + if (!*filename) { + av_log(NULL, AV_LOG_FATAL, "No filename specified and no 'filename' tag" + "in stream #%d:%d.\n", nb_input_files - 1, st->index); + exit_program(1); + } + + assert_file_overwrite(filename); + + if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &int_cb, NULL)) < 0) { + av_log(NULL, AV_LOG_FATAL, "Could not open file %s for writing.\n", + filename); + exit_program(1); + } + + avio_write(out, st->codecpar->extradata, st->codecpar->extradata_size); + avio_flush(out); + avio_close(out); +} + +int ifile_open(const OptionsContext *o, const char *filename) +{ + Demuxer *d; + InputFile *f; + AVFormatContext *ic; + const AVInputFormat *file_iformat = NULL; + int err, i, ret; + int64_t timestamp; + AVDictionary *unused_opts = NULL; + const AVDictionaryEntry *e = NULL; + char * video_codec_name = NULL; + char * audio_codec_name = NULL; + char *subtitle_codec_name = NULL; + char * data_codec_name = NULL; + int scan_all_pmts_set = 0; + + int64_t start_time = o->start_time; + int64_t start_time_eof = o->start_time_eof; + int64_t stop_time = o->stop_time; + int64_t recording_time = o->recording_time; + + if (stop_time != INT64_MAX && recording_time != INT64_MAX) { + stop_time = INT64_MAX; + av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); + } + + if (stop_time != INT64_MAX && recording_time == INT64_MAX) { + int64_t start = start_time == AV_NOPTS_VALUE ? 0 : start_time; + if (stop_time <= start) { + av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); + exit_program(1); + } else { + recording_time = stop_time - start; + } + } + + if (o->format) { + if (!(file_iformat = av_find_input_format(o->format))) { + av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format); + exit_program(1); + } + } + + if (!strcmp(filename, "-")) + filename = "fd:"; + + stdin_interaction &= strncmp(filename, "pipe:", 5) && + strcmp(filename, "fd:") && + strcmp(filename, "/dev/stdin"); + + /* get default parameters from command line */ + ic = avformat_alloc_context(); + if (!ic) + report_and_exit(AVERROR(ENOMEM)); + if (o->nb_audio_sample_rate) { + av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0); + } + if (o->nb_audio_channels) { + const AVClass *priv_class; + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "ch_layout", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + char buf[32]; + snprintf(buf, sizeof(buf), "%dC", o->audio_channels[o->nb_audio_channels - 1].u.i); + av_dict_set(&o->g->format_opts, "ch_layout", buf, 0); + } + } + if (o->nb_audio_ch_layouts) { + const AVClass *priv_class; + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "ch_layout", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + av_dict_set(&o->g->format_opts, "ch_layout", o->audio_ch_layouts[o->nb_audio_ch_layouts - 1].u.str, 0); + } + } + if (o->nb_frame_rates) { + const AVClass *priv_class; + /* set the format-level framerate option; + * this is important for video grabbers, e.g. x11 */ + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "framerate", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + av_dict_set(&o->g->format_opts, "framerate", + o->frame_rates[o->nb_frame_rates - 1].u.str, 0); + } + } + if (o->nb_frame_sizes) { + av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0); + } + if (o->nb_frame_pix_fmts) + av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0); + + MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v"); + MATCH_PER_TYPE_OPT(codec_names, str, audio_codec_name, ic, "a"); + MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s"); + MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d"); + + if (video_codec_name) + ic->video_codec = find_codec_or_die(NULL, video_codec_name , AVMEDIA_TYPE_VIDEO , 0); + if (audio_codec_name) + ic->audio_codec = find_codec_or_die(NULL, audio_codec_name , AVMEDIA_TYPE_AUDIO , 0); + if (subtitle_codec_name) + ic->subtitle_codec = find_codec_or_die(NULL, subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0); + if (data_codec_name) + ic->data_codec = find_codec_or_die(NULL, data_codec_name , AVMEDIA_TYPE_DATA , 0); + + ic->video_codec_id = video_codec_name ? ic->video_codec->id : AV_CODEC_ID_NONE; + ic->audio_codec_id = audio_codec_name ? ic->audio_codec->id : AV_CODEC_ID_NONE; + ic->subtitle_codec_id = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE; + ic->data_codec_id = data_codec_name ? ic->data_codec->id : AV_CODEC_ID_NONE; + + ic->flags |= AVFMT_FLAG_NONBLOCK; + if (o->bitexact) + ic->flags |= AVFMT_FLAG_BITEXACT; + ic->interrupt_callback = int_cb; + + if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { + av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); + scan_all_pmts_set = 1; + } + /* open the input file with generic avformat function */ + err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); + if (err < 0) { + print_error(filename, err); + if (err == AVERROR_PROTOCOL_NOT_FOUND) + av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename); + exit_program(1); + } + if (scan_all_pmts_set) + av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); + remove_avoptions(&o->g->format_opts, o->g->codec_opts); + assert_avoptions(o->g->format_opts); + + /* apply forced codec ids */ + for (i = 0; i < ic->nb_streams; i++) + choose_decoder(o, ic, ic->streams[i], HWACCEL_NONE, AV_HWDEVICE_TYPE_NONE); + + if (o->find_stream_info) { + AVDictionary **opts = setup_find_stream_info_opts(ic, o->g->codec_opts); + int orig_nb_streams = ic->nb_streams; + + /* If not enough info to get the stream parameters, we decode the + first frames to get it. (used in mpeg case for example) */ + ret = avformat_find_stream_info(ic, opts); + + for (i = 0; i < orig_nb_streams; i++) + av_dict_free(&opts[i]); + av_freep(&opts); + + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename); + if (ic->nb_streams == 0) { + avformat_close_input(&ic); + exit_program(1); + } + } + } + + if (start_time != AV_NOPTS_VALUE && start_time_eof != AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_WARNING, "Cannot use -ss and -sseof both, using -ss for %s\n", filename); + start_time_eof = AV_NOPTS_VALUE; + } + + if (start_time_eof != AV_NOPTS_VALUE) { + if (start_time_eof >= 0) { + av_log(NULL, AV_LOG_ERROR, "-sseof value must be negative; aborting\n"); + exit_program(1); + } + if (ic->duration > 0) { + start_time = start_time_eof + ic->duration; + if (start_time < 0) { + av_log(NULL, AV_LOG_WARNING, "-sseof value seeks to before start of file %s; ignored\n", filename); + start_time = AV_NOPTS_VALUE; + } + } else + av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename); + } + timestamp = (start_time == AV_NOPTS_VALUE) ? 0 : start_time; + /* add the stream start time */ + if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE) + timestamp += ic->start_time; + + /* if seeking requested, we execute it */ + if (start_time != AV_NOPTS_VALUE) { + int64_t seek_timestamp = timestamp; + + if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) { + int dts_heuristic = 0; + for (i=0; inb_streams; i++) { + const AVCodecParameters *par = ic->streams[i]->codecpar; + if (par->video_delay) { + dts_heuristic = 1; + break; + } + } + if (dts_heuristic) { + seek_timestamp -= 3*AV_TIME_BASE / 23; + } + } + ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", + filename, (double)timestamp / AV_TIME_BASE); + } + } + + d = allocate_array_elem(&input_files, sizeof(*d), &nb_input_files); + f = &d->f; + + f->ctx = ic; + f->index = nb_input_files - 1; + f->start_time = start_time; + f->recording_time = recording_time; + f->input_sync_ref = o->input_sync_ref; + f->input_ts_offset = o->input_ts_offset; + f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); + f->rate_emu = o->rate_emu; + f->accurate_seek = o->accurate_seek; + d->loop = o->loop; + d->duration = 0; + d->time_base = (AVRational){ 1, 1 }; + + f->readrate = o->readrate ? o->readrate : 0.0; + if (f->readrate < 0.0f) { + av_log(NULL, AV_LOG_ERROR, "Option -readrate for Input #%d is %0.3f; it must be non-negative.\n", f->index, f->readrate); + exit_program(1); + } + if (f->readrate && f->rate_emu) { + av_log(NULL, AV_LOG_WARNING, "Both -readrate and -re set for Input #%d. Using -readrate %0.3f.\n", f->index, f->readrate); + f->rate_emu = 0; + } + + d->thread_queue_size = o->thread_queue_size; + + /* update the current parameters so that they match the one of the input stream */ + add_input_streams(o, d); + + /* dump the file content */ + av_dump_format(ic, f->index, filename, 0); + + /* check if all codec options have been used */ + unused_opts = strip_specifiers(o->g->codec_opts); + for (i = 0; i < f->nb_streams; i++) { + e = NULL; + while ((e = av_dict_iterate(f->streams[i]->decoder_opts, e))) + av_dict_set(&unused_opts, e->key, NULL, 0); + } + + e = NULL; + while ((e = av_dict_iterate(unused_opts, e))) { + const AVClass *class = avcodec_get_class(); + const AVOption *option = av_opt_find(&class, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + const AVClass *fclass = avformat_get_class(); + const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + if (!option || foption) + continue; + + + if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) { + av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " + "input file #%d (%s) is not a decoding option.\n", e->key, + option->help ? option->help : "", f->index, + filename); + exit_program(1); + } + + av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " + "input file #%d (%s) has not been used for any stream. The most " + "likely reason is either wrong type (e.g. a video option with " + "no video streams) or that it is a private option of some decoder " + "which was not actually used for any stream.\n", e->key, + option->help ? option->help : "", f->index, filename); + } + av_dict_free(&unused_opts); + + for (i = 0; i < o->nb_dump_attachment; i++) { + int j; + + for (j = 0; j < ic->nb_streams; j++) { + AVStream *st = ic->streams[j]; + + if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1) + dump_attachment(st, o->dump_attachment[i].u.str); + } + } + + return 0; +} diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_filter.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_filter.c index 46edae5..73d7763 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_filter.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_filter.c @@ -1,6 +1,7 @@ /* * ffmpeg filter configuration * Copyright (c) 2018 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,12 @@ * We manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 08.2018 @@ -69,8 +76,9 @@ static const enum AVPixelFormat *get_compliance_normal_pix_fmts(const AVCodec *c } } -enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, - const AVCodec *codec, enum AVPixelFormat target) +enum AVPixelFormat +choose_pixel_fmt(const AVCodec *codec, enum AVPixelFormat target, + int strict_std_compliance) { if (codec && codec->pix_fmts) { const enum AVPixelFormat *p = codec->pix_fmts; @@ -79,7 +87,7 @@ enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, int has_alpha = desc ? desc->nb_components % 2 == 0 : 0; enum AVPixelFormat best= AV_PIX_FMT_NONE; - if (enc_ctx->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { + if (strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { p = get_compliance_normal_pix_fmts(codec, p); } for (; *p != AV_PIX_FMT_NONE; p++) { @@ -106,6 +114,7 @@ enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) { OutputStream *ost = ofilter->ost; + AVCodecContext *enc = ost->enc_ctx; const AVDictionaryEntry *strict_dict = av_dict_get(ost->encoder_opts, "strict", NULL, 0); if (strict_dict) // used by choose_pixel_fmt() and below @@ -119,13 +128,14 @@ static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) return av_get_pix_fmt_name(ost->enc_ctx->pix_fmt); } if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { - return av_get_pix_fmt_name(choose_pixel_fmt(ost->st, ost->enc_ctx, ost->enc, ost->enc_ctx->pix_fmt)); - } else if (ost->enc && ost->enc->pix_fmts) { + return av_get_pix_fmt_name(choose_pixel_fmt(enc->codec, enc->pix_fmt, + ost->enc_ctx->strict_std_compliance)); + } else if (enc->codec->pix_fmts) { const enum AVPixelFormat *p; - p = ost->enc->pix_fmts; + p = enc->codec->pix_fmts; if (ost->enc_ctx->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { - p = get_compliance_normal_pix_fmts(ost->enc, p); + p = get_compliance_normal_pix_fmts(enc->codec, p); } for (; *p != AV_PIX_FMT_NONE; p++) { @@ -133,7 +143,7 @@ static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) av_bprintf(bprint, "%s%c", name, p[1] == AV_PIX_FMT_NONE ? '\0' : '|'); } if (!av_bprint_is_complete(bprint)) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); return bprint->str; } else return NULL; @@ -197,7 +207,7 @@ int init_simple_filtergraph(InputStream *ist, OutputStream *ost) InputFilter *ifilter; if (!fg) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); fg->index = nb_filtergraphs; ofilter = ALLOC_ARRAY_ELEM(fg->outputs, fg->nb_outputs); @@ -214,7 +224,7 @@ int init_simple_filtergraph(InputStream *ist, OutputStream *ost) ifilter->frame_queue = av_fifo_alloc2(8, sizeof(AVFrame*), AV_FIFO_FLAG_AUTO_GROW); if (!ifilter->frame_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); GROW_ARRAY(ist->filters, ist->nb_filters); ist->filters[ist->nb_filters - 1] = ifilter; @@ -238,7 +248,7 @@ static char *describe_filter_link(FilterGraph *fg, AVFilterInOut *inout, int in) res = av_asprintf("%s:%s", ctx->filter->name, avfilter_pad_get_name(pads, inout->pad_idx)); if (!res) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); return res; } @@ -285,7 +295,7 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) "matches no streams.\n", p, fg->graph_desc); exit_program(1); } - ist = input_streams[input_files[file_idx]->ist_index + st->index]; + ist = input_files[file_idx]->streams[st->index]; if (ist->user_set_discard == AVDISCARD_ALL) { av_log(NULL, AV_LOG_FATAL, "Stream specifier '%s' in filtergraph description %s " "matches a disabled input stream.\n", p, fg->graph_desc); @@ -293,14 +303,13 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) } } else { /* find the first unused stream of corresponding type */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; + for (ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { if (ist->user_set_discard == AVDISCARD_ALL) continue; if (ist->dec_ctx->codec_type == type && ist->discard) break; } - if (i == nb_input_streams) { + if (!ist) { av_log(NULL, AV_LOG_FATAL, "Cannot find a matching stream for " "unlabeled input pad %d on filter %s\n", in->pad_idx, in->filter_ctx->name); @@ -323,12 +332,162 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) ifilter->frame_queue = av_fifo_alloc2(8, sizeof(AVFrame*), AV_FIFO_FLAG_AUTO_GROW); if (!ifilter->frame_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); GROW_ARRAY(ist->filters, ist->nb_filters); ist->filters[ist->nb_filters - 1] = ifilter; } +static int read_binary(const char *path, uint8_t **data, int *len) +{ + AVIOContext *io = NULL; + int64_t fsize; + int ret; + + *data = NULL; + *len = 0; + + ret = avio_open2(&io, path, AVIO_FLAG_READ, &int_cb, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot open file '%s': %s\n", + path, av_err2str(ret)); + return ret; + } + + fsize = avio_size(io); + if (fsize < 0 || fsize > INT_MAX) { + av_log(NULL, AV_LOG_ERROR, "Cannot obtain size of file %s\n", path); + ret = AVERROR(EIO); + goto fail; + } + + *data = av_malloc(fsize); + if (!*data) { + ret = AVERROR(ENOMEM); + goto fail; + } + + ret = avio_read(io, *data, fsize); + if (ret != fsize) { + av_log(NULL, AV_LOG_ERROR, "Error reading file %s\n", path); + ret = ret < 0 ? ret : AVERROR(EIO); + goto fail; + } + + *len = fsize; + + return 0; +fail: + avio_close(io); + av_freep(data); + *len = 0; + return ret; +} + +static int filter_opt_apply(AVFilterContext *f, const char *key, const char *val) +{ + const AVOption *o = NULL; + int ret; + + ret = av_opt_set(f, key, val, AV_OPT_SEARCH_CHILDREN); + if (ret >= 0) + return 0; + + if (ret == AVERROR_OPTION_NOT_FOUND && key[0] == '/') + o = av_opt_find(f, key + 1, NULL, 0, AV_OPT_SEARCH_CHILDREN); + if (!o) + goto err_apply; + + // key is a valid option name prefixed with '/' + // interpret value as a path from which to load the actual option value + key++; + + if (o->type == AV_OPT_TYPE_BINARY) { + uint8_t *data; + int len; + + ret = read_binary(val, &data, &len); + if (ret < 0) + goto err_load; + + ret = av_opt_set_bin(f, key, data, len, AV_OPT_SEARCH_CHILDREN); + av_freep(&data); + } else { + char *data = file_read(val); + if (!data) { + ret = AVERROR(EIO); + goto err_load; + } + + ret = av_opt_set(f, key, data, AV_OPT_SEARCH_CHILDREN); + av_freep(&data); + } + if (ret < 0) + goto err_apply; + + return 0; + +err_apply: + av_log(NULL, AV_LOG_ERROR, + "Error applying option '%s' to filter '%s': %s\n", + key, f->filter->name, av_err2str(ret)); + return ret; +err_load: + av_log(NULL, AV_LOG_ERROR, + "Error loading value for option '%s' from file '%s'\n", + key, val); + return ret; +} + +static int graph_opts_apply(AVFilterGraphSegment *seg) +{ + for (size_t i = 0; i < seg->nb_chains; i++) { + AVFilterChain *ch = seg->chains[i]; + + for (size_t j = 0; j < ch->nb_filters; j++) { + AVFilterParams *p = ch->filters[j]; + const AVDictionaryEntry *e = NULL; + + av_assert0(p->filter); + + while ((e = av_dict_iterate(p->opts, e))) { + int ret = filter_opt_apply(p->filter, e->key, e->value); + if (ret < 0) + return ret; + } + + av_dict_free(&p->opts); + } + } + + return 0; +} + +static int graph_parse(AVFilterGraph *graph, const char *desc, + AVFilterInOut **inputs, AVFilterInOut **outputs) +{ + AVFilterGraphSegment *seg; + int ret; + + ret = avfilter_graph_segment_parse(graph, desc, 0, &seg); + if (ret < 0) + return ret; + + ret = avfilter_graph_segment_create_filters(seg, 0); + if (ret < 0) + goto fail; + + ret = graph_opts_apply(seg); + if (ret < 0) + goto fail; + + ret = avfilter_graph_segment_apply(seg, 0, inputs, outputs); + +fail: + avfilter_graph_segment_free(&seg); + return ret; +} + int init_complex_filtergraph(FilterGraph *fg) { AVFilterInOut *inputs, *outputs, *cur; @@ -342,7 +501,7 @@ int init_complex_filtergraph(FilterGraph *fg) return AVERROR(ENOMEM); graph->nb_threads = 1; - ret = avfilter_graph_parse2(graph, fg->graph_desc, &inputs, &outputs); + ret = graph_parse(graph, fg->graph_desc, &inputs, &outputs); if (ret < 0) goto fail; @@ -467,8 +626,7 @@ static int configure_output_video_filter(FilterGraph *fg, OutputFilter *ofilter, snprintf(args, sizeof(args), "%d:%d", ofilter->width, ofilter->height); - while ((e = av_dict_get(ost->sws_dict, "", e, - AV_DICT_IGNORE_SUFFIX))) { + while ((e = av_dict_iterate(ost->sws_dict, e))) { av_strlcatf(args, sizeof(args), ":%s=%s", e->key, e->value); } @@ -575,6 +733,7 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, pad_idx = 0; \ } while (0) av_bprint_init(&args, 0, AV_BPRINT_SIZE_UNLIMITED); +#if FFMPEG_OPT_MAP_CHANNEL if (ost->audio_channels_mapped) { AVChannelLayout mapped_layout = { 0 }; int i; @@ -587,6 +746,7 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, AUTO_INSERT_FILTER("-map_channel", "pan", args.str); av_bprint_clear(&args); } +#endif if (codec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) av_channel_layout_default(&codec->ch_layout, codec->ch_layout.nb_channels); @@ -620,11 +780,11 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, if (ost->apad && of->shortest) { int i; - for (i=0; ictx->nb_streams; i++) - if (of->ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + for (i = 0; i < of->nb_streams; i++) + if (of->streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) break; - if (ictx->nb_streams) { + if (i < of->nb_streams) { AUTO_INSERT_FILTER("-apad", "apad", ost->apad); } } @@ -751,7 +911,7 @@ static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter, } if (!fr.num) - fr = av_guess_frame_rate(input_files[ist->file_index]->ctx, ist->st, NULL); + fr = ist->framerate_guessed; if (ist->dec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { ret = sub2video_prepare(ist, ifilter); @@ -904,40 +1064,6 @@ static int configure_input_audio_filter(FilterGraph *fg, InputFilter *ifilter, last_filter = filt_ctx; \ } while (0) - if (audio_sync_method > 0) { - char args[256] = {0}; - - av_strlcatf(args, sizeof(args), "async=%d", audio_sync_method); - if (audio_drift_threshold != 0.1) - av_strlcatf(args, sizeof(args), ":min_hard_comp=%f", audio_drift_threshold); - if (!fg->reconfiguration) - av_strlcatf(args, sizeof(args), ":first_pts=0"); - AUTO_INSERT_FILTER_INPUT("-async", "aresample", args); - } - -// if (ost->audio_channels_mapped) { -// int i; -// AVBPrint pan_buf; -// av_bprint_init(&pan_buf, 256, 8192); -// av_bprintf(&pan_buf, "0x%"PRIx64, -// av_get_default_channel_layout(ost->audio_channels_mapped)); -// for (i = 0; i < ost->audio_channels_mapped; i++) -// if (ost->audio_channels_map[i] != -1) -// av_bprintf(&pan_buf, ":c%d=c%d", i, ost->audio_channels_map[i]); -// AUTO_INSERT_FILTER_INPUT("-map_channel", "pan", pan_buf.str); -// av_bprint_finalize(&pan_buf, NULL); -// } - - if (audio_volume != 256) { - char args[256]; - - av_log(NULL, AV_LOG_WARNING, "-vol has been deprecated. Use the volume " - "audio filter instead.\n"); - - snprintf(args, sizeof(args), "%f", audio_volume / 256.); - AUTO_INSERT_FILTER_INPUT("-vol", "volume", args); - } - snprintf(name, sizeof(name), "trim for input stream %d:%d", ist->file_index, ist->st->index); if (copy_ts) { @@ -1020,44 +1146,39 @@ int configure_filtergraph(FilterGraph *fg) if (simple) { OutputStream *ost = fg->outputs[0]->ost; - char args[512]; - const AVDictionaryEntry *e = NULL; if (filter_nbthreads) { ret = av_opt_set(fg->graph, "threads", filter_nbthreads, 0); if (ret < 0) goto fail; } else { + const AVDictionaryEntry *e = NULL; e = av_dict_get(ost->encoder_opts, "threads", NULL, 0); if (e) av_opt_set(fg->graph, "threads", e->value, 0); } - args[0] = 0; - e = NULL; - while ((e = av_dict_get(ost->sws_dict, "", e, - AV_DICT_IGNORE_SUFFIX))) { - av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); - } - if (strlen(args)) { - args[strlen(args)-1] = 0; - fg->graph->scale_sws_opts = av_strdup(args); + if (av_dict_count(ost->sws_dict)) { + ret = av_dict_get_string(ost->sws_dict, + &fg->graph->scale_sws_opts, + '=', ':'); + if (ret < 0) + goto fail; } - args[0] = 0; - e = NULL; - while ((e = av_dict_get(ost->swr_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) { - av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); + if (av_dict_count(ost->swr_opts)) { + char *args; + ret = av_dict_get_string(ost->swr_opts, &args, '=', ':'); + if (ret < 0) + goto fail; + av_opt_set(fg->graph, "aresample_swr_opts", args, 0); + av_free(args); } - if (strlen(args)) - args[strlen(args)-1] = 0; - av_opt_set(fg->graph, "aresample_swr_opts", args, 0); } else { fg->graph->nb_threads = filter_complex_nbthreads; } - if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0) + if ((ret = graph_parse(fg->graph, graph_desc, &inputs, &outputs)) < 0) goto fail; ret = hw_device_setup_for_filter(fg); @@ -1131,16 +1252,8 @@ int configure_filtergraph(FilterGraph *fg) for (i = 0; i < fg->nb_outputs; i++) { OutputStream *ost = fg->outputs[i]->ost; - if (!ost->enc) { - /* identical to the same check in ffmpeg.c, needed because - complex filter graphs are initialized earlier */ - av_log(NULL, AV_LOG_ERROR, "Encoder (codec %s) not found for output stream #%d:%d\n", - avcodec_get_name(ost->st->codecpar->codec_id), ost->file_index, ost->index); - ret = AVERROR(EINVAL); - goto fail; - } - if (ost->enc->type == AVMEDIA_TYPE_AUDIO && - !(ost->enc->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + if (ost->enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO && + !(ost->enc_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) av_buffersink_set_frame_size(ost->filter->filter, ost->enc_ctx->frame_size); } diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_hw.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_hw.c index 8e5427a..42f16ec 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_hw.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_hw.c @@ -1,5 +1,6 @@ /* - * Copyright (c) 2018 Taner Sener + * Copyright (c) 2018-2019 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -23,6 +24,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 12.2019 @@ -357,7 +364,7 @@ int hw_device_setup_for_decode(InputStream *ist) if (ist->hwaccel_id == HWACCEL_AUTO) { ist->hwaccel_device_type = dev->type; } else if (ist->hwaccel_device_type != dev->type) { - av_log(ist->dec_ctx, AV_LOG_ERROR, "Invalid hwaccel device " + av_log(NULL, AV_LOG_ERROR, "Invalid hwaccel device " "specified for decoder: device %s of type %s is not " "usable with hwaccel %s.\n", dev->name, av_hwdevice_get_type_name(dev->type), @@ -408,7 +415,7 @@ int hw_device_setup_for_decode(InputStream *ist) type = config->device_type; dev = hw_device_get_by_type(type); if (dev) { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with existing device %s.\n", av_hwdevice_get_type_name(type), dev->name); } @@ -426,12 +433,12 @@ int hw_device_setup_for_decode(InputStream *ist) continue; } if (ist->hwaccel_device) { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with new device created " "from %s.\n", av_hwdevice_get_type_name(type), ist->hwaccel_device); } else { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with new default device.\n", av_hwdevice_get_type_name(type)); } @@ -439,7 +446,7 @@ int hw_device_setup_for_decode(InputStream *ist) if (dev) { ist->hwaccel_device_type = type; } else { - av_log(ist->dec_ctx, AV_LOG_INFO, "Auto hwaccel " + av_log(NULL, AV_LOG_INFO, "Auto hwaccel " "disabled: no device found.\n"); ist->hwaccel_id = HWACCEL_NONE; return 0; @@ -447,7 +454,7 @@ int hw_device_setup_for_decode(InputStream *ist) } if (!dev) { - av_log(ist->dec_ctx, AV_LOG_ERROR, "No device available " + av_log(NULL, AV_LOG_ERROR, "No device available " "for decoder: device type %s needed for codec %s.\n", av_hwdevice_get_type_name(type), ist->dec->name); return err; @@ -479,7 +486,7 @@ int hw_device_setup_for_encode(OutputStream *ost) } for (i = 0;; i++) { - config = avcodec_get_hw_config(ost->enc, i); + config = avcodec_get_hw_config(ost->enc_ctx->codec, i); if (!config) break; @@ -490,7 +497,7 @@ int hw_device_setup_for_encode(OutputStream *ost) av_log(ost->enc_ctx, AV_LOG_VERBOSE, "Using input " "frames context (format %s) with %s encoder.\n", av_get_pix_fmt_name(ost->enc_ctx->pix_fmt), - ost->enc->name); + ost->enc_ctx->codec->name); ost->enc_ctx->hw_frames_ctx = av_buffer_ref(frames_ref); if (!ost->enc_ctx->hw_frames_ctx) return AVERROR(ENOMEM); @@ -505,7 +512,7 @@ int hw_device_setup_for_encode(OutputStream *ost) if (dev) { av_log(ost->enc_ctx, AV_LOG_VERBOSE, "Using device %s " "(type %s) with %s encoder.\n", dev->name, - av_hwdevice_get_type_name(dev->type), ost->enc->name); + av_hwdevice_get_type_name(dev->type), ost->enc_ctx->codec->name); ost->enc_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref); if (!ost->enc_ctx->hw_device_ctx) return AVERROR(ENOMEM); diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux.c index 9728feb..0a3d1e7 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux.c @@ -1,6 +1,7 @@ /* * This file is part of FFmpeg. * Copyright (c) 2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,108 +23,101 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop the ffmpeg-kit library. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - want_sdp marked as thread-local + * - ms_from_ost migrated from ffmpeg_mux.c and marked as non-static + * * ffmpeg-kit changes by Taner Sener * * 09.2022 * -------------------------------------------------------- * - fftools_ prefix added to fftools headers * - using main_ffmpeg_return_code instead of main_return_code + * - printf replaced with av_log statements */ +#include #include #include #include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" +#include "fftools_objpool.h" +#include "fftools_sync_queue.h" +#include "fftools_thread_queue.h" #include "libavutil/fifo.h" #include "libavutil/intreadwrite.h" #include "libavutil/log.h" #include "libavutil/mem.h" #include "libavutil/timestamp.h" +#include "libavutil/thread.h" #include "libavcodec/packet.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" -static void close_all_output_streams(OutputStream *ost, OSTFinished this_stream, OSTFinished others) +__thread int want_sdp = 1; + +MuxStream *ms_from_ost(OutputStream *ost) { - int i; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost2 = output_streams[i]; - ost2->finished |= ost == ost2 ? this_stream : others; + return (MuxStream*)ost; +} + +static Muxer *mux_from_of(OutputFile *of) +{ + return (Muxer*)of; +} + +static int64_t filesize(AVIOContext *pb) +{ + int64_t ret = -1; + + if (pb) { + ret = avio_size(pb); + if (ret <= 0) // FIXME improve avio_size() so it works with non seekable output too + ret = avio_tell(pb); } + + return ret; } -void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, - int unqueue) +static int write_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) { - AVFormatContext *s = of->ctx; + MuxStream *ms = ms_from_ost(ost); + AVFormatContext *s = mux->fc; AVStream *st = ost->st; + int64_t fs; + uint64_t frame_num; int ret; - /* - * Audio encoders may split the packets -- #frames in != #packets out. - * But there is no reordering, so we can limit the number of output packets - * by simply dropping them here. - * Counting encoded video frames needs to be done separately because of - * reordering, see do_video_out(). - * Do not count the packet when unqueued because it has been counted when queued. - */ - if (!(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->encoding_needed) && !unqueue) { - if (ost->frame_number >= ost->max_frames) { - av_packet_unref(pkt); - return; - } - ost->frame_number++; - } - - if (!of->header_written) { - AVPacket *tmp_pkt; - /* the muxer is not initialized yet, buffer the packet */ - if (!av_fifo_can_write(ost->muxing_queue)) { - size_t cur_size = av_fifo_can_read(ost->muxing_queue); - unsigned int are_we_over_size = - (ost->muxing_queue_data_size + pkt->size) > ost->muxing_queue_data_threshold; - size_t limit = are_we_over_size ? ost->max_muxing_queue_size : SIZE_MAX; - size_t new_size = FFMIN(2 * cur_size, limit); - - if (new_size <= cur_size) { - av_log(NULL, AV_LOG_ERROR, - "Too many packets buffered for output stream %d:%d.\n", - ost->file_index, ost->st->index); - exit_program(1); - } - ret = av_fifo_grow2(ost->muxing_queue, new_size - cur_size); - if (ret < 0) - exit_program(1); - } - ret = av_packet_make_refcounted(pkt); - if (ret < 0) - exit_program(1); - tmp_pkt = av_packet_alloc(); - if (!tmp_pkt) - exit_program(1); - av_packet_move_ref(tmp_pkt, pkt); - ost->muxing_queue_data_size += tmp_pkt->size; - av_fifo_write(ost->muxing_queue, &tmp_pkt, 1); - return; + fs = filesize(s->pb); + atomic_store(&mux->last_filesize, fs); + if (fs >= mux->limit_filesize) { + ret = AVERROR_EOF; + goto fail; } - if ((st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->vsync_method == VSYNC_DROP) || - (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_sync_method < 0)) + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->vsync_method == VSYNC_DROP) pkt->pts = pkt->dts = AV_NOPTS_VALUE; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (ost->frame_rate.num && ost->is_cfr) { if (pkt->duration > 0) - av_log(NULL, AV_LOG_WARNING, "Overriding packet duration by frame rate, this should not happen\n"); + av_log(ost, AV_LOG_WARNING, "Overriding packet duration by frame rate, this should not happen\n"); pkt->duration = av_rescale_q(1, av_inv_q(ost->frame_rate), - ost->mux_timebase); + pkt->time_base); } } - av_packet_rescale_ts(pkt, ost->mux_timebase, ost->st->time_base); + av_packet_rescale_ts(pkt, pkt->time_base, ost->st->time_base); + pkt->time_base = ost->st->time_base; if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) { if (pkt->dts != AV_NOPTS_VALUE && @@ -133,25 +127,26 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, pkt->dts, pkt->pts, ost->file_index, ost->st->index); pkt->pts = - pkt->dts = pkt->pts + pkt->dts + ost->last_mux_dts + 1 - - FFMIN3(pkt->pts, pkt->dts, ost->last_mux_dts + 1) - - FFMAX3(pkt->pts, pkt->dts, ost->last_mux_dts + 1); + pkt->dts = pkt->pts + pkt->dts + ms->last_mux_dts + 1 + - FFMIN3(pkt->pts, pkt->dts, ms->last_mux_dts + 1) + - FFMAX3(pkt->pts, pkt->dts, ms->last_mux_dts + 1); } if ((st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) && pkt->dts != AV_NOPTS_VALUE && - ost->last_mux_dts != AV_NOPTS_VALUE) { - int64_t max = ost->last_mux_dts + !(s->oformat->flags & AVFMT_TS_NONSTRICT); + ms->last_mux_dts != AV_NOPTS_VALUE) { + int64_t max = ms->last_mux_dts + !(s->oformat->flags & AVFMT_TS_NONSTRICT); if (pkt->dts < max) { int loglevel = max - pkt->dts > 2 || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? AV_LOG_WARNING : AV_LOG_DEBUG; if (exit_on_error) loglevel = AV_LOG_ERROR; av_log(s, loglevel, "Non-monotonous DTS in output stream " "%d:%d; previous: %"PRId64", current: %"PRId64"; ", - ost->file_index, ost->st->index, ost->last_mux_dts, pkt->dts); + ost->file_index, ost->st->index, ms->last_mux_dts, pkt->dts); if (exit_on_error) { - av_log(NULL, AV_LOG_FATAL, "aborting.\n"); - exit_program(1); + ret = AVERROR(EINVAL); + goto fail; } + av_log(s, loglevel, "changing to %"PRId64". This may result " "in incorrect timestamps in the output file.\n", max); @@ -161,17 +156,17 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, } } } - ost->last_mux_dts = pkt->dts; + ms->last_mux_dts = pkt->dts; - ost->data_size += pkt->size; - ost->packets_written++; + ost->data_size_mux += pkt->size; + frame_num = atomic_fetch_add(&ost->packets_written, 1); pkt->stream_index = ost->index; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "muxer <- type:%s " + av_log(ost, AV_LOG_INFO, "muxer <- type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s size:%d\n", - av_get_media_type_string(ost->enc_ctx->codec_type), + av_get_media_type_string(st->codecpar->codec_type), av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ost->st->time_base), av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ost->st->time_base), av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ost->st->time_base), @@ -179,12 +174,307 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, ); } + if (ms->stats.io) + enc_stats_write(ost, &ms->stats, NULL, pkt, frame_num); + ret = av_interleaved_write_frame(s, pkt); if (ret < 0) { print_error("av_interleaved_write_frame()", ret); - main_ffmpeg_return_code = 1; - close_all_output_streams(ost, MUXER_FINISHED | ENCODER_FINISHED, ENCODER_FINISHED); + goto fail; } + + return 0; +fail: + av_packet_unref(pkt); + return ret; +} + +static int sync_queue_process(Muxer *mux, OutputStream *ost, AVPacket *pkt, int *stream_eof) +{ + OutputFile *of = &mux->of; + + if (ost->sq_idx_mux >= 0) { + int ret = sq_send(mux->sq_mux, ost->sq_idx_mux, SQPKT(pkt)); + if (ret < 0) { + if (ret == AVERROR_EOF) + *stream_eof = 1; + + return ret; + } + + while (1) { + ret = sq_receive(mux->sq_mux, -1, SQPKT(mux->sq_pkt)); + if (ret < 0) + return (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) ? 0 : ret; + + ret = write_packet(mux, of->streams[ret], + mux->sq_pkt); + if (ret < 0) + return ret; + } + } else if (pkt) + return write_packet(mux, ost, pkt); + + return 0; +} + +static void thread_set_name(OutputFile *of) +{ + char name[16]; + snprintf(name, sizeof(name), "mux%d:%s", of->index, of->format->name); + ff_thread_setname(name); +} + +static void *muxer_thread(void *arg) +{ + Muxer *mux = arg; + OutputFile *of = &mux->of; + AVPacket *pkt = NULL; + int ret = 0; + + pkt = av_packet_alloc(); + if (!pkt) { + ret = AVERROR(ENOMEM); + goto finish; + } + + thread_set_name(of); + + while (1) { + OutputStream *ost; + int stream_idx, stream_eof = 0; + + ret = tq_receive(mux->tq, &stream_idx, pkt); + if (stream_idx < 0) { + av_log(mux, AV_LOG_VERBOSE, "All streams finished\n"); + ret = 0; + break; + } + + ost = of->streams[stream_idx]; + ret = sync_queue_process(mux, ost, ret < 0 ? NULL : pkt, &stream_eof); + av_packet_unref(pkt); + if (ret == AVERROR_EOF && stream_eof) + tq_receive_finish(mux->tq, stream_idx); + else if (ret < 0) { + av_log(mux, AV_LOG_ERROR, "Error muxing a packet\n"); + break; + } + } + +finish: + av_packet_free(&pkt); + + for (unsigned int i = 0; i < mux->fc->nb_streams; i++) + tq_receive_finish(mux->tq, i); + + av_log(mux, AV_LOG_VERBOSE, "Terminating muxer thread\n"); + + return (void*)(intptr_t)ret; +} + +static int thread_submit_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) +{ + int ret = 0; + + if (!pkt || ost->finished & MUXER_FINISHED) + goto finish; + + ret = tq_send(mux->tq, ost->index, pkt); + if (ret < 0) + goto finish; + + return 0; + +finish: + if (pkt) + av_packet_unref(pkt); + + ost->finished |= MUXER_FINISHED; + tq_send_finish(mux->tq, ost->index); + return ret == AVERROR_EOF ? 0 : ret; +} + +static int queue_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) +{ + MuxStream *ms = ms_from_ost(ost); + AVPacket *tmp_pkt = NULL; + int ret; + + if (!av_fifo_can_write(ms->muxing_queue)) { + size_t cur_size = av_fifo_can_read(ms->muxing_queue); + size_t pkt_size = pkt ? pkt->size : 0; + unsigned int are_we_over_size = + (ms->muxing_queue_data_size + pkt_size) > ms->muxing_queue_data_threshold; + size_t limit = are_we_over_size ? ms->max_muxing_queue_size : SIZE_MAX; + size_t new_size = FFMIN(2 * cur_size, limit); + + if (new_size <= cur_size) { + av_log(ost, AV_LOG_ERROR, + "Too many packets buffered for output stream %d:%d.\n", + ost->file_index, ost->st->index); + return AVERROR(ENOSPC); + } + ret = av_fifo_grow2(ms->muxing_queue, new_size - cur_size); + if (ret < 0) + return ret; + } + + if (pkt) { + ret = av_packet_make_refcounted(pkt); + if (ret < 0) + return ret; + + tmp_pkt = av_packet_alloc(); + if (!tmp_pkt) + return AVERROR(ENOMEM); + + av_packet_move_ref(tmp_pkt, pkt); + ms->muxing_queue_data_size += tmp_pkt->size; + } + av_fifo_write(ms->muxing_queue, &tmp_pkt, 1); + + return 0; +} + +static int submit_packet(Muxer *mux, AVPacket *pkt, OutputStream *ost) +{ + int ret; + + if (mux->tq) { + return thread_submit_packet(mux, ost, pkt); + } else { + /* the muxer is not initialized yet, buffer the packet */ + ret = queue_packet(mux, ost, pkt); + if (ret < 0) { + if (pkt) + av_packet_unref(pkt); + return ret; + } + } + + return 0; +} + +void of_output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof) +{ + Muxer *mux = mux_from_of(of); + MuxStream *ms = ms_from_ost(ost); + const char *err_msg; + int ret = 0; + + if (!eof && pkt->dts != AV_NOPTS_VALUE) + ost->last_mux_dts = av_rescale_q(pkt->dts, pkt->time_base, AV_TIME_BASE_Q); + + /* apply the output bitstream filters */ + if (ms->bsf_ctx) { + int bsf_eof = 0; + + ret = av_bsf_send_packet(ms->bsf_ctx, eof ? NULL : pkt); + if (ret < 0) { + err_msg = "submitting a packet for bitstream filtering"; + goto fail; + } + + while (!bsf_eof) { + ret = av_bsf_receive_packet(ms->bsf_ctx, pkt); + if (ret == AVERROR(EAGAIN)) + return; + else if (ret == AVERROR_EOF) + bsf_eof = 1; + else if (ret < 0) { + err_msg = "applying bitstream filters to a packet"; + goto fail; + } + + ret = submit_packet(mux, bsf_eof ? NULL : pkt, ost); + if (ret < 0) + goto mux_fail; + } + } else { + ret = submit_packet(mux, eof ? NULL : pkt, ost); + if (ret < 0) + goto mux_fail; + } + + return; + +mux_fail: + err_msg = "submitting a packet to the muxer"; + +fail: + av_log(ost, AV_LOG_ERROR, "Error %s\n", err_msg); + if (exit_on_error) + exit_program(1); + +} + +static int thread_stop(Muxer *mux) +{ + void *ret; + + if (!mux || !mux->tq) + return 0; + + for (unsigned int i = 0; i < mux->fc->nb_streams; i++) + tq_send_finish(mux->tq, i); + + pthread_join(mux->thread, &ret); + + tq_free(&mux->tq); + + return (int)(intptr_t)ret; +} + +static void pkt_move(void *dst, void *src) +{ + av_packet_move_ref(dst, src); +} + +static int thread_start(Muxer *mux) +{ + AVFormatContext *fc = mux->fc; + ObjPool *op; + int ret; + + op = objpool_alloc_packets(); + if (!op) + return AVERROR(ENOMEM); + + mux->tq = tq_alloc(fc->nb_streams, mux->thread_queue_size, op, pkt_move); + if (!mux->tq) { + objpool_free(&op); + return AVERROR(ENOMEM); + } + + ret = pthread_create(&mux->thread, NULL, muxer_thread, (void*)mux); + if (ret) { + tq_free(&mux->tq); + return AVERROR(ret); + } + + /* flush the muxing queues */ + for (int i = 0; i < fc->nb_streams; i++) { + OutputStream *ost = mux->of.streams[i]; + MuxStream *ms = ms_from_ost(ost); + AVPacket *pkt; + + /* try to improve muxing time_base (only possible if nothing has been written yet) */ + if (!av_fifo_can_read(ms->muxing_queue)) + ost->mux_timebase = ost->st->time_base; + + while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0) { + ret = thread_submit_packet(mux, ost, pkt); + if (pkt) { + ms->muxing_queue_data_size -= pkt->size; + av_packet_free(&pkt); + } + if (ret < 0) + return ret; + } + } + + return 0; } static int print_sdp(void) @@ -196,16 +486,16 @@ static int print_sdp(void) AVFormatContext **avc; for (i = 0; i < nb_output_files; i++) { - if (!output_files[i]->header_written) + if (!mux_from_of(output_files[i])->header_written) return 0; } avc = av_malloc_array(nb_output_files, sizeof(*avc)); if (!avc) - exit_program(1); + return AVERROR(ENOMEM); for (i = 0, j = 0; i < nb_output_files; i++) { - if (!strcmp(output_files[i]->ctx->oformat->name, "rtp")) { - avc[j] = output_files[i]->ctx; + if (!strcmp(output_files[i]->format->name, "rtp")) { + avc[j] = mux_from_of(output_files[i])->fc; j++; } } @@ -221,7 +511,7 @@ static int print_sdp(void) goto fail; if (!sdp_filename) { - printf("SDP:\n%s\n", sdp); + av_log(NULL, AV_LOG_ERROR, "SDP:\n%s\n", sdp); fflush(stdout); } else { ret = avio_open2(&sdp_pb, sdp_filename, AVIO_FLAG_WRITE, &int_cb, NULL); @@ -235,34 +525,36 @@ static int print_sdp(void) av_freep(&sdp_filename); } + // SDP successfully written, allow muxer threads to start + ret = 1; + fail: av_freep(&avc); return ret; } -/* open the muxer when all the streams are initialized */ -int of_check_init(OutputFile *of) +int mux_check_init(Muxer *mux) { + OutputFile *of = &mux->of; + AVFormatContext *fc = mux->fc; int ret, i; - for (i = 0; i < of->ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; + for (i = 0; i < fc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; if (!ost->initialized) return 0; } - ret = avformat_write_header(of->ctx, &of->opts); + ret = avformat_write_header(fc, &mux->opts); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, - "Could not write header for output file #%d " - "(incorrect codec parameters ?): %s\n", - of->index, av_err2str(ret)); + av_log(mux, AV_LOG_ERROR, "Could not write header (incorrect codec " + "parameters ?): %s\n", av_err2str(ret)); return ret; } //assert_avoptions(of->opts); - of->header_written = 1; + mux->header_written = 1; - av_dump_format(of->ctx, of->index, of->ctx->url, 1); + av_dump_format(fc, of->index, fc->url, 1); nb_output_dumped++; if (sdp_filename || want_sdp) { @@ -270,62 +562,220 @@ int of_check_init(OutputFile *of) if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error writing the SDP.\n"); return ret; + } else if (ret == 1) { + /* SDP is written only after all the muxers are ready, so now we + * start ALL the threads */ + for (i = 0; i < nb_output_files; i++) { + ret = thread_start(mux_from_of(output_files[i])); + if (ret < 0) + return ret; + } } + } else { + ret = thread_start(mux_from_of(of)); + if (ret < 0) + return ret; } - /* flush the muxing queues */ - for (i = 0; i < of->ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - AVPacket *pkt; + return 0; +} - /* try to improve muxing time_base (only possible if nothing has been written yet) */ - if (!av_fifo_can_read(ost->muxing_queue)) - ost->mux_timebase = ost->st->time_base; +static int bsf_init(MuxStream *ms) +{ + OutputStream *ost = &ms->ost; + AVBSFContext *ctx = ms->bsf_ctx; + int ret; - while (av_fifo_read(ost->muxing_queue, &pkt, 1) >= 0) { - ost->muxing_queue_data_size -= pkt->size; - of_write_packet(of, pkt, ost, 1); - av_packet_free(&pkt); - } + if (!ctx) + return 0; + + ret = avcodec_parameters_copy(ctx->par_in, ost->st->codecpar); + if (ret < 0) + return ret; + + ctx->time_base_in = ost->st->time_base; + + ret = av_bsf_init(ctx); + if (ret < 0) { + av_log(ms, AV_LOG_ERROR, "Error initializing bitstream filter: %s\n", + ctx->filter->name); + return ret; } + ret = avcodec_parameters_copy(ost->st->codecpar, ctx->par_out); + if (ret < 0) + return ret; + ost->st->time_base = ctx->time_base_out; + return 0; } +int of_stream_init(OutputFile *of, OutputStream *ost) +{ + Muxer *mux = mux_from_of(of); + MuxStream *ms = ms_from_ost(ost); + int ret; + + if (ost->sq_idx_mux >= 0) + sq_set_tb(mux->sq_mux, ost->sq_idx_mux, ost->mux_timebase); + + /* initialize bitstream filters for the output stream + * needs to be done here, because the codec id for streamcopy is not + * known until now */ + ret = bsf_init(ms); + if (ret < 0) + return ret; + + ost->initialized = 1; + + return mux_check_init(mux); +} + int of_write_trailer(OutputFile *of) { + Muxer *mux = mux_from_of(of); + AVFormatContext *fc = mux->fc; int ret; - if (!of->header_written) { - av_log(NULL, AV_LOG_ERROR, - "Nothing was written into output file %d (%s), because " - "at least one of its streams received no packets.\n", - of->index, of->ctx->url); + if (!mux->tq) { + av_log(mux, AV_LOG_ERROR, + "Nothing was written into output file, because " + "at least one of its streams received no packets.\n"); return AVERROR(EINVAL); } - ret = av_write_trailer(of->ctx); + ret = thread_stop(mux); + if (ret < 0) + main_ffmpeg_return_code = ret; + + ret = av_write_trailer(fc); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error writing trailer of %s: %s\n", of->ctx->url, av_err2str(ret)); + av_log(mux, AV_LOG_ERROR, "Error writing trailer: %s\n", av_err2str(ret)); return ret; } + mux->last_filesize = filesize(fc->pb); + + if (!(of->format->flags & AVFMT_NOFILE)) { + ret = avio_closep(&fc->pb); + if (ret < 0) { + av_log(mux, AV_LOG_ERROR, "Error closing file: %s\n", av_err2str(ret)); + return ret; + } + } + return 0; } +static void ost_free(OutputStream **post) +{ + OutputStream *ost = *post; + MuxStream *ms; + + if (!ost) + return; + ms = ms_from_ost(ost); + + if (ost->logfile) { + if (fclose(ost->logfile)) + av_log(ms, AV_LOG_ERROR, + "Error closing logfile, loss of information possible: %s\n", + av_err2str(AVERROR(errno))); + ost->logfile = NULL; + } + + if (ms->muxing_queue) { + AVPacket *pkt; + while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0) + av_packet_free(&pkt); + av_fifo_freep2(&ms->muxing_queue); + } + + av_bsf_free(&ms->bsf_ctx); + + av_frame_free(&ost->filtered_frame); + av_frame_free(&ost->sq_frame); + av_frame_free(&ost->last_frame); + av_packet_free(&ost->pkt); + av_dict_free(&ost->encoder_opts); + + av_freep(&ost->kf.pts); + av_expr_free(ost->kf.pexpr); + + av_freep(&ost->avfilter); + av_freep(&ost->logfile_prefix); + av_freep(&ost->apad); + +#if FFMPEG_OPT_MAP_CHANNEL + av_freep(&ost->audio_channels_map); + ost->audio_channels_mapped = 0; +#endif + + av_dict_free(&ost->sws_dict); + av_dict_free(&ost->swr_opts); + + if (ost->enc_ctx) + av_freep(&ost->enc_ctx->stats_in); + avcodec_free_context(&ost->enc_ctx); + + for (int i = 0; i < ost->enc_stats_pre.nb_components; i++) + av_freep(&ost->enc_stats_pre.components[i].str); + av_freep(&ost->enc_stats_pre.components); + + for (int i = 0; i < ost->enc_stats_post.nb_components; i++) + av_freep(&ost->enc_stats_post.components[i].str); + av_freep(&ost->enc_stats_post.components); + + for (int i = 0; i < ms->stats.nb_components; i++) + av_freep(&ms->stats.components[i].str); + av_freep(&ms->stats.components); + + av_freep(post); +} + +static void fc_close(AVFormatContext **pfc) +{ + AVFormatContext *fc = *pfc; + + if (!fc) + return; + + if (!(fc->oformat->flags & AVFMT_NOFILE)) + avio_closep(&fc->pb); + avformat_free_context(fc); + + *pfc = NULL; +} + void of_close(OutputFile **pof) { OutputFile *of = *pof; - AVFormatContext *s; + Muxer *mux; if (!of) return; + mux = mux_from_of(of); + + thread_stop(mux); - s = of->ctx; - if (s && s->oformat && !(s->oformat->flags & AVFMT_NOFILE)) - avio_closep(&s->pb); - avformat_free_context(s); - av_dict_free(&of->opts); + sq_free(&of->sq_encode); + sq_free(&mux->sq_mux); + + for (int i = 0; i < of->nb_streams; i++) + ost_free(&of->streams[i]); + av_freep(&of->streams); + + av_dict_free(&mux->opts); + + av_packet_free(&mux->sq_pkt); + + fc_close(&mux->fc); av_freep(pof); } + +int64_t of_filesize(OutputFile *of) +{ + Muxer *mux = mux_from_of(of); + return atomic_load(&mux->last_filesize); +} diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux.h b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux.h new file mode 100644 index 0000000..c1c7abd --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux.h @@ -0,0 +1,165 @@ +/* + * Muxer internal APIs - should not be included outside of ffmpeg_mux* + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_mux.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - want_sdp made thread-local + * - EncStatsFile declaration migrated from ffmpeg_mux_init.c + * - WARN_MULTIPLE_OPT_USAGE, MATCH_PER_STREAM_OPT, MATCH_PER_TYPE_OPT, SPECIFIER_OPT_FMT declarations migrated from + * ffmpeg.h + * - ms_from_ost migrated to ffmpeg_mux.c + */ + +#ifndef FFTOOLS_FFMPEG_MUX_H +#define FFTOOLS_FFMPEG_MUX_H + +#include +#include + +#include "fftools_thread_queue.h" + +#include "libavformat/avformat.h" + +#include "libavcodec/packet.h" + +#include "libavutil/dict.h" +#include "libavutil/fifo.h" +#include "libavutil/thread.h" + +#define SPECIFIER_OPT_FMT_str "%s" +#define SPECIFIER_OPT_FMT_i "%i" +#define SPECIFIER_OPT_FMT_i64 "%"PRId64 +#define SPECIFIER_OPT_FMT_ui64 "%"PRIu64 +#define SPECIFIER_OPT_FMT_f "%f" +#define SPECIFIER_OPT_FMT_dbl "%lf" + +#define WARN_MULTIPLE_OPT_USAGE(name, type, so, st)\ +{\ + char namestr[128] = "";\ + const char *spec = so->specifier && so->specifier[0] ? so->specifier : "";\ + for (int _i = 0; opt_name_##name[_i]; _i++)\ + av_strlcatf(namestr, sizeof(namestr), "-%s%s", opt_name_##name[_i], opt_name_##name[_i+1] ? (opt_name_##name[_i+2] ? ", " : " or ") : "");\ + av_log(NULL, AV_LOG_WARNING, "Multiple %s options specified for stream %d, only the last option '-%s%s%s "SPECIFIER_OPT_FMT_##type"' will be used.\n",\ + namestr, st->index, opt_name_##name[0], spec[0] ? ":" : "", spec, so->u.type);\ +} + +#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\ +{\ + int _ret, _matches = 0;\ + SpecifierOpt *so;\ + for (int _i = 0; _i < o->nb_ ## name; _i++) {\ + char *spec = o->name[_i].specifier;\ + if ((_ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\ + outvar = o->name[_i].u.type;\ + so = &o->name[_i];\ + _matches++;\ + } else if (_ret < 0)\ + exit_program(1);\ + }\ + if (_matches > 1)\ + WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\ +} + +#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\ +{\ + int i;\ + for (i = 0; i < o->nb_ ## name; i++) {\ + char *spec = o->name[i].specifier;\ + if (!strcmp(spec, mediatype))\ + outvar = o->name[i].u.type;\ + }\ +} + +typedef struct MuxStream { + OutputStream ost; + + // name used for logging + char log_name[32]; + + /* the packets are buffered here until the muxer is ready to be initialized */ + AVFifo *muxing_queue; + + AVBSFContext *bsf_ctx; + + EncStats stats; + + int64_t max_frames; + + /* + * The size of the AVPackets' buffers in queue. + * Updated when a packet is either pushed or pulled from the queue. + */ + size_t muxing_queue_data_size; + + int max_muxing_queue_size; + + /* Threshold after which max_muxing_queue_size will be in effect */ + size_t muxing_queue_data_threshold; + + /* dts of the last packet sent to the muxer, in the stream timebase + * used for making up missing dts values */ + int64_t last_mux_dts; +} MuxStream; + +typedef struct Muxer { + OutputFile of; + + // name used for logging + char log_name[32]; + + AVFormatContext *fc; + + pthread_t thread; + ThreadQueue *tq; + + AVDictionary *opts; + + int thread_queue_size; + + /* filesize limit expressed in bytes */ + int64_t limit_filesize; + atomic_int_least64_t last_filesize; + int header_written; + + SyncQueue *sq_mux; + AVPacket *sq_pkt; +} Muxer; + +typedef struct EncStatsFile { + char *path; + AVIOContext *io; +} EncStatsFile; + +/* whether we want to print an SDP, set in of_open() */ +extern __thread int want_sdp; + +int mux_check_init(Muxer *mux); + +#endif /* FFTOOLS_FFMPEG_MUX_H */ diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux_init.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux_init.c new file mode 100644 index 0000000..522f8b8 --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_mux_init.c @@ -0,0 +1,2414 @@ +/* + * Muxer/output file setup. + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_mux_init.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - EncStatsFile declaration migrated to ffmpeg_mux.h + * - extern ms_from_ost added + * - "class" member field renamed as clazz + */ + +#include + +#include "fftools_cmdutils.h" +#include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" +#include "fftools_fopen_utf8.h" + +#include "libavformat/avformat.h" +#include "libavformat/avio.h" + +#include "libavcodec/avcodec.h" + +#include "libavfilter/avfilter.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/avutil.h" +#include "libavutil/bprint.h" +#include "libavutil/dict.h" +#include "libavutil/getenv_utf8.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/log.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" + +#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass" + +static const char *const opt_name_apad[] = {"apad", NULL}; +static const char *const opt_name_autoscale[] = {"autoscale", NULL}; +static const char *const opt_name_bits_per_raw_sample[] = {"bits_per_raw_sample", NULL}; +static const char *const opt_name_bitstream_filters[] = {"bsf", "absf", "vbsf", NULL}; +static const char *const opt_name_copy_initial_nonkeyframes[] = {"copyinkf", NULL}; +static const char *const opt_name_copy_prior_start[] = {"copypriorss", NULL}; +static const char *const opt_name_disposition[] = {"disposition", NULL}; +static const char *const opt_name_enc_time_bases[] = {"enc_time_base", NULL}; +static const char *const opt_name_enc_stats_pre[] = {"enc_stats_pre", NULL}; +static const char *const opt_name_enc_stats_post[] = {"enc_stats_post", NULL}; +static const char *const opt_name_mux_stats[] = {"mux_stats", NULL}; +static const char *const opt_name_enc_stats_pre_fmt[] = {"enc_stats_pre_fmt", NULL}; +static const char *const opt_name_enc_stats_post_fmt[] = {"enc_stats_post_fmt", NULL}; +static const char *const opt_name_mux_stats_fmt[] = {"mux_stats_fmt", NULL}; +static const char *const opt_name_filters[] = {"filter", "af", "vf", NULL}; +static const char *const opt_name_filter_scripts[] = {"filter_script", NULL}; +static const char *const opt_name_fix_sub_duration_heartbeat[] = {"fix_sub_duration_heartbeat", NULL}; +static const char *const opt_name_fps_mode[] = {"fps_mode", NULL}; +static const char *const opt_name_force_fps[] = {"force_fps", NULL}; +static const char *const opt_name_forced_key_frames[] = {"forced_key_frames", NULL}; +static const char *const opt_name_frame_aspect_ratios[] = {"aspect", NULL}; +static const char *const opt_name_intra_matrices[] = {"intra_matrix", NULL}; +static const char *const opt_name_inter_matrices[] = {"inter_matrix", NULL}; +static const char *const opt_name_chroma_intra_matrices[] = {"chroma_intra_matrix", NULL}; +static const char *const opt_name_max_frame_rates[] = {"fpsmax", NULL}; +static const char *const opt_name_max_frames[] = {"frames", "aframes", "vframes", "dframes", NULL}; +static const char *const opt_name_max_muxing_queue_size[] = {"max_muxing_queue_size", NULL}; +static const char *const opt_name_muxing_queue_data_threshold[] = {"muxing_queue_data_threshold", NULL}; +static const char *const opt_name_pass[] = {"pass", NULL}; +static const char *const opt_name_passlogfiles[] = {"passlogfile", NULL}; +static const char *const opt_name_presets[] = {"pre", "apre", "vpre", "spre", NULL}; +static const char *const opt_name_qscale[] = {"q", "qscale", NULL}; +static const char *const opt_name_rc_overrides[] = {"rc_override", NULL}; +static const char *const opt_name_time_bases[] = {"time_base", NULL}; +static const char *const opt_name_audio_channels[] = {"ac", NULL}; +static const char *const opt_name_audio_ch_layouts[] = {"channel_layout", "ch_layout", NULL}; +static const char *const opt_name_audio_sample_rate[] = {"ar", NULL}; +static const char *const opt_name_frame_sizes[] = {"s", NULL}; +static const char *const opt_name_frame_pix_fmts[] = {"pix_fmt", NULL}; +static const char *const opt_name_sample_fmts[] = {"sample_fmt", NULL}; + +extern MuxStream *ms_from_ost(OutputStream *ost); + +static int check_opt_bitexact(void *ctx, const AVDictionary *opts, + const char *opt_name, int flag) +{ + const AVDictionaryEntry *e = av_dict_get(opts, opt_name, NULL, 0); + + if (e) { + const AVOption *o = av_opt_find(ctx, opt_name, NULL, 0, 0); + int val = 0; + if (!o) + return 0; + av_opt_eval_flags(ctx, o, e->value, &val); + return !!(val & flag); + } + return 0; +} + +static int choose_encoder(const OptionsContext *o, AVFormatContext *s, + OutputStream *ost, const AVCodec **enc) +{ + enum AVMediaType type = ost->st->codecpar->codec_type; + char *codec_name = NULL; + + *enc = NULL; + + if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) { + MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st); + if (!codec_name) { + ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->url, + NULL, ost->st->codecpar->codec_type); + *enc = avcodec_find_encoder(ost->st->codecpar->codec_id); + if (!*enc) { + av_log(ost, AV_LOG_FATAL, "Automatic encoder selection failed " + "Default encoder for format %s (codec %s) is " + "probably disabled. Please choose an encoder manually.\n", + s->oformat->name, avcodec_get_name(ost->st->codecpar->codec_id)); + return AVERROR_ENCODER_NOT_FOUND; + } + } else if (strcmp(codec_name, "copy")) { + *enc = find_codec_or_die(ost, codec_name, ost->st->codecpar->codec_type, 1); + ost->st->codecpar->codec_id = (*enc)->id; + } + } + + return 0; +} + +static char *get_line(AVIOContext *s, AVBPrint *bprint) +{ + char c; + + while ((c = avio_r8(s)) && c != '\n') + av_bprint_chars(bprint, c, 1); + + if (!av_bprint_is_complete(bprint)) + report_and_exit(AVERROR(ENOMEM)); + return bprint->str; +} + +static int get_preset_file_2(const char *preset_name, const char *codec_name, AVIOContext **s) +{ + int i, ret = -1; + char filename[1000]; + char *env_avconv_datadir = getenv_utf8("AVCONV_DATADIR"); + char *env_home = getenv_utf8("HOME"); + const char *base[3] = { env_avconv_datadir, + env_home, + AVCONV_DATADIR, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(base) && ret < 0; i++) { + if (!base[i]) + continue; + if (codec_name) { + snprintf(filename, sizeof(filename), "%s%s/%s-%s.avpreset", base[i], + i != 1 ? "" : "/.avconv", codec_name, preset_name); + ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); + } + if (ret < 0) { + snprintf(filename, sizeof(filename), "%s%s/%s.avpreset", base[i], + i != 1 ? "" : "/.avconv", preset_name); + ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); + } + } + freeenv_utf8(env_home); + freeenv_utf8(env_avconv_datadir); + return ret; +} + +__thread EncStatsFile *enc_stats_files; +__thread int nb_enc_stats_files; + +static int enc_stats_get_file(AVIOContext **io, const char *path) +{ + EncStatsFile *esf; + int ret; + + for (int i = 0; i < nb_enc_stats_files; i++) + if (!strcmp(path, enc_stats_files[i].path)) { + *io = enc_stats_files[i].io; + return 0; + } + + GROW_ARRAY(enc_stats_files, nb_enc_stats_files); + + esf = &enc_stats_files[nb_enc_stats_files - 1]; + + ret = avio_open2(&esf->io, path, AVIO_FLAG_WRITE, &int_cb, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening stats file '%s': %s\n", + path, av_err2str(ret)); + return ret; + } + + esf->path = av_strdup(path); + if (!esf->path) + return AVERROR(ENOMEM); + + *io = esf->io; + + return 0; +} + +void of_enc_stats_close(void) +{ + for (int i = 0; i < nb_enc_stats_files; i++) { + av_freep(&enc_stats_files[i].path); + avio_closep(&enc_stats_files[i].io); + } + av_freep(&enc_stats_files); + nb_enc_stats_files = 0; +} + +static int unescape(char **pdst, size_t *dst_len, + const char **pstr, char delim) +{ + const char *str = *pstr; + char *dst; + size_t len, idx; + + *pdst = NULL; + + len = strlen(str); + if (!len) + return 0; + + dst = av_malloc(len + 1); + if (!dst) + return AVERROR(ENOMEM); + + for (idx = 0; *str; idx++, str++) { + if (str[0] == '\\' && str[1]) + str++; + else if (*str == delim) + break; + + dst[idx] = *str; + } + if (!idx) { + av_freep(&dst); + return 0; + } + + dst[idx] = 0; + + *pdst = dst; + *dst_len = idx; + *pstr = str; + + return 0; +} + +static int enc_stats_init(OutputStream *ost, EncStats *es, int pre, + const char *path, const char *fmt_spec) +{ + static const struct { + enum EncStatsType type; + const char *str; + int pre_only:1; + int post_only:1; + int need_input_data:1; + } fmt_specs[] = { + { ENC_STATS_FILE_IDX, "fidx" }, + { ENC_STATS_STREAM_IDX, "sidx" }, + { ENC_STATS_FRAME_NUM, "n" }, + { ENC_STATS_FRAME_NUM_IN, "ni", 0, 0, 1 }, + { ENC_STATS_TIMEBASE, "tb" }, + { ENC_STATS_TIMEBASE_IN, "tbi", 0, 0, 1 }, + { ENC_STATS_PTS, "pts" }, + { ENC_STATS_PTS_TIME, "t" }, + { ENC_STATS_PTS_IN, "ptsi", 0, 0, 1 }, + { ENC_STATS_PTS_TIME_IN, "ti", 0, 0, 1 }, + { ENC_STATS_DTS, "dts", 0, 1 }, + { ENC_STATS_DTS_TIME, "dt", 0, 1 }, + { ENC_STATS_SAMPLE_NUM, "sn", 1 }, + { ENC_STATS_NB_SAMPLES, "samp", 1 }, + { ENC_STATS_PKT_SIZE, "size", 0, 1 }, + { ENC_STATS_BITRATE, "br", 0, 1 }, + { ENC_STATS_AVG_BITRATE, "abr", 0, 1 }, + }; + const char *next = fmt_spec; + + int ret; + + while (*next) { + EncStatsComponent *c; + char *val; + size_t val_len; + + // get the sequence up until next opening brace + ret = unescape(&val, &val_len, &next, '{'); + if (ret < 0) + return ret; + + if (val) { + GROW_ARRAY(es->components, es->nb_components); + + c = &es->components[es->nb_components - 1]; + c->type = ENC_STATS_LITERAL; + c->str = val; + c->str_len = val_len; + } + + if (!*next) + break; + next++; + + // get the part inside braces + ret = unescape(&val, &val_len, &next, '}'); + if (ret < 0) + return ret; + + if (!val) { + av_log(NULL, AV_LOG_ERROR, + "Empty formatting directive in: %s\n", fmt_spec); + return AVERROR(EINVAL); + } + + if (!*next) { + av_log(NULL, AV_LOG_ERROR, + "Missing closing brace in: %s\n", fmt_spec); + ret = AVERROR(EINVAL); + goto fail; + } + next++; + + GROW_ARRAY(es->components, es->nb_components); + c = &es->components[es->nb_components - 1]; + + for (size_t i = 0; i < FF_ARRAY_ELEMS(fmt_specs); i++) { + if (!strcmp(val, fmt_specs[i].str)) { + if ((pre && fmt_specs[i].post_only) || (!pre && fmt_specs[i].pre_only)) { + av_log(NULL, AV_LOG_ERROR, + "Format directive '%s' may only be used %s-encoding\n", + val, pre ? "post" : "pre"); + ret = AVERROR(EINVAL); + goto fail; + } + + c->type = fmt_specs[i].type; + + if (fmt_specs[i].need_input_data) { + if (ost->ist) + ost->ist->want_frame_data = 1; + else { + av_log(ost, AV_LOG_WARNING, + "Format directive '%s' is unavailable, because " + "this output stream has no associated input stream\n", + val); + } + } + + break; + } + } + + if (!c->type) { + av_log(NULL, AV_LOG_ERROR, "Invalid format directive: %s\n", val); + ret = AVERROR(EINVAL); + goto fail; + } + +fail: + av_freep(&val); + if (ret < 0) + return ret; + } + + ret = enc_stats_get_file(&es->io, path); + if (ret < 0) + return ret; + + return 0; +} + +static const char *output_stream_item_name(void *obj) +{ + const MuxStream *ms = obj; + + return ms->log_name; +} + +static const AVClass output_stream_class = { + .class_name = "OutputStream", + .version = LIBAVUTIL_VERSION_INT, + .item_name = output_stream_item_name, + .category = AV_CLASS_CATEGORY_MUXER, +}; + +static MuxStream *mux_stream_alloc(Muxer *mux, enum AVMediaType type) +{ + const char *type_str = av_get_media_type_string(type); + MuxStream *ms = allocate_array_elem(&mux->of.streams, sizeof(*ms), + &mux->of.nb_streams); + + ms->ost.file_index = mux->of.index; + ms->ost.index = mux->of.nb_streams - 1; + + ms->ost.clazz = &output_stream_class; + + snprintf(ms->log_name, sizeof(ms->log_name), "%cost#%d:%d", + type_str ? *type_str : '?', mux->of.index, ms->ost.index); + + return ms; +} + +static OutputStream *new_output_stream(Muxer *mux, const OptionsContext *o, + enum AVMediaType type, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + MuxStream *ms; + OutputStream *ost; + const AVCodec *enc; + AVStream *st = avformat_new_stream(oc, NULL); + int ret = 0; + const char *bsfs = NULL, *time_base = NULL; + char *next, *codec_tag = NULL; + double qscale = -1; + int i; + + if (!st) + report_and_exit(AVERROR(ENOMEM)); + + if (oc->nb_streams - 1 < o->nb_streamid_map) + st->id = o->streamid_map[oc->nb_streams - 1]; + + ms = mux_stream_alloc(mux, type); + ost = &ms->ost; + + ms->muxing_queue = av_fifo_alloc2(8, sizeof(AVPacket*), 0); + if (!ms->muxing_queue) + report_and_exit(AVERROR(ENOMEM)); + ms->last_mux_dts = AV_NOPTS_VALUE; + + ost->st = st; + ost->ist = ist; + ost->kf.ref_pts = AV_NOPTS_VALUE; + st->codecpar->codec_type = type; + + ret = choose_encoder(o, oc, ost, &enc); + if (ret < 0) { + av_log(ost, AV_LOG_FATAL, "Error selecting an encoder\n"); + exit_program(1); + } + + if (enc) { + ost->enc_ctx = avcodec_alloc_context3(enc); + if (!ost->enc_ctx) + report_and_exit(AVERROR(ENOMEM)); + + av_strlcat(ms->log_name, "/", sizeof(ms->log_name)); + av_strlcat(ms->log_name, enc->name, sizeof(ms->log_name)); + } else { + av_strlcat(ms->log_name, "/copy", sizeof(ms->log_name)); + } + + ost->filtered_frame = av_frame_alloc(); + if (!ost->filtered_frame) + report_and_exit(AVERROR(ENOMEM)); + + ost->pkt = av_packet_alloc(); + if (!ost->pkt) + report_and_exit(AVERROR(ENOMEM)); + + if (ost->enc_ctx) { + AVCodecContext *enc = ost->enc_ctx; + AVIOContext *s = NULL; + char *buf = NULL, *arg = NULL, *preset = NULL; + const char *enc_stats_pre = NULL, *enc_stats_post = NULL, *mux_stats = NULL; + + ost->encoder_opts = filter_codec_opts(o->g->codec_opts, enc->codec_id, + oc, st, enc->codec); + + MATCH_PER_STREAM_OPT(presets, str, preset, oc, st); + ost->autoscale = 1; + MATCH_PER_STREAM_OPT(autoscale, i, ost->autoscale, oc, st); + if (preset && (!(ret = get_preset_file_2(preset, enc->codec->name, &s)))) { + AVBPrint bprint; + av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); + do { + av_bprint_clear(&bprint); + buf = get_line(s, &bprint); + if (!buf[0] || buf[0] == '#') + continue; + if (!(arg = strchr(buf, '='))) { + av_log(ost, AV_LOG_FATAL, "Invalid line found in the preset file.\n"); + exit_program(1); + } + *arg++ = 0; + av_dict_set(&ost->encoder_opts, buf, arg, AV_DICT_DONT_OVERWRITE); + } while (!s->eof_reached); + av_bprint_finalize(&bprint, NULL); + avio_closep(&s); + } + if (ret) { + av_log(ost, AV_LOG_FATAL, + "Preset %s specified, but could not be opened.\n", preset); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(enc_stats_pre, str, enc_stats_pre, oc, st); + if (enc_stats_pre && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(enc_stats_pre_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ost->enc_stats_pre, 1, enc_stats_pre, format); + if (ret < 0) + exit_program(1); + } + + MATCH_PER_STREAM_OPT(enc_stats_post, str, enc_stats_post, oc, st); + if (enc_stats_post && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(enc_stats_post_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ost->enc_stats_post, 0, enc_stats_post, format); + if (ret < 0) + exit_program(1); + } + + MATCH_PER_STREAM_OPT(mux_stats, str, mux_stats, oc, st); + if (mux_stats && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(mux_stats_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ms->stats, 0, mux_stats, format); + if (ret < 0) + exit_program(1); + } + } else { + ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL); + } + + + if (o->bitexact) { + ost->bitexact = 1; + } else if (ost->enc_ctx) { + ost->bitexact = check_opt_bitexact(ost->enc_ctx, ost->encoder_opts, "flags", + AV_CODEC_FLAG_BITEXACT); + } + + MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st); + if (time_base) { + AVRational q; + if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || + q.num <= 0 || q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); + exit_program(1); + } + st->time_base = q; + } + + MATCH_PER_STREAM_OPT(enc_time_bases, str, time_base, oc, st); + if (time_base) { + AVRational q; + if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || + q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); + exit_program(1); + } + ost->enc_timebase = q; + } + + ms->max_frames = INT64_MAX; + MATCH_PER_STREAM_OPT(max_frames, i64, ms->max_frames, oc, st); + for (i = 0; inb_max_frames; i++) { + char *p = o->max_frames[i].specifier; + if (!*p && type != AVMEDIA_TYPE_VIDEO) { + av_log(ost, AV_LOG_WARNING, "Applying unspecific -frames to non video streams, maybe you meant -vframes ?\n"); + break; + } + } + + ost->copy_prior_start = -1; + MATCH_PER_STREAM_OPT(copy_prior_start, i, ost->copy_prior_start, oc ,st); + + MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st); + if (bsfs && *bsfs) { + ret = av_bsf_list_parse_str(bsfs, &ms->bsf_ctx); + if (ret < 0) { + av_log(ost, AV_LOG_ERROR, "Error parsing bitstream filter sequence '%s': %s\n", bsfs, av_err2str(ret)); + exit_program(1); + } + } + + MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st); + if (codec_tag) { + uint32_t tag = strtol(codec_tag, &next, 0); + if (*next) + tag = AV_RL32(codec_tag); + ost->st->codecpar->codec_tag = tag; + if (ost->enc_ctx) + ost->enc_ctx->codec_tag = tag; + } + + MATCH_PER_STREAM_OPT(qscale, dbl, qscale, oc, st); + if (ost->enc_ctx && qscale >= 0) { + ost->enc_ctx->flags |= AV_CODEC_FLAG_QSCALE; + ost->enc_ctx->global_quality = FF_QP2LAMBDA * qscale; + } + + ms->max_muxing_queue_size = 128; + MATCH_PER_STREAM_OPT(max_muxing_queue_size, i, ms->max_muxing_queue_size, oc, st); + + ms->muxing_queue_data_threshold = 50*1024*1024; + MATCH_PER_STREAM_OPT(muxing_queue_data_threshold, i, ms->muxing_queue_data_threshold, oc, st); + + MATCH_PER_STREAM_OPT(bits_per_raw_sample, i, ost->bits_per_raw_sample, + oc, st); + + MATCH_PER_STREAM_OPT(fix_sub_duration_heartbeat, i, ost->fix_sub_duration_heartbeat, + oc, st); + + if (oc->oformat->flags & AVFMT_GLOBALHEADER && ost->enc_ctx) + ost->enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + av_dict_copy(&ost->sws_dict, o->g->sws_dict, 0); + + av_dict_copy(&ost->swr_opts, o->g->swr_opts, 0); + if (ost->enc_ctx && av_get_exact_bits_per_sample(ost->enc_ctx->codec_id) == 24) + av_dict_set(&ost->swr_opts, "output_sample_bits", "24", 0); + + if (ost->ist) { + ost->ist->discard = 0; + ost->ist->st->discard = ost->ist->user_set_discard; + } + ost->last_mux_dts = AV_NOPTS_VALUE; + ost->last_filter_pts = AV_NOPTS_VALUE; + + MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, + ost->copy_initial_nonkeyframes, oc, st); + + return ost; +} + +static char *get_ost_filters(const OptionsContext *o, AVFormatContext *oc, + OutputStream *ost) +{ + AVStream *st = ost->st; + + if (ost->filters_script && ost->filters) { + av_log(ost, AV_LOG_ERROR, "Both -filter and -filter_script set\n"); + exit_program(1); + } + + if (ost->filters_script) + return file_read(ost->filters_script); + else if (ost->filters) + return av_strdup(ost->filters); + + return av_strdup(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? + "null" : "anull"); +} + +static void check_streamcopy_filters(const OptionsContext *o, AVFormatContext *oc, + OutputStream *ost, enum AVMediaType type) +{ + if (ost->filters_script || ost->filters) { + av_log(ost, AV_LOG_ERROR, + "%s '%s' was defined, but codec copy was selected.\n" + "Filtering and streamcopy cannot be used together.\n", + ost->filters ? "Filtergraph" : "Filtergraph script", + ost->filters ? ost->filters : ost->filters_script); + exit_program(1); + } +} + +static void parse_matrix_coeffs(void *logctx, uint16_t *dest, const char *str) +{ + int i; + const char *p = str; + for (i = 0;; i++) { + dest[i] = atoi(p); + if (i == 63) + break; + p = strchr(p, ','); + if (!p) { + av_log(logctx, AV_LOG_FATAL, + "Syntax error in matrix \"%s\" at coeff %d\n", str, i); + exit_program(1); + } + p++; + } +} + +static OutputStream *new_video_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + AVStream *st; + OutputStream *ost; + char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_VIDEO, ist); + st = ost->st; + + MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st); + if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(max_frame_rates, str, max_frame_rate, oc, st); + if (max_frame_rate && av_parse_video_rate(&ost->max_frame_rate, max_frame_rate) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid maximum framerate value: %s\n", max_frame_rate); + exit_program(1); + } + + if (frame_rate && max_frame_rate) { + av_log(ost, AV_LOG_ERROR, "Only one of -fpsmax and -r can be set for a stream.\n"); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st); + if (frame_aspect_ratio) { + AVRational q; + if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 || + q.num <= 0 || q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid aspect ratio: %s\n", frame_aspect_ratio); + exit_program(1); + } + ost->frame_aspect_ratio = q; + } + + MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); + + if (ost->enc_ctx) { + AVCodecContext *video_enc = ost->enc_ctx; + const char *p = NULL, *fps_mode = NULL; + char *frame_size = NULL; + char *frame_pix_fmt = NULL; + char *intra_matrix = NULL, *inter_matrix = NULL; + char *chroma_intra_matrix = NULL; + int do_pass = 0; + int i; + + MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); + if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st); + if (frame_pix_fmt && *frame_pix_fmt == '+') { + ost->keep_pix_fmt = 1; + if (!*++frame_pix_fmt) + frame_pix_fmt = NULL; + } + if (frame_pix_fmt && (video_enc->pix_fmt = av_get_pix_fmt(frame_pix_fmt)) == AV_PIX_FMT_NONE) { + av_log(ost, AV_LOG_FATAL, "Unknown pixel format requested: %s.\n", frame_pix_fmt); + exit_program(1); + } + st->sample_aspect_ratio = video_enc->sample_aspect_ratio; + + MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st); + if (intra_matrix) { + if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64))) + report_and_exit(AVERROR(ENOMEM)); + parse_matrix_coeffs(ost, video_enc->intra_matrix, intra_matrix); + } + MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st); + if (chroma_intra_matrix) { + uint16_t *p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64); + if (!p) + report_and_exit(AVERROR(ENOMEM)); + video_enc->chroma_intra_matrix = p; + parse_matrix_coeffs(ost, p, chroma_intra_matrix); + } + MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st); + if (inter_matrix) { + if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64))) + report_and_exit(AVERROR(ENOMEM)); + parse_matrix_coeffs(ost, video_enc->inter_matrix, inter_matrix); + } + + MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st); + for (i = 0; p; i++) { + int start, end, q; + int e = sscanf(p, "%d,%d,%d", &start, &end, &q); + if (e != 3) { + av_log(ost, AV_LOG_FATAL, "error parsing rc_override\n"); + exit_program(1); + } + video_enc->rc_override = + av_realloc_array(video_enc->rc_override, + i + 1, sizeof(RcOverride)); + if (!video_enc->rc_override) { + av_log(ost, AV_LOG_FATAL, "Could not (re)allocate memory for rc_override.\n"); + exit_program(1); + } + video_enc->rc_override[i].start_frame = start; + video_enc->rc_override[i].end_frame = end; + if (q > 0) { + video_enc->rc_override[i].qscale = q; + video_enc->rc_override[i].quality_factor = 1.0; + } + else { + video_enc->rc_override[i].qscale = 0; + video_enc->rc_override[i].quality_factor = -q/100.0; + } + p = strchr(p, '/'); + if (p) p++; + } + video_enc->rc_override_count = i; + +#if FFMPEG_OPT_PSNR + if (do_psnr) { + av_log(ost, AV_LOG_WARNING, "The -psnr option is deprecated, use -flags +psnr\n"); + video_enc->flags|= AV_CODEC_FLAG_PSNR; + } +#endif + + /* two pass mode */ + MATCH_PER_STREAM_OPT(pass, i, do_pass, oc, st); + if (do_pass) { + if (do_pass & 1) { + video_enc->flags |= AV_CODEC_FLAG_PASS1; + av_dict_set(&ost->encoder_opts, "flags", "+pass1", AV_DICT_APPEND); + } + if (do_pass & 2) { + video_enc->flags |= AV_CODEC_FLAG_PASS2; + av_dict_set(&ost->encoder_opts, "flags", "+pass2", AV_DICT_APPEND); + } + } + + MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st); + if (ost->logfile_prefix && + !(ost->logfile_prefix = av_strdup(ost->logfile_prefix))) + report_and_exit(AVERROR(ENOMEM)); + + if (do_pass) { + int ost_idx = -1; + char logfilename[1024]; + FILE *f; + + /* compute this stream's global index */ + for (int i = 0; i <= ost->file_index; i++) + ost_idx += output_files[i]->nb_streams; + + snprintf(logfilename, sizeof(logfilename), "%s-%d.log", + ost->logfile_prefix ? ost->logfile_prefix : + DEFAULT_PASS_LOGFILENAME_PREFIX, + ost_idx); + if (!strcmp(ost->enc_ctx->codec->name, "libx264")) { + av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE); + } else { + if (video_enc->flags & AV_CODEC_FLAG_PASS2) { + char *logbuffer = file_read(logfilename); + + if (!logbuffer) { + av_log(ost, AV_LOG_FATAL, "Error reading log file '%s' for pass-2 encoding\n", + logfilename); + exit_program(1); + } + video_enc->stats_in = logbuffer; + } + if (video_enc->flags & AV_CODEC_FLAG_PASS1) { + f = fopen_utf8(logfilename, "wb"); + if (!f) { + av_log(ost, AV_LOG_FATAL, + "Cannot write log file '%s' for pass-1 encoding: %s\n", + logfilename, strerror(errno)); + exit_program(1); + } + ost->logfile = f; + } + } + } + + MATCH_PER_STREAM_OPT(force_fps, i, ost->force_fps, oc, st); + + ost->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st); + + ost->vsync_method = video_sync_method; + MATCH_PER_STREAM_OPT(fps_mode, str, fps_mode, oc, st); + if (fps_mode) + parse_and_set_vsync(fps_mode, &ost->vsync_method, ost->file_index, ost->index, 0); + + if ((ost->frame_rate.num || ost->max_frame_rate.num) && + !(ost->vsync_method == VSYNC_AUTO || + ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR)) { + av_log(ost, AV_LOG_FATAL, "One of -r/-fpsmax was specified " + "together a non-CFR -vsync/-fps_mode. This is contradictory.\n"); + exit_program(1); + } + + if (ost->vsync_method == VSYNC_AUTO) { + if (ost->frame_rate.num || ost->max_frame_rate.num) { + ost->vsync_method = VSYNC_CFR; + } else if (!strcmp(oc->oformat->name, "avi")) { + ost->vsync_method = VSYNC_VFR; + } else { + ost->vsync_method = (oc->oformat->flags & AVFMT_VARIABLE_FPS) ? + ((oc->oformat->flags & AVFMT_NOTIMESTAMPS) ? + VSYNC_PASSTHROUGH : VSYNC_VFR) : + VSYNC_CFR; + } + + if (ost->ist && ost->vsync_method == VSYNC_CFR) { + const InputFile *ifile = input_files[ost->ist->file_index]; + + if (ifile->nb_streams == 1 && ifile->input_ts_offset == 0) + ost->vsync_method = VSYNC_VSCFR; + } + + if (ost->vsync_method == VSYNC_CFR && copy_ts) { + ost->vsync_method = VSYNC_VSCFR; + } + } + ost->is_cfr = (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR); + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit_program(1); + + ost->last_frame = av_frame_alloc(); + if (!ost->last_frame) + report_and_exit(AVERROR(ENOMEM)); + } else + check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_VIDEO); + + return ost; +} + +static OutputStream *new_audio_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + AVStream *st; + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_AUDIO, ist); + st = ost->st; + + + MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); + + if (ost->enc_ctx) { + AVCodecContext *audio_enc = ost->enc_ctx; + int channels = 0; + char *layout = NULL; + char *sample_fmt = NULL; + + MATCH_PER_STREAM_OPT(audio_channels, i, channels, oc, st); + if (channels) { + audio_enc->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC; + audio_enc->ch_layout.nb_channels = channels; + } + + MATCH_PER_STREAM_OPT(audio_ch_layouts, str, layout, oc, st); + if (layout) { + if (av_channel_layout_from_string(&audio_enc->ch_layout, layout) < 0) { +#if FF_API_OLD_CHANNEL_LAYOUT + uint64_t mask; + AV_NOWARN_DEPRECATED({ + mask = av_get_channel_layout(layout); + }) + if (!mask) { +#endif + av_log(ost, AV_LOG_FATAL, "Unknown channel layout: %s\n", layout); + exit_program(1); +#if FF_API_OLD_CHANNEL_LAYOUT + } + av_log(ost, AV_LOG_WARNING, "Channel layout '%s' uses a deprecated syntax.\n", + layout); + av_channel_layout_from_mask(&audio_enc->ch_layout, mask); +#endif + } + } + + MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st); + if (sample_fmt && + (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) { + av_log(ost, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st); + + MATCH_PER_STREAM_OPT(apad, str, ost->apad, oc, st); + ost->apad = av_strdup(ost->apad); + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit_program(1); + +#if FFMPEG_OPT_MAP_CHANNEL + /* check for channel mapping for this audio stream */ + for (int n = 0; n < o->nb_audio_channel_maps; n++) { + AudioChannelMap *map = &o->audio_channel_maps[n]; + if ((map->ofile_idx == -1 || ost->file_index == map->ofile_idx) && + (map->ostream_idx == -1 || ost->st->index == map->ostream_idx)) { + InputStream *ist; + + if (map->channel_idx == -1) { + ist = NULL; + } else if (!ost->ist) { + av_log(ost, AV_LOG_FATAL, "Cannot determine input stream for channel mapping %d.%d\n", + ost->file_index, ost->st->index); + continue; + } else { + ist = ost->ist; + } + + if (!ist || (ist->file_index == map->file_idx && ist->st->index == map->stream_idx)) { + if (av_reallocp_array(&ost->audio_channels_map, + ost->audio_channels_mapped + 1, + sizeof(*ost->audio_channels_map) + ) < 0 ) + report_and_exit(AVERROR(ENOMEM)); + + ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx; + } + } + } +#endif + } else + check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_AUDIO); + + return ost; +} + +static OutputStream *new_data_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_DATA, ist); + if (ost->enc_ctx) { + av_log(ost, AV_LOG_FATAL, "Data stream encoding not supported yet (only streamcopy)\n"); + exit_program(1); + } + + return ost; +} + +static OutputStream *new_unknown_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_UNKNOWN, ist); + if (ost->enc_ctx) { + av_log(ost, AV_LOG_FATAL, "Unknown stream encoding not supported yet (only streamcopy)\n"); + exit_program(1); + } + + return ost; +} + +static OutputStream *new_attachment_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost = new_output_stream(mux, o, AVMEDIA_TYPE_ATTACHMENT, ist); + ost->finished = 1; + return ost; +} + +static OutputStream *new_subtitle_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVStream *st; + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_SUBTITLE, ist); + st = ost->st; + + if (ost->enc_ctx) { + AVCodecContext *subtitle_enc = ost->enc_ctx; + char *frame_size = NULL; + + MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, mux->fc, st); + if (frame_size && av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); + exit_program(1); + } + } + + return ost; +} + +static void init_output_filter(OutputFilter *ofilter, const OptionsContext *o, + Muxer *mux) +{ + OutputStream *ost; + + switch (ofilter->type) { + case AVMEDIA_TYPE_VIDEO: ost = new_video_stream(mux, o, NULL); break; + case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream(mux, o, NULL); break; + default: + av_log(mux, AV_LOG_FATAL, "Only video and audio filters are supported " + "currently.\n"); + exit_program(1); + } + + ost->filter = ofilter; + + ofilter->ost = ost; + ofilter->format = -1; + + if (!ost->enc_ctx) { + av_log(ost, AV_LOG_ERROR, "Streamcopy requested for output stream fed " + "from a complex filtergraph. Filtering and streamcopy " + "cannot be used together.\n"); + exit_program(1); + } + + if (ost->avfilter && (ost->filters || ost->filters_script)) { + const char *opt = ost->filters ? "-vf/-af/-filter" : "-filter_script"; + av_log(ost, AV_LOG_ERROR, + "%s '%s' was specified through the %s option " + "for output stream %d:%d, which is fed from a complex filtergraph.\n" + "%s and -filter_complex cannot be used together for the same stream.\n", + ost->filters ? "Filtergraph" : "Filtergraph script", + ost->filters ? ost->filters : ost->filters_script, + opt, ost->file_index, ost->index, opt); + exit_program(1); + } + + avfilter_inout_free(&ofilter->out_tmp); +} + +static void map_auto_video(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + InputStream *best_ist = NULL; + int best_score = 0; + int qcr; + + /* video: highest resolution */ + if (av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_VIDEO) == AV_CODEC_ID_NONE) + return; + + qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0); + for (int j = 0; j < nb_input_files; j++) { + InputFile *ifile = input_files[j]; + InputStream *file_best_ist = NULL; + int file_best_score = 0; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int score; + + if (ist->user_set_discard == AVDISCARD_ALL || + ist->st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + + score = ist->st->codecpar->width * ist->st->codecpar->height + + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) + + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); + if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + score = 1; + + if (score > file_best_score) { + if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + continue; + file_best_score = score; + file_best_ist = ist; + } + } + if (file_best_ist) { + if((qcr == MKTAG('A', 'P', 'I', 'C')) || + !(file_best_ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + file_best_score -= 5000000*!!(file_best_ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (file_best_score > best_score) { + best_score = file_best_score; + best_ist = file_best_ist; + } + } + } + if (best_ist) + new_video_stream(mux, o, best_ist); +} + +static void map_auto_audio(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + InputStream *best_ist = NULL; + int best_score = 0; + + /* audio: most channels */ + if (av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_AUDIO) == AV_CODEC_ID_NONE) + return; + + for (int j = 0; j < nb_input_files; j++) { + InputFile *ifile = input_files[j]; + InputStream *file_best_ist = NULL; + int file_best_score = 0; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int score; + + if (ist->user_set_discard == AVDISCARD_ALL || + ist->st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) + continue; + + score = ist->st->codecpar->ch_layout.nb_channels + + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) + + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (score > file_best_score) { + file_best_score = score; + file_best_ist = ist; + } + } + if (file_best_ist) { + file_best_score -= 5000000*!!(file_best_ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (file_best_score > best_score) { + best_score = file_best_score; + best_ist = file_best_ist; + } + } + } + if (best_ist) + new_audio_stream(mux, o, best_ist); +} + +static void map_auto_subtitle(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + char *subtitle_codec_name = NULL; + + /* subtitles: pick first */ + MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s"); + if (!avcodec_find_encoder(oc->oformat->subtitle_codec) && !subtitle_codec_name) + return; + + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { + AVCodecDescriptor const *input_descriptor = + avcodec_descriptor_get(ist->st->codecpar->codec_id); + AVCodecDescriptor const *output_descriptor = NULL; + AVCodec const *output_codec = + avcodec_find_encoder(oc->oformat->subtitle_codec); + int input_props = 0, output_props = 0; + if (ist->user_set_discard == AVDISCARD_ALL) + continue; + if (output_codec) + output_descriptor = avcodec_descriptor_get(output_codec->id); + if (input_descriptor) + input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (output_descriptor) + output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (subtitle_codec_name || + input_props & output_props || + // Map dvb teletext which has neither property to any output subtitle encoder + (input_descriptor && output_descriptor && + (!input_descriptor->props || + !output_descriptor->props))) { + new_subtitle_stream(mux, o, ist); + break; + } + } +} + +static void map_auto_data(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + /* Data only if codec id match */ + enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_DATA); + + if (codec_id == AV_CODEC_ID_NONE) + return; + + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { + if (ist->user_set_discard == AVDISCARD_ALL) + continue; + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA && + ist->st->codecpar->codec_id == codec_id ) + new_data_stream(mux, o, ist); + } +} + +static void map_manual(Muxer *mux, const OptionsContext *o, const StreamMap *map) +{ + InputStream *ist; + + if (map->disabled) + return; + + if (map->linklabel) { + FilterGraph *fg; + OutputFilter *ofilter = NULL; + int j, k; + + for (j = 0; j < nb_filtergraphs; j++) { + fg = filtergraphs[j]; + for (k = 0; k < fg->nb_outputs; k++) { + AVFilterInOut *out = fg->outputs[k]->out_tmp; + if (out && !strcmp(out->name, map->linklabel)) { + ofilter = fg->outputs[k]; + goto loop_end; + } + } + } +loop_end: + if (!ofilter) { + av_log(mux, AV_LOG_FATAL, "Output with label '%s' does not exist " + "in any defined filter graph, or was already used elsewhere.\n", map->linklabel); + exit_program(1); + } + init_output_filter(ofilter, o, mux); + } else { + ist = input_files[map->file_index]->streams[map->stream_index]; + if (ist->user_set_discard == AVDISCARD_ALL) { + av_log(mux, AV_LOG_FATAL, "Stream #%d:%d is disabled and cannot be mapped.\n", + map->file_index, map->stream_index); + exit_program(1); + } + if(o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) + return; + if(o-> audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + return; + if(o-> video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + return; + if(o-> data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA) + return; + + switch (ist->st->codecpar->codec_type) { + case AVMEDIA_TYPE_VIDEO: new_video_stream (mux, o, ist); break; + case AVMEDIA_TYPE_AUDIO: new_audio_stream (mux, o, ist); break; + case AVMEDIA_TYPE_SUBTITLE: new_subtitle_stream (mux, o, ist); break; + case AVMEDIA_TYPE_DATA: new_data_stream (mux, o, ist); break; + case AVMEDIA_TYPE_ATTACHMENT: new_attachment_stream(mux, o, ist); break; + case AVMEDIA_TYPE_UNKNOWN: + if (copy_unknown_streams) { + new_unknown_stream (mux, o, ist); + break; + } + default: + av_log(mux, ignore_unknown_streams ? AV_LOG_WARNING : AV_LOG_FATAL, + "Cannot map stream #%d:%d - unsupported type.\n", + map->file_index, map->stream_index); + if (!ignore_unknown_streams) { + av_log(mux, AV_LOG_FATAL, + "If you want unsupported types ignored instead " + "of failing, please use the -ignore_unknown option\n" + "If you want them copied, please use -copy_unknown\n"); + exit_program(1); + } + } + } +} + +static void of_add_attachments(Muxer *mux, const OptionsContext *o) +{ + OutputStream *ost; + int err; + + for (int i = 0; i < o->nb_attachments; i++) { + AVIOContext *pb; + uint8_t *attachment; + const char *p; + int64_t len; + + if ((err = avio_open2(&pb, o->attachments[i], AVIO_FLAG_READ, &int_cb, NULL)) < 0) { + av_log(mux, AV_LOG_FATAL, "Could not open attachment file %s.\n", + o->attachments[i]); + exit_program(1); + } + if ((len = avio_size(pb)) <= 0) { + av_log(mux, AV_LOG_FATAL, "Could not get size of the attachment %s.\n", + o->attachments[i]); + exit_program(1); + } + if (len > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE || + !(attachment = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE))) { + av_log(mux, AV_LOG_FATAL, "Attachment %s too large.\n", + o->attachments[i]); + exit_program(1); + } + avio_read(pb, attachment, len); + memset(attachment + len, 0, AV_INPUT_BUFFER_PADDING_SIZE); + + ost = new_attachment_stream(mux, o, NULL); + ost->attachment_filename = o->attachments[i]; + ost->st->codecpar->extradata = attachment; + ost->st->codecpar->extradata_size = len; + + p = strrchr(o->attachments[i], '/'); + av_dict_set(&ost->st->metadata, "filename", (p && *p) ? p + 1 : o->attachments[i], AV_DICT_DONT_OVERWRITE); + avio_closep(&pb); + } +} + +static void create_streams(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + int auto_disable_v = o->video_disable; + int auto_disable_a = o->audio_disable; + int auto_disable_s = o->subtitle_disable; + int auto_disable_d = o->data_disable; + + /* create streams for all unlabeled output pads */ + for (int i = 0; i < nb_filtergraphs; i++) { + FilterGraph *fg = filtergraphs[i]; + for (int j = 0; j < fg->nb_outputs; j++) { + OutputFilter *ofilter = fg->outputs[j]; + + if (!ofilter->out_tmp || ofilter->out_tmp->name) + continue; + + switch (ofilter->type) { + case AVMEDIA_TYPE_VIDEO: auto_disable_v = 1; break; + case AVMEDIA_TYPE_AUDIO: auto_disable_a = 1; break; + case AVMEDIA_TYPE_SUBTITLE: auto_disable_s = 1; break; + } + init_output_filter(ofilter, o, mux); + } + } + + if (!o->nb_stream_maps) { + /* pick the "best" stream of each type */ + if (!auto_disable_v) + map_auto_video(mux, o); + if (!auto_disable_a) + map_auto_audio(mux, o); + if (!auto_disable_s) + map_auto_subtitle(mux, o); + if (!auto_disable_d) + map_auto_data(mux, o); + } else { + for (int i = 0; i < o->nb_stream_maps; i++) + map_manual(mux, o, &o->stream_maps[i]); + } + + of_add_attachments(mux, o); + + if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) { + av_dump_format(oc, nb_output_files - 1, oc->url, 1); + av_log(mux, AV_LOG_ERROR, "Output file does not contain any stream\n"); + exit_program(1); + } +} + +static int setup_sync_queues(Muxer *mux, AVFormatContext *oc, int64_t buf_size_us) +{ + OutputFile *of = &mux->of; + int nb_av_enc = 0, nb_interleaved = 0; + int limit_frames = 0, limit_frames_av_enc = 0; + +#define IS_AV_ENC(ost, type) \ + (ost->enc_ctx && (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) +#define IS_INTERLEAVED(type) (type != AVMEDIA_TYPE_ATTACHMENT) + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + ost->sq_idx_encode = -1; + ost->sq_idx_mux = -1; + + nb_interleaved += IS_INTERLEAVED(type); + nb_av_enc += IS_AV_ENC(ost, type); + + limit_frames |= ms->max_frames < INT64_MAX; + limit_frames_av_enc |= (ms->max_frames < INT64_MAX) && IS_AV_ENC(ost, type); + } + + if (!((nb_interleaved > 1 && of->shortest) || + (nb_interleaved > 0 && limit_frames))) + return 0; + + /* if we have more than one encoded audio/video streams, or at least + * one encoded audio/video stream is frame-limited, then we + * synchronize them before encoding */ + if ((of->shortest && nb_av_enc > 1) || limit_frames_av_enc) { + of->sq_encode = sq_alloc(SYNC_QUEUE_FRAMES, buf_size_us); + if (!of->sq_encode) + return AVERROR(ENOMEM); + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (!IS_AV_ENC(ost, type)) + continue; + + ost->sq_idx_encode = sq_add_stream(of->sq_encode, + of->shortest || ms->max_frames < INT64_MAX); + if (ost->sq_idx_encode < 0) + return ost->sq_idx_encode; + + ost->sq_frame = av_frame_alloc(); + if (!ost->sq_frame) + return AVERROR(ENOMEM); + + if (ms->max_frames != INT64_MAX) + sq_limit_frames(of->sq_encode, ost->sq_idx_encode, ms->max_frames); + } + } + + /* if there are any additional interleaved streams, then ALL the streams + * are also synchronized before sending them to the muxer */ + if (nb_interleaved > nb_av_enc) { + mux->sq_mux = sq_alloc(SYNC_QUEUE_PACKETS, buf_size_us); + if (!mux->sq_mux) + return AVERROR(ENOMEM); + + mux->sq_pkt = av_packet_alloc(); + if (!mux->sq_pkt) + return AVERROR(ENOMEM); + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (!IS_INTERLEAVED(type)) + continue; + + ost->sq_idx_mux = sq_add_stream(mux->sq_mux, + of->shortest || ms->max_frames < INT64_MAX); + if (ost->sq_idx_mux < 0) + return ost->sq_idx_mux; + + if (ms->max_frames != INT64_MAX) + sq_limit_frames(mux->sq_mux, ost->sq_idx_mux, ms->max_frames); + } + } + +#undef IS_AV_ENC +#undef IS_INTERLEAVED + + return 0; +} + +static void of_add_programs(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + /* process manually set programs */ + for (int i = 0; i < o->nb_program; i++) { + const char *p = o->program[i].u.str; + int progid = i+1; + AVProgram *program; + + while(*p) { + const char *p2 = av_get_token(&p, ":"); + const char *to_dealloc = p2; + char *key; + if (!p2) + break; + + if(*p) p++; + + key = av_get_token(&p2, "="); + if (!key || !*p2) { + av_freep(&to_dealloc); + av_freep(&key); + break; + } + p2++; + + if (!strcmp(key, "program_num")) + progid = strtol(p2, NULL, 0); + av_freep(&to_dealloc); + av_freep(&key); + } + + program = av_new_program(oc, progid); + if (!program) + report_and_exit(AVERROR(ENOMEM)); + + p = o->program[i].u.str; + while(*p) { + const char *p2 = av_get_token(&p, ":"); + const char *to_dealloc = p2; + char *key; + if (!p2) + break; + if(*p) p++; + + key = av_get_token(&p2, "="); + if (!key) { + av_log(mux, AV_LOG_FATAL, + "No '=' character in program string %s.\n", + p2); + exit_program(1); + } + if (!*p2) + exit_program(1); + p2++; + + if (!strcmp(key, "title")) { + av_dict_set(&program->metadata, "title", p2, 0); + } else if (!strcmp(key, "program_num")) { + } else if (!strcmp(key, "st")) { + int st_num = strtol(p2, NULL, 0); + av_program_add_stream_index(oc, progid, st_num); + } else { + av_log(mux, AV_LOG_FATAL, "Unknown program key %s.\n", key); + exit_program(1); + } + av_freep(&to_dealloc); + av_freep(&key); + } + } +} + +/** + * Parse a metadata specifier passed as 'arg' parameter. + * @param arg metadata string to parse + * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram) + * @param index for type c/p, chapter/program index is written here + * @param stream_spec for type s, the stream specifier is written here + */ +static void parse_meta_type(void *logctx, const char *arg, + char *type, int *index, const char **stream_spec) +{ + if (*arg) { + *type = *arg; + switch (*arg) { + case 'g': + break; + case 's': + if (*(++arg) && *arg != ':') { + av_log(logctx, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg); + exit_program(1); + } + *stream_spec = *arg == ':' ? arg + 1 : ""; + break; + case 'c': + case 'p': + if (*(++arg) == ':') + *index = strtol(++arg, NULL, 0); + break; + default: + av_log(logctx, AV_LOG_FATAL, "Invalid metadata type %c.\n", *arg); + exit_program(1); + } + } else + *type = 'g'; +} + +static void of_add_metadata(OutputFile *of, AVFormatContext *oc, + const OptionsContext *o) +{ + for (int i = 0; i < o->nb_metadata; i++) { + AVDictionary **m; + char type, *val; + const char *stream_spec; + int index = 0, ret = 0; + + val = strchr(o->metadata[i].u.str, '='); + if (!val) { + av_log(of, AV_LOG_FATAL, "No '=' character in metadata string %s.\n", + o->metadata[i].u.str); + exit_program(1); + } + *val++ = 0; + + parse_meta_type(of, o->metadata[i].specifier, &type, &index, &stream_spec); + if (type == 's') { + for (int j = 0; j < oc->nb_streams; j++) { + OutputStream *ost = of->streams[j]; + if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) { +#if FFMPEG_ROTATION_METADATA + if (!strcmp(o->metadata[i].u.str, "rotate")) { + char *tail; + double theta = av_strtod(val, &tail); + if (!*tail) { + ost->rotate_overridden = 1; + ost->rotate_override_value = theta; + } + + av_log(ost, AV_LOG_WARNING, + "Conversion of a 'rotate' metadata key to a " + "proper display matrix rotation is deprecated. " + "See -display_rotation for setting rotation " + "instead."); + } else { +#endif + av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); +#if FFMPEG_ROTATION_METADATA + } +#endif + } else if (ret < 0) + exit_program(1); + } + } else { + switch (type) { + case 'g': + m = &oc->metadata; + break; + case 'c': + if (index < 0 || index >= oc->nb_chapters) { + av_log(of, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); + exit_program(1); + } + m = &oc->chapters[index]->metadata; + break; + case 'p': + if (index < 0 || index >= oc->nb_programs) { + av_log(of, AV_LOG_FATAL, "Invalid program index %d in metadata specifier.\n", index); + exit_program(1); + } + m = &oc->programs[index]->metadata; + break; + default: + av_log(of, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); + exit_program(1); + } + av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); + } + } +} + +static void set_channel_layout(OutputFilter *f, OutputStream *ost) +{ + const AVCodec *c = ost->enc_ctx->codec; + int i, err; + + if (ost->enc_ctx->ch_layout.order != AV_CHANNEL_ORDER_UNSPEC) { + /* Pass the layout through for all orders but UNSPEC */ + err = av_channel_layout_copy(&f->ch_layout, &ost->enc_ctx->ch_layout); + if (err < 0) + report_and_exit(AVERROR(ENOMEM)); + return; + } + + /* Requested layout is of order UNSPEC */ + if (!c->ch_layouts) { + /* Use the default native layout for the requested amount of channels when the + encoder doesn't have a list of supported layouts */ + av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); + return; + } + /* Encoder has a list of supported layouts. Pick the first layout in it with the + same amount of channels as the requested layout */ + for (i = 0; c->ch_layouts[i].nb_channels; i++) { + if (c->ch_layouts[i].nb_channels == ost->enc_ctx->ch_layout.nb_channels) + break; + } + if (c->ch_layouts[i].nb_channels) { + /* Use it if one is found */ + err = av_channel_layout_copy(&f->ch_layout, &c->ch_layouts[i]); + if (err < 0) + report_and_exit(AVERROR(ENOMEM)); + return; + } + /* If no layout for the amount of channels requested was found, use the default + native layout for it. */ + av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); +} + +static int copy_chapters(InputFile *ifile, OutputFile *ofile, AVFormatContext *os, + int copy_metadata) +{ + AVFormatContext *is = ifile->ctx; + AVChapter **tmp; + int i; + + tmp = av_realloc_f(os->chapters, is->nb_chapters + os->nb_chapters, sizeof(*os->chapters)); + if (!tmp) + return AVERROR(ENOMEM); + os->chapters = tmp; + + for (i = 0; i < is->nb_chapters; i++) { + AVChapter *in_ch = is->chapters[i], *out_ch; + int64_t start_time = (ofile->start_time == AV_NOPTS_VALUE) ? 0 : ofile->start_time; + int64_t ts_off = av_rescale_q(start_time - ifile->ts_offset, + AV_TIME_BASE_Q, in_ch->time_base); + int64_t rt = (ofile->recording_time == INT64_MAX) ? INT64_MAX : + av_rescale_q(ofile->recording_time, AV_TIME_BASE_Q, in_ch->time_base); + + + if (in_ch->end < ts_off) + continue; + if (rt != INT64_MAX && in_ch->start > rt + ts_off) + break; + + out_ch = av_mallocz(sizeof(AVChapter)); + if (!out_ch) + return AVERROR(ENOMEM); + + out_ch->id = in_ch->id; + out_ch->time_base = in_ch->time_base; + out_ch->start = FFMAX(0, in_ch->start - ts_off); + out_ch->end = FFMIN(rt, in_ch->end - ts_off); + + if (copy_metadata) + av_dict_copy(&out_ch->metadata, in_ch->metadata, 0); + + os->chapters[os->nb_chapters++] = out_ch; + } + return 0; +} + +static int copy_metadata(Muxer *mux, AVFormatContext *ic, + const char *outspec, const char *inspec, + int *metadata_global_manual, int *metadata_streams_manual, + int *metadata_chapters_manual, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + AVDictionary **meta_in = NULL; + AVDictionary **meta_out = NULL; + int i, ret = 0; + char type_in, type_out; + const char *istream_spec = NULL, *ostream_spec = NULL; + int idx_in = 0, idx_out = 0; + + parse_meta_type(mux, inspec, &type_in, &idx_in, &istream_spec); + parse_meta_type(mux, outspec, &type_out, &idx_out, &ostream_spec); + + if (type_in == 'g' || type_out == 'g') + *metadata_global_manual = 1; + if (type_in == 's' || type_out == 's') + *metadata_streams_manual = 1; + if (type_in == 'c' || type_out == 'c') + *metadata_chapters_manual = 1; + + /* ic is NULL when just disabling automatic mappings */ + if (!ic) + return 0; + +#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ + if ((index) < 0 || (index) >= (nb_elems)) {\ + av_log(mux, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\ + (desc), (index));\ + exit_program(1);\ + } + +#define SET_DICT(type, meta, context, index)\ + switch (type) {\ + case 'g':\ + meta = &context->metadata;\ + break;\ + case 'c':\ + METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\ + meta = &context->chapters[index]->metadata;\ + break;\ + case 'p':\ + METADATA_CHECK_INDEX(index, context->nb_programs, "program")\ + meta = &context->programs[index]->metadata;\ + break;\ + case 's':\ + break; /* handled separately below */ \ + default: av_assert0(0);\ + }\ + + SET_DICT(type_in, meta_in, ic, idx_in); + SET_DICT(type_out, meta_out, oc, idx_out); + + /* for input streams choose first matching stream */ + if (type_in == 's') { + for (i = 0; i < ic->nb_streams; i++) { + if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) { + meta_in = &ic->streams[i]->metadata; + break; + } else if (ret < 0) + exit_program(1); + } + if (!meta_in) { + av_log(mux, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec); + exit_program(1); + } + } + + if (type_out == 's') { + for (i = 0; i < oc->nb_streams; i++) { + if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) { + meta_out = &oc->streams[i]->metadata; + av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); + } else if (ret < 0) + exit_program(1); + } + } else + av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); + + return 0; +} + +static void copy_meta(Muxer *mux, const OptionsContext *o) +{ + OutputFile *of = &mux->of; + AVFormatContext *oc = mux->fc; + int chapters_input_file = o->chapters_input_file; + int metadata_global_manual = 0; + int metadata_streams_manual = 0; + int metadata_chapters_manual = 0; + + /* copy metadata */ + for (int i = 0; i < o->nb_metadata_map; i++) { + char *p; + int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0); + + if (in_file_index >= nb_input_files) { + av_log(mux, AV_LOG_FATAL, "Invalid input file index %d while " + "processing metadata maps\n", in_file_index); + exit_program(1); + } + copy_metadata(mux, + in_file_index >= 0 ? input_files[in_file_index]->ctx : NULL, + o->metadata_map[i].specifier, *p ? p + 1 : p, + &metadata_global_manual, &metadata_streams_manual, + &metadata_chapters_manual, o); + } + + /* copy chapters */ + if (chapters_input_file >= nb_input_files) { + if (chapters_input_file == INT_MAX) { + /* copy chapters from the first input file that has them*/ + chapters_input_file = -1; + for (int i = 0; i < nb_input_files; i++) + if (input_files[i]->ctx->nb_chapters) { + chapters_input_file = i; + break; + } + } else { + av_log(mux, AV_LOG_FATAL, "Invalid input file index %d in chapter mapping.\n", + chapters_input_file); + exit_program(1); + } + } + if (chapters_input_file >= 0) + copy_chapters(input_files[chapters_input_file], of, oc, + !metadata_chapters_manual); + + /* copy global metadata by default */ + if (!metadata_global_manual && nb_input_files){ + av_dict_copy(&oc->metadata, input_files[0]->ctx->metadata, + AV_DICT_DONT_OVERWRITE); + if (of->recording_time != INT64_MAX) + av_dict_set(&oc->metadata, "duration", NULL, 0); + av_dict_set(&oc->metadata, "creation_time", NULL, 0); + av_dict_set(&oc->metadata, "company_name", NULL, 0); + av_dict_set(&oc->metadata, "product_name", NULL, 0); + av_dict_set(&oc->metadata, "product_version", NULL, 0); + } + if (!metadata_streams_manual) + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + if (!ost->ist) /* this is true e.g. for attached files */ + continue; + av_dict_copy(&ost->st->metadata, ost->ist->st->metadata, AV_DICT_DONT_OVERWRITE); + if (ost->enc_ctx) { + av_dict_set(&ost->st->metadata, "encoder", NULL, 0); + } + } +} + +static int set_dispositions(Muxer *mux, const OptionsContext *o) +{ + OutputFile *of = &mux->of; + AVFormatContext *ctx = mux->fc; + + int nb_streams[AVMEDIA_TYPE_NB] = { 0 }; + int have_default[AVMEDIA_TYPE_NB] = { 0 }; + int have_manual = 0; + int ret = 0; + + const char **dispositions; + + dispositions = av_calloc(ctx->nb_streams, sizeof(*dispositions)); + if (!dispositions) + return AVERROR(ENOMEM); + + // first, copy the input dispositions + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + nb_streams[ost->st->codecpar->codec_type]++; + + MATCH_PER_STREAM_OPT(disposition, str, dispositions[i], ctx, ost->st); + + have_manual |= !!dispositions[i]; + + if (ost->ist) { + ost->st->disposition = ost->ist->st->disposition; + + if (ost->st->disposition & AV_DISPOSITION_DEFAULT) + have_default[ost->st->codecpar->codec_type] = 1; + } + } + + if (have_manual) { + // process manually set dispositions - they override the above copy + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + const char *disp = dispositions[i]; + + if (!disp) + continue; + + ret = av_opt_set(ost->st, "disposition", disp, 0); + if (ret < 0) + goto finish; + } + } else { + // For each media type with more than one stream, find a suitable stream to + // mark as default, unless one is already marked default. + // "Suitable" means the first of that type, skipping attached pictures. + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (nb_streams[type] < 2 || have_default[type] || + ost->st->disposition & AV_DISPOSITION_ATTACHED_PIC) + continue; + + ost->st->disposition |= AV_DISPOSITION_DEFAULT; + have_default[type] = 1; + } + } + +finish: + av_freep(&dispositions); + + return ret; +} + +const char *const forced_keyframes_const_names[] = { + "n", + "n_forced", + "prev_forced_n", + "prev_forced_t", + "t", + NULL +}; + +static int compare_int64(const void *a, const void *b) +{ + return FFDIFFSIGN(*(const int64_t *)a, *(const int64_t *)b); +} + +static void parse_forced_key_frames(KeyframeForceCtx *kf, const Muxer *mux, + const char *spec) +{ + const char *p; + int n = 1, i, size, index = 0; + int64_t t, *pts; + + for (p = spec; *p; p++) + if (*p == ',') + n++; + size = n; + pts = av_malloc_array(size, sizeof(*pts)); + if (!pts) + report_and_exit(AVERROR(ENOMEM)); + + p = spec; + for (i = 0; i < n; i++) { + char *next = strchr(p, ','); + + if (next) + *next++ = 0; + + if (!memcmp(p, "chapters", 8)) { + AVChapter * const *ch = mux->fc->chapters; + unsigned int nb_ch = mux->fc->nb_chapters; + int j; + + if (nb_ch > INT_MAX - size || + !(pts = av_realloc_f(pts, size += nb_ch - 1, + sizeof(*pts)))) + report_and_exit(AVERROR(ENOMEM)); + t = p[8] ? parse_time_or_die("force_key_frames", p + 8, 1) : 0; + + for (j = 0; j < nb_ch; j++) { + const AVChapter *c = ch[j]; + av_assert1(index < size); + pts[index++] = av_rescale_q(c->start, c->time_base, + AV_TIME_BASE_Q) + t; + } + + } else { + av_assert1(index < size); + pts[index++] = parse_time_or_die("force_key_frames", p, 1); + } + + p = next; + } + + av_assert0(index == size); + qsort(pts, size, sizeof(*pts), compare_int64); + kf->nb_pts = size; + kf->pts = pts; +} + +static int process_forced_keyframes(Muxer *mux, const OptionsContext *o) +{ + for (int i = 0; i < mux->of.nb_streams; i++) { + OutputStream *ost = mux->of.streams[i]; + const char *forced_keyframes = NULL; + + MATCH_PER_STREAM_OPT(forced_key_frames, str, forced_keyframes, mux->fc, ost->st); + + if (!(ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + ost->enc_ctx && forced_keyframes)) + continue; + + if (!strncmp(forced_keyframes, "expr:", 5)) { + int ret = av_expr_parse(&ost->kf.pexpr, forced_keyframes + 5, + forced_keyframes_const_names, NULL, NULL, NULL, NULL, 0, NULL); + if (ret < 0) { + av_log(ost, AV_LOG_ERROR, + "Invalid force_key_frames expression '%s'\n", forced_keyframes + 5); + return ret; + } + ost->kf.expr_const_values[FKF_N] = 0; + ost->kf.expr_const_values[FKF_N_FORCED] = 0; + ost->kf.expr_const_values[FKF_PREV_FORCED_N] = NAN; + ost->kf.expr_const_values[FKF_PREV_FORCED_T] = NAN; + + // Don't parse the 'forced_keyframes' in case of 'keep-source-keyframes', + // parse it only for static kf timings + } else if (!strcmp(forced_keyframes, "source")) { + ost->kf.type = KF_FORCE_SOURCE; + } else if (!strcmp(forced_keyframes, "source_no_drop")) { + ost->kf.type = KF_FORCE_SOURCE_NO_DROP; + } else { + parse_forced_key_frames(&ost->kf, mux, forced_keyframes); + } + } + + return 0; +} + +static void validate_enc_avopt(Muxer *mux, const AVDictionary *codec_avopt) +{ + const AVClass *class = avcodec_get_class(); + const AVClass *fclass = avformat_get_class(); + const OutputFile *of = &mux->of; + + AVDictionary *unused_opts; + const AVDictionaryEntry *e; + + unused_opts = strip_specifiers(codec_avopt); + for (int i = 0; i < of->nb_streams; i++) { + e = NULL; + while ((e = av_dict_iterate(of->streams[i]->encoder_opts, e))) + av_dict_set(&unused_opts, e->key, NULL, 0); + } + + e = NULL; + while ((e = av_dict_iterate(unused_opts, e))) { + const AVOption *option = av_opt_find(&class, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + if (!option || foption) + continue; + + if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) { + av_log(mux, AV_LOG_ERROR, "Codec AVOption %s (%s) is not an " + "encoding option.\n", e->key, option->help ? option->help : ""); + exit_program(1); + } + + // gop_timecode is injected by generic code but not always used + if (!strcmp(e->key, "gop_timecode")) + continue; + + av_log(mux, AV_LOG_WARNING, "Codec AVOption %s (%s) has not been used " + "for any stream. The most likely reason is either wrong type " + "(e.g. a video option with no video streams) or that it is a " + "private option of some encoder which was not actually used for " + "any stream.\n", e->key, option->help ? option->help : ""); + } + av_dict_free(&unused_opts); +} + +static const char *output_file_item_name(void *obj) +{ + const Muxer *mux = obj; + + return mux->log_name; +} + +static const AVClass output_file_class = { + .class_name = "OutputFile", + .version = LIBAVUTIL_VERSION_INT, + .item_name = output_file_item_name, + .category = AV_CLASS_CATEGORY_MUXER, +}; + +static Muxer *mux_alloc(void) +{ + Muxer *mux = allocate_array_elem(&output_files, sizeof(*mux), &nb_output_files); + + mux->of.clazz = &output_file_class; + mux->of.index = nb_output_files - 1; + + snprintf(mux->log_name, sizeof(mux->log_name), "out#%d", mux->of.index); + + return mux; +} + +int of_open(const OptionsContext *o, const char *filename) +{ + Muxer *mux; + AVFormatContext *oc; + int err; + OutputFile *of; + + int64_t recording_time = o->recording_time; + int64_t stop_time = o->stop_time; + + mux = mux_alloc(); + of = &mux->of; + + if (stop_time != INT64_MAX && recording_time != INT64_MAX) { + stop_time = INT64_MAX; + av_log(mux, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); + } + + if (stop_time != INT64_MAX && recording_time == INT64_MAX) { + int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; + if (stop_time <= start_time) { + av_log(mux, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); + exit_program(1); + } else { + recording_time = stop_time - start_time; + } + } + + of->recording_time = recording_time; + of->start_time = o->start_time; + of->shortest = o->shortest; + + mux->thread_queue_size = o->thread_queue_size > 0 ? o->thread_queue_size : 8; + mux->limit_filesize = o->limit_filesize; + av_dict_copy(&mux->opts, o->g->format_opts, 0); + + if (!strcmp(filename, "-")) + filename = "pipe:"; + + err = avformat_alloc_output_context2(&oc, NULL, o->format, filename); + if (!oc) { + print_error(filename, err); + exit_program(1); + } + mux->fc = oc; + + av_strlcat(mux->log_name, "/", sizeof(mux->log_name)); + av_strlcat(mux->log_name, oc->oformat->name, sizeof(mux->log_name)); + + if (strcmp(oc->oformat->name, "rtp")) + want_sdp = 0; + + of->format = oc->oformat; + if (recording_time != INT64_MAX) + oc->duration = recording_time; + + oc->interrupt_callback = int_cb; + + if (o->bitexact) { + oc->flags |= AVFMT_FLAG_BITEXACT; + of->bitexact = 1; + } else { + of->bitexact = check_opt_bitexact(oc, mux->opts, "fflags", + AVFMT_FLAG_BITEXACT); + } + + /* create all output streams for this file */ + create_streams(mux, o); + + /* check if all codec options have been used */ + validate_enc_avopt(mux, o->g->codec_opts); + + /* set the decoding_needed flags and create simple filtergraphs */ + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + if (ost->enc_ctx && ost->ist) { + InputStream *ist = ost->ist; + ist->decoding_needed |= DECODING_FOR_OST; + ist->processing_needed = 1; + + if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + err = init_simple_filtergraph(ist, ost); + if (err < 0) { + av_log(ost, AV_LOG_ERROR, + "Error initializing a simple filtergraph\n"); + exit_program(1); + } + } + } else if (ost->ist) { + ost->ist->processing_needed = 1; + } + + /* set the filter output constraints */ + if (ost->filter) { + const AVCodec *c = ost->enc_ctx->codec; + OutputFilter *f = ost->filter; + switch (ost->enc_ctx->codec_type) { + case AVMEDIA_TYPE_VIDEO: + f->frame_rate = ost->frame_rate; + f->width = ost->enc_ctx->width; + f->height = ost->enc_ctx->height; + if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { + f->format = ost->enc_ctx->pix_fmt; + } else { + f->formats = c->pix_fmts; + } + break; + case AVMEDIA_TYPE_AUDIO: + if (ost->enc_ctx->sample_fmt != AV_SAMPLE_FMT_NONE) { + f->format = ost->enc_ctx->sample_fmt; + } else { + f->formats = c->sample_fmts; + } + if (ost->enc_ctx->sample_rate) { + f->sample_rate = ost->enc_ctx->sample_rate; + } else { + f->sample_rates = c->supported_samplerates; + } + if (ost->enc_ctx->ch_layout.nb_channels) { + set_channel_layout(f, ost); + } else if (c->ch_layouts) { + f->ch_layouts = c->ch_layouts; + } + break; + } + } + } + + /* check filename in case of an image number is expected */ + if (oc->oformat->flags & AVFMT_NEEDNUMBER) { + if (!av_filename_number_test(oc->url)) { + print_error(oc->url, AVERROR(EINVAL)); + exit_program(1); + } + } + + if (!(oc->oformat->flags & AVFMT_NOFILE)) { + /* test if it already exists to avoid losing precious files */ + assert_file_overwrite(filename); + + /* open the file */ + if ((err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, + &oc->interrupt_callback, + &mux->opts)) < 0) { + print_error(filename, err); + exit_program(1); + } + } else if (strcmp(oc->oformat->name, "image2")==0 && !av_filename_number_test(filename)) + assert_file_overwrite(filename); + + if (o->mux_preload) { + av_dict_set_int(&mux->opts, "preload", o->mux_preload*AV_TIME_BASE, 0); + } + oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); + + /* copy metadata and chapters from input files */ + copy_meta(mux, o); + + of_add_programs(mux, o); + of_add_metadata(of, oc, o); + + err = set_dispositions(mux, o); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error setting output stream dispositions\n"); + exit_program(1); + } + + // parse forced keyframe specifications; + // must be done after chapters are created + err = process_forced_keyframes(mux, o); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error processing forced keyframes\n"); + exit_program(1); + } + + err = setup_sync_queues(mux, oc, o->shortest_buf_duration * AV_TIME_BASE); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error setting up output sync queues\n"); + exit_program(1); + } + + of->url = filename; + + /* write the header for files with no streams */ + if (of->format->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) { + int ret = mux_check_init(mux); + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_opt.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_opt.c index 84e8cd5..0ae6c67 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_opt.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffmpeg_opt.c @@ -1,6 +1,7 @@ /* * ffmpeg option parsing - * Copyright (c) 2018 Taner Sener + * Copyright (c) 2018-2020 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,14 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - include fftools_ffmpeg_mux.h added + * - fftools header names updated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 08.2020 @@ -60,8 +69,10 @@ #endif #include "fftools_ffmpeg.h" -#include "fftools_fopen_utf8.h" +#include "fftools_ffmpeg_mux.h" #include "fftools_cmdutils.h" +#include "fftools_opt_common.h" +#include "fftools_sync_queue.h" #include "libavformat/avformat.h" @@ -75,7 +86,7 @@ #include "libavutil/avutil.h" #include "libavutil/bprint.h" #include "libavutil/channel_layout.h" -#include "libavutil/getenv_utf8.h" +#include "libavutil/display.h" #include "libavutil/intreadwrite.h" #include "libavutil/fifo.h" #include "libavutil/mathematics.h" @@ -84,99 +95,10 @@ #include "libavutil/pixdesc.h" #include "libavutil/pixfmt.h" -#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass" - -#define SPECIFIER_OPT_FMT_str "%s" -#define SPECIFIER_OPT_FMT_i "%i" -#define SPECIFIER_OPT_FMT_i64 "%"PRId64 -#define SPECIFIER_OPT_FMT_ui64 "%"PRIu64 -#define SPECIFIER_OPT_FMT_f "%f" -#define SPECIFIER_OPT_FMT_dbl "%lf" - -static const char *const opt_name_codec_names[] = {"c", "codec", "acodec", "vcodec", "scodec", "dcodec", NULL}; -static const char *const opt_name_audio_channels[] = {"ac", NULL}; -static const char *const opt_name_audio_ch_layouts[] = {"channel_layout", "ch_layout", NULL}; -static const char *const opt_name_audio_sample_rate[] = {"ar", NULL}; -static const char *const opt_name_frame_rates[] = {"r", NULL}; -static const char *const opt_name_max_frame_rates[] = {"fpsmax", NULL}; -static const char *const opt_name_frame_sizes[] = {"s", NULL}; -static const char *const opt_name_frame_pix_fmts[] = {"pix_fmt", NULL}; -static const char *const opt_name_ts_scale[] = {"itsscale", NULL}; -static const char *const opt_name_hwaccels[] = {"hwaccel", NULL}; -static const char *const opt_name_hwaccel_devices[] = {"hwaccel_device", NULL}; -static const char *const opt_name_hwaccel_output_formats[] = {"hwaccel_output_format", NULL}; -static const char *const opt_name_autorotate[] = {"autorotate", NULL}; -static const char *const opt_name_autoscale[] = {"autoscale", NULL}; -static const char *const opt_name_max_frames[] = {"frames", "aframes", "vframes", "dframes", NULL}; -static const char *const opt_name_bitstream_filters[] = {"bsf", "absf", "vbsf", NULL}; -static const char *const opt_name_codec_tags[] = {"tag", "atag", "vtag", "stag", NULL}; -static const char *const opt_name_sample_fmts[] = {"sample_fmt", NULL}; -static const char *const opt_name_qscale[] = {"q", "qscale", NULL}; -static const char *const opt_name_forced_key_frames[] = {"forced_key_frames", NULL}; -static const char *const opt_name_fps_mode[] = {"fps_mode", NULL}; -static const char *const opt_name_force_fps[] = {"force_fps", NULL}; -static const char *const opt_name_frame_aspect_ratios[] = {"aspect", NULL}; -static const char *const opt_name_rc_overrides[] = {"rc_override", NULL}; -static const char *const opt_name_intra_matrices[] = {"intra_matrix", NULL}; -static const char *const opt_name_inter_matrices[] = {"inter_matrix", NULL}; -static const char *const opt_name_chroma_intra_matrices[] = {"chroma_intra_matrix", NULL}; -static const char *const opt_name_top_field_first[] = {"top", NULL}; -static const char *const opt_name_presets[] = {"pre", "apre", "vpre", "spre", NULL}; -static const char *const opt_name_copy_initial_nonkeyframes[] = {"copyinkf", NULL}; -static const char *const opt_name_copy_prior_start[] = {"copypriorss", NULL}; -static const char *const opt_name_filters[] = {"filter", "af", "vf", NULL}; -static const char *const opt_name_filter_scripts[] = {"filter_script", NULL}; -static const char *const opt_name_reinit_filters[] = {"reinit_filter", NULL}; -static const char *const opt_name_fix_sub_duration[] = {"fix_sub_duration", NULL}; -static const char *const opt_name_canvas_sizes[] = {"canvas_size", NULL}; -static const char *const opt_name_pass[] = {"pass", NULL}; -static const char *const opt_name_passlogfiles[] = {"passlogfile", NULL}; -static const char *const opt_name_max_muxing_queue_size[] = {"max_muxing_queue_size", NULL}; -static const char *const opt_name_muxing_queue_data_threshold[] = {"muxing_queue_data_threshold", NULL}; -static const char *const opt_name_guess_layout_max[] = {"guess_layout_max", NULL}; -static const char *const opt_name_apad[] = {"apad", NULL}; -static const char *const opt_name_discard[] = {"discard", NULL}; -static const char *const opt_name_disposition[] = {"disposition", NULL}; -static const char *const opt_name_time_bases[] = {"time_base", NULL}; -static const char *const opt_name_enc_time_bases[] = {"enc_time_base", NULL}; -static const char *const opt_name_bits_per_raw_sample[] = {"bits_per_raw_sample", NULL}; - -#define WARN_MULTIPLE_OPT_USAGE(name, type, so, st)\ -{\ - char namestr[128] = "";\ - const char *spec = so->specifier && so->specifier[0] ? so->specifier : "";\ - for (i = 0; opt_name_##name[i]; i++)\ - av_strlcatf(namestr, sizeof(namestr), "-%s%s", opt_name_##name[i], opt_name_##name[i+1] ? (opt_name_##name[i+2] ? ", " : " or ") : "");\ - av_log(NULL, AV_LOG_WARNING, "Multiple %s options specified for stream %d, only the last option '-%s%s%s "SPECIFIER_OPT_FMT_##type"' will be used.\n",\ - namestr, st->index, opt_name_##name[0], spec[0] ? ":" : "", spec, so->u.type);\ -} - -#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\ -{\ - int i, ret, matches = 0;\ - SpecifierOpt *so;\ - for (i = 0; i < o->nb_ ## name; i++) {\ - char *spec = o->name[i].specifier;\ - if ((ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\ - outvar = o->name[i].u.type;\ - so = &o->name[i];\ - matches++;\ - } else if (ret < 0)\ - exit_program(1);\ - }\ - if (matches > 1)\ - WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\ -} - -#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\ -{\ - int i;\ - for (i = 0; i < o->nb_ ## name; i++) {\ - char *spec = o->name[i].specifier;\ - if (!strcmp(spec, mediatype))\ - outvar = o->name[i].u.type;\ - }\ -} +const char *const opt_name_codec_names[] = {"c", "codec", "acodec", "vcodec", "scodec", "dcodec", NULL}; +const char *const opt_name_frame_rates[] = {"r", NULL}; +const char *const opt_name_codec_tags[] = {"tag", "atag", "vtag", "stag", NULL}; +const char *const opt_name_top_field_first[] = {"top", NULL}; __thread HWDevice *filter_hw_device; @@ -187,8 +109,6 @@ __thread float audio_drift_threshold = 0.1; __thread float dts_delta_threshold = 10; __thread float dts_error_threshold = 3600*30; -__thread int audio_volume = 256; -__thread int audio_sync_method = 0; __thread enum VideoSyncMethod video_sync_method = VSYNC_AUTO; __thread float frame_drop_threshold = 0; __thread int do_benchmark = 0; @@ -214,12 +134,12 @@ __thread int64_t stats_period = 500000; __thread int file_overwrite = 0; __thread int no_file_overwrite = 0; +#if FFMPEG_OPT_PSNR __thread int do_psnr = 0; -__thread int input_stream_potentially_available = 0; +#endif __thread int ignore_unknown_streams = 0; __thread int copy_unknown_streams = 0; __thread int recast_media = 0; -__thread int find_stream_info = 1; extern __thread OptionDef *ffmpeg_options; @@ -250,7 +170,9 @@ void uninit_options(OptionsContext *o) for (i = 0; i < o->nb_stream_maps; i++) av_freep(&o->stream_maps[i].linklabel); av_freep(&o->stream_maps); +#if FFMPEG_OPT_MAP_CHANNEL av_freep(&o->audio_channel_maps); +#endif av_freep(&o->streamid_map); av_freep(&o->attachments); } @@ -264,11 +186,13 @@ void init_options(OptionsContext *o) o->start_time = AV_NOPTS_VALUE; o->start_time_eof = AV_NOPTS_VALUE; o->recording_time = INT64_MAX; - o->limit_filesize = UINT64_MAX; + o->limit_filesize = INT64_MAX; o->chapters_input_file = INT_MAX; o->accurate_seek = 1; o->thread_queue_size = -1; o->input_sync_ref = -1; + o->find_stream_info = 1; + o->shortest_buf_duration = 10.f; } int show_hwaccels(void *optctx, const char *opt, const char *arg) @@ -284,12 +208,12 @@ int show_hwaccels(void *optctx, const char *opt, const char *arg) } /* return a copy of the input with the stream specifiers removed from the keys */ -AVDictionary *strip_specifiers(AVDictionary *dict) +AVDictionary *strip_specifiers(const AVDictionary *dict) { const AVDictionaryEntry *e = NULL; AVDictionary *ret = NULL; - while ((e = av_dict_get(dict, "", e, AV_DICT_IGNORE_SUFFIX))) { + while ((e = av_dict_iterate(dict, e))) { char *p = strchr(e->key, ':'); if (p) @@ -321,6 +245,44 @@ int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_id return 0; } +/* Correct input file start times based on enabled streams */ +void correct_input_start_times(void) +{ + for (int i = 0; i < nb_input_files; i++) { + InputFile *ifile = input_files[i]; + AVFormatContext *is = ifile->ctx; + int64_t new_start_time = INT64_MAX, diff, abs_start_seek; + + ifile->start_time_effective = is->start_time; + + if (is->start_time == AV_NOPTS_VALUE || + !(is->iformat->flags & AVFMT_TS_DISCONT)) + continue; + + for (int j = 0; j < is->nb_streams; j++) { + AVStream *st = is->streams[j]; + if(st->discard == AVDISCARD_ALL || st->start_time == AV_NOPTS_VALUE) + continue; + new_start_time = FFMIN(new_start_time, av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q)); + } + + diff = new_start_time - is->start_time; + if (diff) { + av_log(NULL, AV_LOG_VERBOSE, "Correcting start time of Input #%d by %"PRId64" us.\n", i, diff); + ifile->start_time_effective = new_start_time; + if (copy_ts && start_at_zero) + ifile->ts_offset = -new_start_time; + else if (!copy_ts) { + abs_start_seek = is->start_time + ((ifile->start_time != AV_NOPTS_VALUE) ? ifile->start_time : 0); + ifile->ts_offset = abs_start_seek > new_start_time ? -abs_start_seek : -new_start_time; + } else if (copy_ts) + ifile->ts_offset = 0; + + ifile->ts_offset += ifile->input_ts_offset; + } + } +} + int apply_sync_offsets(void) { for (int i = 0; i < nb_input_files; i++) { @@ -349,9 +311,9 @@ int apply_sync_offsets(void) if (self->ctx->start_time_realtime != AV_NOPTS_VALUE && ref->ctx->start_time_realtime != AV_NOPTS_VALUE) { self_start_time = self->ctx->start_time_realtime; ref_start_time = ref->ctx->start_time_realtime; - } else if (self->ctx->start_time != AV_NOPTS_VALUE && ref->ctx->start_time != AV_NOPTS_VALUE) { - self_start_time = self->ctx->start_time; - ref_start_time = ref->ctx->start_time; + } else if (self->start_time_effective != AV_NOPTS_VALUE && ref->start_time_effective != AV_NOPTS_VALUE) { + self_start_time = self->start_time_effective; + ref_start_time = ref->start_time_effective; } else { start_times_set = 0; } @@ -383,7 +345,7 @@ int opt_filter_threads(void *optctx, const char *opt, const char *arg) int opt_abort_on(void *optctx, const char *opt, const char *arg) { static const AVOption opts[] = { - { "abort_on" , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, (double)INT64_MAX, .unit = "flags" }, + { "abort_on" , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, (double)INT64_MAX, .unit = "flags" }, { "empty_output" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = ABORT_ON_FLAG_EMPTY_OUTPUT }, .unit = "flags" }, { "empty_output_stream", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM }, .unit = "flags" }, { NULL }, @@ -443,9 +405,10 @@ int opt_map(void *optctx, const char *opt, const char *arg) OptionsContext *o = optctx; StreamMap *m = NULL; int i, negative = 0, file_idx, disabled = 0; - int sync_file_idx = -1, sync_stream_idx = 0; - char *p, *sync; - char *map; +#if FFMPEG_OPT_MAP_SYNC + char *sync; +#endif + char *map, *p; char *allow_unused; if (*arg == '-') { @@ -456,33 +419,13 @@ int opt_map(void *optctx, const char *opt, const char *arg) if (!map) return AVERROR(ENOMEM); +#if FFMPEG_OPT_MAP_SYNC /* parse sync stream first, just pick first matching stream */ if ((sync = strchr(map, ','))) { *sync = 0; - sync_file_idx = strtol(sync + 1, &sync, 0); - if (sync_file_idx >= nb_input_files || sync_file_idx < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid sync file index: %d.\n", sync_file_idx); - exit_program(1); - } - if (*sync) - sync++; - for (i = 0; i < input_files[sync_file_idx]->nb_streams; i++) - if (check_stream_specifier(input_files[sync_file_idx]->ctx, - input_files[sync_file_idx]->ctx->streams[i], sync) == 1) { - sync_stream_idx = i; - break; - } - if (i == input_files[sync_file_idx]->nb_streams) { - av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s does not " - "match any streams.\n", arg); - exit_program(1); - } - if (input_streams[input_files[sync_file_idx]->ist_index + sync_stream_idx]->user_set_discard == AVDISCARD_ALL) { - av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s matches a disabled input " - "stream.\n", arg); - exit_program(1); - } + av_log(NULL, AV_LOG_WARNING, "Specifying a sync stream is deprecated and has no effect\n"); } +#endif if (map[0] == '[') { @@ -518,7 +461,7 @@ int opt_map(void *optctx, const char *opt, const char *arg) if (check_stream_specifier(input_files[file_idx]->ctx, input_files[file_idx]->ctx->streams[i], *p == ':' ? p + 1 : p) <= 0) continue; - if (input_streams[input_files[file_idx]->ist_index + i]->user_set_discard == AVDISCARD_ALL) { + if (input_files[file_idx]->streams[i]->user_set_discard == AVDISCARD_ALL) { disabled = 1; continue; } @@ -527,14 +470,6 @@ int opt_map(void *optctx, const char *opt, const char *arg) m->file_index = file_idx; m->stream_index = i; - - if (sync_file_idx >= 0) { - m->sync_file_index = sync_file_idx; - m->sync_stream_index = sync_stream_idx; - } else { - m->sync_file_index = file_idx; - m->sync_stream_index = i; - } } } @@ -564,6 +499,7 @@ int opt_attach(void *optctx, const char *opt, const char *arg) return 0; } +#if FFMPEG_OPT_MAP_CHANNEL int opt_map_channel(void *optctx, const char *opt, const char *arg) { OptionsContext *o = optctx; @@ -572,6 +508,12 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) AudioChannelMap *m; char *allow_unused; char *mapchan; + + av_log(NULL, AV_LOG_WARNING, + "The -%s option is deprecated and will be removed. " + "It can be replaced by the 'pan' filter, or in some cases by " + "combinations of 'channelsplit', 'channelmap', 'amerge' filters.\n", opt); + mapchan = av_strdup(arg); if (!mapchan) return AVERROR(ENOMEM); @@ -625,7 +567,7 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) if ((allow_unused = strchr(mapchan, '?'))) *allow_unused = 0; if (m->channel_idx < 0 || m->channel_idx >= st->codecpar->ch_layout.nb_channels || - input_streams[input_files[m->file_idx]->ist_index + m->stream_idx]->user_set_discard == AVDISCARD_ALL) { + input_files[m->file_idx]->streams[m->stream_idx]->user_set_discard == AVDISCARD_ALL) { if (allow_unused) { av_log(NULL, AV_LOG_VERBOSE, "mapchan: invalid audio channel #%d.%d.%d\n", m->file_idx, m->stream_idx, m->channel_idx); @@ -640,6 +582,7 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) av_free(mapchan); return 0; } +#endif int opt_sdp_file(void *optctx, const char *opt, const char *arg) { @@ -709,130 +652,6 @@ int opt_filter_hw_device(void *optctx, const char *opt, const char *arg) return 0; } -/** - * Parse a metadata specifier passed as 'arg' parameter. - * @param arg metadata string to parse - * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram) - * @param index for type c/p, chapter/program index is written here - * @param stream_spec for type s, the stream specifier is written here - */ -void parse_meta_type(char *arg, char *type, int *index, const char **stream_spec) -{ - if (*arg) { - *type = *arg; - switch (*arg) { - case 'g': - break; - case 's': - if (*(++arg) && *arg != ':') { - av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg); - exit_program(1); - } - *stream_spec = *arg == ':' ? arg + 1 : ""; - break; - case 'c': - case 'p': - if (*(++arg) == ':') - *index = strtol(++arg, NULL, 0); - break; - default: - av_log(NULL, AV_LOG_FATAL, "Invalid metadata type %c.\n", *arg); - exit_program(1); - } - } else - *type = 'g'; -} - -int fftools_copy_metadata(char *outspec, char *inspec, AVFormatContext *oc, AVFormatContext *ic, OptionsContext *o) -{ - AVDictionary **meta_in = NULL; - AVDictionary **meta_out = NULL; - int i, ret = 0; - char type_in, type_out; - const char *istream_spec = NULL, *ostream_spec = NULL; - int idx_in = 0, idx_out = 0; - - parse_meta_type(inspec, &type_in, &idx_in, &istream_spec); - parse_meta_type(outspec, &type_out, &idx_out, &ostream_spec); - - if (!ic) { - if (type_out == 'g' || !*outspec) - o->metadata_global_manual = 1; - if (type_out == 's' || !*outspec) - o->metadata_streams_manual = 1; - if (type_out == 'c' || !*outspec) - o->metadata_chapters_manual = 1; - return 0; - } - - if (type_in == 'g' || type_out == 'g') - o->metadata_global_manual = 1; - if (type_in == 's' || type_out == 's') - o->metadata_streams_manual = 1; - if (type_in == 'c' || type_out == 'c') - o->metadata_chapters_manual = 1; - - /* ic is NULL when just disabling automatic mappings */ - if (!ic) - return 0; - -#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ - if ((index) < 0 || (index) >= (nb_elems)) {\ - av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\ - (desc), (index));\ - exit_program(1);\ - } - -#define SET_DICT(type, meta, context, index)\ - switch (type) {\ - case 'g':\ - meta = &context->metadata;\ - break;\ - case 'c':\ - METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\ - meta = &context->chapters[index]->metadata;\ - break;\ - case 'p':\ - METADATA_CHECK_INDEX(index, context->nb_programs, "program")\ - meta = &context->programs[index]->metadata;\ - break;\ - case 's':\ - break; /* handled separately below */ \ - default: av_assert0(0);\ - }\ - - SET_DICT(type_in, meta_in, ic, idx_in); - SET_DICT(type_out, meta_out, oc, idx_out); - - /* for input streams choose first matching stream */ - if (type_in == 's') { - for (i = 0; i < ic->nb_streams; i++) { - if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) { - meta_in = &ic->streams[i]->metadata; - break; - } else if (ret < 0) - exit_program(1); - } - if (!meta_in) { - av_log(NULL, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec); - exit_program(1); - } - } - - if (type_out == 's') { - for (i = 0; i < oc->nb_streams; i++) { - if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) { - meta_out = &oc->streams[i]->metadata; - av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); - } else if (ret < 0) - exit_program(1); - } - } else - av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); - - return 0; -} - int opt_recording_timestamp(void *optctx, const char *opt, const char *arg) { OptionsContext *o = optctx; @@ -848,7 +667,8 @@ int opt_recording_timestamp(void *optctx, const char *opt, const char *arg) return 0; } -const AVCodec *find_codec_or_die(const char *name, enum AVMediaType type, int encoder) +const AVCodec *find_codec_or_die(void *logctx, const char *name, + enum AVMediaType type, int encoder) { const AVCodecDescriptor *desc; const char *codec_string = encoder ? "encoder" : "decoder"; @@ -862,247 +682,21 @@ const AVCodec *find_codec_or_die(const char *name, enum AVMediaType type, int en codec = encoder ? avcodec_find_encoder(desc->id) : avcodec_find_decoder(desc->id); if (codec) - av_log(NULL, AV_LOG_VERBOSE, "Matched %s '%s' for codec '%s'.\n", + av_log(logctx, AV_LOG_VERBOSE, "Matched %s '%s' for codec '%s'.\n", codec_string, codec->name, desc->name); } if (!codec) { - av_log(NULL, AV_LOG_FATAL, "Unknown %s '%s'\n", codec_string, name); + av_log(logctx, AV_LOG_FATAL, "Unknown %s '%s'\n", codec_string, name); exit_program(1); } if (codec->type != type && !recast_media) { - av_log(NULL, AV_LOG_FATAL, "Invalid %s type '%s'\n", codec_string, name); + av_log(logctx, AV_LOG_FATAL, "Invalid %s type '%s'\n", codec_string, name); exit_program(1); } return codec; } -const AVCodec *choose_decoder(OptionsContext *o, AVFormatContext *s, AVStream *st) -{ - char *codec_name = NULL; - - MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st); - if (codec_name) { - const AVCodec *codec = find_codec_or_die(codec_name, st->codecpar->codec_type, 0); - st->codecpar->codec_id = codec->id; - if (recast_media && st->codecpar->codec_type != codec->type) - st->codecpar->codec_type = codec->type; - return codec; - } else - return avcodec_find_decoder(st->codecpar->codec_id); -} - -/* Add all the streams from the given input file to the global - * list of input streams. */ -void add_input_streams(OptionsContext *o, AVFormatContext *ic) -{ - int i, ret; - - for (i = 0; i < ic->nb_streams; i++) { - AVStream *st = ic->streams[i]; - AVCodecParameters *par = st->codecpar; - InputStream *ist; - char *framerate = NULL, *hwaccel_device = NULL; - const char *hwaccel = NULL; - char *hwaccel_output_format = NULL; - char *codec_tag = NULL; - char *next; - char *discard_str = NULL; - const AVClass *cc = avcodec_get_class(); - const AVOption *discard_opt = av_opt_find(&cc, "skip_frame", NULL, - 0, AV_OPT_SEARCH_FAKE_OBJ); - - ist = ALLOC_ARRAY_ELEM(input_streams, nb_input_streams); - ist->st = st; - ist->file_index = nb_input_files; - ist->discard = 1; - st->discard = AVDISCARD_ALL; - ist->nb_samples = 0; - ist->first_dts = AV_NOPTS_VALUE; - ist->min_pts = INT64_MAX; - ist->max_pts = INT64_MIN; - - ist->ts_scale = 1.0; - MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st); - - ist->autorotate = 1; - MATCH_PER_STREAM_OPT(autorotate, i, ist->autorotate, ic, st); - - MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st); - if (codec_tag) { - uint32_t tag = strtol(codec_tag, &next, 0); - if (*next) - tag = AV_RL32(codec_tag); - st->codecpar->codec_tag = tag; - } - - ist->dec = choose_decoder(o, ic, st); - ist->decoder_opts = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ic, st, ist->dec); - - ist->reinit_filters = -1; - MATCH_PER_STREAM_OPT(reinit_filters, i, ist->reinit_filters, ic, st); - - MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st); - ist->user_set_discard = AVDISCARD_NONE; - - if ((o->video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) || - (o->audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || - (o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) || - (o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA)) - ist->user_set_discard = AVDISCARD_ALL; - - if (discard_str && av_opt_eval_int(&cc, discard_opt, discard_str, &ist->user_set_discard) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing discard %s.\n", - discard_str); - exit_program(1); - } - - ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE; - ist->prev_pkt_pts = AV_NOPTS_VALUE; - - ist->dec_ctx = avcodec_alloc_context3(ist->dec); - if (!ist->dec_ctx) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the decoder context.\n"); - exit_program(1); - } - - ret = avcodec_parameters_to_context(ist->dec_ctx, par); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); - exit_program(1); - } - - ist->decoded_frame = av_frame_alloc(); - if (!ist->decoded_frame) - exit_program(1); - - ist->pkt = av_packet_alloc(); - if (!ist->pkt) - exit_program(1); - - if (o->bitexact) - ist->dec_ctx->flags |= AV_CODEC_FLAG_BITEXACT; - - switch (par->codec_type) { - case AVMEDIA_TYPE_VIDEO: - if(!ist->dec) - ist->dec = avcodec_find_decoder(par->codec_id); - - // avformat_find_stream_info() doesn't set this for us anymore. - ist->dec_ctx->framerate = st->avg_frame_rate; - - MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st); - if (framerate && av_parse_video_rate(&ist->framerate, - framerate) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing framerate %s.\n", - framerate); - exit_program(1); - } - - ist->top_field_first = -1; - MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st); - - MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st); - MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, - hwaccel_output_format, ic, st); - - if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "cuvid")) { - av_log(NULL, AV_LOG_WARNING, - "WARNING: defaulting hwaccel_output_format to cuda for compatibility " - "with old commandlines. This behaviour is DEPRECATED and will be removed " - "in the future. Please explicitly set \"-hwaccel_output_format cuda\".\n"); - ist->hwaccel_output_format = AV_PIX_FMT_CUDA; - } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "qsv")) { - av_log(NULL, AV_LOG_WARNING, - "WARNING: defaulting hwaccel_output_format to qsv for compatibility " - "with old commandlines. This behaviour is DEPRECATED and will be removed " - "in the future. Please explicitly set \"-hwaccel_output_format qsv\".\n"); - ist->hwaccel_output_format = AV_PIX_FMT_QSV; - } else if (hwaccel_output_format) { - ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format); - if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output " - "format: %s", hwaccel_output_format); - } - } else { - ist->hwaccel_output_format = AV_PIX_FMT_NONE; - } - - if (hwaccel) { - // The NVDEC hwaccels use a CUDA device, so remap the name here. - if (!strcmp(hwaccel, "nvdec") || !strcmp(hwaccel, "cuvid")) - hwaccel = "cuda"; - - if (!strcmp(hwaccel, "none")) - ist->hwaccel_id = HWACCEL_NONE; - else if (!strcmp(hwaccel, "auto")) - ist->hwaccel_id = HWACCEL_AUTO; - else { - enum AVHWDeviceType type = av_hwdevice_find_type_by_name(hwaccel); - if (type != AV_HWDEVICE_TYPE_NONE) { - ist->hwaccel_id = HWACCEL_GENERIC; - ist->hwaccel_device_type = type; - } - - if (!ist->hwaccel_id) { - av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n", - hwaccel); - av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: "); - type = AV_HWDEVICE_TYPE_NONE; - while ((type = av_hwdevice_iterate_types(type)) != - AV_HWDEVICE_TYPE_NONE) - av_log(NULL, AV_LOG_FATAL, "%s ", - av_hwdevice_get_type_name(type)); - av_log(NULL, AV_LOG_FATAL, "\n"); - exit_program(1); - } - } - } - - MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st); - if (hwaccel_device) { - ist->hwaccel_device = av_strdup(hwaccel_device); - if (!ist->hwaccel_device) - exit_program(1); - } - - ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE; - - break; - case AVMEDIA_TYPE_AUDIO: - ist->guess_layout_max = INT_MAX; - MATCH_PER_STREAM_OPT(guess_layout_max, i, ist->guess_layout_max, ic, st); - guess_input_channel_layout(ist); - break; - case AVMEDIA_TYPE_DATA: - case AVMEDIA_TYPE_SUBTITLE: { - char *canvas_size = NULL; - if(!ist->dec) - ist->dec = avcodec_find_decoder(par->codec_id); - MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st); - MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st); - if (canvas_size && - av_parse_video_size(&ist->dec_ctx->width, &ist->dec_ctx->height, canvas_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid canvas size: %s.\n", canvas_size); - exit_program(1); - } - break; - } - case AVMEDIA_TYPE_ATTACHMENT: - case AVMEDIA_TYPE_UNKNOWN: - break; - default: - abort(); - } - - ret = avcodec_parameters_from_context(par, ist->dec_ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); - exit_program(1); - } - } -} - void assert_file_overwrite(const char *filename) { const char *proto_name = avio_find_protocol_name(filename); @@ -1145,2006 +739,134 @@ void assert_file_overwrite(const char *filename) } } -void dump_attachment(AVStream *st, const char *filename) +/* read file contents into a string */ +char *file_read(const char *filename) { - int ret; - AVIOContext *out = NULL; - const AVDictionaryEntry *e; + AVIOContext *pb = NULL; + int ret = avio_open(&pb, filename, AVIO_FLAG_READ); + AVBPrint bprint; + char *str; - if (!st->codecpar->extradata_size) { - av_log(NULL, AV_LOG_WARNING, "No extradata to dump in stream #%d:%d.\n", - nb_input_files - 1, st->index); - return; + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename); + return NULL; } - if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) - filename = e->value; - if (!*filename) { - av_log(NULL, AV_LOG_FATAL, "No filename specified and no 'filename' tag" - "in stream #%d:%d.\n", nb_input_files - 1, st->index); - exit_program(1); + + av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); + ret = avio_read_to_bprint(pb, &bprint, SIZE_MAX); + avio_closep(&pb); + if (ret < 0) { + av_bprint_finalize(&bprint, NULL); + return NULL; } + ret = av_bprint_finalize(&bprint, &str); + if (ret < 0) + return NULL; + return str; +} - assert_file_overwrite(filename); +/* arg format is "output-stream-index:streamid-value". */ +int opt_streamid(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + int idx; + char *p; + char idx_str[16]; - if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &int_cb, NULL)) < 0) { - av_log(NULL, AV_LOG_FATAL, "Could not open file %s for writing.\n", - filename); + av_strlcpy(idx_str, arg, sizeof(idx_str)); + p = strchr(idx_str, ':'); + if (!p) { + av_log(NULL, AV_LOG_FATAL, + "Invalid value '%s' for option '%s', required syntax is 'index:value'\n", + arg, opt); exit_program(1); } - - avio_write(out, st->codecpar->extradata, st->codecpar->extradata_size); - avio_flush(out); - avio_close(out); + *p++ = '\0'; + idx = parse_number_or_die(opt, idx_str, OPT_INT, 0, MAX_STREAMS-1); + o->streamid_map = grow_array(o->streamid_map, sizeof(*o->streamid_map), &o->nb_streamid_map, idx+1); + o->streamid_map[idx] = parse_number_or_die(opt, p, OPT_INT, 0, INT_MAX); + return 0; } -int open_input_file(OptionsContext *o, const char *filename) +int init_complex_filters(void) { - InputFile *f; - AVFormatContext *ic; - const AVInputFormat *file_iformat = NULL; - int err, i, ret; - int64_t timestamp; - AVDictionary *unused_opts = NULL; - const AVDictionaryEntry *e = NULL; - char * video_codec_name = NULL; - char * audio_codec_name = NULL; - char *subtitle_codec_name = NULL; - char * data_codec_name = NULL; - int scan_all_pmts_set = 0; - - if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) { - o->stop_time = INT64_MAX; - av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); - } - - if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) { - int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; - if (o->stop_time <= start_time) { - av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); - exit_program(1); - } else { - o->recording_time = o->stop_time - start_time; - } - } + int i, ret = 0; - if (o->format) { - if (!(file_iformat = av_find_input_format(o->format))) { - av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format); - exit_program(1); - } + for (i = 0; i < nb_filtergraphs; i++) { + ret = init_complex_filtergraph(filtergraphs[i]); + if (ret < 0) + return ret; } + return 0; +} - if (!strcmp(filename, "-")) - filename = "pipe:"; - - stdin_interaction &= strncmp(filename, "pipe:", 5) && - strcmp(filename, "/dev/stdin"); +int opt_target(void *optctx, const char *opt, const char *arg) +{ + const OptionDef *options = ffmpeg_options; + OptionsContext *o = optctx; + enum { PAL, NTSC, FILM, UNKNOWN } norm = UNKNOWN; + static const char *const frame_rates[] = { "25", "30000/1001", "24000/1001" }; - /* get default parameters from command line */ - ic = avformat_alloc_context(); - if (!ic) { - print_error(filename, AVERROR(ENOMEM)); - exit_program(1); - } - if (o->nb_audio_sample_rate) { - av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0); - } - if (o->nb_audio_channels) { - const AVClass *priv_class; - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "ch_layout", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - char buf[32]; - snprintf(buf, sizeof(buf), "%dC", o->audio_channels[o->nb_audio_channels - 1].u.i); - av_dict_set(&o->g->format_opts, "ch_layout", buf, 0); - } - } - if (o->nb_audio_ch_layouts) { - const AVClass *priv_class; - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "ch_layout", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - av_dict_set(&o->g->format_opts, "ch_layout", o->audio_ch_layouts[o->nb_audio_ch_layouts - 1].u.str, 0); - } - } - if (o->nb_frame_rates) { - const AVClass *priv_class; - /* set the format-level framerate option; - * this is important for video grabbers, e.g. x11 */ - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "framerate", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - av_dict_set(&o->g->format_opts, "framerate", - o->frame_rates[o->nb_frame_rates - 1].u.str, 0); + if (!strncmp(arg, "pal-", 4)) { + norm = PAL; + arg += 4; + } else if (!strncmp(arg, "ntsc-", 5)) { + norm = NTSC; + arg += 5; + } else if (!strncmp(arg, "film-", 5)) { + norm = FILM; + arg += 5; + } else { + /* Try to determine PAL/NTSC by peeking in the input files */ + if (nb_input_files) { + int i, j; + for (j = 0; j < nb_input_files; j++) { + for (i = 0; i < input_files[j]->nb_streams; i++) { + AVStream *st = input_files[j]->ctx->streams[i]; + int64_t fr; + if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + fr = st->time_base.den * 1000LL / st->time_base.num; + if (fr == 25000) { + norm = PAL; + break; + } else if ((fr == 29970) || (fr == 23976)) { + norm = NTSC; + break; + } + } + if (norm != UNKNOWN) + break; + } } + if (norm != UNKNOWN) + av_log(NULL, AV_LOG_INFO, "Assuming %s for target.\n", norm == PAL ? "PAL" : "NTSC"); } - if (o->nb_frame_sizes) { - av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0); - } - if (o->nb_frame_pix_fmts) - av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0); - - MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v"); - MATCH_PER_TYPE_OPT(codec_names, str, audio_codec_name, ic, "a"); - MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s"); - MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d"); - - if (video_codec_name) - ic->video_codec = find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0); - if (audio_codec_name) - ic->audio_codec = find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0); - if (subtitle_codec_name) - ic->subtitle_codec = find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0); - if (data_codec_name) - ic->data_codec = find_codec_or_die(data_codec_name , AVMEDIA_TYPE_DATA , 0); - - ic->video_codec_id = video_codec_name ? ic->video_codec->id : AV_CODEC_ID_NONE; - ic->audio_codec_id = audio_codec_name ? ic->audio_codec->id : AV_CODEC_ID_NONE; - ic->subtitle_codec_id = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE; - ic->data_codec_id = data_codec_name ? ic->data_codec->id : AV_CODEC_ID_NONE; - - ic->flags |= AVFMT_FLAG_NONBLOCK; - if (o->bitexact) - ic->flags |= AVFMT_FLAG_BITEXACT; - ic->interrupt_callback = int_cb; - - if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { - av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); - scan_all_pmts_set = 1; - } - /* open the input file with generic avformat function */ - err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); - if (err < 0) { - print_error(filename, err); - if (err == AVERROR_PROTOCOL_NOT_FOUND) - av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename); + + if (norm == UNKNOWN) { + av_log(NULL, AV_LOG_FATAL, "Could not determine norm (PAL/NTSC/NTSC-Film) for target.\n"); + av_log(NULL, AV_LOG_FATAL, "Please prefix target with \"pal-\", \"ntsc-\" or \"film-\",\n"); + av_log(NULL, AV_LOG_FATAL, "or set a framerate with \"-r xxx\".\n"); exit_program(1); } - if (scan_all_pmts_set) - av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); - remove_avoptions(&o->g->format_opts, o->g->codec_opts); - assert_avoptions(o->g->format_opts); - /* apply forced codec ids */ - for (i = 0; i < ic->nb_streams; i++) - choose_decoder(o, ic, ic->streams[i]); - - if (find_stream_info) { - AVDictionary **opts = setup_find_stream_info_opts(ic, o->g->codec_opts); - int orig_nb_streams = ic->nb_streams; + if (!strcmp(arg, "vcd")) { + opt_video_codec(o, "c:v", "mpeg1video"); + opt_audio_codec(o, "c:a", "mp2"); + parse_option(o, "f", "vcd", options); - /* If not enough info to get the stream parameters, we decode the - first frames to get it. (used in mpeg case for example) */ - ret = avformat_find_stream_info(ic, opts); + parse_option(o, "s", norm == PAL ? "352x288" : "352x240", options); + parse_option(o, "r", frame_rates[norm], options); + opt_default(NULL, "g", norm == PAL ? "15" : "18"); - for (i = 0; i < orig_nb_streams; i++) - av_dict_free(&opts[i]); - av_freep(&opts); + opt_default(NULL, "b:v", "1150000"); + opt_default(NULL, "maxrate:v", "1150000"); + opt_default(NULL, "minrate:v", "1150000"); + opt_default(NULL, "bufsize:v", "327680"); // 40*1024*8; - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename); - if (ic->nb_streams == 0) { - avformat_close_input(&ic); - exit_program(1); - } - } - } - - if (o->start_time != AV_NOPTS_VALUE && o->start_time_eof != AV_NOPTS_VALUE) { - av_log(NULL, AV_LOG_WARNING, "Cannot use -ss and -sseof both, using -ss for %s\n", filename); - o->start_time_eof = AV_NOPTS_VALUE; - } - - if (o->start_time_eof != AV_NOPTS_VALUE) { - if (o->start_time_eof >= 0) { - av_log(NULL, AV_LOG_ERROR, "-sseof value must be negative; aborting\n"); - exit_program(1); - } - if (ic->duration > 0) { - o->start_time = o->start_time_eof + ic->duration; - if (o->start_time < 0) { - av_log(NULL, AV_LOG_WARNING, "-sseof value seeks to before start of file %s; ignored\n", filename); - o->start_time = AV_NOPTS_VALUE; - } - } else - av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename); - } - timestamp = (o->start_time == AV_NOPTS_VALUE) ? 0 : o->start_time; - /* add the stream start time */ - if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE) - timestamp += ic->start_time; - - /* if seeking requested, we execute it */ - if (o->start_time != AV_NOPTS_VALUE) { - int64_t seek_timestamp = timestamp; - - if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) { - int dts_heuristic = 0; - for (i=0; inb_streams; i++) { - const AVCodecParameters *par = ic->streams[i]->codecpar; - if (par->video_delay) { - dts_heuristic = 1; - break; - } - } - if (dts_heuristic) { - seek_timestamp -= 3*AV_TIME_BASE / 23; - } - } - ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0); - if (ret < 0) { - av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", - filename, (double)timestamp / AV_TIME_BASE); - } - } - - /* update the current parameters so that they match the one of the input stream */ - add_input_streams(o, ic); - - /* dump the file content */ - av_dump_format(ic, nb_input_files, filename, 0); - - f = ALLOC_ARRAY_ELEM(input_files, nb_input_files); - - f->ctx = ic; - f->ist_index = nb_input_streams - ic->nb_streams; - f->start_time = o->start_time; - f->recording_time = o->recording_time; - f->input_sync_ref = o->input_sync_ref; - f->input_ts_offset = o->input_ts_offset; - f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); - f->nb_streams = ic->nb_streams; - f->rate_emu = o->rate_emu; - f->accurate_seek = o->accurate_seek; - f->loop = o->loop; - f->duration = 0; - f->time_base = (AVRational){ 1, 1 }; - - f->readrate = o->readrate ? o->readrate : 0.0; - if (f->readrate < 0.0f) { - av_log(NULL, AV_LOG_ERROR, "Option -readrate for Input #%d is %0.3f; it must be non-negative.\n", nb_input_files, f->readrate); - exit_program(1); - } - if (f->readrate && f->rate_emu) { - av_log(NULL, AV_LOG_WARNING, "Both -readrate and -re set for Input #%d. Using -readrate %0.3f.\n", nb_input_files, f->readrate); - f->rate_emu = 0; - } - - f->pkt = av_packet_alloc(); - if (!f->pkt) - exit_program(1); -#if HAVE_THREADS - f->thread_queue_size = o->thread_queue_size; -#endif - - /* check if all codec options have been used */ - unused_opts = strip_specifiers(o->g->codec_opts); - for (i = f->ist_index; i < nb_input_streams; i++) { - e = NULL; - while ((e = av_dict_get(input_streams[i]->decoder_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) - av_dict_set(&unused_opts, e->key, NULL, 0); - } - - e = NULL; - while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) { - const AVClass *class = avcodec_get_class(); - const AVOption *option = av_opt_find(&class, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - const AVClass *fclass = avformat_get_class(); - const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - if (!option || foption) - continue; - - - if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) { - av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " - "input file #%d (%s) is not a decoding option.\n", e->key, - option->help ? option->help : "", nb_input_files - 1, - filename); - exit_program(1); - } - - av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " - "input file #%d (%s) has not been used for any stream. The most " - "likely reason is either wrong type (e.g. a video option with " - "no video streams) or that it is a private option of some decoder " - "which was not actually used for any stream.\n", e->key, - option->help ? option->help : "", nb_input_files - 1, filename); - } - av_dict_free(&unused_opts); - - for (i = 0; i < o->nb_dump_attachment; i++) { - int j; - - for (j = 0; j < ic->nb_streams; j++) { - AVStream *st = ic->streams[j]; - - if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1) - dump_attachment(st, o->dump_attachment[i].u.str); - } - } - - input_stream_potentially_available = 1; - - return 0; -} - -char *get_line(AVIOContext *s, AVBPrint *bprint) -{ - char c; - - while ((c = avio_r8(s)) && c != '\n') - av_bprint_chars(bprint, c, 1); - - if (!av_bprint_is_complete(bprint)) { - av_log(NULL, AV_LOG_FATAL, "Could not alloc buffer for reading preset.\n"); - exit_program(1); - } - return bprint->str; -} - -int get_preset_file_2(const char *preset_name, const char *codec_name, AVIOContext **s) -{ - int i, ret = -1; - char filename[1000]; - char *env_avconv_datadir = getenv_utf8("AVCONV_DATADIR"); - char *env_home = getenv_utf8("HOME"); - const char *base[3] = { env_avconv_datadir, - env_home, - AVCONV_DATADIR, - }; - - for (i = 0; i < FF_ARRAY_ELEMS(base) && ret < 0; i++) { - if (!base[i]) - continue; - if (codec_name) { - snprintf(filename, sizeof(filename), "%s%s/%s-%s.avpreset", base[i], - i != 1 ? "" : "/.avconv", codec_name, preset_name); - ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); - } - if (ret < 0) { - snprintf(filename, sizeof(filename), "%s%s/%s.avpreset", base[i], - i != 1 ? "" : "/.avconv", preset_name); - ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); - } - } - freeenv_utf8(env_home); - freeenv_utf8(env_avconv_datadir); - return ret; -} - -int choose_encoder(OptionsContext *o, AVFormatContext *s, OutputStream *ost) -{ - enum AVMediaType type = ost->st->codecpar->codec_type; - char *codec_name = NULL; - - if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) { - MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st); - if (!codec_name) { - ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->url, - NULL, ost->st->codecpar->codec_type); - ost->enc = avcodec_find_encoder(ost->st->codecpar->codec_id); - if (!ost->enc) { - av_log(NULL, AV_LOG_FATAL, "Automatic encoder selection failed for " - "output stream #%d:%d. Default encoder for format %s (codec %s) is " - "probably disabled. Please choose an encoder manually.\n", - ost->file_index, ost->index, s->oformat->name, - avcodec_get_name(ost->st->codecpar->codec_id)); - return AVERROR_ENCODER_NOT_FOUND; - } - } else if (!strcmp(codec_name, "copy")) - ost->stream_copy = 1; - else { - ost->enc = find_codec_or_die(codec_name, ost->st->codecpar->codec_type, 1); - ost->st->codecpar->codec_id = ost->enc->id; - } - ost->encoding_needed = !ost->stream_copy; - } else { - /* no encoding supported for other media types */ - ost->stream_copy = 1; - ost->encoding_needed = 0; - } - - return 0; -} - -OutputStream *new_output_stream(OptionsContext *o, AVFormatContext *oc, enum AVMediaType type, int source_index) -{ - OutputStream *ost; - AVStream *st = avformat_new_stream(oc, NULL); - int idx = oc->nb_streams - 1, ret = 0; - const char *bsfs = NULL, *time_base = NULL; - char *next, *codec_tag = NULL; - double qscale = -1; - int i; - - if (!st) { - av_log(NULL, AV_LOG_FATAL, "Could not alloc stream.\n"); - exit_program(1); - } - - if (oc->nb_streams - 1 < o->nb_streamid_map) - st->id = o->streamid_map[oc->nb_streams - 1]; - - ost = ALLOC_ARRAY_ELEM(output_streams, nb_output_streams); - - ost->file_index = nb_output_files - 1; - ost->index = idx; - ost->st = st; - ost->forced_kf_ref_pts = AV_NOPTS_VALUE; - st->codecpar->codec_type = type; - - ret = choose_encoder(o, oc, ost); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "Error selecting an encoder for stream " - "%d:%d\n", ost->file_index, ost->index); - exit_program(1); - } - - ost->enc_ctx = avcodec_alloc_context3(ost->enc); - if (!ost->enc_ctx) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding context.\n"); - exit_program(1); - } - ost->enc_ctx->codec_type = type; - - ost->ref_par = avcodec_parameters_alloc(); - if (!ost->ref_par) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding parameters.\n"); - exit_program(1); - } - - ost->filtered_frame = av_frame_alloc(); - if (!ost->filtered_frame) - exit_program(1); - - ost->pkt = av_packet_alloc(); - if (!ost->pkt) - exit_program(1); - - if (ost->enc) { - AVIOContext *s = NULL; - char *buf = NULL, *arg = NULL, *preset = NULL; - - ost->encoder_opts = filter_codec_opts(o->g->codec_opts, ost->enc->id, oc, st, ost->enc); - - MATCH_PER_STREAM_OPT(presets, str, preset, oc, st); - ost->autoscale = 1; - MATCH_PER_STREAM_OPT(autoscale, i, ost->autoscale, oc, st); - if (preset && (!(ret = get_preset_file_2(preset, ost->enc->name, &s)))) { - AVBPrint bprint; - av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); - do { - av_bprint_clear(&bprint); - buf = get_line(s, &bprint); - if (!buf[0] || buf[0] == '#') - continue; - if (!(arg = strchr(buf, '='))) { - av_log(NULL, AV_LOG_FATAL, "Invalid line found in the preset file.\n"); - exit_program(1); - } - *arg++ = 0; - av_dict_set(&ost->encoder_opts, buf, arg, AV_DICT_DONT_OVERWRITE); - } while (!s->eof_reached); - av_bprint_finalize(&bprint, NULL); - avio_closep(&s); - } - if (ret) { - av_log(NULL, AV_LOG_FATAL, - "Preset %s specified for stream %d:%d, but could not be opened.\n", - preset, ost->file_index, ost->index); - exit_program(1); - } - } else { - ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL); - } - - - if (o->bitexact) - ost->enc_ctx->flags |= AV_CODEC_FLAG_BITEXACT; - - MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st); - if (time_base) { - AVRational q; - if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || - q.num <= 0 || q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); - exit_program(1); - } - st->time_base = q; - } - - MATCH_PER_STREAM_OPT(enc_time_bases, str, time_base, oc, st); - if (time_base) { - AVRational q; - if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || - q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); - exit_program(1); - } - ost->enc_timebase = q; - } - - ost->max_frames = INT64_MAX; - MATCH_PER_STREAM_OPT(max_frames, i64, ost->max_frames, oc, st); - for (i = 0; inb_max_frames; i++) { - char *p = o->max_frames[i].specifier; - if (!*p && type != AVMEDIA_TYPE_VIDEO) { - av_log(NULL, AV_LOG_WARNING, "Applying unspecific -frames to non video streams, maybe you meant -vframes ?\n"); - break; - } - } - - ost->copy_prior_start = -1; - MATCH_PER_STREAM_OPT(copy_prior_start, i, ost->copy_prior_start, oc ,st); - - MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st); - if (bsfs && *bsfs) { - ret = av_bsf_list_parse_str(bsfs, &ost->bsf_ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing bitstream filter sequence '%s': %s\n", bsfs, av_err2str(ret)); - exit_program(1); - } - } - - MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st); - if (codec_tag) { - uint32_t tag = strtol(codec_tag, &next, 0); - if (*next) - tag = AV_RL32(codec_tag); - ost->st->codecpar->codec_tag = - ost->enc_ctx->codec_tag = tag; - } - - MATCH_PER_STREAM_OPT(qscale, dbl, qscale, oc, st); - if (qscale >= 0) { - ost->enc_ctx->flags |= AV_CODEC_FLAG_QSCALE; - ost->enc_ctx->global_quality = FF_QP2LAMBDA * qscale; - } - - MATCH_PER_STREAM_OPT(disposition, str, ost->disposition, oc, st); - ost->disposition = av_strdup(ost->disposition); - - ost->max_muxing_queue_size = 128; - MATCH_PER_STREAM_OPT(max_muxing_queue_size, i, ost->max_muxing_queue_size, oc, st); - - ost->muxing_queue_data_size = 0; - - ost->muxing_queue_data_threshold = 50*1024*1024; - MATCH_PER_STREAM_OPT(muxing_queue_data_threshold, i, ost->muxing_queue_data_threshold, oc, st); - - MATCH_PER_STREAM_OPT(bits_per_raw_sample, i, ost->bits_per_raw_sample, - oc, st); - - if (oc->oformat->flags & AVFMT_GLOBALHEADER) - ost->enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - - av_dict_copy(&ost->sws_dict, o->g->sws_dict, 0); - - av_dict_copy(&ost->swr_opts, o->g->swr_opts, 0); - if (ost->enc && av_get_exact_bits_per_sample(ost->enc->id) == 24) - av_dict_set(&ost->swr_opts, "output_sample_bits", "24", 0); - - ost->source_index = source_index; - if (source_index >= 0) { - ost->sync_ist = input_streams[source_index]; - input_streams[source_index]->discard = 0; - input_streams[source_index]->st->discard = input_streams[source_index]->user_set_discard; - } - ost->last_mux_dts = AV_NOPTS_VALUE; - - ost->muxing_queue = av_fifo_alloc2(8, sizeof(AVPacket*), 0); - if (!ost->muxing_queue) - exit_program(1); - - MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, - ost->copy_initial_nonkeyframes, oc, st); - - return ost; -} - -void parse_matrix_coeffs(uint16_t *dest, const char *str) -{ - int i; - const char *p = str; - for (i = 0;; i++) { - dest[i] = atoi(p); - if (i == 63) - break; - p = strchr(p, ','); - if (!p) { - av_log(NULL, AV_LOG_FATAL, "Syntax error in matrix \"%s\" at coeff %d\n", str, i); - exit_program(1); - } - p++; - } -} - -/* read file contents into a string */ -char *fftools_read_file(const char *filename) -{ - AVIOContext *pb = NULL; - int ret = avio_open(&pb, filename, AVIO_FLAG_READ); - AVBPrint bprint; - char *str; - - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename); - return NULL; - } - - av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); - ret = avio_read_to_bprint(pb, &bprint, SIZE_MAX); - avio_closep(&pb); - if (ret < 0) { - av_bprint_finalize(&bprint, NULL); - return NULL; - } - ret = av_bprint_finalize(&bprint, &str); - if (ret < 0) - return NULL; - return str; -} - -char *get_ost_filters(OptionsContext *o, AVFormatContext *oc, - OutputStream *ost) -{ - AVStream *st = ost->st; - - if (ost->filters_script && ost->filters) { - av_log(NULL, AV_LOG_ERROR, "Both -filter and -filter_script set for " - "output stream #%d:%d.\n", nb_output_files, st->index); - exit_program(1); - } - - if (ost->filters_script) - return fftools_read_file(ost->filters_script); - else if (ost->filters) - return av_strdup(ost->filters); - - return av_strdup(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? - "null" : "anull"); -} - -void check_streamcopy_filters(OptionsContext *o, AVFormatContext *oc, - const OutputStream *ost, enum AVMediaType type) -{ - if (ost->filters_script || ost->filters) { - av_log(NULL, AV_LOG_ERROR, - "%s '%s' was defined for %s output stream %d:%d but codec copy was selected.\n" - "Filtering and streamcopy cannot be used together.\n", - ost->filters ? "Filtergraph" : "Filtergraph script", - ost->filters ? ost->filters : ost->filters_script, - av_get_media_type_string(type), ost->file_index, ost->index); - exit_program(1); - } -} - -OutputStream *new_video_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - AVStream *st; - OutputStream *ost; - AVCodecContext *video_enc; - char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_VIDEO, source_index); - st = ost->st; - video_enc = ost->enc_ctx; - - MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st); - if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(max_frame_rates, str, max_frame_rate, oc, st); - if (max_frame_rate && av_parse_video_rate(&ost->max_frame_rate, max_frame_rate) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid maximum framerate value: %s\n", max_frame_rate); - exit_program(1); - } - - if (frame_rate && max_frame_rate) { - av_log(NULL, AV_LOG_ERROR, "Only one of -fpsmax and -r can be set for a stream.\n"); - exit_program(1); - } - - if ((frame_rate || max_frame_rate) && - video_sync_method == VSYNC_PASSTHROUGH) - av_log(NULL, AV_LOG_ERROR, "Using -vsync passthrough and -r/-fpsmax can produce invalid output files\n"); - - MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st); - if (frame_aspect_ratio) { - AVRational q; - if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 || - q.num <= 0 || q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid aspect ratio: %s\n", frame_aspect_ratio); - exit_program(1); - } - ost->frame_aspect_ratio = q; - } - - MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); - MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); - - if (!ost->stream_copy) { - const char *p = NULL; - char *frame_size = NULL; - char *frame_pix_fmt = NULL; - char *intra_matrix = NULL, *inter_matrix = NULL; - char *chroma_intra_matrix = NULL; - int do_pass = 0; - int i; - - MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); - if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st); - if (frame_pix_fmt && *frame_pix_fmt == '+') { - ost->keep_pix_fmt = 1; - if (!*++frame_pix_fmt) - frame_pix_fmt = NULL; - } - if (frame_pix_fmt && (video_enc->pix_fmt = av_get_pix_fmt(frame_pix_fmt)) == AV_PIX_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Unknown pixel format requested: %s.\n", frame_pix_fmt); - exit_program(1); - } - st->sample_aspect_ratio = video_enc->sample_aspect_ratio; - - MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st); - if (intra_matrix) { - if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64))) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n"); - exit_program(1); - } - parse_matrix_coeffs(video_enc->intra_matrix, intra_matrix); - } - MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st); - if (chroma_intra_matrix) { - uint16_t *p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64); - if (!p) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n"); - exit_program(1); - } - video_enc->chroma_intra_matrix = p; - parse_matrix_coeffs(p, chroma_intra_matrix); - } - MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st); - if (inter_matrix) { - if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64))) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for inter matrix.\n"); - exit_program(1); - } - parse_matrix_coeffs(video_enc->inter_matrix, inter_matrix); - } - - MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st); - for (i = 0; p; i++) { - int start, end, q; - int e = sscanf(p, "%d,%d,%d", &start, &end, &q); - if (e != 3) { - av_log(NULL, AV_LOG_FATAL, "error parsing rc_override\n"); - exit_program(1); - } - video_enc->rc_override = - av_realloc_array(video_enc->rc_override, - i + 1, sizeof(RcOverride)); - if (!video_enc->rc_override) { - av_log(NULL, AV_LOG_FATAL, "Could not (re)allocate memory for rc_override.\n"); - exit_program(1); - } - video_enc->rc_override[i].start_frame = start; - video_enc->rc_override[i].end_frame = end; - if (q > 0) { - video_enc->rc_override[i].qscale = q; - video_enc->rc_override[i].quality_factor = 1.0; - } - else { - video_enc->rc_override[i].qscale = 0; - video_enc->rc_override[i].quality_factor = -q/100.0; - } - p = strchr(p, '/'); - if (p) p++; - } - video_enc->rc_override_count = i; - - if (do_psnr) - video_enc->flags|= AV_CODEC_FLAG_PSNR; - - /* two pass mode */ - MATCH_PER_STREAM_OPT(pass, i, do_pass, oc, st); - if (do_pass) { - if (do_pass & 1) { - video_enc->flags |= AV_CODEC_FLAG_PASS1; - av_dict_set(&ost->encoder_opts, "flags", "+pass1", AV_DICT_APPEND); - } - if (do_pass & 2) { - video_enc->flags |= AV_CODEC_FLAG_PASS2; - av_dict_set(&ost->encoder_opts, "flags", "+pass2", AV_DICT_APPEND); - } - } - - MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st); - if (ost->logfile_prefix && - !(ost->logfile_prefix = av_strdup(ost->logfile_prefix))) - exit_program(1); - - if (do_pass) { - char logfilename[1024]; - FILE *f; - - snprintf(logfilename, sizeof(logfilename), "%s-%d.log", - ost->logfile_prefix ? ost->logfile_prefix : - DEFAULT_PASS_LOGFILENAME_PREFIX, - nb_output_streams - 1); - if (!strcmp(ost->enc->name, "libx264")) { - av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE); - } else { - if (video_enc->flags & AV_CODEC_FLAG_PASS2) { - char *logbuffer = fftools_read_file(logfilename); - - if (!logbuffer) { - av_log(NULL, AV_LOG_FATAL, "Error reading log file '%s' for pass-2 encoding\n", - logfilename); - exit_program(1); - } - video_enc->stats_in = logbuffer; - } - if (video_enc->flags & AV_CODEC_FLAG_PASS1) { - f = fopen_utf8(logfilename, "wb"); - if (!f) { - av_log(NULL, AV_LOG_FATAL, - "Cannot write log file '%s' for pass-1 encoding: %s\n", - logfilename, strerror(errno)); - exit_program(1); - } - ost->logfile = f; - } - } - } - - MATCH_PER_STREAM_OPT(forced_key_frames, str, ost->forced_keyframes, oc, st); - if (ost->forced_keyframes) - ost->forced_keyframes = av_strdup(ost->forced_keyframes); - - MATCH_PER_STREAM_OPT(force_fps, i, ost->force_fps, oc, st); - - ost->top_field_first = -1; - MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st); - - ost->vsync_method = video_sync_method; - MATCH_PER_STREAM_OPT(fps_mode, str, ost->fps_mode, oc, st); - if (ost->fps_mode) - parse_and_set_vsync(ost->fps_mode, &ost->vsync_method, ost->file_index, ost->index, 0); - - if (ost->vsync_method == VSYNC_AUTO) { - if (!strcmp(oc->oformat->name, "avi")) { - ost->vsync_method = VSYNC_VFR; - } else { - ost->vsync_method = (oc->oformat->flags & AVFMT_VARIABLE_FPS) ? - ((oc->oformat->flags & AVFMT_NOTIMESTAMPS) ? - VSYNC_PASSTHROUGH : VSYNC_VFR) : - VSYNC_CFR; - } - - if (ost->source_index >= 0 && ost->vsync_method == VSYNC_CFR) { - const InputStream *ist = input_streams[ost->source_index]; - const InputFile *ifile = input_files[ist->file_index]; - - if (ifile->nb_streams == 1 && ifile->input_ts_offset == 0) - ost->vsync_method = VSYNC_VSCFR; - } - - if (ost->vsync_method == VSYNC_CFR && copy_ts) { - ost->vsync_method = VSYNC_VSCFR; - } - } - ost->is_cfr = (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR); - - ost->avfilter = get_ost_filters(o, oc, ost); - if (!ost->avfilter) - exit_program(1); - - ost->last_frame = av_frame_alloc(); - if (!ost->last_frame) - exit_program(1); - } - - if (ost->stream_copy) - check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_VIDEO); - - return ost; -} - -OutputStream *new_audio_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - int n; - AVStream *st; - OutputStream *ost; - AVCodecContext *audio_enc; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_AUDIO, source_index); - st = ost->st; - - audio_enc = ost->enc_ctx; - audio_enc->codec_type = AVMEDIA_TYPE_AUDIO; - - MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); - MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); - - if (!ost->stream_copy) { - int channels = 0; - char *layout = NULL; - char *sample_fmt = NULL; - - MATCH_PER_STREAM_OPT(audio_channels, i, channels, oc, st); - if (channels) { - audio_enc->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC; - audio_enc->ch_layout.nb_channels = channels; - } - - MATCH_PER_STREAM_OPT(audio_ch_layouts, str, layout, oc, st); - if (layout) { - if (av_channel_layout_from_string(&audio_enc->ch_layout, layout) < 0) { -#if FF_API_OLD_CHANNEL_LAYOUT - uint64_t mask; - AV_NOWARN_DEPRECATED({ - mask = av_get_channel_layout(layout); - }) - if (!mask) { -#endif - av_log(NULL, AV_LOG_FATAL, "Unknown channel layout: %s\n", layout); - exit_program(1); -#if FF_API_OLD_CHANNEL_LAYOUT - } - av_log(NULL, AV_LOG_WARNING, "Channel layout '%s' uses a deprecated syntax.\n", - layout); - av_channel_layout_from_mask(&audio_enc->ch_layout, mask); -#endif - } - } - - MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st); - if (sample_fmt && - (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st); - - MATCH_PER_STREAM_OPT(apad, str, ost->apad, oc, st); - ost->apad = av_strdup(ost->apad); - - ost->avfilter = get_ost_filters(o, oc, ost); - if (!ost->avfilter) - exit_program(1); - - /* check for channel mapping for this audio stream */ - for (n = 0; n < o->nb_audio_channel_maps; n++) { - AudioChannelMap *map = &o->audio_channel_maps[n]; - if ((map->ofile_idx == -1 || ost->file_index == map->ofile_idx) && - (map->ostream_idx == -1 || ost->st->index == map->ostream_idx)) { - InputStream *ist; - - if (map->channel_idx == -1) { - ist = NULL; - } else if (ost->source_index < 0) { - av_log(NULL, AV_LOG_FATAL, "Cannot determine input stream for channel mapping %d.%d\n", - ost->file_index, ost->st->index); - continue; - } else { - ist = input_streams[ost->source_index]; - } - - if (!ist || (ist->file_index == map->file_idx && ist->st->index == map->stream_idx)) { - if (av_reallocp_array(&ost->audio_channels_map, - ost->audio_channels_mapped + 1, - sizeof(*ost->audio_channels_map) - ) < 0 ) - exit_program(1); - - ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx; - } - } - } - } - - if (ost->stream_copy) - check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_AUDIO); - - return ost; -} - -OutputStream *new_data_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_DATA, source_index); - if (!ost->stream_copy) { - av_log(NULL, AV_LOG_FATAL, "Data stream encoding not supported yet (only streamcopy)\n"); - exit_program(1); - } - - return ost; -} - -OutputStream *new_unknown_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_UNKNOWN, source_index); - if (!ost->stream_copy) { - av_log(NULL, AV_LOG_FATAL, "Unknown stream encoding not supported yet (only streamcopy)\n"); - exit_program(1); - } - - return ost; -} - -OutputStream *new_attachment_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost = new_output_stream(o, oc, AVMEDIA_TYPE_ATTACHMENT, source_index); - ost->stream_copy = 1; - ost->finished = 1; - return ost; -} - -OutputStream *new_subtitle_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - AVStream *st; - OutputStream *ost; - AVCodecContext *subtitle_enc; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_SUBTITLE, source_index); - st = ost->st; - subtitle_enc = ost->enc_ctx; - - subtitle_enc->codec_type = AVMEDIA_TYPE_SUBTITLE; - - if (!ost->stream_copy) { - char *frame_size = NULL; - - MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); - if (frame_size && av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); - exit_program(1); - } - } - - return ost; -} - -/* arg format is "output-stream-index:streamid-value". */ -int opt_streamid(void *optctx, const char *opt, const char *arg) -{ - OptionsContext *o = optctx; - int idx; - char *p; - char idx_str[16]; - - av_strlcpy(idx_str, arg, sizeof(idx_str)); - p = strchr(idx_str, ':'); - if (!p) { - av_log(NULL, AV_LOG_FATAL, - "Invalid value '%s' for option '%s', required syntax is 'index:value'\n", - arg, opt); - exit_program(1); - } - *p++ = '\0'; - idx = parse_number_or_die(opt, idx_str, OPT_INT, 0, MAX_STREAMS-1); - o->streamid_map = grow_array(o->streamid_map, sizeof(*o->streamid_map), &o->nb_streamid_map, idx+1); - o->streamid_map[idx] = parse_number_or_die(opt, p, OPT_INT, 0, INT_MAX); - return 0; -} - -int copy_chapters(InputFile *ifile, OutputFile *ofile, AVFormatContext *os, - int copy_metadata) -{ - AVFormatContext *is = ifile->ctx; - AVChapter **tmp; - int i; - - tmp = av_realloc_f(os->chapters, is->nb_chapters + os->nb_chapters, sizeof(*os->chapters)); - if (!tmp) - return AVERROR(ENOMEM); - os->chapters = tmp; - - for (i = 0; i < is->nb_chapters; i++) { - AVChapter *in_ch = is->chapters[i], *out_ch; - int64_t start_time = (ofile->start_time == AV_NOPTS_VALUE) ? 0 : ofile->start_time; - int64_t ts_off = av_rescale_q(start_time - ifile->ts_offset, - AV_TIME_BASE_Q, in_ch->time_base); - int64_t rt = (ofile->recording_time == INT64_MAX) ? INT64_MAX : - av_rescale_q(ofile->recording_time, AV_TIME_BASE_Q, in_ch->time_base); - - - if (in_ch->end < ts_off) - continue; - if (rt != INT64_MAX && in_ch->start > rt + ts_off) - break; - - out_ch = av_mallocz(sizeof(AVChapter)); - if (!out_ch) - return AVERROR(ENOMEM); - - out_ch->id = in_ch->id; - out_ch->time_base = in_ch->time_base; - out_ch->start = FFMAX(0, in_ch->start - ts_off); - out_ch->end = FFMIN(rt, in_ch->end - ts_off); - - if (copy_metadata) - av_dict_copy(&out_ch->metadata, in_ch->metadata, 0); - - os->chapters[os->nb_chapters++] = out_ch; - } - return 0; -} - -int set_dispositions(OutputFile *of, AVFormatContext *ctx) -{ - int nb_streams[AVMEDIA_TYPE_NB] = { 0 }; - int have_default[AVMEDIA_TYPE_NB] = { 0 }; - int have_manual = 0; - - // first, copy the input dispositions - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - - nb_streams[ost->st->codecpar->codec_type]++; - - have_manual |= !!ost->disposition; - - if (ost->source_index >= 0) { - ost->st->disposition = input_streams[ost->source_index]->st->disposition; - - if (ost->st->disposition & AV_DISPOSITION_DEFAULT) - have_default[ost->st->codecpar->codec_type] = 1; - } - } - - if (have_manual) { - // process manually set dispositions - they override the above copy - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - int ret; - - if (!ost->disposition) - continue; - -#if LIBAVFORMAT_VERSION_MAJOR >= 60 - ret = av_opt_set(ost->st, "disposition", ost->disposition, 0); -#else - { - const AVClass *class = av_stream_get_class(); - const AVOption *o = av_opt_find(&class, "disposition", NULL, 0, AV_OPT_SEARCH_FAKE_OBJ); - - av_assert0(o); - ret = av_opt_eval_flags(&class, o, ost->disposition, &ost->st->disposition); - } -#endif - - if (ret < 0) - return ret; - } - } else { - // For each media type with more than one stream, find a suitable stream to - // mark as default, unless one is already marked default. - // "Suitable" means the first of that type, skipping attached pictures. - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - enum AVMediaType type = ost->st->codecpar->codec_type; - - if (nb_streams[type] < 2 || have_default[type] || - ost->st->disposition & AV_DISPOSITION_ATTACHED_PIC) - continue; - - ost->st->disposition |= AV_DISPOSITION_DEFAULT; - have_default[type] = 1; - } - } - - return 0; -} - -void init_output_filter(OutputFilter *ofilter, OptionsContext *o, - AVFormatContext *oc) -{ - OutputStream *ost; - - switch (ofilter->type) { - case AVMEDIA_TYPE_VIDEO: ost = new_video_stream(o, oc, -1); break; - case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream(o, oc, -1); break; - default: - av_log(NULL, AV_LOG_FATAL, "Only video and audio filters are supported " - "currently.\n"); - exit_program(1); - } - - ost->filter = ofilter; - - ofilter->ost = ost; - ofilter->format = -1; - - if (ost->stream_copy) { - av_log(NULL, AV_LOG_ERROR, "Streamcopy requested for output stream %d:%d, " - "which is fed from a complex filtergraph. Filtering and streamcopy " - "cannot be used together.\n", ost->file_index, ost->index); - exit_program(1); - } - - if (ost->avfilter && (ost->filters || ost->filters_script)) { - const char *opt = ost->filters ? "-vf/-af/-filter" : "-filter_script"; - av_log(NULL, AV_LOG_ERROR, - "%s '%s' was specified through the %s option " - "for output stream %d:%d, which is fed from a complex filtergraph.\n" - "%s and -filter_complex cannot be used together for the same stream.\n", - ost->filters ? "Filtergraph" : "Filtergraph script", - ost->filters ? ost->filters : ost->filters_script, - opt, ost->file_index, ost->index, opt); - exit_program(1); - } - - avfilter_inout_free(&ofilter->out_tmp); -} - -int init_complex_filters(void) -{ - int i, ret = 0; - - for (i = 0; i < nb_filtergraphs; i++) { - ret = init_complex_filtergraph(filtergraphs[i]); - if (ret < 0) - return ret; - } - return 0; -} - -void set_channel_layout(OutputFilter *f, OutputStream *ost) -{ - int i, err; - - if (ost->enc_ctx->ch_layout.order != AV_CHANNEL_ORDER_UNSPEC) { - /* Pass the layout through for all orders but UNSPEC */ - err = av_channel_layout_copy(&f->ch_layout, &ost->enc_ctx->ch_layout); - if (err < 0) - exit_program(1); - return; - } - - /* Requested layout is of order UNSPEC */ - if (!ost->enc->ch_layouts) { - /* Use the default native layout for the requested amount of channels when the - encoder doesn't have a list of supported layouts */ - av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); - return; - } - /* Encoder has a list of supported layouts. Pick the first layout in it with the - same amount of channels as the requested layout */ - for (i = 0; ost->enc->ch_layouts[i].nb_channels; i++) { - if (ost->enc->ch_layouts[i].nb_channels == ost->enc_ctx->ch_layout.nb_channels) - break; - } - if (ost->enc->ch_layouts[i].nb_channels) { - /* Use it if one is found */ - err = av_channel_layout_copy(&f->ch_layout, &ost->enc->ch_layouts[i]); - if (err < 0) - exit_program(1); - return; - } - /* If no layout for the amount of channels requested was found, use the default - native layout for it. */ - av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); -} - -int open_output_file(OptionsContext *o, const char *filename) -{ - AVFormatContext *oc; - int i, j, err; - OutputFile *of; - OutputStream *ost; - InputStream *ist; - AVDictionary *unused_opts = NULL; - const AVDictionaryEntry *e = NULL; - - if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) { - o->stop_time = INT64_MAX; - av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); - } - - if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) { - int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; - if (o->stop_time <= start_time) { - av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); - exit_program(1); - } else { - o->recording_time = o->stop_time - start_time; - } - } - - of = ALLOC_ARRAY_ELEM(output_files, nb_output_files); - - of->index = nb_output_files - 1; - of->ost_index = nb_output_streams; - of->recording_time = o->recording_time; - of->start_time = o->start_time; - of->limit_filesize = o->limit_filesize; - of->shortest = o->shortest; - av_dict_copy(&of->opts, o->g->format_opts, 0); - - if (!strcmp(filename, "-")) - filename = "pipe:"; - - err = avformat_alloc_output_context2(&oc, NULL, o->format, filename); - if (!oc) { - print_error(filename, err); - exit_program(1); - } - - of->ctx = oc; - of->format = oc->oformat; - if (o->recording_time != INT64_MAX) - oc->duration = o->recording_time; - - oc->interrupt_callback = int_cb; - - if (o->bitexact) { - oc->flags |= AVFMT_FLAG_BITEXACT; - } - - /* create streams for all unlabeled output pads */ - for (i = 0; i < nb_filtergraphs; i++) { - FilterGraph *fg = filtergraphs[i]; - for (j = 0; j < fg->nb_outputs; j++) { - OutputFilter *ofilter = fg->outputs[j]; - - if (!ofilter->out_tmp || ofilter->out_tmp->name) - continue; - - switch (ofilter->type) { - case AVMEDIA_TYPE_VIDEO: o->video_disable = 1; break; - case AVMEDIA_TYPE_AUDIO: o->audio_disable = 1; break; - case AVMEDIA_TYPE_SUBTITLE: o->subtitle_disable = 1; break; - } - init_output_filter(ofilter, o, oc); - } - } - - if (!o->nb_stream_maps) { - char *subtitle_codec_name = NULL; - /* pick the "best" stream of each type */ - - /* video: highest resolution */ - if (!o->video_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_VIDEO) != AV_CODEC_ID_NONE) { - int best_score = 0, idx = -1; - int qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0); - for (j = 0; j < nb_input_files; j++) { - InputFile *ifile = input_files[j]; - int file_best_score = 0, file_best_idx = -1; - for (i = 0; i < ifile->nb_streams; i++) { - int score; - ist = input_streams[ifile->ist_index + i]; - score = ist->st->codecpar->width * ist->st->codecpar->height - + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) - + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); - if (ist->user_set_discard == AVDISCARD_ALL) - continue; - if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - score = 1; - if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && - score > file_best_score) { - if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - continue; - file_best_score = score; - file_best_idx = ifile->ist_index + i; - } - } - if (file_best_idx >= 0) { - if((qcr == MKTAG('A', 'P', 'I', 'C')) || !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - file_best_score -= 5000000*!!(input_streams[file_best_idx]->st->disposition & AV_DISPOSITION_DEFAULT); - if (file_best_score > best_score) { - best_score = file_best_score; - idx = file_best_idx; - } - } - } - if (idx >= 0) - new_video_stream(o, oc, idx); - } - - /* audio: most channels */ - if (!o->audio_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_AUDIO) != AV_CODEC_ID_NONE) { - int best_score = 0, idx = -1; - for (j = 0; j < nb_input_files; j++) { - InputFile *ifile = input_files[j]; - int file_best_score = 0, file_best_idx = -1; - for (i = 0; i < ifile->nb_streams; i++) { - int score; - ist = input_streams[ifile->ist_index + i]; - score = ist->st->codecpar->ch_layout.nb_channels - + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) - + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); - if (ist->user_set_discard == AVDISCARD_ALL) - continue; - if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && - score > file_best_score) { - file_best_score = score; - file_best_idx = ifile->ist_index + i; - } - } - if (file_best_idx >= 0) { - file_best_score -= 5000000*!!(input_streams[file_best_idx]->st->disposition & AV_DISPOSITION_DEFAULT); - if (file_best_score > best_score) { - best_score = file_best_score; - idx = file_best_idx; - } - } - } - if (idx >= 0) - new_audio_stream(o, oc, idx); - } - - /* subtitles: pick first */ - MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s"); - if (!o->subtitle_disable && (avcodec_find_encoder(oc->oformat->subtitle_codec) || subtitle_codec_name)) { - for (i = 0; i < nb_input_streams; i++) - if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { - AVCodecDescriptor const *input_descriptor = - avcodec_descriptor_get(input_streams[i]->st->codecpar->codec_id); - AVCodecDescriptor const *output_descriptor = NULL; - AVCodec const *output_codec = - avcodec_find_encoder(oc->oformat->subtitle_codec); - int input_props = 0, output_props = 0; - if (input_streams[i]->user_set_discard == AVDISCARD_ALL) - continue; - if (output_codec) - output_descriptor = avcodec_descriptor_get(output_codec->id); - if (input_descriptor) - input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (output_descriptor) - output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (subtitle_codec_name || - input_props & output_props || - // Map dvb teletext which has neither property to any output subtitle encoder - (input_descriptor && output_descriptor && - (!input_descriptor->props || - !output_descriptor->props))) { - new_subtitle_stream(o, oc, i); - break; - } - } - } - /* Data only if codec id match */ - if (!o->data_disable ) { - enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_DATA); - for (i = 0; codec_id != AV_CODEC_ID_NONE && i < nb_input_streams; i++) { - if (input_streams[i]->user_set_discard == AVDISCARD_ALL) - continue; - if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_DATA - && input_streams[i]->st->codecpar->codec_id == codec_id ) - new_data_stream(o, oc, i); - } - } - } else { - for (i = 0; i < o->nb_stream_maps; i++) { - StreamMap *map = &o->stream_maps[i]; - - if (map->disabled) - continue; - - if (map->linklabel) { - FilterGraph *fg; - OutputFilter *ofilter = NULL; - int j, k; - - for (j = 0; j < nb_filtergraphs; j++) { - fg = filtergraphs[j]; - for (k = 0; k < fg->nb_outputs; k++) { - AVFilterInOut *out = fg->outputs[k]->out_tmp; - if (out && !strcmp(out->name, map->linklabel)) { - ofilter = fg->outputs[k]; - goto loop_end; - } - } - } -loop_end: - if (!ofilter) { - av_log(NULL, AV_LOG_FATAL, "Output with label '%s' does not exist " - "in any defined filter graph, or was already used elsewhere.\n", map->linklabel); - exit_program(1); - } - init_output_filter(ofilter, o, oc); - } else { - int src_idx = input_files[map->file_index]->ist_index + map->stream_index; - - ist = input_streams[input_files[map->file_index]->ist_index + map->stream_index]; - if (ist->user_set_discard == AVDISCARD_ALL) { - av_log(NULL, AV_LOG_FATAL, "Stream #%d:%d is disabled and cannot be mapped.\n", - map->file_index, map->stream_index); - exit_program(1); - } - if(o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) - continue; - if(o-> audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - continue; - if(o-> video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) - continue; - if(o-> data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA) - continue; - - ost = NULL; - switch (ist->st->codecpar->codec_type) { - case AVMEDIA_TYPE_VIDEO: ost = new_video_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_SUBTITLE: ost = new_subtitle_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_DATA: ost = new_data_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_ATTACHMENT: ost = new_attachment_stream(o, oc, src_idx); break; - case AVMEDIA_TYPE_UNKNOWN: - if (copy_unknown_streams) { - ost = new_unknown_stream (o, oc, src_idx); - break; - } - default: - av_log(NULL, ignore_unknown_streams ? AV_LOG_WARNING : AV_LOG_FATAL, - "Cannot map stream #%d:%d - unsupported type.\n", - map->file_index, map->stream_index); - if (!ignore_unknown_streams) { - av_log(NULL, AV_LOG_FATAL, - "If you want unsupported types ignored instead " - "of failing, please use the -ignore_unknown option\n" - "If you want them copied, please use -copy_unknown\n"); - exit_program(1); - } - } - if (ost) - ost->sync_ist = input_streams[ input_files[map->sync_file_index]->ist_index - + map->sync_stream_index]; - } - } - } - - /* handle attached files */ - for (i = 0; i < o->nb_attachments; i++) { - AVIOContext *pb; - uint8_t *attachment; - const char *p; - int64_t len; - - if ((err = avio_open2(&pb, o->attachments[i], AVIO_FLAG_READ, &int_cb, NULL)) < 0) { - av_log(NULL, AV_LOG_FATAL, "Could not open attachment file %s.\n", - o->attachments[i]); - exit_program(1); - } - if ((len = avio_size(pb)) <= 0) { - av_log(NULL, AV_LOG_FATAL, "Could not get size of the attachment %s.\n", - o->attachments[i]); - exit_program(1); - } - if (len > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE || - !(attachment = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE))) { - av_log(NULL, AV_LOG_FATAL, "Attachment %s too large.\n", - o->attachments[i]); - exit_program(1); - } - avio_read(pb, attachment, len); - memset(attachment + len, 0, AV_INPUT_BUFFER_PADDING_SIZE); - - ost = new_attachment_stream(o, oc, -1); - ost->stream_copy = 0; - ost->attachment_filename = o->attachments[i]; - ost->st->codecpar->extradata = attachment; - ost->st->codecpar->extradata_size = len; - - p = strrchr(o->attachments[i], '/'); - av_dict_set(&ost->st->metadata, "filename", (p && *p) ? p + 1 : o->attachments[i], AV_DICT_DONT_OVERWRITE); - avio_closep(&pb); - } - - if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) { - av_dump_format(oc, nb_output_files - 1, oc->url, 1); - av_log(NULL, AV_LOG_ERROR, "Output file #%d does not contain any stream\n", nb_output_files - 1); - exit_program(1); - } - - /* check if all codec options have been used */ - unused_opts = strip_specifiers(o->g->codec_opts); - for (i = of->ost_index; i < nb_output_streams; i++) { - e = NULL; - while ((e = av_dict_get(output_streams[i]->encoder_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) - av_dict_set(&unused_opts, e->key, NULL, 0); - } - - e = NULL; - while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) { - const AVClass *class = avcodec_get_class(); - const AVOption *option = av_opt_find(&class, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - const AVClass *fclass = avformat_get_class(); - const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - if (!option || foption) - continue; - - - if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) { - av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " - "output file #%d (%s) is not an encoding option.\n", e->key, - option->help ? option->help : "", nb_output_files - 1, - filename); - exit_program(1); - } - - // gop_timecode is injected by generic code but not always used - if (!strcmp(e->key, "gop_timecode")) - continue; - - av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " - "output file #%d (%s) has not been used for any stream. The most " - "likely reason is either wrong type (e.g. a video option with " - "no video streams) or that it is a private option of some encoder " - "which was not actually used for any stream.\n", e->key, - option->help ? option->help : "", nb_output_files - 1, filename); - } - av_dict_free(&unused_opts); - - /* set the decoding_needed flags and create simple filtergraphs */ - for (i = of->ost_index; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (ost->encoding_needed && ost->source_index >= 0) { - InputStream *ist = input_streams[ost->source_index]; - ist->decoding_needed |= DECODING_FOR_OST; - ist->processing_needed = 1; - - if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || - ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - err = init_simple_filtergraph(ist, ost); - if (err < 0) { - av_log(NULL, AV_LOG_ERROR, - "Error initializing a simple filtergraph between streams " - "%d:%d->%d:%d\n", ist->file_index, ost->source_index, - nb_output_files - 1, ost->st->index); - exit_program(1); - } - } - } else if (ost->stream_copy && ost->source_index >= 0) { - InputStream *ist = input_streams[ost->source_index]; - ist->processing_needed = 1; - } - - /* set the filter output constraints */ - if (ost->filter) { - OutputFilter *f = ost->filter; - switch (ost->enc_ctx->codec_type) { - case AVMEDIA_TYPE_VIDEO: - f->frame_rate = ost->frame_rate; - f->width = ost->enc_ctx->width; - f->height = ost->enc_ctx->height; - if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { - f->format = ost->enc_ctx->pix_fmt; - } else { - f->formats = ost->enc->pix_fmts; - } - break; - case AVMEDIA_TYPE_AUDIO: - if (ost->enc_ctx->sample_fmt != AV_SAMPLE_FMT_NONE) { - f->format = ost->enc_ctx->sample_fmt; - } else { - f->formats = ost->enc->sample_fmts; - } - if (ost->enc_ctx->sample_rate) { - f->sample_rate = ost->enc_ctx->sample_rate; - } else { - f->sample_rates = ost->enc->supported_samplerates; - } - if (ost->enc_ctx->ch_layout.nb_channels) { - set_channel_layout(f, ost); - } else if (ost->enc->ch_layouts) { - f->ch_layouts = ost->enc->ch_layouts; - } - break; - } - } - } - - /* check filename in case of an image number is expected */ - if (oc->oformat->flags & AVFMT_NEEDNUMBER) { - if (!av_filename_number_test(oc->url)) { - print_error(oc->url, AVERROR(EINVAL)); - exit_program(1); - } - } - - if (!(oc->oformat->flags & AVFMT_NOSTREAMS) && !input_stream_potentially_available) { - av_log(NULL, AV_LOG_ERROR, - "No input streams but output needs an input stream\n"); - exit_program(1); - } - - if (!(oc->oformat->flags & AVFMT_NOFILE)) { - /* test if it already exists to avoid losing precious files */ - assert_file_overwrite(filename); - - /* open the file */ - if ((err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, - &oc->interrupt_callback, - &of->opts)) < 0) { - print_error(filename, err); - exit_program(1); - } - } else if (strcmp(oc->oformat->name, "image2")==0 && !av_filename_number_test(filename)) - assert_file_overwrite(filename); - - if (o->mux_preload) { - av_dict_set_int(&of->opts, "preload", o->mux_preload*AV_TIME_BASE, 0); - } - oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); - - /* copy metadata */ - for (i = 0; i < o->nb_metadata_map; i++) { - char *p; - int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0); - - if (in_file_index >= nb_input_files) { - av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d while processing metadata maps\n", in_file_index); - exit_program(1); - } - fftools_copy_metadata(o->metadata_map[i].specifier, *p ? p + 1 : p, oc, - in_file_index >= 0 ? - input_files[in_file_index]->ctx : NULL, o); - } - - /* copy chapters */ - if (o->chapters_input_file >= nb_input_files) { - if (o->chapters_input_file == INT_MAX) { - /* copy chapters from the first input file that has them*/ - o->chapters_input_file = -1; - for (i = 0; i < nb_input_files; i++) - if (input_files[i]->ctx->nb_chapters) { - o->chapters_input_file = i; - break; - } - } else { - av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d in chapter mapping.\n", - o->chapters_input_file); - exit_program(1); - } - } - if (o->chapters_input_file >= 0) - copy_chapters(input_files[o->chapters_input_file], of, oc, - !o->metadata_chapters_manual); - - /* copy global metadata by default */ - if (!o->metadata_global_manual && nb_input_files){ - av_dict_copy(&oc->metadata, input_files[0]->ctx->metadata, - AV_DICT_DONT_OVERWRITE); - if(o->recording_time != INT64_MAX) - av_dict_set(&oc->metadata, "duration", NULL, 0); - av_dict_set(&oc->metadata, "creation_time", NULL, 0); - av_dict_set(&oc->metadata, "company_name", NULL, 0); - av_dict_set(&oc->metadata, "product_name", NULL, 0); - av_dict_set(&oc->metadata, "product_version", NULL, 0); - } - if (!o->metadata_streams_manual) - for (i = of->ost_index; i < nb_output_streams; i++) { - InputStream *ist; - if (output_streams[i]->source_index < 0) /* this is true e.g. for attached files */ - continue; - ist = input_streams[output_streams[i]->source_index]; - av_dict_copy(&output_streams[i]->st->metadata, ist->st->metadata, AV_DICT_DONT_OVERWRITE); - if (!output_streams[i]->stream_copy) { - av_dict_set(&output_streams[i]->st->metadata, "encoder", NULL, 0); - } - } - - /* process manually set programs */ - for (i = 0; i < o->nb_program; i++) { - const char *p = o->program[i].u.str; - int progid = i+1; - AVProgram *program; - - while(*p) { - const char *p2 = av_get_token(&p, ":"); - const char *to_dealloc = p2; - char *key; - if (!p2) - break; - - if(*p) p++; - - key = av_get_token(&p2, "="); - if (!key || !*p2) { - av_freep(&to_dealloc); - av_freep(&key); - break; - } - p2++; - - if (!strcmp(key, "program_num")) - progid = strtol(p2, NULL, 0); - av_freep(&to_dealloc); - av_freep(&key); - } - - program = av_new_program(oc, progid); - - p = o->program[i].u.str; - while(*p) { - const char *p2 = av_get_token(&p, ":"); - const char *to_dealloc = p2; - char *key; - if (!p2) - break; - if(*p) p++; - - key = av_get_token(&p2, "="); - if (!key) { - av_log(NULL, AV_LOG_FATAL, - "No '=' character in program string %s.\n", - p2); - exit_program(1); - } - if (!*p2) - exit_program(1); - p2++; - - if (!strcmp(key, "title")) { - av_dict_set(&program->metadata, "title", p2, 0); - } else if (!strcmp(key, "program_num")) { - } else if (!strcmp(key, "st")) { - int st_num = strtol(p2, NULL, 0); - av_program_add_stream_index(oc, progid, st_num); - } else { - av_log(NULL, AV_LOG_FATAL, "Unknown program key %s.\n", key); - exit_program(1); - } - av_freep(&to_dealloc); - av_freep(&key); - } - } - - /* process manually set metadata */ - for (i = 0; i < o->nb_metadata; i++) { - AVDictionary **m; - char type, *val; - const char *stream_spec; - int index = 0, j, ret = 0; - - val = strchr(o->metadata[i].u.str, '='); - if (!val) { - av_log(NULL, AV_LOG_FATAL, "No '=' character in metadata string %s.\n", - o->metadata[i].u.str); - exit_program(1); - } - *val++ = 0; - - parse_meta_type(o->metadata[i].specifier, &type, &index, &stream_spec); - if (type == 's') { - for (j = 0; j < oc->nb_streams; j++) { - ost = output_streams[nb_output_streams - oc->nb_streams + j]; - if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) { - if (!strcmp(o->metadata[i].u.str, "rotate")) { - char *tail; - double theta = av_strtod(val, &tail); - if (!*tail) { - ost->rotate_overridden = 1; - ost->rotate_override_value = theta; - } - } else { - av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); - } - } else if (ret < 0) - exit_program(1); - } - } - else { - switch (type) { - case 'g': - m = &oc->metadata; - break; - case 'c': - if (index < 0 || index >= oc->nb_chapters) { - av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); - exit_program(1); - } - m = &oc->chapters[index]->metadata; - break; - case 'p': - if (index < 0 || index >= oc->nb_programs) { - av_log(NULL, AV_LOG_FATAL, "Invalid program index %d in metadata specifier.\n", index); - exit_program(1); - } - m = &oc->programs[index]->metadata; - break; - default: - av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); - exit_program(1); - } - av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); - } - } - - err = set_dispositions(of, oc); - if (err < 0) { - av_log(NULL, AV_LOG_FATAL, "Error setting output stream dispositions\n"); - exit_program(1); - } - - return 0; -} - -int opt_target(void *optctx, const char *opt, const char *arg) -{ - const OptionDef *options = ffmpeg_options; - OptionsContext *o = optctx; - enum { PAL, NTSC, FILM, UNKNOWN } norm = UNKNOWN; - static const char *const frame_rates[] = { "25", "30000/1001", "24000/1001" }; - - if (!strncmp(arg, "pal-", 4)) { - norm = PAL; - arg += 4; - } else if (!strncmp(arg, "ntsc-", 5)) { - norm = NTSC; - arg += 5; - } else if (!strncmp(arg, "film-", 5)) { - norm = FILM; - arg += 5; - } else { - /* Try to determine PAL/NTSC by peeking in the input files */ - if (nb_input_files) { - int i, j; - for (j = 0; j < nb_input_files; j++) { - for (i = 0; i < input_files[j]->nb_streams; i++) { - AVStream *st = input_files[j]->ctx->streams[i]; - int64_t fr; - if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) - continue; - fr = st->time_base.den * 1000LL / st->time_base.num; - if (fr == 25000) { - norm = PAL; - break; - } else if ((fr == 29970) || (fr == 23976)) { - norm = NTSC; - break; - } - } - if (norm != UNKNOWN) - break; - } - } - if (norm != UNKNOWN) - av_log(NULL, AV_LOG_INFO, "Assuming %s for target.\n", norm == PAL ? "PAL" : "NTSC"); - } - - if (norm == UNKNOWN) { - av_log(NULL, AV_LOG_FATAL, "Could not determine norm (PAL/NTSC/NTSC-Film) for target.\n"); - av_log(NULL, AV_LOG_FATAL, "Please prefix target with \"pal-\", \"ntsc-\" or \"film-\",\n"); - av_log(NULL, AV_LOG_FATAL, "or set a framerate with \"-r xxx\".\n"); - exit_program(1); - } - - if (!strcmp(arg, "vcd")) { - opt_video_codec(o, "c:v", "mpeg1video"); - opt_audio_codec(o, "c:a", "mp2"); - parse_option(o, "f", "vcd", options); - - parse_option(o, "s", norm == PAL ? "352x288" : "352x240", options); - parse_option(o, "r", frame_rates[norm], options); - opt_default(NULL, "g", norm == PAL ? "15" : "18"); - - opt_default(NULL, "b:v", "1150000"); - opt_default(NULL, "maxrate:v", "1150000"); - opt_default(NULL, "minrate:v", "1150000"); - opt_default(NULL, "bufsize:v", "327680"); // 40*1024*8; - - opt_default(NULL, "b:a", "224000"); - parse_option(o, "ar", "44100", options); - parse_option(o, "ac", "2", options); + opt_default(NULL, "b:a", "224000"); + parse_option(o, "ar", "44100", options); + parse_option(o, "ac", "2", options); opt_default(NULL, "packetsize", "2324"); opt_default(NULL, "muxrate", "1411200"); // 2352 * 75 * 8; @@ -3437,15 +1159,13 @@ int opt_filter_complex(void *optctx, const char *opt, const char *arg) if (!fg->graph_desc) return AVERROR(ENOMEM); - input_stream_potentially_available = 1; - return 0; } int opt_filter_complex_script(void *optctx, const char *opt, const char *arg) { FilterGraph *fg; - char *graph_desc = fftools_read_file(arg); + char *graph_desc = file_read(arg); if (!graph_desc) return AVERROR(EINVAL); @@ -3453,8 +1173,6 @@ int opt_filter_complex_script(void *optctx, const char *opt, const char *arg) fg->index = nb_filtergraphs - 1; fg->graph_desc = graph_desc; - input_stream_potentially_available = 1; - return 0; } @@ -3549,7 +1267,7 @@ static const OptionGroupDef groups[] = { }; int open_files(OptionGroupList *l, const char *inout, - int (*open_file)(OptionsContext*, const char*)) + int (*open_file)(const OptionsContext*, const char*)) { int i, ret; @@ -3585,7 +1303,6 @@ int open_files(OptionGroupList *l, const char *inout, int ffmpeg_parse_options(int argc, char **argv) { OptionParseContext octx; - uint8_t error[128]; int ret; memset(&octx, 0, sizeof(octx)); @@ -3609,14 +1326,12 @@ int ffmpeg_parse_options(int argc, char **argv) term_init(); /* open input files */ - ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file); + ret = open_files(&octx.groups[GROUP_INFILE], "input", ifile_open); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening input files: "); goto fail; } - apply_sync_offsets(); - /* create the complex filtergraphs */ ret = init_complex_filters(); if (ret < 0) { @@ -3625,19 +1340,22 @@ int ffmpeg_parse_options(int argc, char **argv) } /* open output files */ - ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file); + ret = open_files(&octx.groups[GROUP_OUTFILE], "output", of_open); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening output files: "); goto fail; } + correct_input_start_times(); + + apply_sync_offsets(); + check_filter_outputs(); fail: uninit_parse_context(&octx); if (ret < 0) { - av_strerror(ret, error, sizeof(error)); - av_log(NULL, AV_LOG_FATAL, "%s\n", error); + av_log(NULL, AV_LOG_FATAL, "%s\n", av_err2str(ret)); } return ret; } diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffprobe.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffprobe.c index 769e430..dccd966 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffprobe.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_ffprobe.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2007-2010 Stefano Sabatini - * Copyright (c) 2020 Taner Sener + * Copyright (c) 2020-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -29,6 +30,13 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -52,9 +60,11 @@ #include "libavutil/ffversion.h" #include +#include #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" +#include "libavutil/ambient_viewing_environment.h" #include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/bprint.h" @@ -163,6 +173,8 @@ typedef struct ReadInterval { __thread ReadInterval *read_intervals; __thread int read_intervals_nb = 0; +__thread int find_stream_info = 1; + /* section structure definition */ #define SECTION_MAX_NB_CHILDREN 10 @@ -626,6 +638,7 @@ static inline void writer_put_str_printf(WriterContext *wctx, const char *str) static inline void writer_printf_printf(WriterContext *wctx, const char *fmt, ...) { va_list ap; + va_start(ap, fmt); av_vlog(NULL, AV_LOG_STDERR, fmt, ap); va_end(ap); @@ -671,7 +684,7 @@ static int writer_open(WriterContext **wctx, const Writer *writer, const char *a goto fail; } - while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) { + while ((opt = av_dict_iterate(opts, opt))) { if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) { av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n", opt->key, opt->value); @@ -1907,12 +1920,14 @@ static void writer_register_all(void) writer_print_string(w, k, pbuf.str, 0); \ } while (0) -#define print_list_fmt(k, f, n, ...) do { \ +#define print_list_fmt(k, f, n, m, ...) do { \ av_bprint_clear(&pbuf); \ for (int idx = 0; idx < n; idx++) { \ - if (idx > 0) \ - av_bprint_chars(&pbuf, ' ', 1); \ - av_bprintf(&pbuf, f, __VA_ARGS__); \ + for (int idx2 = 0; idx2 < m; idx2++) { \ + if (idx > 0 || idx2 > 0) \ + av_bprint_chars(&pbuf, ' ', 1); \ + av_bprintf(&pbuf, f, __VA_ARGS__); \ + } \ } \ writer_print_string(w, k, pbuf.str, 0); \ } while (0) @@ -1953,7 +1968,7 @@ static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id return 0; writer_print_section_header(w, section_id); - while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) { + while ((tag = av_dict_iterate(tags, tag))) { if ((ret = print_str_validate(tag->key, tag->value)) < 0) break; } @@ -2023,7 +2038,7 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) const AVDOVIReshapingCurve *curve = &mapping->curves[c]; writer_print_section_header(w, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); - print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, curve->pivots[idx]); + print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, 1, curve->pivots[idx]); writer_print_section_header(w, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); for (int i = 0; i < curve->num_pivots - 1; i++) { @@ -2035,7 +2050,7 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_str("mapping_idc_name", "polynomial"); print_int("poly_order", curve->poly_order[i]); print_list_fmt("poly_coef", "%"PRIi64, - curve->poly_order[i] + 1, + curve->poly_order[i] + 1, 1, curve->poly_coef[i][idx]); break; case AV_DOVI_MAPPING_MMR: @@ -2043,8 +2058,8 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_int("mmr_order", curve->mmr_order[i]); print_int("mmr_constant", curve->mmr_constant[i]); print_list_fmt("mmr_coef", "%"PRIi64, - curve->mmr_order[i] * 7, - curve->mmr_coef[i][0][idx]); + curve->mmr_order[i], 7, + curve->mmr_coef[i][idx][idx2]); break; default: print_str("mapping_idc_name", "unknown"); @@ -2082,15 +2097,15 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_int("dm_metadata_id", color->dm_metadata_id); print_int("scene_refresh_flag", color->scene_refresh_flag); print_list_fmt("ycc_to_rgb_matrix", "%d/%d", - FF_ARRAY_ELEMS(color->ycc_to_rgb_matrix), + FF_ARRAY_ELEMS(color->ycc_to_rgb_matrix), 1, color->ycc_to_rgb_matrix[idx].num, color->ycc_to_rgb_matrix[idx].den); print_list_fmt("ycc_to_rgb_offset", "%d/%d", - FF_ARRAY_ELEMS(color->ycc_to_rgb_offset), + FF_ARRAY_ELEMS(color->ycc_to_rgb_offset), 1, color->ycc_to_rgb_offset[idx].num, color->ycc_to_rgb_offset[idx].den); print_list_fmt("rgb_to_lms_matrix", "%d/%d", - FF_ARRAY_ELEMS(color->rgb_to_lms_matrix), + FF_ARRAY_ELEMS(color->rgb_to_lms_matrix), 1, color->rgb_to_lms_matrix[idx].num, color->rgb_to_lms_matrix[idx].den); print_int("signal_eotf", color->signal_eotf); @@ -2276,6 +2291,17 @@ static void print_dynamic_hdr_vivid(WriterContext *w, const AVDynamicHDRVivid *m } } +static void print_ambient_viewing_environment(WriterContext *w, + const AVAmbientViewingEnvironment *env) +{ + if (!env) + return; + + print_q("ambient_illuminance", env->ambient_illuminance, '/'); + print_q("ambient_light_x", env->ambient_light_x, '/'); + print_q("ambient_light_y", env->ambient_light_y, '/'); +} + static void print_pkt_side_data(WriterContext *w, AVCodecParameters *par, const AVPacketSideData *side_data, @@ -2293,8 +2319,11 @@ static void print_pkt_side_data(WriterContext *w, writer_print_section_header(w, id_data); print_str("side_data_type", name ? name : "unknown"); if (sd->type == AV_PKT_DATA_DISPLAYMATRIX && sd->size >= 9*4) { + double rotation = av_display_rotation_get((int32_t *)sd->data); + if (isnan(rotation)) + rotation = 0; writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); - print_int("rotation", av_display_rotation_get((int32_t *)sd->data)); + print_int("rotation", rotation); } else if (sd->type == AV_PKT_DATA_STEREO3D) { const AVStereo3D *stereo = (AVStereo3D *)sd->data; print_str("type", av_stereo3d_type_name(stereo->type)); @@ -2506,8 +2535,12 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p print_val("size", pkt->size, unit_byte_str); if (pkt->pos != -1) print_fmt ("pos", "%"PRId64, pkt->pos); else print_str_opt("pos", "N/A"); - print_fmt("flags", "%c%c", pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_', - pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_'); + print_fmt("flags", "%c%c%c", pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_', + pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_', + pkt->flags & AV_PKT_FLAG_CORRUPT ? 'C' : '_'); + if (do_show_data) + writer_print_data(w, "data", pkt->data, pkt->size); + writer_print_data_hash(w, "data_hash", pkt->data, pkt->size); if (pkt->side_data_elems) { size_t size; @@ -2526,9 +2559,6 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p SECTION_ID_PACKET_SIDE_DATA); } - if (do_show_data) - writer_print_data(w, "data", pkt->data, pkt->size); - writer_print_data_hash(w, "data_hash", pkt->data, pkt->size); writer_print_section_footer(w); av_bprint_finalize(&pbuf, NULL); @@ -2581,8 +2611,14 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, print_time("pkt_dts_time", frame->pkt_dts, &stream->time_base); print_ts ("best_effort_timestamp", frame->best_effort_timestamp); print_time("best_effort_timestamp_time", frame->best_effort_timestamp, &stream->time_base); +#if LIBAVUTIL_VERSION_MAJOR < 59 + AV_NOWARN_DEPRECATED( print_duration_ts ("pkt_duration", frame->pkt_duration); print_duration_time("pkt_duration_time", frame->pkt_duration, &stream->time_base); + ) +#endif + print_duration_ts ("duration", frame->duration); + print_duration_time("duration_time", frame->duration, &stream->time_base); if (frame->pkt_pos != -1) print_fmt ("pkt_pos", "%"PRId64, frame->pkt_pos); else print_str_opt("pkt_pos", "N/A"); if (frame->pkt_size != -1) print_val ("pkt_size", frame->pkt_size, unit_byte_str); @@ -2604,8 +2640,12 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, print_str_opt("sample_aspect_ratio", "N/A"); } print_fmt("pict_type", "%c", av_get_picture_type_char(frame->pict_type)); +#if LIBAVUTIL_VERSION_MAJOR < 59 + AV_NOWARN_DEPRECATED( print_int("coded_picture_number", frame->coded_picture_number); print_int("display_picture_number", frame->display_picture_number); + ) +#endif print_int("interlaced_frame", frame->interlaced_frame); print_int("top_field_first", frame->top_field_first); print_int("repeat_pict", frame->repeat_pict); @@ -2644,8 +2684,11 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, name = av_frame_side_data_name(sd->type); print_str("side_data_type", name ? name : "unknown"); if (sd->type == AV_FRAME_DATA_DISPLAYMATRIX && sd->size >= 9*4) { + double rotation = av_display_rotation_get((int32_t *)sd->data); + if (isnan(rotation)) + rotation = 0; writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); - print_int("rotation", av_display_rotation_get((int32_t *)sd->data)); + print_int("rotation", rotation); } else if (sd->type == AV_FRAME_DATA_AFD && sd->size > 0) { print_int("active_format", *sd->data); } else if (sd->type == AV_FRAME_DATA_GOP_TIMECODE && sd->size >= 8) { @@ -2700,6 +2743,9 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, } else if (sd->type == AV_FRAME_DATA_DYNAMIC_HDR_VIVID) { AVDynamicHDRVivid *metadata = (AVDynamicHDRVivid *)sd->data; print_dynamic_hdr_vivid(w, metadata); + } else if (sd->type == AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT) { + print_ambient_viewing_environment( + w, (const AVAmbientViewingEnvironment *)sd->data); } writer_print_section_footer(w); } @@ -2714,7 +2760,7 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, static av_always_inline int process_frame(WriterContext *w, InputFile *ifile, - AVFrame *frame, AVPacket *pkt, + AVFrame *frame, const AVPacket *pkt, int *packet_new) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; @@ -2858,9 +2904,10 @@ static int read_interval_packets(WriterContext *w, InputFile *ifile, } if (selected_streams[pkt->stream_index]) { AVRational tb = ifile->streams[pkt->stream_index].st->time_base; + int64_t pts = pkt->pts != AV_NOPTS_VALUE ? pkt->pts : pkt->dts; - if (pkt->pts != AV_NOPTS_VALUE) - *cur_ts = av_rescale_q(pkt->pts, tb, AV_TIME_BASE_Q); + if (pts != AV_NOPTS_VALUE) + *cur_ts = av_rescale_q(pts, tb, AV_TIME_BASE_Q); if (!has_start && *cur_ts != AV_NOPTS_VALUE) { start = *cur_ts; @@ -2894,7 +2941,7 @@ static int read_interval_packets(WriterContext *w, InputFile *ifile, } av_packet_unref(pkt); //Flush remaining frames that are cached in the decoder - for (i = 0; i < fmt_ctx->nb_streams; i++) { + for (i = 0; i < ifile->nb_streams; i++) { pkt->stream_index = i; if (do_read_frames) { while (process_frame(w, ifile, frame, pkt, &(int){1}) > 0); @@ -3052,6 +3099,8 @@ static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_id } print_int("bits_per_sample", av_get_bits_per_sample(par->codec_id)); + + print_int("initial_padding", par->initial_padding); break; case AVMEDIA_TYPE_SUBTITLE: @@ -3278,15 +3327,9 @@ static int show_format(WriterContext *w, InputFile *ifile) static void show_error(WriterContext *w, int err) { - char errbuf[128]; - const char *errbuf_ptr = errbuf; - - if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) - errbuf_ptr = strerror(AVUNERROR(err)); - writer_print_section_header(w, SECTION_ID_ERROR); print_int("code", err); - print_str("string", errbuf_ptr); + print_str("string", av_err2str(err)); writer_print_section_footer(w); } @@ -3299,10 +3342,8 @@ static int open_input_file(InputFile *ifile, const char *filename, int scan_all_pmts_set = 0; fmt_ctx = avformat_alloc_context(); - if (!fmt_ctx) { - print_error(filename, AVERROR(ENOMEM)); - exit_program(1); - } + if (!fmt_ctx) + report_and_exit(AVERROR(ENOMEM)); if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); @@ -3320,7 +3361,7 @@ static int open_input_file(InputFile *ifile, const char *filename, ifile->fmt_ctx = fmt_ctx; if (scan_all_pmts_set) av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); - while ((t = av_dict_get(format_opts, "", t, AV_DICT_IGNORE_SUFFIX))) + while ((t = av_dict_iterate(format_opts, t))) av_log(NULL, AV_LOG_WARNING, "Option %s skipped - not known to demuxer.\n", t->key); if (find_stream_info) { @@ -3718,7 +3759,7 @@ static void opt_input_file(void *optctx, const char *arg) exit_program(1); } if (!strcmp(arg, "-")) - arg = "pipe:"; + arg = "fd:"; input_filename = arg; } @@ -3737,7 +3778,7 @@ static void opt_output_file(void *optctx, const char *arg) exit_program(1); } if (!strcmp(arg, "-")) - arg = "pipe:"; + arg = "fd:"; output_filename = arg; } diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_objpool.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_objpool.c new file mode 100644 index 0000000..2884271 --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_objpool.c @@ -0,0 +1,145 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of objpool.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include + +#include "libavcodec/packet.h" + +#include "libavutil/common.h" +#include "libavutil/error.h" +#include "libavutil/frame.h" +#include "libavutil/mem.h" + +#include "fftools_objpool.h" + +struct ObjPool { + void *pool[32]; + unsigned int pool_count; + + ObjPoolCBAlloc alloc; + ObjPoolCBReset reset; + ObjPoolCBFree free; +}; + +ObjPool *objpool_alloc(ObjPoolCBAlloc cb_alloc, ObjPoolCBReset cb_reset, + ObjPoolCBFree cb_free) +{ + ObjPool *op = av_mallocz(sizeof(*op)); + + if (!op) + return NULL; + + op->alloc = cb_alloc; + op->reset = cb_reset; + op->free = cb_free; + + return op; +} + +void objpool_free(ObjPool **pop) +{ + ObjPool *op = *pop; + + if (!op) + return; + + for (unsigned int i = 0; i < op->pool_count; i++) + op->free(&op->pool[i]); + + av_freep(pop); +} + +int objpool_get(ObjPool *op, void **obj) +{ + if (op->pool_count) { + *obj = op->pool[--op->pool_count]; + op->pool[op->pool_count] = NULL; + } else + *obj = op->alloc(); + + return *obj ? 0 : AVERROR(ENOMEM); +} + +void objpool_release(ObjPool *op, void **obj) +{ + if (!*obj) + return; + + op->reset(*obj); + + if (op->pool_count < FF_ARRAY_ELEMS(op->pool)) + op->pool[op->pool_count++] = *obj; + else + op->free(obj); + + *obj = NULL; +} + +static void *alloc_packet(void) +{ + return av_packet_alloc(); +} +static void *alloc_frame(void) +{ + return av_frame_alloc(); +} + +static void reset_packet(void *obj) +{ + av_packet_unref(obj); +} +static void reset_frame(void *obj) +{ + av_frame_unref(obj); +} + +static void free_packet(void **obj) +{ + AVPacket *pkt = *obj; + av_packet_free(&pkt); + *obj = NULL; +} +static void free_frame(void **obj) +{ + AVFrame *frame = *obj; + av_frame_free(&frame); + *obj = NULL; +} + +ObjPool *objpool_alloc_packets(void) +{ + return objpool_alloc(alloc_packet, reset_packet, free_packet); +} +ObjPool *objpool_alloc_frames(void) +{ + return objpool_alloc(alloc_frame, reset_frame, free_frame); +} diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_objpool.h b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_objpool.h new file mode 100644 index 0000000..29cf2b0 --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_objpool.h @@ -0,0 +1,50 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of objpool.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_OBJPOOL_H +#define FFTOOLS_OBJPOOL_H + +typedef struct ObjPool ObjPool; + +typedef void* (*ObjPoolCBAlloc)(void); +typedef void (*ObjPoolCBReset)(void *); +typedef void (*ObjPoolCBFree)(void **); + +void objpool_free(ObjPool **op); +ObjPool *objpool_alloc(ObjPoolCBAlloc cb_alloc, ObjPoolCBReset cb_reset, + ObjPoolCBFree cb_free); +ObjPool *objpool_alloc_packets(void); +ObjPool *objpool_alloc_frames(void); + +int objpool_get(ObjPool *op, void **obj); +void objpool_release(ObjPool *op, void **obj); + +#endif // FFTOOLS_OBJPOOL_H diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_opt_common.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_opt_common.c index 759f1ae..e95c546 100644 --- a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_opt_common.c +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_opt_common.c @@ -1,6 +1,7 @@ /* * Option handlers shared between the tools. * Copyright (c) 2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop the ffmpeg-kit library. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - time field in report_callback updated as double + * * ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -88,7 +95,7 @@ static __thread FILE *report_file = NULL; static __thread int report_file_level = AV_LOG_DEBUG; extern void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, va_list vargs); -extern void (*report_callback)(int, float, float, int64_t, int, double, double); +extern void (*report_callback)(int, float, float, int64_t, double, double, double); extern __thread char *program_name; int show_license(void *optctx, const char *opt, const char *arg) diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_sync_queue.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_sync_queue.c new file mode 100644 index 0000000..cad5db3 --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_sync_queue.c @@ -0,0 +1,462 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of sync_queue.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/error.h" +#include "libavutil/fifo.h" +#include "libavutil/mathematics.h" +#include "libavutil/mem.h" + +#include "fftools_objpool.h" +#include "fftools_sync_queue.h" + +typedef struct SyncQueueStream { + AVFifo *fifo; + AVRational tb; + + /* stream head: largest timestamp seen */ + int64_t head_ts; + int limiting; + /* no more frames will be sent for this stream */ + int finished; + + uint64_t frames_sent; + uint64_t frames_max; +} SyncQueueStream; + +struct SyncQueue { + enum SyncQueueType type; + + /* no more frames will be sent for any stream */ + int finished; + /* sync head: the stream with the _smallest_ head timestamp + * this stream determines which frames can be output */ + int head_stream; + /* the finished stream with the smallest finish timestamp or -1 */ + int head_finished_stream; + + // maximum buffering duration in microseconds + int64_t buf_size_us; + + SyncQueueStream *streams; + unsigned int nb_streams; + + // pool of preallocated frames to avoid constant allocations + ObjPool *pool; +}; + +static void frame_move(const SyncQueue *sq, SyncQueueFrame dst, + SyncQueueFrame src) +{ + if (sq->type == SYNC_QUEUE_PACKETS) + av_packet_move_ref(dst.p, src.p); + else + av_frame_move_ref(dst.f, src.f); +} + +static int64_t frame_ts(const SyncQueue *sq, SyncQueueFrame frame) +{ + return (sq->type == SYNC_QUEUE_PACKETS) ? + frame.p->pts + frame.p->duration : + frame.f->pts + frame.f->duration; +} + +static int frame_null(const SyncQueue *sq, SyncQueueFrame frame) +{ + return (sq->type == SYNC_QUEUE_PACKETS) ? (frame.p == NULL) : (frame.f == NULL); +} + +static void finish_stream(SyncQueue *sq, unsigned int stream_idx) +{ + SyncQueueStream *st = &sq->streams[stream_idx]; + + st->finished = 1; + + if (st->limiting && st->head_ts != AV_NOPTS_VALUE) { + /* check if this stream is the new finished head */ + if (sq->head_finished_stream < 0 || + av_compare_ts(st->head_ts, st->tb, + sq->streams[sq->head_finished_stream].head_ts, + sq->streams[sq->head_finished_stream].tb) < 0) { + sq->head_finished_stream = stream_idx; + } + + /* mark as finished all streams that should no longer receive new frames, + * due to them being ahead of some finished stream */ + st = &sq->streams[sq->head_finished_stream]; + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st1 = &sq->streams[i]; + if (st != st1 && st1->head_ts != AV_NOPTS_VALUE && + av_compare_ts(st->head_ts, st->tb, st1->head_ts, st1->tb) <= 0) + st1->finished = 1; + } + } + + /* mark the whole queue as finished if all streams are finished */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + if (!sq->streams[i].finished) + return; + } + sq->finished = 1; +} + +static void queue_head_update(SyncQueue *sq) +{ + if (sq->head_stream < 0) { + /* wait for one timestamp in each stream before determining + * the queue head */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st = &sq->streams[i]; + if (st->limiting && st->head_ts == AV_NOPTS_VALUE) + return; + } + + // placeholder value, correct one will be found below + sq->head_stream = 0; + } + + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st_head = &sq->streams[sq->head_stream]; + SyncQueueStream *st_other = &sq->streams[i]; + if (st_other->limiting && st_other->head_ts != AV_NOPTS_VALUE && + av_compare_ts(st_other->head_ts, st_other->tb, + st_head->head_ts, st_head->tb) < 0) + sq->head_stream = i; + } +} + +/* update this stream's head timestamp */ +static void stream_update_ts(SyncQueue *sq, unsigned int stream_idx, int64_t ts) +{ + SyncQueueStream *st = &sq->streams[stream_idx]; + + if (ts == AV_NOPTS_VALUE || + (st->head_ts != AV_NOPTS_VALUE && st->head_ts >= ts)) + return; + + st->head_ts = ts; + + /* if this stream is now ahead of some finished stream, then + * this stream is also finished */ + if (sq->head_finished_stream >= 0 && + av_compare_ts(sq->streams[sq->head_finished_stream].head_ts, + sq->streams[sq->head_finished_stream].tb, + ts, st->tb) <= 0) + finish_stream(sq, stream_idx); + + /* update the overall head timestamp if it could have changed */ + if (st->limiting && + (sq->head_stream < 0 || sq->head_stream == stream_idx)) + queue_head_update(sq); +} + +/* If the queue for the given stream (or all streams when stream_idx=-1) + * is overflowing, trigger a fake heartbeat on lagging streams. + * + * @return 1 if heartbeat triggered, 0 otherwise + */ +static int overflow_heartbeat(SyncQueue *sq, int stream_idx) +{ + SyncQueueStream *st; + SyncQueueFrame frame; + int64_t tail_ts = AV_NOPTS_VALUE; + + /* if no stream specified, pick the one that is most ahead */ + if (stream_idx < 0) { + int64_t ts = AV_NOPTS_VALUE; + + for (int i = 0; i < sq->nb_streams; i++) { + st = &sq->streams[i]; + if (st->head_ts != AV_NOPTS_VALUE && + (ts == AV_NOPTS_VALUE || + av_compare_ts(ts, sq->streams[stream_idx].tb, + st->head_ts, st->tb) < 0)) { + ts = st->head_ts; + stream_idx = i; + } + } + /* no stream has a timestamp yet -> nothing to do */ + if (stream_idx < 0) + return 0; + } + + st = &sq->streams[stream_idx]; + + /* get the chosen stream's tail timestamp */ + for (size_t i = 0; tail_ts == AV_NOPTS_VALUE && + av_fifo_peek(st->fifo, &frame, 1, i) >= 0; i++) + tail_ts = frame_ts(sq, frame); + + /* overflow triggers when the tail is over specified duration behind the head */ + if (tail_ts == AV_NOPTS_VALUE || tail_ts >= st->head_ts || + av_rescale_q(st->head_ts - tail_ts, st->tb, AV_TIME_BASE_Q) < sq->buf_size_us) + return 0; + + /* signal a fake timestamp for all streams that prevent tail_ts from being output */ + tail_ts++; + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st1 = &sq->streams[i]; + int64_t ts; + + if (st == st1 || st1->finished || + (st1->head_ts != AV_NOPTS_VALUE && + av_compare_ts(tail_ts, st->tb, st1->head_ts, st1->tb) <= 0)) + continue; + + ts = av_rescale_q(tail_ts, st->tb, st1->tb); + if (st1->head_ts != AV_NOPTS_VALUE) + ts = FFMAX(st1->head_ts + 1, ts); + + stream_update_ts(sq, i, ts); + } + + return 1; +} + +int sq_send(SyncQueue *sq, unsigned int stream_idx, SyncQueueFrame frame) +{ + SyncQueueStream *st; + SyncQueueFrame dst; + int64_t ts; + int ret; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + av_assert0(st->tb.num > 0 && st->tb.den > 0); + + if (frame_null(sq, frame)) { + finish_stream(sq, stream_idx); + return 0; + } + if (st->finished) + return AVERROR_EOF; + + ret = objpool_get(sq->pool, (void**)&dst); + if (ret < 0) + return ret; + + frame_move(sq, dst, frame); + + ts = frame_ts(sq, dst); + + ret = av_fifo_write(st->fifo, &dst, 1); + if (ret < 0) { + frame_move(sq, frame, dst); + objpool_release(sq->pool, (void**)&dst); + return ret; + } + + stream_update_ts(sq, stream_idx, ts); + + st->frames_sent++; + if (st->frames_sent >= st->frames_max) + finish_stream(sq, stream_idx); + + return 0; +} + +static int receive_for_stream(SyncQueue *sq, unsigned int stream_idx, + SyncQueueFrame frame) +{ + SyncQueueStream *st_head = sq->head_stream >= 0 ? + &sq->streams[sq->head_stream] : NULL; + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + if (av_fifo_can_read(st->fifo)) { + SyncQueueFrame peek; + int64_t ts; + int cmp = 1; + + av_fifo_peek(st->fifo, &peek, 1, 0); + ts = frame_ts(sq, peek); + + /* check if this stream's tail timestamp does not overtake + * the overall queue head */ + if (ts != AV_NOPTS_VALUE && st_head) + cmp = av_compare_ts(ts, st->tb, st_head->head_ts, st_head->tb); + + /* We can release frames that do not end after the queue head. + * Frames with no timestamps are just passed through with no conditions. + */ + if (cmp <= 0 || ts == AV_NOPTS_VALUE) { + frame_move(sq, frame, peek); + objpool_release(sq->pool, (void**)&peek); + av_fifo_drain2(st->fifo, 1); + return 0; + } + } + + return (sq->finished || (st->finished && !av_fifo_can_read(st->fifo))) ? + AVERROR_EOF : AVERROR(EAGAIN); +} + +static int receive_internal(SyncQueue *sq, int stream_idx, SyncQueueFrame frame) +{ + int nb_eof = 0; + int ret; + + /* read a frame for a specific stream */ + if (stream_idx >= 0) { + ret = receive_for_stream(sq, stream_idx, frame); + return (ret < 0) ? ret : stream_idx; + } + + /* read a frame for any stream with available output */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + ret = receive_for_stream(sq, i, frame); + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { + nb_eof += (ret == AVERROR_EOF); + continue; + } + return (ret < 0) ? ret : i; + } + + return (nb_eof == sq->nb_streams) ? AVERROR_EOF : AVERROR(EAGAIN); +} + +int sq_receive(SyncQueue *sq, int stream_idx, SyncQueueFrame frame) +{ + int ret = receive_internal(sq, stream_idx, frame); + + /* try again if the queue overflowed and triggered a fake heartbeat + * for lagging streams */ + if (ret == AVERROR(EAGAIN) && overflow_heartbeat(sq, stream_idx)) + ret = receive_internal(sq, stream_idx, frame); + + return ret; +} + +int sq_add_stream(SyncQueue *sq, int limiting) +{ + SyncQueueStream *tmp, *st; + + tmp = av_realloc_array(sq->streams, sq->nb_streams + 1, sizeof(*sq->streams)); + if (!tmp) + return AVERROR(ENOMEM); + sq->streams = tmp; + + st = &sq->streams[sq->nb_streams]; + memset(st, 0, sizeof(*st)); + + st->fifo = av_fifo_alloc2(1, sizeof(SyncQueueFrame), AV_FIFO_FLAG_AUTO_GROW); + if (!st->fifo) + return AVERROR(ENOMEM); + + /* we set a valid default, so that a pathological stream that never + * receives even a real timebase (and no frames) won't stall all other + * streams forever; cf. overflow_heartbeat() */ + st->tb = (AVRational){ 1, 1 }; + st->head_ts = AV_NOPTS_VALUE; + st->frames_max = UINT64_MAX; + st->limiting = limiting; + + return sq->nb_streams++; +} + +void sq_set_tb(SyncQueue *sq, unsigned int stream_idx, AVRational tb) +{ + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + av_assert0(!av_fifo_can_read(st->fifo)); + + if (st->head_ts != AV_NOPTS_VALUE) + st->head_ts = av_rescale_q(st->head_ts, st->tb, tb); + + st->tb = tb; +} + +void sq_limit_frames(SyncQueue *sq, unsigned int stream_idx, uint64_t frames) +{ + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + st->frames_max = frames; + if (st->frames_sent >= st->frames_max) + finish_stream(sq, stream_idx); +} + +SyncQueue *sq_alloc(enum SyncQueueType type, int64_t buf_size_us) +{ + SyncQueue *sq = av_mallocz(sizeof(*sq)); + + if (!sq) + return NULL; + + sq->type = type; + sq->buf_size_us = buf_size_us; + + sq->head_stream = -1; + sq->head_finished_stream = -1; + + sq->pool = (type == SYNC_QUEUE_PACKETS) ? objpool_alloc_packets() : + objpool_alloc_frames(); + if (!sq->pool) { + av_freep(&sq); + return NULL; + } + + return sq; +} + +void sq_free(SyncQueue **psq) +{ + SyncQueue *sq = *psq; + + if (!sq) + return; + + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueFrame frame; + while (av_fifo_read(sq->streams[i].fifo, &frame, 1) >= 0) + objpool_release(sq->pool, (void**)&frame); + + av_fifo_freep2(&sq->streams[i].fifo); + } + + av_freep(&sq->streams); + + objpool_free(&sq->pool); + + av_freep(psq); +} diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_sync_queue.h b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_sync_queue.h new file mode 100644 index 0000000..0fd7b3c --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_sync_queue.h @@ -0,0 +1,122 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of sync_queue.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_SYNC_QUEUE_H +#define FFTOOLS_SYNC_QUEUE_H + +#include + +#include "libavcodec/packet.h" + +#include "libavutil/frame.h" + +enum SyncQueueType { + SYNC_QUEUE_PACKETS, + SYNC_QUEUE_FRAMES, +}; + +typedef union SyncQueueFrame { + AVFrame *f; + AVPacket *p; +} SyncQueueFrame; + +#define SQFRAME(frame) ((SyncQueueFrame){ .f = (frame) }) +#define SQPKT(pkt) ((SyncQueueFrame){ .p = (pkt) }) + +typedef struct SyncQueue SyncQueue; + +/** + * Allocate a sync queue of the given type. + * + * @param buf_size_us maximum duration that will be buffered in microseconds + */ +SyncQueue *sq_alloc(enum SyncQueueType type, int64_t buf_size_us); +void sq_free(SyncQueue **sq); + +/** + * Add a new stream to the sync queue. + * + * @param limiting whether the stream is limiting, i.e. no other stream can be + * longer than this one + * @return + * - a non-negative stream index on success + * - a negative error code on error + */ +int sq_add_stream(SyncQueue *sq, int limiting); + +/** + * Set the timebase for the stream with index stream_idx. Should be called + * before sending any frames for this stream. + */ +void sq_set_tb(SyncQueue *sq, unsigned int stream_idx, AVRational tb); + +/** + * Limit the number of output frames for stream with index stream_idx + * to max_frames. + */ +void sq_limit_frames(SyncQueue *sq, unsigned int stream_idx, + uint64_t max_frames); + +/** + * Submit a frame for the stream with index stream_idx. + * + * On success, the sync queue takes ownership of the frame and will reset the + * contents of the supplied frame. On failure, the frame remains owned by the + * caller. + * + * Sending a frame with NULL contents marks the stream as finished. + * + * @return + * - 0 on success + * - AVERROR_EOF when no more frames should be submitted for this stream + * - another a negative error code on failure + */ +int sq_send(SyncQueue *sq, unsigned int stream_idx, SyncQueueFrame frame); + +/** + * Read a frame from the queue. + * + * @param stream_idx index of the stream to read a frame for. May be -1, then + * try to read a frame from any stream that is ready for + * output. + * @param frame output frame will be written here on success. The frame is owned + * by the caller. + * + * @return + * - a non-negative index of the stream to which the returned frame belongs + * - AVERROR(EAGAIN) when more frames need to be submitted to the queue + * - AVERROR_EOF when no more frames will be available for this stream (for any + * stream if stream_idx is -1) + * - another negative error code on failure + */ +int sq_receive(SyncQueue *sq, int stream_idx, SyncQueueFrame frame); + +#endif // FFTOOLS_SYNC_QUEUE_H diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_thread_queue.c b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_thread_queue.c new file mode 100644 index 0000000..e8eade5 --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_thread_queue.c @@ -0,0 +1,259 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of thread_queue.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/error.h" +#include "libavutil/fifo.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" +#include "libavutil/thread.h" + +#include "fftools_objpool.h" +#include "fftools_thread_queue.h" + +enum { + FINISHED_SEND = (1 << 0), + FINISHED_RECV = (1 << 1), +}; + +typedef struct FifoElem { + void *obj; + unsigned int stream_idx; +} FifoElem; + +struct ThreadQueue { + int *finished; + unsigned int nb_streams; + + AVFifo *fifo; + + ObjPool *obj_pool; + void (*obj_move)(void *dst, void *src); + + pthread_mutex_t lock; + pthread_cond_t cond; +}; + +void tq_free(ThreadQueue **ptq) +{ + ThreadQueue *tq = *ptq; + + if (!tq) + return; + + if (tq->fifo) { + FifoElem elem; + while (av_fifo_read(tq->fifo, &elem, 1) >= 0) + objpool_release(tq->obj_pool, &elem.obj); + } + av_fifo_freep2(&tq->fifo); + + objpool_free(&tq->obj_pool); + + av_freep(&tq->finished); + + pthread_cond_destroy(&tq->cond); + pthread_mutex_destroy(&tq->lock); + + av_freep(ptq); +} + +ThreadQueue *tq_alloc(unsigned int nb_streams, size_t queue_size, + ObjPool *obj_pool, void (*obj_move)(void *dst, void *src)) +{ + ThreadQueue *tq; + int ret; + + tq = av_mallocz(sizeof(*tq)); + if (!tq) + return NULL; + + ret = pthread_cond_init(&tq->cond, NULL); + if (ret) { + av_freep(&tq); + return NULL; + } + + ret = pthread_mutex_init(&tq->lock, NULL); + if (ret) { + pthread_cond_destroy(&tq->cond); + av_freep(&tq); + return NULL; + } + + tq->finished = av_calloc(nb_streams, sizeof(*tq->finished)); + if (!tq->finished) + goto fail; + tq->nb_streams = nb_streams; + + tq->fifo = av_fifo_alloc2(queue_size, sizeof(FifoElem), 0); + if (!tq->fifo) + goto fail; + + tq->obj_pool = obj_pool; + tq->obj_move = obj_move; + + return tq; +fail: + tq_free(&tq); + return NULL; +} + +int tq_send(ThreadQueue *tq, unsigned int stream_idx, void *data) +{ + int *finished; + int ret; + + av_assert0(stream_idx < tq->nb_streams); + finished = &tq->finished[stream_idx]; + + pthread_mutex_lock(&tq->lock); + + if (*finished & FINISHED_SEND) { + ret = AVERROR(EINVAL); + goto finish; + } + + while (!(*finished & FINISHED_RECV) && !av_fifo_can_write(tq->fifo)) + pthread_cond_wait(&tq->cond, &tq->lock); + + if (*finished & FINISHED_RECV) { + ret = AVERROR_EOF; + *finished |= FINISHED_SEND; + } else { + FifoElem elem = { .stream_idx = stream_idx }; + + ret = objpool_get(tq->obj_pool, &elem.obj); + if (ret < 0) + goto finish; + + tq->obj_move(elem.obj, data); + + ret = av_fifo_write(tq->fifo, &elem, 1); + av_assert0(ret >= 0); + pthread_cond_broadcast(&tq->cond); + } + +finish: + pthread_mutex_unlock(&tq->lock); + + return ret; +} + +static int receive_locked(ThreadQueue *tq, int *stream_idx, + void *data) +{ + FifoElem elem; + unsigned int nb_finished = 0; + + if (av_fifo_read(tq->fifo, &elem, 1) >= 0) { + tq->obj_move(data, elem.obj); + objpool_release(tq->obj_pool, &elem.obj); + *stream_idx = elem.stream_idx; + return 0; + } + + for (unsigned int i = 0; i < tq->nb_streams; i++) { + if (!(tq->finished[i] & FINISHED_SEND)) + continue; + + /* return EOF to the consumer at most once for each stream */ + if (!(tq->finished[i] & FINISHED_RECV)) { + tq->finished[i] |= FINISHED_RECV; + *stream_idx = i; + return AVERROR_EOF; + } + + nb_finished++; + } + + return nb_finished == tq->nb_streams ? AVERROR_EOF : AVERROR(EAGAIN); +} + +int tq_receive(ThreadQueue *tq, int *stream_idx, void *data) +{ + int ret; + + *stream_idx = -1; + + pthread_mutex_lock(&tq->lock); + + while (1) { + ret = receive_locked(tq, stream_idx, data); + if (ret == AVERROR(EAGAIN)) { + pthread_cond_wait(&tq->cond, &tq->lock); + continue; + } + + break; + } + + if (ret == 0) + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); + + return ret; +} + +void tq_send_finish(ThreadQueue *tq, unsigned int stream_idx) +{ + av_assert0(stream_idx < tq->nb_streams); + + pthread_mutex_lock(&tq->lock); + + /* mark the stream as send-finished; + * next time the consumer thread tries to read this stream it will get + * an EOF and recv-finished flag will be set */ + tq->finished[stream_idx] |= FINISHED_SEND; + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); +} + +void tq_receive_finish(ThreadQueue *tq, unsigned int stream_idx) +{ + av_assert0(stream_idx < tq->nb_streams); + + pthread_mutex_lock(&tq->lock); + + /* mark the stream as recv-finished; + * next time the producer thread tries to send for this stream, it will + * get an EOF and send-finished flag will be set */ + tq->finished[stream_idx] |= FINISHED_RECV; + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); +} diff --git a/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_thread_queue.h b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_thread_queue.h new file mode 100644 index 0000000..b8736dd --- /dev/null +++ b/android/ffmpeg-kit-android-lib/src/main/cpp/fftools_thread_queue.h @@ -0,0 +1,94 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of thread_queue.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_THREAD_QUEUE_H +#define FFTOOLS_THREAD_QUEUE_H + +#include + +#include "fftools_objpool.h" + +typedef struct ThreadQueue ThreadQueue; + +/** + * Allocate a queue for sending data between threads. + * + * @param nb_streams number of streams for which a distinct EOF state is + * maintained + * @param queue_size number of items that can be stored in the queue without + * blocking + * @param obj_pool object pool that will be used to allocate items stored in the + * queue; the pool becomes owned by the queue + * @param callback that moves the contents between two data pointers + */ +ThreadQueue *tq_alloc(unsigned int nb_streams, size_t queue_size, + ObjPool *obj_pool, void (*obj_move)(void *dst, void *src)); +void tq_free(ThreadQueue **tq); + +/** + * Send an item for the given stream to the queue. + * + * @param data the item to send, its contents will be moved using the callback + * provided to tq_alloc(); on failure the item will be left + * untouched + * @return + * - 0 the item was successfully sent + * - AVERROR(ENOMEM) could not allocate an item for writing to the FIFO + * - AVERROR(EINVAL) the sending side has previously been marked as finished + * - AVERROR_EOF the receiving side has marked the given stream as finished + */ +int tq_send(ThreadQueue *tq, unsigned int stream_idx, void *data); +/** + * Mark the given stream finished from the sending side. + */ +void tq_send_finish(ThreadQueue *tq, unsigned int stream_idx); + +/** + * Read the next item from the queue. + * + * @param stream_idx the index of the stream that was processed or -1 will be + * written here + * @param data the data item will be written here on success using the + * callback provided to tq_alloc() + * @return + * - 0 a data item was successfully read; *stream_idx contains a non-negative + * stream index + * - AVERROR_EOF When *stream_idx is non-negative, this signals that the sending + * side has marked the given stream as finished. This will happen at most once + * for each stream. When *stream_idx is -1, all streams are done. + */ +int tq_receive(ThreadQueue *tq, int *stream_idx, void *data); +/** + * Mark the given stream finished from the receiving side. + */ +void tq_receive_finish(ThreadQueue *tq, unsigned int stream_idx); + +#endif // FFTOOLS_THREAD_QUEUE_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 80ef18b..073e44a 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 @@ -334,7 +334,7 @@ public class FFmpegKitConfig { */ private static void statistics(final long sessionId, final int videoFrameNumber, final float videoFps, final float videoQuality, final long size, - final int time, final double bitrate, final double speed) { + final double time, final double bitrate, final double speed) { final Statistics statistics = new Statistics(sessionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed); final Session session = getSession(sessionId); diff --git a/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java b/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java index 485e977..e9446fe 100644 --- a/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java +++ b/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java @@ -85,7 +85,7 @@ public class NativeLoader { } static String loadVersion() { - final String version = "5.1"; + final String version = "6.0"; if (isTestModeDisabled()) { return FFmpegKitConfig.getVersion(); diff --git a/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/Statistics.java b/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/Statistics.java index f65f1f7..14768bb 100644 --- a/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/Statistics.java +++ b/android/ffmpeg-kit-android-lib/src/main/java/com/arthenica/ffmpegkit/Statistics.java @@ -28,11 +28,11 @@ public class Statistics { private float videoFps; private float videoQuality; private long size; - private int time; + private double time; private double bitrate; private double speed; - public Statistics(final long sessionId, final int videoFrameNumber, final float videoFps, final float videoQuality, final long size, final int time, final double bitrate, final double speed) { + public Statistics(final long sessionId, final int videoFrameNumber, final float videoFps, final float videoQuality, final long size, final double time, final double bitrate, final double speed) { this.sessionId = sessionId; this.videoFrameNumber = videoFrameNumber; this.videoFps = videoFps; @@ -83,11 +83,11 @@ public class Statistics { this.size = size; } - public int getTime() { + public double getTime() { return time; } - public void setTime(int time) { + public void setTime(double time) { this.time = time; } diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 21e67dd..ee65090 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -69,7 +69,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_mux.c fftools_ffmpeg_opt.c fftools_opt_common.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c +MY_SRC_FILES := ffmpegkit.c ffprobekit.c ffmpegkit_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_mux.c fftools_ffmpeg_mux_init.c fftools_ffmpeg_demux.c fftools_ffmpeg_opt.c fftools_opt_common.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c fftools_objpool.c fftools_sync_queue.c fftools_thread_queue.c ifeq ($(TARGET_PLATFORM),android-16) MY_SRC_FILES += android_lts_support.c diff --git a/apple/Doxyfile b/apple/Doxyfile index 9e92dbd..4fe932c 100644 --- a/apple/Doxyfile +++ b/apple/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "FFmpegKit iOS / macOS / tvOS API" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 5.1 +PROJECT_NUMBER = 6.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/apple/README.md b/apple/README.md index f6c6ec3..990a200 100644 --- a/apple/README.md +++ b/apple/README.md @@ -111,17 +111,17 @@ All libraries created can be found under the `prebuilt` directory. - iOS ```yaml - pod 'ffmpeg-kit-ios-full', '~> 5.1' + pod 'ffmpeg-kit-ios-full', '~> 6.0' ``` - macOS ```yaml - pod 'ffmpeg-kit-macos-full', '~> 5.1' + pod 'ffmpeg-kit-macos-full', '~> 6.0' ``` - tvOS ```yaml - pod 'ffmpeg-kit-tvos-full', '~> 5.1' + pod 'ffmpeg-kit-tvos-full', '~> 6.0' ``` 2. Execute synchronous `FFmpeg` commands. diff --git a/apple/configure.ac b/apple/configure.ac index 0620d34..af7ff7e 100644 --- a/apple/configure.ac +++ b/apple/configure.ac @@ -1,6 +1,6 @@ -# ffmpeg-kit 5.1 configure.ac +# ffmpeg-kit 6.0 configure.ac -AC_INIT([ffmpeg-kit], [5.1], [https://github.com/arthenica/ffmpeg-kit/issues/new]) +AC_INIT([ffmpeg-kit], [6.0], [https://github.com/arthenica/ffmpeg-kit/issues/new]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([src/FFmpegKit.m]) diff --git a/apple/src/FFmpegKitConfig.m b/apple/src/FFmpegKitConfig.m index 73de6ab..774f3f6 100644 --- a/apple/src/FFmpegKitConfig.m +++ b/apple/src/FFmpegKitConfig.m @@ -36,7 +36,7 @@ #import "SessionState.h" /** Global library version */ -NSString* const FFmpegKitVersion = @"5.1"; +NSString* const FFmpegKitVersion = @"6.0"; /** * Prefix of named pipes created by ffmpeg-kit. @@ -150,7 +150,7 @@ void addSessionToSessionHistory(id session) { float _statisticsFps; // statistics fps float _statisticsQuality; // statistics quality int64_t _statisticsSize; // statistics size - int _statisticsTime; // statistics time + double _statisticsTime; // statistics time double _statisticsBitrate; // statistics bitrate double _statisticsSpeed; // statistics speed } @@ -173,7 +173,7 @@ void addSessionToSessionHistory(id session) { fps:(float)videoFps quality:(float)videoQuality size:(int64_t)size - time:(int)time + time:(double)time bitrate:(double)bitrate speed:(double)speed { self = [super init]; @@ -224,7 +224,7 @@ void addSessionToSessionHistory(id session) { return _statisticsSize; } -- (int)getStatisticsTime { +- (double)getStatisticsTime { return _statisticsTime; } @@ -340,7 +340,7 @@ void logCallbackDataAdd(int level, AVBPrint *data) { /** * Adds statistics data to the end of callback data list. */ -void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) { +void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, double time, double bitrate, double speed) { CallbackData *callbackData = [[CallbackData alloc] init:globalSessionId videoFrameNumber:frameNumber fps:fps quality:quality size:size time:time bitrate:bitrate speed:speed]; [lock lock]; @@ -481,7 +481,7 @@ void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, v * @param bitrate output bit rate in kbits/s * @param speed processing speed = processed duration / operation duration */ -void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) { +void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, double time, double bitrate, double speed) { statisticsCallbackDataAdd(frameNumber, fps, quality, size, time, bitrate, speed); } @@ -575,7 +575,7 @@ void process_log(long sessionId, int levelValue, AVBPrint* logMessage) { } } -void process_statistics(long sessionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) { +void process_statistics(long sessionId, int videoFrameNumber, float videoFps, float videoQuality, long size, double time, double bitrate, double speed) { Statistics *statistics = [[Statistics alloc] init:sessionId videoFrameNumber:videoFrameNumber videoFps:videoFps videoQuality:videoQuality size:size time:time bitrate:bitrate speed:speed]; diff --git a/apple/src/Makefile.am b/apple/src/Makefile.am index c0c3cd5..be0bcd4 100644 --- a/apple/src/Makefile.am +++ b/apple/src/Makefile.am @@ -23,12 +23,17 @@ libffmpegkit_la_SOURCES = \ ffmpegkit_exception.m \ fftools_cmdutils.c \ fftools_ffmpeg.c \ + fftools_ffmpeg_demux.c \ fftools_ffmpeg_filter.c \ fftools_ffmpeg_hw.c \ fftools_ffmpeg_mux.c \ + fftools_ffmpeg_mux_init.c \ fftools_ffmpeg_opt.c \ fftools_ffprobe.c \ - fftools_opt_common.c + fftools_objpool.c \ + fftools_opt_common.c \ + fftools_sync_queue.c \ + fftools_thread_queue.c include_HEADERS = \ AbstractSession.h \ @@ -60,8 +65,12 @@ include_HEADERS = \ ffmpegkit_exception.h \ fftools_cmdutils.h \ fftools_ffmpeg.h \ + fftools_ffmpeg_mux.h \ fftools_fopen_utf8.h \ - fftools_opt_common.h + fftools_objpool.h \ + fftools_opt_common.h \ + fftools_sync_queue.h \ + fftools_thread_queue.h libffmpegkit_la_CFLAGS = $(CFLAGS) libffmpegkit_la_OBJCFLAGS = $(CFLAGS) diff --git a/apple/src/Makefile.in b/apple/src/Makefile.in index 8548bfe..b7580fb 100644 --- a/apple/src/Makefile.in +++ b/apple/src/Makefile.in @@ -147,12 +147,17 @@ am_libffmpegkit_la_OBJECTS = libffmpegkit_la-AbstractSession.lo \ libffmpegkit_la-ffmpegkit_exception.lo \ libffmpegkit_la-fftools_cmdutils.lo \ libffmpegkit_la-fftools_ffmpeg.lo \ + libffmpegkit_la-fftools_ffmpeg_demux.lo \ libffmpegkit_la-fftools_ffmpeg_filter.lo \ libffmpegkit_la-fftools_ffmpeg_hw.lo \ libffmpegkit_la-fftools_ffmpeg_mux.lo \ + libffmpegkit_la-fftools_ffmpeg_mux_init.lo \ libffmpegkit_la-fftools_ffmpeg_opt.lo \ libffmpegkit_la-fftools_ffprobe.lo \ - libffmpegkit_la-fftools_opt_common.lo + libffmpegkit_la-fftools_objpool.lo \ + libffmpegkit_la-fftools_opt_common.lo \ + libffmpegkit_la-fftools_sync_queue.lo \ + libffmpegkit_la-fftools_thread_queue.lo libffmpegkit_la_OBJECTS = $(am_libffmpegkit_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) @@ -197,12 +202,17 @@ am__depfiles_remade = ./$(DEPDIR)/libffmpegkit_la-AbstractSession.Plo \ ./$(DEPDIR)/libffmpegkit_la-ffmpegkit_exception.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_cmdutils.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_hw.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffprobe.Plo \ - ./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo + ./$(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -415,12 +425,17 @@ libffmpegkit_la_SOURCES = \ ffmpegkit_exception.m \ fftools_cmdutils.c \ fftools_ffmpeg.c \ + fftools_ffmpeg_demux.c \ fftools_ffmpeg_filter.c \ fftools_ffmpeg_hw.c \ fftools_ffmpeg_mux.c \ + fftools_ffmpeg_mux_init.c \ fftools_ffmpeg_opt.c \ fftools_ffprobe.c \ - fftools_opt_common.c + fftools_objpool.c \ + fftools_opt_common.c \ + fftools_sync_queue.c \ + fftools_thread_queue.c include_HEADERS = \ AbstractSession.h \ @@ -452,8 +467,12 @@ include_HEADERS = \ ffmpegkit_exception.h \ fftools_cmdutils.h \ fftools_ffmpeg.h \ + fftools_ffmpeg_mux.h \ fftools_fopen_utf8.h \ - fftools_opt_common.h + fftools_objpool.h \ + fftools_opt_common.h \ + fftools_sync_queue.h \ + fftools_thread_queue.h libffmpegkit_la_CFLAGS = $(CFLAGS) libffmpegkit_la_OBJCFLAGS = $(CFLAGS) @@ -557,12 +576,17 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-ffmpegkit_exception.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_cmdutils.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_hw.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffprobe.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @@ -608,6 +632,13 @@ libffmpegkit_la-fftools_ffmpeg.lo: fftools_ffmpeg.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffmpeg.lo `test -f 'fftools_ffmpeg.c' || echo '$(srcdir)/'`fftools_ffmpeg.c +libffmpegkit_la-fftools_ffmpeg_demux.lo: fftools_ffmpeg_demux.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_ffmpeg_demux.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Tpo -c -o libffmpegkit_la-fftools_ffmpeg_demux.lo `test -f 'fftools_ffmpeg_demux.c' || echo '$(srcdir)/'`fftools_ffmpeg_demux.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Tpo $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_ffmpeg_demux.c' object='libffmpegkit_la-fftools_ffmpeg_demux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffmpeg_demux.lo `test -f 'fftools_ffmpeg_demux.c' || echo '$(srcdir)/'`fftools_ffmpeg_demux.c + libffmpegkit_la-fftools_ffmpeg_filter.lo: fftools_ffmpeg_filter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_ffmpeg_filter.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Tpo -c -o libffmpegkit_la-fftools_ffmpeg_filter.lo `test -f 'fftools_ffmpeg_filter.c' || echo '$(srcdir)/'`fftools_ffmpeg_filter.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Tpo $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo @@ -629,6 +660,13 @@ libffmpegkit_la-fftools_ffmpeg_mux.lo: fftools_ffmpeg_mux.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffmpeg_mux.lo `test -f 'fftools_ffmpeg_mux.c' || echo '$(srcdir)/'`fftools_ffmpeg_mux.c +libffmpegkit_la-fftools_ffmpeg_mux_init.lo: fftools_ffmpeg_mux_init.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_ffmpeg_mux_init.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Tpo -c -o libffmpegkit_la-fftools_ffmpeg_mux_init.lo `test -f 'fftools_ffmpeg_mux_init.c' || echo '$(srcdir)/'`fftools_ffmpeg_mux_init.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Tpo $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_ffmpeg_mux_init.c' object='libffmpegkit_la-fftools_ffmpeg_mux_init.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffmpeg_mux_init.lo `test -f 'fftools_ffmpeg_mux_init.c' || echo '$(srcdir)/'`fftools_ffmpeg_mux_init.c + libffmpegkit_la-fftools_ffmpeg_opt.lo: fftools_ffmpeg_opt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_ffmpeg_opt.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Tpo -c -o libffmpegkit_la-fftools_ffmpeg_opt.lo `test -f 'fftools_ffmpeg_opt.c' || echo '$(srcdir)/'`fftools_ffmpeg_opt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Tpo $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo @@ -643,6 +681,13 @@ libffmpegkit_la-fftools_ffprobe.lo: fftools_ffprobe.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffprobe.lo `test -f 'fftools_ffprobe.c' || echo '$(srcdir)/'`fftools_ffprobe.c +libffmpegkit_la-fftools_objpool.lo: fftools_objpool.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_objpool.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_objpool.Tpo -c -o libffmpegkit_la-fftools_objpool.lo `test -f 'fftools_objpool.c' || echo '$(srcdir)/'`fftools_objpool.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_objpool.Tpo $(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_objpool.c' object='libffmpegkit_la-fftools_objpool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_objpool.lo `test -f 'fftools_objpool.c' || echo '$(srcdir)/'`fftools_objpool.c + libffmpegkit_la-fftools_opt_common.lo: fftools_opt_common.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_opt_common.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_opt_common.Tpo -c -o libffmpegkit_la-fftools_opt_common.lo `test -f 'fftools_opt_common.c' || echo '$(srcdir)/'`fftools_opt_common.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_opt_common.Tpo $(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo @@ -650,6 +695,20 @@ libffmpegkit_la-fftools_opt_common.lo: fftools_opt_common.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_opt_common.lo `test -f 'fftools_opt_common.c' || echo '$(srcdir)/'`fftools_opt_common.c +libffmpegkit_la-fftools_sync_queue.lo: fftools_sync_queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_sync_queue.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Tpo -c -o libffmpegkit_la-fftools_sync_queue.lo `test -f 'fftools_sync_queue.c' || echo '$(srcdir)/'`fftools_sync_queue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Tpo $(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_sync_queue.c' object='libffmpegkit_la-fftools_sync_queue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_sync_queue.lo `test -f 'fftools_sync_queue.c' || echo '$(srcdir)/'`fftools_sync_queue.c + +libffmpegkit_la-fftools_thread_queue.lo: fftools_thread_queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_thread_queue.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Tpo -c -o libffmpegkit_la-fftools_thread_queue.lo `test -f 'fftools_thread_queue.c' || echo '$(srcdir)/'`fftools_thread_queue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Tpo $(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_thread_queue.c' object='libffmpegkit_la-fftools_thread_queue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_thread_queue.lo `test -f 'fftools_thread_queue.c' || echo '$(srcdir)/'`fftools_thread_queue.c + .m.o: @am__fastdepOBJC_TRUE@ $(AM_V_OBJC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @am__fastdepOBJC_TRUE@ $(OBJCCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @@ -974,12 +1033,17 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/libffmpegkit_la-ffmpegkit_exception.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_cmdutils.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_hw.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffprobe.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -1045,12 +1109,17 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libffmpegkit_la-ffmpegkit_exception.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_cmdutils.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_hw.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffprobe.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic diff --git a/apple/src/Statistics.h b/apple/src/Statistics.h index 3b4f786..44221e1 100644 --- a/apple/src/Statistics.h +++ b/apple/src/Statistics.h @@ -27,7 +27,7 @@ */ @interface Statistics : NSObject -- (instancetype)init:(long)sessionId videoFrameNumber:(int)videoFrameNumber videoFps:(float)videoFps videoQuality:(float)videoQuality size:(int64_t)size time:(int)time bitrate:(double)bitrate speed:(double)speed; +- (instancetype)init:(long)sessionId videoFrameNumber:(int)videoFrameNumber videoFps:(float)videoFps videoQuality:(float)videoQuality size:(int64_t)size time:(double)time bitrate:(double)bitrate speed:(double)speed; - (long)getSessionId; @@ -39,7 +39,7 @@ - (long)getSize; -- (int)getTime; +- (double)getTime; - (double)getBitrate; diff --git a/apple/src/Statistics.m b/apple/src/Statistics.m index 4016ab9..3f32493 100644 --- a/apple/src/Statistics.m +++ b/apple/src/Statistics.m @@ -25,12 +25,12 @@ float _videoFps; float _videoQuality; long _size; - int _time; + double _time; double _bitrate; double _speed; } -- (instancetype)init:(long)sessionId videoFrameNumber:(int)videoFrameNumber videoFps:(float)videoFps videoQuality:(float)videoQuality size:(int64_t)size time:(int)time bitrate:(double)bitrate speed:(double)speed { +- (instancetype)init:(long)sessionId videoFrameNumber:(int)videoFrameNumber videoFps:(float)videoFps videoQuality:(float)videoQuality size:(int64_t)size time:(double)time bitrate:(double)bitrate speed:(double)speed { self = [super init]; if (self) { _sessionId = sessionId; @@ -66,7 +66,7 @@ return _size; } -- (int)getTime { +- (double)getTime { return _time; } diff --git a/apple/src/fftools_cmdutils.c b/apple/src/fftools_cmdutils.c index b895179..09a21d2 100644 --- a/apple/src/fftools_cmdutils.c +++ b/apple/src/fftools_cmdutils.c @@ -2,6 +2,7 @@ * Various utilities for command line tools * Copyright (c) 2000-2003 Fabrice Bellard * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -25,6 +26,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -129,11 +136,18 @@ void register_exit(void (*cb)(int ret)) program_exit = cb; } +void report_and_exit(int ret) +{ + av_log(NULL, AV_LOG_FATAL, "%s\n", av_err2str(ret)); + exit_program(AVUNERROR(ret)); +} + void exit_program(int ret) { if (program_exit) program_exit(ret); + // FFmpegKit // exit disabled and replaced with longjmp, exit value stored in longjmp_value // exit(ret); longjmp_value = ret; @@ -696,7 +710,7 @@ static void init_parse_context(OptionParseContext *octx, octx->nb_groups = nb_groups; octx->groups = av_calloc(octx->nb_groups, sizeof(*octx->groups)); if (!octx->groups) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); for (i = 0; i < octx->nb_groups; i++) octx->groups[i].group_def = &groups[i]; @@ -843,12 +857,7 @@ do { \ void print_error(const char *filename, int err) { - char errbuf[128]; - const char *errbuf_ptr = errbuf; - - if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) - errbuf_ptr = strerror(AVUNERROR(err)); - av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, errbuf_ptr); + av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, av_err2str(err)); } int read_yesno(void) @@ -971,7 +980,7 @@ AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, break; } - while ((t = av_dict_get(opts, "", t, AV_DICT_IGNORE_SUFFIX))) { + while ((t = av_dict_iterate(opts, t))) { const AVClass *priv_class; char *p = strchr(t->key, ':'); @@ -1009,11 +1018,8 @@ AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, if (!s->nb_streams) return NULL; opts = av_calloc(s->nb_streams, sizeof(*opts)); - if (!opts) { - av_log(NULL, AV_LOG_ERROR, - "Could not alloc memory for stream options.\n"); - exit_program(1); - } + if (!opts) + report_and_exit(AVERROR(ENOMEM)); for (i = 0; i < s->nb_streams; i++) opts[i] = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id, s, s->streams[i], NULL); @@ -1028,10 +1034,8 @@ void *grow_array(void *array, int elem_size, int *size, int new_size) } if (*size < new_size) { uint8_t *tmp = av_realloc_array(array, new_size, elem_size); - if (!tmp) { - av_log(NULL, AV_LOG_ERROR, "Could not alloc buffer.\n"); - exit_program(1); - } + if (!tmp) + report_and_exit(AVERROR(ENOMEM)); memset(tmp + *size*elem_size, 0, (new_size-*size) * elem_size); *size = new_size; return tmp; @@ -1044,10 +1048,8 @@ void *allocate_array_elem(void *ptr, size_t elem_size, int *nb_elems) void *new_elem; if (!(new_elem = av_mallocz(elem_size)) || - av_dynarray_add_nofree(ptr, nb_elems, new_elem) < 0) { - av_log(NULL, AV_LOG_ERROR, "Could not alloc buffer.\n"); - exit_program(1); - } + av_dynarray_add_nofree(ptr, nb_elems, new_elem) < 0) + report_and_exit(AVERROR(ENOMEM)); return new_elem; } diff --git a/apple/src/fftools_cmdutils.h b/apple/src/fftools_cmdutils.h index 559817a..b925cf0 100644 --- a/apple/src/fftools_cmdutils.h +++ b/apple/src/fftools_cmdutils.h @@ -2,6 +2,7 @@ * Various utilities for command line tools * copyright (c) 2003 Fabrice Bellard * copyright (c) 2018-2022 Taner Sener + * copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -25,6 +26,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -95,6 +102,17 @@ extern __thread int find_stream_info; */ void register_exit(void (*cb)(int ret)); +/** + * Reports an error corresponding to the provided + * AVERROR code and calls exit_program() with the + * corresponding POSIX error code. + * @note ret must be an AVERROR-value of a POSIX error code + * (i.e. AVERROR(EFOO) and not AVERROR_FOO). + * library functions can return both, so call this only + * with AVERROR(EFOO) of your own. + */ +void report_and_exit(int ret) av_noreturn; + /** * Wraps exit with a program-specific cleanup routine. */ @@ -232,11 +250,6 @@ void show_help_children(const AVClass *clazz, int flags); void show_help_default_ffmpeg(const char *opt, const char *arg); void show_help_default_ffprobe(const char *opt, const char *arg); -/** - * Generic -h handler common to all fftools. - */ -int show_help(void *optctx, const char *opt, const char *arg); - /** * Parse the command line arguments. * diff --git a/apple/src/fftools_ffmpeg.c b/apple/src/fftools_ffmpeg.c index 67d4683..731128f 100644 --- a/apple/src/fftools_ffmpeg.c +++ b/apple/src/fftools_ffmpeg.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2000-2003 Fabrice Bellard * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -29,6 +30,15 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - vstats_file, received_sigterm and received_nb_signals updated as thread-local + * - forward_report method signature updated + * - time field in report_callback/forward_report/set_report_callback updated as double + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -160,19 +170,18 @@ #include "fftools_ffmpeg.h" #include "fftools_cmdutils.h" +#include "fftools_sync_queue.h" #include "libavutil/avassert.h" -static FILE *vstats_file; +static __thread FILE *vstats_file; -const char *const forced_keyframes_const_names[] = { - "n", - "n_forced", - "prev_forced_n", - "prev_forced_t", - "t", - NULL -}; +// optionally attached as opaque_ref to decoded AVFrames +typedef struct FrameData { + uint64_t idx; + int64_t pts; + AVRational tb; +} FrameData; typedef struct BenchmarkTimeStamps { int64_t real_usec; @@ -180,6 +189,7 @@ typedef struct BenchmarkTimeStamps { int64_t sys_usec; } BenchmarkTimeStamps; +static int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt); static BenchmarkTimeStamps get_benchmark_time_stamps(void); static int64_t getmaxrss(void); static int ifilter_has_all_input_formats(FilterGraph *fg); @@ -190,20 +200,12 @@ __thread int64_t nb_frames_drop = 0; __thread int64_t decode_error_stat[2]; __thread unsigned nb_output_dumped = 0; -__thread int want_sdp = 1; - __thread BenchmarkTimeStamps current_time; __thread AVIOContext *progress_avio = NULL; -__thread uint8_t *subtitle_out; - -__thread InputStream **input_streams = NULL; -__thread int nb_input_streams = 0; __thread InputFile **input_files = NULL; __thread int nb_input_files = 0; -__thread OutputStream **output_streams = NULL; -__thread int nb_output_streams = 0; __thread OutputFile **output_files = NULL; __thread int nb_output_files = 0; @@ -215,7 +217,7 @@ __thread int64_t keyboard_last_time = 0; __thread int first_report = 1; __thread int qp_histogram[52]; -void (*report_callback)(int, float, float, int64_t, int, double, double) = NULL; +void (*report_callback)(int, float, float, int64_t, double, double, double) = NULL; extern int opt_map(void *optctx, const char *opt, const char *arg); extern int opt_map_channel(void *optctx, const char *opt, const char *arg); @@ -263,7 +265,6 @@ extern int opt_data_codec(void *optctx, const char *opt, const char *arg); extern int opt_init_hw_device(void *optctx, const char *opt, const char *arg); extern int opt_filter_hw_device(void *optctx, const char *opt, const char *arg); extern int opt_filter_threads(void *optctx, const char *opt, const char *arg); -extern __thread int input_sync; extern __thread int file_overwrite; extern __thread int no_file_overwrite; extern __thread int do_psnr; @@ -278,10 +279,6 @@ __thread struct termios oldtty; __thread int restore_tty; #endif -#if HAVE_THREADS -static void free_input_threads(void); -#endif - extern volatile int handleSIGQUIT; extern volatile int handleSIGINT; extern volatile int handleSIGTERM; @@ -388,7 +385,7 @@ void sub2video_update(InputStream *ist, int64_t heartbeat_pts, AVSubtitle *sub) num_rects = 0; } if (sub2video_get_blank_frame(ist) < 0) { - av_log(ist->dec_ctx, AV_LOG_ERROR, + av_log(NULL, AV_LOG_ERROR, "Impossible to get a blank canvas.\n"); return; } @@ -412,7 +409,7 @@ static void sub2video_heartbeat(InputStream *ist, int64_t pts) video frames could be accumulating in the filter graph while a filter (possibly overlay) is desperately waiting for a subtitle frame. */ for (i = 0; i < infile->nb_streams; i++) { - InputStream *ist2 = input_streams[infile->ist_index + i]; + InputStream *ist2 = infile->streams[i]; if (!ist2->sub2video.frame) continue; /* subtitles seem to be usually muxed ahead of other streams; @@ -463,20 +460,32 @@ void term_exit(void) term_exit_sigsafe(); } -static volatile int received_sigterm = 0; -static volatile int received_nb_signals = 0; +__thread int received_sigterm = 0; +__thread int received_nb_signals = 0; __thread atomic_int transcode_init_done = ATOMIC_VAR_INIT(0); __thread int ffmpeg_exited = 0; __thread int main_ffmpeg_return_code = 0; __thread int64_t copy_ts_first_pts = AV_NOPTS_VALUE; extern __thread int longjmp_value; +extern __thread int want_sdp; +struct EncStatsFile; +extern __thread struct EncStatsFile *enc_stats_files; +extern __thread int nb_enc_stats_files; static void sigterm_handler(int sig) { + // int ret; received_sigterm = sig; received_nb_signals++; term_exit_sigsafe(); + // FFmpegKit - Hard Exit Disabled + // if(received_nb_signals > 3) { + // ret = write(2/*STDERR_FILENO*/, "Received > 3 system signals, hard exiting\n", + // strlen("Received > 3 system signals, hard exiting\n")); + // if (ret < 0) { /* Do nothing */ }; + // exit(123); + // } } #if HAVE_SETCONSOLECTRLHANDLER @@ -695,72 +704,12 @@ static void ffmpeg_cleanup(int ret) } av_freep(&filtergraphs); - av_freep(&subtitle_out); - /* close files */ for (i = 0; i < nb_output_files; i++) of_close(&output_files[i]); - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!ost) - continue; - - av_bsf_free(&ost->bsf_ctx); - - av_frame_free(&ost->filtered_frame); - av_frame_free(&ost->last_frame); - av_packet_free(&ost->pkt); - av_dict_free(&ost->encoder_opts); - - av_freep(&ost->forced_keyframes); - av_expr_free(ost->forced_keyframes_pexpr); - av_freep(&ost->avfilter); - av_freep(&ost->logfile_prefix); - - av_freep(&ost->audio_channels_map); - ost->audio_channels_mapped = 0; - - av_dict_free(&ost->sws_dict); - av_dict_free(&ost->swr_opts); - - avcodec_free_context(&ost->enc_ctx); - avcodec_parameters_free(&ost->ref_par); - - if (ost->muxing_queue) { - AVPacket *pkt; - while (av_fifo_read(ost->muxing_queue, &pkt, 1) >= 0) - av_packet_free(&pkt); - av_fifo_freep2(&ost->muxing_queue); - } - - av_freep(&output_streams[i]); - } -#if HAVE_THREADS - free_input_threads(); -#endif - for (i = 0; i < nb_input_files; i++) { - avformat_close_input(&input_files[i]->ctx); - av_packet_free(&input_files[i]->pkt); - av_freep(&input_files[i]); - } - for (i = 0; i < nb_input_streams; i++) { - InputStream *ist = input_streams[i]; - - av_frame_free(&ist->decoded_frame); - av_packet_free(&ist->pkt); - av_dict_free(&ist->decoder_opts); - avsubtitle_free(&ist->prev_sub.subtitle); - av_frame_free(&ist->sub2video.frame); - av_freep(&ist->filters); - av_freep(&ist->hwaccel_device); - av_freep(&ist->dts_buffer); - - avcodec_free_context(&ist->dec_ctx); - - av_freep(&input_streams[i]); - } + for (i = 0; i < nb_input_files; i++) + ifile_close(&input_files[i]); if (vstats_file) { if (fclose(vstats_file)) @@ -769,11 +718,11 @@ static void ffmpeg_cleanup(int ret) av_err2str(AVERROR(errno))); } av_freep(&vstats_filename); + of_enc_stats_close(); + av_freep(&filter_nbthreads); - av_freep(&input_streams); av_freep(&input_files); - av_freep(&output_streams); av_freep(&output_files); uninit_opts(); @@ -792,11 +741,45 @@ static void ffmpeg_cleanup(int ret) ffmpeg_exited = 1; } +/* iterate over all output streams in all output files; + * pass NULL to start iteration */ +static OutputStream *ost_iter(OutputStream *prev) +{ + int of_idx = prev ? prev->file_index : 0; + int ost_idx = prev ? prev->index + 1 : 0; + + for (; of_idx < nb_output_files; of_idx++) { + OutputFile *of = output_files[of_idx]; + if (ost_idx < of->nb_streams) + return of->streams[ost_idx]; + + ost_idx = 0; + } + + return NULL; +} + +InputStream *ist_iter(InputStream *prev) +{ + int if_idx = prev ? prev->file_index : 0; + int ist_idx = prev ? prev->st->index + 1 : 0; + + for (; if_idx < nb_input_files; if_idx++) { + InputFile *f = input_files[if_idx]; + if (ist_idx < f->nb_streams) + return f->streams[ist_idx]; + + ist_idx = 0; + } + + return NULL; +} + void remove_avoptions(AVDictionary **a, AVDictionary *b) { const AVDictionaryEntry *t = NULL; - while ((t = av_dict_get(b, "", t, AV_DICT_IGNORE_SUFFIX))) { + while ((t = av_dict_iterate(b, t))) { av_dict_set(a, t->key, NULL, AV_DICT_MATCH_CASE); } } @@ -839,59 +822,18 @@ static void update_benchmark(const char *fmt, ...) static void close_output_stream(OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - AVRational time_base = ost->stream_copy ? ost->mux_timebase : ost->enc_ctx->time_base; - ost->finished |= ENCODER_FINISHED; - if (of->shortest) { - int64_t end = av_rescale_q(ost->sync_opts - ost->first_pts, time_base, AV_TIME_BASE_Q); - of->recording_time = FFMIN(of->recording_time, end); - } -} -/* - * Send a single packet to the output, applying any bitstream filters - * associated with the output stream. This may result in any number - * of packets actually being written, depending on what bitstream - * filters are applied. The supplied packet is consumed and will be - * blank (as if newly-allocated) when this function returns. - * - * If eof is set, instead indicate EOF to all bitstream filters and - * therefore flush any delayed packets to the output. A blank packet - * must be supplied in this case. - */ -static void output_packet(OutputFile *of, AVPacket *pkt, - OutputStream *ost, int eof) -{ - int ret = 0; - - /* apply the output bitstream filters */ - if (ost->bsf_ctx) { - ret = av_bsf_send_packet(ost->bsf_ctx, eof ? NULL : pkt); - if (ret < 0) - goto finish; - while ((ret = av_bsf_receive_packet(ost->bsf_ctx, pkt)) >= 0) - of_write_packet(of, pkt, ost, 0); - if (ret == AVERROR(EAGAIN)) - ret = 0; - } else if (!eof) - of_write_packet(of, pkt, ost, 0); - -finish: - if (ret < 0 && ret != AVERROR_EOF) { - av_log(NULL, AV_LOG_ERROR, "Error applying bitstream filters to an output " - "packet for stream #%d:%d.\n", ost->file_index, ost->index); - if(exit_on_error) - exit_program(1); - } + if (ost->sq_idx_encode >= 0) + sq_send(of->sq_encode, ost->sq_idx_encode, SQFRAME(NULL)); } -static int check_recording_time(OutputStream *ost) +static int check_recording_time(OutputStream *ost, int64_t ts, AVRational tb) { OutputFile *of = output_files[ost->file_index]; if (of->recording_time != INT64_MAX && - av_compare_ts(ost->sync_opts - ost->first_pts, ost->enc_ctx->time_base, of->recording_time, - AV_TIME_BASE_Q) >= 0) { + av_compare_ts(ts, tb, of->recording_time, AV_TIME_BASE_Q) >= 0) { close_output_stream(ost); return 0; } @@ -902,38 +844,36 @@ static double adjust_frame_pts_to_encoder_tb(OutputFile *of, OutputStream *ost, AVFrame *frame) { double float_pts = AV_NOPTS_VALUE; // this is identical to frame.pts but with higher precision - AVCodecContext *enc = ost->enc_ctx; - if (!frame || frame->pts == AV_NOPTS_VALUE || - !enc || !ost->filter || !ost->filter->graph->graph) - goto early_exit; + const int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? + 0 : of->start_time; - { - AVFilterContext *filter = ost->filter->filter; + AVCodecContext *const enc = ost->enc_ctx; - int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; - AVRational filter_tb = av_buffersink_get_time_base(filter); - AVRational tb = enc->time_base; - int extra_bits = av_clip(29 - av_log2(tb.den), 0, 16); - - tb.den <<= extra_bits; - float_pts = - av_rescale_q(frame->pts, filter_tb, tb) - - av_rescale_q(start_time, AV_TIME_BASE_Q, tb); - float_pts /= 1 << extra_bits; - // avoid exact midoints to reduce the chance of rounding differences, this can be removed in case the fps code is changed to work with integers - float_pts += FFSIGN(float_pts) * 1.0 / (1<<17); + AVRational tb = enc->time_base; + AVRational filter_tb = frame->time_base; + const int extra_bits = av_clip(29 - av_log2(tb.den), 0, 16); - frame->pts = - av_rescale_q(frame->pts, filter_tb, enc->time_base) - - av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); - } + if (frame->pts == AV_NOPTS_VALUE) + goto early_exit; + + tb.den <<= extra_bits; + float_pts = av_rescale_q(frame->pts, filter_tb, tb) - + av_rescale_q(start_time, AV_TIME_BASE_Q, tb); + float_pts /= 1 << extra_bits; + // avoid exact midoints to reduce the chance of rounding differences, this + // can be removed in case the fps code is changed to work with integers + float_pts += FFSIGN(float_pts) * 1.0 / (1<<17); + + frame->pts = av_rescale_q(frame->pts, filter_tb, enc->time_base) - + av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); + frame->time_base = enc->time_base; early_exit: if (debug_ts) { av_log(NULL, AV_LOG_INFO, "filter -> pts:%s pts_time:%s exact:%f time_base:%d/%d\n", frame ? av_ts2str(frame->pts) : "NULL", - frame ? av_ts2timestr(frame->pts, &enc->time_base) : "NULL", + (enc && frame) ? av_ts2timestr(frame->pts, &enc->time_base) : "NULL", float_pts, enc ? enc->time_base.num : -1, enc ? enc->time_base.den : -1); @@ -956,8 +896,8 @@ static int init_output_stream_wrapper(OutputStream *ost, AVFrame *frame, ret = init_output_stream(ost, frame, error, sizeof(error)); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n", - ost->file_index, ost->index, error); + av_log(ost, AV_LOG_ERROR, "Error initializing output stream: %s\n", + error); if (fatal) exit_program(1); @@ -1015,17 +955,83 @@ static void update_video_stats(OutputStream *ost, const AVPacket *pkt, int write fprintf(vstats_file,"f_size= %6d ", pkt->size); /* compute pts value */ - ti1 = pkt->dts * av_q2d(ost->mux_timebase); + ti1 = pkt->dts * av_q2d(pkt->time_base); if (ti1 < 0.01) ti1 = 0.01; bitrate = (pkt->size * 8) / av_q2d(enc->time_base) / 1000.0; - avg_bitrate = (double)(ost->data_size * 8) / ti1 / 1000.0; + avg_bitrate = (double)(ost->data_size_enc * 8) / ti1 / 1000.0; fprintf(vstats_file, "s_size= %8.0fkB time= %0.3f br= %7.1fkbits/s avg_br= %7.1fkbits/s ", - (double)ost->data_size / 1024, ti1, bitrate, avg_bitrate); + (double)ost->data_size_enc / 1024, ti1, bitrate, avg_bitrate); fprintf(vstats_file, "type= %c\n", av_get_picture_type_char(ost->pict_type)); } +void enc_stats_write(OutputStream *ost, EncStats *es, + const AVFrame *frame, const AVPacket *pkt, + uint64_t frame_num) +{ + AVIOContext *io = es->io; + AVRational tb = frame ? frame->time_base : pkt->time_base; + int64_t pts = frame ? frame->pts : pkt->pts; + + AVRational tbi = (AVRational){ 0, 1}; + int64_t ptsi = INT64_MAX; + + const FrameData *fd; + + if ((frame && frame->opaque_ref) || (pkt && pkt->opaque_ref)) { + fd = (const FrameData*)(frame ? frame->opaque_ref->data : pkt->opaque_ref->data); + tbi = fd->tb; + ptsi = fd->pts; + } + + for (size_t i = 0; i < es->nb_components; i++) { + const EncStatsComponent *c = &es->components[i]; + + switch (c->type) { + case ENC_STATS_LITERAL: avio_write (io, c->str, c->str_len); continue; + case ENC_STATS_FILE_IDX: avio_printf(io, "%d", ost->file_index); continue; + case ENC_STATS_STREAM_IDX: avio_printf(io, "%d", ost->index); continue; + case ENC_STATS_TIMEBASE: avio_printf(io, "%d/%d", tb.num, tb.den); continue; + case ENC_STATS_TIMEBASE_IN: avio_printf(io, "%d/%d", tbi.num, tbi.den); continue; + case ENC_STATS_PTS: avio_printf(io, "%"PRId64, pts); continue; + case ENC_STATS_PTS_IN: avio_printf(io, "%"PRId64, ptsi); continue; + case ENC_STATS_PTS_TIME: avio_printf(io, "%g", pts * av_q2d(tb)); continue; + case ENC_STATS_PTS_TIME_IN: avio_printf(io, "%g", ptsi == INT64_MAX ? + INFINITY : ptsi * av_q2d(tbi)); continue; + case ENC_STATS_FRAME_NUM: avio_printf(io, "%"PRIu64, frame_num); continue; + case ENC_STATS_FRAME_NUM_IN: avio_printf(io, "%"PRIu64, fd ? fd->idx : -1); continue; + } + + if (frame) { + switch (c->type) { + case ENC_STATS_SAMPLE_NUM: avio_printf(io, "%"PRIu64, ost->samples_encoded); continue; + case ENC_STATS_NB_SAMPLES: avio_printf(io, "%d", frame->nb_samples); continue; + default: av_assert0(0); + } + } else { + switch (c->type) { + case ENC_STATS_DTS: avio_printf(io, "%"PRId64, pkt->dts); continue; + case ENC_STATS_DTS_TIME: avio_printf(io, "%g", pkt->dts * av_q2d(tb)); continue; + case ENC_STATS_PKT_SIZE: avio_printf(io, "%d", pkt->size); continue; + case ENC_STATS_BITRATE: { + double duration = FFMAX(pkt->duration, 1) * av_q2d(tb); + avio_printf(io, "%g", 8.0 * pkt->size / duration); + continue; + } + case ENC_STATS_AVG_BITRATE: { + double duration = pkt->dts * av_q2d(tb); + avio_printf(io, "%g", duration > 0 ? 8.0 * ost->data_size_enc / duration : -1.); + continue; + } + default: av_assert0(0); + } + } + } + avio_w8(io, '\n'); + avio_flush(io); +} + static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) { AVCodecContext *enc = ost->enc_ctx; @@ -1035,10 +1041,15 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) int ret; if (frame) { + if (ost->enc_stats_pre.io) + enc_stats_write(ost, &ost->enc_stats_pre, frame, NULL, + ost->frames_encoded); + ost->frames_encoded++; + ost->samples_encoded += frame->nb_samples; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder <- type:%s " + av_log(ost, AV_LOG_INFO, "encoder <- type:%s " "frame_pts:%s frame_pts_time:%s time_base:%d/%d\n", type_desc, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &enc->time_base), @@ -1050,7 +1061,7 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) ret = avcodec_send_frame(enc, frame); if (ret < 0 && !(ret == AVERROR_EOF && !frame)) { - av_log(NULL, AV_LOG_ERROR, "Error submitting %s frame to the encoder\n", + av_log(ost, AV_LOG_ERROR, "Error submitting %s frame to the encoder\n", type_desc); return ret; } @@ -1060,6 +1071,8 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) update_benchmark("%s_%s %d.%d", action, type_desc, ost->file_index, ost->index); + pkt->time_base = enc->time_base; + /* if two pass, output log on success and EOF */ if ((ret >= 0 || ret == AVERROR_EOF) && ost->logfile && enc->stats_out) fprintf(ost->logfile, "%s", enc->stats_out); @@ -1068,15 +1081,21 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_assert0(frame); // should never happen during flushing return 0; } else if (ret == AVERROR_EOF) { - output_packet(of, pkt, ost, 1); + of_output_packet(of, pkt, ost, 1); return ret; } else if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "%s encoding failed\n", type_desc); + av_log(ost, AV_LOG_ERROR, "%s encoding failed\n", type_desc); return ret; } + if (enc->codec_type == AVMEDIA_TYPE_VIDEO) + update_video_stats(ost, pkt, !!vstats_filename); + if (ost->enc_stats_post.io) + enc_stats_write(ost, &ost->enc_stats_post, NULL, pkt, + ost->packets_encoded); + if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder -> type:%s " + av_log(ost, AV_LOG_INFO, "encoder -> type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s " "duration:%s duration_time:%s\n", type_desc, @@ -1085,10 +1104,11 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base)); } - av_packet_rescale_ts(pkt, enc->time_base, ost->mux_timebase); + av_packet_rescale_ts(pkt, pkt->time_base, ost->mux_timebase); + pkt->time_base = ost->mux_timebase; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder -> type:%s " + av_log(ost, AV_LOG_INFO, "encoder -> type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s " "duration:%s duration_time:%s\n", type_desc, @@ -1097,34 +1117,92 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base)); } - if (enc->codec_type == AVMEDIA_TYPE_VIDEO) - update_video_stats(ost, pkt, !!vstats_filename); + if ((ret = trigger_fix_sub_duration_heartbeat(ost, pkt)) < 0) { + av_log(NULL, AV_LOG_ERROR, + "Subtitle heartbeat logic failed in %s! (%s)\n", + __func__, av_err2str(ret)); + exit_program(1); + } + + ost->data_size_enc += pkt->size; ost->packets_encoded++; - output_packet(of, pkt, ost, 0); + of_output_packet(of, pkt, ost, 0); } av_assert0(0); } +static int submit_encode_frame(OutputFile *of, OutputStream *ost, + AVFrame *frame) +{ + int ret; + + if (ost->sq_idx_encode < 0) + return encode_frame(of, ost, frame); + + if (frame) { + ret = av_frame_ref(ost->sq_frame, frame); + if (ret < 0) + return ret; + frame = ost->sq_frame; + } + + ret = sq_send(of->sq_encode, ost->sq_idx_encode, + SQFRAME(frame)); + if (ret < 0) { + if (frame) + av_frame_unref(frame); + if (ret != AVERROR_EOF) + return ret; + } + + while (1) { + AVFrame *enc_frame = ost->sq_frame; + + ret = sq_receive(of->sq_encode, ost->sq_idx_encode, + SQFRAME(enc_frame)); + if (ret == AVERROR_EOF) { + enc_frame = NULL; + } else if (ret < 0) { + return (ret == AVERROR(EAGAIN)) ? 0 : ret; + } + + ret = encode_frame(of, ost, enc_frame); + if (enc_frame) + av_frame_unref(enc_frame); + if (ret < 0) { + if (ret == AVERROR_EOF) + close_output_stream(ost); + return ret; + } + } +} + static void do_audio_out(OutputFile *of, OutputStream *ost, AVFrame *frame) { + AVCodecContext *enc = ost->enc_ctx; int ret; - adjust_frame_pts_to_encoder_tb(of, ost, frame); + if (frame->pts == AV_NOPTS_VALUE) + frame->pts = ost->next_pts; + else { + int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; + frame->pts = + av_rescale_q(frame->pts, frame->time_base, enc->time_base) - + av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); + } + frame->time_base = enc->time_base; - if (!check_recording_time(ost)) + if (!check_recording_time(ost, frame->pts, frame->time_base)) return; - if (frame->pts == AV_NOPTS_VALUE || audio_sync_method < 0) - frame->pts = ost->sync_opts; - ost->sync_opts = frame->pts + frame->nb_samples; - ost->samples_encoded += frame->nb_samples; + ost->next_pts = frame->pts + frame->nb_samples; - ret = encode_frame(of, ost, frame); - if (ret < 0) + ret = submit_encode_frame(of, ost, frame); + if (ret < 0 && ret != AVERROR_EOF) exit_program(1); } @@ -1133,13 +1211,13 @@ static void do_subtitle_out(OutputFile *of, AVSubtitle *sub) { int subtitle_out_max_size = 1024 * 1024; - int subtitle_out_size, nb, i; + int subtitle_out_size, nb, i, ret; AVCodecContext *enc; AVPacket *pkt = ost->pkt; int64_t pts; if (sub->pts == AV_NOPTS_VALUE) { - av_log(NULL, AV_LOG_ERROR, "Subtitle packets must have a pts\n"); + av_log(ost, AV_LOG_ERROR, "Subtitle packets must have a pts\n"); if (exit_on_error) exit_program(1); return; @@ -1147,14 +1225,6 @@ static void do_subtitle_out(OutputFile *of, enc = ost->enc_ctx; - if (!subtitle_out) { - subtitle_out = av_malloc(subtitle_out_max_size); - if (!subtitle_out) { - av_log(NULL, AV_LOG_FATAL, "Failed to allocate subtitle_out\n"); - exit_program(1); - } - } - /* Note: DVB subtitle need one packet to draw them and one other packet to clear them */ /* XXX: signal it in the codec context ? */ @@ -1170,10 +1240,13 @@ static void do_subtitle_out(OutputFile *of, for (i = 0; i < nb; i++) { unsigned save_num_rects = sub->num_rects; - ost->sync_opts = av_rescale_q(pts, AV_TIME_BASE_Q, enc->time_base); - if (!check_recording_time(ost)) + if (!check_recording_time(ost, pts, AV_TIME_BASE_Q)) return; + ret = av_new_packet(pkt, subtitle_out_max_size); + if (ret < 0) + report_and_exit(AVERROR(ENOMEM)); + sub->pts = pts; // start_display_time is required to be 0 sub->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, AV_TIME_BASE_Q); @@ -1184,33 +1257,156 @@ static void do_subtitle_out(OutputFile *of, ost->frames_encoded++; - subtitle_out_size = avcodec_encode_subtitle(enc, subtitle_out, - subtitle_out_max_size, sub); + subtitle_out_size = avcodec_encode_subtitle(enc, pkt->data, pkt->size, sub); if (i == 1) sub->num_rects = save_num_rects; if (subtitle_out_size < 0) { - av_log(NULL, AV_LOG_FATAL, "Subtitle encoding failed\n"); + av_log(ost, AV_LOG_FATAL, "Subtitle encoding failed\n"); exit_program(1); } - av_packet_unref(pkt); - pkt->data = subtitle_out; - pkt->size = subtitle_out_size; - pkt->pts = av_rescale_q(sub->pts, AV_TIME_BASE_Q, ost->mux_timebase); - pkt->duration = av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + av_shrink_packet(pkt, subtitle_out_size); + pkt->time_base = ost->mux_timebase; + pkt->pts = av_rescale_q(sub->pts, AV_TIME_BASE_Q, pkt->time_base); + pkt->duration = av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, pkt->time_base); if (enc->codec_id == AV_CODEC_ID_DVB_SUBTITLE) { /* XXX: the pts correction is handled here. Maybe handling it in the codec would be better */ if (i == 0) - pkt->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + pkt->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, pkt->time_base); else - pkt->pts += av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + pkt->pts += av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, pkt->time_base); } pkt->dts = pkt->pts; - output_packet(of, pkt, ost, 0); + + of_output_packet(of, pkt, ost, 0); + } +} + +/* Convert frame timestamps to the encoder timebase and decide how many times + * should this (and possibly previous) frame be repeated in order to conform to + * desired target framerate (if any). + */ +static void video_sync_process(OutputFile *of, OutputStream *ost, + AVFrame *next_picture, double duration, + int64_t *nb_frames, int64_t *nb_frames_prev) +{ + double delta0, delta; + + double sync_ipts = adjust_frame_pts_to_encoder_tb(of, ost, next_picture); + /* delta0 is the "drift" between the input frame (next_picture) and + * where it would fall in the output. */ + delta0 = sync_ipts - ost->next_pts; + delta = delta0 + duration; + + // tracks the number of times the PREVIOUS frame should be duplicated, + // mostly for variable framerate (VFR) + *nb_frames_prev = 0; + /* by default, we output a single frame */ + *nb_frames = 1; + + if (delta0 < 0 && + delta > 0 && + ost->vsync_method != VSYNC_PASSTHROUGH && + ost->vsync_method != VSYNC_DROP) { + if (delta0 < -0.6) { + av_log(ost, AV_LOG_VERBOSE, "Past duration %f too large\n", -delta0); + } else + av_log(ost, AV_LOG_DEBUG, "Clipping frame in rate conversion by %f\n", -delta0); + sync_ipts = ost->next_pts; + duration += delta0; + delta0 = 0; + } + + switch (ost->vsync_method) { + case VSYNC_VSCFR: + if (ost->vsync_frame_number == 0 && delta0 >= 0.5) { + av_log(ost, AV_LOG_DEBUG, "Not duplicating %d initial frames\n", (int)lrintf(delta0)); + delta = duration; + delta0 = 0; + ost->next_pts = llrint(sync_ipts); + } + case VSYNC_CFR: + // FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c + if (frame_drop_threshold && delta < frame_drop_threshold && ost->vsync_frame_number) { + *nb_frames = 0; + } else if (delta < -1.1) + *nb_frames = 0; + else if (delta > 1.1) { + *nb_frames = llrintf(delta); + if (delta0 > 1.1) + *nb_frames_prev = llrintf(delta0 - 0.6); + } + next_picture->duration = 1; + break; + case VSYNC_VFR: + if (delta <= -0.6) + *nb_frames = 0; + else if (delta > 0.6) + ost->next_pts = llrint(sync_ipts); + next_picture->duration = duration; + break; + case VSYNC_DROP: + case VSYNC_PASSTHROUGH: + next_picture->duration = duration; + ost->next_pts = llrint(sync_ipts); + break; + default: + av_assert0(0); } } +enum AVPictureType forced_kf_apply(void *logctx, KeyframeForceCtx *kf, + AVRational tb, const AVFrame *in_picture, + int dup_idx) +{ + double pts_time; + + if (kf->ref_pts == AV_NOPTS_VALUE) + kf->ref_pts = in_picture->pts; + + pts_time = (in_picture->pts - kf->ref_pts) * av_q2d(tb); + if (kf->index < kf->nb_pts && + av_compare_ts(in_picture->pts, tb, kf->pts[kf->index], AV_TIME_BASE_Q) >= 0) { + kf->index++; + goto force_keyframe; + } else if (kf->pexpr) { + double res; + kf->expr_const_values[FKF_T] = pts_time; + res = av_expr_eval(kf->pexpr, + kf->expr_const_values, NULL); + ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n", + kf->expr_const_values[FKF_N], + kf->expr_const_values[FKF_N_FORCED], + kf->expr_const_values[FKF_PREV_FORCED_N], + kf->expr_const_values[FKF_T], + kf->expr_const_values[FKF_PREV_FORCED_T], + res); + + kf->expr_const_values[FKF_N] += 1; + + if (res) { + kf->expr_const_values[FKF_PREV_FORCED_N] = kf->expr_const_values[FKF_N] - 1; + kf->expr_const_values[FKF_PREV_FORCED_T] = kf->expr_const_values[FKF_T]; + kf->expr_const_values[FKF_N_FORCED] += 1; + goto force_keyframe; + } + } else if (kf->type == KF_FORCE_SOURCE && + in_picture->key_frame == 1 && !dup_idx) { + goto force_keyframe; + } else if (kf->type == KF_FORCE_SOURCE_NO_DROP && !dup_idx) { + kf->dropped_keyframe = 0; + if ((in_picture->key_frame == 1) || kf->dropped_keyframe) + goto force_keyframe; + } + + return AV_PICTURE_TYPE_NONE; + +force_keyframe: + av_log(logctx, AV_LOG_DEBUG, "Forced keyframe at time %f\n", pts_time); + return AV_PICTURE_TYPE_I; +} + /* May modify/reset next_picture */ static void do_video_out(OutputFile *of, OutputStream *ost, @@ -1219,18 +1415,12 @@ static void do_video_out(OutputFile *of, int ret; AVCodecContext *enc = ost->enc_ctx; AVRational frame_rate; - int64_t nb_frames, nb0_frames, i; - double delta, delta0; + int64_t nb_frames, nb_frames_prev, i; double duration = 0; - double sync_ipts = AV_NOPTS_VALUE; - InputStream *ist = NULL; + InputStream *ist = ost->ist; AVFilterContext *filter = ost->filter->filter; init_output_stream_wrapper(ost, next_picture, 1); - sync_ipts = adjust_frame_pts_to_encoder_tb(of, ost, next_picture); - - if (ost->source_index >= 0) - ist = input_streams[ost->source_index]; frame_rate = av_buffersink_get_frame_rate(filter); if (frame_rate.num > 0 && frame_rate.den > 0) @@ -1244,113 +1434,52 @@ static void do_video_out(OutputFile *of, (nb_filtergraphs == 0 || !filtergraphs[0]->graph_desc) && next_picture && ist && - lrintf(next_picture->pkt_duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)) > 0) { - duration = lrintf(next_picture->pkt_duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)); + lrintf(next_picture->duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)) > 0) { + duration = lrintf(next_picture->duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)); } if (!next_picture) { //end, flushing - nb0_frames = nb_frames = mid_pred(ost->last_nb0_frames[0], - ost->last_nb0_frames[1], - ost->last_nb0_frames[2]); + nb_frames_prev = nb_frames = mid_pred(ost->last_nb0_frames[0], + ost->last_nb0_frames[1], + ost->last_nb0_frames[2]); } else { - delta0 = sync_ipts - ost->sync_opts; // delta0 is the "drift" between the input frame (next_picture) and where it would fall in the output. - delta = delta0 + duration; - - /* by default, we output a single frame */ - nb0_frames = 0; // tracks the number of times the PREVIOUS frame should be duplicated, mostly for variable framerate (VFR) - nb_frames = 1; - - if (delta0 < 0 && - delta > 0 && - ost->vsync_method != VSYNC_PASSTHROUGH && - ost->vsync_method != VSYNC_DROP) { - if (delta0 < -0.6) { - av_log(NULL, AV_LOG_VERBOSE, "Past duration %f too large\n", -delta0); - } else - av_log(NULL, AV_LOG_DEBUG, "Clipping frame in rate conversion by %f\n", -delta0); - sync_ipts = ost->sync_opts; - duration += delta0; - delta0 = 0; - } - - switch (ost->vsync_method) { - case VSYNC_VSCFR: - if (ost->frame_number == 0 && delta0 >= 0.5) { - av_log(NULL, AV_LOG_DEBUG, "Not duplicating %d initial frames\n", (int)lrintf(delta0)); - delta = duration; - delta0 = 0; - ost->sync_opts = llrint(sync_ipts); - } - case VSYNC_CFR: - // FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c - if (frame_drop_threshold && delta < frame_drop_threshold && ost->frame_number) { - nb_frames = 0; - } else if (delta < -1.1) - nb_frames = 0; - else if (delta > 1.1) { - nb_frames = llrintf(delta); - if (delta0 > 1.1) - nb0_frames = llrintf(delta0 - 0.6); - } - break; - case VSYNC_VFR: - if (delta <= -0.6) - nb_frames = 0; - else if (delta > 0.6) - ost->sync_opts = llrint(sync_ipts); - break; - case VSYNC_DROP: - case VSYNC_PASSTHROUGH: - ost->sync_opts = llrint(sync_ipts); - break; - default: - av_assert0(0); - } + video_sync_process(of, ost, next_picture, duration, + &nb_frames, &nb_frames_prev); } - /* - * For video, number of frames in == number of packets out. - * But there may be reordering, so we can't throw away frames on encoder - * flush, we need to limit them here, before they go into encoder. - */ - nb_frames = FFMIN(nb_frames, ost->max_frames - ost->frame_number); - nb0_frames = FFMIN(nb0_frames, nb_frames); - memmove(ost->last_nb0_frames + 1, ost->last_nb0_frames, sizeof(ost->last_nb0_frames[0]) * (FF_ARRAY_ELEMS(ost->last_nb0_frames) - 1)); - ost->last_nb0_frames[0] = nb0_frames; + ost->last_nb0_frames[0] = nb_frames_prev; - if (nb0_frames == 0 && ost->last_dropped) { + if (nb_frames_prev == 0 && ost->last_dropped) { nb_frames_drop++; - av_log(NULL, AV_LOG_VERBOSE, - "*** dropping frame %"PRId64" from stream %d at ts %"PRId64"\n", - ost->frame_number, ost->st->index, ost->last_frame->pts); + av_log(ost, AV_LOG_VERBOSE, + "*** dropping frame %"PRId64" at ts %"PRId64"\n", + ost->vsync_frame_number, ost->last_frame->pts); } - if (nb_frames > (nb0_frames && ost->last_dropped) + (nb_frames > nb0_frames)) { + if (nb_frames > (nb_frames_prev && ost->last_dropped) + (nb_frames > nb_frames_prev)) { if (nb_frames > dts_error_threshold * 30) { - av_log(NULL, AV_LOG_ERROR, "%"PRId64" frame duplication too large, skipping\n", nb_frames - 1); + av_log(ost, AV_LOG_ERROR, "%"PRId64" frame duplication too large, skipping\n", nb_frames - 1); nb_frames_drop++; return; } - nb_frames_dup += nb_frames - (nb0_frames && ost->last_dropped) - (nb_frames > nb0_frames); - av_log(NULL, AV_LOG_VERBOSE, "*** %"PRId64" dup!\n", nb_frames - 1); + nb_frames_dup += nb_frames - (nb_frames_prev && ost->last_dropped) - (nb_frames > nb_frames_prev); + av_log(ost, AV_LOG_VERBOSE, "*** %"PRId64" dup!\n", nb_frames - 1); if (nb_frames_dup > dup_warning) { - av_log(NULL, AV_LOG_WARNING, "More than %"PRIu64" frames duplicated\n", dup_warning); + av_log(ost, AV_LOG_WARNING, "More than %"PRIu64" frames duplicated\n", dup_warning); dup_warning *= 10; } } - ost->last_dropped = nb_frames == nb0_frames && next_picture; - ost->dropped_keyframe = ost->last_dropped && next_picture && next_picture->key_frame; + ost->last_dropped = nb_frames == nb_frames_prev && next_picture; + ost->kf.dropped_keyframe = ost->last_dropped && next_picture && next_picture->key_frame; /* duplicates frame if needed */ for (i = 0; i < nb_frames; i++) { AVFrame *in_picture; - int forced_keyframe = 0; - double pts_time; - if (i < nb0_frames && ost->last_frame->buf[0]) { + if (i < nb_frames_prev && ost->last_frame->buf[0]) { in_picture = ost->last_frame; } else in_picture = next_picture; @@ -1358,69 +1487,22 @@ static void do_video_out(OutputFile *of, if (!in_picture) return; - in_picture->pts = ost->sync_opts; + in_picture->pts = ost->next_pts; - if (!check_recording_time(ost)) + if (!check_recording_time(ost, in_picture->pts, ost->enc_ctx->time_base)) return; in_picture->quality = enc->global_quality; - in_picture->pict_type = 0; - - if (ost->forced_kf_ref_pts == AV_NOPTS_VALUE && - in_picture->pts != AV_NOPTS_VALUE) - ost->forced_kf_ref_pts = in_picture->pts; - - pts_time = in_picture->pts != AV_NOPTS_VALUE ? - (in_picture->pts - ost->forced_kf_ref_pts) * av_q2d(enc->time_base) : NAN; - if (ost->forced_kf_index < ost->forced_kf_count && - in_picture->pts >= ost->forced_kf_pts[ost->forced_kf_index]) { - ost->forced_kf_index++; - forced_keyframe = 1; - } else if (ost->forced_keyframes_pexpr) { - double res; - ost->forced_keyframes_expr_const_values[FKF_T] = pts_time; - res = av_expr_eval(ost->forced_keyframes_pexpr, - ost->forced_keyframes_expr_const_values, NULL); - ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n", - ost->forced_keyframes_expr_const_values[FKF_N], - ost->forced_keyframes_expr_const_values[FKF_N_FORCED], - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N], - ost->forced_keyframes_expr_const_values[FKF_T], - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T], - res); - if (res) { - forced_keyframe = 1; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N] = - ost->forced_keyframes_expr_const_values[FKF_N]; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T] = - ost->forced_keyframes_expr_const_values[FKF_T]; - ost->forced_keyframes_expr_const_values[FKF_N_FORCED] += 1; - } + in_picture->pict_type = forced_kf_apply(ost, &ost->kf, enc->time_base, in_picture, i); - ost->forced_keyframes_expr_const_values[FKF_N] += 1; - } else if ( ost->forced_keyframes - && !strncmp(ost->forced_keyframes, "source", 6) - && in_picture->key_frame==1 - && !i) { - forced_keyframe = 1; - } else if ( ost->forced_keyframes - && !strncmp(ost->forced_keyframes, "source_no_drop", 14) - && !i) { - forced_keyframe = (in_picture->key_frame == 1) || ost->dropped_keyframe; - ost->dropped_keyframe = 0; - } - - if (forced_keyframe) { - in_picture->pict_type = AV_PICTURE_TYPE_I; - av_log(NULL, AV_LOG_DEBUG, "Forced keyframe at time %f\n", pts_time); - } - - ret = encode_frame(of, ost, in_picture); - if (ret < 0) + ret = submit_encode_frame(of, ost, in_picture); + if (ret == AVERROR_EOF) + break; + else if (ret < 0) exit_program(1); - ost->sync_opts++; - ost->frame_number++; + ost->next_pts++; + ost->vsync_frame_number++; } av_frame_unref(ost->last_frame); @@ -1428,19 +1510,6 @@ static void do_video_out(OutputFile *of, av_frame_move_ref(ost->last_frame, next_picture); } -static void finish_output_stream(OutputStream *ost) -{ - OutputFile *of = output_files[ost->file_index]; - AVRational time_base = ost->stream_copy ? ost->mux_timebase : ost->enc_ctx->time_base; - - ost->finished = ENCODER_FINISHED | MUXER_FINISHED; - - if (of->shortest) { - int64_t end = av_rescale_q(ost->sync_opts - ost->first_pts, time_base, AV_TIME_BASE_Q); - of->recording_time = FFMIN(of->recording_time, end); - } -} - /** * Get and encode new output from any of the filtergraphs, without causing * activity. @@ -1450,11 +1519,9 @@ static void finish_output_stream(OutputStream *ost) static int reap_filters(int flush) { AVFrame *filtered_frame = NULL; - int i; /* Reap all buffers present in the buffer sinks */ - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { OutputFile *of = output_files[ost->file_index]; AVFilterContext *filter; AVCodecContext *enc = ost->enc_ctx; @@ -1496,6 +1563,19 @@ static int reap_filters(int flush) continue; } + if (filtered_frame->pts != AV_NOPTS_VALUE) { + AVRational tb = av_buffersink_get_time_base(filter); + ost->last_filter_pts = av_rescale_q(filtered_frame->pts, tb, + AV_TIME_BASE_Q); + filtered_frame->time_base = tb; + + if (debug_ts) + av_log(NULL, AV_LOG_INFO, "filter_raw -> pts:%s pts_time:%s time_base:%d/%d\n", + av_ts2str(filtered_frame->pts), + av_ts2timestr(filtered_frame->pts, &tb), + tb.num, tb.den); + } + switch (av_buffersink_get_type(filter)) { case AVMEDIA_TYPE_VIDEO: if (!ost->frame_aspect_ratio.num) @@ -1532,19 +1612,21 @@ static void print_final_stats(int64_t total_size) float percent = -1.0; int i, j; int pass1_used = 1; - char percentString[50] = {0}; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - switch (ost->enc_ctx->codec_type) { - case AVMEDIA_TYPE_VIDEO: video_size += ost->data_size; break; - case AVMEDIA_TYPE_AUDIO: audio_size += ost->data_size; break; - case AVMEDIA_TYPE_SUBTITLE: subtitle_size += ost->data_size; break; - default: other_size += ost->data_size; break; + + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + AVCodecParameters *par = ost->st->codecpar; + const uint64_t s = ost->data_size_mux; + + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: video_size += s; break; + case AVMEDIA_TYPE_AUDIO: audio_size += s; break; + case AVMEDIA_TYPE_SUBTITLE: subtitle_size += s; break; + default: other_size += s; break; } - extra_size += ost->enc_ctx->extradata_size; - data_size += ost->data_size; - if ( (ost->enc_ctx->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2)) + extra_size += par->extradata_size; + data_size += s; + if (ost->enc_ctx && + (ost->enc_ctx->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2)) != AV_CODEC_FLAG_PASS1) pass1_used = 0; } @@ -1552,18 +1634,17 @@ static void print_final_stats(int64_t total_size) if (data_size && total_size>0 && total_size >= data_size) percent = 100.0 * (total_size - data_size) / data_size; - if (percent >= 0.0) - sprintf(percentString, "%f%%", percent); - else - sprintf(percentString, "unknown"); - - av_log(NULL, AV_LOG_INFO, "video:%1.0fkB audio:%1.0fkB subtitle:%1.0fkB other streams:%1.0fkB global headers:%1.0fkB muxing overhead: %s\n", + av_log(NULL, AV_LOG_INFO, "video:%1.0fkB audio:%1.0fkB subtitle:%1.0fkB other streams:%1.0fkB global headers:%1.0fkB muxing overhead: ", video_size / 1024.0, audio_size / 1024.0, subtitle_size / 1024.0, other_size / 1024.0, - extra_size / 1024.0, - percentString); + extra_size / 1024.0); + if (percent >= 0.0) + av_log(NULL, AV_LOG_INFO, "%f%%", percent); + else + av_log(NULL, AV_LOG_INFO, "unknown"); + av_log(NULL, AV_LOG_INFO, "\n"); /* print verbose per-stream stats */ for (i = 0; i < nb_input_files; i++) { @@ -1574,8 +1655,8 @@ static void print_final_stats(int64_t total_size) i, f->ctx->url); for (j = 0; j < f->nb_streams; j++) { - InputStream *ist = input_streams[f->ist_index + j]; - enum AVMediaType type = ist->dec_ctx->codec_type; + InputStream *ist = f->streams[j]; + enum AVMediaType type = ist->par->codec_type; total_size += ist->data_size; total_packets += ist->nb_packets; @@ -1605,18 +1686,18 @@ static void print_final_stats(int64_t total_size) uint64_t total_packets = 0, total_size = 0; av_log(NULL, AV_LOG_VERBOSE, "Output file #%d (%s):\n", - i, of->ctx->url); + i, of->url); - for (j = 0; j < of->ctx->nb_streams; j++) { - OutputStream *ost = output_streams[of->ost_index + j]; - enum AVMediaType type = ost->enc_ctx->codec_type; + for (j = 0; j < of->nb_streams; j++) { + OutputStream *ost = of->streams[j]; + enum AVMediaType type = ost->st->codecpar->codec_type; - total_size += ost->data_size; - total_packets += ost->packets_written; + total_size += ost->data_size_mux; + total_packets += atomic_load(&ost->packets_written); av_log(NULL, AV_LOG_VERBOSE, " Output stream #%d:%d (%s): ", i, j, av_get_media_type_string(type)); - if (ost->encoding_needed) { + if (ost->enc_ctx) { av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" frames encoded", ost->frames_encoded); if (type == AVMEDIA_TYPE_AUDIO) @@ -1625,7 +1706,7 @@ static void print_final_stats(int64_t total_size) } av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" packets muxed (%"PRIu64" bytes); ", - ost->packets_written, ost->data_size); + atomic_load(&ost->packets_written), ost->data_size_mux); av_log(NULL, AV_LOG_VERBOSE, "\n"); } @@ -1643,83 +1724,19 @@ static void print_final_stats(int64_t total_size) } } -static void forward_report(int is_last_report, int64_t timer_start, int64_t cur_time) +static void forward_report(uint64_t frame_number, float fps, float quality, int64_t total_size, int64_t seconds, int64_t microseconds, double bitrate, double speed) { - AVFormatContext *oc = NULL; - AVCodecContext *enc = NULL; - OutputStream *ost = NULL; - int64_t pts = INT64_MIN + 1; - int vid, i; - - int64_t frame_number = 0; - float fps = 0; - float quality = 0; - int64_t total_size = 0; - int seconds = 0; - double bitrate = 0.0; - double speed = 0.0; - - float t = (cur_time-timer_start) / 1000000.0; - - oc = output_files[0]->ctx; - - // 1. calculate size - total_size = avio_size(oc->pb); - if (total_size <= 0) { - total_size = avio_tell(oc->pb); - } - - vid = 0; - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - enc = ost->enc_ctx; - - if (!ost->stream_copy) { - - // 2. extract quality - quality = ost->quality / (float) FF_QP2LAMBDA; - } - - if (!vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { - - // 3. extract frame number - frame_number = ost->frame_number; - - // 4. calculate fps - fps = t > 1 ? frame_number / t : 0; - } - - // 5. calculate time - if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) - pts = FFMAX(pts, av_rescale_q(av_stream_get_end_pts(ost->st), - ost->st->time_base, AV_TIME_BASE_Q)); - - vid = 1; - } - - // 6. calculate time, with microseconds to milliseconds conversion - seconds = FFABS(pts) / 1000; - - // 7. calculating kbit/s value - bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; - - // 9. calculate processing speed = processed stream duration/operation duration - speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; - // FORWARD DATA if (report_callback != NULL) { - report_callback(frame_number, fps, quality, total_size, seconds, bitrate, speed); + report_callback(frame_number, fps, quality, total_size, ((double)seconds*1000) + ((double)microseconds)/1000, bitrate, speed); } } static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time) { AVBPrint buf, buf_script; - OutputStream *ost; - AVFormatContext *oc; - int64_t total_size; - AVCodecContext *enc; - int vid, i; + int64_t total_size = of_filesize(output_files[0]); + int vid; double bitrate; double speed; int64_t pts = INT64_MIN + 1; @@ -1728,6 +1745,15 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti int ret; float t; + // FFmpegKit field declarations + int local_print_stats = 1; + uint64_t frame_number = 0; + float fps = 0; + float q = 0; + + if (!print_stats && !is_last_report && !progress_avio) + local_print_stats = 0; + if (!is_last_report) { if (last_time == -1) { last_time = cur_time; @@ -1738,58 +1764,48 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti last_time = cur_time; } - forward_report(is_last_report, timer_start, cur_time); - - if (!print_stats && !is_last_report && !progress_avio) - return; - t = (cur_time-timer_start) / 1000000.0; - - oc = output_files[0]->ctx; - - total_size = avio_size(oc->pb); - if (total_size <= 0) // FIXME improve avio_size() so it works with non seekable output too - total_size = avio_tell(oc->pb); - vid = 0; - av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); - av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC); - for (i = 0; i < nb_output_streams; i++) { - float q = -1; - ost = output_streams[i]; - enc = ost->enc_ctx; - if (!ost->stream_copy) - q = ost->quality / (float) FF_QP2LAMBDA; - - if (vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { + if (local_print_stats) { + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); + av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC); + } + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + const AVCodecContext * const enc = ost->enc_ctx; + q = enc ? ost->quality / (float) FF_QP2LAMBDA : -1; + + if (local_print_stats && vid && ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { av_bprintf(&buf, "q=%2.1f ", q); av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", ost->file_index, ost->index, q); } - if (!vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { - float fps; - int64_t frame_number = ost->frame_number; - + if (!vid && ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + frame_number = atomic_load(&ost->packets_written); fps = t > 1 ? frame_number / t : 0; - av_bprintf(&buf, "frame=%5"PRId64" fps=%3.*f q=%3.1f ", - frame_number, fps < 9.95, fps, q); - av_bprintf(&buf_script, "frame=%"PRId64"\n", frame_number); - av_bprintf(&buf_script, "fps=%.2f\n", fps); - av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", - ost->file_index, ost->index, q); - if (is_last_report) + if (local_print_stats) { + av_bprintf(&buf, "frame=%5"PRId64" fps=%3.*f q=%3.1f ", + frame_number, fps < 9.95, fps, q); + av_bprintf(&buf_script, "frame=%"PRId64"\n", frame_number); + av_bprintf(&buf_script, "fps=%.2f\n", fps); + av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", + ost->file_index, ost->index, q); + } + if (local_print_stats && is_last_report) av_bprintf(&buf, "L"); if (qp_hist) { int j; int qp = lrintf(q); if (qp >= 0 && qp < FF_ARRAY_ELEMS(qp_histogram)) qp_histogram[qp]++; - for (j = 0; j < 32; j++) - av_bprintf(&buf, "%X", av_log2(qp_histogram[j] + 1)); + if (local_print_stats) { + for (j = 0; j < 32; j++) + av_bprintf(&buf, "%X", av_log2(qp_histogram[j] + 1)); + } } - if ((enc->flags & AV_CODEC_FLAG_PSNR) && (ost->pict_type != AV_PICTURE_TYPE_NONE || is_last_report)) { + if (local_print_stats && enc && (enc->flags & AV_CODEC_FLAG_PSNR) && + (ost->pict_type != AV_PICTURE_TYPE_NONE || is_last_report)) { int j; double error, error_sum = 0; double scale, scale_sum = 0; @@ -1821,9 +1837,8 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti vid = 1; } /* compute min output value */ - if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) { - pts = FFMAX(pts, av_rescale_q(av_stream_get_end_pts(ost->st), - ost->st->time_base, AV_TIME_BASE_Q)); + if (ost->last_mux_dts != AV_NOPTS_VALUE) { + pts = FFMAX(pts, ost->last_mux_dts); if (copy_ts) { if (copy_ts_first_pts == AV_NOPTS_VALUE && pts > 1) copy_ts_first_pts = pts; @@ -1847,77 +1862,82 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; - if (total_size < 0) av_bprintf(&buf, "size=N/A time="); - else av_bprintf(&buf, "size=%8.0fkB time=", total_size / 1024.0); - if (pts == AV_NOPTS_VALUE) { - av_bprintf(&buf, "N/A "); - } else { - av_bprintf(&buf, "%s%02d:%02d:%02d.%02d ", - hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE); - } + // FFmpegKit forward report + forward_report(frame_number, fps, q, total_size, secs, us, bitrate, speed); - if (bitrate < 0) { - av_bprintf(&buf, "bitrate=N/A"); - av_bprintf(&buf_script, "bitrate=N/A\n"); - }else{ - av_bprintf(&buf, "bitrate=%6.1fkbits/s", bitrate); - av_bprintf(&buf_script, "bitrate=%6.1fkbits/s\n", bitrate); - } + if (local_print_stats) { + if (total_size < 0) av_bprintf(&buf, "size=N/A time="); + else av_bprintf(&buf, "size=%8.0fkB time=", total_size / 1024.0); + if (pts == AV_NOPTS_VALUE) { + av_bprintf(&buf, "N/A "); + } else { + av_bprintf(&buf, "%s%02d:%02d:%02d.%02d ", + hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE); + } - if (total_size < 0) av_bprintf(&buf_script, "total_size=N/A\n"); - else av_bprintf(&buf_script, "total_size=%"PRId64"\n", total_size); - if (pts == AV_NOPTS_VALUE) { - av_bprintf(&buf_script, "out_time_us=N/A\n"); - av_bprintf(&buf_script, "out_time_ms=N/A\n"); - av_bprintf(&buf_script, "out_time=N/A\n"); - } else { - av_bprintf(&buf_script, "out_time_us=%"PRId64"\n", pts); - av_bprintf(&buf_script, "out_time_ms=%"PRId64"\n", pts); - av_bprintf(&buf_script, "out_time=%s%02d:%02d:%02d.%06d\n", - hours_sign, hours, mins, secs, us); - } + if (bitrate < 0) { + av_bprintf(&buf, "bitrate=N/A"); + av_bprintf(&buf_script, "bitrate=N/A\n"); + }else{ + av_bprintf(&buf, "bitrate=%6.1fkbits/s", bitrate); + av_bprintf(&buf_script, "bitrate=%6.1fkbits/s\n", bitrate); + } - if (nb_frames_dup || nb_frames_drop) - av_bprintf(&buf, " dup=%"PRId64" drop=%"PRId64, nb_frames_dup, nb_frames_drop); - av_bprintf(&buf_script, "dup_frames=%"PRId64"\n", nb_frames_dup); - av_bprintf(&buf_script, "drop_frames=%"PRId64"\n", nb_frames_drop); + if (total_size < 0) av_bprintf(&buf_script, "total_size=N/A\n"); + else av_bprintf(&buf_script, "total_size=%"PRId64"\n", total_size); + if (pts == AV_NOPTS_VALUE) { + av_bprintf(&buf_script, "out_time_us=N/A\n"); + av_bprintf(&buf_script, "out_time_ms=N/A\n"); + av_bprintf(&buf_script, "out_time=N/A\n"); + } else { + av_bprintf(&buf_script, "out_time_us=%"PRId64"\n", pts); + av_bprintf(&buf_script, "out_time_ms=%"PRId64"\n", pts); + av_bprintf(&buf_script, "out_time=%s%02d:%02d:%02d.%06d\n", + hours_sign, hours, mins, secs, us); + } - if (speed < 0) { - av_bprintf(&buf, " speed=N/A"); - av_bprintf(&buf_script, "speed=N/A\n"); - } else { - av_bprintf(&buf, " speed=%4.3gx", speed); - av_bprintf(&buf_script, "speed=%4.3gx\n", speed); - } + if (nb_frames_dup || nb_frames_drop) + av_bprintf(&buf, " dup=%"PRId64" drop=%"PRId64, nb_frames_dup, nb_frames_drop); + av_bprintf(&buf_script, "dup_frames=%"PRId64"\n", nb_frames_dup); + av_bprintf(&buf_script, "drop_frames=%"PRId64"\n", nb_frames_drop); - if (print_stats || is_last_report) { - const char end = is_last_report ? '\n' : '\r'; - if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) { - av_log(NULL, AV_LOG_STDERR, "%s %c", buf.str, end); - } else - av_log(NULL, AV_LOG_INFO, "%s %c", buf.str, end); - } - av_bprint_finalize(&buf, NULL); - - if (progress_avio) { - av_bprintf(&buf_script, "progress=%s\n", - is_last_report ? "end" : "continue"); - avio_write(progress_avio, buf_script.str, - FFMIN(buf_script.len, buf_script.size - 1)); - avio_flush(progress_avio); - av_bprint_finalize(&buf_script, NULL); - if (is_last_report) { - if ((ret = avio_closep(&progress_avio)) < 0) - av_log(NULL, AV_LOG_ERROR, - "Error closing progress log, loss of information possible: %s\n", av_err2str(ret)); + if (speed < 0) { + av_bprintf(&buf, " speed=N/A"); + av_bprintf(&buf_script, "speed=N/A\n"); + } else { + av_bprintf(&buf, " speed=%4.3gx", speed); + av_bprintf(&buf_script, "speed=%4.3gx\n", speed); } - } - first_report = 0; - - if (is_last_report) - print_final_stats(total_size); -} + if (print_stats || is_last_report) { + const char end = is_last_report ? '\n' : '\r'; + if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) { + av_log(NULL, AV_LOG_STDERR, "%s %c", buf.str, end); + } else + av_log(NULL, AV_LOG_INFO, "%s %c", buf.str, end); + } + av_bprint_finalize(&buf, NULL); + + if (progress_avio) { + av_bprintf(&buf_script, "progress=%s\n", + is_last_report ? "end" : "continue"); + avio_write(progress_avio, buf_script.str, + FFMIN(buf_script.len, buf_script.size - 1)); + avio_flush(progress_avio); + av_bprint_finalize(&buf_script, NULL); + if (is_last_report) { + if ((ret = avio_closep(&progress_avio)) < 0) + av_log(NULL, AV_LOG_ERROR, + "Error closing progress log, loss of information possible: %s\n", av_err2str(ret)); + } + } + + first_report = 0; + + if (is_last_report) + print_final_stats(total_size); + } +} static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par) { @@ -1939,14 +1959,19 @@ static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParamet static void flush_encoders(void) { - int i, ret; + int ret; + + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + OutputFile *of = output_files[ost->file_index]; + if (ost->sq_idx_encode >= 0) + sq_send(of->sq_encode, ost->sq_idx_encode, SQFRAME(NULL)); + } - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { AVCodecContext *enc = ost->enc_ctx; OutputFile *of = output_files[ost->file_index]; - if (!ost->encoding_needed) + if (!enc) continue; // Try to enable encoding with no input frames. @@ -1954,17 +1979,16 @@ static void flush_encoders(void) if (!ost->initialized) { FilterGraph *fg = ost->filter->graph; - av_log(NULL, AV_LOG_WARNING, - "Finishing stream %d:%d without any data written to it.\n", - ost->file_index, ost->st->index); + av_log(ost, AV_LOG_WARNING, + "Finishing stream without any data written to it.\n"); if (ost->filter && !fg->graph) { int x; for (x = 0; x < fg->nb_inputs; x++) { InputFilter *ifilter = fg->inputs[x]; if (ifilter->format < 0 && - ifilter_parameters_from_codecpar(ifilter, ifilter->ist->st->codecpar) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error copying paramerets from input stream\n"); + ifilter_parameters_from_codecpar(ifilter, ifilter->ist->par) < 0) { + av_log(ost, AV_LOG_ERROR, "Error copying paramerets from input stream\n"); exit_program(1); } } @@ -1974,11 +1998,11 @@ static void flush_encoders(void) ret = configure_filtergraph(fg); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error configuring filter graph\n"); + av_log(ost, AV_LOG_ERROR, "Error configuring filter graph\n"); exit_program(1); } - finish_output_stream(ost); + of_output_packet(of, ost->pkt, ost, 1); } init_output_stream_wrapper(ost, NULL, 1); @@ -1987,7 +2011,7 @@ static void flush_encoders(void) if (enc->codec_type != AVMEDIA_TYPE_VIDEO && enc->codec_type != AVMEDIA_TYPE_AUDIO) continue; - ret = encode_frame(of, ost, NULL); + ret = submit_encode_frame(of, ost, NULL); if (ret != AVERROR_EOF) exit_program(1); } @@ -1999,9 +2023,8 @@ static void flush_encoders(void) static int check_output_constraints(InputStream *ist, OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - int ist_index = input_files[ist->file_index]->ist_index + ist->st->index; - if (ost->source_index != ist_index) + if (ost->ist != ist) return 0; if (ost->finished & MUXER_FINISHED) @@ -2024,7 +2047,7 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p av_packet_unref(opkt); // EOF: flush output bitstream filters. if (!pkt) { - output_packet(of, opkt, ost, 1); + of_output_packet(of, opkt, ost, 1); return; } @@ -2033,12 +2056,9 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p return; if (!ost->streamcopy_started && !ost->copy_prior_start) { - int64_t comp_start = start_time; - if (copy_ts && f->start_time != AV_NOPTS_VALUE) - comp_start = FFMAX(start_time, f->start_time + f->ts_offset); if (pkt->pts == AV_NOPTS_VALUE ? - ist->pts < comp_start : - pkt->pts < av_rescale_q(comp_start, AV_TIME_BASE_Q, ist->st->time_base)) + ist->pts < ost->ts_copy_start : + pkt->pts < av_rescale_q(ost->ts_copy_start, AV_TIME_BASE_Q, ist->st->time_base)) return; } @@ -2052,7 +2072,7 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p start_time = 0; if (copy_ts) { start_time += f->start_time != AV_NOPTS_VALUE ? f->start_time : 0; - start_time += start_at_zero ? 0 : f->ctx->start_time; + start_time += start_at_zero ? 0 : f->start_time_effective; } if (ist->pts >= f->recording_time + start_time) { close_output_stream(ost); @@ -2063,52 +2083,43 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p if (av_packet_ref(opkt, pkt) < 0) exit_program(1); + opkt->time_base = ost->mux_timebase; + if (pkt->pts != AV_NOPTS_VALUE) - opkt->pts = av_rescale_q(pkt->pts, ist->st->time_base, ost->mux_timebase) - ost_tb_start_time; + opkt->pts = av_rescale_q(pkt->pts, ist->st->time_base, opkt->time_base) - ost_tb_start_time; if (pkt->dts == AV_NOPTS_VALUE) { - opkt->dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ost->mux_timebase); + opkt->dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, opkt->time_base); } else if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - int duration = av_get_audio_frame_duration(ist->dec_ctx, pkt->size); + int duration = av_get_audio_frame_duration2(ist->par, pkt->size); if(!duration) - duration = ist->dec_ctx->frame_size; + duration = ist->par->frame_size; opkt->dts = av_rescale_delta(ist->st->time_base, pkt->dts, - (AVRational){1, ist->dec_ctx->sample_rate}, duration, - &ist->filter_in_rescale_delta_last, ost->mux_timebase); + (AVRational){1, ist->par->sample_rate}, duration, + &ist->filter_in_rescale_delta_last, opkt->time_base); /* dts will be set immediately afterwards to what pts is now */ opkt->pts = opkt->dts - ost_tb_start_time; } else - opkt->dts = av_rescale_q(pkt->dts, ist->st->time_base, ost->mux_timebase); + opkt->dts = av_rescale_q(pkt->dts, ist->st->time_base, opkt->time_base); opkt->dts -= ost_tb_start_time; - opkt->duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->mux_timebase); + opkt->duration = av_rescale_q(pkt->duration, ist->st->time_base, opkt->time_base); - ost->sync_opts += opkt->duration; + { + int ret = trigger_fix_sub_duration_heartbeat(ost, pkt); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "Subtitle heartbeat logic failed in %s! (%s)\n", + __func__, av_err2str(ret)); + exit_program(1); + } + } - output_packet(of, opkt, ost, 0); + of_output_packet(of, opkt, ost, 0); ost->streamcopy_started = 1; } -int guess_input_channel_layout(InputStream *ist) -{ - AVCodecContext *dec = ist->dec_ctx; - - if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) { - char layout_name[256]; - - if (dec->ch_layout.nb_channels > ist->guess_layout_max) - return 0; - av_channel_layout_default(&dec->ch_layout, dec->ch_layout.nb_channels); - if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) - return 0; - av_channel_layout_describe(&dec->ch_layout, layout_name, sizeof(layout_name)); - av_log(NULL, AV_LOG_WARNING, "Guessed Channel Layout for Input Stream " - "#%d.%d : %s\n", ist->file_index, ist->st->index, layout_name); - } - return 1; -} - static void check_decode_result(InputStream *ist, int *got_output, int ret) { if (*got_output || ret<0) @@ -2152,7 +2163,7 @@ static int ifilter_send_frame(InputFilter *ifilter, AVFrame *frame, int keep_ref /* determine if the parameters for this input changed */ need_reinit = ifilter->format != frame->format; - switch (ifilter->ist->st->codecpar->codec_type) { + switch (ifilter->ist->par->codec_type) { case AVMEDIA_TYPE_AUDIO: need_reinit |= ifilter->sample_rate != frame->sample_rate || av_channel_layout_compare(&ifilter->ch_layout, &frame->ch_layout); @@ -2236,7 +2247,7 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts) } else { // the filtergraph was never configured if (ifilter->format < 0) { - ret = ifilter_parameters_from_codecpar(ifilter, ifilter->ist->st->codecpar); + ret = ifilter_parameters_from_codecpar(ifilter, ifilter->ist->par); if (ret < 0) return ret; } @@ -2253,7 +2264,8 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts) // There is the following difference: if you got a frame, you must call // it again with pkt=NULL. pkt==NULL is treated differently from pkt->size==0 // (pkt==NULL means get more output, pkt->size==0 is a flush/drain packet) -static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt) +static int decode(InputStream *ist, AVCodecContext *avctx, + AVFrame *frame, int *got_frame, AVPacket *pkt) { int ret; @@ -2270,8 +2282,24 @@ static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacke ret = avcodec_receive_frame(avctx, frame); if (ret < 0 && ret != AVERROR(EAGAIN)) return ret; - if (ret >= 0) + if (ret >= 0) { + if (ist->want_frame_data) { + FrameData *fd; + + av_assert0(!frame->opaque_ref); + frame->opaque_ref = av_buffer_allocz(sizeof(*fd)); + if (!frame->opaque_ref) { + av_frame_unref(frame); + return AVERROR(ENOMEM); + } + fd = (FrameData*)frame->opaque_ref->data; + fd->pts = frame->pts; + fd->tb = avctx->pkt_timebase; + fd->idx = avctx->frame_num - 1; + } + *got_frame = 1; + } return 0; } @@ -2303,16 +2331,11 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, AVRational decoded_frame_tb; update_benchmark(NULL); - ret = decode(avctx, decoded_frame, got_output, pkt); + ret = decode(ist, avctx, decoded_frame, got_output, pkt); update_benchmark("decode_audio %d.%d", ist->file_index, ist->st->index); if (ret < 0) *decode_failed = 1; - if (ret >= 0 && avctx->sample_rate <= 0) { - av_log(avctx, AV_LOG_ERROR, "Sample rate %d invalid\n", avctx->sample_rate); - ret = AVERROR_INVALIDDATA; - } - if (ret != AVERROR_EOF) check_decode_result(ist, got_output, ret); @@ -2325,9 +2348,9 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, /* increment next_dts to use for the case where the input stream does not have timestamps or there are multiple frames in the packet */ ist->next_pts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / - avctx->sample_rate; + decoded_frame->sample_rate; ist->next_dts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / - avctx->sample_rate; + decoded_frame->sample_rate; if (decoded_frame->pts != AV_NOPTS_VALUE) { decoded_frame_tb = ist->st->time_base; @@ -2345,8 +2368,10 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, ist->prev_pkt_pts = pkt->pts; if (decoded_frame->pts != AV_NOPTS_VALUE) decoded_frame->pts = av_rescale_delta(decoded_frame_tb, decoded_frame->pts, - (AVRational){1, avctx->sample_rate}, decoded_frame->nb_samples, &ist->filter_in_rescale_delta_last, - (AVRational){1, avctx->sample_rate}); + (AVRational){1, decoded_frame->sample_rate}, + decoded_frame->nb_samples, + &ist->filter_in_rescale_delta_last, + (AVRational){1, decoded_frame->sample_rate}); ist->nb_samples = decoded_frame->nb_samples; err = send_frame_to_filters(ist, decoded_frame); @@ -2385,16 +2410,16 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ } update_benchmark(NULL); - ret = decode(ist->dec_ctx, decoded_frame, got_output, pkt); + ret = decode(ist, ist->dec_ctx, decoded_frame, got_output, pkt); update_benchmark("decode_video %d.%d", ist->file_index, ist->st->index); if (ret < 0) *decode_failed = 1; // The following line may be required in some cases where there is no parser // or the parser does not has_b_frames correctly - if (ist->st->codecpar->video_delay < ist->dec_ctx->has_b_frames) { + if (ist->par->video_delay < ist->dec_ctx->has_b_frames) { if (ist->dec_ctx->codec_id == AV_CODEC_ID_H264) { - ist->st->codecpar->video_delay = ist->dec_ctx->has_b_frames; + ist->par->video_delay = ist->dec_ctx->has_b_frames; } else av_log(ist->dec_ctx, AV_LOG_WARNING, "video_delay is larger in decoder than demuxer %d > %d.\n" @@ -2402,7 +2427,7 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ "of this file to https://streams.videolan.org/upload/ " "and contact the ffmpeg-devel mailing list. (ffmpeg-devel@ffmpeg.org)\n", ist->dec_ctx->has_b_frames, - ist->st->codecpar->video_delay); + ist->par->video_delay); } if (ret != AVERROR_EOF) @@ -2435,10 +2460,9 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ if (err < 0) goto fail; } - ist->hwaccel_retrieved_pix_fmt = decoded_frame->format; best_effort_timestamp= decoded_frame->best_effort_timestamp; - *duration_pts = decoded_frame->pkt_duration; + *duration_pts = decoded_frame->duration; if (ist->framerate.num) best_effort_timestamp = ist->cfr_next_pts++; @@ -2479,30 +2503,18 @@ fail: return err < 0 ? err : ret; } -static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, - int *decode_failed) +static int process_subtitle(InputStream *ist, AVSubtitle *subtitle, int *got_output) { - AVSubtitle subtitle; + int ret = 0; int free_sub = 1; - int i, ret = avcodec_decode_subtitle2(ist->dec_ctx, - &subtitle, got_output, pkt); - - check_decode_result(NULL, got_output, ret); - - if (ret < 0 || !*got_output) { - *decode_failed = 1; - if (!pkt->size) - sub2video_flush(ist); - return ret; - } if (ist->fix_sub_duration) { int end = 1; if (ist->prev_sub.got_output) { - end = av_rescale(subtitle.pts - ist->prev_sub.subtitle.pts, + end = av_rescale(subtitle->pts - ist->prev_sub.subtitle.pts, 1000, AV_TIME_BASE); if (end < ist->prev_sub.subtitle.end_display_time) { - av_log(ist->dec_ctx, AV_LOG_DEBUG, + av_log(NULL, AV_LOG_DEBUG, "Subtitle duration reduced from %"PRId32" to %d%s\n", ist->prev_sub.subtitle.end_display_time, end, end <= 0 ? ", dropping it" : ""); @@ -2511,7 +2523,7 @@ static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, } FFSWAP(int, *got_output, ist->prev_sub.got_output); FFSWAP(int, ret, ist->prev_sub.ret); - FFSWAP(AVSubtitle, subtitle, ist->prev_sub.subtitle); + FFSWAP(AVSubtitle, *subtitle, ist->prev_sub.subtitle); if (end <= 0) goto out; } @@ -2520,40 +2532,187 @@ static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, return ret; if (ist->sub2video.frame) { - sub2video_update(ist, INT64_MIN, &subtitle); + sub2video_update(ist, INT64_MIN, subtitle); } else if (ist->nb_filters) { if (!ist->sub2video.sub_queue) ist->sub2video.sub_queue = av_fifo_alloc2(8, sizeof(AVSubtitle), AV_FIFO_FLAG_AUTO_GROW); if (!ist->sub2video.sub_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); - ret = av_fifo_write(ist->sub2video.sub_queue, &subtitle, 1); + ret = av_fifo_write(ist->sub2video.sub_queue, subtitle, 1); if (ret < 0) exit_program(1); free_sub = 0; } - if (!subtitle.num_rects) + if (!subtitle->num_rects) goto out; - ist->frames_decoded++; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!check_output_constraints(ist, ost) || !ost->encoding_needed - || ost->enc->type != AVMEDIA_TYPE_SUBTITLE) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (!check_output_constraints(ist, ost) || !ost->enc_ctx + || ost->enc_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE) continue; - do_subtitle_out(output_files[ost->file_index], ost, &subtitle); + do_subtitle_out(output_files[ost->file_index], ost, subtitle); } out: if (free_sub) - avsubtitle_free(&subtitle); + avsubtitle_free(subtitle); + return ret; +} + +static int copy_av_subtitle(AVSubtitle *dst, AVSubtitle *src) +{ + int ret = AVERROR_BUG; + AVSubtitle tmp = { + .format = src->format, + .start_display_time = src->start_display_time, + .end_display_time = src->end_display_time, + .num_rects = 0, + .rects = NULL, + .pts = src->pts + }; + + if (!src->num_rects) + goto success; + + if (!(tmp.rects = av_calloc(src->num_rects, sizeof(*tmp.rects)))) + return AVERROR(ENOMEM); + + for (int i = 0; i < src->num_rects; i++) { + AVSubtitleRect *src_rect = src->rects[i]; + AVSubtitleRect *dst_rect; + + if (!(dst_rect = tmp.rects[i] = av_mallocz(sizeof(*tmp.rects[0])))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + tmp.num_rects++; + + dst_rect->type = src_rect->type; + dst_rect->flags = src_rect->flags; + + dst_rect->x = src_rect->x; + dst_rect->y = src_rect->y; + dst_rect->w = src_rect->w; + dst_rect->h = src_rect->h; + dst_rect->nb_colors = src_rect->nb_colors; + + if (src_rect->text) + if (!(dst_rect->text = av_strdup(src_rect->text))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + if (src_rect->ass) + if (!(dst_rect->ass = av_strdup(src_rect->ass))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + for (int j = 0; j < 4; j++) { + // SUBTITLE_BITMAP images are special in the sense that they + // are like PAL8 images. first pointer to data, second to + // palette. This makes the size calculation match this. + size_t buf_size = src_rect->type == SUBTITLE_BITMAP && j == 1 ? + AVPALETTE_SIZE : + src_rect->h * src_rect->linesize[j]; + + if (!src_rect->data[j]) + continue; + + if (!(dst_rect->data[j] = av_memdup(src_rect->data[j], buf_size))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + dst_rect->linesize[j] = src_rect->linesize[j]; + } + } + +success: + *dst = tmp; + + return 0; + +cleanup: + avsubtitle_free(&tmp); + return ret; } +static int fix_sub_duration_heartbeat(InputStream *ist, int64_t signal_pts) +{ + int ret = AVERROR_BUG; + int got_output = 1; + AVSubtitle *prev_subtitle = &ist->prev_sub.subtitle; + AVSubtitle subtitle; + + if (!ist->fix_sub_duration || !prev_subtitle->num_rects || + signal_pts <= prev_subtitle->pts) + return 0; + + if ((ret = copy_av_subtitle(&subtitle, prev_subtitle)) < 0) + return ret; + + subtitle.pts = signal_pts; + + return process_subtitle(ist, &subtitle, &got_output); +} + +static int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt) +{ + OutputFile *of = output_files[ost->file_index]; + int64_t signal_pts = av_rescale_q(pkt->pts, pkt->time_base, + AV_TIME_BASE_Q); + + if (!ost->fix_sub_duration_heartbeat || !(pkt->flags & AV_PKT_FLAG_KEY)) + // we are only interested in heartbeats on streams configured, and + // only on random access points. + return 0; + + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *iter_ost = of->streams[i]; + InputStream *ist = iter_ost->ist; + int ret = AVERROR_BUG; + + if (iter_ost == ost || !ist || !ist->decoding_needed || + ist->dec_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE) + // We wish to skip the stream that causes the heartbeat, + // output streams without an input stream, streams not decoded + // (as fix_sub_duration is only done for decoded subtitles) as + // well as non-subtitle streams. + continue; + + if ((ret = fix_sub_duration_heartbeat(ist, signal_pts)) < 0) + return ret; + } + + return 0; +} + +static int transcode_subtitles(InputStream *ist, const AVPacket *pkt, + int *got_output, int *decode_failed) +{ + AVSubtitle subtitle; + int ret = avcodec_decode_subtitle2(ist->dec_ctx, + &subtitle, got_output, pkt); + + check_decode_result(NULL, got_output, ret); + + if (ret < 0 || !*got_output) { + *decode_failed = 1; + if (!pkt->size) + sub2video_flush(ist); + return ret; + } + + ist->frames_decoded++; + + return process_subtitle(ist, &subtitle, got_output); +} + static int send_filter_eof(InputStream *ist) { int i, ret; @@ -2572,7 +2731,8 @@ static int send_filter_eof(InputStream *ist) /* pkt = NULL means EOF (needed to flush decoder buffers) */ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof) { - int ret = 0, i; + const AVCodecParameters *par = ist->par; + int ret = 0; int repeating = 0; int eof_reached = 0; @@ -2604,7 +2764,7 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo if (pkt && pkt->dts != AV_NOPTS_VALUE) { ist->next_dts = ist->dts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); - if (ist->dec_ctx->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) + if (par->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) ist->next_pts = ist->pts = ist->dts; } @@ -2618,7 +2778,7 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo ist->pts = ist->next_pts; ist->dts = ist->next_dts; - switch (ist->dec_ctx->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: ret = decode_audio (ist, repeating ? NULL : avpkt, &got_output, &decode_failed); @@ -2631,7 +2791,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo if (pkt && pkt->duration) { duration_dts = av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } else if(ist->dec_ctx->framerate.num != 0 && ist->dec_ctx->framerate.den != 0) { - int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict+1 : ist->dec_ctx->ticks_per_frame; + int ticks = ist->last_pkt_repeat_pict >= 0 ? + ist->last_pkt_repeat_pict + 1 : + ist->dec_ctx->ticks_per_frame; duration_dts = ((int64_t)AV_TIME_BASE * ist->dec_ctx->framerate.den * ticks) / ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; @@ -2715,12 +2877,12 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo /* handle stream copy */ if (!ist->decoding_needed && pkt) { ist->dts = ist->next_dts; - switch (ist->dec_ctx->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: av_assert1(pkt->duration >= 0); - if (ist->dec_ctx->sample_rate) { - ist->next_dts += ((int64_t)AV_TIME_BASE * ist->dec_ctx->frame_size) / - ist->dec_ctx->sample_rate; + if (par->sample_rate) { + ist->next_dts += ((int64_t)AV_TIME_BASE * par->frame_size) / + par->sample_rate; } else { ist->next_dts += av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } @@ -2734,7 +2896,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo } else if (pkt->duration) { ist->next_dts += av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } else if(ist->dec_ctx->framerate.num != 0) { - int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict + 1 : ist->dec_ctx->ticks_per_frame; + int ticks = ist->last_pkt_repeat_pict >= 0 ? + ist->last_pkt_repeat_pict + 1 : + ist->dec_ctx->ticks_per_frame; ist->next_dts += ((int64_t)AV_TIME_BASE * ist->dec_ctx->framerate.den * ticks) / ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; @@ -2746,10 +2910,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo } else if (!ist->decoding_needed) eof_reached = 1; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!check_output_constraints(ist, ost) || ost->encoding_needed) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (!check_output_constraints(ist, ost) || ost->enc_ctx || + (!pkt && no_eof)) continue; do_streamcopy(ist, ost, pkt); @@ -2807,10 +2970,9 @@ static enum AVPixelFormat get_format(AVCodecContext *s, const enum AVPixelFormat return *p; } -static int init_input_stream(int ist_index, char *error, int error_len) +static int init_input_stream(InputStream *ist, char *error, int error_len) { int ret; - InputStream *ist = input_streams[ist_index]; if (ist->decoding_needed) { const AVCodec *codec = ist->dec; @@ -2822,11 +2984,6 @@ static int init_input_stream(int ist_index, char *error, int error_len) ist->dec_ctx->opaque = ist; ist->dec_ctx->get_format = get_format; -#if LIBAVCODEC_VERSION_MAJOR < 60 - AV_NOWARN_DEPRECATED({ - ist->dec_ctx->thread_safe_callbacks = 1; - }) -#endif if (ist->dec_ctx->codec_id == AV_CODEC_ID_DVB_SUBTITLE && (ist->decoding_needed & DECODING_FOR_OST)) { @@ -2872,71 +3029,37 @@ static int init_input_stream(int ist_index, char *error, int error_len) return 0; } -static InputStream *get_input_stream(OutputStream *ost) -{ - if (ost->source_index >= 0) - return input_streams[ost->source_index]; - return NULL; -} - -static int compare_int64(const void *a, const void *b) -{ - return FFDIFFSIGN(*(const int64_t *)a, *(const int64_t *)b); -} - -static int init_output_bsfs(OutputStream *ost) -{ - AVBSFContext *ctx = ost->bsf_ctx; - int ret; - - if (!ctx) - return 0; - - ret = avcodec_parameters_copy(ctx->par_in, ost->st->codecpar); - if (ret < 0) - return ret; - - ctx->time_base_in = ost->st->time_base; - - ret = av_bsf_init(ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing bitstream filter: %s\n", - ctx->filter->name); - return ret; - } - - ret = avcodec_parameters_copy(ost->st->codecpar, ctx->par_out); - if (ret < 0) - return ret; - ost->st->time_base = ctx->time_base_out; - - return 0; -} - static int init_output_stream_streamcopy(OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - InputStream *ist = get_input_stream(ost); - AVCodecParameters *par_dst = ost->st->codecpar; - AVCodecParameters *par_src = ost->ref_par; + InputStream *ist = ost->ist; + InputFile *ifile = input_files[ist->file_index]; + AVCodecParameters *par = ost->st->codecpar; + AVCodecContext *codec_ctx; AVRational sar; int i, ret; - uint32_t codec_tag = par_dst->codec_tag; + uint32_t codec_tag = par->codec_tag; av_assert0(ist && !ost->filter); - ret = avcodec_parameters_to_context(ost->enc_ctx, ist->st->codecpar); + codec_ctx = avcodec_alloc_context3(NULL); + if (!codec_ctx) + return AVERROR(ENOMEM); + + ret = avcodec_parameters_to_context(codec_ctx, ist->par); if (ret >= 0) - ret = av_opt_set_dict(ost->enc_ctx, &ost->encoder_opts); + ret = av_opt_set_dict(codec_ctx, &ost->encoder_opts); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error setting up codec context options.\n"); + avcodec_free_context(&codec_ctx); return ret; } - ret = avcodec_parameters_from_context(par_src, ost->enc_ctx); + ret = avcodec_parameters_from_context(par, codec_ctx); + avcodec_free_context(&codec_ctx); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error getting reference codec parameters.\n"); return ret; } @@ -2944,16 +3067,12 @@ static int init_output_stream_streamcopy(OutputStream *ost) if (!codec_tag) { unsigned int codec_tag_tmp; if (!of->format->codec_tag || - av_codec_get_id (of->format->codec_tag, par_src->codec_tag) == par_src->codec_id || - !av_codec_get_tag2(of->format->codec_tag, par_src->codec_id, &codec_tag_tmp)) - codec_tag = par_src->codec_tag; + av_codec_get_id (of->format->codec_tag, par->codec_tag) == par->codec_id || + !av_codec_get_tag2(of->format->codec_tag, par->codec_id, &codec_tag_tmp)) + codec_tag = par->codec_tag; } - ret = avcodec_parameters_copy(par_dst, par_src); - if (ret < 0) - return ret; - - par_dst->codec_tag = codec_tag; + par->codec_tag = codec_tag; if (!ost->frame_rate.num) ost->frame_rate = ist->framerate; @@ -2979,6 +3098,15 @@ static int init_output_stream_streamcopy(OutputStream *ost) if (ost->st->duration <= 0 && ist->st->duration > 0) ost->st->duration = av_rescale_q(ist->st->duration, ist->st->time_base, ost->st->time_base); + if (!ost->copy_prior_start) { + ost->ts_copy_start = (of->start_time == AV_NOPTS_VALUE) ? + 0 : of->start_time; + if (copy_ts && ifile->start_time != AV_NOPTS_VALUE) { + ost->ts_copy_start = FFMAX(ost->ts_copy_start, + ifile->start_time + ifile->ts_offset); + } + } + if (ist->st->nb_side_data) { for (i = 0; i < ist->st->nb_side_data; i++) { const AVPacketSideData *sd_src = &ist->st->side_data[i]; @@ -2991,37 +3119,36 @@ static int init_output_stream_streamcopy(OutputStream *ost) } } +#if FFMPEG_ROTATION_METADATA if (ost->rotate_overridden) { uint8_t *sd = av_stream_new_side_data(ost->st, AV_PKT_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); if (sd) av_display_rotation_set((int32_t *)sd, -ost->rotate_override_value); } +#endif - switch (par_dst->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: - if (audio_volume != 256) { - av_log(NULL, AV_LOG_FATAL, "-acodec copy and -vol are incompatible (frames are not decoded)\n"); - exit_program(1); - } - if((par_dst->block_align == 1 || par_dst->block_align == 1152 || par_dst->block_align == 576) && par_dst->codec_id == AV_CODEC_ID_MP3) - par_dst->block_align= 0; - if(par_dst->codec_id == AV_CODEC_ID_AC3) - par_dst->block_align= 0; + if ((par->block_align == 1 || par->block_align == 1152 || par->block_align == 576) && + par->codec_id == AV_CODEC_ID_MP3) + par->block_align = 0; + if (par->codec_id == AV_CODEC_ID_AC3) + par->block_align = 0; break; case AVMEDIA_TYPE_VIDEO: if (ost->frame_aspect_ratio.num) { // overridden by the -aspect cli option sar = av_mul_q(ost->frame_aspect_ratio, - (AVRational){ par_dst->height, par_dst->width }); - av_log(NULL, AV_LOG_WARNING, "Overriding aspect ratio " + (AVRational){ par->height, par->width }); + av_log(ost, AV_LOG_WARNING, "Overriding aspect ratio " "with stream copy may produce invalid files\n"); } else if (ist->st->sample_aspect_ratio.num) sar = ist->st->sample_aspect_ratio; else - sar = par_src->sample_aspect_ratio; - ost->st->sample_aspect_ratio = par_dst->sample_aspect_ratio = sar; + sar = par->sample_aspect_ratio; + ost->st->sample_aspect_ratio = par->sample_aspect_ratio = sar; ost->st->avg_frame_rate = ist->st->avg_frame_rate; ost->st->r_frame_rate = ist->st->r_frame_rate; break; @@ -3034,113 +3161,31 @@ static int init_output_stream_streamcopy(OutputStream *ost) static void set_encoder_id(OutputFile *of, OutputStream *ost) { - const AVDictionaryEntry *e; - + const char *cname = ost->enc_ctx->codec->name; uint8_t *encoder_string; int encoder_string_len; - int format_flags = 0; - int codec_flags = ost->enc_ctx->flags; if (av_dict_get(ost->st->metadata, "encoder", NULL, 0)) return; - e = av_dict_get(of->opts, "fflags", NULL, 0); - if (e) { - const AVOption *o = av_opt_find(of->ctx, "fflags", NULL, 0, 0); - if (!o) - return; - av_opt_eval_flags(of->ctx, o, e->value, &format_flags); - } - e = av_dict_get(ost->encoder_opts, "flags", NULL, 0); - if (e) { - const AVOption *o = av_opt_find(ost->enc_ctx, "flags", NULL, 0, 0); - if (!o) - return; - av_opt_eval_flags(ost->enc_ctx, o, e->value, &codec_flags); - } - - encoder_string_len = sizeof(LIBAVCODEC_IDENT) + strlen(ost->enc->name) + 2; + encoder_string_len = sizeof(LIBAVCODEC_IDENT) + strlen(cname) + 2; encoder_string = av_mallocz(encoder_string_len); if (!encoder_string) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); - if (!(format_flags & AVFMT_FLAG_BITEXACT) && !(codec_flags & AV_CODEC_FLAG_BITEXACT)) + if (!of->bitexact && !ost->bitexact) av_strlcpy(encoder_string, LIBAVCODEC_IDENT " ", encoder_string_len); else av_strlcpy(encoder_string, "Lavc ", encoder_string_len); - av_strlcat(encoder_string, ost->enc->name, encoder_string_len); + av_strlcat(encoder_string, cname, encoder_string_len); av_dict_set(&ost->st->metadata, "encoder", encoder_string, AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_OVERWRITE); } -static void parse_forced_key_frames(char *kf, OutputStream *ost, - AVCodecContext *avctx) -{ - char *p; - int n = 1, i, size, index = 0; - int64_t t, *pts; - - for (p = kf; *p; p++) - if (*p == ',') - n++; - size = n; - pts = av_malloc_array(size, sizeof(*pts)); - if (!pts) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate forced key frames array.\n"); - exit_program(1); - } - - p = kf; - for (i = 0; i < n; i++) { - char *next = strchr(p, ','); - - if (next) - *next++ = 0; - - if (!memcmp(p, "chapters", 8)) { - - AVFormatContext *avf = output_files[ost->file_index]->ctx; - int j; - - if (avf->nb_chapters > INT_MAX - size || - !(pts = av_realloc_f(pts, size += avf->nb_chapters - 1, - sizeof(*pts)))) { - av_log(NULL, AV_LOG_FATAL, - "Could not allocate forced key frames array.\n"); - exit_program(1); - } - t = p[8] ? parse_time_or_die("force_key_frames", p + 8, 1) : 0; - t = av_rescale_q(t, AV_TIME_BASE_Q, avctx->time_base); - - for (j = 0; j < avf->nb_chapters; j++) { - AVChapter *c = avf->chapters[j]; - av_assert1(index < size); - pts[index++] = av_rescale_q(c->start, c->time_base, - avctx->time_base) + t; - } - - } else { - - t = parse_time_or_die("force_key_frames", p, 1); - av_assert1(index < size); - pts[index++] = av_rescale_q(t, AV_TIME_BASE_Q, avctx->time_base); - - } - - p = next; - } - - av_assert0(index == size); - qsort(pts, size, sizeof(*pts), compare_int64); - ost->forced_kf_count = size; - ost->forced_kf_pts = pts; -} - static void init_encoder_time_base(OutputStream *ost, AVRational default_time_base) { - InputStream *ist = get_input_stream(ost); + InputStream *ist = ost->ist; AVCodecContext *enc_ctx = ost->enc_ctx; - AVFormatContext *oc; if (ost->enc_timebase.num > 0) { enc_ctx->time_base = ost->enc_timebase; @@ -3153,8 +3198,8 @@ static void init_encoder_time_base(OutputStream *ost, AVRational default_time_ba return; } - oc = output_files[ost->file_index]->ctx; - av_log(oc, AV_LOG_WARNING, "Input stream data not available, using default time base\n"); + av_log(ost, AV_LOG_WARNING, + "Input stream data not available, using default time base\n"); } enc_ctx->time_base = default_time_base; @@ -3162,11 +3207,10 @@ static void init_encoder_time_base(OutputStream *ost, AVRational default_time_ba static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) { - InputStream *ist = get_input_stream(ost); + InputStream *ist = ost->ist; AVCodecContext *enc_ctx = ost->enc_ctx; AVCodecContext *dec_ctx = NULL; OutputFile *of = output_files[ost->file_index]; - AVFormatContext *oc = of->ctx; int ret; set_encoder_id(output_files[ost->file_index], ost); @@ -3178,14 +3222,13 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) if (enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { if (!ost->frame_rate.num) ost->frame_rate = av_buffersink_get_frame_rate(ost->filter->filter); - if (ist && !ost->frame_rate.num && !ost->max_frame_rate.num) { + if (!ost->frame_rate.num && !ost->max_frame_rate.num) { ost->frame_rate = (AVRational){25, 1}; - av_log(NULL, AV_LOG_WARNING, + av_log(ost, AV_LOG_WARNING, "No information " "about the input framerate is available. Falling " - "back to a default value of 25fps for output stream #%d:%d. Use the -r option " - "if you want a different framerate.\n", - ost->file_index, ost->index); + "back to a default value of 25fps. Use the -r option " + "if you want a different framerate.\n"); } if (ost->max_frame_rate.num && @@ -3193,9 +3236,9 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) !ost->frame_rate.den)) ost->frame_rate = ost->max_frame_rate; - if (ost->enc->supported_framerates && !ost->force_fps) { - int idx = av_find_nearest_q_idx(ost->frame_rate, ost->enc->supported_framerates); - ost->frame_rate = ost->enc->supported_framerates[idx]; + if (enc_ctx->codec->supported_framerates && !ost->force_fps) { + int idx = av_find_nearest_q_idx(ost->frame_rate, enc_ctx->codec->supported_framerates); + ost->frame_rate = enc_ctx->codec->supported_framerates[idx]; } // reduce frame rate for mpeg4 to be within the spec limits if (enc_ctx->codec_id == AV_CODEC_ID_MPEG4) { @@ -3229,9 +3272,9 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) if ( av_q2d(enc_ctx->time_base) < 0.001 && ost->vsync_method != VSYNC_PASSTHROUGH && (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR || (ost->vsync_method == VSYNC_AUTO && !(of->format->flags & AVFMT_VARIABLE_FPS)))){ - av_log(oc, AV_LOG_WARNING, "Frame rate very high for a muxer not efficiently supporting it.\n" - "Please consider specifying a lower framerate, a different muxer or " - "setting vsync/fps_mode to vfr\n"); + av_log(ost, AV_LOG_WARNING, "Frame rate very high for a muxer not efficiently supporting it.\n" + "Please consider specifying a lower framerate, a different muxer or " + "setting vsync/fps_mode to vfr\n"); } enc_ctx->width = av_buffersink_get_w(ost->filter->filter); @@ -3283,33 +3326,41 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) enc_ctx->field_order = AV_FIELD_TT; } - if (ost->forced_keyframes) { - if (!strncmp(ost->forced_keyframes, "expr:", 5)) { - ret = av_expr_parse(&ost->forced_keyframes_pexpr, ost->forced_keyframes+5, - forced_keyframes_const_names, NULL, NULL, NULL, NULL, 0, NULL); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, - "Invalid force_key_frames expression '%s'\n", ost->forced_keyframes+5); - return ret; - } - ost->forced_keyframes_expr_const_values[FKF_N] = 0; - ost->forced_keyframes_expr_const_values[FKF_N_FORCED] = 0; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N] = NAN; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T] = NAN; - - // Don't parse the 'forced_keyframes' in case of 'keep-source-keyframes', - // parse it only for static kf timings - } else if(strncmp(ost->forced_keyframes, "source", 6)) { - parse_forced_key_frames(ost->forced_keyframes, ost, ost->enc_ctx); - } - } break; case AVMEDIA_TYPE_SUBTITLE: enc_ctx->time_base = AV_TIME_BASE_Q; if (!enc_ctx->width) { - enc_ctx->width = input_streams[ost->source_index]->st->codecpar->width; - enc_ctx->height = input_streams[ost->source_index]->st->codecpar->height; + enc_ctx->width = ost->ist->par->width; + enc_ctx->height = ost->ist->par->height; + } + if (dec_ctx && dec_ctx->subtitle_header) { + /* ASS code assumes this buffer is null terminated so add extra byte. */ + ost->enc_ctx->subtitle_header = av_mallocz(dec_ctx->subtitle_header_size + 1); + if (!ost->enc_ctx->subtitle_header) + return AVERROR(ENOMEM); + memcpy(ost->enc_ctx->subtitle_header, dec_ctx->subtitle_header, + dec_ctx->subtitle_header_size); + ost->enc_ctx->subtitle_header_size = dec_ctx->subtitle_header_size; + } + if (ist && ist->dec->type == AVMEDIA_TYPE_SUBTITLE && + enc_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { + int input_props = 0, output_props = 0; + AVCodecDescriptor const *input_descriptor = + avcodec_descriptor_get(ist->dec->id); + AVCodecDescriptor const *output_descriptor = + avcodec_descriptor_get(ost->enc_ctx->codec_id); + if (input_descriptor) + input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (output_descriptor) + output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (input_props && output_props && input_props != output_props) { + av_log(ost, AV_LOG_ERROR, + "Subtitle encoding currently only possible from text to text " + "or bitmap to bitmap"); + return AVERROR_INVALIDDATA; + } } + break; case AVMEDIA_TYPE_DATA: break; @@ -3318,6 +3369,12 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) break; } + if (ost->bitexact) + enc_ctx->flags |= AV_CODEC_FLAG_BITEXACT; + + if (ost->sq_idx_encode >= 0) + sq_set_tb(of->sq_encode, ost->sq_idx_encode, enc_ctx->time_base); + ost->mux_timebase = enc_ctx->time_base; return 0; @@ -3328,28 +3385,23 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, { int ret = 0; - if (ost->encoding_needed) { - const AVCodec *codec = ost->enc; - AVCodecContext *dec = NULL; - InputStream *ist; + if (ost->enc_ctx) { + const AVCodec *codec = ost->enc_ctx->codec; + InputStream *ist = ost->ist; ret = init_output_stream_encode(ost, frame); if (ret < 0) return ret; - if ((ist = get_input_stream(ost))) - dec = ist->dec_ctx; - if (dec && dec->subtitle_header) { - /* ASS code assumes this buffer is null terminated so add extra byte. */ - ost->enc_ctx->subtitle_header = av_mallocz(dec->subtitle_header_size + 1); - if (!ost->enc_ctx->subtitle_header) - return AVERROR(ENOMEM); - memcpy(ost->enc_ctx->subtitle_header, dec->subtitle_header, dec->subtitle_header_size); - ost->enc_ctx->subtitle_header_size = dec->subtitle_header_size; - } if (!av_dict_get(ost->encoder_opts, "threads", NULL, 0)) av_dict_set(&ost->encoder_opts, "threads", "auto", 0); + if (codec->capabilities & AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE) { + ret = av_dict_set(&ost->encoder_opts, "flags", "+copy_opaque", AV_DICT_MULTIKEY); + if (ret < 0) + return ret; + } + ret = hw_device_setup_for_encode(ost); if (ret < 0) { snprintf(error, error_len, "Device setup failed for " @@ -3358,24 +3410,6 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, return ret; } - if (ist && ist->dec->type == AVMEDIA_TYPE_SUBTITLE && ost->enc->type == AVMEDIA_TYPE_SUBTITLE) { - int input_props = 0, output_props = 0; - AVCodecDescriptor const *input_descriptor = - avcodec_descriptor_get(dec->codec_id); - AVCodecDescriptor const *output_descriptor = - avcodec_descriptor_get(ost->enc_ctx->codec_id); - if (input_descriptor) - input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (output_descriptor) - output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (input_props && output_props && input_props != output_props) { - snprintf(error, error_len, - "Subtitle encoding currently only possible from text to text " - "or bitmap to bitmap"); - return AVERROR_INVALIDDATA; - } - } - if ((ret = avcodec_open2(ost->enc_ctx, codec, &ost->encoder_opts)) < 0) { if (ret == AVERROR_EXPERIMENTAL) abort_codec_experimental(codec, 1); @@ -3385,19 +3419,19 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, ost->file_index, ost->index); return ret; } - if (ost->enc->type == AVMEDIA_TYPE_AUDIO && - !(ost->enc->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + if (codec->type == AVMEDIA_TYPE_AUDIO && + !(codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) av_buffersink_set_frame_size(ost->filter->filter, ost->enc_ctx->frame_size); assert_avoptions(ost->encoder_opts); if (ost->enc_ctx->bit_rate && ost->enc_ctx->bit_rate < 1000 && ost->enc_ctx->codec_id != AV_CODEC_ID_CODEC2 /* don't complain about 700 bit/s modes */) - av_log(NULL, AV_LOG_WARNING, "The bitrate parameter is set too low." - " It takes bits/s as argument, not kbits/s\n"); + av_log(ost, AV_LOG_WARNING, "The bitrate parameter is set too low." + " It takes bits/s as argument, not kbits/s\n"); ret = avcodec_parameters_from_context(ost->st->codecpar, ost->enc_ctx); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error initializing the output stream codec context.\n"); exit_program(1); } @@ -3433,7 +3467,7 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, return AVERROR(ENOMEM); memcpy(dst, sd->data, sd->size); if (ist->autorotate && sd->type == AV_PKT_DATA_DISPLAYMATRIX) - av_display_rotation_set((uint32_t *)dst, 0); + av_display_rotation_set((int32_t *)dst, 0); } } } @@ -3445,83 +3479,36 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, // copy estimated duration as a hint to the muxer if (ost->st->duration <= 0 && ist && ist->st->duration > 0) ost->st->duration = av_rescale_q(ist->st->duration, ist->st->time_base, ost->st->time_base); - } else if (ost->stream_copy) { + } else if (ost->ist) { ret = init_output_stream_streamcopy(ost); if (ret < 0) return ret; } - /* initialize bitstream filters for the output stream - * needs to be done here, because the codec id for streamcopy is not - * known until now */ - ret = init_output_bsfs(ost); - if (ret < 0) - return ret; - - ost->initialized = 1; - - ret = of_check_init(output_files[ost->file_index]); + ret = of_stream_init(output_files[ost->file_index], ost); if (ret < 0) return ret; return ret; } -static void report_new_stream(int input_index, AVPacket *pkt) -{ - InputFile *file = input_files[input_index]; - AVStream *st = file->ctx->streams[pkt->stream_index]; - - if (pkt->stream_index < file->nb_streams_warn) - return; - av_log(file->ctx, AV_LOG_WARNING, - "New %s stream %d:%d at pos:%"PRId64" and DTS:%ss\n", - av_get_media_type_string(st->codecpar->codec_type), - input_index, pkt->stream_index, - pkt->pos, av_ts2timestr(pkt->dts, &st->time_base)); - file->nb_streams_warn = pkt->stream_index + 1; -} - static int transcode_init(void) { - int ret = 0, i, j, k; - AVFormatContext *oc; - OutputStream *ost; - InputStream *ist; + int ret = 0; char error[1024] = {0}; - for (i = 0; i < nb_filtergraphs; i++) { - FilterGraph *fg = filtergraphs[i]; - for (j = 0; j < fg->nb_outputs; j++) { - OutputFilter *ofilter = fg->outputs[j]; - if (!ofilter->ost || ofilter->ost->source_index >= 0) - continue; - if (fg->nb_inputs != 1) - continue; - for (k = nb_input_streams-1; k >= 0 ; k--) - if (fg->inputs[0]->ist == input_streams[k]) - break; - ofilter->ost->source_index = k; - } - } - /* init framerate emulation */ - for (i = 0; i < nb_input_files; i++) { + for (int i = 0; i < nb_input_files; i++) { InputFile *ifile = input_files[i]; if (ifile->readrate || ifile->rate_emu) - for (j = 0; j < ifile->nb_streams; j++) - input_streams[j + ifile->ist_index]->start = av_gettime_relative(); + for (int j = 0; j < ifile->nb_streams; j++) + ifile->streams[j]->start = av_gettime_relative(); } /* init input streams */ - for (i = 0; i < nb_input_streams; i++) - if ((ret = init_input_stream(i, error, sizeof(error))) < 0) { - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - avcodec_close(ost->enc_ctx); - } + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + if ((ret = init_input_stream(ist, error, sizeof(error))) < 0) goto dump_format; - } /* * initialize stream copy and subtitle/data streams. @@ -3532,26 +3519,26 @@ static int transcode_init(void) * to be configured with the correct audio frame size, which is only * known after the encoder is initialized. */ - for (i = 0; i < nb_output_streams; i++) { - if (!output_streams[i]->stream_copy && - (output_streams[i]->enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - output_streams[i]->enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO)) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->enc_ctx && + (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)) continue; - ret = init_output_stream_wrapper(output_streams[i], NULL, 0); + ret = init_output_stream_wrapper(ost, NULL, 0); if (ret < 0) goto dump_format; } /* discard unused programs */ - for (i = 0; i < nb_input_files; i++) { + for (int i = 0; i < nb_input_files; i++) { InputFile *ifile = input_files[i]; - for (j = 0; j < ifile->ctx->nb_programs; j++) { + for (int j = 0; j < ifile->ctx->nb_programs; j++) { AVProgram *p = ifile->ctx->programs[j]; int discard = AVDISCARD_ALL; - for (k = 0; k < p->nb_stream_indexes; k++) - if (!input_streams[ifile->ist_index + p->stream_index[k]]->discard) { + for (int k = 0; k < p->nb_stream_indexes; k++) + if (!ifile->streams[p->stream_index[k]]->discard) { discard = AVDISCARD_DEFAULT; break; } @@ -3559,23 +3546,11 @@ static int transcode_init(void) } } - /* write headers for files with no streams */ - for (i = 0; i < nb_output_files; i++) { - oc = output_files[i]->ctx; - if (output_files[i]->format->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) { - ret = of_check_init(output_files[i]); - if (ret < 0) - goto dump_format; - } - } - dump_format: /* dump the stream mapping */ av_log(NULL, AV_LOG_INFO, "Stream mapping:\n"); - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; - - for (j = 0; j < ist->nb_filters; j++) { + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { + for (int j = 0; j < ist->nb_filters; j++) { if (!filtergraph_is_simple(ist->filters[j]->graph)) { av_log(NULL, AV_LOG_INFO, " Stream #%d:%d (%s) -> %s", ist->file_index, ist->st->index, ist->dec ? ist->dec->name : "?", @@ -3587,9 +3562,7 @@ static int transcode_init(void) } } - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { if (ost->attachment_filename) { /* an attached file */ av_log(NULL, AV_LOG_INFO, " File %s -> Stream #%d:%d\n", @@ -3604,24 +3577,18 @@ static int transcode_init(void) av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index); av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)\n", ost->file_index, - ost->index, ost->enc ? ost->enc->name : "?"); + ost->index, ost->enc_ctx->codec->name); continue; } av_log(NULL, AV_LOG_INFO, " Stream #%d:%d -> #%d:%d", - input_streams[ost->source_index]->file_index, - input_streams[ost->source_index]->st->index, + ost->ist->file_index, + ost->ist->st->index, ost->file_index, ost->index); - if (ost->sync_ist != input_streams[ost->source_index]) - av_log(NULL, AV_LOG_INFO, " [sync #%d:%d]", - ost->sync_ist->file_index, - ost->sync_ist->st->index); - if (ost->stream_copy) - av_log(NULL, AV_LOG_INFO, " (copy)"); - else { - const AVCodec *in_codec = input_streams[ost->source_index]->dec; - const AVCodec *out_codec = ost->enc; + if (ost->enc_ctx) { + const AVCodec *in_codec = ost->ist->dec; + const AVCodec *out_codec = ost->enc_ctx->codec; const char *decoder_name = "?"; const char *in_codec_name = "?"; const char *encoder_name = "?"; @@ -3649,7 +3616,8 @@ static int transcode_init(void) av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))", in_codec_name, decoder_name, out_codec_name, encoder_name); - } + } else + av_log(NULL, AV_LOG_INFO, " (copy)"); av_log(NULL, AV_LOG_INFO, "\n"); } @@ -3666,22 +3634,9 @@ static int transcode_init(void) /* Return 1 if there remain streams where more output is wanted, 0 otherwise. */ static int need_output(void) { - int i; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - OutputFile *of = output_files[ost->file_index]; - AVFormatContext *os = output_files[ost->file_index]->ctx; - - if (ost->finished || - (os->pb && avio_tell(os->pb) >= of->limit_filesize)) - continue; - if (ost->frame_number >= ost->max_frames) { - int j; - for (j = 0; j < of->ctx->nb_streams; j++) - close_output_stream(output_streams[of->ost_index + j]); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->finished) continue; - } return 1; } @@ -3696,19 +3651,22 @@ static int need_output(void) */ static OutputStream *choose_output(void) { - int i; int64_t opts_min = INT64_MAX; OutputStream *ost_min = NULL; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - int64_t opts = ost->last_mux_dts == AV_NOPTS_VALUE ? INT64_MIN : - av_rescale_q(ost->last_mux_dts, ost->st->time_base, - AV_TIME_BASE_Q); - if (ost->last_mux_dts == AV_NOPTS_VALUE) - av_log(NULL, AV_LOG_DEBUG, - "cur_dts is invalid st:%d (%d) [init:%d i_done:%d finish:%d] (this is harmless if it occurs once at the start per stream)\n", - ost->st->index, ost->st->id, ost->initialized, ost->inputs_done, ost->finished); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + int64_t opts; + + if (ost->filter && ost->last_filter_pts != AV_NOPTS_VALUE) { + opts = ost->last_filter_pts; + } else { + opts = ost->last_mux_dts == AV_NOPTS_VALUE ? + INT64_MIN : ost->last_mux_dts; + if (ost->last_mux_dts == AV_NOPTS_VALUE) + av_log(ost, AV_LOG_DEBUG, + "cur_dts is invalid [init:%d i_done:%d finish:%d] (this is harmless if it occurs once at the start per stream)\n", + ost->initialized, ost->inputs_done, ost->finished); + } if (!ost->initialized && !ost->inputs_done) return ost->unavailable ? NULL : ost; @@ -3751,15 +3709,6 @@ static int check_keyboard_interaction(int64_t cur_time) if (key == '+') av_log_set_level(av_log_get_level()+10); if (key == '-') av_log_set_level(av_log_get_level()-10); if (key == 's') qp_hist ^= 1; - if (key == 'h'){ - if (do_hex_dump){ - do_hex_dump = do_pkt_dump = 0; - } else if(do_pkt_dump){ - do_hex_dump = 1; - } else - do_pkt_dump = 1; - av_log_set_level(AV_LOG_DEBUG); - } if (key == 'c' || key == 'C'){ char buf[4096], target[64], command[256], arg[256] = {0}; double time; @@ -3803,7 +3752,11 @@ static int check_keyboard_interaction(int64_t cur_time) if (key == 'd' || key == 'D'){ int debug=0; if(key == 'D') { - debug = input_streams[0]->dec_ctx->debug << 1; + InputStream *ist = ist_iter(NULL); + + if (ist) + debug = ist->dec_ctx->debug << 1; + if(!debug) debug = 1; while (debug & FF_DEBUG_DCT_COEFF) //unsupported, would just crash debug += debug; @@ -3821,12 +3774,11 @@ static int check_keyboard_interaction(int64_t cur_time) if (k <= 0 || sscanf(buf, "%d", &debug)!=1) av_log(NULL, AV_LOG_STDERR,"error parsing debug value\n"); } - for(i=0;idec_ctx->debug = debug; - } - for(i=0;ienc_ctx->debug = debug; + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + ist->dec_ctx->debug = debug; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->enc_ctx) + ost->enc_ctx->debug = debug; } if(debug) av_log_set_level(AV_LOG_DEBUG); av_log(NULL, AV_LOG_STDERR,"debug=%d\n", debug); @@ -3847,160 +3799,10 @@ static int check_keyboard_interaction(int64_t cur_time) return 0; } -#if HAVE_THREADS -static void *input_thread(void *arg) -{ - InputFile *f = arg; - AVPacket *pkt = f->pkt, *queue_pkt; - unsigned flags = f->non_blocking ? AV_THREAD_MESSAGE_NONBLOCK : 0; - int ret = 0; - - while (1) { - ret = av_read_frame(f->ctx, pkt); - - if (ret == AVERROR(EAGAIN)) { - av_usleep(10000); - continue; - } - if (ret < 0) { - av_thread_message_queue_set_err_recv(f->in_thread_queue, ret); - break; - } - queue_pkt = av_packet_alloc(); - if (!queue_pkt) { - av_packet_unref(pkt); - av_thread_message_queue_set_err_recv(f->in_thread_queue, AVERROR(ENOMEM)); - break; - } - av_packet_move_ref(queue_pkt, pkt); - ret = av_thread_message_queue_send(f->in_thread_queue, &queue_pkt, flags); - if (flags && ret == AVERROR(EAGAIN)) { - flags = 0; - ret = av_thread_message_queue_send(f->in_thread_queue, &queue_pkt, flags); - av_log(f->ctx, AV_LOG_WARNING, - "Thread message queue blocking; consider raising the " - "thread_queue_size option (current value: %d)\n", - f->thread_queue_size); - } - if (ret < 0) { - if (ret != AVERROR_EOF) - av_log(f->ctx, AV_LOG_ERROR, - "Unable to send packet to main thread: %s\n", - av_err2str(ret)); - av_packet_free(&queue_pkt); - av_thread_message_queue_set_err_recv(f->in_thread_queue, ret); - break; - } - } - - return NULL; -} - -static void free_input_thread(int i) -{ - InputFile *f = input_files[i]; - AVPacket *pkt; - - if (!f || !f->in_thread_queue) - return; - av_thread_message_queue_set_err_send(f->in_thread_queue, AVERROR_EOF); - while (av_thread_message_queue_recv(f->in_thread_queue, &pkt, 0) >= 0) - av_packet_free(&pkt); - - pthread_join(f->thread, NULL); - f->joined = 1; - av_thread_message_queue_free(&f->in_thread_queue); -} - -static void free_input_threads(void) -{ - int i; - - for (i = 0; i < nb_input_files; i++) - free_input_thread(i); -} - -static int init_input_thread(int i) -{ - int ret; - InputFile *f = input_files[i]; - - if (f->thread_queue_size < 0) - f->thread_queue_size = (nb_input_files > 1 ? 8 : 0); - if (!f->thread_queue_size) - return 0; - - if (f->ctx->pb ? !f->ctx->pb->seekable : - strcmp(f->ctx->iformat->name, "lavfi")) - f->non_blocking = 1; - ret = av_thread_message_queue_alloc(&f->in_thread_queue, - f->thread_queue_size, sizeof(f->pkt)); - if (ret < 0) - return ret; - - if ((ret = pthread_create(&f->thread, NULL, input_thread, f))) { - av_log(NULL, AV_LOG_ERROR, "pthread_create failed: %s. Try to increase `ulimit -v` or decrease `ulimit -s`.\n", strerror(ret)); - av_thread_message_queue_free(&f->in_thread_queue); - return AVERROR(ret); - } - - return 0; -} - -static int init_input_threads(void) -{ - int i, ret; - - for (i = 0; i < nb_input_files; i++) { - ret = init_input_thread(i); - if (ret < 0) - return ret; - } - return 0; -} - -static int get_input_packet_mt(InputFile *f, AVPacket **pkt) -{ - return av_thread_message_queue_recv(f->in_thread_queue, pkt, - f->non_blocking ? - AV_THREAD_MESSAGE_NONBLOCK : 0); -} -#endif - -static int get_input_packet(InputFile *f, AVPacket **pkt) -{ - if (f->readrate || f->rate_emu) { - int i; - int64_t file_start = copy_ts * ( - (f->ctx->start_time != AV_NOPTS_VALUE ? f->ctx->start_time * !start_at_zero : 0) + - (f->start_time != AV_NOPTS_VALUE ? f->start_time : 0) - ); - float scale = f->rate_emu ? 1.0 : f->readrate; - for (i = 0; i < f->nb_streams; i++) { - InputStream *ist = input_streams[f->ist_index + i]; - int64_t stream_ts_offset, pts, now; - if (!ist->nb_packets || (ist->decoding_needed && !ist->got_output)) continue; - stream_ts_offset = FFMAX(ist->first_dts != AV_NOPTS_VALUE ? ist->first_dts : 0, file_start); - pts = av_rescale(ist->dts, 1000000, AV_TIME_BASE); - now = (av_gettime_relative() - ist->start) * scale + stream_ts_offset; - if (pts > now) - return AVERROR(EAGAIN); - } - } - -#if HAVE_THREADS - if (f->thread_queue_size) - return get_input_packet_mt(f, pkt); -#endif - *pkt = f->pkt; - return av_read_frame(f->ctx, *pkt); -} - static int got_eagain(void) { - int i; - for (i = 0; i < nb_output_streams; i++) - if (output_streams[i]->unavailable) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) + if (ost->unavailable) return 1; return 0; } @@ -4010,87 +3812,123 @@ static void reset_eagain(void) int i; for (i = 0; i < nb_input_files; i++) input_files[i]->eagain = 0; - for (i = 0; i < nb_output_streams; i++) - output_streams[i]->unavailable = 0; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) + ost->unavailable = 0; } -// set duration to max(tmp, duration) in a proper time base and return duration's time_base -static AVRational duration_max(int64_t tmp, int64_t *duration, AVRational tmp_time_base, - AVRational time_base) +static void decode_flush(InputFile *ifile) { - int ret; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int ret; - if (!*duration) { - *duration = tmp; - return tmp_time_base; - } - - ret = av_compare_ts(*duration, time_base, tmp, tmp_time_base); - if (ret < 0) { - *duration = tmp; - return tmp_time_base; - } + if (!ist->processing_needed) + continue; - return time_base; -} + do { + ret = process_input_packet(ist, NULL, 1); + } while (ret > 0); -static int seek_to_start(InputFile *ifile, AVFormatContext *is) -{ - InputStream *ist; - AVCodecContext *avctx; - int i, ret, has_audio = 0; - int64_t duration = 0; + if (ist->decoding_needed) { + /* report last frame duration to the demuxer thread */ + if (ist->par->codec_type == AVMEDIA_TYPE_AUDIO) { + LastFrameDuration dur; - ret = avformat_seek_file(is, -1, INT64_MIN, is->start_time, is->start_time, 0); - if (ret < 0) - return ret; + dur.stream_idx = i; + dur.duration = av_rescale_q(ist->nb_samples, + (AVRational){ 1, ist->dec_ctx->sample_rate}, + ist->st->time_base); - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; + av_thread_message_queue_send(ifile->audio_duration_queue, &dur, 0); + } - /* duration is the length of the last frame in a stream - * when audio stream is present we don't care about - * last video frame length because it's not defined exactly */ - if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && ist->nb_samples) - has_audio = 1; + avcodec_flush_buffers(ist->dec_ctx); + } } +} - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; +static void ts_discontinuity_detect(InputFile *ifile, InputStream *ist, + AVPacket *pkt) +{ + const int fmt_is_discont = ifile->ctx->iformat->flags & AVFMT_TS_DISCONT; + int disable_discontinuity_correction = copy_ts; + int64_t pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, + AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); - if (has_audio) { - if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && ist->nb_samples) { - AVRational sample_rate = {1, avctx->sample_rate}; + if (copy_ts && ist->next_dts != AV_NOPTS_VALUE && + fmt_is_discont && ist->st->pts_wrap_bits < 60) { + int64_t wrap_dts = av_rescale_q_rnd(pkt->dts + (1LL<st->pts_wrap_bits), + ist->st->time_base, AV_TIME_BASE_Q, + AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + if (FFABS(wrap_dts - ist->next_dts) < FFABS(pkt_dts - ist->next_dts)/10) + disable_discontinuity_correction = 0; + } - duration = av_rescale_q(ist->nb_samples, sample_rate, ist->st->time_base); - } else { - continue; + if (ist->next_dts != AV_NOPTS_VALUE && !disable_discontinuity_correction) { + int64_t delta = pkt_dts - ist->next_dts; + if (fmt_is_discont) { + if (FFABS(delta) > 1LL * dts_delta_threshold * AV_TIME_BASE || + pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)) { + ifile->ts_offset_discont -= delta; + av_log(NULL, AV_LOG_DEBUG, + "timestamp discontinuity for stream #%d:%d " + "(id=%d, type=%s): %"PRId64", new offset= %"PRId64"\n", + ist->file_index, ist->st->index, ist->st->id, + av_get_media_type_string(ist->par->codec_type), + delta, ifile->ts_offset_discont); + pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); } } else { - if (ist->framerate.num) { - duration = av_rescale_q(1, av_inv_q(ist->framerate), ist->st->time_base); - } else if (ist->st->avg_frame_rate.num) { - duration = av_rescale_q(1, av_inv_q(ist->st->avg_frame_rate), ist->st->time_base); - } else { - duration = 1; + if (FFABS(delta) > 1LL * dts_error_threshold * AV_TIME_BASE) { + av_log(NULL, AV_LOG_WARNING, "DTS %"PRId64", next:%"PRId64" st:%d invalid dropping\n", pkt->dts, ist->next_dts, pkt->stream_index); + pkt->dts = AV_NOPTS_VALUE; } + if (pkt->pts != AV_NOPTS_VALUE){ + int64_t pkt_pts = av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); + delta = pkt_pts - ist->next_dts; + if (FFABS(delta) > 1LL * dts_error_threshold * AV_TIME_BASE) { + av_log(NULL, AV_LOG_WARNING, "PTS %"PRId64", next:%"PRId64" invalid dropping st:%d\n", pkt->pts, ist->next_dts, pkt->stream_index); + pkt->pts = AV_NOPTS_VALUE; + } + } + } + } else if (ist->next_dts == AV_NOPTS_VALUE && !copy_ts && + fmt_is_discont && ifile->last_ts != AV_NOPTS_VALUE) { + int64_t delta = pkt_dts - ifile->last_ts; + if (FFABS(delta) > 1LL * dts_delta_threshold * AV_TIME_BASE) { + ifile->ts_offset_discont -= delta; + av_log(NULL, AV_LOG_DEBUG, + "Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n", + delta, ifile->ts_offset_discont); + pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); } - if (!ifile->duration) - ifile->time_base = ist->st->time_base; - /* the total duration of the stream, max_pts - min_pts is - * the duration of the stream without the last frame */ - if (ist->max_pts > ist->min_pts && ist->max_pts - (uint64_t)ist->min_pts < INT64_MAX - duration) - duration += ist->max_pts - ist->min_pts; - ifile->time_base = duration_max(duration, &ifile->duration, ist->st->time_base, - ifile->time_base); } - if (ifile->loop > 0) - ifile->loop--; + ifile->last_ts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); +} + +static void ts_discontinuity_process(InputFile *ifile, InputStream *ist, + AVPacket *pkt) +{ + int64_t offset = av_rescale_q(ifile->ts_offset_discont, AV_TIME_BASE_Q, + ist->st->time_base); - return ret; + // apply previously-detected timestamp-discontinuity offset + // (to all streams, not just audio/video) + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += offset; + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += offset; + + // detect timestamp discontinuities for audio/video + if ((ist->par->codec_type == AVMEDIA_TYPE_VIDEO || + ist->par->codec_type == AVMEDIA_TYPE_AUDIO) && + pkt->dts != AV_NOPTS_VALUE) + ts_discontinuity_detect(ifile, ist, pkt); } /* @@ -4106,48 +3944,19 @@ static int process_input(int file_index) AVFormatContext *is; InputStream *ist; AVPacket *pkt; - int ret, thread_ret, i, j; - int64_t duration; - int64_t pkt_dts; - int disable_discontinuity_correction = copy_ts; + int ret, i; is = ifile->ctx; - ret = get_input_packet(ifile, &pkt); + ret = ifile_get_packet(ifile, &pkt); if (ret == AVERROR(EAGAIN)) { ifile->eagain = 1; return ret; } - if (ret < 0 && ifile->loop) { - AVCodecContext *avctx; - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; - if (ist->processing_needed) { - ret = process_input_packet(ist, NULL, 1); - if (ret>0) - return 0; - if (ist->decoding_needed) - avcodec_flush_buffers(avctx); - } - } -#if HAVE_THREADS - free_input_thread(file_index); -#endif - ret = seek_to_start(ifile, is); -#if HAVE_THREADS - thread_ret = init_input_thread(file_index); - if (thread_ret < 0) - return thread_ret; -#endif - if (ret < 0) - av_log(NULL, AV_LOG_WARNING, "Seek to start failed.\n"); - else - ret = get_input_packet(ifile, &pkt); - if (ret == AVERROR(EAGAIN)) { - ifile->eagain = 1; - return ret; - } + if (ret == 1) { + /* the input file is looped: flush the decoders */ + decode_flush(ifile); + return AVERROR(EAGAIN); } if (ret < 0) { if (ret != AVERROR_EOF) { @@ -4157,7 +3966,7 @@ static int process_input(int file_index) } for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; + ist = ifile->streams[i]; if (ist->processing_needed) { ret = process_input_packet(ist, NULL, 0); if (ret>0) @@ -4165,12 +3974,12 @@ static int process_input(int file_index) } /* mark all outputs that don't go through lavfi as finished */ - for (j = 0; j < nb_output_streams; j++) { - OutputStream *ost = output_streams[j]; - - if (ost->source_index == ifile->ist_index + i && - (ost->stream_copy || ost->enc->type == AVMEDIA_TYPE_SUBTITLE)) - finish_output_stream(ost); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->ist == ist && + (!ost->enc_ctx || ost->enc_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE)) { + OutputFile *of = output_files[ost->file_index]; + of_output_packet(of, ost->pkt, ost, 1); + } } } @@ -4180,18 +3989,7 @@ static int process_input(int file_index) reset_eagain(); - if (do_pkt_dump) { - av_pkt_dump_log2(NULL, AV_LOG_INFO, pkt, do_hex_dump, - is->streams[pkt->stream_index]); - } - /* the following test is needed in case new streams appear - dynamically in stream : we ignore them */ - if (pkt->stream_index >= ifile->nb_streams) { - report_new_stream(file_index, pkt); - goto discard_packet; - } - - ist = input_streams[ifile->ist_index + pkt->stream_index]; + ist = ifile->streams[pkt->stream_index]; ist->data_size += pkt->size; ist->nb_packets++; @@ -4199,61 +3997,6 @@ static int process_input(int file_index) if (ist->discard) goto discard_packet; - if (pkt->flags & AV_PKT_FLAG_CORRUPT) { - av_log(NULL, exit_on_error ? AV_LOG_FATAL : AV_LOG_WARNING, - "%s: corrupt input packet in stream %d\n", is->url, pkt->stream_index); - if (exit_on_error) - exit_program(1); - } - - if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "demuxer -> ist_index:%d type:%s " - "next_dts:%s next_dts_time:%s next_pts:%s next_pts_time:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", - ifile->ist_index + pkt->stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), - av_ts2str(ist->next_dts), av_ts2timestr(ist->next_dts, &AV_TIME_BASE_Q), - av_ts2str(ist->next_pts), av_ts2timestr(ist->next_pts, &AV_TIME_BASE_Q), - av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), - av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), - av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base), - av_ts2str(input_files[ist->file_index]->ts_offset), - av_ts2timestr(input_files[ist->file_index]->ts_offset, &AV_TIME_BASE_Q)); - } - - if(!ist->wrap_correction_done && is->start_time != AV_NOPTS_VALUE && ist->st->pts_wrap_bits < 64){ - int64_t stime, stime2; - // Correcting starttime based on the enabled streams - // FIXME this ideally should be done before the first use of starttime but we do not know which are the enabled streams at that point. - // so we instead do it here as part of discontinuity handling - if ( ist->next_dts == AV_NOPTS_VALUE - && ifile->ts_offset == -is->start_time - && (is->iformat->flags & AVFMT_TS_DISCONT)) { - int64_t new_start_time = INT64_MAX; - for (i=0; inb_streams; i++) { - AVStream *st = is->streams[i]; - if(st->discard == AVDISCARD_ALL || st->start_time == AV_NOPTS_VALUE) - continue; - new_start_time = FFMIN(new_start_time, av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q)); - } - if (new_start_time > is->start_time) { - av_log(is, AV_LOG_VERBOSE, "Correcting start time by %"PRId64"\n", new_start_time - is->start_time); - ifile->ts_offset = -new_start_time; - } - } - - stime = av_rescale_q(is->start_time, AV_TIME_BASE_Q, ist->st->time_base); - stime2= stime + (1ULL<st->pts_wrap_bits); - ist->wrap_correction_done = 1; - - if(stime2 > stime && pkt->dts != AV_NOPTS_VALUE && pkt->dts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { - pkt->dts -= 1ULL<st->pts_wrap_bits; - ist->wrap_correction_done = 0; - } - if(stime2 > stime && pkt->pts != AV_NOPTS_VALUE && pkt->pts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { - pkt->pts -= 1ULL<st->pts_wrap_bits; - ist->wrap_correction_done = 0; - } - } - /* add the stream-global side data to the first packet */ if (ist->nb_packets == 1) { for (i = 0; i < ist->st->nb_side_data; i++) { @@ -4268,105 +4011,19 @@ static int process_input(int file_index) dst_data = av_packet_new_side_data(pkt, src_sd->type, src_sd->size); if (!dst_data) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); memcpy(dst_data, src_sd->data, src_sd->size); } } - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); - - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts *= ist->ts_scale; - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts *= ist->ts_scale; - - pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && - pkt_dts != AV_NOPTS_VALUE && ist->next_dts == AV_NOPTS_VALUE && !copy_ts - && (is->iformat->flags & AVFMT_TS_DISCONT) && ifile->last_ts != AV_NOPTS_VALUE) { - int64_t delta = pkt_dts - ifile->last_ts; - if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || - delta > 1LL*dts_delta_threshold*AV_TIME_BASE){ - ifile->ts_offset -= delta; - av_log(NULL, AV_LOG_DEBUG, - "Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n", - delta, ifile->ts_offset); - pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - } - } - - duration = av_rescale_q(ifile->duration, ifile->time_base, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) { - pkt->pts += duration; - ist->max_pts = FFMAX(pkt->pts, ist->max_pts); - ist->min_pts = FFMIN(pkt->pts, ist->min_pts); - } - - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts += duration; - - pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - - if (copy_ts && pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && - (is->iformat->flags & AVFMT_TS_DISCONT) && ist->st->pts_wrap_bits < 60) { - int64_t wrap_dts = av_rescale_q_rnd(pkt->dts + (1LL<st->pts_wrap_bits), - ist->st->time_base, AV_TIME_BASE_Q, - AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - if (FFABS(wrap_dts - ist->next_dts) < FFABS(pkt_dts - ist->next_dts)/10) - disable_discontinuity_correction = 0; - } - - if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && - pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && - !disable_discontinuity_correction) { - int64_t delta = pkt_dts - ist->next_dts; - if (is->iformat->flags & AVFMT_TS_DISCONT) { - if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || - delta > 1LL*dts_delta_threshold*AV_TIME_BASE || - pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)) { - ifile->ts_offset -= delta; - av_log(NULL, AV_LOG_DEBUG, - "timestamp discontinuity for stream #%d:%d " - "(id=%d, type=%s): %"PRId64", new offset= %"PRId64"\n", - ist->file_index, ist->st->index, ist->st->id, - av_get_media_type_string(ist->dec_ctx->codec_type), - delta, ifile->ts_offset); - pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - } - } else { - if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || - delta > 1LL*dts_error_threshold*AV_TIME_BASE) { - av_log(NULL, AV_LOG_WARNING, "DTS %"PRId64", next:%"PRId64" st:%d invalid dropping\n", pkt->dts, ist->next_dts, pkt->stream_index); - pkt->dts = AV_NOPTS_VALUE; - } - if (pkt->pts != AV_NOPTS_VALUE){ - int64_t pkt_pts = av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); - delta = pkt_pts - ist->next_dts; - if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || - delta > 1LL*dts_error_threshold*AV_TIME_BASE) { - av_log(NULL, AV_LOG_WARNING, "PTS %"PRId64", next:%"PRId64" invalid dropping st:%d\n", pkt->pts, ist->next_dts, pkt->stream_index); - pkt->pts = AV_NOPTS_VALUE; - } - } - } - } - - if (pkt->dts != AV_NOPTS_VALUE) - ifile->last_ts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); + // detect and try to correct for timestamp discontinuities + ts_discontinuity_process(ifile, ist, pkt); if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", - ifile->ist_index + pkt->stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), + av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", + ifile->index, pkt->stream_index, + av_get_media_type_string(ist->par->codec_type), av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base), @@ -4379,12 +4036,7 @@ static int process_input(int file_index) process_input_packet(ist, pkt, 0); discard_packet: -#if HAVE_THREADS - if (ifile->thread_queue_size) - av_packet_free(&pkt); - else -#endif - av_packet_unref(pkt); + av_packet_free(&pkt); return 0; } @@ -4512,8 +4164,8 @@ static int transcode_step(void) return 0; } } else { - av_assert0(ost->source_index >= 0); - ist = input_streams[ost->source_index]; + ist = ost->ist; + av_assert0(ist); } ret = process_input(ist->file_index); @@ -4535,8 +4187,6 @@ static int transcode_step(void) static int transcode(void) { int ret, i; - AVFormatContext *os; - OutputStream *ost; InputStream *ist; int64_t timer_start; int64_t total_packets_written = 0; @@ -4551,11 +4201,6 @@ static int transcode(void) timer_start = av_gettime_relative(); -#if HAVE_THREADS - if ((ret = init_input_threads()) < 0) - goto fail; -#endif - while (!received_sigterm && !cancelRequested(globalSessionId)) { int64_t cur_time= av_gettime_relative(); @@ -4579,13 +4224,9 @@ static int transcode(void) /* dump report by using the output first video and audio streams */ print_report(0, timer_start, cur_time); } -#if HAVE_THREADS - free_input_threads(); -#endif /* at the end of stream, we must flush the decoder buffers */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; + for (ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { if (!input_files[ist->file_index]->eof_reached) { process_input_packet(ist, NULL, 0); } @@ -4604,30 +4245,13 @@ static int transcode(void) /* dump report by using the first video and audio streams */ print_report(1, timer_start, av_gettime_relative()); - /* close the output files */ - for (i = 0; i < nb_output_files; i++) { - os = output_files[i]->ctx; - if (os && os->oformat && !(os->oformat->flags & AVFMT_NOFILE)) { - if ((ret = avio_closep(&os->pb)) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error closing file %s: %s\n", os->url, av_err2str(ret)); - if (exit_on_error) - exit_program(1); - } - } - } - - /* dump report by using the first video and audio streams */ - print_report(1, timer_start, av_gettime_relative()); - /* close each encoder */ - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - if (ost->encoding_needed) { - av_freep(&ost->enc_ctx->stats_in); - } - total_packets_written += ost->packets_written; - if (!ost->packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM)) { - av_log(NULL, AV_LOG_FATAL, "Empty output on stream %d.\n", i); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + uint64_t packets_written; + packets_written = atomic_load(&ost->packets_written); + total_packets_written += packets_written; + if (!packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM)) { + av_log(ost, AV_LOG_FATAL, "Empty output\n"); exit_program(1); } } @@ -4637,46 +4261,12 @@ static int transcode(void) exit_program(1); } - /* close each decoder */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; - if (ist->decoding_needed) { - avcodec_close(ist->dec_ctx); - if (ist->hwaccel_uninit) - ist->hwaccel_uninit(ist->dec_ctx); - } - } - hw_device_free_all(); /* finished ! */ ret = 0; fail: -#if HAVE_THREADS - free_input_threads(); -#endif - - if (output_streams) { - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - if (ost) { - if (ost->logfile) { - if (fclose(ost->logfile)) - av_log(NULL, AV_LOG_ERROR, - "Error closing logfile, loss of information possible: %s\n", - av_err2str(AVERROR(errno))); - ost->logfile = NULL; - } - av_freep(&ost->forced_kf_pts); - av_freep(&ost->apad); - av_freep(&ost->disposition); - av_dict_free(&ost->encoder_opts); - av_dict_free(&ost->sws_dict); - av_dict_free(&ost->swr_opts); - } - } - } return ret; } @@ -4725,29 +4315,31 @@ static int64_t getmaxrss(void) } void ffmpeg_var_cleanup() { - main_ffmpeg_return_code = 0; - longjmp_value = 0; received_sigterm = 0; received_nb_signals = 0; + transcode_init_done = ATOMIC_VAR_INIT(0); ffmpeg_exited = 0; + main_ffmpeg_return_code = 0; copy_ts_first_pts = AV_NOPTS_VALUE; + longjmp_value = 0; + want_sdp = 1; + enc_stats_files = NULL; + nb_enc_stats_files = 0; + + vstats_file = NULL; nb_frames_dup = 0; dup_warning = 1000; nb_frames_drop = 0; + decode_error_stat[0] = 0; + decode_error_stat[1] = 0; nb_output_dumped = 0; - want_sdp = 1; - progress_avio = NULL; - input_streams = NULL; - nb_input_streams = 0; input_files = NULL; nb_input_files = 0; - output_streams = NULL; - nb_output_streams = 0; output_files = NULL; nb_output_files = 0; @@ -4757,9 +4349,12 @@ void ffmpeg_var_cleanup() { last_time = -1; keyboard_last_time = 0; first_report = 1; + for(int i = 0; i < FF_ARRAY_ELEMS(qp_histogram); i++) { + qp_histogram[i] = 0; + } } -void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double)) +void set_report_callback(void (*callback)(int, float, float, int64_t, double, double, double)) { report_callback = callback; } @@ -4848,8 +4443,10 @@ int ffmpeg_execute(int argc, char **argv) OPT_OUTPUT, { .func_arg = opt_map }, "set input stream mapping", "[-]input_file_id[:stream_specifier][,sync_file_id[:stream_specifier]]" }, + #if FFMPEG_OPT_MAP_CHANNEL { "map_channel", HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_map_channel }, - "map an audio channel from one stream to another", "file.stream.channel[:syncfile.syncstream]" }, + "map an audio channel from one stream to another (deprecated)", "file.stream.channel[:syncfile.syncstream]" }, + #endif { "map_metadata", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(metadata_map) }, "set metadata information of outfile from infile", @@ -4922,8 +4519,6 @@ int ffmpeg_execute(int argc, char **argv) "set video sync method globally; deprecated, use -fps_mode", "" }, { "frame_drop_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &frame_drop_threshold }, "frame drop threshold", "" }, - { "async", HAS_ARG | OPT_INT | OPT_EXPERT, { &audio_sync_method }, - "audio sync method", "" }, { "adrift_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &audio_drift_threshold }, "audio drift threshold", "threshold" }, { "copyts", OPT_BOOL | OPT_EXPERT, { ©_ts }, @@ -4935,6 +4530,8 @@ int ffmpeg_execute(int argc, char **argv) { "shortest", OPT_BOOL | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(shortest) }, "finish encoding within shortest input" }, + { "shortest_buf_duration", HAS_ARG | OPT_FLOAT | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(shortest_buf_duration) }, + "maximum buffering duration (in seconds) for the -shortest option" }, { "bitexact", OPT_BOOL | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT | OPT_INPUT, { .off = OFFSET(bitexact) }, "bitexact mode" }, @@ -5007,15 +4604,28 @@ int ffmpeg_execute(int argc, char **argv) { "disposition", OPT_STRING | HAS_ARG | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(disposition) }, "disposition", "" }, - { "thread_queue_size", HAS_ARG | OPT_INT | OPT_OFFSET | OPT_EXPERT | OPT_INPUT, + { "thread_queue_size", HAS_ARG | OPT_INT | OPT_OFFSET | OPT_EXPERT | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(thread_queue_size) }, "set the maximum number of queued packets from the demuxer" }, - { "find_stream_info", OPT_BOOL | OPT_PERFILE | OPT_INPUT | OPT_EXPERT, { &find_stream_info }, + { "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT | OPT_OFFSET, { .off = OFFSET(find_stream_info) }, "read and decode the streams to fill missing information with heuristics" }, { "bits_per_raw_sample", OPT_INT | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(bits_per_raw_sample) }, "set the number of bits per raw sample", "number" }, + { "stats_enc_pre", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_pre) }, + "write encoding stats before encoding" }, + { "stats_enc_post", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_post) }, + "write encoding stats after encoding" }, + { "stats_mux_pre", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(mux_stats) }, + "write packets stats before muxing" }, + { "stats_enc_pre_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_pre_fmt) }, + "format of the stats written with -stats_enc_pre" }, + { "stats_enc_post_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_post_fmt) }, + "format of the stats written with -stats_enc_post" }, + { "stats_mux_pre_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(mux_stats_fmt) }, + "format of the stats written with -stats_mux_pre" }, + /* video options */ { "vframes", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_video_frames }, "set the number of video frames to output", "number" }, @@ -5034,6 +4644,16 @@ int ffmpeg_execute(int argc, char **argv) { "pix_fmt", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(frame_pix_fmts) }, "set pixel format", "format" }, + { "display_rotation", OPT_VIDEO | HAS_ARG | OPT_DOUBLE | OPT_SPEC | + OPT_INPUT, { .off = OFFSET(display_rotations) }, + "set pure counter-clockwise rotation in degrees for stream(s)", + "angle" }, + { "display_hflip", OPT_VIDEO | OPT_BOOL | OPT_SPEC | OPT_INPUT, { .off = OFFSET(display_hflips) }, + "set display horizontal flip for stream(s) " + "(overrides any display rotation if it is not set)"}, + { "display_vflip", OPT_VIDEO | OPT_BOOL | OPT_SPEC | OPT_INPUT, { .off = OFFSET(display_vflips) }, + "set display vertical flip for stream(s) " + "(overrides any display rotation if it is not set)"}, { "vn", OPT_VIDEO | OPT_BOOL | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT,{ .off = OFFSET(video_disable) }, "disable video" }, { "rc_override", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | @@ -5049,8 +4669,10 @@ int ffmpeg_execute(int argc, char **argv) { "passlogfile", OPT_VIDEO | HAS_ARG | OPT_STRING | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(passlogfiles) }, "select two pass log file name prefix", "prefix" }, + #if FFMPEG_OPT_PSNR { "psnr", OPT_VIDEO | OPT_BOOL | OPT_EXPERT, { &do_psnr }, - "calculate PSNR of compressed frames" }, + "calculate PSNR of compressed frames (deprecated, use -flags +psnr)" }, + #endif { "vstats", OPT_VIDEO | OPT_EXPERT , { .func_arg = opt_vstats }, "dump video coding statistics to file" }, { "vstats_file", OPT_VIDEO | HAS_ARG | OPT_EXPERT , { .func_arg = opt_vstats_file }, @@ -5088,8 +4710,6 @@ int ffmpeg_execute(int argc, char **argv) { "force_key_frames", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(forced_key_frames) }, "force key frames at specified timestamps", "timestamps" }, - { "ab", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, - "audio bitrate (please use -b:a)", "bitrate" }, { "b", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, "video bitrate (please use -b:v)", "bitrate" }, { "hwaccel", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | @@ -5109,6 +4729,11 @@ int ffmpeg_execute(int argc, char **argv) { "autoscale", HAS_ARG | OPT_BOOL | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT, { .off = OFFSET(autoscale) }, "automatically insert a scale filter at the end of the filter graph" }, + { "fix_sub_duration_heartbeat", OPT_VIDEO | OPT_BOOL | OPT_EXPERT | + OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(fix_sub_duration_heartbeat) }, + "set this video output stream to be a heartbeat stream for " + "fix_sub_duration, according to which subtitles should be split at " + "random access points" }, /* audio options */ { "aframes", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_audio_frames }, @@ -5126,11 +4751,11 @@ int ffmpeg_execute(int argc, char **argv) { "acodec", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_audio_codec }, "force audio codec ('copy' to copy stream)", "codec" }, + { "ab", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, + "audio bitrate (please use -b:a)", "bitrate" }, { "atag", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_old2new }, "force audio tag/fourcc", "fourcc/tag" }, - { "vol", OPT_AUDIO | HAS_ARG | OPT_INT, { &audio_volume }, - "change audio volume (256=normal)" , "volume" }, { "sample_fmt", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_STRING | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(sample_fmts) }, "set sample format", "format" }, @@ -5220,7 +4845,7 @@ int ffmpeg_execute(int argc, char **argv) ffmpeg_options = options; - int i, ret; + int ret; BenchmarkTimeStamps ti; int savedCode = setjmp(ex_buf__); @@ -5259,11 +4884,6 @@ int ffmpeg_execute(int argc, char **argv) exit_program(1); } - for (i = 0; i < nb_output_files; i++) { - if (strcmp(output_files[i]->format->name, "rtp")) - want_sdp = 0; - } - current_time = ti = get_benchmark_time_stamps(); if (transcode() < 0) exit_program(1); diff --git a/apple/src/fftools_ffmpeg.h b/apple/src/fftools_ffmpeg.h index 81d7d21..66b254f 100644 --- a/apple/src/fftools_ffmpeg.h +++ b/apple/src/fftools_ffmpeg.h @@ -1,6 +1,7 @@ /* * This file is part of FFmpeg. * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,6 +23,16 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - WARN_MULTIPLE_OPT_USAGE, MATCH_PER_STREAM_OPT, MATCH_PER_TYPE_OPT, SPECIFIER_OPT_FMT declarations migrated to + * ffmpeg_mux.h + * - "class" member field renamed as clazz + * - time field in set_report_callback updated as double + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -59,11 +70,13 @@ #include "config.h" +#include #include #include #include #include "fftools_cmdutils.h" +#include "fftools_sync_queue.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" @@ -85,6 +98,12 @@ #include "libswresample/swresample.h" +// deprecated features +#define FFMPEG_OPT_PSNR 1 +#define FFMPEG_OPT_MAP_CHANNEL 1 +#define FFMPEG_OPT_MAP_SYNC 1 +#define FFMPEG_ROTATION_METADATA 1 + enum VideoSyncMethod { VSYNC_AUTO = -1, VSYNC_PASSTHROUGH, @@ -113,15 +132,15 @@ typedef struct StreamMap { int disabled; /* 1 is this mapping is disabled by a negative map */ int file_index; int stream_index; - int sync_file_index; - int sync_stream_index; char *linklabel; /* name of an output link, for mapping lavfi outputs */ } StreamMap; +#if FFMPEG_OPT_MAP_CHANNEL typedef struct { int file_idx, stream_idx, channel_idx; // input int ofile_idx, ostream_idx; // output } AudioChannelMap; +#endif typedef struct OptionsContext { OptionGroup *g; @@ -157,6 +176,7 @@ typedef struct OptionsContext { int accurate_seek; int thread_queue_size; int input_sync_ref; + int find_stream_info; SpecifierOpt *ts_scale; int nb_ts_scale; @@ -174,11 +194,10 @@ typedef struct OptionsContext { /* output options */ StreamMap *stream_maps; int nb_stream_maps; +#if FFMPEG_OPT_MAP_CHANNEL AudioChannelMap *audio_channel_maps; /* one info entry per -map_channel */ int nb_audio_channel_maps; /* number of (valid) -map_channel settings */ - int metadata_global_manual; - int metadata_streams_manual; - int metadata_chapters_manual; +#endif const char **attachments; int nb_attachments; @@ -186,9 +205,10 @@ typedef struct OptionsContext { int64_t recording_time; int64_t stop_time; - uint64_t limit_filesize; + int64_t limit_filesize; float mux_preload; float mux_max_delay; + float shortest_buf_duration; int shortest; int bitexact; @@ -221,6 +241,12 @@ typedef struct OptionsContext { int nb_force_fps; SpecifierOpt *frame_aspect_ratios; int nb_frame_aspect_ratios; + SpecifierOpt *display_rotations; + int nb_display_rotations; + SpecifierOpt *display_hflips; + int nb_display_hflips; + SpecifierOpt *display_vflips; + int nb_display_vflips; SpecifierOpt *rc_overrides; int nb_rc_overrides; SpecifierOpt *intra_matrices; @@ -247,6 +273,8 @@ typedef struct OptionsContext { int nb_reinit_filters; SpecifierOpt *fix_sub_duration; int nb_fix_sub_duration; + SpecifierOpt *fix_sub_duration_heartbeat; + int nb_fix_sub_duration_heartbeat; SpecifierOpt *canvas_sizes; int nb_canvas_sizes; SpecifierOpt *pass; @@ -275,6 +303,18 @@ typedef struct OptionsContext { int nb_autoscale; SpecifierOpt *bits_per_raw_sample; int nb_bits_per_raw_sample; + SpecifierOpt *enc_stats_pre; + int nb_enc_stats_pre; + SpecifierOpt *enc_stats_post; + int nb_enc_stats_post; + SpecifierOpt *mux_stats; + int nb_mux_stats; + SpecifierOpt *enc_stats_pre_fmt; + int nb_enc_stats_pre_fmt; + SpecifierOpt *enc_stats_post_fmt; + int nb_enc_stats_post_fmt; + SpecifierOpt *mux_stats_fmt; + int nb_mux_stats_fmt; } OptionsContext; typedef struct InputFilter { @@ -350,12 +390,22 @@ typedef struct InputStream { #define DECODING_FOR_OST 1 #define DECODING_FOR_FILTER 2 int processing_needed; /* non zero if the packets must be processed */ + // should attach FrameData as opaque_ref after decoding + int want_frame_data; + /** + * Codec parameters - to be used by the decoding/streamcopy code. + * st->codecpar should not be accessed, because it may be modified + * concurrently by the demuxing thread. + */ + AVCodecParameters *par; AVCodecContext *dec_ctx; const AVCodec *dec; AVFrame *decoded_frame; AVPacket *pkt; + AVRational framerate_guessed; + int64_t prev_pkt_pts; int64_t start; /* time when read started */ /* predicted dts of the next packet read for this stream or (when there are @@ -368,6 +418,12 @@ typedef struct InputStream { int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units) int wrap_correction_done; + // the value of AVCodecParserContext.repeat_pict from the AVStream parser + // for the last packet returned from ifile_get_packet() + // -1 if unknown + // FIXME: this is a hack, the avstream parser should not be used + int last_pkt_repeat_pict; + int64_t filter_in_rescale_delta_last; int64_t min_pts; /* pts with the smallest value in a current stream */ @@ -417,12 +473,8 @@ typedef struct InputStream { char *hwaccel_device; enum AVPixelFormat hwaccel_output_format; - /* hwaccel context */ - void *hwaccel_ctx; - void (*hwaccel_uninit)(AVCodecContext *s); int (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame); enum AVPixelFormat hwaccel_pix_fmt; - enum AVPixelFormat hwaccel_retrieved_pix_fmt; /* stats */ // combined size of all the packets read @@ -439,38 +491,46 @@ typedef struct InputStream { int got_output; } InputStream; +typedef struct LastFrameDuration { + int stream_idx; + int64_t duration; +} LastFrameDuration; + typedef struct InputFile { + int index; + AVFormatContext *ctx; int eof_reached; /* true if eof reached */ int eagain; /* true if last read attempt returned EAGAIN */ - int ist_index; /* index of first stream in input_streams */ - int loop; /* set number of times input stream should be looped */ - int64_t duration; /* actual duration of the longest stream in a file - at the moment when looping happens */ - AVRational time_base; /* time base of the duration */ int64_t input_ts_offset; int input_sync_ref; - + /** + * Effective format start time based on enabled streams. + */ + int64_t start_time_effective; int64_t ts_offset; + /** + * Extra timestamp offset added by discontinuity handling. + */ + int64_t ts_offset_discont; int64_t last_ts; int64_t start_time; /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */ int64_t recording_time; - int nb_streams; /* number of stream that ffmpeg is aware of; may be different - from ctx.nb_streams if new streams appear during av_read_frame() */ - int nb_streams_warn; /* number of streams that the user was warned of */ + + /* streams that ffmpeg is aware of; + * there may be extra streams in ctx that are not mapped to an InputStream + * if new streams appear dynamically during demuxing */ + InputStream **streams; + int nb_streams; + int rate_emu; float readrate; int accurate_seek; - AVPacket *pkt; - -#if HAVE_THREADS - AVThreadMessageQueue *in_thread_queue; - pthread_t thread; /* thread reading from this file */ - int non_blocking; /* reading packets from the thread should not block */ - int joined; /* the thread has been joined */ - int thread_queue_size; /* maximum number of queued packets */ -#endif + /* when looping the input file, this queue is used by decoders to report + * the last frame duration back to the demuxer thread */ + AVThreadMessageQueue *audio_duration_queue; + int audio_duration_queue_size; } InputFile; enum forced_keyframes_const { @@ -485,6 +545,41 @@ enum forced_keyframes_const { #define ABORT_ON_FLAG_EMPTY_OUTPUT (1 << 0) #define ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM (1 << 1) +enum EncStatsType { + ENC_STATS_LITERAL = 0, + ENC_STATS_FILE_IDX, + ENC_STATS_STREAM_IDX, + ENC_STATS_FRAME_NUM, + ENC_STATS_FRAME_NUM_IN, + ENC_STATS_TIMEBASE, + ENC_STATS_TIMEBASE_IN, + ENC_STATS_PTS, + ENC_STATS_PTS_TIME, + ENC_STATS_PTS_IN, + ENC_STATS_PTS_TIME_IN, + ENC_STATS_DTS, + ENC_STATS_DTS_TIME, + ENC_STATS_SAMPLE_NUM, + ENC_STATS_NB_SAMPLES, + ENC_STATS_PKT_SIZE, + ENC_STATS_BITRATE, + ENC_STATS_AVG_BITRATE, +}; + +typedef struct EncStatsComponent { + enum EncStatsType type; + + uint8_t *str; + size_t str_len; +} EncStatsComponent; + +typedef struct EncStats { + EncStatsComponent *components; + int nb_components; + + AVIOContext *io; +} EncStats; + extern const char *const forced_keyframes_const_names[]; typedef enum { @@ -492,68 +587,92 @@ typedef enum { MUXER_FINISHED = 2, } OSTFinished ; +enum { + KF_FORCE_SOURCE = 1, + KF_FORCE_SOURCE_NO_DROP = 2, +}; + +typedef struct KeyframeForceCtx { + int type; + + int64_t ref_pts; + + // timestamps of the forced keyframes, in AV_TIME_BASE_Q + int64_t *pts; + int nb_pts; + int index; + + AVExpr *pexpr; + double expr_const_values[FKF_NB]; + + int dropped_keyframe; +} KeyframeForceCtx; + typedef struct OutputStream { + const AVClass *clazz; + int file_index; /* file index */ int index; /* stream index in the output file */ - int source_index; /* InputStream index */ + + /* input stream that is the source for this output stream; + * may be NULL for streams with no well-defined source, e.g. + * attachments or outputs from complex filtergraphs */ + InputStream *ist; + AVStream *st; /* stream in the output file */ - int encoding_needed; /* true if encoding needed for this stream */ - int64_t frame_number; - /* input pts and corresponding output pts - for A/V sync */ - struct InputStream *sync_ist; /* input stream to sync against */ - int64_t sync_opts; /* output frame counter, could be changed to some true timestamp */ // FIXME look at frame_number - /* pts of the first frame encoded for this stream, used for limiting - * recording time */ - int64_t first_pts; - /* dts of the last packet sent to the muxer */ + /* number of frames emitted by the video-encoding sync code */ + int64_t vsync_frame_number; + /* predicted pts of the next frame to be encoded + * audio/video encoding only */ + int64_t next_pts; + /* dts of the last packet sent to the muxing queue, in AV_TIME_BASE_Q */ int64_t last_mux_dts; + /* pts of the last frame received from the filters, in AV_TIME_BASE_Q */ + int64_t last_filter_pts; + + // timestamp from which the streamcopied streams should start, + // in AV_TIME_BASE_Q; + // everything before it should be discarded + int64_t ts_copy_start; + // the timebase of the packets sent to the muxer AVRational mux_timebase; AVRational enc_timebase; - AVBSFContext *bsf_ctx; - AVCodecContext *enc_ctx; - AVCodecParameters *ref_par; /* associated input codec parameters with encoders options applied */ - const AVCodec *enc; - int64_t max_frames; AVFrame *filtered_frame; AVFrame *last_frame; + AVFrame *sq_frame; AVPacket *pkt; int64_t last_dropped; int64_t last_nb0_frames[3]; - void *hwaccel_ctx; - /* video only */ AVRational frame_rate; AVRational max_frame_rate; enum VideoSyncMethod vsync_method; int is_cfr; - const char *fps_mode; int force_fps; int top_field_first; +#if FFMPEG_ROTATION_METADATA int rotate_overridden; +#endif int autoscale; + int bitexact; int bits_per_raw_sample; +#if FFMPEG_ROTATION_METADATA double rotate_override_value; +#endif AVRational frame_aspect_ratio; - /* forced key frames */ - int64_t forced_kf_ref_pts; - int64_t *forced_kf_pts; - int forced_kf_count; - int forced_kf_index; - char *forced_keyframes; - AVExpr *forced_keyframes_pexpr; - double forced_keyframes_expr_const_values[FKF_NB]; - int dropped_keyframe; + KeyframeForceCtx kf; /* audio only */ +#if FFMPEG_OPT_MAP_CHANNEL int *audio_channels_map; /* list of the channels id to pick from the source stream */ int audio_channels_mapped; /* number of channels in audio_channels_map */ +#endif char *logfile_prefix; FILE *logfile; @@ -569,7 +688,6 @@ typedef struct OutputStream { char *apad; OSTFinished finished; /* no more packets should be written for this stream */ int unavailable; /* true if the steram is unavailable (possibly temporarily) */ - int stream_copy; // init_output_stream() has been called for this stream // The encoder and the bitstream filters have been initialized and the stream @@ -582,15 +700,16 @@ typedef struct OutputStream { int streamcopy_started; int copy_initial_nonkeyframes; int copy_prior_start; - char *disposition; int keep_pix_fmt; /* stats */ - // combined size of all the packets written - uint64_t data_size; + // combined size of all the packets sent to the muxer + uint64_t data_size_mux; + // combined size of all the packets received from the encoder + uint64_t data_size_enc; // number of packets send to the muxer - uint64_t packets_written; + atomic_uint_least64_t packets_written; // number of frames/samples sent to the encoder uint64_t frames_encoded; uint64_t samples_encoded; @@ -600,51 +719,48 @@ typedef struct OutputStream { /* packet quality factor */ int quality; - int max_muxing_queue_size; - - /* the packets are buffered here until the muxer is ready to be initialized */ - AVFifo *muxing_queue; - - /* - * The size of the AVPackets' buffers in queue. - * Updated when a packet is either pushed or pulled from the queue. - */ - size_t muxing_queue_data_size; - - /* Threshold after which max_muxing_queue_size will be in effect */ - size_t muxing_queue_data_threshold; - /* packet picture type */ int pict_type; /* frame encode sum of squared error values */ int64_t error[4]; + + int sq_idx_encode; + int sq_idx_mux; + + EncStats enc_stats_pre; + EncStats enc_stats_post; + + /* + * bool on whether this stream should be utilized for splitting + * subtitles utilizing fix_sub_duration at random access points. + */ + unsigned int fix_sub_duration_heartbeat; } OutputStream; typedef struct OutputFile { + const AVClass *clazz; + int index; const AVOutputFormat *format; + const char *url; + + OutputStream **streams; + int nb_streams; + + SyncQueue *sq_encode; - AVFormatContext *ctx; - AVDictionary *opts; - int ost_index; /* index of the first stream in output_streams */ int64_t recording_time; ///< desired length of the resulting file in microseconds == AV_TIME_BASE units int64_t start_time; ///< start time in microseconds == AV_TIME_BASE units - uint64_t limit_filesize; /* filesize limit expressed in bytes */ int shortest; - - int header_written; + int bitexact; } OutputFile; -extern __thread InputStream **input_streams; -extern __thread int nb_input_streams; extern __thread InputFile **input_files; extern __thread int nb_input_files; -extern __thread OutputStream **output_streams; -extern __thread int nb_output_streams; extern __thread OutputFile **output_files; extern __thread int nb_output_files; @@ -658,13 +774,10 @@ extern __thread float audio_drift_threshold; extern __thread float dts_delta_threshold; extern __thread float dts_error_threshold; -extern __thread int audio_volume; -extern __thread int audio_sync_method; extern __thread enum VideoSyncMethod video_sync_method; extern __thread float frame_drop_threshold; extern __thread int do_benchmark; extern __thread int do_benchmark_all; -extern __thread int do_deinterlace; extern __thread int do_hex_dump; extern __thread int do_pkt_dump; extern __thread int copy_ts; @@ -677,7 +790,6 @@ extern __thread int print_stats; extern __thread int64_t stats_period; extern __thread int qp_hist; extern __thread int stdin_interaction; -extern __thread int frame_bits_per_raw_sample; extern __thread AVIOContext *progress_avio; extern __thread float max_error_rate; @@ -688,15 +800,20 @@ extern __thread int auto_conversion_filters; extern __thread const AVIOInterruptCB int_cb; -#if CONFIG_QSV -extern __thread char *qsv_device; -#endif extern __thread HWDevice *filter_hw_device; -extern __thread int want_sdp; extern __thread unsigned nb_output_dumped; extern __thread int main_ffmpeg_return_code; +extern __thread int ignore_unknown_streams; +extern __thread int copy_unknown_streams; + +extern __thread int recast_media; + +#if FFMPEG_OPT_PSNR +extern __thread int do_psnr; +#endif + void term_init(void); void term_exit(void); @@ -705,7 +822,12 @@ void show_usage(void); void remove_avoptions(AVDictionary **a, AVDictionary *b); void assert_avoptions(AVDictionary *m); -int guess_input_channel_layout(InputStream *ist); +void assert_file_overwrite(const char *filename); +char *file_read(const char *filename); +AVDictionary *strip_specifiers(const AVDictionary *dict); +const AVCodec *find_codec_or_die(void *logctx, const char *name, + enum AVMediaType type, int encoder); +int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_idx, int is_global); int configure_filtergraph(FilterGraph *fg); void check_filter_outputs(void); @@ -719,8 +841,9 @@ int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame); int ffmpeg_parse_options(int argc, char **argv); -int videotoolbox_init(AVCodecContext *s); -int qsv_init(AVCodecContext *s); +void enc_stats_write(OutputStream *ost, EncStats *es, + const AVFrame *frame, const AVPacket *pkt, + uint64_t frame_num); HWDevice *hw_device_get_by_name(const char *name); int hw_device_init_from_string(const char *arg, HWDevice **dev); @@ -732,15 +855,58 @@ int hw_device_setup_for_filter(FilterGraph *fg); int hwaccel_decode_init(AVCodecContext *avctx); -/* open the muxer when all the streams are initialized */ -int of_check_init(OutputFile *of); +/* + * Initialize muxing state for the given stream, should be called + * after the codec/streamcopy setup has been done. + * + * Open the muxer once all the streams have been initialized. + */ +int of_stream_init(OutputFile *of, OutputStream *ost); int of_write_trailer(OutputFile *of); +int of_open(const OptionsContext *o, const char *filename); void of_close(OutputFile **pof); -void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, - int unqueue); +void of_enc_stats_close(void); + +/* + * Send a single packet to the output, applying any bitstream filters + * associated with the output stream. This may result in any number + * of packets actually being written, depending on what bitstream + * filters are applied. The supplied packet is consumed and will be + * blank (as if newly-allocated) when this function returns. + * + * If eof is set, instead indicate EOF to all bitstream filters and + * therefore flush any delayed packets to the output. A blank packet + * must be supplied in this case. + */ +void of_output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof); +int64_t of_filesize(OutputFile *of); + +int ifile_open(const OptionsContext *o, const char *filename); +void ifile_close(InputFile **f); + +/** + * Get next input packet from the demuxer. + * + * @param pkt the packet is written here when this function returns 0 + * @return + * - 0 when a packet has been read successfully + * - 1 when stream end was reached, but the stream is looped; + * caller should flush decoders and read from this demuxer again + * - a negative error code on failure + */ +int ifile_get_packet(InputFile *f, AVPacket **pkt); + +/* iterate over all input streams in all input files; + * pass NULL to start iteration */ +InputStream *ist_iter(InputStream *prev); + +extern const char * const opt_name_codec_names[]; +extern const char * const opt_name_codec_tags[]; +extern const char * const opt_name_frame_rates[]; +extern const char * const opt_name_top_field_first[]; -void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double)); +void set_report_callback(void (*callback)(int, float, float, int64_t, double, double, double)); void cancel_operation(long id); #endif /* FFTOOLS_FFMPEG_H */ diff --git a/apple/src/fftools_ffmpeg_demux.c b/apple/src/fftools_ffmpeg_demux.c new file mode 100644 index 0000000..de246da --- /dev/null +++ b/apple/src/fftools_ffmpeg_demux.c @@ -0,0 +1,1148 @@ +/* + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_demux.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools_ prefix added to the file name + * - fftools_ffmpeg_mux.h include added + */ + +#include +#include + +#include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/display.h" +#include "libavutil/error.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" +#include "libavutil/time.h" +#include "libavutil/timestamp.h" +#include "libavutil/thread.h" +#include "libavutil/threadmessage.h" + +#include "libavcodec/packet.h" + +#include "libavformat/avformat.h" + +static const char *const opt_name_discard[] = {"discard", NULL}; +static const char *const opt_name_reinit_filters[] = {"reinit_filter", NULL}; +static const char *const opt_name_fix_sub_duration[] = {"fix_sub_duration", NULL}; +static const char *const opt_name_canvas_sizes[] = {"canvas_size", NULL}; +static const char *const opt_name_guess_layout_max[] = {"guess_layout_max", NULL}; +static const char *const opt_name_ts_scale[] = {"itsscale", NULL}; +static const char *const opt_name_hwaccels[] = {"hwaccel", NULL}; +static const char *const opt_name_hwaccel_devices[] = {"hwaccel_device", NULL}; +static const char *const opt_name_hwaccel_output_formats[] = {"hwaccel_output_format", NULL}; +static const char *const opt_name_autorotate[] = {"autorotate", NULL}; +static const char *const opt_name_display_rotations[] = {"display_rotation", NULL}; +static const char *const opt_name_display_hflips[] = {"display_hflip", NULL}; +static const char *const opt_name_display_vflips[] = {"display_vflip", NULL}; + +typedef struct Demuxer { + InputFile f; + + /* number of times input stream should be looped */ + int loop; + /* actual duration of the longest stream in a file at the moment when + * looping happens */ + int64_t duration; + /* time base of the duration */ + AVRational time_base; + + /* number of streams that the user was warned of */ + int nb_streams_warn; + + AVThreadMessageQueue *in_thread_queue; + int thread_queue_size; + pthread_t thread; + int non_blocking; +} Demuxer; + +typedef struct DemuxMsg { + AVPacket *pkt; + int looping; + + // repeat_pict from the demuxer-internal parser + int repeat_pict; +} DemuxMsg; + +static Demuxer *demuxer_from_ifile(InputFile *f) +{ + return (Demuxer*)f; +} + +static void report_new_stream(Demuxer *d, const AVPacket *pkt) +{ + AVStream *st = d->f.ctx->streams[pkt->stream_index]; + + if (pkt->stream_index < d->nb_streams_warn) + return; + av_log(NULL, AV_LOG_WARNING, + "New %s stream %d:%d at pos:%"PRId64" and DTS:%ss\n", + av_get_media_type_string(st->codecpar->codec_type), + d->f.index, pkt->stream_index, + pkt->pos, av_ts2timestr(pkt->dts, &st->time_base)); + d->nb_streams_warn = pkt->stream_index + 1; +} + +static void ifile_duration_update(Demuxer *d, InputStream *ist, + int64_t last_duration) +{ + /* the total duration of the stream, max_pts - min_pts is + * the duration of the stream without the last frame */ + if (ist->max_pts > ist->min_pts && + ist->max_pts - (uint64_t)ist->min_pts < INT64_MAX - last_duration) + last_duration += ist->max_pts - ist->min_pts; + + if (!d->duration || + av_compare_ts(d->duration, d->time_base, + last_duration, ist->st->time_base) < 0) { + d->duration = last_duration; + d->time_base = ist->st->time_base; + } +} + +static int seek_to_start(Demuxer *d) +{ + InputFile *ifile = &d->f; + AVFormatContext *is = ifile->ctx; + InputStream *ist; + int ret; + + ret = avformat_seek_file(is, -1, INT64_MIN, is->start_time, is->start_time, 0); + if (ret < 0) + return ret; + + if (ifile->audio_duration_queue_size) { + /* duration is the length of the last frame in a stream + * when audio stream is present we don't care about + * last video frame length because it's not defined exactly */ + int got_durations = 0; + + while (got_durations < ifile->audio_duration_queue_size) { + LastFrameDuration dur; + ret = av_thread_message_queue_recv(ifile->audio_duration_queue, &dur, 0); + if (ret < 0) + return ret; + got_durations++; + + ist = ifile->streams[dur.stream_idx]; + ifile_duration_update(d, ist, dur.duration); + } + } else { + for (int i = 0; i < ifile->nb_streams; i++) { + int64_t duration = 0; + ist = ifile->streams[i]; + + if (ist->framerate.num) { + duration = av_rescale_q(1, av_inv_q(ist->framerate), ist->st->time_base); + } else if (ist->st->avg_frame_rate.num) { + duration = av_rescale_q(1, av_inv_q(ist->st->avg_frame_rate), ist->st->time_base); + } else { + duration = 1; + } + + ifile_duration_update(d, ist, duration); + } + } + + if (d->loop > 0) + d->loop--; + + return ret; +} + +static void ts_fixup(Demuxer *d, AVPacket *pkt, int *repeat_pict) +{ + InputFile *ifile = &d->f; + InputStream *ist = ifile->streams[pkt->stream_index]; + const int64_t start_time = ifile->start_time_effective; + int64_t duration; + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "demuxer -> ist_index:%d:%d type:%s " + "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s\n", + ifile->index, pkt->stream_index, + av_get_media_type_string(ist->st->codecpar->codec_type), + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), + av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base)); + } + + if (!ist->wrap_correction_done && start_time != AV_NOPTS_VALUE && + ist->st->pts_wrap_bits < 64) { + int64_t stime, stime2; + + stime = av_rescale_q(start_time, AV_TIME_BASE_Q, ist->st->time_base); + stime2= stime + (1ULL<st->pts_wrap_bits); + ist->wrap_correction_done = 1; + + if(stime2 > stime && pkt->dts != AV_NOPTS_VALUE && pkt->dts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { + pkt->dts -= 1ULL<st->pts_wrap_bits; + ist->wrap_correction_done = 0; + } + if(stime2 > stime && pkt->pts != AV_NOPTS_VALUE && pkt->pts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { + pkt->pts -= 1ULL<st->pts_wrap_bits; + ist->wrap_correction_done = 0; + } + } + + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); + + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts *= ist->ts_scale; + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts *= ist->ts_scale; + + duration = av_rescale_q(d->duration, d->time_base, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) { + pkt->pts += duration; + ist->max_pts = FFMAX(pkt->pts, ist->max_pts); + ist->min_pts = FFMIN(pkt->pts, ist->min_pts); + } + + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += duration; + + *repeat_pict = -1; + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + av_stream_get_parser(ist->st)) + *repeat_pict = av_stream_get_parser(ist->st)->repeat_pict; +} + +static void thread_set_name(InputFile *f) +{ + char name[16]; + snprintf(name, sizeof(name), "dmx%d:%s", f->index, f->ctx->iformat->name); + ff_thread_setname(name); +} + +static void *input_thread(void *arg) +{ + Demuxer *d = arg; + InputFile *f = &d->f; + AVPacket *pkt; + unsigned flags = d->non_blocking ? AV_THREAD_MESSAGE_NONBLOCK : 0; + int ret = 0; + + pkt = av_packet_alloc(); + if (!pkt) { + ret = AVERROR(ENOMEM); + goto finish; + } + + thread_set_name(f); + + while (1) { + DemuxMsg msg = { NULL }; + + ret = av_read_frame(f->ctx, pkt); + + if (ret == AVERROR(EAGAIN)) { + av_usleep(10000); + continue; + } + if (ret < 0) { + if (d->loop) { + /* signal looping to the consumer thread */ + msg.looping = 1; + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, 0); + if (ret >= 0) + ret = seek_to_start(d); + if (ret >= 0) + continue; + + /* fallthrough to the error path */ + } + + if (ret == AVERROR_EOF) + av_log(NULL, AV_LOG_VERBOSE, "EOF in input file %d\n", f->index); + else + av_log(NULL, AV_LOG_ERROR, "Error demuxing input file %d: %s\n", + f->index, av_err2str(ret)); + + break; + } + + if (do_pkt_dump) { + av_pkt_dump_log2(NULL, AV_LOG_INFO, pkt, do_hex_dump, + f->ctx->streams[pkt->stream_index]); + } + + /* the following test is needed in case new streams appear + dynamically in stream : we ignore them */ + if (pkt->stream_index >= f->nb_streams) { + report_new_stream(d, pkt); + av_packet_unref(pkt); + continue; + } + + if (pkt->flags & AV_PKT_FLAG_CORRUPT) { + av_log(NULL, exit_on_error ? AV_LOG_FATAL : AV_LOG_WARNING, + "%s: corrupt input packet in stream %d\n", + f->ctx->url, pkt->stream_index); + if (exit_on_error) { + av_packet_unref(pkt); + ret = AVERROR_INVALIDDATA; + break; + } + } + + ts_fixup(d, pkt, &msg.repeat_pict); + + msg.pkt = av_packet_alloc(); + if (!msg.pkt) { + av_packet_unref(pkt); + ret = AVERROR(ENOMEM); + break; + } + av_packet_move_ref(msg.pkt, pkt); + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, flags); + if (flags && ret == AVERROR(EAGAIN)) { + flags = 0; + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, flags); + av_log(f->ctx, AV_LOG_WARNING, + "Thread message queue blocking; consider raising the " + "thread_queue_size option (current value: %d)\n", + d->thread_queue_size); + } + if (ret < 0) { + if (ret != AVERROR_EOF) + av_log(f->ctx, AV_LOG_ERROR, + "Unable to send packet to main thread: %s\n", + av_err2str(ret)); + av_packet_free(&msg.pkt); + break; + } + } + +finish: + av_assert0(ret < 0); + av_thread_message_queue_set_err_recv(d->in_thread_queue, ret); + + av_packet_free(&pkt); + + av_log(NULL, AV_LOG_VERBOSE, "Terminating demuxer thread %d\n", f->index); + + return NULL; +} + +static void thread_stop(Demuxer *d) +{ + InputFile *f = &d->f; + DemuxMsg msg; + + if (!d->in_thread_queue) + return; + av_thread_message_queue_set_err_send(d->in_thread_queue, AVERROR_EOF); + while (av_thread_message_queue_recv(d->in_thread_queue, &msg, 0) >= 0) + av_packet_free(&msg.pkt); + + pthread_join(d->thread, NULL); + av_thread_message_queue_free(&d->in_thread_queue); + av_thread_message_queue_free(&f->audio_duration_queue); +} + +static int thread_start(Demuxer *d) +{ + int ret; + InputFile *f = &d->f; + + if (d->thread_queue_size <= 0) + d->thread_queue_size = (nb_input_files > 1 ? 8 : 1); + + if (nb_input_files > 1 && + (f->ctx->pb ? !f->ctx->pb->seekable : + strcmp(f->ctx->iformat->name, "lavfi"))) + d->non_blocking = 1; + ret = av_thread_message_queue_alloc(&d->in_thread_queue, + d->thread_queue_size, sizeof(DemuxMsg)); + if (ret < 0) + return ret; + + if (d->loop) { + int nb_audio_dec = 0; + + for (int i = 0; i < f->nb_streams; i++) { + InputStream *ist = f->streams[i]; + nb_audio_dec += !!(ist->decoding_needed && + ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO); + } + + if (nb_audio_dec) { + ret = av_thread_message_queue_alloc(&f->audio_duration_queue, + nb_audio_dec, sizeof(LastFrameDuration)); + if (ret < 0) + goto fail; + f->audio_duration_queue_size = nb_audio_dec; + } + } + + if ((ret = pthread_create(&d->thread, NULL, input_thread, d))) { + av_log(NULL, AV_LOG_ERROR, "pthread_create failed: %s. Try to increase `ulimit -v` or decrease `ulimit -s`.\n", strerror(ret)); + ret = AVERROR(ret); + goto fail; + } + + return 0; +fail: + av_thread_message_queue_free(&d->in_thread_queue); + return ret; +} + +int ifile_get_packet(InputFile *f, AVPacket **pkt) +{ + Demuxer *d = demuxer_from_ifile(f); + InputStream *ist; + DemuxMsg msg; + int ret; + + if (!d->in_thread_queue) { + ret = thread_start(d); + if (ret < 0) + return ret; + } + + if (f->readrate || f->rate_emu) { + int i; + int64_t file_start = copy_ts * ( + (f->start_time_effective != AV_NOPTS_VALUE ? f->start_time_effective * !start_at_zero : 0) + + (f->start_time != AV_NOPTS_VALUE ? f->start_time : 0) + ); + float scale = f->rate_emu ? 1.0 : f->readrate; + for (i = 0; i < f->nb_streams; i++) { + InputStream *ist = f->streams[i]; + int64_t stream_ts_offset, pts, now; + if (!ist->nb_packets || (ist->decoding_needed && !ist->got_output)) continue; + stream_ts_offset = FFMAX(ist->first_dts != AV_NOPTS_VALUE ? ist->first_dts : 0, file_start); + pts = av_rescale(ist->dts, 1000000, AV_TIME_BASE); + now = (av_gettime_relative() - ist->start) * scale + stream_ts_offset; + if (pts > now) + return AVERROR(EAGAIN); + } + } + + ret = av_thread_message_queue_recv(d->in_thread_queue, &msg, + d->non_blocking ? + AV_THREAD_MESSAGE_NONBLOCK : 0); + if (ret < 0) + return ret; + if (msg.looping) + return 1; + + ist = f->streams[msg.pkt->stream_index]; + ist->last_pkt_repeat_pict = msg.repeat_pict; + + *pkt = msg.pkt; + return 0; +} + +static void ist_free(InputStream **pist) +{ + InputStream *ist = *pist; + + if (!ist) + return; + + av_frame_free(&ist->decoded_frame); + av_packet_free(&ist->pkt); + av_dict_free(&ist->decoder_opts); + avsubtitle_free(&ist->prev_sub.subtitle); + av_frame_free(&ist->sub2video.frame); + av_freep(&ist->filters); + av_freep(&ist->hwaccel_device); + av_freep(&ist->dts_buffer); + + avcodec_free_context(&ist->dec_ctx); + avcodec_parameters_free(&ist->par); + + av_freep(pist); +} + +void ifile_close(InputFile **pf) +{ + InputFile *f = *pf; + Demuxer *d = demuxer_from_ifile(f); + + if (!f) + return; + + thread_stop(d); + + for (int i = 0; i < f->nb_streams; i++) + ist_free(&f->streams[i]); + av_freep(&f->streams); + + avformat_close_input(&f->ctx); + + av_freep(pf); +} + +static const AVCodec *choose_decoder(const OptionsContext *o, AVFormatContext *s, AVStream *st, + enum HWAccelID hwaccel_id, enum AVHWDeviceType hwaccel_device_type) + +{ + char *codec_name = NULL; + + MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st); + if (codec_name) { + const AVCodec *codec = find_codec_or_die(NULL, codec_name, st->codecpar->codec_type, 0); + st->codecpar->codec_id = codec->id; + if (recast_media && st->codecpar->codec_type != codec->type) + st->codecpar->codec_type = codec->type; + return codec; + } else { + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + hwaccel_id == HWACCEL_GENERIC && + hwaccel_device_type != AV_HWDEVICE_TYPE_NONE) { + const AVCodec *c; + void *i = NULL; + + while ((c = av_codec_iterate(&i))) { + const AVCodecHWConfig *config; + + if (c->id != st->codecpar->codec_id || + !av_codec_is_decoder(c)) + continue; + + for (int j = 0; (config = avcodec_get_hw_config(c, j)); j++) { + if (config->device_type == hwaccel_device_type) { + av_log(NULL, AV_LOG_VERBOSE, "Selecting decoder '%s' because of requested hwaccel method %s\n", + c->name, av_hwdevice_get_type_name(hwaccel_device_type)); + return c; + } + } + } + } + + return avcodec_find_decoder(st->codecpar->codec_id); + } +} + +static int guess_input_channel_layout(InputStream *ist) +{ + AVCodecContext *dec = ist->dec_ctx; + + if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) { + char layout_name[256]; + + if (dec->ch_layout.nb_channels > ist->guess_layout_max) + return 0; + av_channel_layout_default(&dec->ch_layout, dec->ch_layout.nb_channels); + if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) + return 0; + av_channel_layout_describe(&dec->ch_layout, layout_name, sizeof(layout_name)); + av_log(NULL, AV_LOG_WARNING, "Guessed Channel Layout for Input Stream " + "#%d.%d : %s\n", ist->file_index, ist->st->index, layout_name); + } + return 1; +} + +static void add_display_matrix_to_stream(const OptionsContext *o, + AVFormatContext *ctx, AVStream *st) +{ + double rotation = DBL_MAX; + int hflip = -1, vflip = -1; + int hflip_set = 0, vflip_set = 0, rotation_set = 0; + int32_t *buf; + + MATCH_PER_STREAM_OPT(display_rotations, dbl, rotation, ctx, st); + MATCH_PER_STREAM_OPT(display_hflips, i, hflip, ctx, st); + MATCH_PER_STREAM_OPT(display_vflips, i, vflip, ctx, st); + + rotation_set = rotation != DBL_MAX; + hflip_set = hflip != -1; + vflip_set = vflip != -1; + + if (!rotation_set && !hflip_set && !vflip_set) + return; + + buf = (int32_t *)av_stream_new_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); + if (!buf) { + av_log(NULL, AV_LOG_FATAL, "Failed to generate a display matrix!\n"); + exit_program(1); + } + + av_display_rotation_set(buf, + rotation_set ? -(rotation) : -0.0f); + + av_display_matrix_flip(buf, + hflip_set ? hflip : 0, + vflip_set ? vflip : 0); +} + +/* Add all the streams from the given input file to the demuxer */ +static void add_input_streams(const OptionsContext *o, Demuxer *d) +{ + InputFile *f = &d->f; + AVFormatContext *ic = f->ctx; + int i, ret; + + for (i = 0; i < ic->nb_streams; i++) { + AVStream *st = ic->streams[i]; + AVCodecParameters *par = st->codecpar; + InputStream *ist; + char *framerate = NULL, *hwaccel_device = NULL; + const char *hwaccel = NULL; + char *hwaccel_output_format = NULL; + char *codec_tag = NULL; + char *next; + char *discard_str = NULL; + const AVClass *cc = avcodec_get_class(); + const AVOption *discard_opt = av_opt_find(&cc, "skip_frame", NULL, + 0, AV_OPT_SEARCH_FAKE_OBJ); + + ist = ALLOC_ARRAY_ELEM(f->streams, f->nb_streams); + ist->st = st; + ist->file_index = f->index; + ist->discard = 1; + st->discard = AVDISCARD_ALL; + ist->nb_samples = 0; + ist->first_dts = AV_NOPTS_VALUE; + ist->min_pts = INT64_MAX; + ist->max_pts = INT64_MIN; + + ist->ts_scale = 1.0; + MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st); + + ist->autorotate = 1; + MATCH_PER_STREAM_OPT(autorotate, i, ist->autorotate, ic, st); + + MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st); + if (codec_tag) { + uint32_t tag = strtol(codec_tag, &next, 0); + if (*next) + tag = AV_RL32(codec_tag); + st->codecpar->codec_tag = tag; + } + + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + add_display_matrix_to_stream(o, ic, st); + + MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st); + MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, + hwaccel_output_format, ic, st); + + if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "cuvid")) { + av_log(NULL, AV_LOG_WARNING, + "WARNING: defaulting hwaccel_output_format to cuda for compatibility " + "with old commandlines. This behaviour is DEPRECATED and will be removed " + "in the future. Please explicitly set \"-hwaccel_output_format cuda\".\n"); + ist->hwaccel_output_format = AV_PIX_FMT_CUDA; + } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "qsv")) { + av_log(NULL, AV_LOG_WARNING, + "WARNING: defaulting hwaccel_output_format to qsv for compatibility " + "with old commandlines. This behaviour is DEPRECATED and will be removed " + "in the future. Please explicitly set \"-hwaccel_output_format qsv\".\n"); + ist->hwaccel_output_format = AV_PIX_FMT_QSV; + } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "mediacodec")) { + // There is no real AVHWFrameContext implementation. Set + // hwaccel_output_format to avoid av_hwframe_transfer_data error. + ist->hwaccel_output_format = AV_PIX_FMT_MEDIACODEC; + } else if (hwaccel_output_format) { + ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format); + if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) { + av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output " + "format: %s", hwaccel_output_format); + } + } else { + ist->hwaccel_output_format = AV_PIX_FMT_NONE; + } + + if (hwaccel) { + // The NVDEC hwaccels use a CUDA device, so remap the name here. + if (!strcmp(hwaccel, "nvdec") || !strcmp(hwaccel, "cuvid")) + hwaccel = "cuda"; + + if (!strcmp(hwaccel, "none")) + ist->hwaccel_id = HWACCEL_NONE; + else if (!strcmp(hwaccel, "auto")) + ist->hwaccel_id = HWACCEL_AUTO; + else { + enum AVHWDeviceType type = av_hwdevice_find_type_by_name(hwaccel); + if (type != AV_HWDEVICE_TYPE_NONE) { + ist->hwaccel_id = HWACCEL_GENERIC; + ist->hwaccel_device_type = type; + } + + if (!ist->hwaccel_id) { + av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n", + hwaccel); + av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: "); + type = AV_HWDEVICE_TYPE_NONE; + while ((type = av_hwdevice_iterate_types(type)) != + AV_HWDEVICE_TYPE_NONE) + av_log(NULL, AV_LOG_FATAL, "%s ", + av_hwdevice_get_type_name(type)); + av_log(NULL, AV_LOG_FATAL, "\n"); + exit_program(1); + } + } + } + + MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st); + if (hwaccel_device) { + ist->hwaccel_device = av_strdup(hwaccel_device); + if (!ist->hwaccel_device) + report_and_exit(AVERROR(ENOMEM)); + } + + ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE; + } + + ist->dec = choose_decoder(o, ic, st, ist->hwaccel_id, ist->hwaccel_device_type); + ist->decoder_opts = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ic, st, ist->dec); + + ist->reinit_filters = -1; + MATCH_PER_STREAM_OPT(reinit_filters, i, ist->reinit_filters, ic, st); + + MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st); + ist->user_set_discard = AVDISCARD_NONE; + + if ((o->video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) || + (o->audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || + (o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) || + (o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA)) + ist->user_set_discard = AVDISCARD_ALL; + + if (discard_str && av_opt_eval_int(&cc, discard_opt, discard_str, &ist->user_set_discard) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing discard %s.\n", + discard_str); + exit_program(1); + } + + ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE; + ist->prev_pkt_pts = AV_NOPTS_VALUE; + + ist->dec_ctx = avcodec_alloc_context3(ist->dec); + if (!ist->dec_ctx) + report_and_exit(AVERROR(ENOMEM)); + + ret = avcodec_parameters_to_context(ist->dec_ctx, par); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); + exit_program(1); + } + + ist->decoded_frame = av_frame_alloc(); + if (!ist->decoded_frame) + report_and_exit(AVERROR(ENOMEM)); + + ist->pkt = av_packet_alloc(); + if (!ist->pkt) + report_and_exit(AVERROR(ENOMEM)); + + if (o->bitexact) + ist->dec_ctx->flags |= AV_CODEC_FLAG_BITEXACT; + + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: + // avformat_find_stream_info() doesn't set this for us anymore. + ist->dec_ctx->framerate = st->avg_frame_rate; + + MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st); + if (framerate && av_parse_video_rate(&ist->framerate, + framerate) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing framerate %s.\n", + framerate); + exit_program(1); + } + + ist->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st); + + ist->framerate_guessed = av_guess_frame_rate(ic, st, NULL); + + break; + case AVMEDIA_TYPE_AUDIO: + ist->guess_layout_max = INT_MAX; + MATCH_PER_STREAM_OPT(guess_layout_max, i, ist->guess_layout_max, ic, st); + guess_input_channel_layout(ist); + break; + case AVMEDIA_TYPE_DATA: + case AVMEDIA_TYPE_SUBTITLE: { + char *canvas_size = NULL; + MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st); + MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st); + if (canvas_size && + av_parse_video_size(&ist->dec_ctx->width, &ist->dec_ctx->height, canvas_size) < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid canvas size: %s.\n", canvas_size); + exit_program(1); + } + break; + } + case AVMEDIA_TYPE_ATTACHMENT: + case AVMEDIA_TYPE_UNKNOWN: + break; + default: + abort(); + } + + ist->par = avcodec_parameters_alloc(); + if (!ist->par) + report_and_exit(AVERROR(ENOMEM)); + + ret = avcodec_parameters_from_context(ist->par, ist->dec_ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); + exit_program(1); + } + } +} + +static void dump_attachment(AVStream *st, const char *filename) +{ + int ret; + AVIOContext *out = NULL; + const AVDictionaryEntry *e; + + if (!st->codecpar->extradata_size) { + av_log(NULL, AV_LOG_WARNING, "No extradata to dump in stream #%d:%d.\n", + nb_input_files - 1, st->index); + return; + } + if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) + filename = e->value; + if (!*filename) { + av_log(NULL, AV_LOG_FATAL, "No filename specified and no 'filename' tag" + "in stream #%d:%d.\n", nb_input_files - 1, st->index); + exit_program(1); + } + + assert_file_overwrite(filename); + + if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &int_cb, NULL)) < 0) { + av_log(NULL, AV_LOG_FATAL, "Could not open file %s for writing.\n", + filename); + exit_program(1); + } + + avio_write(out, st->codecpar->extradata, st->codecpar->extradata_size); + avio_flush(out); + avio_close(out); +} + +int ifile_open(const OptionsContext *o, const char *filename) +{ + Demuxer *d; + InputFile *f; + AVFormatContext *ic; + const AVInputFormat *file_iformat = NULL; + int err, i, ret; + int64_t timestamp; + AVDictionary *unused_opts = NULL; + const AVDictionaryEntry *e = NULL; + char * video_codec_name = NULL; + char * audio_codec_name = NULL; + char *subtitle_codec_name = NULL; + char * data_codec_name = NULL; + int scan_all_pmts_set = 0; + + int64_t start_time = o->start_time; + int64_t start_time_eof = o->start_time_eof; + int64_t stop_time = o->stop_time; + int64_t recording_time = o->recording_time; + + if (stop_time != INT64_MAX && recording_time != INT64_MAX) { + stop_time = INT64_MAX; + av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); + } + + if (stop_time != INT64_MAX && recording_time == INT64_MAX) { + int64_t start = start_time == AV_NOPTS_VALUE ? 0 : start_time; + if (stop_time <= start) { + av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); + exit_program(1); + } else { + recording_time = stop_time - start; + } + } + + if (o->format) { + if (!(file_iformat = av_find_input_format(o->format))) { + av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format); + exit_program(1); + } + } + + if (!strcmp(filename, "-")) + filename = "fd:"; + + stdin_interaction &= strncmp(filename, "pipe:", 5) && + strcmp(filename, "fd:") && + strcmp(filename, "/dev/stdin"); + + /* get default parameters from command line */ + ic = avformat_alloc_context(); + if (!ic) + report_and_exit(AVERROR(ENOMEM)); + if (o->nb_audio_sample_rate) { + av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0); + } + if (o->nb_audio_channels) { + const AVClass *priv_class; + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "ch_layout", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + char buf[32]; + snprintf(buf, sizeof(buf), "%dC", o->audio_channels[o->nb_audio_channels - 1].u.i); + av_dict_set(&o->g->format_opts, "ch_layout", buf, 0); + } + } + if (o->nb_audio_ch_layouts) { + const AVClass *priv_class; + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "ch_layout", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + av_dict_set(&o->g->format_opts, "ch_layout", o->audio_ch_layouts[o->nb_audio_ch_layouts - 1].u.str, 0); + } + } + if (o->nb_frame_rates) { + const AVClass *priv_class; + /* set the format-level framerate option; + * this is important for video grabbers, e.g. x11 */ + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "framerate", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + av_dict_set(&o->g->format_opts, "framerate", + o->frame_rates[o->nb_frame_rates - 1].u.str, 0); + } + } + if (o->nb_frame_sizes) { + av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0); + } + if (o->nb_frame_pix_fmts) + av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0); + + MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v"); + MATCH_PER_TYPE_OPT(codec_names, str, audio_codec_name, ic, "a"); + MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s"); + MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d"); + + if (video_codec_name) + ic->video_codec = find_codec_or_die(NULL, video_codec_name , AVMEDIA_TYPE_VIDEO , 0); + if (audio_codec_name) + ic->audio_codec = find_codec_or_die(NULL, audio_codec_name , AVMEDIA_TYPE_AUDIO , 0); + if (subtitle_codec_name) + ic->subtitle_codec = find_codec_or_die(NULL, subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0); + if (data_codec_name) + ic->data_codec = find_codec_or_die(NULL, data_codec_name , AVMEDIA_TYPE_DATA , 0); + + ic->video_codec_id = video_codec_name ? ic->video_codec->id : AV_CODEC_ID_NONE; + ic->audio_codec_id = audio_codec_name ? ic->audio_codec->id : AV_CODEC_ID_NONE; + ic->subtitle_codec_id = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE; + ic->data_codec_id = data_codec_name ? ic->data_codec->id : AV_CODEC_ID_NONE; + + ic->flags |= AVFMT_FLAG_NONBLOCK; + if (o->bitexact) + ic->flags |= AVFMT_FLAG_BITEXACT; + ic->interrupt_callback = int_cb; + + if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { + av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); + scan_all_pmts_set = 1; + } + /* open the input file with generic avformat function */ + err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); + if (err < 0) { + print_error(filename, err); + if (err == AVERROR_PROTOCOL_NOT_FOUND) + av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename); + exit_program(1); + } + if (scan_all_pmts_set) + av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); + remove_avoptions(&o->g->format_opts, o->g->codec_opts); + assert_avoptions(o->g->format_opts); + + /* apply forced codec ids */ + for (i = 0; i < ic->nb_streams; i++) + choose_decoder(o, ic, ic->streams[i], HWACCEL_NONE, AV_HWDEVICE_TYPE_NONE); + + if (o->find_stream_info) { + AVDictionary **opts = setup_find_stream_info_opts(ic, o->g->codec_opts); + int orig_nb_streams = ic->nb_streams; + + /* If not enough info to get the stream parameters, we decode the + first frames to get it. (used in mpeg case for example) */ + ret = avformat_find_stream_info(ic, opts); + + for (i = 0; i < orig_nb_streams; i++) + av_dict_free(&opts[i]); + av_freep(&opts); + + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename); + if (ic->nb_streams == 0) { + avformat_close_input(&ic); + exit_program(1); + } + } + } + + if (start_time != AV_NOPTS_VALUE && start_time_eof != AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_WARNING, "Cannot use -ss and -sseof both, using -ss for %s\n", filename); + start_time_eof = AV_NOPTS_VALUE; + } + + if (start_time_eof != AV_NOPTS_VALUE) { + if (start_time_eof >= 0) { + av_log(NULL, AV_LOG_ERROR, "-sseof value must be negative; aborting\n"); + exit_program(1); + } + if (ic->duration > 0) { + start_time = start_time_eof + ic->duration; + if (start_time < 0) { + av_log(NULL, AV_LOG_WARNING, "-sseof value seeks to before start of file %s; ignored\n", filename); + start_time = AV_NOPTS_VALUE; + } + } else + av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename); + } + timestamp = (start_time == AV_NOPTS_VALUE) ? 0 : start_time; + /* add the stream start time */ + if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE) + timestamp += ic->start_time; + + /* if seeking requested, we execute it */ + if (start_time != AV_NOPTS_VALUE) { + int64_t seek_timestamp = timestamp; + + if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) { + int dts_heuristic = 0; + for (i=0; inb_streams; i++) { + const AVCodecParameters *par = ic->streams[i]->codecpar; + if (par->video_delay) { + dts_heuristic = 1; + break; + } + } + if (dts_heuristic) { + seek_timestamp -= 3*AV_TIME_BASE / 23; + } + } + ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", + filename, (double)timestamp / AV_TIME_BASE); + } + } + + d = allocate_array_elem(&input_files, sizeof(*d), &nb_input_files); + f = &d->f; + + f->ctx = ic; + f->index = nb_input_files - 1; + f->start_time = start_time; + f->recording_time = recording_time; + f->input_sync_ref = o->input_sync_ref; + f->input_ts_offset = o->input_ts_offset; + f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); + f->rate_emu = o->rate_emu; + f->accurate_seek = o->accurate_seek; + d->loop = o->loop; + d->duration = 0; + d->time_base = (AVRational){ 1, 1 }; + + f->readrate = o->readrate ? o->readrate : 0.0; + if (f->readrate < 0.0f) { + av_log(NULL, AV_LOG_ERROR, "Option -readrate for Input #%d is %0.3f; it must be non-negative.\n", f->index, f->readrate); + exit_program(1); + } + if (f->readrate && f->rate_emu) { + av_log(NULL, AV_LOG_WARNING, "Both -readrate and -re set for Input #%d. Using -readrate %0.3f.\n", f->index, f->readrate); + f->rate_emu = 0; + } + + d->thread_queue_size = o->thread_queue_size; + + /* update the current parameters so that they match the one of the input stream */ + add_input_streams(o, d); + + /* dump the file content */ + av_dump_format(ic, f->index, filename, 0); + + /* check if all codec options have been used */ + unused_opts = strip_specifiers(o->g->codec_opts); + for (i = 0; i < f->nb_streams; i++) { + e = NULL; + while ((e = av_dict_iterate(f->streams[i]->decoder_opts, e))) + av_dict_set(&unused_opts, e->key, NULL, 0); + } + + e = NULL; + while ((e = av_dict_iterate(unused_opts, e))) { + const AVClass *class = avcodec_get_class(); + const AVOption *option = av_opt_find(&class, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + const AVClass *fclass = avformat_get_class(); + const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + if (!option || foption) + continue; + + + if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) { + av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " + "input file #%d (%s) is not a decoding option.\n", e->key, + option->help ? option->help : "", f->index, + filename); + exit_program(1); + } + + av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " + "input file #%d (%s) has not been used for any stream. The most " + "likely reason is either wrong type (e.g. a video option with " + "no video streams) or that it is a private option of some decoder " + "which was not actually used for any stream.\n", e->key, + option->help ? option->help : "", f->index, filename); + } + av_dict_free(&unused_opts); + + for (i = 0; i < o->nb_dump_attachment; i++) { + int j; + + for (j = 0; j < ic->nb_streams; j++) { + AVStream *st = ic->streams[j]; + + if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1) + dump_attachment(st, o->dump_attachment[i].u.str); + } + } + + return 0; +} diff --git a/apple/src/fftools_ffmpeg_filter.c b/apple/src/fftools_ffmpeg_filter.c index 46edae5..73d7763 100644 --- a/apple/src/fftools_ffmpeg_filter.c +++ b/apple/src/fftools_ffmpeg_filter.c @@ -1,6 +1,7 @@ /* * ffmpeg filter configuration * Copyright (c) 2018 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,12 @@ * We manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 08.2018 @@ -69,8 +76,9 @@ static const enum AVPixelFormat *get_compliance_normal_pix_fmts(const AVCodec *c } } -enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, - const AVCodec *codec, enum AVPixelFormat target) +enum AVPixelFormat +choose_pixel_fmt(const AVCodec *codec, enum AVPixelFormat target, + int strict_std_compliance) { if (codec && codec->pix_fmts) { const enum AVPixelFormat *p = codec->pix_fmts; @@ -79,7 +87,7 @@ enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, int has_alpha = desc ? desc->nb_components % 2 == 0 : 0; enum AVPixelFormat best= AV_PIX_FMT_NONE; - if (enc_ctx->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { + if (strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { p = get_compliance_normal_pix_fmts(codec, p); } for (; *p != AV_PIX_FMT_NONE; p++) { @@ -106,6 +114,7 @@ enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) { OutputStream *ost = ofilter->ost; + AVCodecContext *enc = ost->enc_ctx; const AVDictionaryEntry *strict_dict = av_dict_get(ost->encoder_opts, "strict", NULL, 0); if (strict_dict) // used by choose_pixel_fmt() and below @@ -119,13 +128,14 @@ static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) return av_get_pix_fmt_name(ost->enc_ctx->pix_fmt); } if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { - return av_get_pix_fmt_name(choose_pixel_fmt(ost->st, ost->enc_ctx, ost->enc, ost->enc_ctx->pix_fmt)); - } else if (ost->enc && ost->enc->pix_fmts) { + return av_get_pix_fmt_name(choose_pixel_fmt(enc->codec, enc->pix_fmt, + ost->enc_ctx->strict_std_compliance)); + } else if (enc->codec->pix_fmts) { const enum AVPixelFormat *p; - p = ost->enc->pix_fmts; + p = enc->codec->pix_fmts; if (ost->enc_ctx->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { - p = get_compliance_normal_pix_fmts(ost->enc, p); + p = get_compliance_normal_pix_fmts(enc->codec, p); } for (; *p != AV_PIX_FMT_NONE; p++) { @@ -133,7 +143,7 @@ static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) av_bprintf(bprint, "%s%c", name, p[1] == AV_PIX_FMT_NONE ? '\0' : '|'); } if (!av_bprint_is_complete(bprint)) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); return bprint->str; } else return NULL; @@ -197,7 +207,7 @@ int init_simple_filtergraph(InputStream *ist, OutputStream *ost) InputFilter *ifilter; if (!fg) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); fg->index = nb_filtergraphs; ofilter = ALLOC_ARRAY_ELEM(fg->outputs, fg->nb_outputs); @@ -214,7 +224,7 @@ int init_simple_filtergraph(InputStream *ist, OutputStream *ost) ifilter->frame_queue = av_fifo_alloc2(8, sizeof(AVFrame*), AV_FIFO_FLAG_AUTO_GROW); if (!ifilter->frame_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); GROW_ARRAY(ist->filters, ist->nb_filters); ist->filters[ist->nb_filters - 1] = ifilter; @@ -238,7 +248,7 @@ static char *describe_filter_link(FilterGraph *fg, AVFilterInOut *inout, int in) res = av_asprintf("%s:%s", ctx->filter->name, avfilter_pad_get_name(pads, inout->pad_idx)); if (!res) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); return res; } @@ -285,7 +295,7 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) "matches no streams.\n", p, fg->graph_desc); exit_program(1); } - ist = input_streams[input_files[file_idx]->ist_index + st->index]; + ist = input_files[file_idx]->streams[st->index]; if (ist->user_set_discard == AVDISCARD_ALL) { av_log(NULL, AV_LOG_FATAL, "Stream specifier '%s' in filtergraph description %s " "matches a disabled input stream.\n", p, fg->graph_desc); @@ -293,14 +303,13 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) } } else { /* find the first unused stream of corresponding type */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; + for (ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { if (ist->user_set_discard == AVDISCARD_ALL) continue; if (ist->dec_ctx->codec_type == type && ist->discard) break; } - if (i == nb_input_streams) { + if (!ist) { av_log(NULL, AV_LOG_FATAL, "Cannot find a matching stream for " "unlabeled input pad %d on filter %s\n", in->pad_idx, in->filter_ctx->name); @@ -323,12 +332,162 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) ifilter->frame_queue = av_fifo_alloc2(8, sizeof(AVFrame*), AV_FIFO_FLAG_AUTO_GROW); if (!ifilter->frame_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); GROW_ARRAY(ist->filters, ist->nb_filters); ist->filters[ist->nb_filters - 1] = ifilter; } +static int read_binary(const char *path, uint8_t **data, int *len) +{ + AVIOContext *io = NULL; + int64_t fsize; + int ret; + + *data = NULL; + *len = 0; + + ret = avio_open2(&io, path, AVIO_FLAG_READ, &int_cb, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot open file '%s': %s\n", + path, av_err2str(ret)); + return ret; + } + + fsize = avio_size(io); + if (fsize < 0 || fsize > INT_MAX) { + av_log(NULL, AV_LOG_ERROR, "Cannot obtain size of file %s\n", path); + ret = AVERROR(EIO); + goto fail; + } + + *data = av_malloc(fsize); + if (!*data) { + ret = AVERROR(ENOMEM); + goto fail; + } + + ret = avio_read(io, *data, fsize); + if (ret != fsize) { + av_log(NULL, AV_LOG_ERROR, "Error reading file %s\n", path); + ret = ret < 0 ? ret : AVERROR(EIO); + goto fail; + } + + *len = fsize; + + return 0; +fail: + avio_close(io); + av_freep(data); + *len = 0; + return ret; +} + +static int filter_opt_apply(AVFilterContext *f, const char *key, const char *val) +{ + const AVOption *o = NULL; + int ret; + + ret = av_opt_set(f, key, val, AV_OPT_SEARCH_CHILDREN); + if (ret >= 0) + return 0; + + if (ret == AVERROR_OPTION_NOT_FOUND && key[0] == '/') + o = av_opt_find(f, key + 1, NULL, 0, AV_OPT_SEARCH_CHILDREN); + if (!o) + goto err_apply; + + // key is a valid option name prefixed with '/' + // interpret value as a path from which to load the actual option value + key++; + + if (o->type == AV_OPT_TYPE_BINARY) { + uint8_t *data; + int len; + + ret = read_binary(val, &data, &len); + if (ret < 0) + goto err_load; + + ret = av_opt_set_bin(f, key, data, len, AV_OPT_SEARCH_CHILDREN); + av_freep(&data); + } else { + char *data = file_read(val); + if (!data) { + ret = AVERROR(EIO); + goto err_load; + } + + ret = av_opt_set(f, key, data, AV_OPT_SEARCH_CHILDREN); + av_freep(&data); + } + if (ret < 0) + goto err_apply; + + return 0; + +err_apply: + av_log(NULL, AV_LOG_ERROR, + "Error applying option '%s' to filter '%s': %s\n", + key, f->filter->name, av_err2str(ret)); + return ret; +err_load: + av_log(NULL, AV_LOG_ERROR, + "Error loading value for option '%s' from file '%s'\n", + key, val); + return ret; +} + +static int graph_opts_apply(AVFilterGraphSegment *seg) +{ + for (size_t i = 0; i < seg->nb_chains; i++) { + AVFilterChain *ch = seg->chains[i]; + + for (size_t j = 0; j < ch->nb_filters; j++) { + AVFilterParams *p = ch->filters[j]; + const AVDictionaryEntry *e = NULL; + + av_assert0(p->filter); + + while ((e = av_dict_iterate(p->opts, e))) { + int ret = filter_opt_apply(p->filter, e->key, e->value); + if (ret < 0) + return ret; + } + + av_dict_free(&p->opts); + } + } + + return 0; +} + +static int graph_parse(AVFilterGraph *graph, const char *desc, + AVFilterInOut **inputs, AVFilterInOut **outputs) +{ + AVFilterGraphSegment *seg; + int ret; + + ret = avfilter_graph_segment_parse(graph, desc, 0, &seg); + if (ret < 0) + return ret; + + ret = avfilter_graph_segment_create_filters(seg, 0); + if (ret < 0) + goto fail; + + ret = graph_opts_apply(seg); + if (ret < 0) + goto fail; + + ret = avfilter_graph_segment_apply(seg, 0, inputs, outputs); + +fail: + avfilter_graph_segment_free(&seg); + return ret; +} + int init_complex_filtergraph(FilterGraph *fg) { AVFilterInOut *inputs, *outputs, *cur; @@ -342,7 +501,7 @@ int init_complex_filtergraph(FilterGraph *fg) return AVERROR(ENOMEM); graph->nb_threads = 1; - ret = avfilter_graph_parse2(graph, fg->graph_desc, &inputs, &outputs); + ret = graph_parse(graph, fg->graph_desc, &inputs, &outputs); if (ret < 0) goto fail; @@ -467,8 +626,7 @@ static int configure_output_video_filter(FilterGraph *fg, OutputFilter *ofilter, snprintf(args, sizeof(args), "%d:%d", ofilter->width, ofilter->height); - while ((e = av_dict_get(ost->sws_dict, "", e, - AV_DICT_IGNORE_SUFFIX))) { + while ((e = av_dict_iterate(ost->sws_dict, e))) { av_strlcatf(args, sizeof(args), ":%s=%s", e->key, e->value); } @@ -575,6 +733,7 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, pad_idx = 0; \ } while (0) av_bprint_init(&args, 0, AV_BPRINT_SIZE_UNLIMITED); +#if FFMPEG_OPT_MAP_CHANNEL if (ost->audio_channels_mapped) { AVChannelLayout mapped_layout = { 0 }; int i; @@ -587,6 +746,7 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, AUTO_INSERT_FILTER("-map_channel", "pan", args.str); av_bprint_clear(&args); } +#endif if (codec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) av_channel_layout_default(&codec->ch_layout, codec->ch_layout.nb_channels); @@ -620,11 +780,11 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, if (ost->apad && of->shortest) { int i; - for (i=0; ictx->nb_streams; i++) - if (of->ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + for (i = 0; i < of->nb_streams; i++) + if (of->streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) break; - if (ictx->nb_streams) { + if (i < of->nb_streams) { AUTO_INSERT_FILTER("-apad", "apad", ost->apad); } } @@ -751,7 +911,7 @@ static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter, } if (!fr.num) - fr = av_guess_frame_rate(input_files[ist->file_index]->ctx, ist->st, NULL); + fr = ist->framerate_guessed; if (ist->dec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { ret = sub2video_prepare(ist, ifilter); @@ -904,40 +1064,6 @@ static int configure_input_audio_filter(FilterGraph *fg, InputFilter *ifilter, last_filter = filt_ctx; \ } while (0) - if (audio_sync_method > 0) { - char args[256] = {0}; - - av_strlcatf(args, sizeof(args), "async=%d", audio_sync_method); - if (audio_drift_threshold != 0.1) - av_strlcatf(args, sizeof(args), ":min_hard_comp=%f", audio_drift_threshold); - if (!fg->reconfiguration) - av_strlcatf(args, sizeof(args), ":first_pts=0"); - AUTO_INSERT_FILTER_INPUT("-async", "aresample", args); - } - -// if (ost->audio_channels_mapped) { -// int i; -// AVBPrint pan_buf; -// av_bprint_init(&pan_buf, 256, 8192); -// av_bprintf(&pan_buf, "0x%"PRIx64, -// av_get_default_channel_layout(ost->audio_channels_mapped)); -// for (i = 0; i < ost->audio_channels_mapped; i++) -// if (ost->audio_channels_map[i] != -1) -// av_bprintf(&pan_buf, ":c%d=c%d", i, ost->audio_channels_map[i]); -// AUTO_INSERT_FILTER_INPUT("-map_channel", "pan", pan_buf.str); -// av_bprint_finalize(&pan_buf, NULL); -// } - - if (audio_volume != 256) { - char args[256]; - - av_log(NULL, AV_LOG_WARNING, "-vol has been deprecated. Use the volume " - "audio filter instead.\n"); - - snprintf(args, sizeof(args), "%f", audio_volume / 256.); - AUTO_INSERT_FILTER_INPUT("-vol", "volume", args); - } - snprintf(name, sizeof(name), "trim for input stream %d:%d", ist->file_index, ist->st->index); if (copy_ts) { @@ -1020,44 +1146,39 @@ int configure_filtergraph(FilterGraph *fg) if (simple) { OutputStream *ost = fg->outputs[0]->ost; - char args[512]; - const AVDictionaryEntry *e = NULL; if (filter_nbthreads) { ret = av_opt_set(fg->graph, "threads", filter_nbthreads, 0); if (ret < 0) goto fail; } else { + const AVDictionaryEntry *e = NULL; e = av_dict_get(ost->encoder_opts, "threads", NULL, 0); if (e) av_opt_set(fg->graph, "threads", e->value, 0); } - args[0] = 0; - e = NULL; - while ((e = av_dict_get(ost->sws_dict, "", e, - AV_DICT_IGNORE_SUFFIX))) { - av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); - } - if (strlen(args)) { - args[strlen(args)-1] = 0; - fg->graph->scale_sws_opts = av_strdup(args); + if (av_dict_count(ost->sws_dict)) { + ret = av_dict_get_string(ost->sws_dict, + &fg->graph->scale_sws_opts, + '=', ':'); + if (ret < 0) + goto fail; } - args[0] = 0; - e = NULL; - while ((e = av_dict_get(ost->swr_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) { - av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); + if (av_dict_count(ost->swr_opts)) { + char *args; + ret = av_dict_get_string(ost->swr_opts, &args, '=', ':'); + if (ret < 0) + goto fail; + av_opt_set(fg->graph, "aresample_swr_opts", args, 0); + av_free(args); } - if (strlen(args)) - args[strlen(args)-1] = 0; - av_opt_set(fg->graph, "aresample_swr_opts", args, 0); } else { fg->graph->nb_threads = filter_complex_nbthreads; } - if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0) + if ((ret = graph_parse(fg->graph, graph_desc, &inputs, &outputs)) < 0) goto fail; ret = hw_device_setup_for_filter(fg); @@ -1131,16 +1252,8 @@ int configure_filtergraph(FilterGraph *fg) for (i = 0; i < fg->nb_outputs; i++) { OutputStream *ost = fg->outputs[i]->ost; - if (!ost->enc) { - /* identical to the same check in ffmpeg.c, needed because - complex filter graphs are initialized earlier */ - av_log(NULL, AV_LOG_ERROR, "Encoder (codec %s) not found for output stream #%d:%d\n", - avcodec_get_name(ost->st->codecpar->codec_id), ost->file_index, ost->index); - ret = AVERROR(EINVAL); - goto fail; - } - if (ost->enc->type == AVMEDIA_TYPE_AUDIO && - !(ost->enc->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + if (ost->enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO && + !(ost->enc_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) av_buffersink_set_frame_size(ost->filter->filter, ost->enc_ctx->frame_size); } diff --git a/apple/src/fftools_ffmpeg_hw.c b/apple/src/fftools_ffmpeg_hw.c index 240b375..42f16ec 100644 --- a/apple/src/fftools_ffmpeg_hw.c +++ b/apple/src/fftools_ffmpeg_hw.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2019 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -23,6 +24,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 12.2019 @@ -357,7 +364,7 @@ int hw_device_setup_for_decode(InputStream *ist) if (ist->hwaccel_id == HWACCEL_AUTO) { ist->hwaccel_device_type = dev->type; } else if (ist->hwaccel_device_type != dev->type) { - av_log(ist->dec_ctx, AV_LOG_ERROR, "Invalid hwaccel device " + av_log(NULL, AV_LOG_ERROR, "Invalid hwaccel device " "specified for decoder: device %s of type %s is not " "usable with hwaccel %s.\n", dev->name, av_hwdevice_get_type_name(dev->type), @@ -408,7 +415,7 @@ int hw_device_setup_for_decode(InputStream *ist) type = config->device_type; dev = hw_device_get_by_type(type); if (dev) { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with existing device %s.\n", av_hwdevice_get_type_name(type), dev->name); } @@ -426,12 +433,12 @@ int hw_device_setup_for_decode(InputStream *ist) continue; } if (ist->hwaccel_device) { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with new device created " "from %s.\n", av_hwdevice_get_type_name(type), ist->hwaccel_device); } else { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with new default device.\n", av_hwdevice_get_type_name(type)); } @@ -439,7 +446,7 @@ int hw_device_setup_for_decode(InputStream *ist) if (dev) { ist->hwaccel_device_type = type; } else { - av_log(ist->dec_ctx, AV_LOG_INFO, "Auto hwaccel " + av_log(NULL, AV_LOG_INFO, "Auto hwaccel " "disabled: no device found.\n"); ist->hwaccel_id = HWACCEL_NONE; return 0; @@ -447,7 +454,7 @@ int hw_device_setup_for_decode(InputStream *ist) } if (!dev) { - av_log(ist->dec_ctx, AV_LOG_ERROR, "No device available " + av_log(NULL, AV_LOG_ERROR, "No device available " "for decoder: device type %s needed for codec %s.\n", av_hwdevice_get_type_name(type), ist->dec->name); return err; @@ -479,7 +486,7 @@ int hw_device_setup_for_encode(OutputStream *ost) } for (i = 0;; i++) { - config = avcodec_get_hw_config(ost->enc, i); + config = avcodec_get_hw_config(ost->enc_ctx->codec, i); if (!config) break; @@ -490,7 +497,7 @@ int hw_device_setup_for_encode(OutputStream *ost) av_log(ost->enc_ctx, AV_LOG_VERBOSE, "Using input " "frames context (format %s) with %s encoder.\n", av_get_pix_fmt_name(ost->enc_ctx->pix_fmt), - ost->enc->name); + ost->enc_ctx->codec->name); ost->enc_ctx->hw_frames_ctx = av_buffer_ref(frames_ref); if (!ost->enc_ctx->hw_frames_ctx) return AVERROR(ENOMEM); @@ -505,7 +512,7 @@ int hw_device_setup_for_encode(OutputStream *ost) if (dev) { av_log(ost->enc_ctx, AV_LOG_VERBOSE, "Using device %s " "(type %s) with %s encoder.\n", dev->name, - av_hwdevice_get_type_name(dev->type), ost->enc->name); + av_hwdevice_get_type_name(dev->type), ost->enc_ctx->codec->name); ost->enc_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref); if (!ost->enc_ctx->hw_device_ctx) return AVERROR(ENOMEM); diff --git a/apple/src/fftools_ffmpeg_mux.c b/apple/src/fftools_ffmpeg_mux.c index b379cef..0a3d1e7 100644 --- a/apple/src/fftools_ffmpeg_mux.c +++ b/apple/src/fftools_ffmpeg_mux.c @@ -1,6 +1,7 @@ /* * This file is part of FFmpeg. * Copyright (c) 2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,6 +23,15 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop the ffmpeg-kit library. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - want_sdp marked as thread-local + * - ms_from_ost migrated from ffmpeg_mux.c and marked as non-static + * * ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -31,100 +41,83 @@ * - printf replaced with av_log statements */ +#include #include #include #include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" +#include "fftools_objpool.h" +#include "fftools_sync_queue.h" +#include "fftools_thread_queue.h" #include "libavutil/fifo.h" #include "libavutil/intreadwrite.h" #include "libavutil/log.h" #include "libavutil/mem.h" #include "libavutil/timestamp.h" +#include "libavutil/thread.h" #include "libavcodec/packet.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" -static void close_all_output_streams(OutputStream *ost, OSTFinished this_stream, OSTFinished others) +__thread int want_sdp = 1; + +MuxStream *ms_from_ost(OutputStream *ost) { - int i; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost2 = output_streams[i]; - ost2->finished |= ost == ost2 ? this_stream : others; + return (MuxStream*)ost; +} + +static Muxer *mux_from_of(OutputFile *of) +{ + return (Muxer*)of; +} + +static int64_t filesize(AVIOContext *pb) +{ + int64_t ret = -1; + + if (pb) { + ret = avio_size(pb); + if (ret <= 0) // FIXME improve avio_size() so it works with non seekable output too + ret = avio_tell(pb); } + + return ret; } -void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, - int unqueue) +static int write_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) { - AVFormatContext *s = of->ctx; + MuxStream *ms = ms_from_ost(ost); + AVFormatContext *s = mux->fc; AVStream *st = ost->st; + int64_t fs; + uint64_t frame_num; int ret; - /* - * Audio encoders may split the packets -- #frames in != #packets out. - * But there is no reordering, so we can limit the number of output packets - * by simply dropping them here. - * Counting encoded video frames needs to be done separately because of - * reordering, see do_video_out(). - * Do not count the packet when unqueued because it has been counted when queued. - */ - if (!(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->encoding_needed) && !unqueue) { - if (ost->frame_number >= ost->max_frames) { - av_packet_unref(pkt); - return; - } - ost->frame_number++; - } - - if (!of->header_written) { - AVPacket *tmp_pkt; - /* the muxer is not initialized yet, buffer the packet */ - if (!av_fifo_can_write(ost->muxing_queue)) { - size_t cur_size = av_fifo_can_read(ost->muxing_queue); - unsigned int are_we_over_size = - (ost->muxing_queue_data_size + pkt->size) > ost->muxing_queue_data_threshold; - size_t limit = are_we_over_size ? ost->max_muxing_queue_size : SIZE_MAX; - size_t new_size = FFMIN(2 * cur_size, limit); - - if (new_size <= cur_size) { - av_log(NULL, AV_LOG_ERROR, - "Too many packets buffered for output stream %d:%d.\n", - ost->file_index, ost->st->index); - exit_program(1); - } - ret = av_fifo_grow2(ost->muxing_queue, new_size - cur_size); - if (ret < 0) - exit_program(1); - } - ret = av_packet_make_refcounted(pkt); - if (ret < 0) - exit_program(1); - tmp_pkt = av_packet_alloc(); - if (!tmp_pkt) - exit_program(1); - av_packet_move_ref(tmp_pkt, pkt); - ost->muxing_queue_data_size += tmp_pkt->size; - av_fifo_write(ost->muxing_queue, &tmp_pkt, 1); - return; + fs = filesize(s->pb); + atomic_store(&mux->last_filesize, fs); + if (fs >= mux->limit_filesize) { + ret = AVERROR_EOF; + goto fail; } - if ((st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->vsync_method == VSYNC_DROP) || - (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_sync_method < 0)) + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->vsync_method == VSYNC_DROP) pkt->pts = pkt->dts = AV_NOPTS_VALUE; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (ost->frame_rate.num && ost->is_cfr) { if (pkt->duration > 0) - av_log(NULL, AV_LOG_WARNING, "Overriding packet duration by frame rate, this should not happen\n"); + av_log(ost, AV_LOG_WARNING, "Overriding packet duration by frame rate, this should not happen\n"); pkt->duration = av_rescale_q(1, av_inv_q(ost->frame_rate), - ost->mux_timebase); + pkt->time_base); } } - av_packet_rescale_ts(pkt, ost->mux_timebase, ost->st->time_base); + av_packet_rescale_ts(pkt, pkt->time_base, ost->st->time_base); + pkt->time_base = ost->st->time_base; if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) { if (pkt->dts != AV_NOPTS_VALUE && @@ -134,25 +127,26 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, pkt->dts, pkt->pts, ost->file_index, ost->st->index); pkt->pts = - pkt->dts = pkt->pts + pkt->dts + ost->last_mux_dts + 1 - - FFMIN3(pkt->pts, pkt->dts, ost->last_mux_dts + 1) - - FFMAX3(pkt->pts, pkt->dts, ost->last_mux_dts + 1); + pkt->dts = pkt->pts + pkt->dts + ms->last_mux_dts + 1 + - FFMIN3(pkt->pts, pkt->dts, ms->last_mux_dts + 1) + - FFMAX3(pkt->pts, pkt->dts, ms->last_mux_dts + 1); } if ((st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) && pkt->dts != AV_NOPTS_VALUE && - ost->last_mux_dts != AV_NOPTS_VALUE) { - int64_t max = ost->last_mux_dts + !(s->oformat->flags & AVFMT_TS_NONSTRICT); + ms->last_mux_dts != AV_NOPTS_VALUE) { + int64_t max = ms->last_mux_dts + !(s->oformat->flags & AVFMT_TS_NONSTRICT); if (pkt->dts < max) { int loglevel = max - pkt->dts > 2 || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? AV_LOG_WARNING : AV_LOG_DEBUG; if (exit_on_error) loglevel = AV_LOG_ERROR; av_log(s, loglevel, "Non-monotonous DTS in output stream " "%d:%d; previous: %"PRId64", current: %"PRId64"; ", - ost->file_index, ost->st->index, ost->last_mux_dts, pkt->dts); + ost->file_index, ost->st->index, ms->last_mux_dts, pkt->dts); if (exit_on_error) { - av_log(NULL, AV_LOG_FATAL, "aborting.\n"); - exit_program(1); + ret = AVERROR(EINVAL); + goto fail; } + av_log(s, loglevel, "changing to %"PRId64". This may result " "in incorrect timestamps in the output file.\n", max); @@ -162,17 +156,17 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, } } } - ost->last_mux_dts = pkt->dts; + ms->last_mux_dts = pkt->dts; - ost->data_size += pkt->size; - ost->packets_written++; + ost->data_size_mux += pkt->size; + frame_num = atomic_fetch_add(&ost->packets_written, 1); pkt->stream_index = ost->index; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "muxer <- type:%s " + av_log(ost, AV_LOG_INFO, "muxer <- type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s size:%d\n", - av_get_media_type_string(ost->enc_ctx->codec_type), + av_get_media_type_string(st->codecpar->codec_type), av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ost->st->time_base), av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ost->st->time_base), av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ost->st->time_base), @@ -180,12 +174,307 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, ); } + if (ms->stats.io) + enc_stats_write(ost, &ms->stats, NULL, pkt, frame_num); + ret = av_interleaved_write_frame(s, pkt); if (ret < 0) { print_error("av_interleaved_write_frame()", ret); - main_ffmpeg_return_code = 1; - close_all_output_streams(ost, MUXER_FINISHED | ENCODER_FINISHED, ENCODER_FINISHED); + goto fail; } + + return 0; +fail: + av_packet_unref(pkt); + return ret; +} + +static int sync_queue_process(Muxer *mux, OutputStream *ost, AVPacket *pkt, int *stream_eof) +{ + OutputFile *of = &mux->of; + + if (ost->sq_idx_mux >= 0) { + int ret = sq_send(mux->sq_mux, ost->sq_idx_mux, SQPKT(pkt)); + if (ret < 0) { + if (ret == AVERROR_EOF) + *stream_eof = 1; + + return ret; + } + + while (1) { + ret = sq_receive(mux->sq_mux, -1, SQPKT(mux->sq_pkt)); + if (ret < 0) + return (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) ? 0 : ret; + + ret = write_packet(mux, of->streams[ret], + mux->sq_pkt); + if (ret < 0) + return ret; + } + } else if (pkt) + return write_packet(mux, ost, pkt); + + return 0; +} + +static void thread_set_name(OutputFile *of) +{ + char name[16]; + snprintf(name, sizeof(name), "mux%d:%s", of->index, of->format->name); + ff_thread_setname(name); +} + +static void *muxer_thread(void *arg) +{ + Muxer *mux = arg; + OutputFile *of = &mux->of; + AVPacket *pkt = NULL; + int ret = 0; + + pkt = av_packet_alloc(); + if (!pkt) { + ret = AVERROR(ENOMEM); + goto finish; + } + + thread_set_name(of); + + while (1) { + OutputStream *ost; + int stream_idx, stream_eof = 0; + + ret = tq_receive(mux->tq, &stream_idx, pkt); + if (stream_idx < 0) { + av_log(mux, AV_LOG_VERBOSE, "All streams finished\n"); + ret = 0; + break; + } + + ost = of->streams[stream_idx]; + ret = sync_queue_process(mux, ost, ret < 0 ? NULL : pkt, &stream_eof); + av_packet_unref(pkt); + if (ret == AVERROR_EOF && stream_eof) + tq_receive_finish(mux->tq, stream_idx); + else if (ret < 0) { + av_log(mux, AV_LOG_ERROR, "Error muxing a packet\n"); + break; + } + } + +finish: + av_packet_free(&pkt); + + for (unsigned int i = 0; i < mux->fc->nb_streams; i++) + tq_receive_finish(mux->tq, i); + + av_log(mux, AV_LOG_VERBOSE, "Terminating muxer thread\n"); + + return (void*)(intptr_t)ret; +} + +static int thread_submit_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) +{ + int ret = 0; + + if (!pkt || ost->finished & MUXER_FINISHED) + goto finish; + + ret = tq_send(mux->tq, ost->index, pkt); + if (ret < 0) + goto finish; + + return 0; + +finish: + if (pkt) + av_packet_unref(pkt); + + ost->finished |= MUXER_FINISHED; + tq_send_finish(mux->tq, ost->index); + return ret == AVERROR_EOF ? 0 : ret; +} + +static int queue_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) +{ + MuxStream *ms = ms_from_ost(ost); + AVPacket *tmp_pkt = NULL; + int ret; + + if (!av_fifo_can_write(ms->muxing_queue)) { + size_t cur_size = av_fifo_can_read(ms->muxing_queue); + size_t pkt_size = pkt ? pkt->size : 0; + unsigned int are_we_over_size = + (ms->muxing_queue_data_size + pkt_size) > ms->muxing_queue_data_threshold; + size_t limit = are_we_over_size ? ms->max_muxing_queue_size : SIZE_MAX; + size_t new_size = FFMIN(2 * cur_size, limit); + + if (new_size <= cur_size) { + av_log(ost, AV_LOG_ERROR, + "Too many packets buffered for output stream %d:%d.\n", + ost->file_index, ost->st->index); + return AVERROR(ENOSPC); + } + ret = av_fifo_grow2(ms->muxing_queue, new_size - cur_size); + if (ret < 0) + return ret; + } + + if (pkt) { + ret = av_packet_make_refcounted(pkt); + if (ret < 0) + return ret; + + tmp_pkt = av_packet_alloc(); + if (!tmp_pkt) + return AVERROR(ENOMEM); + + av_packet_move_ref(tmp_pkt, pkt); + ms->muxing_queue_data_size += tmp_pkt->size; + } + av_fifo_write(ms->muxing_queue, &tmp_pkt, 1); + + return 0; +} + +static int submit_packet(Muxer *mux, AVPacket *pkt, OutputStream *ost) +{ + int ret; + + if (mux->tq) { + return thread_submit_packet(mux, ost, pkt); + } else { + /* the muxer is not initialized yet, buffer the packet */ + ret = queue_packet(mux, ost, pkt); + if (ret < 0) { + if (pkt) + av_packet_unref(pkt); + return ret; + } + } + + return 0; +} + +void of_output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof) +{ + Muxer *mux = mux_from_of(of); + MuxStream *ms = ms_from_ost(ost); + const char *err_msg; + int ret = 0; + + if (!eof && pkt->dts != AV_NOPTS_VALUE) + ost->last_mux_dts = av_rescale_q(pkt->dts, pkt->time_base, AV_TIME_BASE_Q); + + /* apply the output bitstream filters */ + if (ms->bsf_ctx) { + int bsf_eof = 0; + + ret = av_bsf_send_packet(ms->bsf_ctx, eof ? NULL : pkt); + if (ret < 0) { + err_msg = "submitting a packet for bitstream filtering"; + goto fail; + } + + while (!bsf_eof) { + ret = av_bsf_receive_packet(ms->bsf_ctx, pkt); + if (ret == AVERROR(EAGAIN)) + return; + else if (ret == AVERROR_EOF) + bsf_eof = 1; + else if (ret < 0) { + err_msg = "applying bitstream filters to a packet"; + goto fail; + } + + ret = submit_packet(mux, bsf_eof ? NULL : pkt, ost); + if (ret < 0) + goto mux_fail; + } + } else { + ret = submit_packet(mux, eof ? NULL : pkt, ost); + if (ret < 0) + goto mux_fail; + } + + return; + +mux_fail: + err_msg = "submitting a packet to the muxer"; + +fail: + av_log(ost, AV_LOG_ERROR, "Error %s\n", err_msg); + if (exit_on_error) + exit_program(1); + +} + +static int thread_stop(Muxer *mux) +{ + void *ret; + + if (!mux || !mux->tq) + return 0; + + for (unsigned int i = 0; i < mux->fc->nb_streams; i++) + tq_send_finish(mux->tq, i); + + pthread_join(mux->thread, &ret); + + tq_free(&mux->tq); + + return (int)(intptr_t)ret; +} + +static void pkt_move(void *dst, void *src) +{ + av_packet_move_ref(dst, src); +} + +static int thread_start(Muxer *mux) +{ + AVFormatContext *fc = mux->fc; + ObjPool *op; + int ret; + + op = objpool_alloc_packets(); + if (!op) + return AVERROR(ENOMEM); + + mux->tq = tq_alloc(fc->nb_streams, mux->thread_queue_size, op, pkt_move); + if (!mux->tq) { + objpool_free(&op); + return AVERROR(ENOMEM); + } + + ret = pthread_create(&mux->thread, NULL, muxer_thread, (void*)mux); + if (ret) { + tq_free(&mux->tq); + return AVERROR(ret); + } + + /* flush the muxing queues */ + for (int i = 0; i < fc->nb_streams; i++) { + OutputStream *ost = mux->of.streams[i]; + MuxStream *ms = ms_from_ost(ost); + AVPacket *pkt; + + /* try to improve muxing time_base (only possible if nothing has been written yet) */ + if (!av_fifo_can_read(ms->muxing_queue)) + ost->mux_timebase = ost->st->time_base; + + while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0) { + ret = thread_submit_packet(mux, ost, pkt); + if (pkt) { + ms->muxing_queue_data_size -= pkt->size; + av_packet_free(&pkt); + } + if (ret < 0) + return ret; + } + } + + return 0; } static int print_sdp(void) @@ -197,16 +486,16 @@ static int print_sdp(void) AVFormatContext **avc; for (i = 0; i < nb_output_files; i++) { - if (!output_files[i]->header_written) + if (!mux_from_of(output_files[i])->header_written) return 0; } avc = av_malloc_array(nb_output_files, sizeof(*avc)); if (!avc) - exit_program(1); + return AVERROR(ENOMEM); for (i = 0, j = 0; i < nb_output_files; i++) { - if (!strcmp(output_files[i]->ctx->oformat->name, "rtp")) { - avc[j] = output_files[i]->ctx; + if (!strcmp(output_files[i]->format->name, "rtp")) { + avc[j] = mux_from_of(output_files[i])->fc; j++; } } @@ -236,34 +525,36 @@ static int print_sdp(void) av_freep(&sdp_filename); } + // SDP successfully written, allow muxer threads to start + ret = 1; + fail: av_freep(&avc); return ret; } -/* open the muxer when all the streams are initialized */ -int of_check_init(OutputFile *of) +int mux_check_init(Muxer *mux) { + OutputFile *of = &mux->of; + AVFormatContext *fc = mux->fc; int ret, i; - for (i = 0; i < of->ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; + for (i = 0; i < fc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; if (!ost->initialized) return 0; } - ret = avformat_write_header(of->ctx, &of->opts); + ret = avformat_write_header(fc, &mux->opts); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, - "Could not write header for output file #%d " - "(incorrect codec parameters ?): %s\n", - of->index, av_err2str(ret)); + av_log(mux, AV_LOG_ERROR, "Could not write header (incorrect codec " + "parameters ?): %s\n", av_err2str(ret)); return ret; } //assert_avoptions(of->opts); - of->header_written = 1; + mux->header_written = 1; - av_dump_format(of->ctx, of->index, of->ctx->url, 1); + av_dump_format(fc, of->index, fc->url, 1); nb_output_dumped++; if (sdp_filename || want_sdp) { @@ -271,62 +562,220 @@ int of_check_init(OutputFile *of) if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error writing the SDP.\n"); return ret; + } else if (ret == 1) { + /* SDP is written only after all the muxers are ready, so now we + * start ALL the threads */ + for (i = 0; i < nb_output_files; i++) { + ret = thread_start(mux_from_of(output_files[i])); + if (ret < 0) + return ret; + } } + } else { + ret = thread_start(mux_from_of(of)); + if (ret < 0) + return ret; } - /* flush the muxing queues */ - for (i = 0; i < of->ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - AVPacket *pkt; + return 0; +} - /* try to improve muxing time_base (only possible if nothing has been written yet) */ - if (!av_fifo_can_read(ost->muxing_queue)) - ost->mux_timebase = ost->st->time_base; +static int bsf_init(MuxStream *ms) +{ + OutputStream *ost = &ms->ost; + AVBSFContext *ctx = ms->bsf_ctx; + int ret; - while (av_fifo_read(ost->muxing_queue, &pkt, 1) >= 0) { - ost->muxing_queue_data_size -= pkt->size; - of_write_packet(of, pkt, ost, 1); - av_packet_free(&pkt); - } + if (!ctx) + return 0; + + ret = avcodec_parameters_copy(ctx->par_in, ost->st->codecpar); + if (ret < 0) + return ret; + + ctx->time_base_in = ost->st->time_base; + + ret = av_bsf_init(ctx); + if (ret < 0) { + av_log(ms, AV_LOG_ERROR, "Error initializing bitstream filter: %s\n", + ctx->filter->name); + return ret; } + ret = avcodec_parameters_copy(ost->st->codecpar, ctx->par_out); + if (ret < 0) + return ret; + ost->st->time_base = ctx->time_base_out; + return 0; } +int of_stream_init(OutputFile *of, OutputStream *ost) +{ + Muxer *mux = mux_from_of(of); + MuxStream *ms = ms_from_ost(ost); + int ret; + + if (ost->sq_idx_mux >= 0) + sq_set_tb(mux->sq_mux, ost->sq_idx_mux, ost->mux_timebase); + + /* initialize bitstream filters for the output stream + * needs to be done here, because the codec id for streamcopy is not + * known until now */ + ret = bsf_init(ms); + if (ret < 0) + return ret; + + ost->initialized = 1; + + return mux_check_init(mux); +} + int of_write_trailer(OutputFile *of) { + Muxer *mux = mux_from_of(of); + AVFormatContext *fc = mux->fc; int ret; - if (!of->header_written) { - av_log(NULL, AV_LOG_ERROR, - "Nothing was written into output file %d (%s), because " - "at least one of its streams received no packets.\n", - of->index, of->ctx->url); + if (!mux->tq) { + av_log(mux, AV_LOG_ERROR, + "Nothing was written into output file, because " + "at least one of its streams received no packets.\n"); return AVERROR(EINVAL); } - ret = av_write_trailer(of->ctx); + ret = thread_stop(mux); + if (ret < 0) + main_ffmpeg_return_code = ret; + + ret = av_write_trailer(fc); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error writing trailer of %s: %s\n", of->ctx->url, av_err2str(ret)); + av_log(mux, AV_LOG_ERROR, "Error writing trailer: %s\n", av_err2str(ret)); return ret; } + mux->last_filesize = filesize(fc->pb); + + if (!(of->format->flags & AVFMT_NOFILE)) { + ret = avio_closep(&fc->pb); + if (ret < 0) { + av_log(mux, AV_LOG_ERROR, "Error closing file: %s\n", av_err2str(ret)); + return ret; + } + } + return 0; } +static void ost_free(OutputStream **post) +{ + OutputStream *ost = *post; + MuxStream *ms; + + if (!ost) + return; + ms = ms_from_ost(ost); + + if (ost->logfile) { + if (fclose(ost->logfile)) + av_log(ms, AV_LOG_ERROR, + "Error closing logfile, loss of information possible: %s\n", + av_err2str(AVERROR(errno))); + ost->logfile = NULL; + } + + if (ms->muxing_queue) { + AVPacket *pkt; + while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0) + av_packet_free(&pkt); + av_fifo_freep2(&ms->muxing_queue); + } + + av_bsf_free(&ms->bsf_ctx); + + av_frame_free(&ost->filtered_frame); + av_frame_free(&ost->sq_frame); + av_frame_free(&ost->last_frame); + av_packet_free(&ost->pkt); + av_dict_free(&ost->encoder_opts); + + av_freep(&ost->kf.pts); + av_expr_free(ost->kf.pexpr); + + av_freep(&ost->avfilter); + av_freep(&ost->logfile_prefix); + av_freep(&ost->apad); + +#if FFMPEG_OPT_MAP_CHANNEL + av_freep(&ost->audio_channels_map); + ost->audio_channels_mapped = 0; +#endif + + av_dict_free(&ost->sws_dict); + av_dict_free(&ost->swr_opts); + + if (ost->enc_ctx) + av_freep(&ost->enc_ctx->stats_in); + avcodec_free_context(&ost->enc_ctx); + + for (int i = 0; i < ost->enc_stats_pre.nb_components; i++) + av_freep(&ost->enc_stats_pre.components[i].str); + av_freep(&ost->enc_stats_pre.components); + + for (int i = 0; i < ost->enc_stats_post.nb_components; i++) + av_freep(&ost->enc_stats_post.components[i].str); + av_freep(&ost->enc_stats_post.components); + + for (int i = 0; i < ms->stats.nb_components; i++) + av_freep(&ms->stats.components[i].str); + av_freep(&ms->stats.components); + + av_freep(post); +} + +static void fc_close(AVFormatContext **pfc) +{ + AVFormatContext *fc = *pfc; + + if (!fc) + return; + + if (!(fc->oformat->flags & AVFMT_NOFILE)) + avio_closep(&fc->pb); + avformat_free_context(fc); + + *pfc = NULL; +} + void of_close(OutputFile **pof) { OutputFile *of = *pof; - AVFormatContext *s; + Muxer *mux; if (!of) return; + mux = mux_from_of(of); + + thread_stop(mux); - s = of->ctx; - if (s && s->oformat && !(s->oformat->flags & AVFMT_NOFILE)) - avio_closep(&s->pb); - avformat_free_context(s); - av_dict_free(&of->opts); + sq_free(&of->sq_encode); + sq_free(&mux->sq_mux); + + for (int i = 0; i < of->nb_streams; i++) + ost_free(&of->streams[i]); + av_freep(&of->streams); + + av_dict_free(&mux->opts); + + av_packet_free(&mux->sq_pkt); + + fc_close(&mux->fc); av_freep(pof); } + +int64_t of_filesize(OutputFile *of) +{ + Muxer *mux = mux_from_of(of); + return atomic_load(&mux->last_filesize); +} diff --git a/apple/src/fftools_ffmpeg_mux.h b/apple/src/fftools_ffmpeg_mux.h new file mode 100644 index 0000000..c1c7abd --- /dev/null +++ b/apple/src/fftools_ffmpeg_mux.h @@ -0,0 +1,165 @@ +/* + * Muxer internal APIs - should not be included outside of ffmpeg_mux* + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_mux.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - want_sdp made thread-local + * - EncStatsFile declaration migrated from ffmpeg_mux_init.c + * - WARN_MULTIPLE_OPT_USAGE, MATCH_PER_STREAM_OPT, MATCH_PER_TYPE_OPT, SPECIFIER_OPT_FMT declarations migrated from + * ffmpeg.h + * - ms_from_ost migrated to ffmpeg_mux.c + */ + +#ifndef FFTOOLS_FFMPEG_MUX_H +#define FFTOOLS_FFMPEG_MUX_H + +#include +#include + +#include "fftools_thread_queue.h" + +#include "libavformat/avformat.h" + +#include "libavcodec/packet.h" + +#include "libavutil/dict.h" +#include "libavutil/fifo.h" +#include "libavutil/thread.h" + +#define SPECIFIER_OPT_FMT_str "%s" +#define SPECIFIER_OPT_FMT_i "%i" +#define SPECIFIER_OPT_FMT_i64 "%"PRId64 +#define SPECIFIER_OPT_FMT_ui64 "%"PRIu64 +#define SPECIFIER_OPT_FMT_f "%f" +#define SPECIFIER_OPT_FMT_dbl "%lf" + +#define WARN_MULTIPLE_OPT_USAGE(name, type, so, st)\ +{\ + char namestr[128] = "";\ + const char *spec = so->specifier && so->specifier[0] ? so->specifier : "";\ + for (int _i = 0; opt_name_##name[_i]; _i++)\ + av_strlcatf(namestr, sizeof(namestr), "-%s%s", opt_name_##name[_i], opt_name_##name[_i+1] ? (opt_name_##name[_i+2] ? ", " : " or ") : "");\ + av_log(NULL, AV_LOG_WARNING, "Multiple %s options specified for stream %d, only the last option '-%s%s%s "SPECIFIER_OPT_FMT_##type"' will be used.\n",\ + namestr, st->index, opt_name_##name[0], spec[0] ? ":" : "", spec, so->u.type);\ +} + +#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\ +{\ + int _ret, _matches = 0;\ + SpecifierOpt *so;\ + for (int _i = 0; _i < o->nb_ ## name; _i++) {\ + char *spec = o->name[_i].specifier;\ + if ((_ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\ + outvar = o->name[_i].u.type;\ + so = &o->name[_i];\ + _matches++;\ + } else if (_ret < 0)\ + exit_program(1);\ + }\ + if (_matches > 1)\ + WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\ +} + +#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\ +{\ + int i;\ + for (i = 0; i < o->nb_ ## name; i++) {\ + char *spec = o->name[i].specifier;\ + if (!strcmp(spec, mediatype))\ + outvar = o->name[i].u.type;\ + }\ +} + +typedef struct MuxStream { + OutputStream ost; + + // name used for logging + char log_name[32]; + + /* the packets are buffered here until the muxer is ready to be initialized */ + AVFifo *muxing_queue; + + AVBSFContext *bsf_ctx; + + EncStats stats; + + int64_t max_frames; + + /* + * The size of the AVPackets' buffers in queue. + * Updated when a packet is either pushed or pulled from the queue. + */ + size_t muxing_queue_data_size; + + int max_muxing_queue_size; + + /* Threshold after which max_muxing_queue_size will be in effect */ + size_t muxing_queue_data_threshold; + + /* dts of the last packet sent to the muxer, in the stream timebase + * used for making up missing dts values */ + int64_t last_mux_dts; +} MuxStream; + +typedef struct Muxer { + OutputFile of; + + // name used for logging + char log_name[32]; + + AVFormatContext *fc; + + pthread_t thread; + ThreadQueue *tq; + + AVDictionary *opts; + + int thread_queue_size; + + /* filesize limit expressed in bytes */ + int64_t limit_filesize; + atomic_int_least64_t last_filesize; + int header_written; + + SyncQueue *sq_mux; + AVPacket *sq_pkt; +} Muxer; + +typedef struct EncStatsFile { + char *path; + AVIOContext *io; +} EncStatsFile; + +/* whether we want to print an SDP, set in of_open() */ +extern __thread int want_sdp; + +int mux_check_init(Muxer *mux); + +#endif /* FFTOOLS_FFMPEG_MUX_H */ diff --git a/apple/src/fftools_ffmpeg_mux_init.c b/apple/src/fftools_ffmpeg_mux_init.c new file mode 100644 index 0000000..522f8b8 --- /dev/null +++ b/apple/src/fftools_ffmpeg_mux_init.c @@ -0,0 +1,2414 @@ +/* + * Muxer/output file setup. + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_mux_init.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - EncStatsFile declaration migrated to ffmpeg_mux.h + * - extern ms_from_ost added + * - "class" member field renamed as clazz + */ + +#include + +#include "fftools_cmdutils.h" +#include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" +#include "fftools_fopen_utf8.h" + +#include "libavformat/avformat.h" +#include "libavformat/avio.h" + +#include "libavcodec/avcodec.h" + +#include "libavfilter/avfilter.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/avutil.h" +#include "libavutil/bprint.h" +#include "libavutil/dict.h" +#include "libavutil/getenv_utf8.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/log.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" + +#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass" + +static const char *const opt_name_apad[] = {"apad", NULL}; +static const char *const opt_name_autoscale[] = {"autoscale", NULL}; +static const char *const opt_name_bits_per_raw_sample[] = {"bits_per_raw_sample", NULL}; +static const char *const opt_name_bitstream_filters[] = {"bsf", "absf", "vbsf", NULL}; +static const char *const opt_name_copy_initial_nonkeyframes[] = {"copyinkf", NULL}; +static const char *const opt_name_copy_prior_start[] = {"copypriorss", NULL}; +static const char *const opt_name_disposition[] = {"disposition", NULL}; +static const char *const opt_name_enc_time_bases[] = {"enc_time_base", NULL}; +static const char *const opt_name_enc_stats_pre[] = {"enc_stats_pre", NULL}; +static const char *const opt_name_enc_stats_post[] = {"enc_stats_post", NULL}; +static const char *const opt_name_mux_stats[] = {"mux_stats", NULL}; +static const char *const opt_name_enc_stats_pre_fmt[] = {"enc_stats_pre_fmt", NULL}; +static const char *const opt_name_enc_stats_post_fmt[] = {"enc_stats_post_fmt", NULL}; +static const char *const opt_name_mux_stats_fmt[] = {"mux_stats_fmt", NULL}; +static const char *const opt_name_filters[] = {"filter", "af", "vf", NULL}; +static const char *const opt_name_filter_scripts[] = {"filter_script", NULL}; +static const char *const opt_name_fix_sub_duration_heartbeat[] = {"fix_sub_duration_heartbeat", NULL}; +static const char *const opt_name_fps_mode[] = {"fps_mode", NULL}; +static const char *const opt_name_force_fps[] = {"force_fps", NULL}; +static const char *const opt_name_forced_key_frames[] = {"forced_key_frames", NULL}; +static const char *const opt_name_frame_aspect_ratios[] = {"aspect", NULL}; +static const char *const opt_name_intra_matrices[] = {"intra_matrix", NULL}; +static const char *const opt_name_inter_matrices[] = {"inter_matrix", NULL}; +static const char *const opt_name_chroma_intra_matrices[] = {"chroma_intra_matrix", NULL}; +static const char *const opt_name_max_frame_rates[] = {"fpsmax", NULL}; +static const char *const opt_name_max_frames[] = {"frames", "aframes", "vframes", "dframes", NULL}; +static const char *const opt_name_max_muxing_queue_size[] = {"max_muxing_queue_size", NULL}; +static const char *const opt_name_muxing_queue_data_threshold[] = {"muxing_queue_data_threshold", NULL}; +static const char *const opt_name_pass[] = {"pass", NULL}; +static const char *const opt_name_passlogfiles[] = {"passlogfile", NULL}; +static const char *const opt_name_presets[] = {"pre", "apre", "vpre", "spre", NULL}; +static const char *const opt_name_qscale[] = {"q", "qscale", NULL}; +static const char *const opt_name_rc_overrides[] = {"rc_override", NULL}; +static const char *const opt_name_time_bases[] = {"time_base", NULL}; +static const char *const opt_name_audio_channels[] = {"ac", NULL}; +static const char *const opt_name_audio_ch_layouts[] = {"channel_layout", "ch_layout", NULL}; +static const char *const opt_name_audio_sample_rate[] = {"ar", NULL}; +static const char *const opt_name_frame_sizes[] = {"s", NULL}; +static const char *const opt_name_frame_pix_fmts[] = {"pix_fmt", NULL}; +static const char *const opt_name_sample_fmts[] = {"sample_fmt", NULL}; + +extern MuxStream *ms_from_ost(OutputStream *ost); + +static int check_opt_bitexact(void *ctx, const AVDictionary *opts, + const char *opt_name, int flag) +{ + const AVDictionaryEntry *e = av_dict_get(opts, opt_name, NULL, 0); + + if (e) { + const AVOption *o = av_opt_find(ctx, opt_name, NULL, 0, 0); + int val = 0; + if (!o) + return 0; + av_opt_eval_flags(ctx, o, e->value, &val); + return !!(val & flag); + } + return 0; +} + +static int choose_encoder(const OptionsContext *o, AVFormatContext *s, + OutputStream *ost, const AVCodec **enc) +{ + enum AVMediaType type = ost->st->codecpar->codec_type; + char *codec_name = NULL; + + *enc = NULL; + + if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) { + MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st); + if (!codec_name) { + ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->url, + NULL, ost->st->codecpar->codec_type); + *enc = avcodec_find_encoder(ost->st->codecpar->codec_id); + if (!*enc) { + av_log(ost, AV_LOG_FATAL, "Automatic encoder selection failed " + "Default encoder for format %s (codec %s) is " + "probably disabled. Please choose an encoder manually.\n", + s->oformat->name, avcodec_get_name(ost->st->codecpar->codec_id)); + return AVERROR_ENCODER_NOT_FOUND; + } + } else if (strcmp(codec_name, "copy")) { + *enc = find_codec_or_die(ost, codec_name, ost->st->codecpar->codec_type, 1); + ost->st->codecpar->codec_id = (*enc)->id; + } + } + + return 0; +} + +static char *get_line(AVIOContext *s, AVBPrint *bprint) +{ + char c; + + while ((c = avio_r8(s)) && c != '\n') + av_bprint_chars(bprint, c, 1); + + if (!av_bprint_is_complete(bprint)) + report_and_exit(AVERROR(ENOMEM)); + return bprint->str; +} + +static int get_preset_file_2(const char *preset_name, const char *codec_name, AVIOContext **s) +{ + int i, ret = -1; + char filename[1000]; + char *env_avconv_datadir = getenv_utf8("AVCONV_DATADIR"); + char *env_home = getenv_utf8("HOME"); + const char *base[3] = { env_avconv_datadir, + env_home, + AVCONV_DATADIR, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(base) && ret < 0; i++) { + if (!base[i]) + continue; + if (codec_name) { + snprintf(filename, sizeof(filename), "%s%s/%s-%s.avpreset", base[i], + i != 1 ? "" : "/.avconv", codec_name, preset_name); + ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); + } + if (ret < 0) { + snprintf(filename, sizeof(filename), "%s%s/%s.avpreset", base[i], + i != 1 ? "" : "/.avconv", preset_name); + ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); + } + } + freeenv_utf8(env_home); + freeenv_utf8(env_avconv_datadir); + return ret; +} + +__thread EncStatsFile *enc_stats_files; +__thread int nb_enc_stats_files; + +static int enc_stats_get_file(AVIOContext **io, const char *path) +{ + EncStatsFile *esf; + int ret; + + for (int i = 0; i < nb_enc_stats_files; i++) + if (!strcmp(path, enc_stats_files[i].path)) { + *io = enc_stats_files[i].io; + return 0; + } + + GROW_ARRAY(enc_stats_files, nb_enc_stats_files); + + esf = &enc_stats_files[nb_enc_stats_files - 1]; + + ret = avio_open2(&esf->io, path, AVIO_FLAG_WRITE, &int_cb, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening stats file '%s': %s\n", + path, av_err2str(ret)); + return ret; + } + + esf->path = av_strdup(path); + if (!esf->path) + return AVERROR(ENOMEM); + + *io = esf->io; + + return 0; +} + +void of_enc_stats_close(void) +{ + for (int i = 0; i < nb_enc_stats_files; i++) { + av_freep(&enc_stats_files[i].path); + avio_closep(&enc_stats_files[i].io); + } + av_freep(&enc_stats_files); + nb_enc_stats_files = 0; +} + +static int unescape(char **pdst, size_t *dst_len, + const char **pstr, char delim) +{ + const char *str = *pstr; + char *dst; + size_t len, idx; + + *pdst = NULL; + + len = strlen(str); + if (!len) + return 0; + + dst = av_malloc(len + 1); + if (!dst) + return AVERROR(ENOMEM); + + for (idx = 0; *str; idx++, str++) { + if (str[0] == '\\' && str[1]) + str++; + else if (*str == delim) + break; + + dst[idx] = *str; + } + if (!idx) { + av_freep(&dst); + return 0; + } + + dst[idx] = 0; + + *pdst = dst; + *dst_len = idx; + *pstr = str; + + return 0; +} + +static int enc_stats_init(OutputStream *ost, EncStats *es, int pre, + const char *path, const char *fmt_spec) +{ + static const struct { + enum EncStatsType type; + const char *str; + int pre_only:1; + int post_only:1; + int need_input_data:1; + } fmt_specs[] = { + { ENC_STATS_FILE_IDX, "fidx" }, + { ENC_STATS_STREAM_IDX, "sidx" }, + { ENC_STATS_FRAME_NUM, "n" }, + { ENC_STATS_FRAME_NUM_IN, "ni", 0, 0, 1 }, + { ENC_STATS_TIMEBASE, "tb" }, + { ENC_STATS_TIMEBASE_IN, "tbi", 0, 0, 1 }, + { ENC_STATS_PTS, "pts" }, + { ENC_STATS_PTS_TIME, "t" }, + { ENC_STATS_PTS_IN, "ptsi", 0, 0, 1 }, + { ENC_STATS_PTS_TIME_IN, "ti", 0, 0, 1 }, + { ENC_STATS_DTS, "dts", 0, 1 }, + { ENC_STATS_DTS_TIME, "dt", 0, 1 }, + { ENC_STATS_SAMPLE_NUM, "sn", 1 }, + { ENC_STATS_NB_SAMPLES, "samp", 1 }, + { ENC_STATS_PKT_SIZE, "size", 0, 1 }, + { ENC_STATS_BITRATE, "br", 0, 1 }, + { ENC_STATS_AVG_BITRATE, "abr", 0, 1 }, + }; + const char *next = fmt_spec; + + int ret; + + while (*next) { + EncStatsComponent *c; + char *val; + size_t val_len; + + // get the sequence up until next opening brace + ret = unescape(&val, &val_len, &next, '{'); + if (ret < 0) + return ret; + + if (val) { + GROW_ARRAY(es->components, es->nb_components); + + c = &es->components[es->nb_components - 1]; + c->type = ENC_STATS_LITERAL; + c->str = val; + c->str_len = val_len; + } + + if (!*next) + break; + next++; + + // get the part inside braces + ret = unescape(&val, &val_len, &next, '}'); + if (ret < 0) + return ret; + + if (!val) { + av_log(NULL, AV_LOG_ERROR, + "Empty formatting directive in: %s\n", fmt_spec); + return AVERROR(EINVAL); + } + + if (!*next) { + av_log(NULL, AV_LOG_ERROR, + "Missing closing brace in: %s\n", fmt_spec); + ret = AVERROR(EINVAL); + goto fail; + } + next++; + + GROW_ARRAY(es->components, es->nb_components); + c = &es->components[es->nb_components - 1]; + + for (size_t i = 0; i < FF_ARRAY_ELEMS(fmt_specs); i++) { + if (!strcmp(val, fmt_specs[i].str)) { + if ((pre && fmt_specs[i].post_only) || (!pre && fmt_specs[i].pre_only)) { + av_log(NULL, AV_LOG_ERROR, + "Format directive '%s' may only be used %s-encoding\n", + val, pre ? "post" : "pre"); + ret = AVERROR(EINVAL); + goto fail; + } + + c->type = fmt_specs[i].type; + + if (fmt_specs[i].need_input_data) { + if (ost->ist) + ost->ist->want_frame_data = 1; + else { + av_log(ost, AV_LOG_WARNING, + "Format directive '%s' is unavailable, because " + "this output stream has no associated input stream\n", + val); + } + } + + break; + } + } + + if (!c->type) { + av_log(NULL, AV_LOG_ERROR, "Invalid format directive: %s\n", val); + ret = AVERROR(EINVAL); + goto fail; + } + +fail: + av_freep(&val); + if (ret < 0) + return ret; + } + + ret = enc_stats_get_file(&es->io, path); + if (ret < 0) + return ret; + + return 0; +} + +static const char *output_stream_item_name(void *obj) +{ + const MuxStream *ms = obj; + + return ms->log_name; +} + +static const AVClass output_stream_class = { + .class_name = "OutputStream", + .version = LIBAVUTIL_VERSION_INT, + .item_name = output_stream_item_name, + .category = AV_CLASS_CATEGORY_MUXER, +}; + +static MuxStream *mux_stream_alloc(Muxer *mux, enum AVMediaType type) +{ + const char *type_str = av_get_media_type_string(type); + MuxStream *ms = allocate_array_elem(&mux->of.streams, sizeof(*ms), + &mux->of.nb_streams); + + ms->ost.file_index = mux->of.index; + ms->ost.index = mux->of.nb_streams - 1; + + ms->ost.clazz = &output_stream_class; + + snprintf(ms->log_name, sizeof(ms->log_name), "%cost#%d:%d", + type_str ? *type_str : '?', mux->of.index, ms->ost.index); + + return ms; +} + +static OutputStream *new_output_stream(Muxer *mux, const OptionsContext *o, + enum AVMediaType type, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + MuxStream *ms; + OutputStream *ost; + const AVCodec *enc; + AVStream *st = avformat_new_stream(oc, NULL); + int ret = 0; + const char *bsfs = NULL, *time_base = NULL; + char *next, *codec_tag = NULL; + double qscale = -1; + int i; + + if (!st) + report_and_exit(AVERROR(ENOMEM)); + + if (oc->nb_streams - 1 < o->nb_streamid_map) + st->id = o->streamid_map[oc->nb_streams - 1]; + + ms = mux_stream_alloc(mux, type); + ost = &ms->ost; + + ms->muxing_queue = av_fifo_alloc2(8, sizeof(AVPacket*), 0); + if (!ms->muxing_queue) + report_and_exit(AVERROR(ENOMEM)); + ms->last_mux_dts = AV_NOPTS_VALUE; + + ost->st = st; + ost->ist = ist; + ost->kf.ref_pts = AV_NOPTS_VALUE; + st->codecpar->codec_type = type; + + ret = choose_encoder(o, oc, ost, &enc); + if (ret < 0) { + av_log(ost, AV_LOG_FATAL, "Error selecting an encoder\n"); + exit_program(1); + } + + if (enc) { + ost->enc_ctx = avcodec_alloc_context3(enc); + if (!ost->enc_ctx) + report_and_exit(AVERROR(ENOMEM)); + + av_strlcat(ms->log_name, "/", sizeof(ms->log_name)); + av_strlcat(ms->log_name, enc->name, sizeof(ms->log_name)); + } else { + av_strlcat(ms->log_name, "/copy", sizeof(ms->log_name)); + } + + ost->filtered_frame = av_frame_alloc(); + if (!ost->filtered_frame) + report_and_exit(AVERROR(ENOMEM)); + + ost->pkt = av_packet_alloc(); + if (!ost->pkt) + report_and_exit(AVERROR(ENOMEM)); + + if (ost->enc_ctx) { + AVCodecContext *enc = ost->enc_ctx; + AVIOContext *s = NULL; + char *buf = NULL, *arg = NULL, *preset = NULL; + const char *enc_stats_pre = NULL, *enc_stats_post = NULL, *mux_stats = NULL; + + ost->encoder_opts = filter_codec_opts(o->g->codec_opts, enc->codec_id, + oc, st, enc->codec); + + MATCH_PER_STREAM_OPT(presets, str, preset, oc, st); + ost->autoscale = 1; + MATCH_PER_STREAM_OPT(autoscale, i, ost->autoscale, oc, st); + if (preset && (!(ret = get_preset_file_2(preset, enc->codec->name, &s)))) { + AVBPrint bprint; + av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); + do { + av_bprint_clear(&bprint); + buf = get_line(s, &bprint); + if (!buf[0] || buf[0] == '#') + continue; + if (!(arg = strchr(buf, '='))) { + av_log(ost, AV_LOG_FATAL, "Invalid line found in the preset file.\n"); + exit_program(1); + } + *arg++ = 0; + av_dict_set(&ost->encoder_opts, buf, arg, AV_DICT_DONT_OVERWRITE); + } while (!s->eof_reached); + av_bprint_finalize(&bprint, NULL); + avio_closep(&s); + } + if (ret) { + av_log(ost, AV_LOG_FATAL, + "Preset %s specified, but could not be opened.\n", preset); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(enc_stats_pre, str, enc_stats_pre, oc, st); + if (enc_stats_pre && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(enc_stats_pre_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ost->enc_stats_pre, 1, enc_stats_pre, format); + if (ret < 0) + exit_program(1); + } + + MATCH_PER_STREAM_OPT(enc_stats_post, str, enc_stats_post, oc, st); + if (enc_stats_post && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(enc_stats_post_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ost->enc_stats_post, 0, enc_stats_post, format); + if (ret < 0) + exit_program(1); + } + + MATCH_PER_STREAM_OPT(mux_stats, str, mux_stats, oc, st); + if (mux_stats && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(mux_stats_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ms->stats, 0, mux_stats, format); + if (ret < 0) + exit_program(1); + } + } else { + ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL); + } + + + if (o->bitexact) { + ost->bitexact = 1; + } else if (ost->enc_ctx) { + ost->bitexact = check_opt_bitexact(ost->enc_ctx, ost->encoder_opts, "flags", + AV_CODEC_FLAG_BITEXACT); + } + + MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st); + if (time_base) { + AVRational q; + if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || + q.num <= 0 || q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); + exit_program(1); + } + st->time_base = q; + } + + MATCH_PER_STREAM_OPT(enc_time_bases, str, time_base, oc, st); + if (time_base) { + AVRational q; + if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || + q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); + exit_program(1); + } + ost->enc_timebase = q; + } + + ms->max_frames = INT64_MAX; + MATCH_PER_STREAM_OPT(max_frames, i64, ms->max_frames, oc, st); + for (i = 0; inb_max_frames; i++) { + char *p = o->max_frames[i].specifier; + if (!*p && type != AVMEDIA_TYPE_VIDEO) { + av_log(ost, AV_LOG_WARNING, "Applying unspecific -frames to non video streams, maybe you meant -vframes ?\n"); + break; + } + } + + ost->copy_prior_start = -1; + MATCH_PER_STREAM_OPT(copy_prior_start, i, ost->copy_prior_start, oc ,st); + + MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st); + if (bsfs && *bsfs) { + ret = av_bsf_list_parse_str(bsfs, &ms->bsf_ctx); + if (ret < 0) { + av_log(ost, AV_LOG_ERROR, "Error parsing bitstream filter sequence '%s': %s\n", bsfs, av_err2str(ret)); + exit_program(1); + } + } + + MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st); + if (codec_tag) { + uint32_t tag = strtol(codec_tag, &next, 0); + if (*next) + tag = AV_RL32(codec_tag); + ost->st->codecpar->codec_tag = tag; + if (ost->enc_ctx) + ost->enc_ctx->codec_tag = tag; + } + + MATCH_PER_STREAM_OPT(qscale, dbl, qscale, oc, st); + if (ost->enc_ctx && qscale >= 0) { + ost->enc_ctx->flags |= AV_CODEC_FLAG_QSCALE; + ost->enc_ctx->global_quality = FF_QP2LAMBDA * qscale; + } + + ms->max_muxing_queue_size = 128; + MATCH_PER_STREAM_OPT(max_muxing_queue_size, i, ms->max_muxing_queue_size, oc, st); + + ms->muxing_queue_data_threshold = 50*1024*1024; + MATCH_PER_STREAM_OPT(muxing_queue_data_threshold, i, ms->muxing_queue_data_threshold, oc, st); + + MATCH_PER_STREAM_OPT(bits_per_raw_sample, i, ost->bits_per_raw_sample, + oc, st); + + MATCH_PER_STREAM_OPT(fix_sub_duration_heartbeat, i, ost->fix_sub_duration_heartbeat, + oc, st); + + if (oc->oformat->flags & AVFMT_GLOBALHEADER && ost->enc_ctx) + ost->enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + av_dict_copy(&ost->sws_dict, o->g->sws_dict, 0); + + av_dict_copy(&ost->swr_opts, o->g->swr_opts, 0); + if (ost->enc_ctx && av_get_exact_bits_per_sample(ost->enc_ctx->codec_id) == 24) + av_dict_set(&ost->swr_opts, "output_sample_bits", "24", 0); + + if (ost->ist) { + ost->ist->discard = 0; + ost->ist->st->discard = ost->ist->user_set_discard; + } + ost->last_mux_dts = AV_NOPTS_VALUE; + ost->last_filter_pts = AV_NOPTS_VALUE; + + MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, + ost->copy_initial_nonkeyframes, oc, st); + + return ost; +} + +static char *get_ost_filters(const OptionsContext *o, AVFormatContext *oc, + OutputStream *ost) +{ + AVStream *st = ost->st; + + if (ost->filters_script && ost->filters) { + av_log(ost, AV_LOG_ERROR, "Both -filter and -filter_script set\n"); + exit_program(1); + } + + if (ost->filters_script) + return file_read(ost->filters_script); + else if (ost->filters) + return av_strdup(ost->filters); + + return av_strdup(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? + "null" : "anull"); +} + +static void check_streamcopy_filters(const OptionsContext *o, AVFormatContext *oc, + OutputStream *ost, enum AVMediaType type) +{ + if (ost->filters_script || ost->filters) { + av_log(ost, AV_LOG_ERROR, + "%s '%s' was defined, but codec copy was selected.\n" + "Filtering and streamcopy cannot be used together.\n", + ost->filters ? "Filtergraph" : "Filtergraph script", + ost->filters ? ost->filters : ost->filters_script); + exit_program(1); + } +} + +static void parse_matrix_coeffs(void *logctx, uint16_t *dest, const char *str) +{ + int i; + const char *p = str; + for (i = 0;; i++) { + dest[i] = atoi(p); + if (i == 63) + break; + p = strchr(p, ','); + if (!p) { + av_log(logctx, AV_LOG_FATAL, + "Syntax error in matrix \"%s\" at coeff %d\n", str, i); + exit_program(1); + } + p++; + } +} + +static OutputStream *new_video_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + AVStream *st; + OutputStream *ost; + char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_VIDEO, ist); + st = ost->st; + + MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st); + if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(max_frame_rates, str, max_frame_rate, oc, st); + if (max_frame_rate && av_parse_video_rate(&ost->max_frame_rate, max_frame_rate) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid maximum framerate value: %s\n", max_frame_rate); + exit_program(1); + } + + if (frame_rate && max_frame_rate) { + av_log(ost, AV_LOG_ERROR, "Only one of -fpsmax and -r can be set for a stream.\n"); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st); + if (frame_aspect_ratio) { + AVRational q; + if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 || + q.num <= 0 || q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid aspect ratio: %s\n", frame_aspect_ratio); + exit_program(1); + } + ost->frame_aspect_ratio = q; + } + + MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); + + if (ost->enc_ctx) { + AVCodecContext *video_enc = ost->enc_ctx; + const char *p = NULL, *fps_mode = NULL; + char *frame_size = NULL; + char *frame_pix_fmt = NULL; + char *intra_matrix = NULL, *inter_matrix = NULL; + char *chroma_intra_matrix = NULL; + int do_pass = 0; + int i; + + MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); + if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st); + if (frame_pix_fmt && *frame_pix_fmt == '+') { + ost->keep_pix_fmt = 1; + if (!*++frame_pix_fmt) + frame_pix_fmt = NULL; + } + if (frame_pix_fmt && (video_enc->pix_fmt = av_get_pix_fmt(frame_pix_fmt)) == AV_PIX_FMT_NONE) { + av_log(ost, AV_LOG_FATAL, "Unknown pixel format requested: %s.\n", frame_pix_fmt); + exit_program(1); + } + st->sample_aspect_ratio = video_enc->sample_aspect_ratio; + + MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st); + if (intra_matrix) { + if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64))) + report_and_exit(AVERROR(ENOMEM)); + parse_matrix_coeffs(ost, video_enc->intra_matrix, intra_matrix); + } + MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st); + if (chroma_intra_matrix) { + uint16_t *p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64); + if (!p) + report_and_exit(AVERROR(ENOMEM)); + video_enc->chroma_intra_matrix = p; + parse_matrix_coeffs(ost, p, chroma_intra_matrix); + } + MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st); + if (inter_matrix) { + if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64))) + report_and_exit(AVERROR(ENOMEM)); + parse_matrix_coeffs(ost, video_enc->inter_matrix, inter_matrix); + } + + MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st); + for (i = 0; p; i++) { + int start, end, q; + int e = sscanf(p, "%d,%d,%d", &start, &end, &q); + if (e != 3) { + av_log(ost, AV_LOG_FATAL, "error parsing rc_override\n"); + exit_program(1); + } + video_enc->rc_override = + av_realloc_array(video_enc->rc_override, + i + 1, sizeof(RcOverride)); + if (!video_enc->rc_override) { + av_log(ost, AV_LOG_FATAL, "Could not (re)allocate memory for rc_override.\n"); + exit_program(1); + } + video_enc->rc_override[i].start_frame = start; + video_enc->rc_override[i].end_frame = end; + if (q > 0) { + video_enc->rc_override[i].qscale = q; + video_enc->rc_override[i].quality_factor = 1.0; + } + else { + video_enc->rc_override[i].qscale = 0; + video_enc->rc_override[i].quality_factor = -q/100.0; + } + p = strchr(p, '/'); + if (p) p++; + } + video_enc->rc_override_count = i; + +#if FFMPEG_OPT_PSNR + if (do_psnr) { + av_log(ost, AV_LOG_WARNING, "The -psnr option is deprecated, use -flags +psnr\n"); + video_enc->flags|= AV_CODEC_FLAG_PSNR; + } +#endif + + /* two pass mode */ + MATCH_PER_STREAM_OPT(pass, i, do_pass, oc, st); + if (do_pass) { + if (do_pass & 1) { + video_enc->flags |= AV_CODEC_FLAG_PASS1; + av_dict_set(&ost->encoder_opts, "flags", "+pass1", AV_DICT_APPEND); + } + if (do_pass & 2) { + video_enc->flags |= AV_CODEC_FLAG_PASS2; + av_dict_set(&ost->encoder_opts, "flags", "+pass2", AV_DICT_APPEND); + } + } + + MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st); + if (ost->logfile_prefix && + !(ost->logfile_prefix = av_strdup(ost->logfile_prefix))) + report_and_exit(AVERROR(ENOMEM)); + + if (do_pass) { + int ost_idx = -1; + char logfilename[1024]; + FILE *f; + + /* compute this stream's global index */ + for (int i = 0; i <= ost->file_index; i++) + ost_idx += output_files[i]->nb_streams; + + snprintf(logfilename, sizeof(logfilename), "%s-%d.log", + ost->logfile_prefix ? ost->logfile_prefix : + DEFAULT_PASS_LOGFILENAME_PREFIX, + ost_idx); + if (!strcmp(ost->enc_ctx->codec->name, "libx264")) { + av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE); + } else { + if (video_enc->flags & AV_CODEC_FLAG_PASS2) { + char *logbuffer = file_read(logfilename); + + if (!logbuffer) { + av_log(ost, AV_LOG_FATAL, "Error reading log file '%s' for pass-2 encoding\n", + logfilename); + exit_program(1); + } + video_enc->stats_in = logbuffer; + } + if (video_enc->flags & AV_CODEC_FLAG_PASS1) { + f = fopen_utf8(logfilename, "wb"); + if (!f) { + av_log(ost, AV_LOG_FATAL, + "Cannot write log file '%s' for pass-1 encoding: %s\n", + logfilename, strerror(errno)); + exit_program(1); + } + ost->logfile = f; + } + } + } + + MATCH_PER_STREAM_OPT(force_fps, i, ost->force_fps, oc, st); + + ost->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st); + + ost->vsync_method = video_sync_method; + MATCH_PER_STREAM_OPT(fps_mode, str, fps_mode, oc, st); + if (fps_mode) + parse_and_set_vsync(fps_mode, &ost->vsync_method, ost->file_index, ost->index, 0); + + if ((ost->frame_rate.num || ost->max_frame_rate.num) && + !(ost->vsync_method == VSYNC_AUTO || + ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR)) { + av_log(ost, AV_LOG_FATAL, "One of -r/-fpsmax was specified " + "together a non-CFR -vsync/-fps_mode. This is contradictory.\n"); + exit_program(1); + } + + if (ost->vsync_method == VSYNC_AUTO) { + if (ost->frame_rate.num || ost->max_frame_rate.num) { + ost->vsync_method = VSYNC_CFR; + } else if (!strcmp(oc->oformat->name, "avi")) { + ost->vsync_method = VSYNC_VFR; + } else { + ost->vsync_method = (oc->oformat->flags & AVFMT_VARIABLE_FPS) ? + ((oc->oformat->flags & AVFMT_NOTIMESTAMPS) ? + VSYNC_PASSTHROUGH : VSYNC_VFR) : + VSYNC_CFR; + } + + if (ost->ist && ost->vsync_method == VSYNC_CFR) { + const InputFile *ifile = input_files[ost->ist->file_index]; + + if (ifile->nb_streams == 1 && ifile->input_ts_offset == 0) + ost->vsync_method = VSYNC_VSCFR; + } + + if (ost->vsync_method == VSYNC_CFR && copy_ts) { + ost->vsync_method = VSYNC_VSCFR; + } + } + ost->is_cfr = (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR); + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit_program(1); + + ost->last_frame = av_frame_alloc(); + if (!ost->last_frame) + report_and_exit(AVERROR(ENOMEM)); + } else + check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_VIDEO); + + return ost; +} + +static OutputStream *new_audio_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + AVStream *st; + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_AUDIO, ist); + st = ost->st; + + + MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); + + if (ost->enc_ctx) { + AVCodecContext *audio_enc = ost->enc_ctx; + int channels = 0; + char *layout = NULL; + char *sample_fmt = NULL; + + MATCH_PER_STREAM_OPT(audio_channels, i, channels, oc, st); + if (channels) { + audio_enc->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC; + audio_enc->ch_layout.nb_channels = channels; + } + + MATCH_PER_STREAM_OPT(audio_ch_layouts, str, layout, oc, st); + if (layout) { + if (av_channel_layout_from_string(&audio_enc->ch_layout, layout) < 0) { +#if FF_API_OLD_CHANNEL_LAYOUT + uint64_t mask; + AV_NOWARN_DEPRECATED({ + mask = av_get_channel_layout(layout); + }) + if (!mask) { +#endif + av_log(ost, AV_LOG_FATAL, "Unknown channel layout: %s\n", layout); + exit_program(1); +#if FF_API_OLD_CHANNEL_LAYOUT + } + av_log(ost, AV_LOG_WARNING, "Channel layout '%s' uses a deprecated syntax.\n", + layout); + av_channel_layout_from_mask(&audio_enc->ch_layout, mask); +#endif + } + } + + MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st); + if (sample_fmt && + (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) { + av_log(ost, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st); + + MATCH_PER_STREAM_OPT(apad, str, ost->apad, oc, st); + ost->apad = av_strdup(ost->apad); + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit_program(1); + +#if FFMPEG_OPT_MAP_CHANNEL + /* check for channel mapping for this audio stream */ + for (int n = 0; n < o->nb_audio_channel_maps; n++) { + AudioChannelMap *map = &o->audio_channel_maps[n]; + if ((map->ofile_idx == -1 || ost->file_index == map->ofile_idx) && + (map->ostream_idx == -1 || ost->st->index == map->ostream_idx)) { + InputStream *ist; + + if (map->channel_idx == -1) { + ist = NULL; + } else if (!ost->ist) { + av_log(ost, AV_LOG_FATAL, "Cannot determine input stream for channel mapping %d.%d\n", + ost->file_index, ost->st->index); + continue; + } else { + ist = ost->ist; + } + + if (!ist || (ist->file_index == map->file_idx && ist->st->index == map->stream_idx)) { + if (av_reallocp_array(&ost->audio_channels_map, + ost->audio_channels_mapped + 1, + sizeof(*ost->audio_channels_map) + ) < 0 ) + report_and_exit(AVERROR(ENOMEM)); + + ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx; + } + } + } +#endif + } else + check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_AUDIO); + + return ost; +} + +static OutputStream *new_data_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_DATA, ist); + if (ost->enc_ctx) { + av_log(ost, AV_LOG_FATAL, "Data stream encoding not supported yet (only streamcopy)\n"); + exit_program(1); + } + + return ost; +} + +static OutputStream *new_unknown_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_UNKNOWN, ist); + if (ost->enc_ctx) { + av_log(ost, AV_LOG_FATAL, "Unknown stream encoding not supported yet (only streamcopy)\n"); + exit_program(1); + } + + return ost; +} + +static OutputStream *new_attachment_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost = new_output_stream(mux, o, AVMEDIA_TYPE_ATTACHMENT, ist); + ost->finished = 1; + return ost; +} + +static OutputStream *new_subtitle_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVStream *st; + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_SUBTITLE, ist); + st = ost->st; + + if (ost->enc_ctx) { + AVCodecContext *subtitle_enc = ost->enc_ctx; + char *frame_size = NULL; + + MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, mux->fc, st); + if (frame_size && av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); + exit_program(1); + } + } + + return ost; +} + +static void init_output_filter(OutputFilter *ofilter, const OptionsContext *o, + Muxer *mux) +{ + OutputStream *ost; + + switch (ofilter->type) { + case AVMEDIA_TYPE_VIDEO: ost = new_video_stream(mux, o, NULL); break; + case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream(mux, o, NULL); break; + default: + av_log(mux, AV_LOG_FATAL, "Only video and audio filters are supported " + "currently.\n"); + exit_program(1); + } + + ost->filter = ofilter; + + ofilter->ost = ost; + ofilter->format = -1; + + if (!ost->enc_ctx) { + av_log(ost, AV_LOG_ERROR, "Streamcopy requested for output stream fed " + "from a complex filtergraph. Filtering and streamcopy " + "cannot be used together.\n"); + exit_program(1); + } + + if (ost->avfilter && (ost->filters || ost->filters_script)) { + const char *opt = ost->filters ? "-vf/-af/-filter" : "-filter_script"; + av_log(ost, AV_LOG_ERROR, + "%s '%s' was specified through the %s option " + "for output stream %d:%d, which is fed from a complex filtergraph.\n" + "%s and -filter_complex cannot be used together for the same stream.\n", + ost->filters ? "Filtergraph" : "Filtergraph script", + ost->filters ? ost->filters : ost->filters_script, + opt, ost->file_index, ost->index, opt); + exit_program(1); + } + + avfilter_inout_free(&ofilter->out_tmp); +} + +static void map_auto_video(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + InputStream *best_ist = NULL; + int best_score = 0; + int qcr; + + /* video: highest resolution */ + if (av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_VIDEO) == AV_CODEC_ID_NONE) + return; + + qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0); + for (int j = 0; j < nb_input_files; j++) { + InputFile *ifile = input_files[j]; + InputStream *file_best_ist = NULL; + int file_best_score = 0; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int score; + + if (ist->user_set_discard == AVDISCARD_ALL || + ist->st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + + score = ist->st->codecpar->width * ist->st->codecpar->height + + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) + + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); + if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + score = 1; + + if (score > file_best_score) { + if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + continue; + file_best_score = score; + file_best_ist = ist; + } + } + if (file_best_ist) { + if((qcr == MKTAG('A', 'P', 'I', 'C')) || + !(file_best_ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + file_best_score -= 5000000*!!(file_best_ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (file_best_score > best_score) { + best_score = file_best_score; + best_ist = file_best_ist; + } + } + } + if (best_ist) + new_video_stream(mux, o, best_ist); +} + +static void map_auto_audio(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + InputStream *best_ist = NULL; + int best_score = 0; + + /* audio: most channels */ + if (av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_AUDIO) == AV_CODEC_ID_NONE) + return; + + for (int j = 0; j < nb_input_files; j++) { + InputFile *ifile = input_files[j]; + InputStream *file_best_ist = NULL; + int file_best_score = 0; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int score; + + if (ist->user_set_discard == AVDISCARD_ALL || + ist->st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) + continue; + + score = ist->st->codecpar->ch_layout.nb_channels + + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) + + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (score > file_best_score) { + file_best_score = score; + file_best_ist = ist; + } + } + if (file_best_ist) { + file_best_score -= 5000000*!!(file_best_ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (file_best_score > best_score) { + best_score = file_best_score; + best_ist = file_best_ist; + } + } + } + if (best_ist) + new_audio_stream(mux, o, best_ist); +} + +static void map_auto_subtitle(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + char *subtitle_codec_name = NULL; + + /* subtitles: pick first */ + MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s"); + if (!avcodec_find_encoder(oc->oformat->subtitle_codec) && !subtitle_codec_name) + return; + + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { + AVCodecDescriptor const *input_descriptor = + avcodec_descriptor_get(ist->st->codecpar->codec_id); + AVCodecDescriptor const *output_descriptor = NULL; + AVCodec const *output_codec = + avcodec_find_encoder(oc->oformat->subtitle_codec); + int input_props = 0, output_props = 0; + if (ist->user_set_discard == AVDISCARD_ALL) + continue; + if (output_codec) + output_descriptor = avcodec_descriptor_get(output_codec->id); + if (input_descriptor) + input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (output_descriptor) + output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (subtitle_codec_name || + input_props & output_props || + // Map dvb teletext which has neither property to any output subtitle encoder + (input_descriptor && output_descriptor && + (!input_descriptor->props || + !output_descriptor->props))) { + new_subtitle_stream(mux, o, ist); + break; + } + } +} + +static void map_auto_data(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + /* Data only if codec id match */ + enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_DATA); + + if (codec_id == AV_CODEC_ID_NONE) + return; + + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { + if (ist->user_set_discard == AVDISCARD_ALL) + continue; + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA && + ist->st->codecpar->codec_id == codec_id ) + new_data_stream(mux, o, ist); + } +} + +static void map_manual(Muxer *mux, const OptionsContext *o, const StreamMap *map) +{ + InputStream *ist; + + if (map->disabled) + return; + + if (map->linklabel) { + FilterGraph *fg; + OutputFilter *ofilter = NULL; + int j, k; + + for (j = 0; j < nb_filtergraphs; j++) { + fg = filtergraphs[j]; + for (k = 0; k < fg->nb_outputs; k++) { + AVFilterInOut *out = fg->outputs[k]->out_tmp; + if (out && !strcmp(out->name, map->linklabel)) { + ofilter = fg->outputs[k]; + goto loop_end; + } + } + } +loop_end: + if (!ofilter) { + av_log(mux, AV_LOG_FATAL, "Output with label '%s' does not exist " + "in any defined filter graph, or was already used elsewhere.\n", map->linklabel); + exit_program(1); + } + init_output_filter(ofilter, o, mux); + } else { + ist = input_files[map->file_index]->streams[map->stream_index]; + if (ist->user_set_discard == AVDISCARD_ALL) { + av_log(mux, AV_LOG_FATAL, "Stream #%d:%d is disabled and cannot be mapped.\n", + map->file_index, map->stream_index); + exit_program(1); + } + if(o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) + return; + if(o-> audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + return; + if(o-> video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + return; + if(o-> data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA) + return; + + switch (ist->st->codecpar->codec_type) { + case AVMEDIA_TYPE_VIDEO: new_video_stream (mux, o, ist); break; + case AVMEDIA_TYPE_AUDIO: new_audio_stream (mux, o, ist); break; + case AVMEDIA_TYPE_SUBTITLE: new_subtitle_stream (mux, o, ist); break; + case AVMEDIA_TYPE_DATA: new_data_stream (mux, o, ist); break; + case AVMEDIA_TYPE_ATTACHMENT: new_attachment_stream(mux, o, ist); break; + case AVMEDIA_TYPE_UNKNOWN: + if (copy_unknown_streams) { + new_unknown_stream (mux, o, ist); + break; + } + default: + av_log(mux, ignore_unknown_streams ? AV_LOG_WARNING : AV_LOG_FATAL, + "Cannot map stream #%d:%d - unsupported type.\n", + map->file_index, map->stream_index); + if (!ignore_unknown_streams) { + av_log(mux, AV_LOG_FATAL, + "If you want unsupported types ignored instead " + "of failing, please use the -ignore_unknown option\n" + "If you want them copied, please use -copy_unknown\n"); + exit_program(1); + } + } + } +} + +static void of_add_attachments(Muxer *mux, const OptionsContext *o) +{ + OutputStream *ost; + int err; + + for (int i = 0; i < o->nb_attachments; i++) { + AVIOContext *pb; + uint8_t *attachment; + const char *p; + int64_t len; + + if ((err = avio_open2(&pb, o->attachments[i], AVIO_FLAG_READ, &int_cb, NULL)) < 0) { + av_log(mux, AV_LOG_FATAL, "Could not open attachment file %s.\n", + o->attachments[i]); + exit_program(1); + } + if ((len = avio_size(pb)) <= 0) { + av_log(mux, AV_LOG_FATAL, "Could not get size of the attachment %s.\n", + o->attachments[i]); + exit_program(1); + } + if (len > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE || + !(attachment = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE))) { + av_log(mux, AV_LOG_FATAL, "Attachment %s too large.\n", + o->attachments[i]); + exit_program(1); + } + avio_read(pb, attachment, len); + memset(attachment + len, 0, AV_INPUT_BUFFER_PADDING_SIZE); + + ost = new_attachment_stream(mux, o, NULL); + ost->attachment_filename = o->attachments[i]; + ost->st->codecpar->extradata = attachment; + ost->st->codecpar->extradata_size = len; + + p = strrchr(o->attachments[i], '/'); + av_dict_set(&ost->st->metadata, "filename", (p && *p) ? p + 1 : o->attachments[i], AV_DICT_DONT_OVERWRITE); + avio_closep(&pb); + } +} + +static void create_streams(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + int auto_disable_v = o->video_disable; + int auto_disable_a = o->audio_disable; + int auto_disable_s = o->subtitle_disable; + int auto_disable_d = o->data_disable; + + /* create streams for all unlabeled output pads */ + for (int i = 0; i < nb_filtergraphs; i++) { + FilterGraph *fg = filtergraphs[i]; + for (int j = 0; j < fg->nb_outputs; j++) { + OutputFilter *ofilter = fg->outputs[j]; + + if (!ofilter->out_tmp || ofilter->out_tmp->name) + continue; + + switch (ofilter->type) { + case AVMEDIA_TYPE_VIDEO: auto_disable_v = 1; break; + case AVMEDIA_TYPE_AUDIO: auto_disable_a = 1; break; + case AVMEDIA_TYPE_SUBTITLE: auto_disable_s = 1; break; + } + init_output_filter(ofilter, o, mux); + } + } + + if (!o->nb_stream_maps) { + /* pick the "best" stream of each type */ + if (!auto_disable_v) + map_auto_video(mux, o); + if (!auto_disable_a) + map_auto_audio(mux, o); + if (!auto_disable_s) + map_auto_subtitle(mux, o); + if (!auto_disable_d) + map_auto_data(mux, o); + } else { + for (int i = 0; i < o->nb_stream_maps; i++) + map_manual(mux, o, &o->stream_maps[i]); + } + + of_add_attachments(mux, o); + + if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) { + av_dump_format(oc, nb_output_files - 1, oc->url, 1); + av_log(mux, AV_LOG_ERROR, "Output file does not contain any stream\n"); + exit_program(1); + } +} + +static int setup_sync_queues(Muxer *mux, AVFormatContext *oc, int64_t buf_size_us) +{ + OutputFile *of = &mux->of; + int nb_av_enc = 0, nb_interleaved = 0; + int limit_frames = 0, limit_frames_av_enc = 0; + +#define IS_AV_ENC(ost, type) \ + (ost->enc_ctx && (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) +#define IS_INTERLEAVED(type) (type != AVMEDIA_TYPE_ATTACHMENT) + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + ost->sq_idx_encode = -1; + ost->sq_idx_mux = -1; + + nb_interleaved += IS_INTERLEAVED(type); + nb_av_enc += IS_AV_ENC(ost, type); + + limit_frames |= ms->max_frames < INT64_MAX; + limit_frames_av_enc |= (ms->max_frames < INT64_MAX) && IS_AV_ENC(ost, type); + } + + if (!((nb_interleaved > 1 && of->shortest) || + (nb_interleaved > 0 && limit_frames))) + return 0; + + /* if we have more than one encoded audio/video streams, or at least + * one encoded audio/video stream is frame-limited, then we + * synchronize them before encoding */ + if ((of->shortest && nb_av_enc > 1) || limit_frames_av_enc) { + of->sq_encode = sq_alloc(SYNC_QUEUE_FRAMES, buf_size_us); + if (!of->sq_encode) + return AVERROR(ENOMEM); + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (!IS_AV_ENC(ost, type)) + continue; + + ost->sq_idx_encode = sq_add_stream(of->sq_encode, + of->shortest || ms->max_frames < INT64_MAX); + if (ost->sq_idx_encode < 0) + return ost->sq_idx_encode; + + ost->sq_frame = av_frame_alloc(); + if (!ost->sq_frame) + return AVERROR(ENOMEM); + + if (ms->max_frames != INT64_MAX) + sq_limit_frames(of->sq_encode, ost->sq_idx_encode, ms->max_frames); + } + } + + /* if there are any additional interleaved streams, then ALL the streams + * are also synchronized before sending them to the muxer */ + if (nb_interleaved > nb_av_enc) { + mux->sq_mux = sq_alloc(SYNC_QUEUE_PACKETS, buf_size_us); + if (!mux->sq_mux) + return AVERROR(ENOMEM); + + mux->sq_pkt = av_packet_alloc(); + if (!mux->sq_pkt) + return AVERROR(ENOMEM); + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (!IS_INTERLEAVED(type)) + continue; + + ost->sq_idx_mux = sq_add_stream(mux->sq_mux, + of->shortest || ms->max_frames < INT64_MAX); + if (ost->sq_idx_mux < 0) + return ost->sq_idx_mux; + + if (ms->max_frames != INT64_MAX) + sq_limit_frames(mux->sq_mux, ost->sq_idx_mux, ms->max_frames); + } + } + +#undef IS_AV_ENC +#undef IS_INTERLEAVED + + return 0; +} + +static void of_add_programs(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + /* process manually set programs */ + for (int i = 0; i < o->nb_program; i++) { + const char *p = o->program[i].u.str; + int progid = i+1; + AVProgram *program; + + while(*p) { + const char *p2 = av_get_token(&p, ":"); + const char *to_dealloc = p2; + char *key; + if (!p2) + break; + + if(*p) p++; + + key = av_get_token(&p2, "="); + if (!key || !*p2) { + av_freep(&to_dealloc); + av_freep(&key); + break; + } + p2++; + + if (!strcmp(key, "program_num")) + progid = strtol(p2, NULL, 0); + av_freep(&to_dealloc); + av_freep(&key); + } + + program = av_new_program(oc, progid); + if (!program) + report_and_exit(AVERROR(ENOMEM)); + + p = o->program[i].u.str; + while(*p) { + const char *p2 = av_get_token(&p, ":"); + const char *to_dealloc = p2; + char *key; + if (!p2) + break; + if(*p) p++; + + key = av_get_token(&p2, "="); + if (!key) { + av_log(mux, AV_LOG_FATAL, + "No '=' character in program string %s.\n", + p2); + exit_program(1); + } + if (!*p2) + exit_program(1); + p2++; + + if (!strcmp(key, "title")) { + av_dict_set(&program->metadata, "title", p2, 0); + } else if (!strcmp(key, "program_num")) { + } else if (!strcmp(key, "st")) { + int st_num = strtol(p2, NULL, 0); + av_program_add_stream_index(oc, progid, st_num); + } else { + av_log(mux, AV_LOG_FATAL, "Unknown program key %s.\n", key); + exit_program(1); + } + av_freep(&to_dealloc); + av_freep(&key); + } + } +} + +/** + * Parse a metadata specifier passed as 'arg' parameter. + * @param arg metadata string to parse + * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram) + * @param index for type c/p, chapter/program index is written here + * @param stream_spec for type s, the stream specifier is written here + */ +static void parse_meta_type(void *logctx, const char *arg, + char *type, int *index, const char **stream_spec) +{ + if (*arg) { + *type = *arg; + switch (*arg) { + case 'g': + break; + case 's': + if (*(++arg) && *arg != ':') { + av_log(logctx, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg); + exit_program(1); + } + *stream_spec = *arg == ':' ? arg + 1 : ""; + break; + case 'c': + case 'p': + if (*(++arg) == ':') + *index = strtol(++arg, NULL, 0); + break; + default: + av_log(logctx, AV_LOG_FATAL, "Invalid metadata type %c.\n", *arg); + exit_program(1); + } + } else + *type = 'g'; +} + +static void of_add_metadata(OutputFile *of, AVFormatContext *oc, + const OptionsContext *o) +{ + for (int i = 0; i < o->nb_metadata; i++) { + AVDictionary **m; + char type, *val; + const char *stream_spec; + int index = 0, ret = 0; + + val = strchr(o->metadata[i].u.str, '='); + if (!val) { + av_log(of, AV_LOG_FATAL, "No '=' character in metadata string %s.\n", + o->metadata[i].u.str); + exit_program(1); + } + *val++ = 0; + + parse_meta_type(of, o->metadata[i].specifier, &type, &index, &stream_spec); + if (type == 's') { + for (int j = 0; j < oc->nb_streams; j++) { + OutputStream *ost = of->streams[j]; + if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) { +#if FFMPEG_ROTATION_METADATA + if (!strcmp(o->metadata[i].u.str, "rotate")) { + char *tail; + double theta = av_strtod(val, &tail); + if (!*tail) { + ost->rotate_overridden = 1; + ost->rotate_override_value = theta; + } + + av_log(ost, AV_LOG_WARNING, + "Conversion of a 'rotate' metadata key to a " + "proper display matrix rotation is deprecated. " + "See -display_rotation for setting rotation " + "instead."); + } else { +#endif + av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); +#if FFMPEG_ROTATION_METADATA + } +#endif + } else if (ret < 0) + exit_program(1); + } + } else { + switch (type) { + case 'g': + m = &oc->metadata; + break; + case 'c': + if (index < 0 || index >= oc->nb_chapters) { + av_log(of, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); + exit_program(1); + } + m = &oc->chapters[index]->metadata; + break; + case 'p': + if (index < 0 || index >= oc->nb_programs) { + av_log(of, AV_LOG_FATAL, "Invalid program index %d in metadata specifier.\n", index); + exit_program(1); + } + m = &oc->programs[index]->metadata; + break; + default: + av_log(of, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); + exit_program(1); + } + av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); + } + } +} + +static void set_channel_layout(OutputFilter *f, OutputStream *ost) +{ + const AVCodec *c = ost->enc_ctx->codec; + int i, err; + + if (ost->enc_ctx->ch_layout.order != AV_CHANNEL_ORDER_UNSPEC) { + /* Pass the layout through for all orders but UNSPEC */ + err = av_channel_layout_copy(&f->ch_layout, &ost->enc_ctx->ch_layout); + if (err < 0) + report_and_exit(AVERROR(ENOMEM)); + return; + } + + /* Requested layout is of order UNSPEC */ + if (!c->ch_layouts) { + /* Use the default native layout for the requested amount of channels when the + encoder doesn't have a list of supported layouts */ + av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); + return; + } + /* Encoder has a list of supported layouts. Pick the first layout in it with the + same amount of channels as the requested layout */ + for (i = 0; c->ch_layouts[i].nb_channels; i++) { + if (c->ch_layouts[i].nb_channels == ost->enc_ctx->ch_layout.nb_channels) + break; + } + if (c->ch_layouts[i].nb_channels) { + /* Use it if one is found */ + err = av_channel_layout_copy(&f->ch_layout, &c->ch_layouts[i]); + if (err < 0) + report_and_exit(AVERROR(ENOMEM)); + return; + } + /* If no layout for the amount of channels requested was found, use the default + native layout for it. */ + av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); +} + +static int copy_chapters(InputFile *ifile, OutputFile *ofile, AVFormatContext *os, + int copy_metadata) +{ + AVFormatContext *is = ifile->ctx; + AVChapter **tmp; + int i; + + tmp = av_realloc_f(os->chapters, is->nb_chapters + os->nb_chapters, sizeof(*os->chapters)); + if (!tmp) + return AVERROR(ENOMEM); + os->chapters = tmp; + + for (i = 0; i < is->nb_chapters; i++) { + AVChapter *in_ch = is->chapters[i], *out_ch; + int64_t start_time = (ofile->start_time == AV_NOPTS_VALUE) ? 0 : ofile->start_time; + int64_t ts_off = av_rescale_q(start_time - ifile->ts_offset, + AV_TIME_BASE_Q, in_ch->time_base); + int64_t rt = (ofile->recording_time == INT64_MAX) ? INT64_MAX : + av_rescale_q(ofile->recording_time, AV_TIME_BASE_Q, in_ch->time_base); + + + if (in_ch->end < ts_off) + continue; + if (rt != INT64_MAX && in_ch->start > rt + ts_off) + break; + + out_ch = av_mallocz(sizeof(AVChapter)); + if (!out_ch) + return AVERROR(ENOMEM); + + out_ch->id = in_ch->id; + out_ch->time_base = in_ch->time_base; + out_ch->start = FFMAX(0, in_ch->start - ts_off); + out_ch->end = FFMIN(rt, in_ch->end - ts_off); + + if (copy_metadata) + av_dict_copy(&out_ch->metadata, in_ch->metadata, 0); + + os->chapters[os->nb_chapters++] = out_ch; + } + return 0; +} + +static int copy_metadata(Muxer *mux, AVFormatContext *ic, + const char *outspec, const char *inspec, + int *metadata_global_manual, int *metadata_streams_manual, + int *metadata_chapters_manual, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + AVDictionary **meta_in = NULL; + AVDictionary **meta_out = NULL; + int i, ret = 0; + char type_in, type_out; + const char *istream_spec = NULL, *ostream_spec = NULL; + int idx_in = 0, idx_out = 0; + + parse_meta_type(mux, inspec, &type_in, &idx_in, &istream_spec); + parse_meta_type(mux, outspec, &type_out, &idx_out, &ostream_spec); + + if (type_in == 'g' || type_out == 'g') + *metadata_global_manual = 1; + if (type_in == 's' || type_out == 's') + *metadata_streams_manual = 1; + if (type_in == 'c' || type_out == 'c') + *metadata_chapters_manual = 1; + + /* ic is NULL when just disabling automatic mappings */ + if (!ic) + return 0; + +#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ + if ((index) < 0 || (index) >= (nb_elems)) {\ + av_log(mux, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\ + (desc), (index));\ + exit_program(1);\ + } + +#define SET_DICT(type, meta, context, index)\ + switch (type) {\ + case 'g':\ + meta = &context->metadata;\ + break;\ + case 'c':\ + METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\ + meta = &context->chapters[index]->metadata;\ + break;\ + case 'p':\ + METADATA_CHECK_INDEX(index, context->nb_programs, "program")\ + meta = &context->programs[index]->metadata;\ + break;\ + case 's':\ + break; /* handled separately below */ \ + default: av_assert0(0);\ + }\ + + SET_DICT(type_in, meta_in, ic, idx_in); + SET_DICT(type_out, meta_out, oc, idx_out); + + /* for input streams choose first matching stream */ + if (type_in == 's') { + for (i = 0; i < ic->nb_streams; i++) { + if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) { + meta_in = &ic->streams[i]->metadata; + break; + } else if (ret < 0) + exit_program(1); + } + if (!meta_in) { + av_log(mux, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec); + exit_program(1); + } + } + + if (type_out == 's') { + for (i = 0; i < oc->nb_streams; i++) { + if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) { + meta_out = &oc->streams[i]->metadata; + av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); + } else if (ret < 0) + exit_program(1); + } + } else + av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); + + return 0; +} + +static void copy_meta(Muxer *mux, const OptionsContext *o) +{ + OutputFile *of = &mux->of; + AVFormatContext *oc = mux->fc; + int chapters_input_file = o->chapters_input_file; + int metadata_global_manual = 0; + int metadata_streams_manual = 0; + int metadata_chapters_manual = 0; + + /* copy metadata */ + for (int i = 0; i < o->nb_metadata_map; i++) { + char *p; + int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0); + + if (in_file_index >= nb_input_files) { + av_log(mux, AV_LOG_FATAL, "Invalid input file index %d while " + "processing metadata maps\n", in_file_index); + exit_program(1); + } + copy_metadata(mux, + in_file_index >= 0 ? input_files[in_file_index]->ctx : NULL, + o->metadata_map[i].specifier, *p ? p + 1 : p, + &metadata_global_manual, &metadata_streams_manual, + &metadata_chapters_manual, o); + } + + /* copy chapters */ + if (chapters_input_file >= nb_input_files) { + if (chapters_input_file == INT_MAX) { + /* copy chapters from the first input file that has them*/ + chapters_input_file = -1; + for (int i = 0; i < nb_input_files; i++) + if (input_files[i]->ctx->nb_chapters) { + chapters_input_file = i; + break; + } + } else { + av_log(mux, AV_LOG_FATAL, "Invalid input file index %d in chapter mapping.\n", + chapters_input_file); + exit_program(1); + } + } + if (chapters_input_file >= 0) + copy_chapters(input_files[chapters_input_file], of, oc, + !metadata_chapters_manual); + + /* copy global metadata by default */ + if (!metadata_global_manual && nb_input_files){ + av_dict_copy(&oc->metadata, input_files[0]->ctx->metadata, + AV_DICT_DONT_OVERWRITE); + if (of->recording_time != INT64_MAX) + av_dict_set(&oc->metadata, "duration", NULL, 0); + av_dict_set(&oc->metadata, "creation_time", NULL, 0); + av_dict_set(&oc->metadata, "company_name", NULL, 0); + av_dict_set(&oc->metadata, "product_name", NULL, 0); + av_dict_set(&oc->metadata, "product_version", NULL, 0); + } + if (!metadata_streams_manual) + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + if (!ost->ist) /* this is true e.g. for attached files */ + continue; + av_dict_copy(&ost->st->metadata, ost->ist->st->metadata, AV_DICT_DONT_OVERWRITE); + if (ost->enc_ctx) { + av_dict_set(&ost->st->metadata, "encoder", NULL, 0); + } + } +} + +static int set_dispositions(Muxer *mux, const OptionsContext *o) +{ + OutputFile *of = &mux->of; + AVFormatContext *ctx = mux->fc; + + int nb_streams[AVMEDIA_TYPE_NB] = { 0 }; + int have_default[AVMEDIA_TYPE_NB] = { 0 }; + int have_manual = 0; + int ret = 0; + + const char **dispositions; + + dispositions = av_calloc(ctx->nb_streams, sizeof(*dispositions)); + if (!dispositions) + return AVERROR(ENOMEM); + + // first, copy the input dispositions + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + nb_streams[ost->st->codecpar->codec_type]++; + + MATCH_PER_STREAM_OPT(disposition, str, dispositions[i], ctx, ost->st); + + have_manual |= !!dispositions[i]; + + if (ost->ist) { + ost->st->disposition = ost->ist->st->disposition; + + if (ost->st->disposition & AV_DISPOSITION_DEFAULT) + have_default[ost->st->codecpar->codec_type] = 1; + } + } + + if (have_manual) { + // process manually set dispositions - they override the above copy + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + const char *disp = dispositions[i]; + + if (!disp) + continue; + + ret = av_opt_set(ost->st, "disposition", disp, 0); + if (ret < 0) + goto finish; + } + } else { + // For each media type with more than one stream, find a suitable stream to + // mark as default, unless one is already marked default. + // "Suitable" means the first of that type, skipping attached pictures. + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (nb_streams[type] < 2 || have_default[type] || + ost->st->disposition & AV_DISPOSITION_ATTACHED_PIC) + continue; + + ost->st->disposition |= AV_DISPOSITION_DEFAULT; + have_default[type] = 1; + } + } + +finish: + av_freep(&dispositions); + + return ret; +} + +const char *const forced_keyframes_const_names[] = { + "n", + "n_forced", + "prev_forced_n", + "prev_forced_t", + "t", + NULL +}; + +static int compare_int64(const void *a, const void *b) +{ + return FFDIFFSIGN(*(const int64_t *)a, *(const int64_t *)b); +} + +static void parse_forced_key_frames(KeyframeForceCtx *kf, const Muxer *mux, + const char *spec) +{ + const char *p; + int n = 1, i, size, index = 0; + int64_t t, *pts; + + for (p = spec; *p; p++) + if (*p == ',') + n++; + size = n; + pts = av_malloc_array(size, sizeof(*pts)); + if (!pts) + report_and_exit(AVERROR(ENOMEM)); + + p = spec; + for (i = 0; i < n; i++) { + char *next = strchr(p, ','); + + if (next) + *next++ = 0; + + if (!memcmp(p, "chapters", 8)) { + AVChapter * const *ch = mux->fc->chapters; + unsigned int nb_ch = mux->fc->nb_chapters; + int j; + + if (nb_ch > INT_MAX - size || + !(pts = av_realloc_f(pts, size += nb_ch - 1, + sizeof(*pts)))) + report_and_exit(AVERROR(ENOMEM)); + t = p[8] ? parse_time_or_die("force_key_frames", p + 8, 1) : 0; + + for (j = 0; j < nb_ch; j++) { + const AVChapter *c = ch[j]; + av_assert1(index < size); + pts[index++] = av_rescale_q(c->start, c->time_base, + AV_TIME_BASE_Q) + t; + } + + } else { + av_assert1(index < size); + pts[index++] = parse_time_or_die("force_key_frames", p, 1); + } + + p = next; + } + + av_assert0(index == size); + qsort(pts, size, sizeof(*pts), compare_int64); + kf->nb_pts = size; + kf->pts = pts; +} + +static int process_forced_keyframes(Muxer *mux, const OptionsContext *o) +{ + for (int i = 0; i < mux->of.nb_streams; i++) { + OutputStream *ost = mux->of.streams[i]; + const char *forced_keyframes = NULL; + + MATCH_PER_STREAM_OPT(forced_key_frames, str, forced_keyframes, mux->fc, ost->st); + + if (!(ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + ost->enc_ctx && forced_keyframes)) + continue; + + if (!strncmp(forced_keyframes, "expr:", 5)) { + int ret = av_expr_parse(&ost->kf.pexpr, forced_keyframes + 5, + forced_keyframes_const_names, NULL, NULL, NULL, NULL, 0, NULL); + if (ret < 0) { + av_log(ost, AV_LOG_ERROR, + "Invalid force_key_frames expression '%s'\n", forced_keyframes + 5); + return ret; + } + ost->kf.expr_const_values[FKF_N] = 0; + ost->kf.expr_const_values[FKF_N_FORCED] = 0; + ost->kf.expr_const_values[FKF_PREV_FORCED_N] = NAN; + ost->kf.expr_const_values[FKF_PREV_FORCED_T] = NAN; + + // Don't parse the 'forced_keyframes' in case of 'keep-source-keyframes', + // parse it only for static kf timings + } else if (!strcmp(forced_keyframes, "source")) { + ost->kf.type = KF_FORCE_SOURCE; + } else if (!strcmp(forced_keyframes, "source_no_drop")) { + ost->kf.type = KF_FORCE_SOURCE_NO_DROP; + } else { + parse_forced_key_frames(&ost->kf, mux, forced_keyframes); + } + } + + return 0; +} + +static void validate_enc_avopt(Muxer *mux, const AVDictionary *codec_avopt) +{ + const AVClass *class = avcodec_get_class(); + const AVClass *fclass = avformat_get_class(); + const OutputFile *of = &mux->of; + + AVDictionary *unused_opts; + const AVDictionaryEntry *e; + + unused_opts = strip_specifiers(codec_avopt); + for (int i = 0; i < of->nb_streams; i++) { + e = NULL; + while ((e = av_dict_iterate(of->streams[i]->encoder_opts, e))) + av_dict_set(&unused_opts, e->key, NULL, 0); + } + + e = NULL; + while ((e = av_dict_iterate(unused_opts, e))) { + const AVOption *option = av_opt_find(&class, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + if (!option || foption) + continue; + + if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) { + av_log(mux, AV_LOG_ERROR, "Codec AVOption %s (%s) is not an " + "encoding option.\n", e->key, option->help ? option->help : ""); + exit_program(1); + } + + // gop_timecode is injected by generic code but not always used + if (!strcmp(e->key, "gop_timecode")) + continue; + + av_log(mux, AV_LOG_WARNING, "Codec AVOption %s (%s) has not been used " + "for any stream. The most likely reason is either wrong type " + "(e.g. a video option with no video streams) or that it is a " + "private option of some encoder which was not actually used for " + "any stream.\n", e->key, option->help ? option->help : ""); + } + av_dict_free(&unused_opts); +} + +static const char *output_file_item_name(void *obj) +{ + const Muxer *mux = obj; + + return mux->log_name; +} + +static const AVClass output_file_class = { + .class_name = "OutputFile", + .version = LIBAVUTIL_VERSION_INT, + .item_name = output_file_item_name, + .category = AV_CLASS_CATEGORY_MUXER, +}; + +static Muxer *mux_alloc(void) +{ + Muxer *mux = allocate_array_elem(&output_files, sizeof(*mux), &nb_output_files); + + mux->of.clazz = &output_file_class; + mux->of.index = nb_output_files - 1; + + snprintf(mux->log_name, sizeof(mux->log_name), "out#%d", mux->of.index); + + return mux; +} + +int of_open(const OptionsContext *o, const char *filename) +{ + Muxer *mux; + AVFormatContext *oc; + int err; + OutputFile *of; + + int64_t recording_time = o->recording_time; + int64_t stop_time = o->stop_time; + + mux = mux_alloc(); + of = &mux->of; + + if (stop_time != INT64_MAX && recording_time != INT64_MAX) { + stop_time = INT64_MAX; + av_log(mux, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); + } + + if (stop_time != INT64_MAX && recording_time == INT64_MAX) { + int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; + if (stop_time <= start_time) { + av_log(mux, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); + exit_program(1); + } else { + recording_time = stop_time - start_time; + } + } + + of->recording_time = recording_time; + of->start_time = o->start_time; + of->shortest = o->shortest; + + mux->thread_queue_size = o->thread_queue_size > 0 ? o->thread_queue_size : 8; + mux->limit_filesize = o->limit_filesize; + av_dict_copy(&mux->opts, o->g->format_opts, 0); + + if (!strcmp(filename, "-")) + filename = "pipe:"; + + err = avformat_alloc_output_context2(&oc, NULL, o->format, filename); + if (!oc) { + print_error(filename, err); + exit_program(1); + } + mux->fc = oc; + + av_strlcat(mux->log_name, "/", sizeof(mux->log_name)); + av_strlcat(mux->log_name, oc->oformat->name, sizeof(mux->log_name)); + + if (strcmp(oc->oformat->name, "rtp")) + want_sdp = 0; + + of->format = oc->oformat; + if (recording_time != INT64_MAX) + oc->duration = recording_time; + + oc->interrupt_callback = int_cb; + + if (o->bitexact) { + oc->flags |= AVFMT_FLAG_BITEXACT; + of->bitexact = 1; + } else { + of->bitexact = check_opt_bitexact(oc, mux->opts, "fflags", + AVFMT_FLAG_BITEXACT); + } + + /* create all output streams for this file */ + create_streams(mux, o); + + /* check if all codec options have been used */ + validate_enc_avopt(mux, o->g->codec_opts); + + /* set the decoding_needed flags and create simple filtergraphs */ + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + if (ost->enc_ctx && ost->ist) { + InputStream *ist = ost->ist; + ist->decoding_needed |= DECODING_FOR_OST; + ist->processing_needed = 1; + + if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + err = init_simple_filtergraph(ist, ost); + if (err < 0) { + av_log(ost, AV_LOG_ERROR, + "Error initializing a simple filtergraph\n"); + exit_program(1); + } + } + } else if (ost->ist) { + ost->ist->processing_needed = 1; + } + + /* set the filter output constraints */ + if (ost->filter) { + const AVCodec *c = ost->enc_ctx->codec; + OutputFilter *f = ost->filter; + switch (ost->enc_ctx->codec_type) { + case AVMEDIA_TYPE_VIDEO: + f->frame_rate = ost->frame_rate; + f->width = ost->enc_ctx->width; + f->height = ost->enc_ctx->height; + if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { + f->format = ost->enc_ctx->pix_fmt; + } else { + f->formats = c->pix_fmts; + } + break; + case AVMEDIA_TYPE_AUDIO: + if (ost->enc_ctx->sample_fmt != AV_SAMPLE_FMT_NONE) { + f->format = ost->enc_ctx->sample_fmt; + } else { + f->formats = c->sample_fmts; + } + if (ost->enc_ctx->sample_rate) { + f->sample_rate = ost->enc_ctx->sample_rate; + } else { + f->sample_rates = c->supported_samplerates; + } + if (ost->enc_ctx->ch_layout.nb_channels) { + set_channel_layout(f, ost); + } else if (c->ch_layouts) { + f->ch_layouts = c->ch_layouts; + } + break; + } + } + } + + /* check filename in case of an image number is expected */ + if (oc->oformat->flags & AVFMT_NEEDNUMBER) { + if (!av_filename_number_test(oc->url)) { + print_error(oc->url, AVERROR(EINVAL)); + exit_program(1); + } + } + + if (!(oc->oformat->flags & AVFMT_NOFILE)) { + /* test if it already exists to avoid losing precious files */ + assert_file_overwrite(filename); + + /* open the file */ + if ((err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, + &oc->interrupt_callback, + &mux->opts)) < 0) { + print_error(filename, err); + exit_program(1); + } + } else if (strcmp(oc->oformat->name, "image2")==0 && !av_filename_number_test(filename)) + assert_file_overwrite(filename); + + if (o->mux_preload) { + av_dict_set_int(&mux->opts, "preload", o->mux_preload*AV_TIME_BASE, 0); + } + oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); + + /* copy metadata and chapters from input files */ + copy_meta(mux, o); + + of_add_programs(mux, o); + of_add_metadata(of, oc, o); + + err = set_dispositions(mux, o); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error setting output stream dispositions\n"); + exit_program(1); + } + + // parse forced keyframe specifications; + // must be done after chapters are created + err = process_forced_keyframes(mux, o); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error processing forced keyframes\n"); + exit_program(1); + } + + err = setup_sync_queues(mux, oc, o->shortest_buf_duration * AV_TIME_BASE); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error setting up output sync queues\n"); + exit_program(1); + } + + of->url = filename; + + /* write the header for files with no streams */ + if (of->format->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) { + int ret = mux_check_init(mux); + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/apple/src/fftools_ffmpeg_opt.c b/apple/src/fftools_ffmpeg_opt.c index 12c0e23..0ae6c67 100644 --- a/apple/src/fftools_ffmpeg_opt.c +++ b/apple/src/fftools_ffmpeg_opt.c @@ -1,6 +1,7 @@ /* * ffmpeg option parsing * Copyright (c) 2018-2020 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,14 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - include fftools_ffmpeg_mux.h added + * - fftools header names updated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 08.2020 @@ -60,8 +69,10 @@ #endif #include "fftools_ffmpeg.h" -#include "fftools_fopen_utf8.h" +#include "fftools_ffmpeg_mux.h" #include "fftools_cmdutils.h" +#include "fftools_opt_common.h" +#include "fftools_sync_queue.h" #include "libavformat/avformat.h" @@ -75,7 +86,7 @@ #include "libavutil/avutil.h" #include "libavutil/bprint.h" #include "libavutil/channel_layout.h" -#include "libavutil/getenv_utf8.h" +#include "libavutil/display.h" #include "libavutil/intreadwrite.h" #include "libavutil/fifo.h" #include "libavutil/mathematics.h" @@ -84,99 +95,10 @@ #include "libavutil/pixdesc.h" #include "libavutil/pixfmt.h" -#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass" - -#define SPECIFIER_OPT_FMT_str "%s" -#define SPECIFIER_OPT_FMT_i "%i" -#define SPECIFIER_OPT_FMT_i64 "%"PRId64 -#define SPECIFIER_OPT_FMT_ui64 "%"PRIu64 -#define SPECIFIER_OPT_FMT_f "%f" -#define SPECIFIER_OPT_FMT_dbl "%lf" - -static const char *const opt_name_codec_names[] = {"c", "codec", "acodec", "vcodec", "scodec", "dcodec", NULL}; -static const char *const opt_name_audio_channels[] = {"ac", NULL}; -static const char *const opt_name_audio_ch_layouts[] = {"channel_layout", "ch_layout", NULL}; -static const char *const opt_name_audio_sample_rate[] = {"ar", NULL}; -static const char *const opt_name_frame_rates[] = {"r", NULL}; -static const char *const opt_name_max_frame_rates[] = {"fpsmax", NULL}; -static const char *const opt_name_frame_sizes[] = {"s", NULL}; -static const char *const opt_name_frame_pix_fmts[] = {"pix_fmt", NULL}; -static const char *const opt_name_ts_scale[] = {"itsscale", NULL}; -static const char *const opt_name_hwaccels[] = {"hwaccel", NULL}; -static const char *const opt_name_hwaccel_devices[] = {"hwaccel_device", NULL}; -static const char *const opt_name_hwaccel_output_formats[] = {"hwaccel_output_format", NULL}; -static const char *const opt_name_autorotate[] = {"autorotate", NULL}; -static const char *const opt_name_autoscale[] = {"autoscale", NULL}; -static const char *const opt_name_max_frames[] = {"frames", "aframes", "vframes", "dframes", NULL}; -static const char *const opt_name_bitstream_filters[] = {"bsf", "absf", "vbsf", NULL}; -static const char *const opt_name_codec_tags[] = {"tag", "atag", "vtag", "stag", NULL}; -static const char *const opt_name_sample_fmts[] = {"sample_fmt", NULL}; -static const char *const opt_name_qscale[] = {"q", "qscale", NULL}; -static const char *const opt_name_forced_key_frames[] = {"forced_key_frames", NULL}; -static const char *const opt_name_fps_mode[] = {"fps_mode", NULL}; -static const char *const opt_name_force_fps[] = {"force_fps", NULL}; -static const char *const opt_name_frame_aspect_ratios[] = {"aspect", NULL}; -static const char *const opt_name_rc_overrides[] = {"rc_override", NULL}; -static const char *const opt_name_intra_matrices[] = {"intra_matrix", NULL}; -static const char *const opt_name_inter_matrices[] = {"inter_matrix", NULL}; -static const char *const opt_name_chroma_intra_matrices[] = {"chroma_intra_matrix", NULL}; -static const char *const opt_name_top_field_first[] = {"top", NULL}; -static const char *const opt_name_presets[] = {"pre", "apre", "vpre", "spre", NULL}; -static const char *const opt_name_copy_initial_nonkeyframes[] = {"copyinkf", NULL}; -static const char *const opt_name_copy_prior_start[] = {"copypriorss", NULL}; -static const char *const opt_name_filters[] = {"filter", "af", "vf", NULL}; -static const char *const opt_name_filter_scripts[] = {"filter_script", NULL}; -static const char *const opt_name_reinit_filters[] = {"reinit_filter", NULL}; -static const char *const opt_name_fix_sub_duration[] = {"fix_sub_duration", NULL}; -static const char *const opt_name_canvas_sizes[] = {"canvas_size", NULL}; -static const char *const opt_name_pass[] = {"pass", NULL}; -static const char *const opt_name_passlogfiles[] = {"passlogfile", NULL}; -static const char *const opt_name_max_muxing_queue_size[] = {"max_muxing_queue_size", NULL}; -static const char *const opt_name_muxing_queue_data_threshold[] = {"muxing_queue_data_threshold", NULL}; -static const char *const opt_name_guess_layout_max[] = {"guess_layout_max", NULL}; -static const char *const opt_name_apad[] = {"apad", NULL}; -static const char *const opt_name_discard[] = {"discard", NULL}; -static const char *const opt_name_disposition[] = {"disposition", NULL}; -static const char *const opt_name_time_bases[] = {"time_base", NULL}; -static const char *const opt_name_enc_time_bases[] = {"enc_time_base", NULL}; -static const char *const opt_name_bits_per_raw_sample[] = {"bits_per_raw_sample", NULL}; - -#define WARN_MULTIPLE_OPT_USAGE(name, type, so, st)\ -{\ - char namestr[128] = "";\ - const char *spec = so->specifier && so->specifier[0] ? so->specifier : "";\ - for (i = 0; opt_name_##name[i]; i++)\ - av_strlcatf(namestr, sizeof(namestr), "-%s%s", opt_name_##name[i], opt_name_##name[i+1] ? (opt_name_##name[i+2] ? ", " : " or ") : "");\ - av_log(NULL, AV_LOG_WARNING, "Multiple %s options specified for stream %d, only the last option '-%s%s%s "SPECIFIER_OPT_FMT_##type"' will be used.\n",\ - namestr, st->index, opt_name_##name[0], spec[0] ? ":" : "", spec, so->u.type);\ -} - -#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\ -{\ - int i, ret, matches = 0;\ - SpecifierOpt *so;\ - for (i = 0; i < o->nb_ ## name; i++) {\ - char *spec = o->name[i].specifier;\ - if ((ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\ - outvar = o->name[i].u.type;\ - so = &o->name[i];\ - matches++;\ - } else if (ret < 0)\ - exit_program(1);\ - }\ - if (matches > 1)\ - WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\ -} - -#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\ -{\ - int i;\ - for (i = 0; i < o->nb_ ## name; i++) {\ - char *spec = o->name[i].specifier;\ - if (!strcmp(spec, mediatype))\ - outvar = o->name[i].u.type;\ - }\ -} +const char *const opt_name_codec_names[] = {"c", "codec", "acodec", "vcodec", "scodec", "dcodec", NULL}; +const char *const opt_name_frame_rates[] = {"r", NULL}; +const char *const opt_name_codec_tags[] = {"tag", "atag", "vtag", "stag", NULL}; +const char *const opt_name_top_field_first[] = {"top", NULL}; __thread HWDevice *filter_hw_device; @@ -187,8 +109,6 @@ __thread float audio_drift_threshold = 0.1; __thread float dts_delta_threshold = 10; __thread float dts_error_threshold = 3600*30; -__thread int audio_volume = 256; -__thread int audio_sync_method = 0; __thread enum VideoSyncMethod video_sync_method = VSYNC_AUTO; __thread float frame_drop_threshold = 0; __thread int do_benchmark = 0; @@ -214,12 +134,12 @@ __thread int64_t stats_period = 500000; __thread int file_overwrite = 0; __thread int no_file_overwrite = 0; +#if FFMPEG_OPT_PSNR __thread int do_psnr = 0; -__thread int input_stream_potentially_available = 0; +#endif __thread int ignore_unknown_streams = 0; __thread int copy_unknown_streams = 0; __thread int recast_media = 0; -__thread int find_stream_info = 1; extern __thread OptionDef *ffmpeg_options; @@ -250,7 +170,9 @@ void uninit_options(OptionsContext *o) for (i = 0; i < o->nb_stream_maps; i++) av_freep(&o->stream_maps[i].linklabel); av_freep(&o->stream_maps); +#if FFMPEG_OPT_MAP_CHANNEL av_freep(&o->audio_channel_maps); +#endif av_freep(&o->streamid_map); av_freep(&o->attachments); } @@ -264,11 +186,13 @@ void init_options(OptionsContext *o) o->start_time = AV_NOPTS_VALUE; o->start_time_eof = AV_NOPTS_VALUE; o->recording_time = INT64_MAX; - o->limit_filesize = UINT64_MAX; + o->limit_filesize = INT64_MAX; o->chapters_input_file = INT_MAX; o->accurate_seek = 1; o->thread_queue_size = -1; o->input_sync_ref = -1; + o->find_stream_info = 1; + o->shortest_buf_duration = 10.f; } int show_hwaccels(void *optctx, const char *opt, const char *arg) @@ -284,12 +208,12 @@ int show_hwaccels(void *optctx, const char *opt, const char *arg) } /* return a copy of the input with the stream specifiers removed from the keys */ -AVDictionary *strip_specifiers(AVDictionary *dict) +AVDictionary *strip_specifiers(const AVDictionary *dict) { const AVDictionaryEntry *e = NULL; AVDictionary *ret = NULL; - while ((e = av_dict_get(dict, "", e, AV_DICT_IGNORE_SUFFIX))) { + while ((e = av_dict_iterate(dict, e))) { char *p = strchr(e->key, ':'); if (p) @@ -321,6 +245,44 @@ int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_id return 0; } +/* Correct input file start times based on enabled streams */ +void correct_input_start_times(void) +{ + for (int i = 0; i < nb_input_files; i++) { + InputFile *ifile = input_files[i]; + AVFormatContext *is = ifile->ctx; + int64_t new_start_time = INT64_MAX, diff, abs_start_seek; + + ifile->start_time_effective = is->start_time; + + if (is->start_time == AV_NOPTS_VALUE || + !(is->iformat->flags & AVFMT_TS_DISCONT)) + continue; + + for (int j = 0; j < is->nb_streams; j++) { + AVStream *st = is->streams[j]; + if(st->discard == AVDISCARD_ALL || st->start_time == AV_NOPTS_VALUE) + continue; + new_start_time = FFMIN(new_start_time, av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q)); + } + + diff = new_start_time - is->start_time; + if (diff) { + av_log(NULL, AV_LOG_VERBOSE, "Correcting start time of Input #%d by %"PRId64" us.\n", i, diff); + ifile->start_time_effective = new_start_time; + if (copy_ts && start_at_zero) + ifile->ts_offset = -new_start_time; + else if (!copy_ts) { + abs_start_seek = is->start_time + ((ifile->start_time != AV_NOPTS_VALUE) ? ifile->start_time : 0); + ifile->ts_offset = abs_start_seek > new_start_time ? -abs_start_seek : -new_start_time; + } else if (copy_ts) + ifile->ts_offset = 0; + + ifile->ts_offset += ifile->input_ts_offset; + } + } +} + int apply_sync_offsets(void) { for (int i = 0; i < nb_input_files; i++) { @@ -349,9 +311,9 @@ int apply_sync_offsets(void) if (self->ctx->start_time_realtime != AV_NOPTS_VALUE && ref->ctx->start_time_realtime != AV_NOPTS_VALUE) { self_start_time = self->ctx->start_time_realtime; ref_start_time = ref->ctx->start_time_realtime; - } else if (self->ctx->start_time != AV_NOPTS_VALUE && ref->ctx->start_time != AV_NOPTS_VALUE) { - self_start_time = self->ctx->start_time; - ref_start_time = ref->ctx->start_time; + } else if (self->start_time_effective != AV_NOPTS_VALUE && ref->start_time_effective != AV_NOPTS_VALUE) { + self_start_time = self->start_time_effective; + ref_start_time = ref->start_time_effective; } else { start_times_set = 0; } @@ -383,7 +345,7 @@ int opt_filter_threads(void *optctx, const char *opt, const char *arg) int opt_abort_on(void *optctx, const char *opt, const char *arg) { static const AVOption opts[] = { - { "abort_on" , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, (double)INT64_MAX, .unit = "flags" }, + { "abort_on" , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, (double)INT64_MAX, .unit = "flags" }, { "empty_output" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = ABORT_ON_FLAG_EMPTY_OUTPUT }, .unit = "flags" }, { "empty_output_stream", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM }, .unit = "flags" }, { NULL }, @@ -443,9 +405,10 @@ int opt_map(void *optctx, const char *opt, const char *arg) OptionsContext *o = optctx; StreamMap *m = NULL; int i, negative = 0, file_idx, disabled = 0; - int sync_file_idx = -1, sync_stream_idx = 0; - char *p, *sync; - char *map; +#if FFMPEG_OPT_MAP_SYNC + char *sync; +#endif + char *map, *p; char *allow_unused; if (*arg == '-') { @@ -456,33 +419,13 @@ int opt_map(void *optctx, const char *opt, const char *arg) if (!map) return AVERROR(ENOMEM); +#if FFMPEG_OPT_MAP_SYNC /* parse sync stream first, just pick first matching stream */ if ((sync = strchr(map, ','))) { *sync = 0; - sync_file_idx = strtol(sync + 1, &sync, 0); - if (sync_file_idx >= nb_input_files || sync_file_idx < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid sync file index: %d.\n", sync_file_idx); - exit_program(1); - } - if (*sync) - sync++; - for (i = 0; i < input_files[sync_file_idx]->nb_streams; i++) - if (check_stream_specifier(input_files[sync_file_idx]->ctx, - input_files[sync_file_idx]->ctx->streams[i], sync) == 1) { - sync_stream_idx = i; - break; - } - if (i == input_files[sync_file_idx]->nb_streams) { - av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s does not " - "match any streams.\n", arg); - exit_program(1); - } - if (input_streams[input_files[sync_file_idx]->ist_index + sync_stream_idx]->user_set_discard == AVDISCARD_ALL) { - av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s matches a disabled input " - "stream.\n", arg); - exit_program(1); - } + av_log(NULL, AV_LOG_WARNING, "Specifying a sync stream is deprecated and has no effect\n"); } +#endif if (map[0] == '[') { @@ -518,7 +461,7 @@ int opt_map(void *optctx, const char *opt, const char *arg) if (check_stream_specifier(input_files[file_idx]->ctx, input_files[file_idx]->ctx->streams[i], *p == ':' ? p + 1 : p) <= 0) continue; - if (input_streams[input_files[file_idx]->ist_index + i]->user_set_discard == AVDISCARD_ALL) { + if (input_files[file_idx]->streams[i]->user_set_discard == AVDISCARD_ALL) { disabled = 1; continue; } @@ -527,14 +470,6 @@ int opt_map(void *optctx, const char *opt, const char *arg) m->file_index = file_idx; m->stream_index = i; - - if (sync_file_idx >= 0) { - m->sync_file_index = sync_file_idx; - m->sync_stream_index = sync_stream_idx; - } else { - m->sync_file_index = file_idx; - m->sync_stream_index = i; - } } } @@ -564,6 +499,7 @@ int opt_attach(void *optctx, const char *opt, const char *arg) return 0; } +#if FFMPEG_OPT_MAP_CHANNEL int opt_map_channel(void *optctx, const char *opt, const char *arg) { OptionsContext *o = optctx; @@ -572,6 +508,12 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) AudioChannelMap *m; char *allow_unused; char *mapchan; + + av_log(NULL, AV_LOG_WARNING, + "The -%s option is deprecated and will be removed. " + "It can be replaced by the 'pan' filter, or in some cases by " + "combinations of 'channelsplit', 'channelmap', 'amerge' filters.\n", opt); + mapchan = av_strdup(arg); if (!mapchan) return AVERROR(ENOMEM); @@ -625,7 +567,7 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) if ((allow_unused = strchr(mapchan, '?'))) *allow_unused = 0; if (m->channel_idx < 0 || m->channel_idx >= st->codecpar->ch_layout.nb_channels || - input_streams[input_files[m->file_idx]->ist_index + m->stream_idx]->user_set_discard == AVDISCARD_ALL) { + input_files[m->file_idx]->streams[m->stream_idx]->user_set_discard == AVDISCARD_ALL) { if (allow_unused) { av_log(NULL, AV_LOG_VERBOSE, "mapchan: invalid audio channel #%d.%d.%d\n", m->file_idx, m->stream_idx, m->channel_idx); @@ -640,6 +582,7 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) av_free(mapchan); return 0; } +#endif int opt_sdp_file(void *optctx, const char *opt, const char *arg) { @@ -709,130 +652,6 @@ int opt_filter_hw_device(void *optctx, const char *opt, const char *arg) return 0; } -/** - * Parse a metadata specifier passed as 'arg' parameter. - * @param arg metadata string to parse - * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram) - * @param index for type c/p, chapter/program index is written here - * @param stream_spec for type s, the stream specifier is written here - */ -void parse_meta_type(char *arg, char *type, int *index, const char **stream_spec) -{ - if (*arg) { - *type = *arg; - switch (*arg) { - case 'g': - break; - case 's': - if (*(++arg) && *arg != ':') { - av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg); - exit_program(1); - } - *stream_spec = *arg == ':' ? arg + 1 : ""; - break; - case 'c': - case 'p': - if (*(++arg) == ':') - *index = strtol(++arg, NULL, 0); - break; - default: - av_log(NULL, AV_LOG_FATAL, "Invalid metadata type %c.\n", *arg); - exit_program(1); - } - } else - *type = 'g'; -} - -int fftools_copy_metadata(char *outspec, char *inspec, AVFormatContext *oc, AVFormatContext *ic, OptionsContext *o) -{ - AVDictionary **meta_in = NULL; - AVDictionary **meta_out = NULL; - int i, ret = 0; - char type_in, type_out; - const char *istream_spec = NULL, *ostream_spec = NULL; - int idx_in = 0, idx_out = 0; - - parse_meta_type(inspec, &type_in, &idx_in, &istream_spec); - parse_meta_type(outspec, &type_out, &idx_out, &ostream_spec); - - if (!ic) { - if (type_out == 'g' || !*outspec) - o->metadata_global_manual = 1; - if (type_out == 's' || !*outspec) - o->metadata_streams_manual = 1; - if (type_out == 'c' || !*outspec) - o->metadata_chapters_manual = 1; - return 0; - } - - if (type_in == 'g' || type_out == 'g') - o->metadata_global_manual = 1; - if (type_in == 's' || type_out == 's') - o->metadata_streams_manual = 1; - if (type_in == 'c' || type_out == 'c') - o->metadata_chapters_manual = 1; - - /* ic is NULL when just disabling automatic mappings */ - if (!ic) - return 0; - -#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ - if ((index) < 0 || (index) >= (nb_elems)) {\ - av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\ - (desc), (index));\ - exit_program(1);\ - } - -#define SET_DICT(type, meta, context, index)\ - switch (type) {\ - case 'g':\ - meta = &context->metadata;\ - break;\ - case 'c':\ - METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\ - meta = &context->chapters[index]->metadata;\ - break;\ - case 'p':\ - METADATA_CHECK_INDEX(index, context->nb_programs, "program")\ - meta = &context->programs[index]->metadata;\ - break;\ - case 's':\ - break; /* handled separately below */ \ - default: av_assert0(0);\ - }\ - - SET_DICT(type_in, meta_in, ic, idx_in); - SET_DICT(type_out, meta_out, oc, idx_out); - - /* for input streams choose first matching stream */ - if (type_in == 's') { - for (i = 0; i < ic->nb_streams; i++) { - if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) { - meta_in = &ic->streams[i]->metadata; - break; - } else if (ret < 0) - exit_program(1); - } - if (!meta_in) { - av_log(NULL, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec); - exit_program(1); - } - } - - if (type_out == 's') { - for (i = 0; i < oc->nb_streams; i++) { - if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) { - meta_out = &oc->streams[i]->metadata; - av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); - } else if (ret < 0) - exit_program(1); - } - } else - av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); - - return 0; -} - int opt_recording_timestamp(void *optctx, const char *opt, const char *arg) { OptionsContext *o = optctx; @@ -848,7 +667,8 @@ int opt_recording_timestamp(void *optctx, const char *opt, const char *arg) return 0; } -const AVCodec *find_codec_or_die(const char *name, enum AVMediaType type, int encoder) +const AVCodec *find_codec_or_die(void *logctx, const char *name, + enum AVMediaType type, int encoder) { const AVCodecDescriptor *desc; const char *codec_string = encoder ? "encoder" : "decoder"; @@ -862,247 +682,21 @@ const AVCodec *find_codec_or_die(const char *name, enum AVMediaType type, int en codec = encoder ? avcodec_find_encoder(desc->id) : avcodec_find_decoder(desc->id); if (codec) - av_log(NULL, AV_LOG_VERBOSE, "Matched %s '%s' for codec '%s'.\n", + av_log(logctx, AV_LOG_VERBOSE, "Matched %s '%s' for codec '%s'.\n", codec_string, codec->name, desc->name); } if (!codec) { - av_log(NULL, AV_LOG_FATAL, "Unknown %s '%s'\n", codec_string, name); + av_log(logctx, AV_LOG_FATAL, "Unknown %s '%s'\n", codec_string, name); exit_program(1); } if (codec->type != type && !recast_media) { - av_log(NULL, AV_LOG_FATAL, "Invalid %s type '%s'\n", codec_string, name); + av_log(logctx, AV_LOG_FATAL, "Invalid %s type '%s'\n", codec_string, name); exit_program(1); } return codec; } -const AVCodec *choose_decoder(OptionsContext *o, AVFormatContext *s, AVStream *st) -{ - char *codec_name = NULL; - - MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st); - if (codec_name) { - const AVCodec *codec = find_codec_or_die(codec_name, st->codecpar->codec_type, 0); - st->codecpar->codec_id = codec->id; - if (recast_media && st->codecpar->codec_type != codec->type) - st->codecpar->codec_type = codec->type; - return codec; - } else - return avcodec_find_decoder(st->codecpar->codec_id); -} - -/* Add all the streams from the given input file to the global - * list of input streams. */ -void add_input_streams(OptionsContext *o, AVFormatContext *ic) -{ - int i, ret; - - for (i = 0; i < ic->nb_streams; i++) { - AVStream *st = ic->streams[i]; - AVCodecParameters *par = st->codecpar; - InputStream *ist; - char *framerate = NULL, *hwaccel_device = NULL; - const char *hwaccel = NULL; - char *hwaccel_output_format = NULL; - char *codec_tag = NULL; - char *next; - char *discard_str = NULL; - const AVClass *cc = avcodec_get_class(); - const AVOption *discard_opt = av_opt_find(&cc, "skip_frame", NULL, - 0, AV_OPT_SEARCH_FAKE_OBJ); - - ist = ALLOC_ARRAY_ELEM(input_streams, nb_input_streams); - ist->st = st; - ist->file_index = nb_input_files; - ist->discard = 1; - st->discard = AVDISCARD_ALL; - ist->nb_samples = 0; - ist->first_dts = AV_NOPTS_VALUE; - ist->min_pts = INT64_MAX; - ist->max_pts = INT64_MIN; - - ist->ts_scale = 1.0; - MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st); - - ist->autorotate = 1; - MATCH_PER_STREAM_OPT(autorotate, i, ist->autorotate, ic, st); - - MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st); - if (codec_tag) { - uint32_t tag = strtol(codec_tag, &next, 0); - if (*next) - tag = AV_RL32(codec_tag); - st->codecpar->codec_tag = tag; - } - - ist->dec = choose_decoder(o, ic, st); - ist->decoder_opts = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ic, st, ist->dec); - - ist->reinit_filters = -1; - MATCH_PER_STREAM_OPT(reinit_filters, i, ist->reinit_filters, ic, st); - - MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st); - ist->user_set_discard = AVDISCARD_NONE; - - if ((o->video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) || - (o->audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || - (o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) || - (o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA)) - ist->user_set_discard = AVDISCARD_ALL; - - if (discard_str && av_opt_eval_int(&cc, discard_opt, discard_str, &ist->user_set_discard) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing discard %s.\n", - discard_str); - exit_program(1); - } - - ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE; - ist->prev_pkt_pts = AV_NOPTS_VALUE; - - ist->dec_ctx = avcodec_alloc_context3(ist->dec); - if (!ist->dec_ctx) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the decoder context.\n"); - exit_program(1); - } - - ret = avcodec_parameters_to_context(ist->dec_ctx, par); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); - exit_program(1); - } - - ist->decoded_frame = av_frame_alloc(); - if (!ist->decoded_frame) - exit_program(1); - - ist->pkt = av_packet_alloc(); - if (!ist->pkt) - exit_program(1); - - if (o->bitexact) - ist->dec_ctx->flags |= AV_CODEC_FLAG_BITEXACT; - - switch (par->codec_type) { - case AVMEDIA_TYPE_VIDEO: - if(!ist->dec) - ist->dec = avcodec_find_decoder(par->codec_id); - - // avformat_find_stream_info() doesn't set this for us anymore. - ist->dec_ctx->framerate = st->avg_frame_rate; - - MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st); - if (framerate && av_parse_video_rate(&ist->framerate, - framerate) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing framerate %s.\n", - framerate); - exit_program(1); - } - - ist->top_field_first = -1; - MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st); - - MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st); - MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, - hwaccel_output_format, ic, st); - - if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "cuvid")) { - av_log(NULL, AV_LOG_WARNING, - "WARNING: defaulting hwaccel_output_format to cuda for compatibility " - "with old commandlines. This behaviour is DEPRECATED and will be removed " - "in the future. Please explicitly set \"-hwaccel_output_format cuda\".\n"); - ist->hwaccel_output_format = AV_PIX_FMT_CUDA; - } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "qsv")) { - av_log(NULL, AV_LOG_WARNING, - "WARNING: defaulting hwaccel_output_format to qsv for compatibility " - "with old commandlines. This behaviour is DEPRECATED and will be removed " - "in the future. Please explicitly set \"-hwaccel_output_format qsv\".\n"); - ist->hwaccel_output_format = AV_PIX_FMT_QSV; - } else if (hwaccel_output_format) { - ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format); - if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output " - "format: %s", hwaccel_output_format); - } - } else { - ist->hwaccel_output_format = AV_PIX_FMT_NONE; - } - - if (hwaccel) { - // The NVDEC hwaccels use a CUDA device, so remap the name here. - if (!strcmp(hwaccel, "nvdec") || !strcmp(hwaccel, "cuvid")) - hwaccel = "cuda"; - - if (!strcmp(hwaccel, "none")) - ist->hwaccel_id = HWACCEL_NONE; - else if (!strcmp(hwaccel, "auto")) - ist->hwaccel_id = HWACCEL_AUTO; - else { - enum AVHWDeviceType type = av_hwdevice_find_type_by_name(hwaccel); - if (type != AV_HWDEVICE_TYPE_NONE) { - ist->hwaccel_id = HWACCEL_GENERIC; - ist->hwaccel_device_type = type; - } - - if (!ist->hwaccel_id) { - av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n", - hwaccel); - av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: "); - type = AV_HWDEVICE_TYPE_NONE; - while ((type = av_hwdevice_iterate_types(type)) != - AV_HWDEVICE_TYPE_NONE) - av_log(NULL, AV_LOG_FATAL, "%s ", - av_hwdevice_get_type_name(type)); - av_log(NULL, AV_LOG_FATAL, "\n"); - exit_program(1); - } - } - } - - MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st); - if (hwaccel_device) { - ist->hwaccel_device = av_strdup(hwaccel_device); - if (!ist->hwaccel_device) - exit_program(1); - } - - ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE; - - break; - case AVMEDIA_TYPE_AUDIO: - ist->guess_layout_max = INT_MAX; - MATCH_PER_STREAM_OPT(guess_layout_max, i, ist->guess_layout_max, ic, st); - guess_input_channel_layout(ist); - break; - case AVMEDIA_TYPE_DATA: - case AVMEDIA_TYPE_SUBTITLE: { - char *canvas_size = NULL; - if(!ist->dec) - ist->dec = avcodec_find_decoder(par->codec_id); - MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st); - MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st); - if (canvas_size && - av_parse_video_size(&ist->dec_ctx->width, &ist->dec_ctx->height, canvas_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid canvas size: %s.\n", canvas_size); - exit_program(1); - } - break; - } - case AVMEDIA_TYPE_ATTACHMENT: - case AVMEDIA_TYPE_UNKNOWN: - break; - default: - abort(); - } - - ret = avcodec_parameters_from_context(par, ist->dec_ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); - exit_program(1); - } - } -} - void assert_file_overwrite(const char *filename) { const char *proto_name = avio_find_protocol_name(filename); @@ -1145,2006 +739,134 @@ void assert_file_overwrite(const char *filename) } } -void dump_attachment(AVStream *st, const char *filename) +/* read file contents into a string */ +char *file_read(const char *filename) { - int ret; - AVIOContext *out = NULL; - const AVDictionaryEntry *e; + AVIOContext *pb = NULL; + int ret = avio_open(&pb, filename, AVIO_FLAG_READ); + AVBPrint bprint; + char *str; - if (!st->codecpar->extradata_size) { - av_log(NULL, AV_LOG_WARNING, "No extradata to dump in stream #%d:%d.\n", - nb_input_files - 1, st->index); - return; + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename); + return NULL; } - if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) - filename = e->value; - if (!*filename) { - av_log(NULL, AV_LOG_FATAL, "No filename specified and no 'filename' tag" - "in stream #%d:%d.\n", nb_input_files - 1, st->index); - exit_program(1); + + av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); + ret = avio_read_to_bprint(pb, &bprint, SIZE_MAX); + avio_closep(&pb); + if (ret < 0) { + av_bprint_finalize(&bprint, NULL); + return NULL; } + ret = av_bprint_finalize(&bprint, &str); + if (ret < 0) + return NULL; + return str; +} - assert_file_overwrite(filename); +/* arg format is "output-stream-index:streamid-value". */ +int opt_streamid(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + int idx; + char *p; + char idx_str[16]; - if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &int_cb, NULL)) < 0) { - av_log(NULL, AV_LOG_FATAL, "Could not open file %s for writing.\n", - filename); + av_strlcpy(idx_str, arg, sizeof(idx_str)); + p = strchr(idx_str, ':'); + if (!p) { + av_log(NULL, AV_LOG_FATAL, + "Invalid value '%s' for option '%s', required syntax is 'index:value'\n", + arg, opt); exit_program(1); } - - avio_write(out, st->codecpar->extradata, st->codecpar->extradata_size); - avio_flush(out); - avio_close(out); + *p++ = '\0'; + idx = parse_number_or_die(opt, idx_str, OPT_INT, 0, MAX_STREAMS-1); + o->streamid_map = grow_array(o->streamid_map, sizeof(*o->streamid_map), &o->nb_streamid_map, idx+1); + o->streamid_map[idx] = parse_number_or_die(opt, p, OPT_INT, 0, INT_MAX); + return 0; } -int open_input_file(OptionsContext *o, const char *filename) +int init_complex_filters(void) { - InputFile *f; - AVFormatContext *ic; - const AVInputFormat *file_iformat = NULL; - int err, i, ret; - int64_t timestamp; - AVDictionary *unused_opts = NULL; - const AVDictionaryEntry *e = NULL; - char * video_codec_name = NULL; - char * audio_codec_name = NULL; - char *subtitle_codec_name = NULL; - char * data_codec_name = NULL; - int scan_all_pmts_set = 0; - - if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) { - o->stop_time = INT64_MAX; - av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); - } - - if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) { - int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; - if (o->stop_time <= start_time) { - av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); - exit_program(1); - } else { - o->recording_time = o->stop_time - start_time; - } - } + int i, ret = 0; - if (o->format) { - if (!(file_iformat = av_find_input_format(o->format))) { - av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format); - exit_program(1); - } + for (i = 0; i < nb_filtergraphs; i++) { + ret = init_complex_filtergraph(filtergraphs[i]); + if (ret < 0) + return ret; } + return 0; +} - if (!strcmp(filename, "-")) - filename = "pipe:"; - - stdin_interaction &= strncmp(filename, "pipe:", 5) && - strcmp(filename, "/dev/stdin"); +int opt_target(void *optctx, const char *opt, const char *arg) +{ + const OptionDef *options = ffmpeg_options; + OptionsContext *o = optctx; + enum { PAL, NTSC, FILM, UNKNOWN } norm = UNKNOWN; + static const char *const frame_rates[] = { "25", "30000/1001", "24000/1001" }; - /* get default parameters from command line */ - ic = avformat_alloc_context(); - if (!ic) { - print_error(filename, AVERROR(ENOMEM)); - exit_program(1); - } - if (o->nb_audio_sample_rate) { - av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0); - } - if (o->nb_audio_channels) { - const AVClass *priv_class; - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "ch_layout", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - char buf[32]; - snprintf(buf, sizeof(buf), "%dC", o->audio_channels[o->nb_audio_channels - 1].u.i); - av_dict_set(&o->g->format_opts, "ch_layout", buf, 0); - } - } - if (o->nb_audio_ch_layouts) { - const AVClass *priv_class; - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "ch_layout", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - av_dict_set(&o->g->format_opts, "ch_layout", o->audio_ch_layouts[o->nb_audio_ch_layouts - 1].u.str, 0); - } - } - if (o->nb_frame_rates) { - const AVClass *priv_class; - /* set the format-level framerate option; - * this is important for video grabbers, e.g. x11 */ - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "framerate", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - av_dict_set(&o->g->format_opts, "framerate", - o->frame_rates[o->nb_frame_rates - 1].u.str, 0); + if (!strncmp(arg, "pal-", 4)) { + norm = PAL; + arg += 4; + } else if (!strncmp(arg, "ntsc-", 5)) { + norm = NTSC; + arg += 5; + } else if (!strncmp(arg, "film-", 5)) { + norm = FILM; + arg += 5; + } else { + /* Try to determine PAL/NTSC by peeking in the input files */ + if (nb_input_files) { + int i, j; + for (j = 0; j < nb_input_files; j++) { + for (i = 0; i < input_files[j]->nb_streams; i++) { + AVStream *st = input_files[j]->ctx->streams[i]; + int64_t fr; + if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + fr = st->time_base.den * 1000LL / st->time_base.num; + if (fr == 25000) { + norm = PAL; + break; + } else if ((fr == 29970) || (fr == 23976)) { + norm = NTSC; + break; + } + } + if (norm != UNKNOWN) + break; + } } + if (norm != UNKNOWN) + av_log(NULL, AV_LOG_INFO, "Assuming %s for target.\n", norm == PAL ? "PAL" : "NTSC"); } - if (o->nb_frame_sizes) { - av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0); - } - if (o->nb_frame_pix_fmts) - av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0); - - MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v"); - MATCH_PER_TYPE_OPT(codec_names, str, audio_codec_name, ic, "a"); - MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s"); - MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d"); - - if (video_codec_name) - ic->video_codec = find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0); - if (audio_codec_name) - ic->audio_codec = find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0); - if (subtitle_codec_name) - ic->subtitle_codec = find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0); - if (data_codec_name) - ic->data_codec = find_codec_or_die(data_codec_name , AVMEDIA_TYPE_DATA , 0); - - ic->video_codec_id = video_codec_name ? ic->video_codec->id : AV_CODEC_ID_NONE; - ic->audio_codec_id = audio_codec_name ? ic->audio_codec->id : AV_CODEC_ID_NONE; - ic->subtitle_codec_id = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE; - ic->data_codec_id = data_codec_name ? ic->data_codec->id : AV_CODEC_ID_NONE; - - ic->flags |= AVFMT_FLAG_NONBLOCK; - if (o->bitexact) - ic->flags |= AVFMT_FLAG_BITEXACT; - ic->interrupt_callback = int_cb; - - if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { - av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); - scan_all_pmts_set = 1; - } - /* open the input file with generic avformat function */ - err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); - if (err < 0) { - print_error(filename, err); - if (err == AVERROR_PROTOCOL_NOT_FOUND) - av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename); + + if (norm == UNKNOWN) { + av_log(NULL, AV_LOG_FATAL, "Could not determine norm (PAL/NTSC/NTSC-Film) for target.\n"); + av_log(NULL, AV_LOG_FATAL, "Please prefix target with \"pal-\", \"ntsc-\" or \"film-\",\n"); + av_log(NULL, AV_LOG_FATAL, "or set a framerate with \"-r xxx\".\n"); exit_program(1); } - if (scan_all_pmts_set) - av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); - remove_avoptions(&o->g->format_opts, o->g->codec_opts); - assert_avoptions(o->g->format_opts); - /* apply forced codec ids */ - for (i = 0; i < ic->nb_streams; i++) - choose_decoder(o, ic, ic->streams[i]); - - if (find_stream_info) { - AVDictionary **opts = setup_find_stream_info_opts(ic, o->g->codec_opts); - int orig_nb_streams = ic->nb_streams; + if (!strcmp(arg, "vcd")) { + opt_video_codec(o, "c:v", "mpeg1video"); + opt_audio_codec(o, "c:a", "mp2"); + parse_option(o, "f", "vcd", options); - /* If not enough info to get the stream parameters, we decode the - first frames to get it. (used in mpeg case for example) */ - ret = avformat_find_stream_info(ic, opts); + parse_option(o, "s", norm == PAL ? "352x288" : "352x240", options); + parse_option(o, "r", frame_rates[norm], options); + opt_default(NULL, "g", norm == PAL ? "15" : "18"); - for (i = 0; i < orig_nb_streams; i++) - av_dict_free(&opts[i]); - av_freep(&opts); + opt_default(NULL, "b:v", "1150000"); + opt_default(NULL, "maxrate:v", "1150000"); + opt_default(NULL, "minrate:v", "1150000"); + opt_default(NULL, "bufsize:v", "327680"); // 40*1024*8; - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename); - if (ic->nb_streams == 0) { - avformat_close_input(&ic); - exit_program(1); - } - } - } - - if (o->start_time != AV_NOPTS_VALUE && o->start_time_eof != AV_NOPTS_VALUE) { - av_log(NULL, AV_LOG_WARNING, "Cannot use -ss and -sseof both, using -ss for %s\n", filename); - o->start_time_eof = AV_NOPTS_VALUE; - } - - if (o->start_time_eof != AV_NOPTS_VALUE) { - if (o->start_time_eof >= 0) { - av_log(NULL, AV_LOG_ERROR, "-sseof value must be negative; aborting\n"); - exit_program(1); - } - if (ic->duration > 0) { - o->start_time = o->start_time_eof + ic->duration; - if (o->start_time < 0) { - av_log(NULL, AV_LOG_WARNING, "-sseof value seeks to before start of file %s; ignored\n", filename); - o->start_time = AV_NOPTS_VALUE; - } - } else - av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename); - } - timestamp = (o->start_time == AV_NOPTS_VALUE) ? 0 : o->start_time; - /* add the stream start time */ - if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE) - timestamp += ic->start_time; - - /* if seeking requested, we execute it */ - if (o->start_time != AV_NOPTS_VALUE) { - int64_t seek_timestamp = timestamp; - - if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) { - int dts_heuristic = 0; - for (i=0; inb_streams; i++) { - const AVCodecParameters *par = ic->streams[i]->codecpar; - if (par->video_delay) { - dts_heuristic = 1; - break; - } - } - if (dts_heuristic) { - seek_timestamp -= 3*AV_TIME_BASE / 23; - } - } - ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0); - if (ret < 0) { - av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", - filename, (double)timestamp / AV_TIME_BASE); - } - } - - /* update the current parameters so that they match the one of the input stream */ - add_input_streams(o, ic); - - /* dump the file content */ - av_dump_format(ic, nb_input_files, filename, 0); - - f = ALLOC_ARRAY_ELEM(input_files, nb_input_files); - - f->ctx = ic; - f->ist_index = nb_input_streams - ic->nb_streams; - f->start_time = o->start_time; - f->recording_time = o->recording_time; - f->input_sync_ref = o->input_sync_ref; - f->input_ts_offset = o->input_ts_offset; - f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); - f->nb_streams = ic->nb_streams; - f->rate_emu = o->rate_emu; - f->accurate_seek = o->accurate_seek; - f->loop = o->loop; - f->duration = 0; - f->time_base = (AVRational){ 1, 1 }; - - f->readrate = o->readrate ? o->readrate : 0.0; - if (f->readrate < 0.0f) { - av_log(NULL, AV_LOG_ERROR, "Option -readrate for Input #%d is %0.3f; it must be non-negative.\n", nb_input_files, f->readrate); - exit_program(1); - } - if (f->readrate && f->rate_emu) { - av_log(NULL, AV_LOG_WARNING, "Both -readrate and -re set for Input #%d. Using -readrate %0.3f.\n", nb_input_files, f->readrate); - f->rate_emu = 0; - } - - f->pkt = av_packet_alloc(); - if (!f->pkt) - exit_program(1); -#if HAVE_THREADS - f->thread_queue_size = o->thread_queue_size; -#endif - - /* check if all codec options have been used */ - unused_opts = strip_specifiers(o->g->codec_opts); - for (i = f->ist_index; i < nb_input_streams; i++) { - e = NULL; - while ((e = av_dict_get(input_streams[i]->decoder_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) - av_dict_set(&unused_opts, e->key, NULL, 0); - } - - e = NULL; - while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) { - const AVClass *class = avcodec_get_class(); - const AVOption *option = av_opt_find(&class, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - const AVClass *fclass = avformat_get_class(); - const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - if (!option || foption) - continue; - - - if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) { - av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " - "input file #%d (%s) is not a decoding option.\n", e->key, - option->help ? option->help : "", nb_input_files - 1, - filename); - exit_program(1); - } - - av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " - "input file #%d (%s) has not been used for any stream. The most " - "likely reason is either wrong type (e.g. a video option with " - "no video streams) or that it is a private option of some decoder " - "which was not actually used for any stream.\n", e->key, - option->help ? option->help : "", nb_input_files - 1, filename); - } - av_dict_free(&unused_opts); - - for (i = 0; i < o->nb_dump_attachment; i++) { - int j; - - for (j = 0; j < ic->nb_streams; j++) { - AVStream *st = ic->streams[j]; - - if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1) - dump_attachment(st, o->dump_attachment[i].u.str); - } - } - - input_stream_potentially_available = 1; - - return 0; -} - -char *get_line(AVIOContext *s, AVBPrint *bprint) -{ - char c; - - while ((c = avio_r8(s)) && c != '\n') - av_bprint_chars(bprint, c, 1); - - if (!av_bprint_is_complete(bprint)) { - av_log(NULL, AV_LOG_FATAL, "Could not alloc buffer for reading preset.\n"); - exit_program(1); - } - return bprint->str; -} - -int get_preset_file_2(const char *preset_name, const char *codec_name, AVIOContext **s) -{ - int i, ret = -1; - char filename[1000]; - char *env_avconv_datadir = getenv_utf8("AVCONV_DATADIR"); - char *env_home = getenv_utf8("HOME"); - const char *base[3] = { env_avconv_datadir, - env_home, - AVCONV_DATADIR, - }; - - for (i = 0; i < FF_ARRAY_ELEMS(base) && ret < 0; i++) { - if (!base[i]) - continue; - if (codec_name) { - snprintf(filename, sizeof(filename), "%s%s/%s-%s.avpreset", base[i], - i != 1 ? "" : "/.avconv", codec_name, preset_name); - ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); - } - if (ret < 0) { - snprintf(filename, sizeof(filename), "%s%s/%s.avpreset", base[i], - i != 1 ? "" : "/.avconv", preset_name); - ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); - } - } - freeenv_utf8(env_home); - freeenv_utf8(env_avconv_datadir); - return ret; -} - -int choose_encoder(OptionsContext *o, AVFormatContext *s, OutputStream *ost) -{ - enum AVMediaType type = ost->st->codecpar->codec_type; - char *codec_name = NULL; - - if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) { - MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st); - if (!codec_name) { - ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->url, - NULL, ost->st->codecpar->codec_type); - ost->enc = avcodec_find_encoder(ost->st->codecpar->codec_id); - if (!ost->enc) { - av_log(NULL, AV_LOG_FATAL, "Automatic encoder selection failed for " - "output stream #%d:%d. Default encoder for format %s (codec %s) is " - "probably disabled. Please choose an encoder manually.\n", - ost->file_index, ost->index, s->oformat->name, - avcodec_get_name(ost->st->codecpar->codec_id)); - return AVERROR_ENCODER_NOT_FOUND; - } - } else if (!strcmp(codec_name, "copy")) - ost->stream_copy = 1; - else { - ost->enc = find_codec_or_die(codec_name, ost->st->codecpar->codec_type, 1); - ost->st->codecpar->codec_id = ost->enc->id; - } - ost->encoding_needed = !ost->stream_copy; - } else { - /* no encoding supported for other media types */ - ost->stream_copy = 1; - ost->encoding_needed = 0; - } - - return 0; -} - -OutputStream *new_output_stream(OptionsContext *o, AVFormatContext *oc, enum AVMediaType type, int source_index) -{ - OutputStream *ost; - AVStream *st = avformat_new_stream(oc, NULL); - int idx = oc->nb_streams - 1, ret = 0; - const char *bsfs = NULL, *time_base = NULL; - char *next, *codec_tag = NULL; - double qscale = -1; - int i; - - if (!st) { - av_log(NULL, AV_LOG_FATAL, "Could not alloc stream.\n"); - exit_program(1); - } - - if (oc->nb_streams - 1 < o->nb_streamid_map) - st->id = o->streamid_map[oc->nb_streams - 1]; - - ost = ALLOC_ARRAY_ELEM(output_streams, nb_output_streams); - - ost->file_index = nb_output_files - 1; - ost->index = idx; - ost->st = st; - ost->forced_kf_ref_pts = AV_NOPTS_VALUE; - st->codecpar->codec_type = type; - - ret = choose_encoder(o, oc, ost); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "Error selecting an encoder for stream " - "%d:%d\n", ost->file_index, ost->index); - exit_program(1); - } - - ost->enc_ctx = avcodec_alloc_context3(ost->enc); - if (!ost->enc_ctx) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding context.\n"); - exit_program(1); - } - ost->enc_ctx->codec_type = type; - - ost->ref_par = avcodec_parameters_alloc(); - if (!ost->ref_par) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding parameters.\n"); - exit_program(1); - } - - ost->filtered_frame = av_frame_alloc(); - if (!ost->filtered_frame) - exit_program(1); - - ost->pkt = av_packet_alloc(); - if (!ost->pkt) - exit_program(1); - - if (ost->enc) { - AVIOContext *s = NULL; - char *buf = NULL, *arg = NULL, *preset = NULL; - - ost->encoder_opts = filter_codec_opts(o->g->codec_opts, ost->enc->id, oc, st, ost->enc); - - MATCH_PER_STREAM_OPT(presets, str, preset, oc, st); - ost->autoscale = 1; - MATCH_PER_STREAM_OPT(autoscale, i, ost->autoscale, oc, st); - if (preset && (!(ret = get_preset_file_2(preset, ost->enc->name, &s)))) { - AVBPrint bprint; - av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); - do { - av_bprint_clear(&bprint); - buf = get_line(s, &bprint); - if (!buf[0] || buf[0] == '#') - continue; - if (!(arg = strchr(buf, '='))) { - av_log(NULL, AV_LOG_FATAL, "Invalid line found in the preset file.\n"); - exit_program(1); - } - *arg++ = 0; - av_dict_set(&ost->encoder_opts, buf, arg, AV_DICT_DONT_OVERWRITE); - } while (!s->eof_reached); - av_bprint_finalize(&bprint, NULL); - avio_closep(&s); - } - if (ret) { - av_log(NULL, AV_LOG_FATAL, - "Preset %s specified for stream %d:%d, but could not be opened.\n", - preset, ost->file_index, ost->index); - exit_program(1); - } - } else { - ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL); - } - - - if (o->bitexact) - ost->enc_ctx->flags |= AV_CODEC_FLAG_BITEXACT; - - MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st); - if (time_base) { - AVRational q; - if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || - q.num <= 0 || q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); - exit_program(1); - } - st->time_base = q; - } - - MATCH_PER_STREAM_OPT(enc_time_bases, str, time_base, oc, st); - if (time_base) { - AVRational q; - if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || - q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); - exit_program(1); - } - ost->enc_timebase = q; - } - - ost->max_frames = INT64_MAX; - MATCH_PER_STREAM_OPT(max_frames, i64, ost->max_frames, oc, st); - for (i = 0; inb_max_frames; i++) { - char *p = o->max_frames[i].specifier; - if (!*p && type != AVMEDIA_TYPE_VIDEO) { - av_log(NULL, AV_LOG_WARNING, "Applying unspecific -frames to non video streams, maybe you meant -vframes ?\n"); - break; - } - } - - ost->copy_prior_start = -1; - MATCH_PER_STREAM_OPT(copy_prior_start, i, ost->copy_prior_start, oc ,st); - - MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st); - if (bsfs && *bsfs) { - ret = av_bsf_list_parse_str(bsfs, &ost->bsf_ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing bitstream filter sequence '%s': %s\n", bsfs, av_err2str(ret)); - exit_program(1); - } - } - - MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st); - if (codec_tag) { - uint32_t tag = strtol(codec_tag, &next, 0); - if (*next) - tag = AV_RL32(codec_tag); - ost->st->codecpar->codec_tag = - ost->enc_ctx->codec_tag = tag; - } - - MATCH_PER_STREAM_OPT(qscale, dbl, qscale, oc, st); - if (qscale >= 0) { - ost->enc_ctx->flags |= AV_CODEC_FLAG_QSCALE; - ost->enc_ctx->global_quality = FF_QP2LAMBDA * qscale; - } - - MATCH_PER_STREAM_OPT(disposition, str, ost->disposition, oc, st); - ost->disposition = av_strdup(ost->disposition); - - ost->max_muxing_queue_size = 128; - MATCH_PER_STREAM_OPT(max_muxing_queue_size, i, ost->max_muxing_queue_size, oc, st); - - ost->muxing_queue_data_size = 0; - - ost->muxing_queue_data_threshold = 50*1024*1024; - MATCH_PER_STREAM_OPT(muxing_queue_data_threshold, i, ost->muxing_queue_data_threshold, oc, st); - - MATCH_PER_STREAM_OPT(bits_per_raw_sample, i, ost->bits_per_raw_sample, - oc, st); - - if (oc->oformat->flags & AVFMT_GLOBALHEADER) - ost->enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - - av_dict_copy(&ost->sws_dict, o->g->sws_dict, 0); - - av_dict_copy(&ost->swr_opts, o->g->swr_opts, 0); - if (ost->enc && av_get_exact_bits_per_sample(ost->enc->id) == 24) - av_dict_set(&ost->swr_opts, "output_sample_bits", "24", 0); - - ost->source_index = source_index; - if (source_index >= 0) { - ost->sync_ist = input_streams[source_index]; - input_streams[source_index]->discard = 0; - input_streams[source_index]->st->discard = input_streams[source_index]->user_set_discard; - } - ost->last_mux_dts = AV_NOPTS_VALUE; - - ost->muxing_queue = av_fifo_alloc2(8, sizeof(AVPacket*), 0); - if (!ost->muxing_queue) - exit_program(1); - - MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, - ost->copy_initial_nonkeyframes, oc, st); - - return ost; -} - -void parse_matrix_coeffs(uint16_t *dest, const char *str) -{ - int i; - const char *p = str; - for (i = 0;; i++) { - dest[i] = atoi(p); - if (i == 63) - break; - p = strchr(p, ','); - if (!p) { - av_log(NULL, AV_LOG_FATAL, "Syntax error in matrix \"%s\" at coeff %d\n", str, i); - exit_program(1); - } - p++; - } -} - -/* read file contents into a string */ -char *fftools_read_file(const char *filename) -{ - AVIOContext *pb = NULL; - int ret = avio_open(&pb, filename, AVIO_FLAG_READ); - AVBPrint bprint; - char *str; - - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename); - return NULL; - } - - av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); - ret = avio_read_to_bprint(pb, &bprint, SIZE_MAX); - avio_closep(&pb); - if (ret < 0) { - av_bprint_finalize(&bprint, NULL); - return NULL; - } - ret = av_bprint_finalize(&bprint, &str); - if (ret < 0) - return NULL; - return str; -} - -char *get_ost_filters(OptionsContext *o, AVFormatContext *oc, - OutputStream *ost) -{ - AVStream *st = ost->st; - - if (ost->filters_script && ost->filters) { - av_log(NULL, AV_LOG_ERROR, "Both -filter and -filter_script set for " - "output stream #%d:%d.\n", nb_output_files, st->index); - exit_program(1); - } - - if (ost->filters_script) - return fftools_read_file(ost->filters_script); - else if (ost->filters) - return av_strdup(ost->filters); - - return av_strdup(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? - "null" : "anull"); -} - -void check_streamcopy_filters(OptionsContext *o, AVFormatContext *oc, - const OutputStream *ost, enum AVMediaType type) -{ - if (ost->filters_script || ost->filters) { - av_log(NULL, AV_LOG_ERROR, - "%s '%s' was defined for %s output stream %d:%d but codec copy was selected.\n" - "Filtering and streamcopy cannot be used together.\n", - ost->filters ? "Filtergraph" : "Filtergraph script", - ost->filters ? ost->filters : ost->filters_script, - av_get_media_type_string(type), ost->file_index, ost->index); - exit_program(1); - } -} - -OutputStream *new_video_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - AVStream *st; - OutputStream *ost; - AVCodecContext *video_enc; - char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_VIDEO, source_index); - st = ost->st; - video_enc = ost->enc_ctx; - - MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st); - if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(max_frame_rates, str, max_frame_rate, oc, st); - if (max_frame_rate && av_parse_video_rate(&ost->max_frame_rate, max_frame_rate) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid maximum framerate value: %s\n", max_frame_rate); - exit_program(1); - } - - if (frame_rate && max_frame_rate) { - av_log(NULL, AV_LOG_ERROR, "Only one of -fpsmax and -r can be set for a stream.\n"); - exit_program(1); - } - - if ((frame_rate || max_frame_rate) && - video_sync_method == VSYNC_PASSTHROUGH) - av_log(NULL, AV_LOG_ERROR, "Using -vsync passthrough and -r/-fpsmax can produce invalid output files\n"); - - MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st); - if (frame_aspect_ratio) { - AVRational q; - if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 || - q.num <= 0 || q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid aspect ratio: %s\n", frame_aspect_ratio); - exit_program(1); - } - ost->frame_aspect_ratio = q; - } - - MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); - MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); - - if (!ost->stream_copy) { - const char *p = NULL; - char *frame_size = NULL; - char *frame_pix_fmt = NULL; - char *intra_matrix = NULL, *inter_matrix = NULL; - char *chroma_intra_matrix = NULL; - int do_pass = 0; - int i; - - MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); - if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st); - if (frame_pix_fmt && *frame_pix_fmt == '+') { - ost->keep_pix_fmt = 1; - if (!*++frame_pix_fmt) - frame_pix_fmt = NULL; - } - if (frame_pix_fmt && (video_enc->pix_fmt = av_get_pix_fmt(frame_pix_fmt)) == AV_PIX_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Unknown pixel format requested: %s.\n", frame_pix_fmt); - exit_program(1); - } - st->sample_aspect_ratio = video_enc->sample_aspect_ratio; - - MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st); - if (intra_matrix) { - if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64))) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n"); - exit_program(1); - } - parse_matrix_coeffs(video_enc->intra_matrix, intra_matrix); - } - MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st); - if (chroma_intra_matrix) { - uint16_t *p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64); - if (!p) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n"); - exit_program(1); - } - video_enc->chroma_intra_matrix = p; - parse_matrix_coeffs(p, chroma_intra_matrix); - } - MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st); - if (inter_matrix) { - if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64))) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for inter matrix.\n"); - exit_program(1); - } - parse_matrix_coeffs(video_enc->inter_matrix, inter_matrix); - } - - MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st); - for (i = 0; p; i++) { - int start, end, q; - int e = sscanf(p, "%d,%d,%d", &start, &end, &q); - if (e != 3) { - av_log(NULL, AV_LOG_FATAL, "error parsing rc_override\n"); - exit_program(1); - } - video_enc->rc_override = - av_realloc_array(video_enc->rc_override, - i + 1, sizeof(RcOverride)); - if (!video_enc->rc_override) { - av_log(NULL, AV_LOG_FATAL, "Could not (re)allocate memory for rc_override.\n"); - exit_program(1); - } - video_enc->rc_override[i].start_frame = start; - video_enc->rc_override[i].end_frame = end; - if (q > 0) { - video_enc->rc_override[i].qscale = q; - video_enc->rc_override[i].quality_factor = 1.0; - } - else { - video_enc->rc_override[i].qscale = 0; - video_enc->rc_override[i].quality_factor = -q/100.0; - } - p = strchr(p, '/'); - if (p) p++; - } - video_enc->rc_override_count = i; - - if (do_psnr) - video_enc->flags|= AV_CODEC_FLAG_PSNR; - - /* two pass mode */ - MATCH_PER_STREAM_OPT(pass, i, do_pass, oc, st); - if (do_pass) { - if (do_pass & 1) { - video_enc->flags |= AV_CODEC_FLAG_PASS1; - av_dict_set(&ost->encoder_opts, "flags", "+pass1", AV_DICT_APPEND); - } - if (do_pass & 2) { - video_enc->flags |= AV_CODEC_FLAG_PASS2; - av_dict_set(&ost->encoder_opts, "flags", "+pass2", AV_DICT_APPEND); - } - } - - MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st); - if (ost->logfile_prefix && - !(ost->logfile_prefix = av_strdup(ost->logfile_prefix))) - exit_program(1); - - if (do_pass) { - char logfilename[1024]; - FILE *f; - - snprintf(logfilename, sizeof(logfilename), "%s-%d.log", - ost->logfile_prefix ? ost->logfile_prefix : - DEFAULT_PASS_LOGFILENAME_PREFIX, - nb_output_streams - 1); - if (!strcmp(ost->enc->name, "libx264")) { - av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE); - } else { - if (video_enc->flags & AV_CODEC_FLAG_PASS2) { - char *logbuffer = fftools_read_file(logfilename); - - if (!logbuffer) { - av_log(NULL, AV_LOG_FATAL, "Error reading log file '%s' for pass-2 encoding\n", - logfilename); - exit_program(1); - } - video_enc->stats_in = logbuffer; - } - if (video_enc->flags & AV_CODEC_FLAG_PASS1) { - f = fopen_utf8(logfilename, "wb"); - if (!f) { - av_log(NULL, AV_LOG_FATAL, - "Cannot write log file '%s' for pass-1 encoding: %s\n", - logfilename, strerror(errno)); - exit_program(1); - } - ost->logfile = f; - } - } - } - - MATCH_PER_STREAM_OPT(forced_key_frames, str, ost->forced_keyframes, oc, st); - if (ost->forced_keyframes) - ost->forced_keyframes = av_strdup(ost->forced_keyframes); - - MATCH_PER_STREAM_OPT(force_fps, i, ost->force_fps, oc, st); - - ost->top_field_first = -1; - MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st); - - ost->vsync_method = video_sync_method; - MATCH_PER_STREAM_OPT(fps_mode, str, ost->fps_mode, oc, st); - if (ost->fps_mode) - parse_and_set_vsync(ost->fps_mode, &ost->vsync_method, ost->file_index, ost->index, 0); - - if (ost->vsync_method == VSYNC_AUTO) { - if (!strcmp(oc->oformat->name, "avi")) { - ost->vsync_method = VSYNC_VFR; - } else { - ost->vsync_method = (oc->oformat->flags & AVFMT_VARIABLE_FPS) ? - ((oc->oformat->flags & AVFMT_NOTIMESTAMPS) ? - VSYNC_PASSTHROUGH : VSYNC_VFR) : - VSYNC_CFR; - } - - if (ost->source_index >= 0 && ost->vsync_method == VSYNC_CFR) { - const InputStream *ist = input_streams[ost->source_index]; - const InputFile *ifile = input_files[ist->file_index]; - - if (ifile->nb_streams == 1 && ifile->input_ts_offset == 0) - ost->vsync_method = VSYNC_VSCFR; - } - - if (ost->vsync_method == VSYNC_CFR && copy_ts) { - ost->vsync_method = VSYNC_VSCFR; - } - } - ost->is_cfr = (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR); - - ost->avfilter = get_ost_filters(o, oc, ost); - if (!ost->avfilter) - exit_program(1); - - ost->last_frame = av_frame_alloc(); - if (!ost->last_frame) - exit_program(1); - } - - if (ost->stream_copy) - check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_VIDEO); - - return ost; -} - -OutputStream *new_audio_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - int n; - AVStream *st; - OutputStream *ost; - AVCodecContext *audio_enc; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_AUDIO, source_index); - st = ost->st; - - audio_enc = ost->enc_ctx; - audio_enc->codec_type = AVMEDIA_TYPE_AUDIO; - - MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); - MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); - - if (!ost->stream_copy) { - int channels = 0; - char *layout = NULL; - char *sample_fmt = NULL; - - MATCH_PER_STREAM_OPT(audio_channels, i, channels, oc, st); - if (channels) { - audio_enc->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC; - audio_enc->ch_layout.nb_channels = channels; - } - - MATCH_PER_STREAM_OPT(audio_ch_layouts, str, layout, oc, st); - if (layout) { - if (av_channel_layout_from_string(&audio_enc->ch_layout, layout) < 0) { -#if FF_API_OLD_CHANNEL_LAYOUT - uint64_t mask; - AV_NOWARN_DEPRECATED({ - mask = av_get_channel_layout(layout); - }) - if (!mask) { -#endif - av_log(NULL, AV_LOG_FATAL, "Unknown channel layout: %s\n", layout); - exit_program(1); -#if FF_API_OLD_CHANNEL_LAYOUT - } - av_log(NULL, AV_LOG_WARNING, "Channel layout '%s' uses a deprecated syntax.\n", - layout); - av_channel_layout_from_mask(&audio_enc->ch_layout, mask); -#endif - } - } - - MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st); - if (sample_fmt && - (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st); - - MATCH_PER_STREAM_OPT(apad, str, ost->apad, oc, st); - ost->apad = av_strdup(ost->apad); - - ost->avfilter = get_ost_filters(o, oc, ost); - if (!ost->avfilter) - exit_program(1); - - /* check for channel mapping for this audio stream */ - for (n = 0; n < o->nb_audio_channel_maps; n++) { - AudioChannelMap *map = &o->audio_channel_maps[n]; - if ((map->ofile_idx == -1 || ost->file_index == map->ofile_idx) && - (map->ostream_idx == -1 || ost->st->index == map->ostream_idx)) { - InputStream *ist; - - if (map->channel_idx == -1) { - ist = NULL; - } else if (ost->source_index < 0) { - av_log(NULL, AV_LOG_FATAL, "Cannot determine input stream for channel mapping %d.%d\n", - ost->file_index, ost->st->index); - continue; - } else { - ist = input_streams[ost->source_index]; - } - - if (!ist || (ist->file_index == map->file_idx && ist->st->index == map->stream_idx)) { - if (av_reallocp_array(&ost->audio_channels_map, - ost->audio_channels_mapped + 1, - sizeof(*ost->audio_channels_map) - ) < 0 ) - exit_program(1); - - ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx; - } - } - } - } - - if (ost->stream_copy) - check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_AUDIO); - - return ost; -} - -OutputStream *new_data_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_DATA, source_index); - if (!ost->stream_copy) { - av_log(NULL, AV_LOG_FATAL, "Data stream encoding not supported yet (only streamcopy)\n"); - exit_program(1); - } - - return ost; -} - -OutputStream *new_unknown_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_UNKNOWN, source_index); - if (!ost->stream_copy) { - av_log(NULL, AV_LOG_FATAL, "Unknown stream encoding not supported yet (only streamcopy)\n"); - exit_program(1); - } - - return ost; -} - -OutputStream *new_attachment_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost = new_output_stream(o, oc, AVMEDIA_TYPE_ATTACHMENT, source_index); - ost->stream_copy = 1; - ost->finished = 1; - return ost; -} - -OutputStream *new_subtitle_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - AVStream *st; - OutputStream *ost; - AVCodecContext *subtitle_enc; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_SUBTITLE, source_index); - st = ost->st; - subtitle_enc = ost->enc_ctx; - - subtitle_enc->codec_type = AVMEDIA_TYPE_SUBTITLE; - - if (!ost->stream_copy) { - char *frame_size = NULL; - - MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); - if (frame_size && av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); - exit_program(1); - } - } - - return ost; -} - -/* arg format is "output-stream-index:streamid-value". */ -int opt_streamid(void *optctx, const char *opt, const char *arg) -{ - OptionsContext *o = optctx; - int idx; - char *p; - char idx_str[16]; - - av_strlcpy(idx_str, arg, sizeof(idx_str)); - p = strchr(idx_str, ':'); - if (!p) { - av_log(NULL, AV_LOG_FATAL, - "Invalid value '%s' for option '%s', required syntax is 'index:value'\n", - arg, opt); - exit_program(1); - } - *p++ = '\0'; - idx = parse_number_or_die(opt, idx_str, OPT_INT, 0, MAX_STREAMS-1); - o->streamid_map = grow_array(o->streamid_map, sizeof(*o->streamid_map), &o->nb_streamid_map, idx+1); - o->streamid_map[idx] = parse_number_or_die(opt, p, OPT_INT, 0, INT_MAX); - return 0; -} - -int copy_chapters(InputFile *ifile, OutputFile *ofile, AVFormatContext *os, - int copy_metadata) -{ - AVFormatContext *is = ifile->ctx; - AVChapter **tmp; - int i; - - tmp = av_realloc_f(os->chapters, is->nb_chapters + os->nb_chapters, sizeof(*os->chapters)); - if (!tmp) - return AVERROR(ENOMEM); - os->chapters = tmp; - - for (i = 0; i < is->nb_chapters; i++) { - AVChapter *in_ch = is->chapters[i], *out_ch; - int64_t start_time = (ofile->start_time == AV_NOPTS_VALUE) ? 0 : ofile->start_time; - int64_t ts_off = av_rescale_q(start_time - ifile->ts_offset, - AV_TIME_BASE_Q, in_ch->time_base); - int64_t rt = (ofile->recording_time == INT64_MAX) ? INT64_MAX : - av_rescale_q(ofile->recording_time, AV_TIME_BASE_Q, in_ch->time_base); - - - if (in_ch->end < ts_off) - continue; - if (rt != INT64_MAX && in_ch->start > rt + ts_off) - break; - - out_ch = av_mallocz(sizeof(AVChapter)); - if (!out_ch) - return AVERROR(ENOMEM); - - out_ch->id = in_ch->id; - out_ch->time_base = in_ch->time_base; - out_ch->start = FFMAX(0, in_ch->start - ts_off); - out_ch->end = FFMIN(rt, in_ch->end - ts_off); - - if (copy_metadata) - av_dict_copy(&out_ch->metadata, in_ch->metadata, 0); - - os->chapters[os->nb_chapters++] = out_ch; - } - return 0; -} - -int set_dispositions(OutputFile *of, AVFormatContext *ctx) -{ - int nb_streams[AVMEDIA_TYPE_NB] = { 0 }; - int have_default[AVMEDIA_TYPE_NB] = { 0 }; - int have_manual = 0; - - // first, copy the input dispositions - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - - nb_streams[ost->st->codecpar->codec_type]++; - - have_manual |= !!ost->disposition; - - if (ost->source_index >= 0) { - ost->st->disposition = input_streams[ost->source_index]->st->disposition; - - if (ost->st->disposition & AV_DISPOSITION_DEFAULT) - have_default[ost->st->codecpar->codec_type] = 1; - } - } - - if (have_manual) { - // process manually set dispositions - they override the above copy - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - int ret; - - if (!ost->disposition) - continue; - -#if LIBAVFORMAT_VERSION_MAJOR >= 60 - ret = av_opt_set(ost->st, "disposition", ost->disposition, 0); -#else - { - const AVClass *class = av_stream_get_class(); - const AVOption *o = av_opt_find(&class, "disposition", NULL, 0, AV_OPT_SEARCH_FAKE_OBJ); - - av_assert0(o); - ret = av_opt_eval_flags(&class, o, ost->disposition, &ost->st->disposition); - } -#endif - - if (ret < 0) - return ret; - } - } else { - // For each media type with more than one stream, find a suitable stream to - // mark as default, unless one is already marked default. - // "Suitable" means the first of that type, skipping attached pictures. - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - enum AVMediaType type = ost->st->codecpar->codec_type; - - if (nb_streams[type] < 2 || have_default[type] || - ost->st->disposition & AV_DISPOSITION_ATTACHED_PIC) - continue; - - ost->st->disposition |= AV_DISPOSITION_DEFAULT; - have_default[type] = 1; - } - } - - return 0; -} - -void init_output_filter(OutputFilter *ofilter, OptionsContext *o, - AVFormatContext *oc) -{ - OutputStream *ost; - - switch (ofilter->type) { - case AVMEDIA_TYPE_VIDEO: ost = new_video_stream(o, oc, -1); break; - case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream(o, oc, -1); break; - default: - av_log(NULL, AV_LOG_FATAL, "Only video and audio filters are supported " - "currently.\n"); - exit_program(1); - } - - ost->filter = ofilter; - - ofilter->ost = ost; - ofilter->format = -1; - - if (ost->stream_copy) { - av_log(NULL, AV_LOG_ERROR, "Streamcopy requested for output stream %d:%d, " - "which is fed from a complex filtergraph. Filtering and streamcopy " - "cannot be used together.\n", ost->file_index, ost->index); - exit_program(1); - } - - if (ost->avfilter && (ost->filters || ost->filters_script)) { - const char *opt = ost->filters ? "-vf/-af/-filter" : "-filter_script"; - av_log(NULL, AV_LOG_ERROR, - "%s '%s' was specified through the %s option " - "for output stream %d:%d, which is fed from a complex filtergraph.\n" - "%s and -filter_complex cannot be used together for the same stream.\n", - ost->filters ? "Filtergraph" : "Filtergraph script", - ost->filters ? ost->filters : ost->filters_script, - opt, ost->file_index, ost->index, opt); - exit_program(1); - } - - avfilter_inout_free(&ofilter->out_tmp); -} - -int init_complex_filters(void) -{ - int i, ret = 0; - - for (i = 0; i < nb_filtergraphs; i++) { - ret = init_complex_filtergraph(filtergraphs[i]); - if (ret < 0) - return ret; - } - return 0; -} - -void set_channel_layout(OutputFilter *f, OutputStream *ost) -{ - int i, err; - - if (ost->enc_ctx->ch_layout.order != AV_CHANNEL_ORDER_UNSPEC) { - /* Pass the layout through for all orders but UNSPEC */ - err = av_channel_layout_copy(&f->ch_layout, &ost->enc_ctx->ch_layout); - if (err < 0) - exit_program(1); - return; - } - - /* Requested layout is of order UNSPEC */ - if (!ost->enc->ch_layouts) { - /* Use the default native layout for the requested amount of channels when the - encoder doesn't have a list of supported layouts */ - av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); - return; - } - /* Encoder has a list of supported layouts. Pick the first layout in it with the - same amount of channels as the requested layout */ - for (i = 0; ost->enc->ch_layouts[i].nb_channels; i++) { - if (ost->enc->ch_layouts[i].nb_channels == ost->enc_ctx->ch_layout.nb_channels) - break; - } - if (ost->enc->ch_layouts[i].nb_channels) { - /* Use it if one is found */ - err = av_channel_layout_copy(&f->ch_layout, &ost->enc->ch_layouts[i]); - if (err < 0) - exit_program(1); - return; - } - /* If no layout for the amount of channels requested was found, use the default - native layout for it. */ - av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); -} - -int open_output_file(OptionsContext *o, const char *filename) -{ - AVFormatContext *oc; - int i, j, err; - OutputFile *of; - OutputStream *ost; - InputStream *ist; - AVDictionary *unused_opts = NULL; - const AVDictionaryEntry *e = NULL; - - if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) { - o->stop_time = INT64_MAX; - av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); - } - - if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) { - int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; - if (o->stop_time <= start_time) { - av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); - exit_program(1); - } else { - o->recording_time = o->stop_time - start_time; - } - } - - of = ALLOC_ARRAY_ELEM(output_files, nb_output_files); - - of->index = nb_output_files - 1; - of->ost_index = nb_output_streams; - of->recording_time = o->recording_time; - of->start_time = o->start_time; - of->limit_filesize = o->limit_filesize; - of->shortest = o->shortest; - av_dict_copy(&of->opts, o->g->format_opts, 0); - - if (!strcmp(filename, "-")) - filename = "pipe:"; - - err = avformat_alloc_output_context2(&oc, NULL, o->format, filename); - if (!oc) { - print_error(filename, err); - exit_program(1); - } - - of->ctx = oc; - of->format = oc->oformat; - if (o->recording_time != INT64_MAX) - oc->duration = o->recording_time; - - oc->interrupt_callback = int_cb; - - if (o->bitexact) { - oc->flags |= AVFMT_FLAG_BITEXACT; - } - - /* create streams for all unlabeled output pads */ - for (i = 0; i < nb_filtergraphs; i++) { - FilterGraph *fg = filtergraphs[i]; - for (j = 0; j < fg->nb_outputs; j++) { - OutputFilter *ofilter = fg->outputs[j]; - - if (!ofilter->out_tmp || ofilter->out_tmp->name) - continue; - - switch (ofilter->type) { - case AVMEDIA_TYPE_VIDEO: o->video_disable = 1; break; - case AVMEDIA_TYPE_AUDIO: o->audio_disable = 1; break; - case AVMEDIA_TYPE_SUBTITLE: o->subtitle_disable = 1; break; - } - init_output_filter(ofilter, o, oc); - } - } - - if (!o->nb_stream_maps) { - char *subtitle_codec_name = NULL; - /* pick the "best" stream of each type */ - - /* video: highest resolution */ - if (!o->video_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_VIDEO) != AV_CODEC_ID_NONE) { - int best_score = 0, idx = -1; - int qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0); - for (j = 0; j < nb_input_files; j++) { - InputFile *ifile = input_files[j]; - int file_best_score = 0, file_best_idx = -1; - for (i = 0; i < ifile->nb_streams; i++) { - int score; - ist = input_streams[ifile->ist_index + i]; - score = ist->st->codecpar->width * ist->st->codecpar->height - + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) - + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); - if (ist->user_set_discard == AVDISCARD_ALL) - continue; - if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - score = 1; - if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && - score > file_best_score) { - if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - continue; - file_best_score = score; - file_best_idx = ifile->ist_index + i; - } - } - if (file_best_idx >= 0) { - if((qcr == MKTAG('A', 'P', 'I', 'C')) || !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - file_best_score -= 5000000*!!(input_streams[file_best_idx]->st->disposition & AV_DISPOSITION_DEFAULT); - if (file_best_score > best_score) { - best_score = file_best_score; - idx = file_best_idx; - } - } - } - if (idx >= 0) - new_video_stream(o, oc, idx); - } - - /* audio: most channels */ - if (!o->audio_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_AUDIO) != AV_CODEC_ID_NONE) { - int best_score = 0, idx = -1; - for (j = 0; j < nb_input_files; j++) { - InputFile *ifile = input_files[j]; - int file_best_score = 0, file_best_idx = -1; - for (i = 0; i < ifile->nb_streams; i++) { - int score; - ist = input_streams[ifile->ist_index + i]; - score = ist->st->codecpar->ch_layout.nb_channels - + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) - + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); - if (ist->user_set_discard == AVDISCARD_ALL) - continue; - if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && - score > file_best_score) { - file_best_score = score; - file_best_idx = ifile->ist_index + i; - } - } - if (file_best_idx >= 0) { - file_best_score -= 5000000*!!(input_streams[file_best_idx]->st->disposition & AV_DISPOSITION_DEFAULT); - if (file_best_score > best_score) { - best_score = file_best_score; - idx = file_best_idx; - } - } - } - if (idx >= 0) - new_audio_stream(o, oc, idx); - } - - /* subtitles: pick first */ - MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s"); - if (!o->subtitle_disable && (avcodec_find_encoder(oc->oformat->subtitle_codec) || subtitle_codec_name)) { - for (i = 0; i < nb_input_streams; i++) - if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { - AVCodecDescriptor const *input_descriptor = - avcodec_descriptor_get(input_streams[i]->st->codecpar->codec_id); - AVCodecDescriptor const *output_descriptor = NULL; - AVCodec const *output_codec = - avcodec_find_encoder(oc->oformat->subtitle_codec); - int input_props = 0, output_props = 0; - if (input_streams[i]->user_set_discard == AVDISCARD_ALL) - continue; - if (output_codec) - output_descriptor = avcodec_descriptor_get(output_codec->id); - if (input_descriptor) - input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (output_descriptor) - output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (subtitle_codec_name || - input_props & output_props || - // Map dvb teletext which has neither property to any output subtitle encoder - (input_descriptor && output_descriptor && - (!input_descriptor->props || - !output_descriptor->props))) { - new_subtitle_stream(o, oc, i); - break; - } - } - } - /* Data only if codec id match */ - if (!o->data_disable ) { - enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_DATA); - for (i = 0; codec_id != AV_CODEC_ID_NONE && i < nb_input_streams; i++) { - if (input_streams[i]->user_set_discard == AVDISCARD_ALL) - continue; - if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_DATA - && input_streams[i]->st->codecpar->codec_id == codec_id ) - new_data_stream(o, oc, i); - } - } - } else { - for (i = 0; i < o->nb_stream_maps; i++) { - StreamMap *map = &o->stream_maps[i]; - - if (map->disabled) - continue; - - if (map->linklabel) { - FilterGraph *fg; - OutputFilter *ofilter = NULL; - int j, k; - - for (j = 0; j < nb_filtergraphs; j++) { - fg = filtergraphs[j]; - for (k = 0; k < fg->nb_outputs; k++) { - AVFilterInOut *out = fg->outputs[k]->out_tmp; - if (out && !strcmp(out->name, map->linklabel)) { - ofilter = fg->outputs[k]; - goto loop_end; - } - } - } -loop_end: - if (!ofilter) { - av_log(NULL, AV_LOG_FATAL, "Output with label '%s' does not exist " - "in any defined filter graph, or was already used elsewhere.\n", map->linklabel); - exit_program(1); - } - init_output_filter(ofilter, o, oc); - } else { - int src_idx = input_files[map->file_index]->ist_index + map->stream_index; - - ist = input_streams[input_files[map->file_index]->ist_index + map->stream_index]; - if (ist->user_set_discard == AVDISCARD_ALL) { - av_log(NULL, AV_LOG_FATAL, "Stream #%d:%d is disabled and cannot be mapped.\n", - map->file_index, map->stream_index); - exit_program(1); - } - if(o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) - continue; - if(o-> audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - continue; - if(o-> video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) - continue; - if(o-> data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA) - continue; - - ost = NULL; - switch (ist->st->codecpar->codec_type) { - case AVMEDIA_TYPE_VIDEO: ost = new_video_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_SUBTITLE: ost = new_subtitle_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_DATA: ost = new_data_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_ATTACHMENT: ost = new_attachment_stream(o, oc, src_idx); break; - case AVMEDIA_TYPE_UNKNOWN: - if (copy_unknown_streams) { - ost = new_unknown_stream (o, oc, src_idx); - break; - } - default: - av_log(NULL, ignore_unknown_streams ? AV_LOG_WARNING : AV_LOG_FATAL, - "Cannot map stream #%d:%d - unsupported type.\n", - map->file_index, map->stream_index); - if (!ignore_unknown_streams) { - av_log(NULL, AV_LOG_FATAL, - "If you want unsupported types ignored instead " - "of failing, please use the -ignore_unknown option\n" - "If you want them copied, please use -copy_unknown\n"); - exit_program(1); - } - } - if (ost) - ost->sync_ist = input_streams[ input_files[map->sync_file_index]->ist_index - + map->sync_stream_index]; - } - } - } - - /* handle attached files */ - for (i = 0; i < o->nb_attachments; i++) { - AVIOContext *pb; - uint8_t *attachment; - const char *p; - int64_t len; - - if ((err = avio_open2(&pb, o->attachments[i], AVIO_FLAG_READ, &int_cb, NULL)) < 0) { - av_log(NULL, AV_LOG_FATAL, "Could not open attachment file %s.\n", - o->attachments[i]); - exit_program(1); - } - if ((len = avio_size(pb)) <= 0) { - av_log(NULL, AV_LOG_FATAL, "Could not get size of the attachment %s.\n", - o->attachments[i]); - exit_program(1); - } - if (len > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE || - !(attachment = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE))) { - av_log(NULL, AV_LOG_FATAL, "Attachment %s too large.\n", - o->attachments[i]); - exit_program(1); - } - avio_read(pb, attachment, len); - memset(attachment + len, 0, AV_INPUT_BUFFER_PADDING_SIZE); - - ost = new_attachment_stream(o, oc, -1); - ost->stream_copy = 0; - ost->attachment_filename = o->attachments[i]; - ost->st->codecpar->extradata = attachment; - ost->st->codecpar->extradata_size = len; - - p = strrchr(o->attachments[i], '/'); - av_dict_set(&ost->st->metadata, "filename", (p && *p) ? p + 1 : o->attachments[i], AV_DICT_DONT_OVERWRITE); - avio_closep(&pb); - } - - if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) { - av_dump_format(oc, nb_output_files - 1, oc->url, 1); - av_log(NULL, AV_LOG_ERROR, "Output file #%d does not contain any stream\n", nb_output_files - 1); - exit_program(1); - } - - /* check if all codec options have been used */ - unused_opts = strip_specifiers(o->g->codec_opts); - for (i = of->ost_index; i < nb_output_streams; i++) { - e = NULL; - while ((e = av_dict_get(output_streams[i]->encoder_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) - av_dict_set(&unused_opts, e->key, NULL, 0); - } - - e = NULL; - while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) { - const AVClass *class = avcodec_get_class(); - const AVOption *option = av_opt_find(&class, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - const AVClass *fclass = avformat_get_class(); - const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - if (!option || foption) - continue; - - - if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) { - av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " - "output file #%d (%s) is not an encoding option.\n", e->key, - option->help ? option->help : "", nb_output_files - 1, - filename); - exit_program(1); - } - - // gop_timecode is injected by generic code but not always used - if (!strcmp(e->key, "gop_timecode")) - continue; - - av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " - "output file #%d (%s) has not been used for any stream. The most " - "likely reason is either wrong type (e.g. a video option with " - "no video streams) or that it is a private option of some encoder " - "which was not actually used for any stream.\n", e->key, - option->help ? option->help : "", nb_output_files - 1, filename); - } - av_dict_free(&unused_opts); - - /* set the decoding_needed flags and create simple filtergraphs */ - for (i = of->ost_index; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (ost->encoding_needed && ost->source_index >= 0) { - InputStream *ist = input_streams[ost->source_index]; - ist->decoding_needed |= DECODING_FOR_OST; - ist->processing_needed = 1; - - if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || - ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - err = init_simple_filtergraph(ist, ost); - if (err < 0) { - av_log(NULL, AV_LOG_ERROR, - "Error initializing a simple filtergraph between streams " - "%d:%d->%d:%d\n", ist->file_index, ost->source_index, - nb_output_files - 1, ost->st->index); - exit_program(1); - } - } - } else if (ost->stream_copy && ost->source_index >= 0) { - InputStream *ist = input_streams[ost->source_index]; - ist->processing_needed = 1; - } - - /* set the filter output constraints */ - if (ost->filter) { - OutputFilter *f = ost->filter; - switch (ost->enc_ctx->codec_type) { - case AVMEDIA_TYPE_VIDEO: - f->frame_rate = ost->frame_rate; - f->width = ost->enc_ctx->width; - f->height = ost->enc_ctx->height; - if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { - f->format = ost->enc_ctx->pix_fmt; - } else { - f->formats = ost->enc->pix_fmts; - } - break; - case AVMEDIA_TYPE_AUDIO: - if (ost->enc_ctx->sample_fmt != AV_SAMPLE_FMT_NONE) { - f->format = ost->enc_ctx->sample_fmt; - } else { - f->formats = ost->enc->sample_fmts; - } - if (ost->enc_ctx->sample_rate) { - f->sample_rate = ost->enc_ctx->sample_rate; - } else { - f->sample_rates = ost->enc->supported_samplerates; - } - if (ost->enc_ctx->ch_layout.nb_channels) { - set_channel_layout(f, ost); - } else if (ost->enc->ch_layouts) { - f->ch_layouts = ost->enc->ch_layouts; - } - break; - } - } - } - - /* check filename in case of an image number is expected */ - if (oc->oformat->flags & AVFMT_NEEDNUMBER) { - if (!av_filename_number_test(oc->url)) { - print_error(oc->url, AVERROR(EINVAL)); - exit_program(1); - } - } - - if (!(oc->oformat->flags & AVFMT_NOSTREAMS) && !input_stream_potentially_available) { - av_log(NULL, AV_LOG_ERROR, - "No input streams but output needs an input stream\n"); - exit_program(1); - } - - if (!(oc->oformat->flags & AVFMT_NOFILE)) { - /* test if it already exists to avoid losing precious files */ - assert_file_overwrite(filename); - - /* open the file */ - if ((err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, - &oc->interrupt_callback, - &of->opts)) < 0) { - print_error(filename, err); - exit_program(1); - } - } else if (strcmp(oc->oformat->name, "image2")==0 && !av_filename_number_test(filename)) - assert_file_overwrite(filename); - - if (o->mux_preload) { - av_dict_set_int(&of->opts, "preload", o->mux_preload*AV_TIME_BASE, 0); - } - oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); - - /* copy metadata */ - for (i = 0; i < o->nb_metadata_map; i++) { - char *p; - int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0); - - if (in_file_index >= nb_input_files) { - av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d while processing metadata maps\n", in_file_index); - exit_program(1); - } - fftools_copy_metadata(o->metadata_map[i].specifier, *p ? p + 1 : p, oc, - in_file_index >= 0 ? - input_files[in_file_index]->ctx : NULL, o); - } - - /* copy chapters */ - if (o->chapters_input_file >= nb_input_files) { - if (o->chapters_input_file == INT_MAX) { - /* copy chapters from the first input file that has them*/ - o->chapters_input_file = -1; - for (i = 0; i < nb_input_files; i++) - if (input_files[i]->ctx->nb_chapters) { - o->chapters_input_file = i; - break; - } - } else { - av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d in chapter mapping.\n", - o->chapters_input_file); - exit_program(1); - } - } - if (o->chapters_input_file >= 0) - copy_chapters(input_files[o->chapters_input_file], of, oc, - !o->metadata_chapters_manual); - - /* copy global metadata by default */ - if (!o->metadata_global_manual && nb_input_files){ - av_dict_copy(&oc->metadata, input_files[0]->ctx->metadata, - AV_DICT_DONT_OVERWRITE); - if(o->recording_time != INT64_MAX) - av_dict_set(&oc->metadata, "duration", NULL, 0); - av_dict_set(&oc->metadata, "creation_time", NULL, 0); - av_dict_set(&oc->metadata, "company_name", NULL, 0); - av_dict_set(&oc->metadata, "product_name", NULL, 0); - av_dict_set(&oc->metadata, "product_version", NULL, 0); - } - if (!o->metadata_streams_manual) - for (i = of->ost_index; i < nb_output_streams; i++) { - InputStream *ist; - if (output_streams[i]->source_index < 0) /* this is true e.g. for attached files */ - continue; - ist = input_streams[output_streams[i]->source_index]; - av_dict_copy(&output_streams[i]->st->metadata, ist->st->metadata, AV_DICT_DONT_OVERWRITE); - if (!output_streams[i]->stream_copy) { - av_dict_set(&output_streams[i]->st->metadata, "encoder", NULL, 0); - } - } - - /* process manually set programs */ - for (i = 0; i < o->nb_program; i++) { - const char *p = o->program[i].u.str; - int progid = i+1; - AVProgram *program; - - while(*p) { - const char *p2 = av_get_token(&p, ":"); - const char *to_dealloc = p2; - char *key; - if (!p2) - break; - - if(*p) p++; - - key = av_get_token(&p2, "="); - if (!key || !*p2) { - av_freep(&to_dealloc); - av_freep(&key); - break; - } - p2++; - - if (!strcmp(key, "program_num")) - progid = strtol(p2, NULL, 0); - av_freep(&to_dealloc); - av_freep(&key); - } - - program = av_new_program(oc, progid); - - p = o->program[i].u.str; - while(*p) { - const char *p2 = av_get_token(&p, ":"); - const char *to_dealloc = p2; - char *key; - if (!p2) - break; - if(*p) p++; - - key = av_get_token(&p2, "="); - if (!key) { - av_log(NULL, AV_LOG_FATAL, - "No '=' character in program string %s.\n", - p2); - exit_program(1); - } - if (!*p2) - exit_program(1); - p2++; - - if (!strcmp(key, "title")) { - av_dict_set(&program->metadata, "title", p2, 0); - } else if (!strcmp(key, "program_num")) { - } else if (!strcmp(key, "st")) { - int st_num = strtol(p2, NULL, 0); - av_program_add_stream_index(oc, progid, st_num); - } else { - av_log(NULL, AV_LOG_FATAL, "Unknown program key %s.\n", key); - exit_program(1); - } - av_freep(&to_dealloc); - av_freep(&key); - } - } - - /* process manually set metadata */ - for (i = 0; i < o->nb_metadata; i++) { - AVDictionary **m; - char type, *val; - const char *stream_spec; - int index = 0, j, ret = 0; - - val = strchr(o->metadata[i].u.str, '='); - if (!val) { - av_log(NULL, AV_LOG_FATAL, "No '=' character in metadata string %s.\n", - o->metadata[i].u.str); - exit_program(1); - } - *val++ = 0; - - parse_meta_type(o->metadata[i].specifier, &type, &index, &stream_spec); - if (type == 's') { - for (j = 0; j < oc->nb_streams; j++) { - ost = output_streams[nb_output_streams - oc->nb_streams + j]; - if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) { - if (!strcmp(o->metadata[i].u.str, "rotate")) { - char *tail; - double theta = av_strtod(val, &tail); - if (!*tail) { - ost->rotate_overridden = 1; - ost->rotate_override_value = theta; - } - } else { - av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); - } - } else if (ret < 0) - exit_program(1); - } - } - else { - switch (type) { - case 'g': - m = &oc->metadata; - break; - case 'c': - if (index < 0 || index >= oc->nb_chapters) { - av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); - exit_program(1); - } - m = &oc->chapters[index]->metadata; - break; - case 'p': - if (index < 0 || index >= oc->nb_programs) { - av_log(NULL, AV_LOG_FATAL, "Invalid program index %d in metadata specifier.\n", index); - exit_program(1); - } - m = &oc->programs[index]->metadata; - break; - default: - av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); - exit_program(1); - } - av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); - } - } - - err = set_dispositions(of, oc); - if (err < 0) { - av_log(NULL, AV_LOG_FATAL, "Error setting output stream dispositions\n"); - exit_program(1); - } - - return 0; -} - -int opt_target(void *optctx, const char *opt, const char *arg) -{ - const OptionDef *options = ffmpeg_options; - OptionsContext *o = optctx; - enum { PAL, NTSC, FILM, UNKNOWN } norm = UNKNOWN; - static const char *const frame_rates[] = { "25", "30000/1001", "24000/1001" }; - - if (!strncmp(arg, "pal-", 4)) { - norm = PAL; - arg += 4; - } else if (!strncmp(arg, "ntsc-", 5)) { - norm = NTSC; - arg += 5; - } else if (!strncmp(arg, "film-", 5)) { - norm = FILM; - arg += 5; - } else { - /* Try to determine PAL/NTSC by peeking in the input files */ - if (nb_input_files) { - int i, j; - for (j = 0; j < nb_input_files; j++) { - for (i = 0; i < input_files[j]->nb_streams; i++) { - AVStream *st = input_files[j]->ctx->streams[i]; - int64_t fr; - if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) - continue; - fr = st->time_base.den * 1000LL / st->time_base.num; - if (fr == 25000) { - norm = PAL; - break; - } else if ((fr == 29970) || (fr == 23976)) { - norm = NTSC; - break; - } - } - if (norm != UNKNOWN) - break; - } - } - if (norm != UNKNOWN) - av_log(NULL, AV_LOG_INFO, "Assuming %s for target.\n", norm == PAL ? "PAL" : "NTSC"); - } - - if (norm == UNKNOWN) { - av_log(NULL, AV_LOG_FATAL, "Could not determine norm (PAL/NTSC/NTSC-Film) for target.\n"); - av_log(NULL, AV_LOG_FATAL, "Please prefix target with \"pal-\", \"ntsc-\" or \"film-\",\n"); - av_log(NULL, AV_LOG_FATAL, "or set a framerate with \"-r xxx\".\n"); - exit_program(1); - } - - if (!strcmp(arg, "vcd")) { - opt_video_codec(o, "c:v", "mpeg1video"); - opt_audio_codec(o, "c:a", "mp2"); - parse_option(o, "f", "vcd", options); - - parse_option(o, "s", norm == PAL ? "352x288" : "352x240", options); - parse_option(o, "r", frame_rates[norm], options); - opt_default(NULL, "g", norm == PAL ? "15" : "18"); - - opt_default(NULL, "b:v", "1150000"); - opt_default(NULL, "maxrate:v", "1150000"); - opt_default(NULL, "minrate:v", "1150000"); - opt_default(NULL, "bufsize:v", "327680"); // 40*1024*8; - - opt_default(NULL, "b:a", "224000"); - parse_option(o, "ar", "44100", options); - parse_option(o, "ac", "2", options); + opt_default(NULL, "b:a", "224000"); + parse_option(o, "ar", "44100", options); + parse_option(o, "ac", "2", options); opt_default(NULL, "packetsize", "2324"); opt_default(NULL, "muxrate", "1411200"); // 2352 * 75 * 8; @@ -3437,15 +1159,13 @@ int opt_filter_complex(void *optctx, const char *opt, const char *arg) if (!fg->graph_desc) return AVERROR(ENOMEM); - input_stream_potentially_available = 1; - return 0; } int opt_filter_complex_script(void *optctx, const char *opt, const char *arg) { FilterGraph *fg; - char *graph_desc = fftools_read_file(arg); + char *graph_desc = file_read(arg); if (!graph_desc) return AVERROR(EINVAL); @@ -3453,8 +1173,6 @@ int opt_filter_complex_script(void *optctx, const char *opt, const char *arg) fg->index = nb_filtergraphs - 1; fg->graph_desc = graph_desc; - input_stream_potentially_available = 1; - return 0; } @@ -3549,7 +1267,7 @@ static const OptionGroupDef groups[] = { }; int open_files(OptionGroupList *l, const char *inout, - int (*open_file)(OptionsContext*, const char*)) + int (*open_file)(const OptionsContext*, const char*)) { int i, ret; @@ -3585,7 +1303,6 @@ int open_files(OptionGroupList *l, const char *inout, int ffmpeg_parse_options(int argc, char **argv) { OptionParseContext octx; - uint8_t error[128]; int ret; memset(&octx, 0, sizeof(octx)); @@ -3609,14 +1326,12 @@ int ffmpeg_parse_options(int argc, char **argv) term_init(); /* open input files */ - ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file); + ret = open_files(&octx.groups[GROUP_INFILE], "input", ifile_open); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening input files: "); goto fail; } - apply_sync_offsets(); - /* create the complex filtergraphs */ ret = init_complex_filters(); if (ret < 0) { @@ -3625,19 +1340,22 @@ int ffmpeg_parse_options(int argc, char **argv) } /* open output files */ - ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file); + ret = open_files(&octx.groups[GROUP_OUTFILE], "output", of_open); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening output files: "); goto fail; } + correct_input_start_times(); + + apply_sync_offsets(); + check_filter_outputs(); fail: uninit_parse_context(&octx); if (ret < 0) { - av_strerror(ret, error, sizeof(error)); - av_log(NULL, AV_LOG_FATAL, "%s\n", error); + av_log(NULL, AV_LOG_FATAL, "%s\n", av_err2str(ret)); } return ret; } diff --git a/apple/src/fftools_ffprobe.c b/apple/src/fftools_ffprobe.c index b6a8b07..dccd966 100644 --- a/apple/src/fftools_ffprobe.c +++ b/apple/src/fftools_ffprobe.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2007-2010 Stefano Sabatini * Copyright (c) 2020-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -29,6 +30,13 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -52,9 +60,11 @@ #include "libavutil/ffversion.h" #include +#include #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" +#include "libavutil/ambient_viewing_environment.h" #include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/bprint.h" @@ -163,6 +173,8 @@ typedef struct ReadInterval { __thread ReadInterval *read_intervals; __thread int read_intervals_nb = 0; +__thread int find_stream_info = 1; + /* section structure definition */ #define SECTION_MAX_NB_CHILDREN 10 @@ -626,6 +638,7 @@ static inline void writer_put_str_printf(WriterContext *wctx, const char *str) static inline void writer_printf_printf(WriterContext *wctx, const char *fmt, ...) { va_list ap; + va_start(ap, fmt); av_vlog(NULL, AV_LOG_STDERR, fmt, ap); va_end(ap); @@ -671,7 +684,7 @@ static int writer_open(WriterContext **wctx, const Writer *writer, const char *a goto fail; } - while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) { + while ((opt = av_dict_iterate(opts, opt))) { if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) { av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n", opt->key, opt->value); @@ -1907,12 +1920,14 @@ static void writer_register_all(void) writer_print_string(w, k, pbuf.str, 0); \ } while (0) -#define print_list_fmt(k, f, n, ...) do { \ +#define print_list_fmt(k, f, n, m, ...) do { \ av_bprint_clear(&pbuf); \ for (int idx = 0; idx < n; idx++) { \ - if (idx > 0) \ - av_bprint_chars(&pbuf, ' ', 1); \ - av_bprintf(&pbuf, f, __VA_ARGS__); \ + for (int idx2 = 0; idx2 < m; idx2++) { \ + if (idx > 0 || idx2 > 0) \ + av_bprint_chars(&pbuf, ' ', 1); \ + av_bprintf(&pbuf, f, __VA_ARGS__); \ + } \ } \ writer_print_string(w, k, pbuf.str, 0); \ } while (0) @@ -1953,7 +1968,7 @@ static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id return 0; writer_print_section_header(w, section_id); - while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) { + while ((tag = av_dict_iterate(tags, tag))) { if ((ret = print_str_validate(tag->key, tag->value)) < 0) break; } @@ -2023,7 +2038,7 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) const AVDOVIReshapingCurve *curve = &mapping->curves[c]; writer_print_section_header(w, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); - print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, curve->pivots[idx]); + print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, 1, curve->pivots[idx]); writer_print_section_header(w, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); for (int i = 0; i < curve->num_pivots - 1; i++) { @@ -2035,7 +2050,7 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_str("mapping_idc_name", "polynomial"); print_int("poly_order", curve->poly_order[i]); print_list_fmt("poly_coef", "%"PRIi64, - curve->poly_order[i] + 1, + curve->poly_order[i] + 1, 1, curve->poly_coef[i][idx]); break; case AV_DOVI_MAPPING_MMR: @@ -2043,8 +2058,8 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_int("mmr_order", curve->mmr_order[i]); print_int("mmr_constant", curve->mmr_constant[i]); print_list_fmt("mmr_coef", "%"PRIi64, - curve->mmr_order[i] * 7, - curve->mmr_coef[i][0][idx]); + curve->mmr_order[i], 7, + curve->mmr_coef[i][idx][idx2]); break; default: print_str("mapping_idc_name", "unknown"); @@ -2082,15 +2097,15 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_int("dm_metadata_id", color->dm_metadata_id); print_int("scene_refresh_flag", color->scene_refresh_flag); print_list_fmt("ycc_to_rgb_matrix", "%d/%d", - FF_ARRAY_ELEMS(color->ycc_to_rgb_matrix), + FF_ARRAY_ELEMS(color->ycc_to_rgb_matrix), 1, color->ycc_to_rgb_matrix[idx].num, color->ycc_to_rgb_matrix[idx].den); print_list_fmt("ycc_to_rgb_offset", "%d/%d", - FF_ARRAY_ELEMS(color->ycc_to_rgb_offset), + FF_ARRAY_ELEMS(color->ycc_to_rgb_offset), 1, color->ycc_to_rgb_offset[idx].num, color->ycc_to_rgb_offset[idx].den); print_list_fmt("rgb_to_lms_matrix", "%d/%d", - FF_ARRAY_ELEMS(color->rgb_to_lms_matrix), + FF_ARRAY_ELEMS(color->rgb_to_lms_matrix), 1, color->rgb_to_lms_matrix[idx].num, color->rgb_to_lms_matrix[idx].den); print_int("signal_eotf", color->signal_eotf); @@ -2276,6 +2291,17 @@ static void print_dynamic_hdr_vivid(WriterContext *w, const AVDynamicHDRVivid *m } } +static void print_ambient_viewing_environment(WriterContext *w, + const AVAmbientViewingEnvironment *env) +{ + if (!env) + return; + + print_q("ambient_illuminance", env->ambient_illuminance, '/'); + print_q("ambient_light_x", env->ambient_light_x, '/'); + print_q("ambient_light_y", env->ambient_light_y, '/'); +} + static void print_pkt_side_data(WriterContext *w, AVCodecParameters *par, const AVPacketSideData *side_data, @@ -2293,8 +2319,11 @@ static void print_pkt_side_data(WriterContext *w, writer_print_section_header(w, id_data); print_str("side_data_type", name ? name : "unknown"); if (sd->type == AV_PKT_DATA_DISPLAYMATRIX && sd->size >= 9*4) { + double rotation = av_display_rotation_get((int32_t *)sd->data); + if (isnan(rotation)) + rotation = 0; writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); - print_int("rotation", av_display_rotation_get((int32_t *)sd->data)); + print_int("rotation", rotation); } else if (sd->type == AV_PKT_DATA_STEREO3D) { const AVStereo3D *stereo = (AVStereo3D *)sd->data; print_str("type", av_stereo3d_type_name(stereo->type)); @@ -2506,8 +2535,12 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p print_val("size", pkt->size, unit_byte_str); if (pkt->pos != -1) print_fmt ("pos", "%"PRId64, pkt->pos); else print_str_opt("pos", "N/A"); - print_fmt("flags", "%c%c", pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_', - pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_'); + print_fmt("flags", "%c%c%c", pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_', + pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_', + pkt->flags & AV_PKT_FLAG_CORRUPT ? 'C' : '_'); + if (do_show_data) + writer_print_data(w, "data", pkt->data, pkt->size); + writer_print_data_hash(w, "data_hash", pkt->data, pkt->size); if (pkt->side_data_elems) { size_t size; @@ -2526,9 +2559,6 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p SECTION_ID_PACKET_SIDE_DATA); } - if (do_show_data) - writer_print_data(w, "data", pkt->data, pkt->size); - writer_print_data_hash(w, "data_hash", pkt->data, pkt->size); writer_print_section_footer(w); av_bprint_finalize(&pbuf, NULL); @@ -2581,8 +2611,14 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, print_time("pkt_dts_time", frame->pkt_dts, &stream->time_base); print_ts ("best_effort_timestamp", frame->best_effort_timestamp); print_time("best_effort_timestamp_time", frame->best_effort_timestamp, &stream->time_base); +#if LIBAVUTIL_VERSION_MAJOR < 59 + AV_NOWARN_DEPRECATED( print_duration_ts ("pkt_duration", frame->pkt_duration); print_duration_time("pkt_duration_time", frame->pkt_duration, &stream->time_base); + ) +#endif + print_duration_ts ("duration", frame->duration); + print_duration_time("duration_time", frame->duration, &stream->time_base); if (frame->pkt_pos != -1) print_fmt ("pkt_pos", "%"PRId64, frame->pkt_pos); else print_str_opt("pkt_pos", "N/A"); if (frame->pkt_size != -1) print_val ("pkt_size", frame->pkt_size, unit_byte_str); @@ -2604,8 +2640,12 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, print_str_opt("sample_aspect_ratio", "N/A"); } print_fmt("pict_type", "%c", av_get_picture_type_char(frame->pict_type)); +#if LIBAVUTIL_VERSION_MAJOR < 59 + AV_NOWARN_DEPRECATED( print_int("coded_picture_number", frame->coded_picture_number); print_int("display_picture_number", frame->display_picture_number); + ) +#endif print_int("interlaced_frame", frame->interlaced_frame); print_int("top_field_first", frame->top_field_first); print_int("repeat_pict", frame->repeat_pict); @@ -2644,8 +2684,11 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, name = av_frame_side_data_name(sd->type); print_str("side_data_type", name ? name : "unknown"); if (sd->type == AV_FRAME_DATA_DISPLAYMATRIX && sd->size >= 9*4) { + double rotation = av_display_rotation_get((int32_t *)sd->data); + if (isnan(rotation)) + rotation = 0; writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); - print_int("rotation", av_display_rotation_get((int32_t *)sd->data)); + print_int("rotation", rotation); } else if (sd->type == AV_FRAME_DATA_AFD && sd->size > 0) { print_int("active_format", *sd->data); } else if (sd->type == AV_FRAME_DATA_GOP_TIMECODE && sd->size >= 8) { @@ -2700,6 +2743,9 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, } else if (sd->type == AV_FRAME_DATA_DYNAMIC_HDR_VIVID) { AVDynamicHDRVivid *metadata = (AVDynamicHDRVivid *)sd->data; print_dynamic_hdr_vivid(w, metadata); + } else if (sd->type == AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT) { + print_ambient_viewing_environment( + w, (const AVAmbientViewingEnvironment *)sd->data); } writer_print_section_footer(w); } @@ -2714,7 +2760,7 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, static av_always_inline int process_frame(WriterContext *w, InputFile *ifile, - AVFrame *frame, AVPacket *pkt, + AVFrame *frame, const AVPacket *pkt, int *packet_new) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; @@ -2858,9 +2904,10 @@ static int read_interval_packets(WriterContext *w, InputFile *ifile, } if (selected_streams[pkt->stream_index]) { AVRational tb = ifile->streams[pkt->stream_index].st->time_base; + int64_t pts = pkt->pts != AV_NOPTS_VALUE ? pkt->pts : pkt->dts; - if (pkt->pts != AV_NOPTS_VALUE) - *cur_ts = av_rescale_q(pkt->pts, tb, AV_TIME_BASE_Q); + if (pts != AV_NOPTS_VALUE) + *cur_ts = av_rescale_q(pts, tb, AV_TIME_BASE_Q); if (!has_start && *cur_ts != AV_NOPTS_VALUE) { start = *cur_ts; @@ -2894,7 +2941,7 @@ static int read_interval_packets(WriterContext *w, InputFile *ifile, } av_packet_unref(pkt); //Flush remaining frames that are cached in the decoder - for (i = 0; i < fmt_ctx->nb_streams; i++) { + for (i = 0; i < ifile->nb_streams; i++) { pkt->stream_index = i; if (do_read_frames) { while (process_frame(w, ifile, frame, pkt, &(int){1}) > 0); @@ -3052,6 +3099,8 @@ static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_id } print_int("bits_per_sample", av_get_bits_per_sample(par->codec_id)); + + print_int("initial_padding", par->initial_padding); break; case AVMEDIA_TYPE_SUBTITLE: @@ -3278,15 +3327,9 @@ static int show_format(WriterContext *w, InputFile *ifile) static void show_error(WriterContext *w, int err) { - char errbuf[128]; - const char *errbuf_ptr = errbuf; - - if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) - errbuf_ptr = strerror(AVUNERROR(err)); - writer_print_section_header(w, SECTION_ID_ERROR); print_int("code", err); - print_str("string", errbuf_ptr); + print_str("string", av_err2str(err)); writer_print_section_footer(w); } @@ -3299,10 +3342,8 @@ static int open_input_file(InputFile *ifile, const char *filename, int scan_all_pmts_set = 0; fmt_ctx = avformat_alloc_context(); - if (!fmt_ctx) { - print_error(filename, AVERROR(ENOMEM)); - exit_program(1); - } + if (!fmt_ctx) + report_and_exit(AVERROR(ENOMEM)); if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); @@ -3320,7 +3361,7 @@ static int open_input_file(InputFile *ifile, const char *filename, ifile->fmt_ctx = fmt_ctx; if (scan_all_pmts_set) av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); - while ((t = av_dict_get(format_opts, "", t, AV_DICT_IGNORE_SUFFIX))) + while ((t = av_dict_iterate(format_opts, t))) av_log(NULL, AV_LOG_WARNING, "Option %s skipped - not known to demuxer.\n", t->key); if (find_stream_info) { @@ -3718,7 +3759,7 @@ static void opt_input_file(void *optctx, const char *arg) exit_program(1); } if (!strcmp(arg, "-")) - arg = "pipe:"; + arg = "fd:"; input_filename = arg; } @@ -3737,7 +3778,7 @@ static void opt_output_file(void *optctx, const char *arg) exit_program(1); } if (!strcmp(arg, "-")) - arg = "pipe:"; + arg = "fd:"; output_filename = arg; } diff --git a/apple/src/fftools_objpool.c b/apple/src/fftools_objpool.c new file mode 100644 index 0000000..2884271 --- /dev/null +++ b/apple/src/fftools_objpool.c @@ -0,0 +1,145 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of objpool.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include + +#include "libavcodec/packet.h" + +#include "libavutil/common.h" +#include "libavutil/error.h" +#include "libavutil/frame.h" +#include "libavutil/mem.h" + +#include "fftools_objpool.h" + +struct ObjPool { + void *pool[32]; + unsigned int pool_count; + + ObjPoolCBAlloc alloc; + ObjPoolCBReset reset; + ObjPoolCBFree free; +}; + +ObjPool *objpool_alloc(ObjPoolCBAlloc cb_alloc, ObjPoolCBReset cb_reset, + ObjPoolCBFree cb_free) +{ + ObjPool *op = av_mallocz(sizeof(*op)); + + if (!op) + return NULL; + + op->alloc = cb_alloc; + op->reset = cb_reset; + op->free = cb_free; + + return op; +} + +void objpool_free(ObjPool **pop) +{ + ObjPool *op = *pop; + + if (!op) + return; + + for (unsigned int i = 0; i < op->pool_count; i++) + op->free(&op->pool[i]); + + av_freep(pop); +} + +int objpool_get(ObjPool *op, void **obj) +{ + if (op->pool_count) { + *obj = op->pool[--op->pool_count]; + op->pool[op->pool_count] = NULL; + } else + *obj = op->alloc(); + + return *obj ? 0 : AVERROR(ENOMEM); +} + +void objpool_release(ObjPool *op, void **obj) +{ + if (!*obj) + return; + + op->reset(*obj); + + if (op->pool_count < FF_ARRAY_ELEMS(op->pool)) + op->pool[op->pool_count++] = *obj; + else + op->free(obj); + + *obj = NULL; +} + +static void *alloc_packet(void) +{ + return av_packet_alloc(); +} +static void *alloc_frame(void) +{ + return av_frame_alloc(); +} + +static void reset_packet(void *obj) +{ + av_packet_unref(obj); +} +static void reset_frame(void *obj) +{ + av_frame_unref(obj); +} + +static void free_packet(void **obj) +{ + AVPacket *pkt = *obj; + av_packet_free(&pkt); + *obj = NULL; +} +static void free_frame(void **obj) +{ + AVFrame *frame = *obj; + av_frame_free(&frame); + *obj = NULL; +} + +ObjPool *objpool_alloc_packets(void) +{ + return objpool_alloc(alloc_packet, reset_packet, free_packet); +} +ObjPool *objpool_alloc_frames(void) +{ + return objpool_alloc(alloc_frame, reset_frame, free_frame); +} diff --git a/apple/src/fftools_objpool.h b/apple/src/fftools_objpool.h new file mode 100644 index 0000000..29cf2b0 --- /dev/null +++ b/apple/src/fftools_objpool.h @@ -0,0 +1,50 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of objpool.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_OBJPOOL_H +#define FFTOOLS_OBJPOOL_H + +typedef struct ObjPool ObjPool; + +typedef void* (*ObjPoolCBAlloc)(void); +typedef void (*ObjPoolCBReset)(void *); +typedef void (*ObjPoolCBFree)(void **); + +void objpool_free(ObjPool **op); +ObjPool *objpool_alloc(ObjPoolCBAlloc cb_alloc, ObjPoolCBReset cb_reset, + ObjPoolCBFree cb_free); +ObjPool *objpool_alloc_packets(void); +ObjPool *objpool_alloc_frames(void); + +int objpool_get(ObjPool *op, void **obj); +void objpool_release(ObjPool *op, void **obj); + +#endif // FFTOOLS_OBJPOOL_H diff --git a/apple/src/fftools_opt_common.c b/apple/src/fftools_opt_common.c index 759f1ae..e95c546 100644 --- a/apple/src/fftools_opt_common.c +++ b/apple/src/fftools_opt_common.c @@ -1,6 +1,7 @@ /* * Option handlers shared between the tools. * Copyright (c) 2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop the ffmpeg-kit library. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - time field in report_callback updated as double + * * ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -88,7 +95,7 @@ static __thread FILE *report_file = NULL; static __thread int report_file_level = AV_LOG_DEBUG; extern void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, va_list vargs); -extern void (*report_callback)(int, float, float, int64_t, int, double, double); +extern void (*report_callback)(int, float, float, int64_t, double, double, double); extern __thread char *program_name; int show_license(void *optctx, const char *opt, const char *arg) diff --git a/apple/src/fftools_sync_queue.c b/apple/src/fftools_sync_queue.c new file mode 100644 index 0000000..cad5db3 --- /dev/null +++ b/apple/src/fftools_sync_queue.c @@ -0,0 +1,462 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of sync_queue.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/error.h" +#include "libavutil/fifo.h" +#include "libavutil/mathematics.h" +#include "libavutil/mem.h" + +#include "fftools_objpool.h" +#include "fftools_sync_queue.h" + +typedef struct SyncQueueStream { + AVFifo *fifo; + AVRational tb; + + /* stream head: largest timestamp seen */ + int64_t head_ts; + int limiting; + /* no more frames will be sent for this stream */ + int finished; + + uint64_t frames_sent; + uint64_t frames_max; +} SyncQueueStream; + +struct SyncQueue { + enum SyncQueueType type; + + /* no more frames will be sent for any stream */ + int finished; + /* sync head: the stream with the _smallest_ head timestamp + * this stream determines which frames can be output */ + int head_stream; + /* the finished stream with the smallest finish timestamp or -1 */ + int head_finished_stream; + + // maximum buffering duration in microseconds + int64_t buf_size_us; + + SyncQueueStream *streams; + unsigned int nb_streams; + + // pool of preallocated frames to avoid constant allocations + ObjPool *pool; +}; + +static void frame_move(const SyncQueue *sq, SyncQueueFrame dst, + SyncQueueFrame src) +{ + if (sq->type == SYNC_QUEUE_PACKETS) + av_packet_move_ref(dst.p, src.p); + else + av_frame_move_ref(dst.f, src.f); +} + +static int64_t frame_ts(const SyncQueue *sq, SyncQueueFrame frame) +{ + return (sq->type == SYNC_QUEUE_PACKETS) ? + frame.p->pts + frame.p->duration : + frame.f->pts + frame.f->duration; +} + +static int frame_null(const SyncQueue *sq, SyncQueueFrame frame) +{ + return (sq->type == SYNC_QUEUE_PACKETS) ? (frame.p == NULL) : (frame.f == NULL); +} + +static void finish_stream(SyncQueue *sq, unsigned int stream_idx) +{ + SyncQueueStream *st = &sq->streams[stream_idx]; + + st->finished = 1; + + if (st->limiting && st->head_ts != AV_NOPTS_VALUE) { + /* check if this stream is the new finished head */ + if (sq->head_finished_stream < 0 || + av_compare_ts(st->head_ts, st->tb, + sq->streams[sq->head_finished_stream].head_ts, + sq->streams[sq->head_finished_stream].tb) < 0) { + sq->head_finished_stream = stream_idx; + } + + /* mark as finished all streams that should no longer receive new frames, + * due to them being ahead of some finished stream */ + st = &sq->streams[sq->head_finished_stream]; + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st1 = &sq->streams[i]; + if (st != st1 && st1->head_ts != AV_NOPTS_VALUE && + av_compare_ts(st->head_ts, st->tb, st1->head_ts, st1->tb) <= 0) + st1->finished = 1; + } + } + + /* mark the whole queue as finished if all streams are finished */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + if (!sq->streams[i].finished) + return; + } + sq->finished = 1; +} + +static void queue_head_update(SyncQueue *sq) +{ + if (sq->head_stream < 0) { + /* wait for one timestamp in each stream before determining + * the queue head */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st = &sq->streams[i]; + if (st->limiting && st->head_ts == AV_NOPTS_VALUE) + return; + } + + // placeholder value, correct one will be found below + sq->head_stream = 0; + } + + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st_head = &sq->streams[sq->head_stream]; + SyncQueueStream *st_other = &sq->streams[i]; + if (st_other->limiting && st_other->head_ts != AV_NOPTS_VALUE && + av_compare_ts(st_other->head_ts, st_other->tb, + st_head->head_ts, st_head->tb) < 0) + sq->head_stream = i; + } +} + +/* update this stream's head timestamp */ +static void stream_update_ts(SyncQueue *sq, unsigned int stream_idx, int64_t ts) +{ + SyncQueueStream *st = &sq->streams[stream_idx]; + + if (ts == AV_NOPTS_VALUE || + (st->head_ts != AV_NOPTS_VALUE && st->head_ts >= ts)) + return; + + st->head_ts = ts; + + /* if this stream is now ahead of some finished stream, then + * this stream is also finished */ + if (sq->head_finished_stream >= 0 && + av_compare_ts(sq->streams[sq->head_finished_stream].head_ts, + sq->streams[sq->head_finished_stream].tb, + ts, st->tb) <= 0) + finish_stream(sq, stream_idx); + + /* update the overall head timestamp if it could have changed */ + if (st->limiting && + (sq->head_stream < 0 || sq->head_stream == stream_idx)) + queue_head_update(sq); +} + +/* If the queue for the given stream (or all streams when stream_idx=-1) + * is overflowing, trigger a fake heartbeat on lagging streams. + * + * @return 1 if heartbeat triggered, 0 otherwise + */ +static int overflow_heartbeat(SyncQueue *sq, int stream_idx) +{ + SyncQueueStream *st; + SyncQueueFrame frame; + int64_t tail_ts = AV_NOPTS_VALUE; + + /* if no stream specified, pick the one that is most ahead */ + if (stream_idx < 0) { + int64_t ts = AV_NOPTS_VALUE; + + for (int i = 0; i < sq->nb_streams; i++) { + st = &sq->streams[i]; + if (st->head_ts != AV_NOPTS_VALUE && + (ts == AV_NOPTS_VALUE || + av_compare_ts(ts, sq->streams[stream_idx].tb, + st->head_ts, st->tb) < 0)) { + ts = st->head_ts; + stream_idx = i; + } + } + /* no stream has a timestamp yet -> nothing to do */ + if (stream_idx < 0) + return 0; + } + + st = &sq->streams[stream_idx]; + + /* get the chosen stream's tail timestamp */ + for (size_t i = 0; tail_ts == AV_NOPTS_VALUE && + av_fifo_peek(st->fifo, &frame, 1, i) >= 0; i++) + tail_ts = frame_ts(sq, frame); + + /* overflow triggers when the tail is over specified duration behind the head */ + if (tail_ts == AV_NOPTS_VALUE || tail_ts >= st->head_ts || + av_rescale_q(st->head_ts - tail_ts, st->tb, AV_TIME_BASE_Q) < sq->buf_size_us) + return 0; + + /* signal a fake timestamp for all streams that prevent tail_ts from being output */ + tail_ts++; + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st1 = &sq->streams[i]; + int64_t ts; + + if (st == st1 || st1->finished || + (st1->head_ts != AV_NOPTS_VALUE && + av_compare_ts(tail_ts, st->tb, st1->head_ts, st1->tb) <= 0)) + continue; + + ts = av_rescale_q(tail_ts, st->tb, st1->tb); + if (st1->head_ts != AV_NOPTS_VALUE) + ts = FFMAX(st1->head_ts + 1, ts); + + stream_update_ts(sq, i, ts); + } + + return 1; +} + +int sq_send(SyncQueue *sq, unsigned int stream_idx, SyncQueueFrame frame) +{ + SyncQueueStream *st; + SyncQueueFrame dst; + int64_t ts; + int ret; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + av_assert0(st->tb.num > 0 && st->tb.den > 0); + + if (frame_null(sq, frame)) { + finish_stream(sq, stream_idx); + return 0; + } + if (st->finished) + return AVERROR_EOF; + + ret = objpool_get(sq->pool, (void**)&dst); + if (ret < 0) + return ret; + + frame_move(sq, dst, frame); + + ts = frame_ts(sq, dst); + + ret = av_fifo_write(st->fifo, &dst, 1); + if (ret < 0) { + frame_move(sq, frame, dst); + objpool_release(sq->pool, (void**)&dst); + return ret; + } + + stream_update_ts(sq, stream_idx, ts); + + st->frames_sent++; + if (st->frames_sent >= st->frames_max) + finish_stream(sq, stream_idx); + + return 0; +} + +static int receive_for_stream(SyncQueue *sq, unsigned int stream_idx, + SyncQueueFrame frame) +{ + SyncQueueStream *st_head = sq->head_stream >= 0 ? + &sq->streams[sq->head_stream] : NULL; + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + if (av_fifo_can_read(st->fifo)) { + SyncQueueFrame peek; + int64_t ts; + int cmp = 1; + + av_fifo_peek(st->fifo, &peek, 1, 0); + ts = frame_ts(sq, peek); + + /* check if this stream's tail timestamp does not overtake + * the overall queue head */ + if (ts != AV_NOPTS_VALUE && st_head) + cmp = av_compare_ts(ts, st->tb, st_head->head_ts, st_head->tb); + + /* We can release frames that do not end after the queue head. + * Frames with no timestamps are just passed through with no conditions. + */ + if (cmp <= 0 || ts == AV_NOPTS_VALUE) { + frame_move(sq, frame, peek); + objpool_release(sq->pool, (void**)&peek); + av_fifo_drain2(st->fifo, 1); + return 0; + } + } + + return (sq->finished || (st->finished && !av_fifo_can_read(st->fifo))) ? + AVERROR_EOF : AVERROR(EAGAIN); +} + +static int receive_internal(SyncQueue *sq, int stream_idx, SyncQueueFrame frame) +{ + int nb_eof = 0; + int ret; + + /* read a frame for a specific stream */ + if (stream_idx >= 0) { + ret = receive_for_stream(sq, stream_idx, frame); + return (ret < 0) ? ret : stream_idx; + } + + /* read a frame for any stream with available output */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + ret = receive_for_stream(sq, i, frame); + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { + nb_eof += (ret == AVERROR_EOF); + continue; + } + return (ret < 0) ? ret : i; + } + + return (nb_eof == sq->nb_streams) ? AVERROR_EOF : AVERROR(EAGAIN); +} + +int sq_receive(SyncQueue *sq, int stream_idx, SyncQueueFrame frame) +{ + int ret = receive_internal(sq, stream_idx, frame); + + /* try again if the queue overflowed and triggered a fake heartbeat + * for lagging streams */ + if (ret == AVERROR(EAGAIN) && overflow_heartbeat(sq, stream_idx)) + ret = receive_internal(sq, stream_idx, frame); + + return ret; +} + +int sq_add_stream(SyncQueue *sq, int limiting) +{ + SyncQueueStream *tmp, *st; + + tmp = av_realloc_array(sq->streams, sq->nb_streams + 1, sizeof(*sq->streams)); + if (!tmp) + return AVERROR(ENOMEM); + sq->streams = tmp; + + st = &sq->streams[sq->nb_streams]; + memset(st, 0, sizeof(*st)); + + st->fifo = av_fifo_alloc2(1, sizeof(SyncQueueFrame), AV_FIFO_FLAG_AUTO_GROW); + if (!st->fifo) + return AVERROR(ENOMEM); + + /* we set a valid default, so that a pathological stream that never + * receives even a real timebase (and no frames) won't stall all other + * streams forever; cf. overflow_heartbeat() */ + st->tb = (AVRational){ 1, 1 }; + st->head_ts = AV_NOPTS_VALUE; + st->frames_max = UINT64_MAX; + st->limiting = limiting; + + return sq->nb_streams++; +} + +void sq_set_tb(SyncQueue *sq, unsigned int stream_idx, AVRational tb) +{ + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + av_assert0(!av_fifo_can_read(st->fifo)); + + if (st->head_ts != AV_NOPTS_VALUE) + st->head_ts = av_rescale_q(st->head_ts, st->tb, tb); + + st->tb = tb; +} + +void sq_limit_frames(SyncQueue *sq, unsigned int stream_idx, uint64_t frames) +{ + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + st->frames_max = frames; + if (st->frames_sent >= st->frames_max) + finish_stream(sq, stream_idx); +} + +SyncQueue *sq_alloc(enum SyncQueueType type, int64_t buf_size_us) +{ + SyncQueue *sq = av_mallocz(sizeof(*sq)); + + if (!sq) + return NULL; + + sq->type = type; + sq->buf_size_us = buf_size_us; + + sq->head_stream = -1; + sq->head_finished_stream = -1; + + sq->pool = (type == SYNC_QUEUE_PACKETS) ? objpool_alloc_packets() : + objpool_alloc_frames(); + if (!sq->pool) { + av_freep(&sq); + return NULL; + } + + return sq; +} + +void sq_free(SyncQueue **psq) +{ + SyncQueue *sq = *psq; + + if (!sq) + return; + + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueFrame frame; + while (av_fifo_read(sq->streams[i].fifo, &frame, 1) >= 0) + objpool_release(sq->pool, (void**)&frame); + + av_fifo_freep2(&sq->streams[i].fifo); + } + + av_freep(&sq->streams); + + objpool_free(&sq->pool); + + av_freep(psq); +} diff --git a/apple/src/fftools_sync_queue.h b/apple/src/fftools_sync_queue.h new file mode 100644 index 0000000..0fd7b3c --- /dev/null +++ b/apple/src/fftools_sync_queue.h @@ -0,0 +1,122 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of sync_queue.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_SYNC_QUEUE_H +#define FFTOOLS_SYNC_QUEUE_H + +#include + +#include "libavcodec/packet.h" + +#include "libavutil/frame.h" + +enum SyncQueueType { + SYNC_QUEUE_PACKETS, + SYNC_QUEUE_FRAMES, +}; + +typedef union SyncQueueFrame { + AVFrame *f; + AVPacket *p; +} SyncQueueFrame; + +#define SQFRAME(frame) ((SyncQueueFrame){ .f = (frame) }) +#define SQPKT(pkt) ((SyncQueueFrame){ .p = (pkt) }) + +typedef struct SyncQueue SyncQueue; + +/** + * Allocate a sync queue of the given type. + * + * @param buf_size_us maximum duration that will be buffered in microseconds + */ +SyncQueue *sq_alloc(enum SyncQueueType type, int64_t buf_size_us); +void sq_free(SyncQueue **sq); + +/** + * Add a new stream to the sync queue. + * + * @param limiting whether the stream is limiting, i.e. no other stream can be + * longer than this one + * @return + * - a non-negative stream index on success + * - a negative error code on error + */ +int sq_add_stream(SyncQueue *sq, int limiting); + +/** + * Set the timebase for the stream with index stream_idx. Should be called + * before sending any frames for this stream. + */ +void sq_set_tb(SyncQueue *sq, unsigned int stream_idx, AVRational tb); + +/** + * Limit the number of output frames for stream with index stream_idx + * to max_frames. + */ +void sq_limit_frames(SyncQueue *sq, unsigned int stream_idx, + uint64_t max_frames); + +/** + * Submit a frame for the stream with index stream_idx. + * + * On success, the sync queue takes ownership of the frame and will reset the + * contents of the supplied frame. On failure, the frame remains owned by the + * caller. + * + * Sending a frame with NULL contents marks the stream as finished. + * + * @return + * - 0 on success + * - AVERROR_EOF when no more frames should be submitted for this stream + * - another a negative error code on failure + */ +int sq_send(SyncQueue *sq, unsigned int stream_idx, SyncQueueFrame frame); + +/** + * Read a frame from the queue. + * + * @param stream_idx index of the stream to read a frame for. May be -1, then + * try to read a frame from any stream that is ready for + * output. + * @param frame output frame will be written here on success. The frame is owned + * by the caller. + * + * @return + * - a non-negative index of the stream to which the returned frame belongs + * - AVERROR(EAGAIN) when more frames need to be submitted to the queue + * - AVERROR_EOF when no more frames will be available for this stream (for any + * stream if stream_idx is -1) + * - another negative error code on failure + */ +int sq_receive(SyncQueue *sq, int stream_idx, SyncQueueFrame frame); + +#endif // FFTOOLS_SYNC_QUEUE_H diff --git a/apple/src/fftools_thread_queue.c b/apple/src/fftools_thread_queue.c new file mode 100644 index 0000000..e8eade5 --- /dev/null +++ b/apple/src/fftools_thread_queue.c @@ -0,0 +1,259 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of thread_queue.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/error.h" +#include "libavutil/fifo.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" +#include "libavutil/thread.h" + +#include "fftools_objpool.h" +#include "fftools_thread_queue.h" + +enum { + FINISHED_SEND = (1 << 0), + FINISHED_RECV = (1 << 1), +}; + +typedef struct FifoElem { + void *obj; + unsigned int stream_idx; +} FifoElem; + +struct ThreadQueue { + int *finished; + unsigned int nb_streams; + + AVFifo *fifo; + + ObjPool *obj_pool; + void (*obj_move)(void *dst, void *src); + + pthread_mutex_t lock; + pthread_cond_t cond; +}; + +void tq_free(ThreadQueue **ptq) +{ + ThreadQueue *tq = *ptq; + + if (!tq) + return; + + if (tq->fifo) { + FifoElem elem; + while (av_fifo_read(tq->fifo, &elem, 1) >= 0) + objpool_release(tq->obj_pool, &elem.obj); + } + av_fifo_freep2(&tq->fifo); + + objpool_free(&tq->obj_pool); + + av_freep(&tq->finished); + + pthread_cond_destroy(&tq->cond); + pthread_mutex_destroy(&tq->lock); + + av_freep(ptq); +} + +ThreadQueue *tq_alloc(unsigned int nb_streams, size_t queue_size, + ObjPool *obj_pool, void (*obj_move)(void *dst, void *src)) +{ + ThreadQueue *tq; + int ret; + + tq = av_mallocz(sizeof(*tq)); + if (!tq) + return NULL; + + ret = pthread_cond_init(&tq->cond, NULL); + if (ret) { + av_freep(&tq); + return NULL; + } + + ret = pthread_mutex_init(&tq->lock, NULL); + if (ret) { + pthread_cond_destroy(&tq->cond); + av_freep(&tq); + return NULL; + } + + tq->finished = av_calloc(nb_streams, sizeof(*tq->finished)); + if (!tq->finished) + goto fail; + tq->nb_streams = nb_streams; + + tq->fifo = av_fifo_alloc2(queue_size, sizeof(FifoElem), 0); + if (!tq->fifo) + goto fail; + + tq->obj_pool = obj_pool; + tq->obj_move = obj_move; + + return tq; +fail: + tq_free(&tq); + return NULL; +} + +int tq_send(ThreadQueue *tq, unsigned int stream_idx, void *data) +{ + int *finished; + int ret; + + av_assert0(stream_idx < tq->nb_streams); + finished = &tq->finished[stream_idx]; + + pthread_mutex_lock(&tq->lock); + + if (*finished & FINISHED_SEND) { + ret = AVERROR(EINVAL); + goto finish; + } + + while (!(*finished & FINISHED_RECV) && !av_fifo_can_write(tq->fifo)) + pthread_cond_wait(&tq->cond, &tq->lock); + + if (*finished & FINISHED_RECV) { + ret = AVERROR_EOF; + *finished |= FINISHED_SEND; + } else { + FifoElem elem = { .stream_idx = stream_idx }; + + ret = objpool_get(tq->obj_pool, &elem.obj); + if (ret < 0) + goto finish; + + tq->obj_move(elem.obj, data); + + ret = av_fifo_write(tq->fifo, &elem, 1); + av_assert0(ret >= 0); + pthread_cond_broadcast(&tq->cond); + } + +finish: + pthread_mutex_unlock(&tq->lock); + + return ret; +} + +static int receive_locked(ThreadQueue *tq, int *stream_idx, + void *data) +{ + FifoElem elem; + unsigned int nb_finished = 0; + + if (av_fifo_read(tq->fifo, &elem, 1) >= 0) { + tq->obj_move(data, elem.obj); + objpool_release(tq->obj_pool, &elem.obj); + *stream_idx = elem.stream_idx; + return 0; + } + + for (unsigned int i = 0; i < tq->nb_streams; i++) { + if (!(tq->finished[i] & FINISHED_SEND)) + continue; + + /* return EOF to the consumer at most once for each stream */ + if (!(tq->finished[i] & FINISHED_RECV)) { + tq->finished[i] |= FINISHED_RECV; + *stream_idx = i; + return AVERROR_EOF; + } + + nb_finished++; + } + + return nb_finished == tq->nb_streams ? AVERROR_EOF : AVERROR(EAGAIN); +} + +int tq_receive(ThreadQueue *tq, int *stream_idx, void *data) +{ + int ret; + + *stream_idx = -1; + + pthread_mutex_lock(&tq->lock); + + while (1) { + ret = receive_locked(tq, stream_idx, data); + if (ret == AVERROR(EAGAIN)) { + pthread_cond_wait(&tq->cond, &tq->lock); + continue; + } + + break; + } + + if (ret == 0) + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); + + return ret; +} + +void tq_send_finish(ThreadQueue *tq, unsigned int stream_idx) +{ + av_assert0(stream_idx < tq->nb_streams); + + pthread_mutex_lock(&tq->lock); + + /* mark the stream as send-finished; + * next time the consumer thread tries to read this stream it will get + * an EOF and recv-finished flag will be set */ + tq->finished[stream_idx] |= FINISHED_SEND; + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); +} + +void tq_receive_finish(ThreadQueue *tq, unsigned int stream_idx) +{ + av_assert0(stream_idx < tq->nb_streams); + + pthread_mutex_lock(&tq->lock); + + /* mark the stream as recv-finished; + * next time the producer thread tries to send for this stream, it will + * get an EOF and send-finished flag will be set */ + tq->finished[stream_idx] |= FINISHED_RECV; + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); +} diff --git a/apple/src/fftools_thread_queue.h b/apple/src/fftools_thread_queue.h new file mode 100644 index 0000000..b8736dd --- /dev/null +++ b/apple/src/fftools_thread_queue.h @@ -0,0 +1,94 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of thread_queue.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_THREAD_QUEUE_H +#define FFTOOLS_THREAD_QUEUE_H + +#include + +#include "fftools_objpool.h" + +typedef struct ThreadQueue ThreadQueue; + +/** + * Allocate a queue for sending data between threads. + * + * @param nb_streams number of streams for which a distinct EOF state is + * maintained + * @param queue_size number of items that can be stored in the queue without + * blocking + * @param obj_pool object pool that will be used to allocate items stored in the + * queue; the pool becomes owned by the queue + * @param callback that moves the contents between two data pointers + */ +ThreadQueue *tq_alloc(unsigned int nb_streams, size_t queue_size, + ObjPool *obj_pool, void (*obj_move)(void *dst, void *src)); +void tq_free(ThreadQueue **tq); + +/** + * Send an item for the given stream to the queue. + * + * @param data the item to send, its contents will be moved using the callback + * provided to tq_alloc(); on failure the item will be left + * untouched + * @return + * - 0 the item was successfully sent + * - AVERROR(ENOMEM) could not allocate an item for writing to the FIFO + * - AVERROR(EINVAL) the sending side has previously been marked as finished + * - AVERROR_EOF the receiving side has marked the given stream as finished + */ +int tq_send(ThreadQueue *tq, unsigned int stream_idx, void *data); +/** + * Mark the given stream finished from the sending side. + */ +void tq_send_finish(ThreadQueue *tq, unsigned int stream_idx); + +/** + * Read the next item from the queue. + * + * @param stream_idx the index of the stream that was processed or -1 will be + * written here + * @param data the data item will be written here on success using the + * callback provided to tq_alloc() + * @return + * - 0 a data item was successfully read; *stream_idx contains a non-negative + * stream index + * - AVERROR_EOF When *stream_idx is non-negative, this signals that the sending + * side has marked the given stream as finished. This will happen at most once + * for each stream. When *stream_idx is -1, all streams are done. + */ +int tq_receive(ThreadQueue *tq, int *stream_idx, void *data); +/** + * Mark the given stream finished from the receiving side. + */ +void tq_receive_finish(ThreadQueue *tq, unsigned int stream_idx); + +#endif // FFTOOLS_THREAD_QUEUE_H diff --git a/linux/Doxyfile b/linux/Doxyfile index bb1352d..11774d7 100644 --- a/linux/Doxyfile +++ b/linux/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "FFmpegKit Linux API" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 5.1 +PROJECT_NUMBER = 6.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/linux/configure.ac b/linux/configure.ac index 5ffa498..d180e7f 100644 --- a/linux/configure.ac +++ b/linux/configure.ac @@ -1,6 +1,6 @@ -# ffmpeg-kit 5.1 configure.ac +# ffmpeg-kit 6.0 configure.ac -AC_INIT([ffmpeg-kit], [5.1], [https://github.com/arthenica/ffmpeg-kit/issues/new]) +AC_INIT([ffmpeg-kit], [6.0], [https://github.com/arthenica/ffmpeg-kit/issues/new]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([src/FFmpegKit.cpp]) @@ -28,7 +28,8 @@ CFLAGS="$cflags_bckup" FFMPEG_LIBS="-lavcodec -lavfilter -lavformat -lavutil -lswscale -lswresample" AC_SUBST(FFMPEG_LIBS) -VERSION_INFO="9:1:5" +# CURRENT INTERFACE:REVISION:AGE +VERSION_INFO="11:0:6" AC_SUBST(VERSION_INFO) LT_INIT diff --git a/linux/src/FFmpegKit.cpp b/linux/src/FFmpegKit.cpp index d9201db..c414177 100644 --- a/linux/src/FFmpegKit.cpp +++ b/linux/src/FFmpegKit.cpp @@ -17,14 +17,13 @@ * along with FFmpegKit. If not, see . */ -extern "C" { - #include "fftools_ffmpeg.h" -} #include "ArchDetect.h" #include "FFmpegKit.h" #include "FFmpegKitConfig.h" #include "Packages.h" +extern void cancel_operation(long id); + extern void* ffmpegKitInitialize(); const void* _ffmpegKitInitializeri{ffmpegKitInitialize()}; diff --git a/linux/src/FFmpegKitConfig.cpp b/linux/src/FFmpegKitConfig.cpp index 60aefdb..eabc31c 100644 --- a/linux/src/FFmpegKitConfig.cpp +++ b/linux/src/FFmpegKitConfig.cpp @@ -23,7 +23,7 @@ extern "C" { #include "libavutil/ffversion.h" #include "libavutil/bprint.h" - #include "fftools_ffmpeg.h" + #include "fftools_cmdutils.h" } #include "ArchDetect.h" #include "FFmpegKit.h" @@ -44,6 +44,10 @@ extern "C" { #include #include +extern void set_report_callback(void (*callback)(int, float, float, int64_t, double, double, double)); + +extern void cancel_operation(long id); + /** * Generates ids for named ffmpeg kit pipes. */ @@ -193,7 +197,7 @@ class CallbackData { const float videoFps, const float videoQuality, const int64_t size, - const int time, + const double time, const double bitrate, const double speed) : _type{StatisticsType}, @@ -239,7 +243,7 @@ class CallbackData { return _statisticsSize; } - int getStatisticsTime() { + double getStatisticsTime() { return _statisticsTime; } @@ -262,7 +266,7 @@ class CallbackData { float _statisticsFps; // statistics fps float _statisticsQuality; // statistics quality int64_t _statisticsSize; // statistics size - int _statisticsTime; // statistics time + double _statisticsTime; // statistics time double _statisticsBitrate; // statistics bitrate double _statisticsSpeed; // statistics speed }; @@ -516,7 +520,7 @@ void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, v * @param bitrate output bit rate in kbits/s * @param speed processing speed = processed duration / operation duration */ -void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) { +void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, double time, double bitrate, double speed) { statisticsCallbackDataAdd(frameNumber, fps, quality, size, time, bitrate, speed); } @@ -604,7 +608,7 @@ static void process_log(long sessionId, int levelValueInt, AVBPrint* logMessage) } } -void process_statistics(long sessionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) { +void process_statistics(long sessionId, int videoFrameNumber, float videoFps, float videoQuality, long size, double time, double bitrate, double speed) { std::shared_ptr statistics = std::make_shared(sessionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed); auto session = ffmpegkit::FFmpegKitConfig::getSession(sessionId); diff --git a/linux/src/FFmpegKitConfig.h b/linux/src/FFmpegKitConfig.h index 8d7e53b..b76cc02 100644 --- a/linux/src/FFmpegKitConfig.h +++ b/linux/src/FFmpegKitConfig.h @@ -42,7 +42,7 @@ namespace ffmpegkit { public: /** Global library version */ - static constexpr const char* FFmpegKitVersion = "5.1"; + static constexpr const char* FFmpegKitVersion = "6.0"; /** * Prefix of named pipes created by ffmpeg-kit. diff --git a/linux/src/FFprobeKit.cpp b/linux/src/FFprobeKit.cpp index a060c6e..13415c2 100644 --- a/linux/src/FFprobeKit.cpp +++ b/linux/src/FFprobeKit.cpp @@ -17,7 +17,6 @@ * along with FFmpegKit. If not, see . */ -#include "fftools_ffmpeg.h" #include "FFmpegKit.h" #include "FFmpegKitConfig.h" #include "FFprobeKit.h" diff --git a/linux/src/Makefile.am b/linux/src/Makefile.am index aa5fd1c..7b8d25c 100644 --- a/linux/src/Makefile.am +++ b/linux/src/Makefile.am @@ -22,12 +22,17 @@ libffmpegkit_la_SOURCES = \ ffmpegkit_exception.cpp \ fftools_cmdutils.c \ fftools_ffmpeg.c \ + fftools_ffmpeg_demux.c \ fftools_ffmpeg_filter.c \ fftools_ffmpeg_hw.c \ fftools_ffmpeg_mux.c \ + fftools_ffmpeg_mux_init.c \ fftools_ffmpeg_opt.c \ fftools_ffprobe.c \ - fftools_opt_common.c + fftools_objpool.c \ + fftools_opt_common.c \ + fftools_sync_queue.c \ + fftools_thread_queue.c include_HEADERS = \ AbstractSession.h \ @@ -59,8 +64,12 @@ include_HEADERS = \ ffmpegkit_exception.h \ fftools_cmdutils.h \ fftools_ffmpeg.h \ + fftools_ffmpeg_mux.h \ fftools_fopen_utf8.h \ - fftools_opt_common.h + fftools_objpool.h \ + fftools_opt_common.h \ + fftools_sync_queue.h \ + fftools_thread_queue.h libffmpegkit_la_CFLAGS = $(CFLAGS) libffmpegkit_la_OBJCFLAGS = $(CFLAGS) diff --git a/linux/src/Makefile.in b/linux/src/Makefile.in index 66be091..485babf 100644 --- a/linux/src/Makefile.in +++ b/linux/src/Makefile.in @@ -147,12 +147,17 @@ am_libffmpegkit_la_OBJECTS = libffmpegkit_la-AbstractSession.lo \ libffmpegkit_la-ffmpegkit_exception.lo \ libffmpegkit_la-fftools_cmdutils.lo \ libffmpegkit_la-fftools_ffmpeg.lo \ + libffmpegkit_la-fftools_ffmpeg_demux.lo \ libffmpegkit_la-fftools_ffmpeg_filter.lo \ libffmpegkit_la-fftools_ffmpeg_hw.lo \ libffmpegkit_la-fftools_ffmpeg_mux.lo \ + libffmpegkit_la-fftools_ffmpeg_mux_init.lo \ libffmpegkit_la-fftools_ffmpeg_opt.lo \ libffmpegkit_la-fftools_ffprobe.lo \ - libffmpegkit_la-fftools_opt_common.lo + libffmpegkit_la-fftools_objpool.lo \ + libffmpegkit_la-fftools_opt_common.lo \ + libffmpegkit_la-fftools_sync_queue.lo \ + libffmpegkit_la-fftools_thread_queue.lo libffmpegkit_la_OBJECTS = $(am_libffmpegkit_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) @@ -196,12 +201,17 @@ am__depfiles_remade = ./$(DEPDIR)/libffmpegkit_la-AbstractSession.Plo \ ./$(DEPDIR)/libffmpegkit_la-ffmpegkit_exception.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_cmdutils.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_hw.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo \ ./$(DEPDIR)/libffmpegkit_la-fftools_ffprobe.Plo \ - ./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo + ./$(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo \ + ./$(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -415,12 +425,17 @@ libffmpegkit_la_SOURCES = \ ffmpegkit_exception.cpp \ fftools_cmdutils.c \ fftools_ffmpeg.c \ + fftools_ffmpeg_demux.c \ fftools_ffmpeg_filter.c \ fftools_ffmpeg_hw.c \ fftools_ffmpeg_mux.c \ + fftools_ffmpeg_mux_init.c \ fftools_ffmpeg_opt.c \ fftools_ffprobe.c \ - fftools_opt_common.c + fftools_objpool.c \ + fftools_opt_common.c \ + fftools_sync_queue.c \ + fftools_thread_queue.c include_HEADERS = \ AbstractSession.h \ @@ -452,8 +467,12 @@ include_HEADERS = \ ffmpegkit_exception.h \ fftools_cmdutils.h \ fftools_ffmpeg.h \ + fftools_ffmpeg_mux.h \ fftools_fopen_utf8.h \ - fftools_opt_common.h + fftools_objpool.h \ + fftools_opt_common.h \ + fftools_sync_queue.h \ + fftools_thread_queue.h libffmpegkit_la_CFLAGS = $(CFLAGS) libffmpegkit_la_OBJCFLAGS = $(CFLAGS) @@ -556,12 +575,17 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-ffmpegkit_exception.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_cmdutils.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_hw.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_ffprobe.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @@ -607,6 +631,13 @@ libffmpegkit_la-fftools_ffmpeg.lo: fftools_ffmpeg.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffmpeg.lo `test -f 'fftools_ffmpeg.c' || echo '$(srcdir)/'`fftools_ffmpeg.c +libffmpegkit_la-fftools_ffmpeg_demux.lo: fftools_ffmpeg_demux.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_ffmpeg_demux.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Tpo -c -o libffmpegkit_la-fftools_ffmpeg_demux.lo `test -f 'fftools_ffmpeg_demux.c' || echo '$(srcdir)/'`fftools_ffmpeg_demux.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Tpo $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_ffmpeg_demux.c' object='libffmpegkit_la-fftools_ffmpeg_demux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffmpeg_demux.lo `test -f 'fftools_ffmpeg_demux.c' || echo '$(srcdir)/'`fftools_ffmpeg_demux.c + libffmpegkit_la-fftools_ffmpeg_filter.lo: fftools_ffmpeg_filter.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_ffmpeg_filter.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Tpo -c -o libffmpegkit_la-fftools_ffmpeg_filter.lo `test -f 'fftools_ffmpeg_filter.c' || echo '$(srcdir)/'`fftools_ffmpeg_filter.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Tpo $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo @@ -628,6 +659,13 @@ libffmpegkit_la-fftools_ffmpeg_mux.lo: fftools_ffmpeg_mux.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffmpeg_mux.lo `test -f 'fftools_ffmpeg_mux.c' || echo '$(srcdir)/'`fftools_ffmpeg_mux.c +libffmpegkit_la-fftools_ffmpeg_mux_init.lo: fftools_ffmpeg_mux_init.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_ffmpeg_mux_init.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Tpo -c -o libffmpegkit_la-fftools_ffmpeg_mux_init.lo `test -f 'fftools_ffmpeg_mux_init.c' || echo '$(srcdir)/'`fftools_ffmpeg_mux_init.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Tpo $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_ffmpeg_mux_init.c' object='libffmpegkit_la-fftools_ffmpeg_mux_init.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffmpeg_mux_init.lo `test -f 'fftools_ffmpeg_mux_init.c' || echo '$(srcdir)/'`fftools_ffmpeg_mux_init.c + libffmpegkit_la-fftools_ffmpeg_opt.lo: fftools_ffmpeg_opt.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_ffmpeg_opt.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Tpo -c -o libffmpegkit_la-fftools_ffmpeg_opt.lo `test -f 'fftools_ffmpeg_opt.c' || echo '$(srcdir)/'`fftools_ffmpeg_opt.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Tpo $(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo @@ -642,6 +680,13 @@ libffmpegkit_la-fftools_ffprobe.lo: fftools_ffprobe.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_ffprobe.lo `test -f 'fftools_ffprobe.c' || echo '$(srcdir)/'`fftools_ffprobe.c +libffmpegkit_la-fftools_objpool.lo: fftools_objpool.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_objpool.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_objpool.Tpo -c -o libffmpegkit_la-fftools_objpool.lo `test -f 'fftools_objpool.c' || echo '$(srcdir)/'`fftools_objpool.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_objpool.Tpo $(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_objpool.c' object='libffmpegkit_la-fftools_objpool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_objpool.lo `test -f 'fftools_objpool.c' || echo '$(srcdir)/'`fftools_objpool.c + libffmpegkit_la-fftools_opt_common.lo: fftools_opt_common.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_opt_common.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_opt_common.Tpo -c -o libffmpegkit_la-fftools_opt_common.lo `test -f 'fftools_opt_common.c' || echo '$(srcdir)/'`fftools_opt_common.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_opt_common.Tpo $(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo @@ -649,6 +694,20 @@ libffmpegkit_la-fftools_opt_common.lo: fftools_opt_common.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_opt_common.lo `test -f 'fftools_opt_common.c' || echo '$(srcdir)/'`fftools_opt_common.c +libffmpegkit_la-fftools_sync_queue.lo: fftools_sync_queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_sync_queue.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Tpo -c -o libffmpegkit_la-fftools_sync_queue.lo `test -f 'fftools_sync_queue.c' || echo '$(srcdir)/'`fftools_sync_queue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Tpo $(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_sync_queue.c' object='libffmpegkit_la-fftools_sync_queue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_sync_queue.lo `test -f 'fftools_sync_queue.c' || echo '$(srcdir)/'`fftools_sync_queue.c + +libffmpegkit_la-fftools_thread_queue.lo: fftools_thread_queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -MT libffmpegkit_la-fftools_thread_queue.lo -MD -MP -MF $(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Tpo -c -o libffmpegkit_la-fftools_thread_queue.lo `test -f 'fftools_thread_queue.c' || echo '$(srcdir)/'`fftools_thread_queue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Tpo $(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fftools_thread_queue.c' object='libffmpegkit_la-fftools_thread_queue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libffmpegkit_la_CFLAGS) $(CFLAGS) -c -o libffmpegkit_la-fftools_thread_queue.lo `test -f 'fftools_thread_queue.c' || echo '$(srcdir)/'`fftools_thread_queue.c + .cpp.o: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @@ -966,12 +1025,17 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/libffmpegkit_la-ffmpegkit_exception.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_cmdutils.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_hw.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffprobe.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -1036,12 +1100,17 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libffmpegkit_la-ffmpegkit_exception.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_cmdutils.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_demux.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_filter.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_hw.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_mux_init.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffmpeg_opt.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_ffprobe.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_objpool.Plo -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_opt_common.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_sync_queue.Plo + -rm -f ./$(DEPDIR)/libffmpegkit_la-fftools_thread_queue.Plo -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic diff --git a/linux/src/Statistics.cpp b/linux/src/Statistics.cpp index 23a4948..5250cd3 100644 --- a/linux/src/Statistics.cpp +++ b/linux/src/Statistics.cpp @@ -19,7 +19,7 @@ #include "Statistics.h" -ffmpegkit::Statistics::Statistics(const long sessionId, const int videoFrameNumber, const float videoFps, const float videoQuality, const int64_t size, const int time, const double bitrate, const double speed) : +ffmpegkit::Statistics::Statistics(const long sessionId, const int videoFrameNumber, const float videoFps, const float videoQuality, const int64_t size, const double time, const double bitrate, const double speed) : _sessionId{sessionId}, _videoFrameNumber{videoFrameNumber}, _videoFps{videoFps}, _videoQuality{videoQuality}, _size{size}, _time{time}, _bitrate{bitrate}, _speed{speed} { } @@ -43,7 +43,7 @@ int64_t ffmpegkit::Statistics::getSize() { return _size; } -int ffmpegkit::Statistics::getTime() { +double ffmpegkit::Statistics::getTime() { return _time; } diff --git a/linux/src/Statistics.h b/linux/src/Statistics.h index 2d2d0a7..81a1d51 100644 --- a/linux/src/Statistics.h +++ b/linux/src/Statistics.h @@ -30,13 +30,13 @@ namespace ffmpegkit { class Statistics { public: - Statistics(const long sessionId, const int videoFrameNumber, const float videoFps, const float videoQuality, const int64_t size, const int time, const double bitrate, const double speed); + Statistics(const long sessionId, const int videoFrameNumber, const float videoFps, const float videoQuality, const int64_t size, const double time, const double bitrate, const double speed); long getSessionId(); int getVideoFrameNumber(); float getVideoFps(); float getVideoQuality(); int64_t getSize(); - int getTime(); + double getTime(); double getBitrate(); double getSpeed(); @@ -46,7 +46,7 @@ namespace ffmpegkit { float _videoFps; float _videoQuality; int64_t _size; - int _time; + double _time; double _bitrate; double _speed; }; diff --git a/linux/src/fftools_cmdutils.c b/linux/src/fftools_cmdutils.c index c0e3f58..e5797e0 100644 --- a/linux/src/fftools_cmdutils.c +++ b/linux/src/fftools_cmdutils.c @@ -1,7 +1,8 @@ /* * Various utilities for command line tools * Copyright (c) 2000-2003 Fabrice Bellard - * Copyright (c) 2018 Taner Sener + * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -25,6 +26,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -129,11 +136,18 @@ void register_exit(void (*cb)(int ret)) program_exit = cb; } +void report_and_exit(int ret) +{ + av_log(NULL, AV_LOG_FATAL, "%s\n", av_err2str(ret)); + exit_program(AVUNERROR(ret)); +} + void exit_program(int ret) { if (program_exit) program_exit(ret); + // FFmpegKit // exit disabled and replaced with longjmp, exit value stored in longjmp_value // exit(ret); longjmp_value = ret; @@ -696,7 +710,7 @@ static void init_parse_context(OptionParseContext *octx, octx->nb_groups = nb_groups; octx->groups = av_calloc(octx->nb_groups, sizeof(*octx->groups)); if (!octx->groups) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); for (i = 0; i < octx->nb_groups; i++) octx->groups[i].group_def = &groups[i]; @@ -843,12 +857,7 @@ do { \ void print_error(const char *filename, int err) { - char errbuf[128]; - const char *errbuf_ptr = errbuf; - - if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) - errbuf_ptr = strerror(AVUNERROR(err)); - av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, errbuf_ptr); + av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, av_err2str(err)); } int read_yesno(void) @@ -971,7 +980,7 @@ AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, break; } - while ((t = av_dict_get(opts, "", t, AV_DICT_IGNORE_SUFFIX))) { + while ((t = av_dict_iterate(opts, t))) { const AVClass *priv_class; char *p = strchr(t->key, ':'); @@ -1009,11 +1018,8 @@ AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, if (!s->nb_streams) return NULL; opts = av_calloc(s->nb_streams, sizeof(*opts)); - if (!opts) { - av_log(NULL, AV_LOG_ERROR, - "Could not alloc memory for stream options.\n"); - exit_program(1); - } + if (!opts) + report_and_exit(AVERROR(ENOMEM)); for (i = 0; i < s->nb_streams; i++) opts[i] = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id, s, s->streams[i], NULL); @@ -1028,10 +1034,8 @@ void *grow_array(void *array, int elem_size, int *size, int new_size) } if (*size < new_size) { uint8_t *tmp = av_realloc_array(array, new_size, elem_size); - if (!tmp) { - av_log(NULL, AV_LOG_ERROR, "Could not alloc buffer.\n"); - exit_program(1); - } + if (!tmp) + report_and_exit(AVERROR(ENOMEM)); memset(tmp + *size*elem_size, 0, (new_size-*size) * elem_size); *size = new_size; return tmp; @@ -1044,10 +1048,8 @@ void *allocate_array_elem(void *ptr, size_t elem_size, int *nb_elems) void *new_elem; if (!(new_elem = av_mallocz(elem_size)) || - av_dynarray_add_nofree(ptr, nb_elems, new_elem) < 0) { - av_log(NULL, AV_LOG_ERROR, "Could not alloc buffer.\n"); - exit_program(1); - } + av_dynarray_add_nofree(ptr, nb_elems, new_elem) < 0) + report_and_exit(AVERROR(ENOMEM)); return new_elem; } diff --git a/linux/src/fftools_cmdutils.h b/linux/src/fftools_cmdutils.h index 7d305ee..b925cf0 100644 --- a/linux/src/fftools_cmdutils.h +++ b/linux/src/fftools_cmdutils.h @@ -1,7 +1,8 @@ /* * Various utilities for command line tools * copyright (c) 2003 Fabrice Bellard - * copyright (c) 2018 Taner Sener + * copyright (c) 2018-2022 Taner Sener + * copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -25,6 +26,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -95,6 +102,17 @@ extern __thread int find_stream_info; */ void register_exit(void (*cb)(int ret)); +/** + * Reports an error corresponding to the provided + * AVERROR code and calls exit_program() with the + * corresponding POSIX error code. + * @note ret must be an AVERROR-value of a POSIX error code + * (i.e. AVERROR(EFOO) and not AVERROR_FOO). + * library functions can return both, so call this only + * with AVERROR(EFOO) of your own. + */ +void report_and_exit(int ret) av_noreturn; + /** * Wraps exit with a program-specific cleanup routine. */ @@ -232,11 +250,6 @@ void show_help_children(const AVClass *clazz, int flags); void show_help_default_ffmpeg(const char *opt, const char *arg); void show_help_default_ffprobe(const char *opt, const char *arg); -/** - * Generic -h handler common to all fftools. - */ -int show_help(void *optctx, const char *opt, const char *arg); - /** * Parse the command line arguments. * diff --git a/linux/src/fftools_ffmpeg.c b/linux/src/fftools_ffmpeg.c index 67d4683..731128f 100644 --- a/linux/src/fftools_ffmpeg.c +++ b/linux/src/fftools_ffmpeg.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2000-2003 Fabrice Bellard * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -29,6 +30,15 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - vstats_file, received_sigterm and received_nb_signals updated as thread-local + * - forward_report method signature updated + * - time field in report_callback/forward_report/set_report_callback updated as double + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -160,19 +170,18 @@ #include "fftools_ffmpeg.h" #include "fftools_cmdutils.h" +#include "fftools_sync_queue.h" #include "libavutil/avassert.h" -static FILE *vstats_file; +static __thread FILE *vstats_file; -const char *const forced_keyframes_const_names[] = { - "n", - "n_forced", - "prev_forced_n", - "prev_forced_t", - "t", - NULL -}; +// optionally attached as opaque_ref to decoded AVFrames +typedef struct FrameData { + uint64_t idx; + int64_t pts; + AVRational tb; +} FrameData; typedef struct BenchmarkTimeStamps { int64_t real_usec; @@ -180,6 +189,7 @@ typedef struct BenchmarkTimeStamps { int64_t sys_usec; } BenchmarkTimeStamps; +static int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt); static BenchmarkTimeStamps get_benchmark_time_stamps(void); static int64_t getmaxrss(void); static int ifilter_has_all_input_formats(FilterGraph *fg); @@ -190,20 +200,12 @@ __thread int64_t nb_frames_drop = 0; __thread int64_t decode_error_stat[2]; __thread unsigned nb_output_dumped = 0; -__thread int want_sdp = 1; - __thread BenchmarkTimeStamps current_time; __thread AVIOContext *progress_avio = NULL; -__thread uint8_t *subtitle_out; - -__thread InputStream **input_streams = NULL; -__thread int nb_input_streams = 0; __thread InputFile **input_files = NULL; __thread int nb_input_files = 0; -__thread OutputStream **output_streams = NULL; -__thread int nb_output_streams = 0; __thread OutputFile **output_files = NULL; __thread int nb_output_files = 0; @@ -215,7 +217,7 @@ __thread int64_t keyboard_last_time = 0; __thread int first_report = 1; __thread int qp_histogram[52]; -void (*report_callback)(int, float, float, int64_t, int, double, double) = NULL; +void (*report_callback)(int, float, float, int64_t, double, double, double) = NULL; extern int opt_map(void *optctx, const char *opt, const char *arg); extern int opt_map_channel(void *optctx, const char *opt, const char *arg); @@ -263,7 +265,6 @@ extern int opt_data_codec(void *optctx, const char *opt, const char *arg); extern int opt_init_hw_device(void *optctx, const char *opt, const char *arg); extern int opt_filter_hw_device(void *optctx, const char *opt, const char *arg); extern int opt_filter_threads(void *optctx, const char *opt, const char *arg); -extern __thread int input_sync; extern __thread int file_overwrite; extern __thread int no_file_overwrite; extern __thread int do_psnr; @@ -278,10 +279,6 @@ __thread struct termios oldtty; __thread int restore_tty; #endif -#if HAVE_THREADS -static void free_input_threads(void); -#endif - extern volatile int handleSIGQUIT; extern volatile int handleSIGINT; extern volatile int handleSIGTERM; @@ -388,7 +385,7 @@ void sub2video_update(InputStream *ist, int64_t heartbeat_pts, AVSubtitle *sub) num_rects = 0; } if (sub2video_get_blank_frame(ist) < 0) { - av_log(ist->dec_ctx, AV_LOG_ERROR, + av_log(NULL, AV_LOG_ERROR, "Impossible to get a blank canvas.\n"); return; } @@ -412,7 +409,7 @@ static void sub2video_heartbeat(InputStream *ist, int64_t pts) video frames could be accumulating in the filter graph while a filter (possibly overlay) is desperately waiting for a subtitle frame. */ for (i = 0; i < infile->nb_streams; i++) { - InputStream *ist2 = input_streams[infile->ist_index + i]; + InputStream *ist2 = infile->streams[i]; if (!ist2->sub2video.frame) continue; /* subtitles seem to be usually muxed ahead of other streams; @@ -463,20 +460,32 @@ void term_exit(void) term_exit_sigsafe(); } -static volatile int received_sigterm = 0; -static volatile int received_nb_signals = 0; +__thread int received_sigterm = 0; +__thread int received_nb_signals = 0; __thread atomic_int transcode_init_done = ATOMIC_VAR_INIT(0); __thread int ffmpeg_exited = 0; __thread int main_ffmpeg_return_code = 0; __thread int64_t copy_ts_first_pts = AV_NOPTS_VALUE; extern __thread int longjmp_value; +extern __thread int want_sdp; +struct EncStatsFile; +extern __thread struct EncStatsFile *enc_stats_files; +extern __thread int nb_enc_stats_files; static void sigterm_handler(int sig) { + // int ret; received_sigterm = sig; received_nb_signals++; term_exit_sigsafe(); + // FFmpegKit - Hard Exit Disabled + // if(received_nb_signals > 3) { + // ret = write(2/*STDERR_FILENO*/, "Received > 3 system signals, hard exiting\n", + // strlen("Received > 3 system signals, hard exiting\n")); + // if (ret < 0) { /* Do nothing */ }; + // exit(123); + // } } #if HAVE_SETCONSOLECTRLHANDLER @@ -695,72 +704,12 @@ static void ffmpeg_cleanup(int ret) } av_freep(&filtergraphs); - av_freep(&subtitle_out); - /* close files */ for (i = 0; i < nb_output_files; i++) of_close(&output_files[i]); - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!ost) - continue; - - av_bsf_free(&ost->bsf_ctx); - - av_frame_free(&ost->filtered_frame); - av_frame_free(&ost->last_frame); - av_packet_free(&ost->pkt); - av_dict_free(&ost->encoder_opts); - - av_freep(&ost->forced_keyframes); - av_expr_free(ost->forced_keyframes_pexpr); - av_freep(&ost->avfilter); - av_freep(&ost->logfile_prefix); - - av_freep(&ost->audio_channels_map); - ost->audio_channels_mapped = 0; - - av_dict_free(&ost->sws_dict); - av_dict_free(&ost->swr_opts); - - avcodec_free_context(&ost->enc_ctx); - avcodec_parameters_free(&ost->ref_par); - - if (ost->muxing_queue) { - AVPacket *pkt; - while (av_fifo_read(ost->muxing_queue, &pkt, 1) >= 0) - av_packet_free(&pkt); - av_fifo_freep2(&ost->muxing_queue); - } - - av_freep(&output_streams[i]); - } -#if HAVE_THREADS - free_input_threads(); -#endif - for (i = 0; i < nb_input_files; i++) { - avformat_close_input(&input_files[i]->ctx); - av_packet_free(&input_files[i]->pkt); - av_freep(&input_files[i]); - } - for (i = 0; i < nb_input_streams; i++) { - InputStream *ist = input_streams[i]; - - av_frame_free(&ist->decoded_frame); - av_packet_free(&ist->pkt); - av_dict_free(&ist->decoder_opts); - avsubtitle_free(&ist->prev_sub.subtitle); - av_frame_free(&ist->sub2video.frame); - av_freep(&ist->filters); - av_freep(&ist->hwaccel_device); - av_freep(&ist->dts_buffer); - - avcodec_free_context(&ist->dec_ctx); - - av_freep(&input_streams[i]); - } + for (i = 0; i < nb_input_files; i++) + ifile_close(&input_files[i]); if (vstats_file) { if (fclose(vstats_file)) @@ -769,11 +718,11 @@ static void ffmpeg_cleanup(int ret) av_err2str(AVERROR(errno))); } av_freep(&vstats_filename); + of_enc_stats_close(); + av_freep(&filter_nbthreads); - av_freep(&input_streams); av_freep(&input_files); - av_freep(&output_streams); av_freep(&output_files); uninit_opts(); @@ -792,11 +741,45 @@ static void ffmpeg_cleanup(int ret) ffmpeg_exited = 1; } +/* iterate over all output streams in all output files; + * pass NULL to start iteration */ +static OutputStream *ost_iter(OutputStream *prev) +{ + int of_idx = prev ? prev->file_index : 0; + int ost_idx = prev ? prev->index + 1 : 0; + + for (; of_idx < nb_output_files; of_idx++) { + OutputFile *of = output_files[of_idx]; + if (ost_idx < of->nb_streams) + return of->streams[ost_idx]; + + ost_idx = 0; + } + + return NULL; +} + +InputStream *ist_iter(InputStream *prev) +{ + int if_idx = prev ? prev->file_index : 0; + int ist_idx = prev ? prev->st->index + 1 : 0; + + for (; if_idx < nb_input_files; if_idx++) { + InputFile *f = input_files[if_idx]; + if (ist_idx < f->nb_streams) + return f->streams[ist_idx]; + + ist_idx = 0; + } + + return NULL; +} + void remove_avoptions(AVDictionary **a, AVDictionary *b) { const AVDictionaryEntry *t = NULL; - while ((t = av_dict_get(b, "", t, AV_DICT_IGNORE_SUFFIX))) { + while ((t = av_dict_iterate(b, t))) { av_dict_set(a, t->key, NULL, AV_DICT_MATCH_CASE); } } @@ -839,59 +822,18 @@ static void update_benchmark(const char *fmt, ...) static void close_output_stream(OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - AVRational time_base = ost->stream_copy ? ost->mux_timebase : ost->enc_ctx->time_base; - ost->finished |= ENCODER_FINISHED; - if (of->shortest) { - int64_t end = av_rescale_q(ost->sync_opts - ost->first_pts, time_base, AV_TIME_BASE_Q); - of->recording_time = FFMIN(of->recording_time, end); - } -} -/* - * Send a single packet to the output, applying any bitstream filters - * associated with the output stream. This may result in any number - * of packets actually being written, depending on what bitstream - * filters are applied. The supplied packet is consumed and will be - * blank (as if newly-allocated) when this function returns. - * - * If eof is set, instead indicate EOF to all bitstream filters and - * therefore flush any delayed packets to the output. A blank packet - * must be supplied in this case. - */ -static void output_packet(OutputFile *of, AVPacket *pkt, - OutputStream *ost, int eof) -{ - int ret = 0; - - /* apply the output bitstream filters */ - if (ost->bsf_ctx) { - ret = av_bsf_send_packet(ost->bsf_ctx, eof ? NULL : pkt); - if (ret < 0) - goto finish; - while ((ret = av_bsf_receive_packet(ost->bsf_ctx, pkt)) >= 0) - of_write_packet(of, pkt, ost, 0); - if (ret == AVERROR(EAGAIN)) - ret = 0; - } else if (!eof) - of_write_packet(of, pkt, ost, 0); - -finish: - if (ret < 0 && ret != AVERROR_EOF) { - av_log(NULL, AV_LOG_ERROR, "Error applying bitstream filters to an output " - "packet for stream #%d:%d.\n", ost->file_index, ost->index); - if(exit_on_error) - exit_program(1); - } + if (ost->sq_idx_encode >= 0) + sq_send(of->sq_encode, ost->sq_idx_encode, SQFRAME(NULL)); } -static int check_recording_time(OutputStream *ost) +static int check_recording_time(OutputStream *ost, int64_t ts, AVRational tb) { OutputFile *of = output_files[ost->file_index]; if (of->recording_time != INT64_MAX && - av_compare_ts(ost->sync_opts - ost->first_pts, ost->enc_ctx->time_base, of->recording_time, - AV_TIME_BASE_Q) >= 0) { + av_compare_ts(ts, tb, of->recording_time, AV_TIME_BASE_Q) >= 0) { close_output_stream(ost); return 0; } @@ -902,38 +844,36 @@ static double adjust_frame_pts_to_encoder_tb(OutputFile *of, OutputStream *ost, AVFrame *frame) { double float_pts = AV_NOPTS_VALUE; // this is identical to frame.pts but with higher precision - AVCodecContext *enc = ost->enc_ctx; - if (!frame || frame->pts == AV_NOPTS_VALUE || - !enc || !ost->filter || !ost->filter->graph->graph) - goto early_exit; + const int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? + 0 : of->start_time; - { - AVFilterContext *filter = ost->filter->filter; + AVCodecContext *const enc = ost->enc_ctx; - int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; - AVRational filter_tb = av_buffersink_get_time_base(filter); - AVRational tb = enc->time_base; - int extra_bits = av_clip(29 - av_log2(tb.den), 0, 16); - - tb.den <<= extra_bits; - float_pts = - av_rescale_q(frame->pts, filter_tb, tb) - - av_rescale_q(start_time, AV_TIME_BASE_Q, tb); - float_pts /= 1 << extra_bits; - // avoid exact midoints to reduce the chance of rounding differences, this can be removed in case the fps code is changed to work with integers - float_pts += FFSIGN(float_pts) * 1.0 / (1<<17); + AVRational tb = enc->time_base; + AVRational filter_tb = frame->time_base; + const int extra_bits = av_clip(29 - av_log2(tb.den), 0, 16); - frame->pts = - av_rescale_q(frame->pts, filter_tb, enc->time_base) - - av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); - } + if (frame->pts == AV_NOPTS_VALUE) + goto early_exit; + + tb.den <<= extra_bits; + float_pts = av_rescale_q(frame->pts, filter_tb, tb) - + av_rescale_q(start_time, AV_TIME_BASE_Q, tb); + float_pts /= 1 << extra_bits; + // avoid exact midoints to reduce the chance of rounding differences, this + // can be removed in case the fps code is changed to work with integers + float_pts += FFSIGN(float_pts) * 1.0 / (1<<17); + + frame->pts = av_rescale_q(frame->pts, filter_tb, enc->time_base) - + av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); + frame->time_base = enc->time_base; early_exit: if (debug_ts) { av_log(NULL, AV_LOG_INFO, "filter -> pts:%s pts_time:%s exact:%f time_base:%d/%d\n", frame ? av_ts2str(frame->pts) : "NULL", - frame ? av_ts2timestr(frame->pts, &enc->time_base) : "NULL", + (enc && frame) ? av_ts2timestr(frame->pts, &enc->time_base) : "NULL", float_pts, enc ? enc->time_base.num : -1, enc ? enc->time_base.den : -1); @@ -956,8 +896,8 @@ static int init_output_stream_wrapper(OutputStream *ost, AVFrame *frame, ret = init_output_stream(ost, frame, error, sizeof(error)); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n", - ost->file_index, ost->index, error); + av_log(ost, AV_LOG_ERROR, "Error initializing output stream: %s\n", + error); if (fatal) exit_program(1); @@ -1015,17 +955,83 @@ static void update_video_stats(OutputStream *ost, const AVPacket *pkt, int write fprintf(vstats_file,"f_size= %6d ", pkt->size); /* compute pts value */ - ti1 = pkt->dts * av_q2d(ost->mux_timebase); + ti1 = pkt->dts * av_q2d(pkt->time_base); if (ti1 < 0.01) ti1 = 0.01; bitrate = (pkt->size * 8) / av_q2d(enc->time_base) / 1000.0; - avg_bitrate = (double)(ost->data_size * 8) / ti1 / 1000.0; + avg_bitrate = (double)(ost->data_size_enc * 8) / ti1 / 1000.0; fprintf(vstats_file, "s_size= %8.0fkB time= %0.3f br= %7.1fkbits/s avg_br= %7.1fkbits/s ", - (double)ost->data_size / 1024, ti1, bitrate, avg_bitrate); + (double)ost->data_size_enc / 1024, ti1, bitrate, avg_bitrate); fprintf(vstats_file, "type= %c\n", av_get_picture_type_char(ost->pict_type)); } +void enc_stats_write(OutputStream *ost, EncStats *es, + const AVFrame *frame, const AVPacket *pkt, + uint64_t frame_num) +{ + AVIOContext *io = es->io; + AVRational tb = frame ? frame->time_base : pkt->time_base; + int64_t pts = frame ? frame->pts : pkt->pts; + + AVRational tbi = (AVRational){ 0, 1}; + int64_t ptsi = INT64_MAX; + + const FrameData *fd; + + if ((frame && frame->opaque_ref) || (pkt && pkt->opaque_ref)) { + fd = (const FrameData*)(frame ? frame->opaque_ref->data : pkt->opaque_ref->data); + tbi = fd->tb; + ptsi = fd->pts; + } + + for (size_t i = 0; i < es->nb_components; i++) { + const EncStatsComponent *c = &es->components[i]; + + switch (c->type) { + case ENC_STATS_LITERAL: avio_write (io, c->str, c->str_len); continue; + case ENC_STATS_FILE_IDX: avio_printf(io, "%d", ost->file_index); continue; + case ENC_STATS_STREAM_IDX: avio_printf(io, "%d", ost->index); continue; + case ENC_STATS_TIMEBASE: avio_printf(io, "%d/%d", tb.num, tb.den); continue; + case ENC_STATS_TIMEBASE_IN: avio_printf(io, "%d/%d", tbi.num, tbi.den); continue; + case ENC_STATS_PTS: avio_printf(io, "%"PRId64, pts); continue; + case ENC_STATS_PTS_IN: avio_printf(io, "%"PRId64, ptsi); continue; + case ENC_STATS_PTS_TIME: avio_printf(io, "%g", pts * av_q2d(tb)); continue; + case ENC_STATS_PTS_TIME_IN: avio_printf(io, "%g", ptsi == INT64_MAX ? + INFINITY : ptsi * av_q2d(tbi)); continue; + case ENC_STATS_FRAME_NUM: avio_printf(io, "%"PRIu64, frame_num); continue; + case ENC_STATS_FRAME_NUM_IN: avio_printf(io, "%"PRIu64, fd ? fd->idx : -1); continue; + } + + if (frame) { + switch (c->type) { + case ENC_STATS_SAMPLE_NUM: avio_printf(io, "%"PRIu64, ost->samples_encoded); continue; + case ENC_STATS_NB_SAMPLES: avio_printf(io, "%d", frame->nb_samples); continue; + default: av_assert0(0); + } + } else { + switch (c->type) { + case ENC_STATS_DTS: avio_printf(io, "%"PRId64, pkt->dts); continue; + case ENC_STATS_DTS_TIME: avio_printf(io, "%g", pkt->dts * av_q2d(tb)); continue; + case ENC_STATS_PKT_SIZE: avio_printf(io, "%d", pkt->size); continue; + case ENC_STATS_BITRATE: { + double duration = FFMAX(pkt->duration, 1) * av_q2d(tb); + avio_printf(io, "%g", 8.0 * pkt->size / duration); + continue; + } + case ENC_STATS_AVG_BITRATE: { + double duration = pkt->dts * av_q2d(tb); + avio_printf(io, "%g", duration > 0 ? 8.0 * ost->data_size_enc / duration : -1.); + continue; + } + default: av_assert0(0); + } + } + } + avio_w8(io, '\n'); + avio_flush(io); +} + static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) { AVCodecContext *enc = ost->enc_ctx; @@ -1035,10 +1041,15 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) int ret; if (frame) { + if (ost->enc_stats_pre.io) + enc_stats_write(ost, &ost->enc_stats_pre, frame, NULL, + ost->frames_encoded); + ost->frames_encoded++; + ost->samples_encoded += frame->nb_samples; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder <- type:%s " + av_log(ost, AV_LOG_INFO, "encoder <- type:%s " "frame_pts:%s frame_pts_time:%s time_base:%d/%d\n", type_desc, av_ts2str(frame->pts), av_ts2timestr(frame->pts, &enc->time_base), @@ -1050,7 +1061,7 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) ret = avcodec_send_frame(enc, frame); if (ret < 0 && !(ret == AVERROR_EOF && !frame)) { - av_log(NULL, AV_LOG_ERROR, "Error submitting %s frame to the encoder\n", + av_log(ost, AV_LOG_ERROR, "Error submitting %s frame to the encoder\n", type_desc); return ret; } @@ -1060,6 +1071,8 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) update_benchmark("%s_%s %d.%d", action, type_desc, ost->file_index, ost->index); + pkt->time_base = enc->time_base; + /* if two pass, output log on success and EOF */ if ((ret >= 0 || ret == AVERROR_EOF) && ost->logfile && enc->stats_out) fprintf(ost->logfile, "%s", enc->stats_out); @@ -1068,15 +1081,21 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_assert0(frame); // should never happen during flushing return 0; } else if (ret == AVERROR_EOF) { - output_packet(of, pkt, ost, 1); + of_output_packet(of, pkt, ost, 1); return ret; } else if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "%s encoding failed\n", type_desc); + av_log(ost, AV_LOG_ERROR, "%s encoding failed\n", type_desc); return ret; } + if (enc->codec_type == AVMEDIA_TYPE_VIDEO) + update_video_stats(ost, pkt, !!vstats_filename); + if (ost->enc_stats_post.io) + enc_stats_write(ost, &ost->enc_stats_post, NULL, pkt, + ost->packets_encoded); + if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder -> type:%s " + av_log(ost, AV_LOG_INFO, "encoder -> type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s " "duration:%s duration_time:%s\n", type_desc, @@ -1085,10 +1104,11 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base)); } - av_packet_rescale_ts(pkt, enc->time_base, ost->mux_timebase); + av_packet_rescale_ts(pkt, pkt->time_base, ost->mux_timebase); + pkt->time_base = ost->mux_timebase; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "encoder -> type:%s " + av_log(ost, AV_LOG_INFO, "encoder -> type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s " "duration:%s duration_time:%s\n", type_desc, @@ -1097,34 +1117,92 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base)); } - if (enc->codec_type == AVMEDIA_TYPE_VIDEO) - update_video_stats(ost, pkt, !!vstats_filename); + if ((ret = trigger_fix_sub_duration_heartbeat(ost, pkt)) < 0) { + av_log(NULL, AV_LOG_ERROR, + "Subtitle heartbeat logic failed in %s! (%s)\n", + __func__, av_err2str(ret)); + exit_program(1); + } + + ost->data_size_enc += pkt->size; ost->packets_encoded++; - output_packet(of, pkt, ost, 0); + of_output_packet(of, pkt, ost, 0); } av_assert0(0); } +static int submit_encode_frame(OutputFile *of, OutputStream *ost, + AVFrame *frame) +{ + int ret; + + if (ost->sq_idx_encode < 0) + return encode_frame(of, ost, frame); + + if (frame) { + ret = av_frame_ref(ost->sq_frame, frame); + if (ret < 0) + return ret; + frame = ost->sq_frame; + } + + ret = sq_send(of->sq_encode, ost->sq_idx_encode, + SQFRAME(frame)); + if (ret < 0) { + if (frame) + av_frame_unref(frame); + if (ret != AVERROR_EOF) + return ret; + } + + while (1) { + AVFrame *enc_frame = ost->sq_frame; + + ret = sq_receive(of->sq_encode, ost->sq_idx_encode, + SQFRAME(enc_frame)); + if (ret == AVERROR_EOF) { + enc_frame = NULL; + } else if (ret < 0) { + return (ret == AVERROR(EAGAIN)) ? 0 : ret; + } + + ret = encode_frame(of, ost, enc_frame); + if (enc_frame) + av_frame_unref(enc_frame); + if (ret < 0) { + if (ret == AVERROR_EOF) + close_output_stream(ost); + return ret; + } + } +} + static void do_audio_out(OutputFile *of, OutputStream *ost, AVFrame *frame) { + AVCodecContext *enc = ost->enc_ctx; int ret; - adjust_frame_pts_to_encoder_tb(of, ost, frame); + if (frame->pts == AV_NOPTS_VALUE) + frame->pts = ost->next_pts; + else { + int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; + frame->pts = + av_rescale_q(frame->pts, frame->time_base, enc->time_base) - + av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); + } + frame->time_base = enc->time_base; - if (!check_recording_time(ost)) + if (!check_recording_time(ost, frame->pts, frame->time_base)) return; - if (frame->pts == AV_NOPTS_VALUE || audio_sync_method < 0) - frame->pts = ost->sync_opts; - ost->sync_opts = frame->pts + frame->nb_samples; - ost->samples_encoded += frame->nb_samples; + ost->next_pts = frame->pts + frame->nb_samples; - ret = encode_frame(of, ost, frame); - if (ret < 0) + ret = submit_encode_frame(of, ost, frame); + if (ret < 0 && ret != AVERROR_EOF) exit_program(1); } @@ -1133,13 +1211,13 @@ static void do_subtitle_out(OutputFile *of, AVSubtitle *sub) { int subtitle_out_max_size = 1024 * 1024; - int subtitle_out_size, nb, i; + int subtitle_out_size, nb, i, ret; AVCodecContext *enc; AVPacket *pkt = ost->pkt; int64_t pts; if (sub->pts == AV_NOPTS_VALUE) { - av_log(NULL, AV_LOG_ERROR, "Subtitle packets must have a pts\n"); + av_log(ost, AV_LOG_ERROR, "Subtitle packets must have a pts\n"); if (exit_on_error) exit_program(1); return; @@ -1147,14 +1225,6 @@ static void do_subtitle_out(OutputFile *of, enc = ost->enc_ctx; - if (!subtitle_out) { - subtitle_out = av_malloc(subtitle_out_max_size); - if (!subtitle_out) { - av_log(NULL, AV_LOG_FATAL, "Failed to allocate subtitle_out\n"); - exit_program(1); - } - } - /* Note: DVB subtitle need one packet to draw them and one other packet to clear them */ /* XXX: signal it in the codec context ? */ @@ -1170,10 +1240,13 @@ static void do_subtitle_out(OutputFile *of, for (i = 0; i < nb; i++) { unsigned save_num_rects = sub->num_rects; - ost->sync_opts = av_rescale_q(pts, AV_TIME_BASE_Q, enc->time_base); - if (!check_recording_time(ost)) + if (!check_recording_time(ost, pts, AV_TIME_BASE_Q)) return; + ret = av_new_packet(pkt, subtitle_out_max_size); + if (ret < 0) + report_and_exit(AVERROR(ENOMEM)); + sub->pts = pts; // start_display_time is required to be 0 sub->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, AV_TIME_BASE_Q); @@ -1184,33 +1257,156 @@ static void do_subtitle_out(OutputFile *of, ost->frames_encoded++; - subtitle_out_size = avcodec_encode_subtitle(enc, subtitle_out, - subtitle_out_max_size, sub); + subtitle_out_size = avcodec_encode_subtitle(enc, pkt->data, pkt->size, sub); if (i == 1) sub->num_rects = save_num_rects; if (subtitle_out_size < 0) { - av_log(NULL, AV_LOG_FATAL, "Subtitle encoding failed\n"); + av_log(ost, AV_LOG_FATAL, "Subtitle encoding failed\n"); exit_program(1); } - av_packet_unref(pkt); - pkt->data = subtitle_out; - pkt->size = subtitle_out_size; - pkt->pts = av_rescale_q(sub->pts, AV_TIME_BASE_Q, ost->mux_timebase); - pkt->duration = av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + av_shrink_packet(pkt, subtitle_out_size); + pkt->time_base = ost->mux_timebase; + pkt->pts = av_rescale_q(sub->pts, AV_TIME_BASE_Q, pkt->time_base); + pkt->duration = av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, pkt->time_base); if (enc->codec_id == AV_CODEC_ID_DVB_SUBTITLE) { /* XXX: the pts correction is handled here. Maybe handling it in the codec would be better */ if (i == 0) - pkt->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + pkt->pts += av_rescale_q(sub->start_display_time, (AVRational){ 1, 1000 }, pkt->time_base); else - pkt->pts += av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + pkt->pts += av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, pkt->time_base); } pkt->dts = pkt->pts; - output_packet(of, pkt, ost, 0); + + of_output_packet(of, pkt, ost, 0); + } +} + +/* Convert frame timestamps to the encoder timebase and decide how many times + * should this (and possibly previous) frame be repeated in order to conform to + * desired target framerate (if any). + */ +static void video_sync_process(OutputFile *of, OutputStream *ost, + AVFrame *next_picture, double duration, + int64_t *nb_frames, int64_t *nb_frames_prev) +{ + double delta0, delta; + + double sync_ipts = adjust_frame_pts_to_encoder_tb(of, ost, next_picture); + /* delta0 is the "drift" between the input frame (next_picture) and + * where it would fall in the output. */ + delta0 = sync_ipts - ost->next_pts; + delta = delta0 + duration; + + // tracks the number of times the PREVIOUS frame should be duplicated, + // mostly for variable framerate (VFR) + *nb_frames_prev = 0; + /* by default, we output a single frame */ + *nb_frames = 1; + + if (delta0 < 0 && + delta > 0 && + ost->vsync_method != VSYNC_PASSTHROUGH && + ost->vsync_method != VSYNC_DROP) { + if (delta0 < -0.6) { + av_log(ost, AV_LOG_VERBOSE, "Past duration %f too large\n", -delta0); + } else + av_log(ost, AV_LOG_DEBUG, "Clipping frame in rate conversion by %f\n", -delta0); + sync_ipts = ost->next_pts; + duration += delta0; + delta0 = 0; + } + + switch (ost->vsync_method) { + case VSYNC_VSCFR: + if (ost->vsync_frame_number == 0 && delta0 >= 0.5) { + av_log(ost, AV_LOG_DEBUG, "Not duplicating %d initial frames\n", (int)lrintf(delta0)); + delta = duration; + delta0 = 0; + ost->next_pts = llrint(sync_ipts); + } + case VSYNC_CFR: + // FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c + if (frame_drop_threshold && delta < frame_drop_threshold && ost->vsync_frame_number) { + *nb_frames = 0; + } else if (delta < -1.1) + *nb_frames = 0; + else if (delta > 1.1) { + *nb_frames = llrintf(delta); + if (delta0 > 1.1) + *nb_frames_prev = llrintf(delta0 - 0.6); + } + next_picture->duration = 1; + break; + case VSYNC_VFR: + if (delta <= -0.6) + *nb_frames = 0; + else if (delta > 0.6) + ost->next_pts = llrint(sync_ipts); + next_picture->duration = duration; + break; + case VSYNC_DROP: + case VSYNC_PASSTHROUGH: + next_picture->duration = duration; + ost->next_pts = llrint(sync_ipts); + break; + default: + av_assert0(0); } } +enum AVPictureType forced_kf_apply(void *logctx, KeyframeForceCtx *kf, + AVRational tb, const AVFrame *in_picture, + int dup_idx) +{ + double pts_time; + + if (kf->ref_pts == AV_NOPTS_VALUE) + kf->ref_pts = in_picture->pts; + + pts_time = (in_picture->pts - kf->ref_pts) * av_q2d(tb); + if (kf->index < kf->nb_pts && + av_compare_ts(in_picture->pts, tb, kf->pts[kf->index], AV_TIME_BASE_Q) >= 0) { + kf->index++; + goto force_keyframe; + } else if (kf->pexpr) { + double res; + kf->expr_const_values[FKF_T] = pts_time; + res = av_expr_eval(kf->pexpr, + kf->expr_const_values, NULL); + ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n", + kf->expr_const_values[FKF_N], + kf->expr_const_values[FKF_N_FORCED], + kf->expr_const_values[FKF_PREV_FORCED_N], + kf->expr_const_values[FKF_T], + kf->expr_const_values[FKF_PREV_FORCED_T], + res); + + kf->expr_const_values[FKF_N] += 1; + + if (res) { + kf->expr_const_values[FKF_PREV_FORCED_N] = kf->expr_const_values[FKF_N] - 1; + kf->expr_const_values[FKF_PREV_FORCED_T] = kf->expr_const_values[FKF_T]; + kf->expr_const_values[FKF_N_FORCED] += 1; + goto force_keyframe; + } + } else if (kf->type == KF_FORCE_SOURCE && + in_picture->key_frame == 1 && !dup_idx) { + goto force_keyframe; + } else if (kf->type == KF_FORCE_SOURCE_NO_DROP && !dup_idx) { + kf->dropped_keyframe = 0; + if ((in_picture->key_frame == 1) || kf->dropped_keyframe) + goto force_keyframe; + } + + return AV_PICTURE_TYPE_NONE; + +force_keyframe: + av_log(logctx, AV_LOG_DEBUG, "Forced keyframe at time %f\n", pts_time); + return AV_PICTURE_TYPE_I; +} + /* May modify/reset next_picture */ static void do_video_out(OutputFile *of, OutputStream *ost, @@ -1219,18 +1415,12 @@ static void do_video_out(OutputFile *of, int ret; AVCodecContext *enc = ost->enc_ctx; AVRational frame_rate; - int64_t nb_frames, nb0_frames, i; - double delta, delta0; + int64_t nb_frames, nb_frames_prev, i; double duration = 0; - double sync_ipts = AV_NOPTS_VALUE; - InputStream *ist = NULL; + InputStream *ist = ost->ist; AVFilterContext *filter = ost->filter->filter; init_output_stream_wrapper(ost, next_picture, 1); - sync_ipts = adjust_frame_pts_to_encoder_tb(of, ost, next_picture); - - if (ost->source_index >= 0) - ist = input_streams[ost->source_index]; frame_rate = av_buffersink_get_frame_rate(filter); if (frame_rate.num > 0 && frame_rate.den > 0) @@ -1244,113 +1434,52 @@ static void do_video_out(OutputFile *of, (nb_filtergraphs == 0 || !filtergraphs[0]->graph_desc) && next_picture && ist && - lrintf(next_picture->pkt_duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)) > 0) { - duration = lrintf(next_picture->pkt_duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)); + lrintf(next_picture->duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)) > 0) { + duration = lrintf(next_picture->duration * av_q2d(ist->st->time_base) / av_q2d(enc->time_base)); } if (!next_picture) { //end, flushing - nb0_frames = nb_frames = mid_pred(ost->last_nb0_frames[0], - ost->last_nb0_frames[1], - ost->last_nb0_frames[2]); + nb_frames_prev = nb_frames = mid_pred(ost->last_nb0_frames[0], + ost->last_nb0_frames[1], + ost->last_nb0_frames[2]); } else { - delta0 = sync_ipts - ost->sync_opts; // delta0 is the "drift" between the input frame (next_picture) and where it would fall in the output. - delta = delta0 + duration; - - /* by default, we output a single frame */ - nb0_frames = 0; // tracks the number of times the PREVIOUS frame should be duplicated, mostly for variable framerate (VFR) - nb_frames = 1; - - if (delta0 < 0 && - delta > 0 && - ost->vsync_method != VSYNC_PASSTHROUGH && - ost->vsync_method != VSYNC_DROP) { - if (delta0 < -0.6) { - av_log(NULL, AV_LOG_VERBOSE, "Past duration %f too large\n", -delta0); - } else - av_log(NULL, AV_LOG_DEBUG, "Clipping frame in rate conversion by %f\n", -delta0); - sync_ipts = ost->sync_opts; - duration += delta0; - delta0 = 0; - } - - switch (ost->vsync_method) { - case VSYNC_VSCFR: - if (ost->frame_number == 0 && delta0 >= 0.5) { - av_log(NULL, AV_LOG_DEBUG, "Not duplicating %d initial frames\n", (int)lrintf(delta0)); - delta = duration; - delta0 = 0; - ost->sync_opts = llrint(sync_ipts); - } - case VSYNC_CFR: - // FIXME set to 0.5 after we fix some dts/pts bugs like in avidec.c - if (frame_drop_threshold && delta < frame_drop_threshold && ost->frame_number) { - nb_frames = 0; - } else if (delta < -1.1) - nb_frames = 0; - else if (delta > 1.1) { - nb_frames = llrintf(delta); - if (delta0 > 1.1) - nb0_frames = llrintf(delta0 - 0.6); - } - break; - case VSYNC_VFR: - if (delta <= -0.6) - nb_frames = 0; - else if (delta > 0.6) - ost->sync_opts = llrint(sync_ipts); - break; - case VSYNC_DROP: - case VSYNC_PASSTHROUGH: - ost->sync_opts = llrint(sync_ipts); - break; - default: - av_assert0(0); - } + video_sync_process(of, ost, next_picture, duration, + &nb_frames, &nb_frames_prev); } - /* - * For video, number of frames in == number of packets out. - * But there may be reordering, so we can't throw away frames on encoder - * flush, we need to limit them here, before they go into encoder. - */ - nb_frames = FFMIN(nb_frames, ost->max_frames - ost->frame_number); - nb0_frames = FFMIN(nb0_frames, nb_frames); - memmove(ost->last_nb0_frames + 1, ost->last_nb0_frames, sizeof(ost->last_nb0_frames[0]) * (FF_ARRAY_ELEMS(ost->last_nb0_frames) - 1)); - ost->last_nb0_frames[0] = nb0_frames; + ost->last_nb0_frames[0] = nb_frames_prev; - if (nb0_frames == 0 && ost->last_dropped) { + if (nb_frames_prev == 0 && ost->last_dropped) { nb_frames_drop++; - av_log(NULL, AV_LOG_VERBOSE, - "*** dropping frame %"PRId64" from stream %d at ts %"PRId64"\n", - ost->frame_number, ost->st->index, ost->last_frame->pts); + av_log(ost, AV_LOG_VERBOSE, + "*** dropping frame %"PRId64" at ts %"PRId64"\n", + ost->vsync_frame_number, ost->last_frame->pts); } - if (nb_frames > (nb0_frames && ost->last_dropped) + (nb_frames > nb0_frames)) { + if (nb_frames > (nb_frames_prev && ost->last_dropped) + (nb_frames > nb_frames_prev)) { if (nb_frames > dts_error_threshold * 30) { - av_log(NULL, AV_LOG_ERROR, "%"PRId64" frame duplication too large, skipping\n", nb_frames - 1); + av_log(ost, AV_LOG_ERROR, "%"PRId64" frame duplication too large, skipping\n", nb_frames - 1); nb_frames_drop++; return; } - nb_frames_dup += nb_frames - (nb0_frames && ost->last_dropped) - (nb_frames > nb0_frames); - av_log(NULL, AV_LOG_VERBOSE, "*** %"PRId64" dup!\n", nb_frames - 1); + nb_frames_dup += nb_frames - (nb_frames_prev && ost->last_dropped) - (nb_frames > nb_frames_prev); + av_log(ost, AV_LOG_VERBOSE, "*** %"PRId64" dup!\n", nb_frames - 1); if (nb_frames_dup > dup_warning) { - av_log(NULL, AV_LOG_WARNING, "More than %"PRIu64" frames duplicated\n", dup_warning); + av_log(ost, AV_LOG_WARNING, "More than %"PRIu64" frames duplicated\n", dup_warning); dup_warning *= 10; } } - ost->last_dropped = nb_frames == nb0_frames && next_picture; - ost->dropped_keyframe = ost->last_dropped && next_picture && next_picture->key_frame; + ost->last_dropped = nb_frames == nb_frames_prev && next_picture; + ost->kf.dropped_keyframe = ost->last_dropped && next_picture && next_picture->key_frame; /* duplicates frame if needed */ for (i = 0; i < nb_frames; i++) { AVFrame *in_picture; - int forced_keyframe = 0; - double pts_time; - if (i < nb0_frames && ost->last_frame->buf[0]) { + if (i < nb_frames_prev && ost->last_frame->buf[0]) { in_picture = ost->last_frame; } else in_picture = next_picture; @@ -1358,69 +1487,22 @@ static void do_video_out(OutputFile *of, if (!in_picture) return; - in_picture->pts = ost->sync_opts; + in_picture->pts = ost->next_pts; - if (!check_recording_time(ost)) + if (!check_recording_time(ost, in_picture->pts, ost->enc_ctx->time_base)) return; in_picture->quality = enc->global_quality; - in_picture->pict_type = 0; - - if (ost->forced_kf_ref_pts == AV_NOPTS_VALUE && - in_picture->pts != AV_NOPTS_VALUE) - ost->forced_kf_ref_pts = in_picture->pts; - - pts_time = in_picture->pts != AV_NOPTS_VALUE ? - (in_picture->pts - ost->forced_kf_ref_pts) * av_q2d(enc->time_base) : NAN; - if (ost->forced_kf_index < ost->forced_kf_count && - in_picture->pts >= ost->forced_kf_pts[ost->forced_kf_index]) { - ost->forced_kf_index++; - forced_keyframe = 1; - } else if (ost->forced_keyframes_pexpr) { - double res; - ost->forced_keyframes_expr_const_values[FKF_T] = pts_time; - res = av_expr_eval(ost->forced_keyframes_pexpr, - ost->forced_keyframes_expr_const_values, NULL); - ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n", - ost->forced_keyframes_expr_const_values[FKF_N], - ost->forced_keyframes_expr_const_values[FKF_N_FORCED], - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N], - ost->forced_keyframes_expr_const_values[FKF_T], - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T], - res); - if (res) { - forced_keyframe = 1; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N] = - ost->forced_keyframes_expr_const_values[FKF_N]; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T] = - ost->forced_keyframes_expr_const_values[FKF_T]; - ost->forced_keyframes_expr_const_values[FKF_N_FORCED] += 1; - } + in_picture->pict_type = forced_kf_apply(ost, &ost->kf, enc->time_base, in_picture, i); - ost->forced_keyframes_expr_const_values[FKF_N] += 1; - } else if ( ost->forced_keyframes - && !strncmp(ost->forced_keyframes, "source", 6) - && in_picture->key_frame==1 - && !i) { - forced_keyframe = 1; - } else if ( ost->forced_keyframes - && !strncmp(ost->forced_keyframes, "source_no_drop", 14) - && !i) { - forced_keyframe = (in_picture->key_frame == 1) || ost->dropped_keyframe; - ost->dropped_keyframe = 0; - } - - if (forced_keyframe) { - in_picture->pict_type = AV_PICTURE_TYPE_I; - av_log(NULL, AV_LOG_DEBUG, "Forced keyframe at time %f\n", pts_time); - } - - ret = encode_frame(of, ost, in_picture); - if (ret < 0) + ret = submit_encode_frame(of, ost, in_picture); + if (ret == AVERROR_EOF) + break; + else if (ret < 0) exit_program(1); - ost->sync_opts++; - ost->frame_number++; + ost->next_pts++; + ost->vsync_frame_number++; } av_frame_unref(ost->last_frame); @@ -1428,19 +1510,6 @@ static void do_video_out(OutputFile *of, av_frame_move_ref(ost->last_frame, next_picture); } -static void finish_output_stream(OutputStream *ost) -{ - OutputFile *of = output_files[ost->file_index]; - AVRational time_base = ost->stream_copy ? ost->mux_timebase : ost->enc_ctx->time_base; - - ost->finished = ENCODER_FINISHED | MUXER_FINISHED; - - if (of->shortest) { - int64_t end = av_rescale_q(ost->sync_opts - ost->first_pts, time_base, AV_TIME_BASE_Q); - of->recording_time = FFMIN(of->recording_time, end); - } -} - /** * Get and encode new output from any of the filtergraphs, without causing * activity. @@ -1450,11 +1519,9 @@ static void finish_output_stream(OutputStream *ost) static int reap_filters(int flush) { AVFrame *filtered_frame = NULL; - int i; /* Reap all buffers present in the buffer sinks */ - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { OutputFile *of = output_files[ost->file_index]; AVFilterContext *filter; AVCodecContext *enc = ost->enc_ctx; @@ -1496,6 +1563,19 @@ static int reap_filters(int flush) continue; } + if (filtered_frame->pts != AV_NOPTS_VALUE) { + AVRational tb = av_buffersink_get_time_base(filter); + ost->last_filter_pts = av_rescale_q(filtered_frame->pts, tb, + AV_TIME_BASE_Q); + filtered_frame->time_base = tb; + + if (debug_ts) + av_log(NULL, AV_LOG_INFO, "filter_raw -> pts:%s pts_time:%s time_base:%d/%d\n", + av_ts2str(filtered_frame->pts), + av_ts2timestr(filtered_frame->pts, &tb), + tb.num, tb.den); + } + switch (av_buffersink_get_type(filter)) { case AVMEDIA_TYPE_VIDEO: if (!ost->frame_aspect_ratio.num) @@ -1532,19 +1612,21 @@ static void print_final_stats(int64_t total_size) float percent = -1.0; int i, j; int pass1_used = 1; - char percentString[50] = {0}; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - switch (ost->enc_ctx->codec_type) { - case AVMEDIA_TYPE_VIDEO: video_size += ost->data_size; break; - case AVMEDIA_TYPE_AUDIO: audio_size += ost->data_size; break; - case AVMEDIA_TYPE_SUBTITLE: subtitle_size += ost->data_size; break; - default: other_size += ost->data_size; break; + + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + AVCodecParameters *par = ost->st->codecpar; + const uint64_t s = ost->data_size_mux; + + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: video_size += s; break; + case AVMEDIA_TYPE_AUDIO: audio_size += s; break; + case AVMEDIA_TYPE_SUBTITLE: subtitle_size += s; break; + default: other_size += s; break; } - extra_size += ost->enc_ctx->extradata_size; - data_size += ost->data_size; - if ( (ost->enc_ctx->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2)) + extra_size += par->extradata_size; + data_size += s; + if (ost->enc_ctx && + (ost->enc_ctx->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2)) != AV_CODEC_FLAG_PASS1) pass1_used = 0; } @@ -1552,18 +1634,17 @@ static void print_final_stats(int64_t total_size) if (data_size && total_size>0 && total_size >= data_size) percent = 100.0 * (total_size - data_size) / data_size; - if (percent >= 0.0) - sprintf(percentString, "%f%%", percent); - else - sprintf(percentString, "unknown"); - - av_log(NULL, AV_LOG_INFO, "video:%1.0fkB audio:%1.0fkB subtitle:%1.0fkB other streams:%1.0fkB global headers:%1.0fkB muxing overhead: %s\n", + av_log(NULL, AV_LOG_INFO, "video:%1.0fkB audio:%1.0fkB subtitle:%1.0fkB other streams:%1.0fkB global headers:%1.0fkB muxing overhead: ", video_size / 1024.0, audio_size / 1024.0, subtitle_size / 1024.0, other_size / 1024.0, - extra_size / 1024.0, - percentString); + extra_size / 1024.0); + if (percent >= 0.0) + av_log(NULL, AV_LOG_INFO, "%f%%", percent); + else + av_log(NULL, AV_LOG_INFO, "unknown"); + av_log(NULL, AV_LOG_INFO, "\n"); /* print verbose per-stream stats */ for (i = 0; i < nb_input_files; i++) { @@ -1574,8 +1655,8 @@ static void print_final_stats(int64_t total_size) i, f->ctx->url); for (j = 0; j < f->nb_streams; j++) { - InputStream *ist = input_streams[f->ist_index + j]; - enum AVMediaType type = ist->dec_ctx->codec_type; + InputStream *ist = f->streams[j]; + enum AVMediaType type = ist->par->codec_type; total_size += ist->data_size; total_packets += ist->nb_packets; @@ -1605,18 +1686,18 @@ static void print_final_stats(int64_t total_size) uint64_t total_packets = 0, total_size = 0; av_log(NULL, AV_LOG_VERBOSE, "Output file #%d (%s):\n", - i, of->ctx->url); + i, of->url); - for (j = 0; j < of->ctx->nb_streams; j++) { - OutputStream *ost = output_streams[of->ost_index + j]; - enum AVMediaType type = ost->enc_ctx->codec_type; + for (j = 0; j < of->nb_streams; j++) { + OutputStream *ost = of->streams[j]; + enum AVMediaType type = ost->st->codecpar->codec_type; - total_size += ost->data_size; - total_packets += ost->packets_written; + total_size += ost->data_size_mux; + total_packets += atomic_load(&ost->packets_written); av_log(NULL, AV_LOG_VERBOSE, " Output stream #%d:%d (%s): ", i, j, av_get_media_type_string(type)); - if (ost->encoding_needed) { + if (ost->enc_ctx) { av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" frames encoded", ost->frames_encoded); if (type == AVMEDIA_TYPE_AUDIO) @@ -1625,7 +1706,7 @@ static void print_final_stats(int64_t total_size) } av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" packets muxed (%"PRIu64" bytes); ", - ost->packets_written, ost->data_size); + atomic_load(&ost->packets_written), ost->data_size_mux); av_log(NULL, AV_LOG_VERBOSE, "\n"); } @@ -1643,83 +1724,19 @@ static void print_final_stats(int64_t total_size) } } -static void forward_report(int is_last_report, int64_t timer_start, int64_t cur_time) +static void forward_report(uint64_t frame_number, float fps, float quality, int64_t total_size, int64_t seconds, int64_t microseconds, double bitrate, double speed) { - AVFormatContext *oc = NULL; - AVCodecContext *enc = NULL; - OutputStream *ost = NULL; - int64_t pts = INT64_MIN + 1; - int vid, i; - - int64_t frame_number = 0; - float fps = 0; - float quality = 0; - int64_t total_size = 0; - int seconds = 0; - double bitrate = 0.0; - double speed = 0.0; - - float t = (cur_time-timer_start) / 1000000.0; - - oc = output_files[0]->ctx; - - // 1. calculate size - total_size = avio_size(oc->pb); - if (total_size <= 0) { - total_size = avio_tell(oc->pb); - } - - vid = 0; - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - enc = ost->enc_ctx; - - if (!ost->stream_copy) { - - // 2. extract quality - quality = ost->quality / (float) FF_QP2LAMBDA; - } - - if (!vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { - - // 3. extract frame number - frame_number = ost->frame_number; - - // 4. calculate fps - fps = t > 1 ? frame_number / t : 0; - } - - // 5. calculate time - if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) - pts = FFMAX(pts, av_rescale_q(av_stream_get_end_pts(ost->st), - ost->st->time_base, AV_TIME_BASE_Q)); - - vid = 1; - } - - // 6. calculate time, with microseconds to milliseconds conversion - seconds = FFABS(pts) / 1000; - - // 7. calculating kbit/s value - bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; - - // 9. calculate processing speed = processed stream duration/operation duration - speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; - // FORWARD DATA if (report_callback != NULL) { - report_callback(frame_number, fps, quality, total_size, seconds, bitrate, speed); + report_callback(frame_number, fps, quality, total_size, ((double)seconds*1000) + ((double)microseconds)/1000, bitrate, speed); } } static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time) { AVBPrint buf, buf_script; - OutputStream *ost; - AVFormatContext *oc; - int64_t total_size; - AVCodecContext *enc; - int vid, i; + int64_t total_size = of_filesize(output_files[0]); + int vid; double bitrate; double speed; int64_t pts = INT64_MIN + 1; @@ -1728,6 +1745,15 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti int ret; float t; + // FFmpegKit field declarations + int local_print_stats = 1; + uint64_t frame_number = 0; + float fps = 0; + float q = 0; + + if (!print_stats && !is_last_report && !progress_avio) + local_print_stats = 0; + if (!is_last_report) { if (last_time == -1) { last_time = cur_time; @@ -1738,58 +1764,48 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti last_time = cur_time; } - forward_report(is_last_report, timer_start, cur_time); - - if (!print_stats && !is_last_report && !progress_avio) - return; - t = (cur_time-timer_start) / 1000000.0; - - oc = output_files[0]->ctx; - - total_size = avio_size(oc->pb); - if (total_size <= 0) // FIXME improve avio_size() so it works with non seekable output too - total_size = avio_tell(oc->pb); - vid = 0; - av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); - av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC); - for (i = 0; i < nb_output_streams; i++) { - float q = -1; - ost = output_streams[i]; - enc = ost->enc_ctx; - if (!ost->stream_copy) - q = ost->quality / (float) FF_QP2LAMBDA; - - if (vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { + if (local_print_stats) { + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); + av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC); + } + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + const AVCodecContext * const enc = ost->enc_ctx; + q = enc ? ost->quality / (float) FF_QP2LAMBDA : -1; + + if (local_print_stats && vid && ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { av_bprintf(&buf, "q=%2.1f ", q); av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", ost->file_index, ost->index, q); } - if (!vid && enc->codec_type == AVMEDIA_TYPE_VIDEO) { - float fps; - int64_t frame_number = ost->frame_number; - + if (!vid && ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + frame_number = atomic_load(&ost->packets_written); fps = t > 1 ? frame_number / t : 0; - av_bprintf(&buf, "frame=%5"PRId64" fps=%3.*f q=%3.1f ", - frame_number, fps < 9.95, fps, q); - av_bprintf(&buf_script, "frame=%"PRId64"\n", frame_number); - av_bprintf(&buf_script, "fps=%.2f\n", fps); - av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", - ost->file_index, ost->index, q); - if (is_last_report) + if (local_print_stats) { + av_bprintf(&buf, "frame=%5"PRId64" fps=%3.*f q=%3.1f ", + frame_number, fps < 9.95, fps, q); + av_bprintf(&buf_script, "frame=%"PRId64"\n", frame_number); + av_bprintf(&buf_script, "fps=%.2f\n", fps); + av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n", + ost->file_index, ost->index, q); + } + if (local_print_stats && is_last_report) av_bprintf(&buf, "L"); if (qp_hist) { int j; int qp = lrintf(q); if (qp >= 0 && qp < FF_ARRAY_ELEMS(qp_histogram)) qp_histogram[qp]++; - for (j = 0; j < 32; j++) - av_bprintf(&buf, "%X", av_log2(qp_histogram[j] + 1)); + if (local_print_stats) { + for (j = 0; j < 32; j++) + av_bprintf(&buf, "%X", av_log2(qp_histogram[j] + 1)); + } } - if ((enc->flags & AV_CODEC_FLAG_PSNR) && (ost->pict_type != AV_PICTURE_TYPE_NONE || is_last_report)) { + if (local_print_stats && enc && (enc->flags & AV_CODEC_FLAG_PSNR) && + (ost->pict_type != AV_PICTURE_TYPE_NONE || is_last_report)) { int j; double error, error_sum = 0; double scale, scale_sum = 0; @@ -1821,9 +1837,8 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti vid = 1; } /* compute min output value */ - if (av_stream_get_end_pts(ost->st) != AV_NOPTS_VALUE) { - pts = FFMAX(pts, av_rescale_q(av_stream_get_end_pts(ost->st), - ost->st->time_base, AV_TIME_BASE_Q)); + if (ost->last_mux_dts != AV_NOPTS_VALUE) { + pts = FFMAX(pts, ost->last_mux_dts); if (copy_ts) { if (copy_ts_first_pts == AV_NOPTS_VALUE && pts > 1) copy_ts_first_pts = pts; @@ -1847,77 +1862,82 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; - if (total_size < 0) av_bprintf(&buf, "size=N/A time="); - else av_bprintf(&buf, "size=%8.0fkB time=", total_size / 1024.0); - if (pts == AV_NOPTS_VALUE) { - av_bprintf(&buf, "N/A "); - } else { - av_bprintf(&buf, "%s%02d:%02d:%02d.%02d ", - hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE); - } + // FFmpegKit forward report + forward_report(frame_number, fps, q, total_size, secs, us, bitrate, speed); - if (bitrate < 0) { - av_bprintf(&buf, "bitrate=N/A"); - av_bprintf(&buf_script, "bitrate=N/A\n"); - }else{ - av_bprintf(&buf, "bitrate=%6.1fkbits/s", bitrate); - av_bprintf(&buf_script, "bitrate=%6.1fkbits/s\n", bitrate); - } + if (local_print_stats) { + if (total_size < 0) av_bprintf(&buf, "size=N/A time="); + else av_bprintf(&buf, "size=%8.0fkB time=", total_size / 1024.0); + if (pts == AV_NOPTS_VALUE) { + av_bprintf(&buf, "N/A "); + } else { + av_bprintf(&buf, "%s%02d:%02d:%02d.%02d ", + hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE); + } - if (total_size < 0) av_bprintf(&buf_script, "total_size=N/A\n"); - else av_bprintf(&buf_script, "total_size=%"PRId64"\n", total_size); - if (pts == AV_NOPTS_VALUE) { - av_bprintf(&buf_script, "out_time_us=N/A\n"); - av_bprintf(&buf_script, "out_time_ms=N/A\n"); - av_bprintf(&buf_script, "out_time=N/A\n"); - } else { - av_bprintf(&buf_script, "out_time_us=%"PRId64"\n", pts); - av_bprintf(&buf_script, "out_time_ms=%"PRId64"\n", pts); - av_bprintf(&buf_script, "out_time=%s%02d:%02d:%02d.%06d\n", - hours_sign, hours, mins, secs, us); - } + if (bitrate < 0) { + av_bprintf(&buf, "bitrate=N/A"); + av_bprintf(&buf_script, "bitrate=N/A\n"); + }else{ + av_bprintf(&buf, "bitrate=%6.1fkbits/s", bitrate); + av_bprintf(&buf_script, "bitrate=%6.1fkbits/s\n", bitrate); + } - if (nb_frames_dup || nb_frames_drop) - av_bprintf(&buf, " dup=%"PRId64" drop=%"PRId64, nb_frames_dup, nb_frames_drop); - av_bprintf(&buf_script, "dup_frames=%"PRId64"\n", nb_frames_dup); - av_bprintf(&buf_script, "drop_frames=%"PRId64"\n", nb_frames_drop); + if (total_size < 0) av_bprintf(&buf_script, "total_size=N/A\n"); + else av_bprintf(&buf_script, "total_size=%"PRId64"\n", total_size); + if (pts == AV_NOPTS_VALUE) { + av_bprintf(&buf_script, "out_time_us=N/A\n"); + av_bprintf(&buf_script, "out_time_ms=N/A\n"); + av_bprintf(&buf_script, "out_time=N/A\n"); + } else { + av_bprintf(&buf_script, "out_time_us=%"PRId64"\n", pts); + av_bprintf(&buf_script, "out_time_ms=%"PRId64"\n", pts); + av_bprintf(&buf_script, "out_time=%s%02d:%02d:%02d.%06d\n", + hours_sign, hours, mins, secs, us); + } - if (speed < 0) { - av_bprintf(&buf, " speed=N/A"); - av_bprintf(&buf_script, "speed=N/A\n"); - } else { - av_bprintf(&buf, " speed=%4.3gx", speed); - av_bprintf(&buf_script, "speed=%4.3gx\n", speed); - } + if (nb_frames_dup || nb_frames_drop) + av_bprintf(&buf, " dup=%"PRId64" drop=%"PRId64, nb_frames_dup, nb_frames_drop); + av_bprintf(&buf_script, "dup_frames=%"PRId64"\n", nb_frames_dup); + av_bprintf(&buf_script, "drop_frames=%"PRId64"\n", nb_frames_drop); - if (print_stats || is_last_report) { - const char end = is_last_report ? '\n' : '\r'; - if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) { - av_log(NULL, AV_LOG_STDERR, "%s %c", buf.str, end); - } else - av_log(NULL, AV_LOG_INFO, "%s %c", buf.str, end); - } - av_bprint_finalize(&buf, NULL); - - if (progress_avio) { - av_bprintf(&buf_script, "progress=%s\n", - is_last_report ? "end" : "continue"); - avio_write(progress_avio, buf_script.str, - FFMIN(buf_script.len, buf_script.size - 1)); - avio_flush(progress_avio); - av_bprint_finalize(&buf_script, NULL); - if (is_last_report) { - if ((ret = avio_closep(&progress_avio)) < 0) - av_log(NULL, AV_LOG_ERROR, - "Error closing progress log, loss of information possible: %s\n", av_err2str(ret)); + if (speed < 0) { + av_bprintf(&buf, " speed=N/A"); + av_bprintf(&buf_script, "speed=N/A\n"); + } else { + av_bprintf(&buf, " speed=%4.3gx", speed); + av_bprintf(&buf_script, "speed=%4.3gx\n", speed); } - } - first_report = 0; - - if (is_last_report) - print_final_stats(total_size); -} + if (print_stats || is_last_report) { + const char end = is_last_report ? '\n' : '\r'; + if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) { + av_log(NULL, AV_LOG_STDERR, "%s %c", buf.str, end); + } else + av_log(NULL, AV_LOG_INFO, "%s %c", buf.str, end); + } + av_bprint_finalize(&buf, NULL); + + if (progress_avio) { + av_bprintf(&buf_script, "progress=%s\n", + is_last_report ? "end" : "continue"); + avio_write(progress_avio, buf_script.str, + FFMIN(buf_script.len, buf_script.size - 1)); + avio_flush(progress_avio); + av_bprint_finalize(&buf_script, NULL); + if (is_last_report) { + if ((ret = avio_closep(&progress_avio)) < 0) + av_log(NULL, AV_LOG_ERROR, + "Error closing progress log, loss of information possible: %s\n", av_err2str(ret)); + } + } + + first_report = 0; + + if (is_last_report) + print_final_stats(total_size); + } +} static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par) { @@ -1939,14 +1959,19 @@ static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParamet static void flush_encoders(void) { - int i, ret; + int ret; + + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + OutputFile *of = output_files[ost->file_index]; + if (ost->sq_idx_encode >= 0) + sq_send(of->sq_encode, ost->sq_idx_encode, SQFRAME(NULL)); + } - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { AVCodecContext *enc = ost->enc_ctx; OutputFile *of = output_files[ost->file_index]; - if (!ost->encoding_needed) + if (!enc) continue; // Try to enable encoding with no input frames. @@ -1954,17 +1979,16 @@ static void flush_encoders(void) if (!ost->initialized) { FilterGraph *fg = ost->filter->graph; - av_log(NULL, AV_LOG_WARNING, - "Finishing stream %d:%d without any data written to it.\n", - ost->file_index, ost->st->index); + av_log(ost, AV_LOG_WARNING, + "Finishing stream without any data written to it.\n"); if (ost->filter && !fg->graph) { int x; for (x = 0; x < fg->nb_inputs; x++) { InputFilter *ifilter = fg->inputs[x]; if (ifilter->format < 0 && - ifilter_parameters_from_codecpar(ifilter, ifilter->ist->st->codecpar) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error copying paramerets from input stream\n"); + ifilter_parameters_from_codecpar(ifilter, ifilter->ist->par) < 0) { + av_log(ost, AV_LOG_ERROR, "Error copying paramerets from input stream\n"); exit_program(1); } } @@ -1974,11 +1998,11 @@ static void flush_encoders(void) ret = configure_filtergraph(fg); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error configuring filter graph\n"); + av_log(ost, AV_LOG_ERROR, "Error configuring filter graph\n"); exit_program(1); } - finish_output_stream(ost); + of_output_packet(of, ost->pkt, ost, 1); } init_output_stream_wrapper(ost, NULL, 1); @@ -1987,7 +2011,7 @@ static void flush_encoders(void) if (enc->codec_type != AVMEDIA_TYPE_VIDEO && enc->codec_type != AVMEDIA_TYPE_AUDIO) continue; - ret = encode_frame(of, ost, NULL); + ret = submit_encode_frame(of, ost, NULL); if (ret != AVERROR_EOF) exit_program(1); } @@ -1999,9 +2023,8 @@ static void flush_encoders(void) static int check_output_constraints(InputStream *ist, OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - int ist_index = input_files[ist->file_index]->ist_index + ist->st->index; - if (ost->source_index != ist_index) + if (ost->ist != ist) return 0; if (ost->finished & MUXER_FINISHED) @@ -2024,7 +2047,7 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p av_packet_unref(opkt); // EOF: flush output bitstream filters. if (!pkt) { - output_packet(of, opkt, ost, 1); + of_output_packet(of, opkt, ost, 1); return; } @@ -2033,12 +2056,9 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p return; if (!ost->streamcopy_started && !ost->copy_prior_start) { - int64_t comp_start = start_time; - if (copy_ts && f->start_time != AV_NOPTS_VALUE) - comp_start = FFMAX(start_time, f->start_time + f->ts_offset); if (pkt->pts == AV_NOPTS_VALUE ? - ist->pts < comp_start : - pkt->pts < av_rescale_q(comp_start, AV_TIME_BASE_Q, ist->st->time_base)) + ist->pts < ost->ts_copy_start : + pkt->pts < av_rescale_q(ost->ts_copy_start, AV_TIME_BASE_Q, ist->st->time_base)) return; } @@ -2052,7 +2072,7 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p start_time = 0; if (copy_ts) { start_time += f->start_time != AV_NOPTS_VALUE ? f->start_time : 0; - start_time += start_at_zero ? 0 : f->ctx->start_time; + start_time += start_at_zero ? 0 : f->start_time_effective; } if (ist->pts >= f->recording_time + start_time) { close_output_stream(ost); @@ -2063,52 +2083,43 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p if (av_packet_ref(opkt, pkt) < 0) exit_program(1); + opkt->time_base = ost->mux_timebase; + if (pkt->pts != AV_NOPTS_VALUE) - opkt->pts = av_rescale_q(pkt->pts, ist->st->time_base, ost->mux_timebase) - ost_tb_start_time; + opkt->pts = av_rescale_q(pkt->pts, ist->st->time_base, opkt->time_base) - ost_tb_start_time; if (pkt->dts == AV_NOPTS_VALUE) { - opkt->dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ost->mux_timebase); + opkt->dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, opkt->time_base); } else if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - int duration = av_get_audio_frame_duration(ist->dec_ctx, pkt->size); + int duration = av_get_audio_frame_duration2(ist->par, pkt->size); if(!duration) - duration = ist->dec_ctx->frame_size; + duration = ist->par->frame_size; opkt->dts = av_rescale_delta(ist->st->time_base, pkt->dts, - (AVRational){1, ist->dec_ctx->sample_rate}, duration, - &ist->filter_in_rescale_delta_last, ost->mux_timebase); + (AVRational){1, ist->par->sample_rate}, duration, + &ist->filter_in_rescale_delta_last, opkt->time_base); /* dts will be set immediately afterwards to what pts is now */ opkt->pts = opkt->dts - ost_tb_start_time; } else - opkt->dts = av_rescale_q(pkt->dts, ist->st->time_base, ost->mux_timebase); + opkt->dts = av_rescale_q(pkt->dts, ist->st->time_base, opkt->time_base); opkt->dts -= ost_tb_start_time; - opkt->duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->mux_timebase); + opkt->duration = av_rescale_q(pkt->duration, ist->st->time_base, opkt->time_base); - ost->sync_opts += opkt->duration; + { + int ret = trigger_fix_sub_duration_heartbeat(ost, pkt); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "Subtitle heartbeat logic failed in %s! (%s)\n", + __func__, av_err2str(ret)); + exit_program(1); + } + } - output_packet(of, opkt, ost, 0); + of_output_packet(of, opkt, ost, 0); ost->streamcopy_started = 1; } -int guess_input_channel_layout(InputStream *ist) -{ - AVCodecContext *dec = ist->dec_ctx; - - if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) { - char layout_name[256]; - - if (dec->ch_layout.nb_channels > ist->guess_layout_max) - return 0; - av_channel_layout_default(&dec->ch_layout, dec->ch_layout.nb_channels); - if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) - return 0; - av_channel_layout_describe(&dec->ch_layout, layout_name, sizeof(layout_name)); - av_log(NULL, AV_LOG_WARNING, "Guessed Channel Layout for Input Stream " - "#%d.%d : %s\n", ist->file_index, ist->st->index, layout_name); - } - return 1; -} - static void check_decode_result(InputStream *ist, int *got_output, int ret) { if (*got_output || ret<0) @@ -2152,7 +2163,7 @@ static int ifilter_send_frame(InputFilter *ifilter, AVFrame *frame, int keep_ref /* determine if the parameters for this input changed */ need_reinit = ifilter->format != frame->format; - switch (ifilter->ist->st->codecpar->codec_type) { + switch (ifilter->ist->par->codec_type) { case AVMEDIA_TYPE_AUDIO: need_reinit |= ifilter->sample_rate != frame->sample_rate || av_channel_layout_compare(&ifilter->ch_layout, &frame->ch_layout); @@ -2236,7 +2247,7 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts) } else { // the filtergraph was never configured if (ifilter->format < 0) { - ret = ifilter_parameters_from_codecpar(ifilter, ifilter->ist->st->codecpar); + ret = ifilter_parameters_from_codecpar(ifilter, ifilter->ist->par); if (ret < 0) return ret; } @@ -2253,7 +2264,8 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts) // There is the following difference: if you got a frame, you must call // it again with pkt=NULL. pkt==NULL is treated differently from pkt->size==0 // (pkt==NULL means get more output, pkt->size==0 is a flush/drain packet) -static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt) +static int decode(InputStream *ist, AVCodecContext *avctx, + AVFrame *frame, int *got_frame, AVPacket *pkt) { int ret; @@ -2270,8 +2282,24 @@ static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacke ret = avcodec_receive_frame(avctx, frame); if (ret < 0 && ret != AVERROR(EAGAIN)) return ret; - if (ret >= 0) + if (ret >= 0) { + if (ist->want_frame_data) { + FrameData *fd; + + av_assert0(!frame->opaque_ref); + frame->opaque_ref = av_buffer_allocz(sizeof(*fd)); + if (!frame->opaque_ref) { + av_frame_unref(frame); + return AVERROR(ENOMEM); + } + fd = (FrameData*)frame->opaque_ref->data; + fd->pts = frame->pts; + fd->tb = avctx->pkt_timebase; + fd->idx = avctx->frame_num - 1; + } + *got_frame = 1; + } return 0; } @@ -2303,16 +2331,11 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, AVRational decoded_frame_tb; update_benchmark(NULL); - ret = decode(avctx, decoded_frame, got_output, pkt); + ret = decode(ist, avctx, decoded_frame, got_output, pkt); update_benchmark("decode_audio %d.%d", ist->file_index, ist->st->index); if (ret < 0) *decode_failed = 1; - if (ret >= 0 && avctx->sample_rate <= 0) { - av_log(avctx, AV_LOG_ERROR, "Sample rate %d invalid\n", avctx->sample_rate); - ret = AVERROR_INVALIDDATA; - } - if (ret != AVERROR_EOF) check_decode_result(ist, got_output, ret); @@ -2325,9 +2348,9 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, /* increment next_dts to use for the case where the input stream does not have timestamps or there are multiple frames in the packet */ ist->next_pts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / - avctx->sample_rate; + decoded_frame->sample_rate; ist->next_dts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / - avctx->sample_rate; + decoded_frame->sample_rate; if (decoded_frame->pts != AV_NOPTS_VALUE) { decoded_frame_tb = ist->st->time_base; @@ -2345,8 +2368,10 @@ static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, ist->prev_pkt_pts = pkt->pts; if (decoded_frame->pts != AV_NOPTS_VALUE) decoded_frame->pts = av_rescale_delta(decoded_frame_tb, decoded_frame->pts, - (AVRational){1, avctx->sample_rate}, decoded_frame->nb_samples, &ist->filter_in_rescale_delta_last, - (AVRational){1, avctx->sample_rate}); + (AVRational){1, decoded_frame->sample_rate}, + decoded_frame->nb_samples, + &ist->filter_in_rescale_delta_last, + (AVRational){1, decoded_frame->sample_rate}); ist->nb_samples = decoded_frame->nb_samples; err = send_frame_to_filters(ist, decoded_frame); @@ -2385,16 +2410,16 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ } update_benchmark(NULL); - ret = decode(ist->dec_ctx, decoded_frame, got_output, pkt); + ret = decode(ist, ist->dec_ctx, decoded_frame, got_output, pkt); update_benchmark("decode_video %d.%d", ist->file_index, ist->st->index); if (ret < 0) *decode_failed = 1; // The following line may be required in some cases where there is no parser // or the parser does not has_b_frames correctly - if (ist->st->codecpar->video_delay < ist->dec_ctx->has_b_frames) { + if (ist->par->video_delay < ist->dec_ctx->has_b_frames) { if (ist->dec_ctx->codec_id == AV_CODEC_ID_H264) { - ist->st->codecpar->video_delay = ist->dec_ctx->has_b_frames; + ist->par->video_delay = ist->dec_ctx->has_b_frames; } else av_log(ist->dec_ctx, AV_LOG_WARNING, "video_delay is larger in decoder than demuxer %d > %d.\n" @@ -2402,7 +2427,7 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ "of this file to https://streams.videolan.org/upload/ " "and contact the ffmpeg-devel mailing list. (ffmpeg-devel@ffmpeg.org)\n", ist->dec_ctx->has_b_frames, - ist->st->codecpar->video_delay); + ist->par->video_delay); } if (ret != AVERROR_EOF) @@ -2435,10 +2460,9 @@ static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_ if (err < 0) goto fail; } - ist->hwaccel_retrieved_pix_fmt = decoded_frame->format; best_effort_timestamp= decoded_frame->best_effort_timestamp; - *duration_pts = decoded_frame->pkt_duration; + *duration_pts = decoded_frame->duration; if (ist->framerate.num) best_effort_timestamp = ist->cfr_next_pts++; @@ -2479,30 +2503,18 @@ fail: return err < 0 ? err : ret; } -static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, - int *decode_failed) +static int process_subtitle(InputStream *ist, AVSubtitle *subtitle, int *got_output) { - AVSubtitle subtitle; + int ret = 0; int free_sub = 1; - int i, ret = avcodec_decode_subtitle2(ist->dec_ctx, - &subtitle, got_output, pkt); - - check_decode_result(NULL, got_output, ret); - - if (ret < 0 || !*got_output) { - *decode_failed = 1; - if (!pkt->size) - sub2video_flush(ist); - return ret; - } if (ist->fix_sub_duration) { int end = 1; if (ist->prev_sub.got_output) { - end = av_rescale(subtitle.pts - ist->prev_sub.subtitle.pts, + end = av_rescale(subtitle->pts - ist->prev_sub.subtitle.pts, 1000, AV_TIME_BASE); if (end < ist->prev_sub.subtitle.end_display_time) { - av_log(ist->dec_ctx, AV_LOG_DEBUG, + av_log(NULL, AV_LOG_DEBUG, "Subtitle duration reduced from %"PRId32" to %d%s\n", ist->prev_sub.subtitle.end_display_time, end, end <= 0 ? ", dropping it" : ""); @@ -2511,7 +2523,7 @@ static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, } FFSWAP(int, *got_output, ist->prev_sub.got_output); FFSWAP(int, ret, ist->prev_sub.ret); - FFSWAP(AVSubtitle, subtitle, ist->prev_sub.subtitle); + FFSWAP(AVSubtitle, *subtitle, ist->prev_sub.subtitle); if (end <= 0) goto out; } @@ -2520,40 +2532,187 @@ static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, return ret; if (ist->sub2video.frame) { - sub2video_update(ist, INT64_MIN, &subtitle); + sub2video_update(ist, INT64_MIN, subtitle); } else if (ist->nb_filters) { if (!ist->sub2video.sub_queue) ist->sub2video.sub_queue = av_fifo_alloc2(8, sizeof(AVSubtitle), AV_FIFO_FLAG_AUTO_GROW); if (!ist->sub2video.sub_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); - ret = av_fifo_write(ist->sub2video.sub_queue, &subtitle, 1); + ret = av_fifo_write(ist->sub2video.sub_queue, subtitle, 1); if (ret < 0) exit_program(1); free_sub = 0; } - if (!subtitle.num_rects) + if (!subtitle->num_rects) goto out; - ist->frames_decoded++; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!check_output_constraints(ist, ost) || !ost->encoding_needed - || ost->enc->type != AVMEDIA_TYPE_SUBTITLE) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (!check_output_constraints(ist, ost) || !ost->enc_ctx + || ost->enc_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE) continue; - do_subtitle_out(output_files[ost->file_index], ost, &subtitle); + do_subtitle_out(output_files[ost->file_index], ost, subtitle); } out: if (free_sub) - avsubtitle_free(&subtitle); + avsubtitle_free(subtitle); + return ret; +} + +static int copy_av_subtitle(AVSubtitle *dst, AVSubtitle *src) +{ + int ret = AVERROR_BUG; + AVSubtitle tmp = { + .format = src->format, + .start_display_time = src->start_display_time, + .end_display_time = src->end_display_time, + .num_rects = 0, + .rects = NULL, + .pts = src->pts + }; + + if (!src->num_rects) + goto success; + + if (!(tmp.rects = av_calloc(src->num_rects, sizeof(*tmp.rects)))) + return AVERROR(ENOMEM); + + for (int i = 0; i < src->num_rects; i++) { + AVSubtitleRect *src_rect = src->rects[i]; + AVSubtitleRect *dst_rect; + + if (!(dst_rect = tmp.rects[i] = av_mallocz(sizeof(*tmp.rects[0])))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + tmp.num_rects++; + + dst_rect->type = src_rect->type; + dst_rect->flags = src_rect->flags; + + dst_rect->x = src_rect->x; + dst_rect->y = src_rect->y; + dst_rect->w = src_rect->w; + dst_rect->h = src_rect->h; + dst_rect->nb_colors = src_rect->nb_colors; + + if (src_rect->text) + if (!(dst_rect->text = av_strdup(src_rect->text))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + if (src_rect->ass) + if (!(dst_rect->ass = av_strdup(src_rect->ass))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + + for (int j = 0; j < 4; j++) { + // SUBTITLE_BITMAP images are special in the sense that they + // are like PAL8 images. first pointer to data, second to + // palette. This makes the size calculation match this. + size_t buf_size = src_rect->type == SUBTITLE_BITMAP && j == 1 ? + AVPALETTE_SIZE : + src_rect->h * src_rect->linesize[j]; + + if (!src_rect->data[j]) + continue; + + if (!(dst_rect->data[j] = av_memdup(src_rect->data[j], buf_size))) { + ret = AVERROR(ENOMEM); + goto cleanup; + } + dst_rect->linesize[j] = src_rect->linesize[j]; + } + } + +success: + *dst = tmp; + + return 0; + +cleanup: + avsubtitle_free(&tmp); + return ret; } +static int fix_sub_duration_heartbeat(InputStream *ist, int64_t signal_pts) +{ + int ret = AVERROR_BUG; + int got_output = 1; + AVSubtitle *prev_subtitle = &ist->prev_sub.subtitle; + AVSubtitle subtitle; + + if (!ist->fix_sub_duration || !prev_subtitle->num_rects || + signal_pts <= prev_subtitle->pts) + return 0; + + if ((ret = copy_av_subtitle(&subtitle, prev_subtitle)) < 0) + return ret; + + subtitle.pts = signal_pts; + + return process_subtitle(ist, &subtitle, &got_output); +} + +static int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt) +{ + OutputFile *of = output_files[ost->file_index]; + int64_t signal_pts = av_rescale_q(pkt->pts, pkt->time_base, + AV_TIME_BASE_Q); + + if (!ost->fix_sub_duration_heartbeat || !(pkt->flags & AV_PKT_FLAG_KEY)) + // we are only interested in heartbeats on streams configured, and + // only on random access points. + return 0; + + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *iter_ost = of->streams[i]; + InputStream *ist = iter_ost->ist; + int ret = AVERROR_BUG; + + if (iter_ost == ost || !ist || !ist->decoding_needed || + ist->dec_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE) + // We wish to skip the stream that causes the heartbeat, + // output streams without an input stream, streams not decoded + // (as fix_sub_duration is only done for decoded subtitles) as + // well as non-subtitle streams. + continue; + + if ((ret = fix_sub_duration_heartbeat(ist, signal_pts)) < 0) + return ret; + } + + return 0; +} + +static int transcode_subtitles(InputStream *ist, const AVPacket *pkt, + int *got_output, int *decode_failed) +{ + AVSubtitle subtitle; + int ret = avcodec_decode_subtitle2(ist->dec_ctx, + &subtitle, got_output, pkt); + + check_decode_result(NULL, got_output, ret); + + if (ret < 0 || !*got_output) { + *decode_failed = 1; + if (!pkt->size) + sub2video_flush(ist); + return ret; + } + + ist->frames_decoded++; + + return process_subtitle(ist, &subtitle, got_output); +} + static int send_filter_eof(InputStream *ist) { int i, ret; @@ -2572,7 +2731,8 @@ static int send_filter_eof(InputStream *ist) /* pkt = NULL means EOF (needed to flush decoder buffers) */ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof) { - int ret = 0, i; + const AVCodecParameters *par = ist->par; + int ret = 0; int repeating = 0; int eof_reached = 0; @@ -2604,7 +2764,7 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo if (pkt && pkt->dts != AV_NOPTS_VALUE) { ist->next_dts = ist->dts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); - if (ist->dec_ctx->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) + if (par->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) ist->next_pts = ist->pts = ist->dts; } @@ -2618,7 +2778,7 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo ist->pts = ist->next_pts; ist->dts = ist->next_dts; - switch (ist->dec_ctx->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: ret = decode_audio (ist, repeating ? NULL : avpkt, &got_output, &decode_failed); @@ -2631,7 +2791,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo if (pkt && pkt->duration) { duration_dts = av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } else if(ist->dec_ctx->framerate.num != 0 && ist->dec_ctx->framerate.den != 0) { - int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict+1 : ist->dec_ctx->ticks_per_frame; + int ticks = ist->last_pkt_repeat_pict >= 0 ? + ist->last_pkt_repeat_pict + 1 : + ist->dec_ctx->ticks_per_frame; duration_dts = ((int64_t)AV_TIME_BASE * ist->dec_ctx->framerate.den * ticks) / ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; @@ -2715,12 +2877,12 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo /* handle stream copy */ if (!ist->decoding_needed && pkt) { ist->dts = ist->next_dts; - switch (ist->dec_ctx->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: av_assert1(pkt->duration >= 0); - if (ist->dec_ctx->sample_rate) { - ist->next_dts += ((int64_t)AV_TIME_BASE * ist->dec_ctx->frame_size) / - ist->dec_ctx->sample_rate; + if (par->sample_rate) { + ist->next_dts += ((int64_t)AV_TIME_BASE * par->frame_size) / + par->sample_rate; } else { ist->next_dts += av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } @@ -2734,7 +2896,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo } else if (pkt->duration) { ist->next_dts += av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } else if(ist->dec_ctx->framerate.num != 0) { - int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict + 1 : ist->dec_ctx->ticks_per_frame; + int ticks = ist->last_pkt_repeat_pict >= 0 ? + ist->last_pkt_repeat_pict + 1 : + ist->dec_ctx->ticks_per_frame; ist->next_dts += ((int64_t)AV_TIME_BASE * ist->dec_ctx->framerate.den * ticks) / ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; @@ -2746,10 +2910,9 @@ static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eo } else if (!ist->decoding_needed) eof_reached = 1; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (!check_output_constraints(ist, ost) || ost->encoding_needed) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (!check_output_constraints(ist, ost) || ost->enc_ctx || + (!pkt && no_eof)) continue; do_streamcopy(ist, ost, pkt); @@ -2807,10 +2970,9 @@ static enum AVPixelFormat get_format(AVCodecContext *s, const enum AVPixelFormat return *p; } -static int init_input_stream(int ist_index, char *error, int error_len) +static int init_input_stream(InputStream *ist, char *error, int error_len) { int ret; - InputStream *ist = input_streams[ist_index]; if (ist->decoding_needed) { const AVCodec *codec = ist->dec; @@ -2822,11 +2984,6 @@ static int init_input_stream(int ist_index, char *error, int error_len) ist->dec_ctx->opaque = ist; ist->dec_ctx->get_format = get_format; -#if LIBAVCODEC_VERSION_MAJOR < 60 - AV_NOWARN_DEPRECATED({ - ist->dec_ctx->thread_safe_callbacks = 1; - }) -#endif if (ist->dec_ctx->codec_id == AV_CODEC_ID_DVB_SUBTITLE && (ist->decoding_needed & DECODING_FOR_OST)) { @@ -2872,71 +3029,37 @@ static int init_input_stream(int ist_index, char *error, int error_len) return 0; } -static InputStream *get_input_stream(OutputStream *ost) -{ - if (ost->source_index >= 0) - return input_streams[ost->source_index]; - return NULL; -} - -static int compare_int64(const void *a, const void *b) -{ - return FFDIFFSIGN(*(const int64_t *)a, *(const int64_t *)b); -} - -static int init_output_bsfs(OutputStream *ost) -{ - AVBSFContext *ctx = ost->bsf_ctx; - int ret; - - if (!ctx) - return 0; - - ret = avcodec_parameters_copy(ctx->par_in, ost->st->codecpar); - if (ret < 0) - return ret; - - ctx->time_base_in = ost->st->time_base; - - ret = av_bsf_init(ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing bitstream filter: %s\n", - ctx->filter->name); - return ret; - } - - ret = avcodec_parameters_copy(ost->st->codecpar, ctx->par_out); - if (ret < 0) - return ret; - ost->st->time_base = ctx->time_base_out; - - return 0; -} - static int init_output_stream_streamcopy(OutputStream *ost) { OutputFile *of = output_files[ost->file_index]; - InputStream *ist = get_input_stream(ost); - AVCodecParameters *par_dst = ost->st->codecpar; - AVCodecParameters *par_src = ost->ref_par; + InputStream *ist = ost->ist; + InputFile *ifile = input_files[ist->file_index]; + AVCodecParameters *par = ost->st->codecpar; + AVCodecContext *codec_ctx; AVRational sar; int i, ret; - uint32_t codec_tag = par_dst->codec_tag; + uint32_t codec_tag = par->codec_tag; av_assert0(ist && !ost->filter); - ret = avcodec_parameters_to_context(ost->enc_ctx, ist->st->codecpar); + codec_ctx = avcodec_alloc_context3(NULL); + if (!codec_ctx) + return AVERROR(ENOMEM); + + ret = avcodec_parameters_to_context(codec_ctx, ist->par); if (ret >= 0) - ret = av_opt_set_dict(ost->enc_ctx, &ost->encoder_opts); + ret = av_opt_set_dict(codec_ctx, &ost->encoder_opts); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error setting up codec context options.\n"); + avcodec_free_context(&codec_ctx); return ret; } - ret = avcodec_parameters_from_context(par_src, ost->enc_ctx); + ret = avcodec_parameters_from_context(par, codec_ctx); + avcodec_free_context(&codec_ctx); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error getting reference codec parameters.\n"); return ret; } @@ -2944,16 +3067,12 @@ static int init_output_stream_streamcopy(OutputStream *ost) if (!codec_tag) { unsigned int codec_tag_tmp; if (!of->format->codec_tag || - av_codec_get_id (of->format->codec_tag, par_src->codec_tag) == par_src->codec_id || - !av_codec_get_tag2(of->format->codec_tag, par_src->codec_id, &codec_tag_tmp)) - codec_tag = par_src->codec_tag; + av_codec_get_id (of->format->codec_tag, par->codec_tag) == par->codec_id || + !av_codec_get_tag2(of->format->codec_tag, par->codec_id, &codec_tag_tmp)) + codec_tag = par->codec_tag; } - ret = avcodec_parameters_copy(par_dst, par_src); - if (ret < 0) - return ret; - - par_dst->codec_tag = codec_tag; + par->codec_tag = codec_tag; if (!ost->frame_rate.num) ost->frame_rate = ist->framerate; @@ -2979,6 +3098,15 @@ static int init_output_stream_streamcopy(OutputStream *ost) if (ost->st->duration <= 0 && ist->st->duration > 0) ost->st->duration = av_rescale_q(ist->st->duration, ist->st->time_base, ost->st->time_base); + if (!ost->copy_prior_start) { + ost->ts_copy_start = (of->start_time == AV_NOPTS_VALUE) ? + 0 : of->start_time; + if (copy_ts && ifile->start_time != AV_NOPTS_VALUE) { + ost->ts_copy_start = FFMAX(ost->ts_copy_start, + ifile->start_time + ifile->ts_offset); + } + } + if (ist->st->nb_side_data) { for (i = 0; i < ist->st->nb_side_data; i++) { const AVPacketSideData *sd_src = &ist->st->side_data[i]; @@ -2991,37 +3119,36 @@ static int init_output_stream_streamcopy(OutputStream *ost) } } +#if FFMPEG_ROTATION_METADATA if (ost->rotate_overridden) { uint8_t *sd = av_stream_new_side_data(ost->st, AV_PKT_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); if (sd) av_display_rotation_set((int32_t *)sd, -ost->rotate_override_value); } +#endif - switch (par_dst->codec_type) { + switch (par->codec_type) { case AVMEDIA_TYPE_AUDIO: - if (audio_volume != 256) { - av_log(NULL, AV_LOG_FATAL, "-acodec copy and -vol are incompatible (frames are not decoded)\n"); - exit_program(1); - } - if((par_dst->block_align == 1 || par_dst->block_align == 1152 || par_dst->block_align == 576) && par_dst->codec_id == AV_CODEC_ID_MP3) - par_dst->block_align= 0; - if(par_dst->codec_id == AV_CODEC_ID_AC3) - par_dst->block_align= 0; + if ((par->block_align == 1 || par->block_align == 1152 || par->block_align == 576) && + par->codec_id == AV_CODEC_ID_MP3) + par->block_align = 0; + if (par->codec_id == AV_CODEC_ID_AC3) + par->block_align = 0; break; case AVMEDIA_TYPE_VIDEO: if (ost->frame_aspect_ratio.num) { // overridden by the -aspect cli option sar = av_mul_q(ost->frame_aspect_ratio, - (AVRational){ par_dst->height, par_dst->width }); - av_log(NULL, AV_LOG_WARNING, "Overriding aspect ratio " + (AVRational){ par->height, par->width }); + av_log(ost, AV_LOG_WARNING, "Overriding aspect ratio " "with stream copy may produce invalid files\n"); } else if (ist->st->sample_aspect_ratio.num) sar = ist->st->sample_aspect_ratio; else - sar = par_src->sample_aspect_ratio; - ost->st->sample_aspect_ratio = par_dst->sample_aspect_ratio = sar; + sar = par->sample_aspect_ratio; + ost->st->sample_aspect_ratio = par->sample_aspect_ratio = sar; ost->st->avg_frame_rate = ist->st->avg_frame_rate; ost->st->r_frame_rate = ist->st->r_frame_rate; break; @@ -3034,113 +3161,31 @@ static int init_output_stream_streamcopy(OutputStream *ost) static void set_encoder_id(OutputFile *of, OutputStream *ost) { - const AVDictionaryEntry *e; - + const char *cname = ost->enc_ctx->codec->name; uint8_t *encoder_string; int encoder_string_len; - int format_flags = 0; - int codec_flags = ost->enc_ctx->flags; if (av_dict_get(ost->st->metadata, "encoder", NULL, 0)) return; - e = av_dict_get(of->opts, "fflags", NULL, 0); - if (e) { - const AVOption *o = av_opt_find(of->ctx, "fflags", NULL, 0, 0); - if (!o) - return; - av_opt_eval_flags(of->ctx, o, e->value, &format_flags); - } - e = av_dict_get(ost->encoder_opts, "flags", NULL, 0); - if (e) { - const AVOption *o = av_opt_find(ost->enc_ctx, "flags", NULL, 0, 0); - if (!o) - return; - av_opt_eval_flags(ost->enc_ctx, o, e->value, &codec_flags); - } - - encoder_string_len = sizeof(LIBAVCODEC_IDENT) + strlen(ost->enc->name) + 2; + encoder_string_len = sizeof(LIBAVCODEC_IDENT) + strlen(cname) + 2; encoder_string = av_mallocz(encoder_string_len); if (!encoder_string) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); - if (!(format_flags & AVFMT_FLAG_BITEXACT) && !(codec_flags & AV_CODEC_FLAG_BITEXACT)) + if (!of->bitexact && !ost->bitexact) av_strlcpy(encoder_string, LIBAVCODEC_IDENT " ", encoder_string_len); else av_strlcpy(encoder_string, "Lavc ", encoder_string_len); - av_strlcat(encoder_string, ost->enc->name, encoder_string_len); + av_strlcat(encoder_string, cname, encoder_string_len); av_dict_set(&ost->st->metadata, "encoder", encoder_string, AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_OVERWRITE); } -static void parse_forced_key_frames(char *kf, OutputStream *ost, - AVCodecContext *avctx) -{ - char *p; - int n = 1, i, size, index = 0; - int64_t t, *pts; - - for (p = kf; *p; p++) - if (*p == ',') - n++; - size = n; - pts = av_malloc_array(size, sizeof(*pts)); - if (!pts) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate forced key frames array.\n"); - exit_program(1); - } - - p = kf; - for (i = 0; i < n; i++) { - char *next = strchr(p, ','); - - if (next) - *next++ = 0; - - if (!memcmp(p, "chapters", 8)) { - - AVFormatContext *avf = output_files[ost->file_index]->ctx; - int j; - - if (avf->nb_chapters > INT_MAX - size || - !(pts = av_realloc_f(pts, size += avf->nb_chapters - 1, - sizeof(*pts)))) { - av_log(NULL, AV_LOG_FATAL, - "Could not allocate forced key frames array.\n"); - exit_program(1); - } - t = p[8] ? parse_time_or_die("force_key_frames", p + 8, 1) : 0; - t = av_rescale_q(t, AV_TIME_BASE_Q, avctx->time_base); - - for (j = 0; j < avf->nb_chapters; j++) { - AVChapter *c = avf->chapters[j]; - av_assert1(index < size); - pts[index++] = av_rescale_q(c->start, c->time_base, - avctx->time_base) + t; - } - - } else { - - t = parse_time_or_die("force_key_frames", p, 1); - av_assert1(index < size); - pts[index++] = av_rescale_q(t, AV_TIME_BASE_Q, avctx->time_base); - - } - - p = next; - } - - av_assert0(index == size); - qsort(pts, size, sizeof(*pts), compare_int64); - ost->forced_kf_count = size; - ost->forced_kf_pts = pts; -} - static void init_encoder_time_base(OutputStream *ost, AVRational default_time_base) { - InputStream *ist = get_input_stream(ost); + InputStream *ist = ost->ist; AVCodecContext *enc_ctx = ost->enc_ctx; - AVFormatContext *oc; if (ost->enc_timebase.num > 0) { enc_ctx->time_base = ost->enc_timebase; @@ -3153,8 +3198,8 @@ static void init_encoder_time_base(OutputStream *ost, AVRational default_time_ba return; } - oc = output_files[ost->file_index]->ctx; - av_log(oc, AV_LOG_WARNING, "Input stream data not available, using default time base\n"); + av_log(ost, AV_LOG_WARNING, + "Input stream data not available, using default time base\n"); } enc_ctx->time_base = default_time_base; @@ -3162,11 +3207,10 @@ static void init_encoder_time_base(OutputStream *ost, AVRational default_time_ba static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) { - InputStream *ist = get_input_stream(ost); + InputStream *ist = ost->ist; AVCodecContext *enc_ctx = ost->enc_ctx; AVCodecContext *dec_ctx = NULL; OutputFile *of = output_files[ost->file_index]; - AVFormatContext *oc = of->ctx; int ret; set_encoder_id(output_files[ost->file_index], ost); @@ -3178,14 +3222,13 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) if (enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { if (!ost->frame_rate.num) ost->frame_rate = av_buffersink_get_frame_rate(ost->filter->filter); - if (ist && !ost->frame_rate.num && !ost->max_frame_rate.num) { + if (!ost->frame_rate.num && !ost->max_frame_rate.num) { ost->frame_rate = (AVRational){25, 1}; - av_log(NULL, AV_LOG_WARNING, + av_log(ost, AV_LOG_WARNING, "No information " "about the input framerate is available. Falling " - "back to a default value of 25fps for output stream #%d:%d. Use the -r option " - "if you want a different framerate.\n", - ost->file_index, ost->index); + "back to a default value of 25fps. Use the -r option " + "if you want a different framerate.\n"); } if (ost->max_frame_rate.num && @@ -3193,9 +3236,9 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) !ost->frame_rate.den)) ost->frame_rate = ost->max_frame_rate; - if (ost->enc->supported_framerates && !ost->force_fps) { - int idx = av_find_nearest_q_idx(ost->frame_rate, ost->enc->supported_framerates); - ost->frame_rate = ost->enc->supported_framerates[idx]; + if (enc_ctx->codec->supported_framerates && !ost->force_fps) { + int idx = av_find_nearest_q_idx(ost->frame_rate, enc_ctx->codec->supported_framerates); + ost->frame_rate = enc_ctx->codec->supported_framerates[idx]; } // reduce frame rate for mpeg4 to be within the spec limits if (enc_ctx->codec_id == AV_CODEC_ID_MPEG4) { @@ -3229,9 +3272,9 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) if ( av_q2d(enc_ctx->time_base) < 0.001 && ost->vsync_method != VSYNC_PASSTHROUGH && (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR || (ost->vsync_method == VSYNC_AUTO && !(of->format->flags & AVFMT_VARIABLE_FPS)))){ - av_log(oc, AV_LOG_WARNING, "Frame rate very high for a muxer not efficiently supporting it.\n" - "Please consider specifying a lower framerate, a different muxer or " - "setting vsync/fps_mode to vfr\n"); + av_log(ost, AV_LOG_WARNING, "Frame rate very high for a muxer not efficiently supporting it.\n" + "Please consider specifying a lower framerate, a different muxer or " + "setting vsync/fps_mode to vfr\n"); } enc_ctx->width = av_buffersink_get_w(ost->filter->filter); @@ -3283,33 +3326,41 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) enc_ctx->field_order = AV_FIELD_TT; } - if (ost->forced_keyframes) { - if (!strncmp(ost->forced_keyframes, "expr:", 5)) { - ret = av_expr_parse(&ost->forced_keyframes_pexpr, ost->forced_keyframes+5, - forced_keyframes_const_names, NULL, NULL, NULL, NULL, 0, NULL); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, - "Invalid force_key_frames expression '%s'\n", ost->forced_keyframes+5); - return ret; - } - ost->forced_keyframes_expr_const_values[FKF_N] = 0; - ost->forced_keyframes_expr_const_values[FKF_N_FORCED] = 0; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N] = NAN; - ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T] = NAN; - - // Don't parse the 'forced_keyframes' in case of 'keep-source-keyframes', - // parse it only for static kf timings - } else if(strncmp(ost->forced_keyframes, "source", 6)) { - parse_forced_key_frames(ost->forced_keyframes, ost, ost->enc_ctx); - } - } break; case AVMEDIA_TYPE_SUBTITLE: enc_ctx->time_base = AV_TIME_BASE_Q; if (!enc_ctx->width) { - enc_ctx->width = input_streams[ost->source_index]->st->codecpar->width; - enc_ctx->height = input_streams[ost->source_index]->st->codecpar->height; + enc_ctx->width = ost->ist->par->width; + enc_ctx->height = ost->ist->par->height; + } + if (dec_ctx && dec_ctx->subtitle_header) { + /* ASS code assumes this buffer is null terminated so add extra byte. */ + ost->enc_ctx->subtitle_header = av_mallocz(dec_ctx->subtitle_header_size + 1); + if (!ost->enc_ctx->subtitle_header) + return AVERROR(ENOMEM); + memcpy(ost->enc_ctx->subtitle_header, dec_ctx->subtitle_header, + dec_ctx->subtitle_header_size); + ost->enc_ctx->subtitle_header_size = dec_ctx->subtitle_header_size; + } + if (ist && ist->dec->type == AVMEDIA_TYPE_SUBTITLE && + enc_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { + int input_props = 0, output_props = 0; + AVCodecDescriptor const *input_descriptor = + avcodec_descriptor_get(ist->dec->id); + AVCodecDescriptor const *output_descriptor = + avcodec_descriptor_get(ost->enc_ctx->codec_id); + if (input_descriptor) + input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (output_descriptor) + output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (input_props && output_props && input_props != output_props) { + av_log(ost, AV_LOG_ERROR, + "Subtitle encoding currently only possible from text to text " + "or bitmap to bitmap"); + return AVERROR_INVALIDDATA; + } } + break; case AVMEDIA_TYPE_DATA: break; @@ -3318,6 +3369,12 @@ static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) break; } + if (ost->bitexact) + enc_ctx->flags |= AV_CODEC_FLAG_BITEXACT; + + if (ost->sq_idx_encode >= 0) + sq_set_tb(of->sq_encode, ost->sq_idx_encode, enc_ctx->time_base); + ost->mux_timebase = enc_ctx->time_base; return 0; @@ -3328,28 +3385,23 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, { int ret = 0; - if (ost->encoding_needed) { - const AVCodec *codec = ost->enc; - AVCodecContext *dec = NULL; - InputStream *ist; + if (ost->enc_ctx) { + const AVCodec *codec = ost->enc_ctx->codec; + InputStream *ist = ost->ist; ret = init_output_stream_encode(ost, frame); if (ret < 0) return ret; - if ((ist = get_input_stream(ost))) - dec = ist->dec_ctx; - if (dec && dec->subtitle_header) { - /* ASS code assumes this buffer is null terminated so add extra byte. */ - ost->enc_ctx->subtitle_header = av_mallocz(dec->subtitle_header_size + 1); - if (!ost->enc_ctx->subtitle_header) - return AVERROR(ENOMEM); - memcpy(ost->enc_ctx->subtitle_header, dec->subtitle_header, dec->subtitle_header_size); - ost->enc_ctx->subtitle_header_size = dec->subtitle_header_size; - } if (!av_dict_get(ost->encoder_opts, "threads", NULL, 0)) av_dict_set(&ost->encoder_opts, "threads", "auto", 0); + if (codec->capabilities & AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE) { + ret = av_dict_set(&ost->encoder_opts, "flags", "+copy_opaque", AV_DICT_MULTIKEY); + if (ret < 0) + return ret; + } + ret = hw_device_setup_for_encode(ost); if (ret < 0) { snprintf(error, error_len, "Device setup failed for " @@ -3358,24 +3410,6 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, return ret; } - if (ist && ist->dec->type == AVMEDIA_TYPE_SUBTITLE && ost->enc->type == AVMEDIA_TYPE_SUBTITLE) { - int input_props = 0, output_props = 0; - AVCodecDescriptor const *input_descriptor = - avcodec_descriptor_get(dec->codec_id); - AVCodecDescriptor const *output_descriptor = - avcodec_descriptor_get(ost->enc_ctx->codec_id); - if (input_descriptor) - input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (output_descriptor) - output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (input_props && output_props && input_props != output_props) { - snprintf(error, error_len, - "Subtitle encoding currently only possible from text to text " - "or bitmap to bitmap"); - return AVERROR_INVALIDDATA; - } - } - if ((ret = avcodec_open2(ost->enc_ctx, codec, &ost->encoder_opts)) < 0) { if (ret == AVERROR_EXPERIMENTAL) abort_codec_experimental(codec, 1); @@ -3385,19 +3419,19 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, ost->file_index, ost->index); return ret; } - if (ost->enc->type == AVMEDIA_TYPE_AUDIO && - !(ost->enc->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + if (codec->type == AVMEDIA_TYPE_AUDIO && + !(codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) av_buffersink_set_frame_size(ost->filter->filter, ost->enc_ctx->frame_size); assert_avoptions(ost->encoder_opts); if (ost->enc_ctx->bit_rate && ost->enc_ctx->bit_rate < 1000 && ost->enc_ctx->codec_id != AV_CODEC_ID_CODEC2 /* don't complain about 700 bit/s modes */) - av_log(NULL, AV_LOG_WARNING, "The bitrate parameter is set too low." - " It takes bits/s as argument, not kbits/s\n"); + av_log(ost, AV_LOG_WARNING, "The bitrate parameter is set too low." + " It takes bits/s as argument, not kbits/s\n"); ret = avcodec_parameters_from_context(ost->st->codecpar, ost->enc_ctx); if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, + av_log(ost, AV_LOG_FATAL, "Error initializing the output stream codec context.\n"); exit_program(1); } @@ -3433,7 +3467,7 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, return AVERROR(ENOMEM); memcpy(dst, sd->data, sd->size); if (ist->autorotate && sd->type == AV_PKT_DATA_DISPLAYMATRIX) - av_display_rotation_set((uint32_t *)dst, 0); + av_display_rotation_set((int32_t *)dst, 0); } } } @@ -3445,83 +3479,36 @@ static int init_output_stream(OutputStream *ost, AVFrame *frame, // copy estimated duration as a hint to the muxer if (ost->st->duration <= 0 && ist && ist->st->duration > 0) ost->st->duration = av_rescale_q(ist->st->duration, ist->st->time_base, ost->st->time_base); - } else if (ost->stream_copy) { + } else if (ost->ist) { ret = init_output_stream_streamcopy(ost); if (ret < 0) return ret; } - /* initialize bitstream filters for the output stream - * needs to be done here, because the codec id for streamcopy is not - * known until now */ - ret = init_output_bsfs(ost); - if (ret < 0) - return ret; - - ost->initialized = 1; - - ret = of_check_init(output_files[ost->file_index]); + ret = of_stream_init(output_files[ost->file_index], ost); if (ret < 0) return ret; return ret; } -static void report_new_stream(int input_index, AVPacket *pkt) -{ - InputFile *file = input_files[input_index]; - AVStream *st = file->ctx->streams[pkt->stream_index]; - - if (pkt->stream_index < file->nb_streams_warn) - return; - av_log(file->ctx, AV_LOG_WARNING, - "New %s stream %d:%d at pos:%"PRId64" and DTS:%ss\n", - av_get_media_type_string(st->codecpar->codec_type), - input_index, pkt->stream_index, - pkt->pos, av_ts2timestr(pkt->dts, &st->time_base)); - file->nb_streams_warn = pkt->stream_index + 1; -} - static int transcode_init(void) { - int ret = 0, i, j, k; - AVFormatContext *oc; - OutputStream *ost; - InputStream *ist; + int ret = 0; char error[1024] = {0}; - for (i = 0; i < nb_filtergraphs; i++) { - FilterGraph *fg = filtergraphs[i]; - for (j = 0; j < fg->nb_outputs; j++) { - OutputFilter *ofilter = fg->outputs[j]; - if (!ofilter->ost || ofilter->ost->source_index >= 0) - continue; - if (fg->nb_inputs != 1) - continue; - for (k = nb_input_streams-1; k >= 0 ; k--) - if (fg->inputs[0]->ist == input_streams[k]) - break; - ofilter->ost->source_index = k; - } - } - /* init framerate emulation */ - for (i = 0; i < nb_input_files; i++) { + for (int i = 0; i < nb_input_files; i++) { InputFile *ifile = input_files[i]; if (ifile->readrate || ifile->rate_emu) - for (j = 0; j < ifile->nb_streams; j++) - input_streams[j + ifile->ist_index]->start = av_gettime_relative(); + for (int j = 0; j < ifile->nb_streams; j++) + ifile->streams[j]->start = av_gettime_relative(); } /* init input streams */ - for (i = 0; i < nb_input_streams; i++) - if ((ret = init_input_stream(i, error, sizeof(error))) < 0) { - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - avcodec_close(ost->enc_ctx); - } + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + if ((ret = init_input_stream(ist, error, sizeof(error))) < 0) goto dump_format; - } /* * initialize stream copy and subtitle/data streams. @@ -3532,26 +3519,26 @@ static int transcode_init(void) * to be configured with the correct audio frame size, which is only * known after the encoder is initialized. */ - for (i = 0; i < nb_output_streams; i++) { - if (!output_streams[i]->stream_copy && - (output_streams[i]->enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - output_streams[i]->enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO)) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->enc_ctx && + (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)) continue; - ret = init_output_stream_wrapper(output_streams[i], NULL, 0); + ret = init_output_stream_wrapper(ost, NULL, 0); if (ret < 0) goto dump_format; } /* discard unused programs */ - for (i = 0; i < nb_input_files; i++) { + for (int i = 0; i < nb_input_files; i++) { InputFile *ifile = input_files[i]; - for (j = 0; j < ifile->ctx->nb_programs; j++) { + for (int j = 0; j < ifile->ctx->nb_programs; j++) { AVProgram *p = ifile->ctx->programs[j]; int discard = AVDISCARD_ALL; - for (k = 0; k < p->nb_stream_indexes; k++) - if (!input_streams[ifile->ist_index + p->stream_index[k]]->discard) { + for (int k = 0; k < p->nb_stream_indexes; k++) + if (!ifile->streams[p->stream_index[k]]->discard) { discard = AVDISCARD_DEFAULT; break; } @@ -3559,23 +3546,11 @@ static int transcode_init(void) } } - /* write headers for files with no streams */ - for (i = 0; i < nb_output_files; i++) { - oc = output_files[i]->ctx; - if (output_files[i]->format->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) { - ret = of_check_init(output_files[i]); - if (ret < 0) - goto dump_format; - } - } - dump_format: /* dump the stream mapping */ av_log(NULL, AV_LOG_INFO, "Stream mapping:\n"); - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; - - for (j = 0; j < ist->nb_filters; j++) { + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { + for (int j = 0; j < ist->nb_filters; j++) { if (!filtergraph_is_simple(ist->filters[j]->graph)) { av_log(NULL, AV_LOG_INFO, " Stream #%d:%d (%s) -> %s", ist->file_index, ist->st->index, ist->dec ? ist->dec->name : "?", @@ -3587,9 +3562,7 @@ static int transcode_init(void) } } - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { if (ost->attachment_filename) { /* an attached file */ av_log(NULL, AV_LOG_INFO, " File %s -> Stream #%d:%d\n", @@ -3604,24 +3577,18 @@ static int transcode_init(void) av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index); av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)\n", ost->file_index, - ost->index, ost->enc ? ost->enc->name : "?"); + ost->index, ost->enc_ctx->codec->name); continue; } av_log(NULL, AV_LOG_INFO, " Stream #%d:%d -> #%d:%d", - input_streams[ost->source_index]->file_index, - input_streams[ost->source_index]->st->index, + ost->ist->file_index, + ost->ist->st->index, ost->file_index, ost->index); - if (ost->sync_ist != input_streams[ost->source_index]) - av_log(NULL, AV_LOG_INFO, " [sync #%d:%d]", - ost->sync_ist->file_index, - ost->sync_ist->st->index); - if (ost->stream_copy) - av_log(NULL, AV_LOG_INFO, " (copy)"); - else { - const AVCodec *in_codec = input_streams[ost->source_index]->dec; - const AVCodec *out_codec = ost->enc; + if (ost->enc_ctx) { + const AVCodec *in_codec = ost->ist->dec; + const AVCodec *out_codec = ost->enc_ctx->codec; const char *decoder_name = "?"; const char *in_codec_name = "?"; const char *encoder_name = "?"; @@ -3649,7 +3616,8 @@ static int transcode_init(void) av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))", in_codec_name, decoder_name, out_codec_name, encoder_name); - } + } else + av_log(NULL, AV_LOG_INFO, " (copy)"); av_log(NULL, AV_LOG_INFO, "\n"); } @@ -3666,22 +3634,9 @@ static int transcode_init(void) /* Return 1 if there remain streams where more output is wanted, 0 otherwise. */ static int need_output(void) { - int i; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - OutputFile *of = output_files[ost->file_index]; - AVFormatContext *os = output_files[ost->file_index]->ctx; - - if (ost->finished || - (os->pb && avio_tell(os->pb) >= of->limit_filesize)) - continue; - if (ost->frame_number >= ost->max_frames) { - int j; - for (j = 0; j < of->ctx->nb_streams; j++) - close_output_stream(output_streams[of->ost_index + j]); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->finished) continue; - } return 1; } @@ -3696,19 +3651,22 @@ static int need_output(void) */ static OutputStream *choose_output(void) { - int i; int64_t opts_min = INT64_MAX; OutputStream *ost_min = NULL; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - int64_t opts = ost->last_mux_dts == AV_NOPTS_VALUE ? INT64_MIN : - av_rescale_q(ost->last_mux_dts, ost->st->time_base, - AV_TIME_BASE_Q); - if (ost->last_mux_dts == AV_NOPTS_VALUE) - av_log(NULL, AV_LOG_DEBUG, - "cur_dts is invalid st:%d (%d) [init:%d i_done:%d finish:%d] (this is harmless if it occurs once at the start per stream)\n", - ost->st->index, ost->st->id, ost->initialized, ost->inputs_done, ost->finished); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + int64_t opts; + + if (ost->filter && ost->last_filter_pts != AV_NOPTS_VALUE) { + opts = ost->last_filter_pts; + } else { + opts = ost->last_mux_dts == AV_NOPTS_VALUE ? + INT64_MIN : ost->last_mux_dts; + if (ost->last_mux_dts == AV_NOPTS_VALUE) + av_log(ost, AV_LOG_DEBUG, + "cur_dts is invalid [init:%d i_done:%d finish:%d] (this is harmless if it occurs once at the start per stream)\n", + ost->initialized, ost->inputs_done, ost->finished); + } if (!ost->initialized && !ost->inputs_done) return ost->unavailable ? NULL : ost; @@ -3751,15 +3709,6 @@ static int check_keyboard_interaction(int64_t cur_time) if (key == '+') av_log_set_level(av_log_get_level()+10); if (key == '-') av_log_set_level(av_log_get_level()-10); if (key == 's') qp_hist ^= 1; - if (key == 'h'){ - if (do_hex_dump){ - do_hex_dump = do_pkt_dump = 0; - } else if(do_pkt_dump){ - do_hex_dump = 1; - } else - do_pkt_dump = 1; - av_log_set_level(AV_LOG_DEBUG); - } if (key == 'c' || key == 'C'){ char buf[4096], target[64], command[256], arg[256] = {0}; double time; @@ -3803,7 +3752,11 @@ static int check_keyboard_interaction(int64_t cur_time) if (key == 'd' || key == 'D'){ int debug=0; if(key == 'D') { - debug = input_streams[0]->dec_ctx->debug << 1; + InputStream *ist = ist_iter(NULL); + + if (ist) + debug = ist->dec_ctx->debug << 1; + if(!debug) debug = 1; while (debug & FF_DEBUG_DCT_COEFF) //unsupported, would just crash debug += debug; @@ -3821,12 +3774,11 @@ static int check_keyboard_interaction(int64_t cur_time) if (k <= 0 || sscanf(buf, "%d", &debug)!=1) av_log(NULL, AV_LOG_STDERR,"error parsing debug value\n"); } - for(i=0;idec_ctx->debug = debug; - } - for(i=0;ienc_ctx->debug = debug; + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + ist->dec_ctx->debug = debug; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->enc_ctx) + ost->enc_ctx->debug = debug; } if(debug) av_log_set_level(AV_LOG_DEBUG); av_log(NULL, AV_LOG_STDERR,"debug=%d\n", debug); @@ -3847,160 +3799,10 @@ static int check_keyboard_interaction(int64_t cur_time) return 0; } -#if HAVE_THREADS -static void *input_thread(void *arg) -{ - InputFile *f = arg; - AVPacket *pkt = f->pkt, *queue_pkt; - unsigned flags = f->non_blocking ? AV_THREAD_MESSAGE_NONBLOCK : 0; - int ret = 0; - - while (1) { - ret = av_read_frame(f->ctx, pkt); - - if (ret == AVERROR(EAGAIN)) { - av_usleep(10000); - continue; - } - if (ret < 0) { - av_thread_message_queue_set_err_recv(f->in_thread_queue, ret); - break; - } - queue_pkt = av_packet_alloc(); - if (!queue_pkt) { - av_packet_unref(pkt); - av_thread_message_queue_set_err_recv(f->in_thread_queue, AVERROR(ENOMEM)); - break; - } - av_packet_move_ref(queue_pkt, pkt); - ret = av_thread_message_queue_send(f->in_thread_queue, &queue_pkt, flags); - if (flags && ret == AVERROR(EAGAIN)) { - flags = 0; - ret = av_thread_message_queue_send(f->in_thread_queue, &queue_pkt, flags); - av_log(f->ctx, AV_LOG_WARNING, - "Thread message queue blocking; consider raising the " - "thread_queue_size option (current value: %d)\n", - f->thread_queue_size); - } - if (ret < 0) { - if (ret != AVERROR_EOF) - av_log(f->ctx, AV_LOG_ERROR, - "Unable to send packet to main thread: %s\n", - av_err2str(ret)); - av_packet_free(&queue_pkt); - av_thread_message_queue_set_err_recv(f->in_thread_queue, ret); - break; - } - } - - return NULL; -} - -static void free_input_thread(int i) -{ - InputFile *f = input_files[i]; - AVPacket *pkt; - - if (!f || !f->in_thread_queue) - return; - av_thread_message_queue_set_err_send(f->in_thread_queue, AVERROR_EOF); - while (av_thread_message_queue_recv(f->in_thread_queue, &pkt, 0) >= 0) - av_packet_free(&pkt); - - pthread_join(f->thread, NULL); - f->joined = 1; - av_thread_message_queue_free(&f->in_thread_queue); -} - -static void free_input_threads(void) -{ - int i; - - for (i = 0; i < nb_input_files; i++) - free_input_thread(i); -} - -static int init_input_thread(int i) -{ - int ret; - InputFile *f = input_files[i]; - - if (f->thread_queue_size < 0) - f->thread_queue_size = (nb_input_files > 1 ? 8 : 0); - if (!f->thread_queue_size) - return 0; - - if (f->ctx->pb ? !f->ctx->pb->seekable : - strcmp(f->ctx->iformat->name, "lavfi")) - f->non_blocking = 1; - ret = av_thread_message_queue_alloc(&f->in_thread_queue, - f->thread_queue_size, sizeof(f->pkt)); - if (ret < 0) - return ret; - - if ((ret = pthread_create(&f->thread, NULL, input_thread, f))) { - av_log(NULL, AV_LOG_ERROR, "pthread_create failed: %s. Try to increase `ulimit -v` or decrease `ulimit -s`.\n", strerror(ret)); - av_thread_message_queue_free(&f->in_thread_queue); - return AVERROR(ret); - } - - return 0; -} - -static int init_input_threads(void) -{ - int i, ret; - - for (i = 0; i < nb_input_files; i++) { - ret = init_input_thread(i); - if (ret < 0) - return ret; - } - return 0; -} - -static int get_input_packet_mt(InputFile *f, AVPacket **pkt) -{ - return av_thread_message_queue_recv(f->in_thread_queue, pkt, - f->non_blocking ? - AV_THREAD_MESSAGE_NONBLOCK : 0); -} -#endif - -static int get_input_packet(InputFile *f, AVPacket **pkt) -{ - if (f->readrate || f->rate_emu) { - int i; - int64_t file_start = copy_ts * ( - (f->ctx->start_time != AV_NOPTS_VALUE ? f->ctx->start_time * !start_at_zero : 0) + - (f->start_time != AV_NOPTS_VALUE ? f->start_time : 0) - ); - float scale = f->rate_emu ? 1.0 : f->readrate; - for (i = 0; i < f->nb_streams; i++) { - InputStream *ist = input_streams[f->ist_index + i]; - int64_t stream_ts_offset, pts, now; - if (!ist->nb_packets || (ist->decoding_needed && !ist->got_output)) continue; - stream_ts_offset = FFMAX(ist->first_dts != AV_NOPTS_VALUE ? ist->first_dts : 0, file_start); - pts = av_rescale(ist->dts, 1000000, AV_TIME_BASE); - now = (av_gettime_relative() - ist->start) * scale + stream_ts_offset; - if (pts > now) - return AVERROR(EAGAIN); - } - } - -#if HAVE_THREADS - if (f->thread_queue_size) - return get_input_packet_mt(f, pkt); -#endif - *pkt = f->pkt; - return av_read_frame(f->ctx, *pkt); -} - static int got_eagain(void) { - int i; - for (i = 0; i < nb_output_streams; i++) - if (output_streams[i]->unavailable) + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) + if (ost->unavailable) return 1; return 0; } @@ -4010,87 +3812,123 @@ static void reset_eagain(void) int i; for (i = 0; i < nb_input_files; i++) input_files[i]->eagain = 0; - for (i = 0; i < nb_output_streams; i++) - output_streams[i]->unavailable = 0; + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) + ost->unavailable = 0; } -// set duration to max(tmp, duration) in a proper time base and return duration's time_base -static AVRational duration_max(int64_t tmp, int64_t *duration, AVRational tmp_time_base, - AVRational time_base) +static void decode_flush(InputFile *ifile) { - int ret; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int ret; - if (!*duration) { - *duration = tmp; - return tmp_time_base; - } - - ret = av_compare_ts(*duration, time_base, tmp, tmp_time_base); - if (ret < 0) { - *duration = tmp; - return tmp_time_base; - } + if (!ist->processing_needed) + continue; - return time_base; -} + do { + ret = process_input_packet(ist, NULL, 1); + } while (ret > 0); -static int seek_to_start(InputFile *ifile, AVFormatContext *is) -{ - InputStream *ist; - AVCodecContext *avctx; - int i, ret, has_audio = 0; - int64_t duration = 0; + if (ist->decoding_needed) { + /* report last frame duration to the demuxer thread */ + if (ist->par->codec_type == AVMEDIA_TYPE_AUDIO) { + LastFrameDuration dur; - ret = avformat_seek_file(is, -1, INT64_MIN, is->start_time, is->start_time, 0); - if (ret < 0) - return ret; + dur.stream_idx = i; + dur.duration = av_rescale_q(ist->nb_samples, + (AVRational){ 1, ist->dec_ctx->sample_rate}, + ist->st->time_base); - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; + av_thread_message_queue_send(ifile->audio_duration_queue, &dur, 0); + } - /* duration is the length of the last frame in a stream - * when audio stream is present we don't care about - * last video frame length because it's not defined exactly */ - if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && ist->nb_samples) - has_audio = 1; + avcodec_flush_buffers(ist->dec_ctx); + } } +} - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; +static void ts_discontinuity_detect(InputFile *ifile, InputStream *ist, + AVPacket *pkt) +{ + const int fmt_is_discont = ifile->ctx->iformat->flags & AVFMT_TS_DISCONT; + int disable_discontinuity_correction = copy_ts; + int64_t pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, + AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); - if (has_audio) { - if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && ist->nb_samples) { - AVRational sample_rate = {1, avctx->sample_rate}; + if (copy_ts && ist->next_dts != AV_NOPTS_VALUE && + fmt_is_discont && ist->st->pts_wrap_bits < 60) { + int64_t wrap_dts = av_rescale_q_rnd(pkt->dts + (1LL<st->pts_wrap_bits), + ist->st->time_base, AV_TIME_BASE_Q, + AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + if (FFABS(wrap_dts - ist->next_dts) < FFABS(pkt_dts - ist->next_dts)/10) + disable_discontinuity_correction = 0; + } - duration = av_rescale_q(ist->nb_samples, sample_rate, ist->st->time_base); - } else { - continue; + if (ist->next_dts != AV_NOPTS_VALUE && !disable_discontinuity_correction) { + int64_t delta = pkt_dts - ist->next_dts; + if (fmt_is_discont) { + if (FFABS(delta) > 1LL * dts_delta_threshold * AV_TIME_BASE || + pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)) { + ifile->ts_offset_discont -= delta; + av_log(NULL, AV_LOG_DEBUG, + "timestamp discontinuity for stream #%d:%d " + "(id=%d, type=%s): %"PRId64", new offset= %"PRId64"\n", + ist->file_index, ist->st->index, ist->st->id, + av_get_media_type_string(ist->par->codec_type), + delta, ifile->ts_offset_discont); + pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); } } else { - if (ist->framerate.num) { - duration = av_rescale_q(1, av_inv_q(ist->framerate), ist->st->time_base); - } else if (ist->st->avg_frame_rate.num) { - duration = av_rescale_q(1, av_inv_q(ist->st->avg_frame_rate), ist->st->time_base); - } else { - duration = 1; + if (FFABS(delta) > 1LL * dts_error_threshold * AV_TIME_BASE) { + av_log(NULL, AV_LOG_WARNING, "DTS %"PRId64", next:%"PRId64" st:%d invalid dropping\n", pkt->dts, ist->next_dts, pkt->stream_index); + pkt->dts = AV_NOPTS_VALUE; } + if (pkt->pts != AV_NOPTS_VALUE){ + int64_t pkt_pts = av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); + delta = pkt_pts - ist->next_dts; + if (FFABS(delta) > 1LL * dts_error_threshold * AV_TIME_BASE) { + av_log(NULL, AV_LOG_WARNING, "PTS %"PRId64", next:%"PRId64" invalid dropping st:%d\n", pkt->pts, ist->next_dts, pkt->stream_index); + pkt->pts = AV_NOPTS_VALUE; + } + } + } + } else if (ist->next_dts == AV_NOPTS_VALUE && !copy_ts && + fmt_is_discont && ifile->last_ts != AV_NOPTS_VALUE) { + int64_t delta = pkt_dts - ifile->last_ts; + if (FFABS(delta) > 1LL * dts_delta_threshold * AV_TIME_BASE) { + ifile->ts_offset_discont -= delta; + av_log(NULL, AV_LOG_DEBUG, + "Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n", + delta, ifile->ts_offset_discont); + pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); } - if (!ifile->duration) - ifile->time_base = ist->st->time_base; - /* the total duration of the stream, max_pts - min_pts is - * the duration of the stream without the last frame */ - if (ist->max_pts > ist->min_pts && ist->max_pts - (uint64_t)ist->min_pts < INT64_MAX - duration) - duration += ist->max_pts - ist->min_pts; - ifile->time_base = duration_max(duration, &ifile->duration, ist->st->time_base, - ifile->time_base); } - if (ifile->loop > 0) - ifile->loop--; + ifile->last_ts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); +} + +static void ts_discontinuity_process(InputFile *ifile, InputStream *ist, + AVPacket *pkt) +{ + int64_t offset = av_rescale_q(ifile->ts_offset_discont, AV_TIME_BASE_Q, + ist->st->time_base); - return ret; + // apply previously-detected timestamp-discontinuity offset + // (to all streams, not just audio/video) + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += offset; + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += offset; + + // detect timestamp discontinuities for audio/video + if ((ist->par->codec_type == AVMEDIA_TYPE_VIDEO || + ist->par->codec_type == AVMEDIA_TYPE_AUDIO) && + pkt->dts != AV_NOPTS_VALUE) + ts_discontinuity_detect(ifile, ist, pkt); } /* @@ -4106,48 +3944,19 @@ static int process_input(int file_index) AVFormatContext *is; InputStream *ist; AVPacket *pkt; - int ret, thread_ret, i, j; - int64_t duration; - int64_t pkt_dts; - int disable_discontinuity_correction = copy_ts; + int ret, i; is = ifile->ctx; - ret = get_input_packet(ifile, &pkt); + ret = ifile_get_packet(ifile, &pkt); if (ret == AVERROR(EAGAIN)) { ifile->eagain = 1; return ret; } - if (ret < 0 && ifile->loop) { - AVCodecContext *avctx; - for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; - avctx = ist->dec_ctx; - if (ist->processing_needed) { - ret = process_input_packet(ist, NULL, 1); - if (ret>0) - return 0; - if (ist->decoding_needed) - avcodec_flush_buffers(avctx); - } - } -#if HAVE_THREADS - free_input_thread(file_index); -#endif - ret = seek_to_start(ifile, is); -#if HAVE_THREADS - thread_ret = init_input_thread(file_index); - if (thread_ret < 0) - return thread_ret; -#endif - if (ret < 0) - av_log(NULL, AV_LOG_WARNING, "Seek to start failed.\n"); - else - ret = get_input_packet(ifile, &pkt); - if (ret == AVERROR(EAGAIN)) { - ifile->eagain = 1; - return ret; - } + if (ret == 1) { + /* the input file is looped: flush the decoders */ + decode_flush(ifile); + return AVERROR(EAGAIN); } if (ret < 0) { if (ret != AVERROR_EOF) { @@ -4157,7 +3966,7 @@ static int process_input(int file_index) } for (i = 0; i < ifile->nb_streams; i++) { - ist = input_streams[ifile->ist_index + i]; + ist = ifile->streams[i]; if (ist->processing_needed) { ret = process_input_packet(ist, NULL, 0); if (ret>0) @@ -4165,12 +3974,12 @@ static int process_input(int file_index) } /* mark all outputs that don't go through lavfi as finished */ - for (j = 0; j < nb_output_streams; j++) { - OutputStream *ost = output_streams[j]; - - if (ost->source_index == ifile->ist_index + i && - (ost->stream_copy || ost->enc->type == AVMEDIA_TYPE_SUBTITLE)) - finish_output_stream(ost); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + if (ost->ist == ist && + (!ost->enc_ctx || ost->enc_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE)) { + OutputFile *of = output_files[ost->file_index]; + of_output_packet(of, ost->pkt, ost, 1); + } } } @@ -4180,18 +3989,7 @@ static int process_input(int file_index) reset_eagain(); - if (do_pkt_dump) { - av_pkt_dump_log2(NULL, AV_LOG_INFO, pkt, do_hex_dump, - is->streams[pkt->stream_index]); - } - /* the following test is needed in case new streams appear - dynamically in stream : we ignore them */ - if (pkt->stream_index >= ifile->nb_streams) { - report_new_stream(file_index, pkt); - goto discard_packet; - } - - ist = input_streams[ifile->ist_index + pkt->stream_index]; + ist = ifile->streams[pkt->stream_index]; ist->data_size += pkt->size; ist->nb_packets++; @@ -4199,61 +3997,6 @@ static int process_input(int file_index) if (ist->discard) goto discard_packet; - if (pkt->flags & AV_PKT_FLAG_CORRUPT) { - av_log(NULL, exit_on_error ? AV_LOG_FATAL : AV_LOG_WARNING, - "%s: corrupt input packet in stream %d\n", is->url, pkt->stream_index); - if (exit_on_error) - exit_program(1); - } - - if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "demuxer -> ist_index:%d type:%s " - "next_dts:%s next_dts_time:%s next_pts:%s next_pts_time:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", - ifile->ist_index + pkt->stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), - av_ts2str(ist->next_dts), av_ts2timestr(ist->next_dts, &AV_TIME_BASE_Q), - av_ts2str(ist->next_pts), av_ts2timestr(ist->next_pts, &AV_TIME_BASE_Q), - av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), - av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), - av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base), - av_ts2str(input_files[ist->file_index]->ts_offset), - av_ts2timestr(input_files[ist->file_index]->ts_offset, &AV_TIME_BASE_Q)); - } - - if(!ist->wrap_correction_done && is->start_time != AV_NOPTS_VALUE && ist->st->pts_wrap_bits < 64){ - int64_t stime, stime2; - // Correcting starttime based on the enabled streams - // FIXME this ideally should be done before the first use of starttime but we do not know which are the enabled streams at that point. - // so we instead do it here as part of discontinuity handling - if ( ist->next_dts == AV_NOPTS_VALUE - && ifile->ts_offset == -is->start_time - && (is->iformat->flags & AVFMT_TS_DISCONT)) { - int64_t new_start_time = INT64_MAX; - for (i=0; inb_streams; i++) { - AVStream *st = is->streams[i]; - if(st->discard == AVDISCARD_ALL || st->start_time == AV_NOPTS_VALUE) - continue; - new_start_time = FFMIN(new_start_time, av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q)); - } - if (new_start_time > is->start_time) { - av_log(is, AV_LOG_VERBOSE, "Correcting start time by %"PRId64"\n", new_start_time - is->start_time); - ifile->ts_offset = -new_start_time; - } - } - - stime = av_rescale_q(is->start_time, AV_TIME_BASE_Q, ist->st->time_base); - stime2= stime + (1ULL<st->pts_wrap_bits); - ist->wrap_correction_done = 1; - - if(stime2 > stime && pkt->dts != AV_NOPTS_VALUE && pkt->dts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { - pkt->dts -= 1ULL<st->pts_wrap_bits; - ist->wrap_correction_done = 0; - } - if(stime2 > stime && pkt->pts != AV_NOPTS_VALUE && pkt->pts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { - pkt->pts -= 1ULL<st->pts_wrap_bits; - ist->wrap_correction_done = 0; - } - } - /* add the stream-global side data to the first packet */ if (ist->nb_packets == 1) { for (i = 0; i < ist->st->nb_side_data; i++) { @@ -4268,105 +4011,19 @@ static int process_input(int file_index) dst_data = av_packet_new_side_data(pkt, src_sd->type, src_sd->size); if (!dst_data) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); memcpy(dst_data, src_sd->data, src_sd->size); } } - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); - - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts *= ist->ts_scale; - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts *= ist->ts_scale; - - pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && - pkt_dts != AV_NOPTS_VALUE && ist->next_dts == AV_NOPTS_VALUE && !copy_ts - && (is->iformat->flags & AVFMT_TS_DISCONT) && ifile->last_ts != AV_NOPTS_VALUE) { - int64_t delta = pkt_dts - ifile->last_ts; - if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || - delta > 1LL*dts_delta_threshold*AV_TIME_BASE){ - ifile->ts_offset -= delta; - av_log(NULL, AV_LOG_DEBUG, - "Inter stream timestamp discontinuity %"PRId64", new offset= %"PRId64"\n", - delta, ifile->ts_offset); - pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - } - } - - duration = av_rescale_q(ifile->duration, ifile->time_base, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) { - pkt->pts += duration; - ist->max_pts = FFMAX(pkt->pts, ist->max_pts); - ist->min_pts = FFMIN(pkt->pts, ist->min_pts); - } - - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts += duration; - - pkt_dts = av_rescale_q_rnd(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - - if (copy_ts && pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && - (is->iformat->flags & AVFMT_TS_DISCONT) && ist->st->pts_wrap_bits < 60) { - int64_t wrap_dts = av_rescale_q_rnd(pkt->dts + (1LL<st->pts_wrap_bits), - ist->st->time_base, AV_TIME_BASE_Q, - AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); - if (FFABS(wrap_dts - ist->next_dts) < FFABS(pkt_dts - ist->next_dts)/10) - disable_discontinuity_correction = 0; - } - - if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || - ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && - pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && - !disable_discontinuity_correction) { - int64_t delta = pkt_dts - ist->next_dts; - if (is->iformat->flags & AVFMT_TS_DISCONT) { - if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE || - delta > 1LL*dts_delta_threshold*AV_TIME_BASE || - pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)) { - ifile->ts_offset -= delta; - av_log(NULL, AV_LOG_DEBUG, - "timestamp discontinuity for stream #%d:%d " - "(id=%d, type=%s): %"PRId64", new offset= %"PRId64"\n", - ist->file_index, ist->st->index, ist->st->id, - av_get_media_type_string(ist->dec_ctx->codec_type), - delta, ifile->ts_offset); - pkt->dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); - } - } else { - if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || - delta > 1LL*dts_error_threshold*AV_TIME_BASE) { - av_log(NULL, AV_LOG_WARNING, "DTS %"PRId64", next:%"PRId64" st:%d invalid dropping\n", pkt->dts, ist->next_dts, pkt->stream_index); - pkt->dts = AV_NOPTS_VALUE; - } - if (pkt->pts != AV_NOPTS_VALUE){ - int64_t pkt_pts = av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); - delta = pkt_pts - ist->next_dts; - if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || - delta > 1LL*dts_error_threshold*AV_TIME_BASE) { - av_log(NULL, AV_LOG_WARNING, "PTS %"PRId64", next:%"PRId64" invalid dropping st:%d\n", pkt->pts, ist->next_dts, pkt->stream_index); - pkt->pts = AV_NOPTS_VALUE; - } - } - } - } - - if (pkt->dts != AV_NOPTS_VALUE) - ifile->last_ts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); + // detect and try to correct for timestamp discontinuities + ts_discontinuity_process(ifile, ist, pkt); if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", - ifile->ist_index + pkt->stream_index, av_get_media_type_string(ist->dec_ctx->codec_type), + av_log(NULL, AV_LOG_INFO, "demuxer+ffmpeg -> ist_index:%d:%d type:%s pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s off:%s off_time:%s\n", + ifile->index, pkt->stream_index, + av_get_media_type_string(ist->par->codec_type), av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base), @@ -4379,12 +4036,7 @@ static int process_input(int file_index) process_input_packet(ist, pkt, 0); discard_packet: -#if HAVE_THREADS - if (ifile->thread_queue_size) - av_packet_free(&pkt); - else -#endif - av_packet_unref(pkt); + av_packet_free(&pkt); return 0; } @@ -4512,8 +4164,8 @@ static int transcode_step(void) return 0; } } else { - av_assert0(ost->source_index >= 0); - ist = input_streams[ost->source_index]; + ist = ost->ist; + av_assert0(ist); } ret = process_input(ist->file_index); @@ -4535,8 +4187,6 @@ static int transcode_step(void) static int transcode(void) { int ret, i; - AVFormatContext *os; - OutputStream *ost; InputStream *ist; int64_t timer_start; int64_t total_packets_written = 0; @@ -4551,11 +4201,6 @@ static int transcode(void) timer_start = av_gettime_relative(); -#if HAVE_THREADS - if ((ret = init_input_threads()) < 0) - goto fail; -#endif - while (!received_sigterm && !cancelRequested(globalSessionId)) { int64_t cur_time= av_gettime_relative(); @@ -4579,13 +4224,9 @@ static int transcode(void) /* dump report by using the output first video and audio streams */ print_report(0, timer_start, cur_time); } -#if HAVE_THREADS - free_input_threads(); -#endif /* at the end of stream, we must flush the decoder buffers */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; + for (ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { if (!input_files[ist->file_index]->eof_reached) { process_input_packet(ist, NULL, 0); } @@ -4604,30 +4245,13 @@ static int transcode(void) /* dump report by using the first video and audio streams */ print_report(1, timer_start, av_gettime_relative()); - /* close the output files */ - for (i = 0; i < nb_output_files; i++) { - os = output_files[i]->ctx; - if (os && os->oformat && !(os->oformat->flags & AVFMT_NOFILE)) { - if ((ret = avio_closep(&os->pb)) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error closing file %s: %s\n", os->url, av_err2str(ret)); - if (exit_on_error) - exit_program(1); - } - } - } - - /* dump report by using the first video and audio streams */ - print_report(1, timer_start, av_gettime_relative()); - /* close each encoder */ - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - if (ost->encoding_needed) { - av_freep(&ost->enc_ctx->stats_in); - } - total_packets_written += ost->packets_written; - if (!ost->packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM)) { - av_log(NULL, AV_LOG_FATAL, "Empty output on stream %d.\n", i); + for (OutputStream *ost = ost_iter(NULL); ost; ost = ost_iter(ost)) { + uint64_t packets_written; + packets_written = atomic_load(&ost->packets_written); + total_packets_written += packets_written; + if (!packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM)) { + av_log(ost, AV_LOG_FATAL, "Empty output\n"); exit_program(1); } } @@ -4637,46 +4261,12 @@ static int transcode(void) exit_program(1); } - /* close each decoder */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; - if (ist->decoding_needed) { - avcodec_close(ist->dec_ctx); - if (ist->hwaccel_uninit) - ist->hwaccel_uninit(ist->dec_ctx); - } - } - hw_device_free_all(); /* finished ! */ ret = 0; fail: -#if HAVE_THREADS - free_input_threads(); -#endif - - if (output_streams) { - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - if (ost) { - if (ost->logfile) { - if (fclose(ost->logfile)) - av_log(NULL, AV_LOG_ERROR, - "Error closing logfile, loss of information possible: %s\n", - av_err2str(AVERROR(errno))); - ost->logfile = NULL; - } - av_freep(&ost->forced_kf_pts); - av_freep(&ost->apad); - av_freep(&ost->disposition); - av_dict_free(&ost->encoder_opts); - av_dict_free(&ost->sws_dict); - av_dict_free(&ost->swr_opts); - } - } - } return ret; } @@ -4725,29 +4315,31 @@ static int64_t getmaxrss(void) } void ffmpeg_var_cleanup() { - main_ffmpeg_return_code = 0; - longjmp_value = 0; received_sigterm = 0; received_nb_signals = 0; + transcode_init_done = ATOMIC_VAR_INIT(0); ffmpeg_exited = 0; + main_ffmpeg_return_code = 0; copy_ts_first_pts = AV_NOPTS_VALUE; + longjmp_value = 0; + want_sdp = 1; + enc_stats_files = NULL; + nb_enc_stats_files = 0; + + vstats_file = NULL; nb_frames_dup = 0; dup_warning = 1000; nb_frames_drop = 0; + decode_error_stat[0] = 0; + decode_error_stat[1] = 0; nb_output_dumped = 0; - want_sdp = 1; - progress_avio = NULL; - input_streams = NULL; - nb_input_streams = 0; input_files = NULL; nb_input_files = 0; - output_streams = NULL; - nb_output_streams = 0; output_files = NULL; nb_output_files = 0; @@ -4757,9 +4349,12 @@ void ffmpeg_var_cleanup() { last_time = -1; keyboard_last_time = 0; first_report = 1; + for(int i = 0; i < FF_ARRAY_ELEMS(qp_histogram); i++) { + qp_histogram[i] = 0; + } } -void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double)) +void set_report_callback(void (*callback)(int, float, float, int64_t, double, double, double)) { report_callback = callback; } @@ -4848,8 +4443,10 @@ int ffmpeg_execute(int argc, char **argv) OPT_OUTPUT, { .func_arg = opt_map }, "set input stream mapping", "[-]input_file_id[:stream_specifier][,sync_file_id[:stream_specifier]]" }, + #if FFMPEG_OPT_MAP_CHANNEL { "map_channel", HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_map_channel }, - "map an audio channel from one stream to another", "file.stream.channel[:syncfile.syncstream]" }, + "map an audio channel from one stream to another (deprecated)", "file.stream.channel[:syncfile.syncstream]" }, + #endif { "map_metadata", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(metadata_map) }, "set metadata information of outfile from infile", @@ -4922,8 +4519,6 @@ int ffmpeg_execute(int argc, char **argv) "set video sync method globally; deprecated, use -fps_mode", "" }, { "frame_drop_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &frame_drop_threshold }, "frame drop threshold", "" }, - { "async", HAS_ARG | OPT_INT | OPT_EXPERT, { &audio_sync_method }, - "audio sync method", "" }, { "adrift_threshold", HAS_ARG | OPT_FLOAT | OPT_EXPERT, { &audio_drift_threshold }, "audio drift threshold", "threshold" }, { "copyts", OPT_BOOL | OPT_EXPERT, { ©_ts }, @@ -4935,6 +4530,8 @@ int ffmpeg_execute(int argc, char **argv) { "shortest", OPT_BOOL | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(shortest) }, "finish encoding within shortest input" }, + { "shortest_buf_duration", HAS_ARG | OPT_FLOAT | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT, { .off = OFFSET(shortest_buf_duration) }, + "maximum buffering duration (in seconds) for the -shortest option" }, { "bitexact", OPT_BOOL | OPT_EXPERT | OPT_OFFSET | OPT_OUTPUT | OPT_INPUT, { .off = OFFSET(bitexact) }, "bitexact mode" }, @@ -5007,15 +4604,28 @@ int ffmpeg_execute(int argc, char **argv) { "disposition", OPT_STRING | HAS_ARG | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(disposition) }, "disposition", "" }, - { "thread_queue_size", HAS_ARG | OPT_INT | OPT_OFFSET | OPT_EXPERT | OPT_INPUT, + { "thread_queue_size", HAS_ARG | OPT_INT | OPT_OFFSET | OPT_EXPERT | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(thread_queue_size) }, "set the maximum number of queued packets from the demuxer" }, - { "find_stream_info", OPT_BOOL | OPT_PERFILE | OPT_INPUT | OPT_EXPERT, { &find_stream_info }, + { "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT | OPT_OFFSET, { .off = OFFSET(find_stream_info) }, "read and decode the streams to fill missing information with heuristics" }, { "bits_per_raw_sample", OPT_INT | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(bits_per_raw_sample) }, "set the number of bits per raw sample", "number" }, + { "stats_enc_pre", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_pre) }, + "write encoding stats before encoding" }, + { "stats_enc_post", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_post) }, + "write encoding stats after encoding" }, + { "stats_mux_pre", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(mux_stats) }, + "write packets stats before muxing" }, + { "stats_enc_pre_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_pre_fmt) }, + "format of the stats written with -stats_enc_pre" }, + { "stats_enc_post_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(enc_stats_post_fmt) }, + "format of the stats written with -stats_enc_post" }, + { "stats_mux_pre_fmt", HAS_ARG | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT | OPT_STRING, { .off = OFFSET(mux_stats_fmt) }, + "format of the stats written with -stats_mux_pre" }, + /* video options */ { "vframes", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_video_frames }, "set the number of video frames to output", "number" }, @@ -5034,6 +4644,16 @@ int ffmpeg_execute(int argc, char **argv) { "pix_fmt", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(frame_pix_fmts) }, "set pixel format", "format" }, + { "display_rotation", OPT_VIDEO | HAS_ARG | OPT_DOUBLE | OPT_SPEC | + OPT_INPUT, { .off = OFFSET(display_rotations) }, + "set pure counter-clockwise rotation in degrees for stream(s)", + "angle" }, + { "display_hflip", OPT_VIDEO | OPT_BOOL | OPT_SPEC | OPT_INPUT, { .off = OFFSET(display_hflips) }, + "set display horizontal flip for stream(s) " + "(overrides any display rotation if it is not set)"}, + { "display_vflip", OPT_VIDEO | OPT_BOOL | OPT_SPEC | OPT_INPUT, { .off = OFFSET(display_vflips) }, + "set display vertical flip for stream(s) " + "(overrides any display rotation if it is not set)"}, { "vn", OPT_VIDEO | OPT_BOOL | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT,{ .off = OFFSET(video_disable) }, "disable video" }, { "rc_override", OPT_VIDEO | HAS_ARG | OPT_EXPERT | OPT_STRING | OPT_SPEC | @@ -5049,8 +4669,10 @@ int ffmpeg_execute(int argc, char **argv) { "passlogfile", OPT_VIDEO | HAS_ARG | OPT_STRING | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(passlogfiles) }, "select two pass log file name prefix", "prefix" }, + #if FFMPEG_OPT_PSNR { "psnr", OPT_VIDEO | OPT_BOOL | OPT_EXPERT, { &do_psnr }, - "calculate PSNR of compressed frames" }, + "calculate PSNR of compressed frames (deprecated, use -flags +psnr)" }, + #endif { "vstats", OPT_VIDEO | OPT_EXPERT , { .func_arg = opt_vstats }, "dump video coding statistics to file" }, { "vstats_file", OPT_VIDEO | HAS_ARG | OPT_EXPERT , { .func_arg = opt_vstats_file }, @@ -5088,8 +4710,6 @@ int ffmpeg_execute(int argc, char **argv) { "force_key_frames", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(forced_key_frames) }, "force key frames at specified timestamps", "timestamps" }, - { "ab", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, - "audio bitrate (please use -b:a)", "bitrate" }, { "b", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, "video bitrate (please use -b:v)", "bitrate" }, { "hwaccel", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT | @@ -5109,6 +4729,11 @@ int ffmpeg_execute(int argc, char **argv) { "autoscale", HAS_ARG | OPT_BOOL | OPT_SPEC | OPT_EXPERT | OPT_OUTPUT, { .off = OFFSET(autoscale) }, "automatically insert a scale filter at the end of the filter graph" }, + { "fix_sub_duration_heartbeat", OPT_VIDEO | OPT_BOOL | OPT_EXPERT | + OPT_SPEC | OPT_OUTPUT, { .off = OFFSET(fix_sub_duration_heartbeat) }, + "set this video output stream to be a heartbeat stream for " + "fix_sub_duration, according to which subtitles should be split at " + "random access points" }, /* audio options */ { "aframes", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_audio_frames }, @@ -5126,11 +4751,11 @@ int ffmpeg_execute(int argc, char **argv) { "acodec", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_INPUT | OPT_OUTPUT, { .func_arg = opt_audio_codec }, "force audio codec ('copy' to copy stream)", "codec" }, + { "ab", OPT_AUDIO | HAS_ARG | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_bitrate }, + "audio bitrate (please use -b:a)", "bitrate" }, { "atag", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_old2new }, "force audio tag/fourcc", "fourcc/tag" }, - { "vol", OPT_AUDIO | HAS_ARG | OPT_INT, { &audio_volume }, - "change audio volume (256=normal)" , "volume" }, { "sample_fmt", OPT_AUDIO | HAS_ARG | OPT_EXPERT | OPT_SPEC | OPT_STRING | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(sample_fmts) }, "set sample format", "format" }, @@ -5220,7 +4845,7 @@ int ffmpeg_execute(int argc, char **argv) ffmpeg_options = options; - int i, ret; + int ret; BenchmarkTimeStamps ti; int savedCode = setjmp(ex_buf__); @@ -5259,11 +4884,6 @@ int ffmpeg_execute(int argc, char **argv) exit_program(1); } - for (i = 0; i < nb_output_files; i++) { - if (strcmp(output_files[i]->format->name, "rtp")) - want_sdp = 0; - } - current_time = ti = get_benchmark_time_stamps(); if (transcode() < 0) exit_program(1); diff --git a/linux/src/fftools_ffmpeg.h b/linux/src/fftools_ffmpeg.h index 653a9d9..66b254f 100644 --- a/linux/src/fftools_ffmpeg.h +++ b/linux/src/fftools_ffmpeg.h @@ -1,6 +1,7 @@ /* * This file is part of FFmpeg. - * Copyright (c) 2018 Taner Sener + * Copyright (c) 2018-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,6 +23,16 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - WARN_MULTIPLE_OPT_USAGE, MATCH_PER_STREAM_OPT, MATCH_PER_TYPE_OPT, SPECIFIER_OPT_FMT declarations migrated to + * ffmpeg_mux.h + * - "class" member field renamed as clazz + * - time field in set_report_callback updated as double + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -59,11 +70,13 @@ #include "config.h" +#include #include #include #include #include "fftools_cmdutils.h" +#include "fftools_sync_queue.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" @@ -85,6 +98,12 @@ #include "libswresample/swresample.h" +// deprecated features +#define FFMPEG_OPT_PSNR 1 +#define FFMPEG_OPT_MAP_CHANNEL 1 +#define FFMPEG_OPT_MAP_SYNC 1 +#define FFMPEG_ROTATION_METADATA 1 + enum VideoSyncMethod { VSYNC_AUTO = -1, VSYNC_PASSTHROUGH, @@ -113,15 +132,15 @@ typedef struct StreamMap { int disabled; /* 1 is this mapping is disabled by a negative map */ int file_index; int stream_index; - int sync_file_index; - int sync_stream_index; char *linklabel; /* name of an output link, for mapping lavfi outputs */ } StreamMap; +#if FFMPEG_OPT_MAP_CHANNEL typedef struct { int file_idx, stream_idx, channel_idx; // input int ofile_idx, ostream_idx; // output } AudioChannelMap; +#endif typedef struct OptionsContext { OptionGroup *g; @@ -157,6 +176,7 @@ typedef struct OptionsContext { int accurate_seek; int thread_queue_size; int input_sync_ref; + int find_stream_info; SpecifierOpt *ts_scale; int nb_ts_scale; @@ -174,11 +194,10 @@ typedef struct OptionsContext { /* output options */ StreamMap *stream_maps; int nb_stream_maps; +#if FFMPEG_OPT_MAP_CHANNEL AudioChannelMap *audio_channel_maps; /* one info entry per -map_channel */ int nb_audio_channel_maps; /* number of (valid) -map_channel settings */ - int metadata_global_manual; - int metadata_streams_manual; - int metadata_chapters_manual; +#endif const char **attachments; int nb_attachments; @@ -186,9 +205,10 @@ typedef struct OptionsContext { int64_t recording_time; int64_t stop_time; - uint64_t limit_filesize; + int64_t limit_filesize; float mux_preload; float mux_max_delay; + float shortest_buf_duration; int shortest; int bitexact; @@ -221,6 +241,12 @@ typedef struct OptionsContext { int nb_force_fps; SpecifierOpt *frame_aspect_ratios; int nb_frame_aspect_ratios; + SpecifierOpt *display_rotations; + int nb_display_rotations; + SpecifierOpt *display_hflips; + int nb_display_hflips; + SpecifierOpt *display_vflips; + int nb_display_vflips; SpecifierOpt *rc_overrides; int nb_rc_overrides; SpecifierOpt *intra_matrices; @@ -247,6 +273,8 @@ typedef struct OptionsContext { int nb_reinit_filters; SpecifierOpt *fix_sub_duration; int nb_fix_sub_duration; + SpecifierOpt *fix_sub_duration_heartbeat; + int nb_fix_sub_duration_heartbeat; SpecifierOpt *canvas_sizes; int nb_canvas_sizes; SpecifierOpt *pass; @@ -275,6 +303,18 @@ typedef struct OptionsContext { int nb_autoscale; SpecifierOpt *bits_per_raw_sample; int nb_bits_per_raw_sample; + SpecifierOpt *enc_stats_pre; + int nb_enc_stats_pre; + SpecifierOpt *enc_stats_post; + int nb_enc_stats_post; + SpecifierOpt *mux_stats; + int nb_mux_stats; + SpecifierOpt *enc_stats_pre_fmt; + int nb_enc_stats_pre_fmt; + SpecifierOpt *enc_stats_post_fmt; + int nb_enc_stats_post_fmt; + SpecifierOpt *mux_stats_fmt; + int nb_mux_stats_fmt; } OptionsContext; typedef struct InputFilter { @@ -350,12 +390,22 @@ typedef struct InputStream { #define DECODING_FOR_OST 1 #define DECODING_FOR_FILTER 2 int processing_needed; /* non zero if the packets must be processed */ + // should attach FrameData as opaque_ref after decoding + int want_frame_data; + /** + * Codec parameters - to be used by the decoding/streamcopy code. + * st->codecpar should not be accessed, because it may be modified + * concurrently by the demuxing thread. + */ + AVCodecParameters *par; AVCodecContext *dec_ctx; const AVCodec *dec; AVFrame *decoded_frame; AVPacket *pkt; + AVRational framerate_guessed; + int64_t prev_pkt_pts; int64_t start; /* time when read started */ /* predicted dts of the next packet read for this stream or (when there are @@ -368,6 +418,12 @@ typedef struct InputStream { int64_t pts; ///< current pts of the decoded frame (in AV_TIME_BASE units) int wrap_correction_done; + // the value of AVCodecParserContext.repeat_pict from the AVStream parser + // for the last packet returned from ifile_get_packet() + // -1 if unknown + // FIXME: this is a hack, the avstream parser should not be used + int last_pkt_repeat_pict; + int64_t filter_in_rescale_delta_last; int64_t min_pts; /* pts with the smallest value in a current stream */ @@ -417,12 +473,8 @@ typedef struct InputStream { char *hwaccel_device; enum AVPixelFormat hwaccel_output_format; - /* hwaccel context */ - void *hwaccel_ctx; - void (*hwaccel_uninit)(AVCodecContext *s); int (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame); enum AVPixelFormat hwaccel_pix_fmt; - enum AVPixelFormat hwaccel_retrieved_pix_fmt; /* stats */ // combined size of all the packets read @@ -439,38 +491,46 @@ typedef struct InputStream { int got_output; } InputStream; +typedef struct LastFrameDuration { + int stream_idx; + int64_t duration; +} LastFrameDuration; + typedef struct InputFile { + int index; + AVFormatContext *ctx; int eof_reached; /* true if eof reached */ int eagain; /* true if last read attempt returned EAGAIN */ - int ist_index; /* index of first stream in input_streams */ - int loop; /* set number of times input stream should be looped */ - int64_t duration; /* actual duration of the longest stream in a file - at the moment when looping happens */ - AVRational time_base; /* time base of the duration */ int64_t input_ts_offset; int input_sync_ref; - + /** + * Effective format start time based on enabled streams. + */ + int64_t start_time_effective; int64_t ts_offset; + /** + * Extra timestamp offset added by discontinuity handling. + */ + int64_t ts_offset_discont; int64_t last_ts; int64_t start_time; /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */ int64_t recording_time; - int nb_streams; /* number of stream that ffmpeg is aware of; may be different - from ctx.nb_streams if new streams appear during av_read_frame() */ - int nb_streams_warn; /* number of streams that the user was warned of */ + + /* streams that ffmpeg is aware of; + * there may be extra streams in ctx that are not mapped to an InputStream + * if new streams appear dynamically during demuxing */ + InputStream **streams; + int nb_streams; + int rate_emu; float readrate; int accurate_seek; - AVPacket *pkt; - -#if HAVE_THREADS - AVThreadMessageQueue *in_thread_queue; - pthread_t thread; /* thread reading from this file */ - int non_blocking; /* reading packets from the thread should not block */ - int joined; /* the thread has been joined */ - int thread_queue_size; /* maximum number of queued packets */ -#endif + /* when looping the input file, this queue is used by decoders to report + * the last frame duration back to the demuxer thread */ + AVThreadMessageQueue *audio_duration_queue; + int audio_duration_queue_size; } InputFile; enum forced_keyframes_const { @@ -485,6 +545,41 @@ enum forced_keyframes_const { #define ABORT_ON_FLAG_EMPTY_OUTPUT (1 << 0) #define ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM (1 << 1) +enum EncStatsType { + ENC_STATS_LITERAL = 0, + ENC_STATS_FILE_IDX, + ENC_STATS_STREAM_IDX, + ENC_STATS_FRAME_NUM, + ENC_STATS_FRAME_NUM_IN, + ENC_STATS_TIMEBASE, + ENC_STATS_TIMEBASE_IN, + ENC_STATS_PTS, + ENC_STATS_PTS_TIME, + ENC_STATS_PTS_IN, + ENC_STATS_PTS_TIME_IN, + ENC_STATS_DTS, + ENC_STATS_DTS_TIME, + ENC_STATS_SAMPLE_NUM, + ENC_STATS_NB_SAMPLES, + ENC_STATS_PKT_SIZE, + ENC_STATS_BITRATE, + ENC_STATS_AVG_BITRATE, +}; + +typedef struct EncStatsComponent { + enum EncStatsType type; + + uint8_t *str; + size_t str_len; +} EncStatsComponent; + +typedef struct EncStats { + EncStatsComponent *components; + int nb_components; + + AVIOContext *io; +} EncStats; + extern const char *const forced_keyframes_const_names[]; typedef enum { @@ -492,68 +587,92 @@ typedef enum { MUXER_FINISHED = 2, } OSTFinished ; +enum { + KF_FORCE_SOURCE = 1, + KF_FORCE_SOURCE_NO_DROP = 2, +}; + +typedef struct KeyframeForceCtx { + int type; + + int64_t ref_pts; + + // timestamps of the forced keyframes, in AV_TIME_BASE_Q + int64_t *pts; + int nb_pts; + int index; + + AVExpr *pexpr; + double expr_const_values[FKF_NB]; + + int dropped_keyframe; +} KeyframeForceCtx; + typedef struct OutputStream { + const AVClass *clazz; + int file_index; /* file index */ int index; /* stream index in the output file */ - int source_index; /* InputStream index */ + + /* input stream that is the source for this output stream; + * may be NULL for streams with no well-defined source, e.g. + * attachments or outputs from complex filtergraphs */ + InputStream *ist; + AVStream *st; /* stream in the output file */ - int encoding_needed; /* true if encoding needed for this stream */ - int64_t frame_number; - /* input pts and corresponding output pts - for A/V sync */ - struct InputStream *sync_ist; /* input stream to sync against */ - int64_t sync_opts; /* output frame counter, could be changed to some true timestamp */ // FIXME look at frame_number - /* pts of the first frame encoded for this stream, used for limiting - * recording time */ - int64_t first_pts; - /* dts of the last packet sent to the muxer */ + /* number of frames emitted by the video-encoding sync code */ + int64_t vsync_frame_number; + /* predicted pts of the next frame to be encoded + * audio/video encoding only */ + int64_t next_pts; + /* dts of the last packet sent to the muxing queue, in AV_TIME_BASE_Q */ int64_t last_mux_dts; + /* pts of the last frame received from the filters, in AV_TIME_BASE_Q */ + int64_t last_filter_pts; + + // timestamp from which the streamcopied streams should start, + // in AV_TIME_BASE_Q; + // everything before it should be discarded + int64_t ts_copy_start; + // the timebase of the packets sent to the muxer AVRational mux_timebase; AVRational enc_timebase; - AVBSFContext *bsf_ctx; - AVCodecContext *enc_ctx; - AVCodecParameters *ref_par; /* associated input codec parameters with encoders options applied */ - const AVCodec *enc; - int64_t max_frames; AVFrame *filtered_frame; AVFrame *last_frame; + AVFrame *sq_frame; AVPacket *pkt; int64_t last_dropped; int64_t last_nb0_frames[3]; - void *hwaccel_ctx; - /* video only */ AVRational frame_rate; AVRational max_frame_rate; enum VideoSyncMethod vsync_method; int is_cfr; - const char *fps_mode; int force_fps; int top_field_first; +#if FFMPEG_ROTATION_METADATA int rotate_overridden; +#endif int autoscale; + int bitexact; int bits_per_raw_sample; +#if FFMPEG_ROTATION_METADATA double rotate_override_value; +#endif AVRational frame_aspect_ratio; - /* forced key frames */ - int64_t forced_kf_ref_pts; - int64_t *forced_kf_pts; - int forced_kf_count; - int forced_kf_index; - char *forced_keyframes; - AVExpr *forced_keyframes_pexpr; - double forced_keyframes_expr_const_values[FKF_NB]; - int dropped_keyframe; + KeyframeForceCtx kf; /* audio only */ +#if FFMPEG_OPT_MAP_CHANNEL int *audio_channels_map; /* list of the channels id to pick from the source stream */ int audio_channels_mapped; /* number of channels in audio_channels_map */ +#endif char *logfile_prefix; FILE *logfile; @@ -569,7 +688,6 @@ typedef struct OutputStream { char *apad; OSTFinished finished; /* no more packets should be written for this stream */ int unavailable; /* true if the steram is unavailable (possibly temporarily) */ - int stream_copy; // init_output_stream() has been called for this stream // The encoder and the bitstream filters have been initialized and the stream @@ -582,15 +700,16 @@ typedef struct OutputStream { int streamcopy_started; int copy_initial_nonkeyframes; int copy_prior_start; - char *disposition; int keep_pix_fmt; /* stats */ - // combined size of all the packets written - uint64_t data_size; + // combined size of all the packets sent to the muxer + uint64_t data_size_mux; + // combined size of all the packets received from the encoder + uint64_t data_size_enc; // number of packets send to the muxer - uint64_t packets_written; + atomic_uint_least64_t packets_written; // number of frames/samples sent to the encoder uint64_t frames_encoded; uint64_t samples_encoded; @@ -600,51 +719,48 @@ typedef struct OutputStream { /* packet quality factor */ int quality; - int max_muxing_queue_size; - - /* the packets are buffered here until the muxer is ready to be initialized */ - AVFifo *muxing_queue; - - /* - * The size of the AVPackets' buffers in queue. - * Updated when a packet is either pushed or pulled from the queue. - */ - size_t muxing_queue_data_size; - - /* Threshold after which max_muxing_queue_size will be in effect */ - size_t muxing_queue_data_threshold; - /* packet picture type */ int pict_type; /* frame encode sum of squared error values */ int64_t error[4]; + + int sq_idx_encode; + int sq_idx_mux; + + EncStats enc_stats_pre; + EncStats enc_stats_post; + + /* + * bool on whether this stream should be utilized for splitting + * subtitles utilizing fix_sub_duration at random access points. + */ + unsigned int fix_sub_duration_heartbeat; } OutputStream; typedef struct OutputFile { + const AVClass *clazz; + int index; const AVOutputFormat *format; + const char *url; + + OutputStream **streams; + int nb_streams; + + SyncQueue *sq_encode; - AVFormatContext *ctx; - AVDictionary *opts; - int ost_index; /* index of the first stream in output_streams */ int64_t recording_time; ///< desired length of the resulting file in microseconds == AV_TIME_BASE units int64_t start_time; ///< start time in microseconds == AV_TIME_BASE units - uint64_t limit_filesize; /* filesize limit expressed in bytes */ int shortest; - - int header_written; + int bitexact; } OutputFile; -extern __thread InputStream **input_streams; -extern __thread int nb_input_streams; extern __thread InputFile **input_files; extern __thread int nb_input_files; -extern __thread OutputStream **output_streams; -extern __thread int nb_output_streams; extern __thread OutputFile **output_files; extern __thread int nb_output_files; @@ -658,13 +774,10 @@ extern __thread float audio_drift_threshold; extern __thread float dts_delta_threshold; extern __thread float dts_error_threshold; -extern __thread int audio_volume; -extern __thread int audio_sync_method; extern __thread enum VideoSyncMethod video_sync_method; extern __thread float frame_drop_threshold; extern __thread int do_benchmark; extern __thread int do_benchmark_all; -extern __thread int do_deinterlace; extern __thread int do_hex_dump; extern __thread int do_pkt_dump; extern __thread int copy_ts; @@ -677,7 +790,6 @@ extern __thread int print_stats; extern __thread int64_t stats_period; extern __thread int qp_hist; extern __thread int stdin_interaction; -extern __thread int frame_bits_per_raw_sample; extern __thread AVIOContext *progress_avio; extern __thread float max_error_rate; @@ -688,15 +800,20 @@ extern __thread int auto_conversion_filters; extern __thread const AVIOInterruptCB int_cb; -#if CONFIG_QSV -extern __thread char *qsv_device; -#endif extern __thread HWDevice *filter_hw_device; -extern __thread int want_sdp; extern __thread unsigned nb_output_dumped; extern __thread int main_ffmpeg_return_code; +extern __thread int ignore_unknown_streams; +extern __thread int copy_unknown_streams; + +extern __thread int recast_media; + +#if FFMPEG_OPT_PSNR +extern __thread int do_psnr; +#endif + void term_init(void); void term_exit(void); @@ -705,7 +822,12 @@ void show_usage(void); void remove_avoptions(AVDictionary **a, AVDictionary *b); void assert_avoptions(AVDictionary *m); -int guess_input_channel_layout(InputStream *ist); +void assert_file_overwrite(const char *filename); +char *file_read(const char *filename); +AVDictionary *strip_specifiers(const AVDictionary *dict); +const AVCodec *find_codec_or_die(void *logctx, const char *name, + enum AVMediaType type, int encoder); +int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_idx, int is_global); int configure_filtergraph(FilterGraph *fg); void check_filter_outputs(void); @@ -719,8 +841,9 @@ int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame); int ffmpeg_parse_options(int argc, char **argv); -int videotoolbox_init(AVCodecContext *s); -int qsv_init(AVCodecContext *s); +void enc_stats_write(OutputStream *ost, EncStats *es, + const AVFrame *frame, const AVPacket *pkt, + uint64_t frame_num); HWDevice *hw_device_get_by_name(const char *name); int hw_device_init_from_string(const char *arg, HWDevice **dev); @@ -732,15 +855,58 @@ int hw_device_setup_for_filter(FilterGraph *fg); int hwaccel_decode_init(AVCodecContext *avctx); -/* open the muxer when all the streams are initialized */ -int of_check_init(OutputFile *of); +/* + * Initialize muxing state for the given stream, should be called + * after the codec/streamcopy setup has been done. + * + * Open the muxer once all the streams have been initialized. + */ +int of_stream_init(OutputFile *of, OutputStream *ost); int of_write_trailer(OutputFile *of); +int of_open(const OptionsContext *o, const char *filename); void of_close(OutputFile **pof); -void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, - int unqueue); +void of_enc_stats_close(void); + +/* + * Send a single packet to the output, applying any bitstream filters + * associated with the output stream. This may result in any number + * of packets actually being written, depending on what bitstream + * filters are applied. The supplied packet is consumed and will be + * blank (as if newly-allocated) when this function returns. + * + * If eof is set, instead indicate EOF to all bitstream filters and + * therefore flush any delayed packets to the output. A blank packet + * must be supplied in this case. + */ +void of_output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof); +int64_t of_filesize(OutputFile *of); + +int ifile_open(const OptionsContext *o, const char *filename); +void ifile_close(InputFile **f); + +/** + * Get next input packet from the demuxer. + * + * @param pkt the packet is written here when this function returns 0 + * @return + * - 0 when a packet has been read successfully + * - 1 when stream end was reached, but the stream is looped; + * caller should flush decoders and read from this demuxer again + * - a negative error code on failure + */ +int ifile_get_packet(InputFile *f, AVPacket **pkt); + +/* iterate over all input streams in all input files; + * pass NULL to start iteration */ +InputStream *ist_iter(InputStream *prev); + +extern const char * const opt_name_codec_names[]; +extern const char * const opt_name_codec_tags[]; +extern const char * const opt_name_frame_rates[]; +extern const char * const opt_name_top_field_first[]; -void set_report_callback(void (*callback)(int, float, float, int64_t, int, double, double)); +void set_report_callback(void (*callback)(int, float, float, int64_t, double, double, double)); void cancel_operation(long id); #endif /* FFTOOLS_FFMPEG_H */ diff --git a/linux/src/fftools_ffmpeg_demux.c b/linux/src/fftools_ffmpeg_demux.c new file mode 100644 index 0000000..de246da --- /dev/null +++ b/linux/src/fftools_ffmpeg_demux.c @@ -0,0 +1,1148 @@ +/* + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_demux.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools_ prefix added to the file name + * - fftools_ffmpeg_mux.h include added + */ + +#include +#include + +#include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/display.h" +#include "libavutil/error.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" +#include "libavutil/time.h" +#include "libavutil/timestamp.h" +#include "libavutil/thread.h" +#include "libavutil/threadmessage.h" + +#include "libavcodec/packet.h" + +#include "libavformat/avformat.h" + +static const char *const opt_name_discard[] = {"discard", NULL}; +static const char *const opt_name_reinit_filters[] = {"reinit_filter", NULL}; +static const char *const opt_name_fix_sub_duration[] = {"fix_sub_duration", NULL}; +static const char *const opt_name_canvas_sizes[] = {"canvas_size", NULL}; +static const char *const opt_name_guess_layout_max[] = {"guess_layout_max", NULL}; +static const char *const opt_name_ts_scale[] = {"itsscale", NULL}; +static const char *const opt_name_hwaccels[] = {"hwaccel", NULL}; +static const char *const opt_name_hwaccel_devices[] = {"hwaccel_device", NULL}; +static const char *const opt_name_hwaccel_output_formats[] = {"hwaccel_output_format", NULL}; +static const char *const opt_name_autorotate[] = {"autorotate", NULL}; +static const char *const opt_name_display_rotations[] = {"display_rotation", NULL}; +static const char *const opt_name_display_hflips[] = {"display_hflip", NULL}; +static const char *const opt_name_display_vflips[] = {"display_vflip", NULL}; + +typedef struct Demuxer { + InputFile f; + + /* number of times input stream should be looped */ + int loop; + /* actual duration of the longest stream in a file at the moment when + * looping happens */ + int64_t duration; + /* time base of the duration */ + AVRational time_base; + + /* number of streams that the user was warned of */ + int nb_streams_warn; + + AVThreadMessageQueue *in_thread_queue; + int thread_queue_size; + pthread_t thread; + int non_blocking; +} Demuxer; + +typedef struct DemuxMsg { + AVPacket *pkt; + int looping; + + // repeat_pict from the demuxer-internal parser + int repeat_pict; +} DemuxMsg; + +static Demuxer *demuxer_from_ifile(InputFile *f) +{ + return (Demuxer*)f; +} + +static void report_new_stream(Demuxer *d, const AVPacket *pkt) +{ + AVStream *st = d->f.ctx->streams[pkt->stream_index]; + + if (pkt->stream_index < d->nb_streams_warn) + return; + av_log(NULL, AV_LOG_WARNING, + "New %s stream %d:%d at pos:%"PRId64" and DTS:%ss\n", + av_get_media_type_string(st->codecpar->codec_type), + d->f.index, pkt->stream_index, + pkt->pos, av_ts2timestr(pkt->dts, &st->time_base)); + d->nb_streams_warn = pkt->stream_index + 1; +} + +static void ifile_duration_update(Demuxer *d, InputStream *ist, + int64_t last_duration) +{ + /* the total duration of the stream, max_pts - min_pts is + * the duration of the stream without the last frame */ + if (ist->max_pts > ist->min_pts && + ist->max_pts - (uint64_t)ist->min_pts < INT64_MAX - last_duration) + last_duration += ist->max_pts - ist->min_pts; + + if (!d->duration || + av_compare_ts(d->duration, d->time_base, + last_duration, ist->st->time_base) < 0) { + d->duration = last_duration; + d->time_base = ist->st->time_base; + } +} + +static int seek_to_start(Demuxer *d) +{ + InputFile *ifile = &d->f; + AVFormatContext *is = ifile->ctx; + InputStream *ist; + int ret; + + ret = avformat_seek_file(is, -1, INT64_MIN, is->start_time, is->start_time, 0); + if (ret < 0) + return ret; + + if (ifile->audio_duration_queue_size) { + /* duration is the length of the last frame in a stream + * when audio stream is present we don't care about + * last video frame length because it's not defined exactly */ + int got_durations = 0; + + while (got_durations < ifile->audio_duration_queue_size) { + LastFrameDuration dur; + ret = av_thread_message_queue_recv(ifile->audio_duration_queue, &dur, 0); + if (ret < 0) + return ret; + got_durations++; + + ist = ifile->streams[dur.stream_idx]; + ifile_duration_update(d, ist, dur.duration); + } + } else { + for (int i = 0; i < ifile->nb_streams; i++) { + int64_t duration = 0; + ist = ifile->streams[i]; + + if (ist->framerate.num) { + duration = av_rescale_q(1, av_inv_q(ist->framerate), ist->st->time_base); + } else if (ist->st->avg_frame_rate.num) { + duration = av_rescale_q(1, av_inv_q(ist->st->avg_frame_rate), ist->st->time_base); + } else { + duration = 1; + } + + ifile_duration_update(d, ist, duration); + } + } + + if (d->loop > 0) + d->loop--; + + return ret; +} + +static void ts_fixup(Demuxer *d, AVPacket *pkt, int *repeat_pict) +{ + InputFile *ifile = &d->f; + InputStream *ist = ifile->streams[pkt->stream_index]; + const int64_t start_time = ifile->start_time_effective; + int64_t duration; + + if (debug_ts) { + av_log(NULL, AV_LOG_INFO, "demuxer -> ist_index:%d:%d type:%s " + "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s\n", + ifile->index, pkt->stream_index, + av_get_media_type_string(ist->st->codecpar->codec_type), + av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ist->st->time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ist->st->time_base), + av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ist->st->time_base)); + } + + if (!ist->wrap_correction_done && start_time != AV_NOPTS_VALUE && + ist->st->pts_wrap_bits < 64) { + int64_t stime, stime2; + + stime = av_rescale_q(start_time, AV_TIME_BASE_Q, ist->st->time_base); + stime2= stime + (1ULL<st->pts_wrap_bits); + ist->wrap_correction_done = 1; + + if(stime2 > stime && pkt->dts != AV_NOPTS_VALUE && pkt->dts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { + pkt->dts -= 1ULL<st->pts_wrap_bits; + ist->wrap_correction_done = 0; + } + if(stime2 > stime && pkt->pts != AV_NOPTS_VALUE && pkt->pts > stime + (1LL<<(ist->st->pts_wrap_bits-1))) { + pkt->pts -= 1ULL<st->pts_wrap_bits; + ist->wrap_correction_done = 0; + } + } + + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += av_rescale_q(ifile->ts_offset, AV_TIME_BASE_Q, ist->st->time_base); + + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts *= ist->ts_scale; + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts *= ist->ts_scale; + + duration = av_rescale_q(d->duration, d->time_base, ist->st->time_base); + if (pkt->pts != AV_NOPTS_VALUE) { + pkt->pts += duration; + ist->max_pts = FFMAX(pkt->pts, ist->max_pts); + ist->min_pts = FFMIN(pkt->pts, ist->min_pts); + } + + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += duration; + + *repeat_pict = -1; + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + av_stream_get_parser(ist->st)) + *repeat_pict = av_stream_get_parser(ist->st)->repeat_pict; +} + +static void thread_set_name(InputFile *f) +{ + char name[16]; + snprintf(name, sizeof(name), "dmx%d:%s", f->index, f->ctx->iformat->name); + ff_thread_setname(name); +} + +static void *input_thread(void *arg) +{ + Demuxer *d = arg; + InputFile *f = &d->f; + AVPacket *pkt; + unsigned flags = d->non_blocking ? AV_THREAD_MESSAGE_NONBLOCK : 0; + int ret = 0; + + pkt = av_packet_alloc(); + if (!pkt) { + ret = AVERROR(ENOMEM); + goto finish; + } + + thread_set_name(f); + + while (1) { + DemuxMsg msg = { NULL }; + + ret = av_read_frame(f->ctx, pkt); + + if (ret == AVERROR(EAGAIN)) { + av_usleep(10000); + continue; + } + if (ret < 0) { + if (d->loop) { + /* signal looping to the consumer thread */ + msg.looping = 1; + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, 0); + if (ret >= 0) + ret = seek_to_start(d); + if (ret >= 0) + continue; + + /* fallthrough to the error path */ + } + + if (ret == AVERROR_EOF) + av_log(NULL, AV_LOG_VERBOSE, "EOF in input file %d\n", f->index); + else + av_log(NULL, AV_LOG_ERROR, "Error demuxing input file %d: %s\n", + f->index, av_err2str(ret)); + + break; + } + + if (do_pkt_dump) { + av_pkt_dump_log2(NULL, AV_LOG_INFO, pkt, do_hex_dump, + f->ctx->streams[pkt->stream_index]); + } + + /* the following test is needed in case new streams appear + dynamically in stream : we ignore them */ + if (pkt->stream_index >= f->nb_streams) { + report_new_stream(d, pkt); + av_packet_unref(pkt); + continue; + } + + if (pkt->flags & AV_PKT_FLAG_CORRUPT) { + av_log(NULL, exit_on_error ? AV_LOG_FATAL : AV_LOG_WARNING, + "%s: corrupt input packet in stream %d\n", + f->ctx->url, pkt->stream_index); + if (exit_on_error) { + av_packet_unref(pkt); + ret = AVERROR_INVALIDDATA; + break; + } + } + + ts_fixup(d, pkt, &msg.repeat_pict); + + msg.pkt = av_packet_alloc(); + if (!msg.pkt) { + av_packet_unref(pkt); + ret = AVERROR(ENOMEM); + break; + } + av_packet_move_ref(msg.pkt, pkt); + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, flags); + if (flags && ret == AVERROR(EAGAIN)) { + flags = 0; + ret = av_thread_message_queue_send(d->in_thread_queue, &msg, flags); + av_log(f->ctx, AV_LOG_WARNING, + "Thread message queue blocking; consider raising the " + "thread_queue_size option (current value: %d)\n", + d->thread_queue_size); + } + if (ret < 0) { + if (ret != AVERROR_EOF) + av_log(f->ctx, AV_LOG_ERROR, + "Unable to send packet to main thread: %s\n", + av_err2str(ret)); + av_packet_free(&msg.pkt); + break; + } + } + +finish: + av_assert0(ret < 0); + av_thread_message_queue_set_err_recv(d->in_thread_queue, ret); + + av_packet_free(&pkt); + + av_log(NULL, AV_LOG_VERBOSE, "Terminating demuxer thread %d\n", f->index); + + return NULL; +} + +static void thread_stop(Demuxer *d) +{ + InputFile *f = &d->f; + DemuxMsg msg; + + if (!d->in_thread_queue) + return; + av_thread_message_queue_set_err_send(d->in_thread_queue, AVERROR_EOF); + while (av_thread_message_queue_recv(d->in_thread_queue, &msg, 0) >= 0) + av_packet_free(&msg.pkt); + + pthread_join(d->thread, NULL); + av_thread_message_queue_free(&d->in_thread_queue); + av_thread_message_queue_free(&f->audio_duration_queue); +} + +static int thread_start(Demuxer *d) +{ + int ret; + InputFile *f = &d->f; + + if (d->thread_queue_size <= 0) + d->thread_queue_size = (nb_input_files > 1 ? 8 : 1); + + if (nb_input_files > 1 && + (f->ctx->pb ? !f->ctx->pb->seekable : + strcmp(f->ctx->iformat->name, "lavfi"))) + d->non_blocking = 1; + ret = av_thread_message_queue_alloc(&d->in_thread_queue, + d->thread_queue_size, sizeof(DemuxMsg)); + if (ret < 0) + return ret; + + if (d->loop) { + int nb_audio_dec = 0; + + for (int i = 0; i < f->nb_streams; i++) { + InputStream *ist = f->streams[i]; + nb_audio_dec += !!(ist->decoding_needed && + ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO); + } + + if (nb_audio_dec) { + ret = av_thread_message_queue_alloc(&f->audio_duration_queue, + nb_audio_dec, sizeof(LastFrameDuration)); + if (ret < 0) + goto fail; + f->audio_duration_queue_size = nb_audio_dec; + } + } + + if ((ret = pthread_create(&d->thread, NULL, input_thread, d))) { + av_log(NULL, AV_LOG_ERROR, "pthread_create failed: %s. Try to increase `ulimit -v` or decrease `ulimit -s`.\n", strerror(ret)); + ret = AVERROR(ret); + goto fail; + } + + return 0; +fail: + av_thread_message_queue_free(&d->in_thread_queue); + return ret; +} + +int ifile_get_packet(InputFile *f, AVPacket **pkt) +{ + Demuxer *d = demuxer_from_ifile(f); + InputStream *ist; + DemuxMsg msg; + int ret; + + if (!d->in_thread_queue) { + ret = thread_start(d); + if (ret < 0) + return ret; + } + + if (f->readrate || f->rate_emu) { + int i; + int64_t file_start = copy_ts * ( + (f->start_time_effective != AV_NOPTS_VALUE ? f->start_time_effective * !start_at_zero : 0) + + (f->start_time != AV_NOPTS_VALUE ? f->start_time : 0) + ); + float scale = f->rate_emu ? 1.0 : f->readrate; + for (i = 0; i < f->nb_streams; i++) { + InputStream *ist = f->streams[i]; + int64_t stream_ts_offset, pts, now; + if (!ist->nb_packets || (ist->decoding_needed && !ist->got_output)) continue; + stream_ts_offset = FFMAX(ist->first_dts != AV_NOPTS_VALUE ? ist->first_dts : 0, file_start); + pts = av_rescale(ist->dts, 1000000, AV_TIME_BASE); + now = (av_gettime_relative() - ist->start) * scale + stream_ts_offset; + if (pts > now) + return AVERROR(EAGAIN); + } + } + + ret = av_thread_message_queue_recv(d->in_thread_queue, &msg, + d->non_blocking ? + AV_THREAD_MESSAGE_NONBLOCK : 0); + if (ret < 0) + return ret; + if (msg.looping) + return 1; + + ist = f->streams[msg.pkt->stream_index]; + ist->last_pkt_repeat_pict = msg.repeat_pict; + + *pkt = msg.pkt; + return 0; +} + +static void ist_free(InputStream **pist) +{ + InputStream *ist = *pist; + + if (!ist) + return; + + av_frame_free(&ist->decoded_frame); + av_packet_free(&ist->pkt); + av_dict_free(&ist->decoder_opts); + avsubtitle_free(&ist->prev_sub.subtitle); + av_frame_free(&ist->sub2video.frame); + av_freep(&ist->filters); + av_freep(&ist->hwaccel_device); + av_freep(&ist->dts_buffer); + + avcodec_free_context(&ist->dec_ctx); + avcodec_parameters_free(&ist->par); + + av_freep(pist); +} + +void ifile_close(InputFile **pf) +{ + InputFile *f = *pf; + Demuxer *d = demuxer_from_ifile(f); + + if (!f) + return; + + thread_stop(d); + + for (int i = 0; i < f->nb_streams; i++) + ist_free(&f->streams[i]); + av_freep(&f->streams); + + avformat_close_input(&f->ctx); + + av_freep(pf); +} + +static const AVCodec *choose_decoder(const OptionsContext *o, AVFormatContext *s, AVStream *st, + enum HWAccelID hwaccel_id, enum AVHWDeviceType hwaccel_device_type) + +{ + char *codec_name = NULL; + + MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st); + if (codec_name) { + const AVCodec *codec = find_codec_or_die(NULL, codec_name, st->codecpar->codec_type, 0); + st->codecpar->codec_id = codec->id; + if (recast_media && st->codecpar->codec_type != codec->type) + st->codecpar->codec_type = codec->type; + return codec; + } else { + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + hwaccel_id == HWACCEL_GENERIC && + hwaccel_device_type != AV_HWDEVICE_TYPE_NONE) { + const AVCodec *c; + void *i = NULL; + + while ((c = av_codec_iterate(&i))) { + const AVCodecHWConfig *config; + + if (c->id != st->codecpar->codec_id || + !av_codec_is_decoder(c)) + continue; + + for (int j = 0; (config = avcodec_get_hw_config(c, j)); j++) { + if (config->device_type == hwaccel_device_type) { + av_log(NULL, AV_LOG_VERBOSE, "Selecting decoder '%s' because of requested hwaccel method %s\n", + c->name, av_hwdevice_get_type_name(hwaccel_device_type)); + return c; + } + } + } + } + + return avcodec_find_decoder(st->codecpar->codec_id); + } +} + +static int guess_input_channel_layout(InputStream *ist) +{ + AVCodecContext *dec = ist->dec_ctx; + + if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) { + char layout_name[256]; + + if (dec->ch_layout.nb_channels > ist->guess_layout_max) + return 0; + av_channel_layout_default(&dec->ch_layout, dec->ch_layout.nb_channels); + if (dec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) + return 0; + av_channel_layout_describe(&dec->ch_layout, layout_name, sizeof(layout_name)); + av_log(NULL, AV_LOG_WARNING, "Guessed Channel Layout for Input Stream " + "#%d.%d : %s\n", ist->file_index, ist->st->index, layout_name); + } + return 1; +} + +static void add_display_matrix_to_stream(const OptionsContext *o, + AVFormatContext *ctx, AVStream *st) +{ + double rotation = DBL_MAX; + int hflip = -1, vflip = -1; + int hflip_set = 0, vflip_set = 0, rotation_set = 0; + int32_t *buf; + + MATCH_PER_STREAM_OPT(display_rotations, dbl, rotation, ctx, st); + MATCH_PER_STREAM_OPT(display_hflips, i, hflip, ctx, st); + MATCH_PER_STREAM_OPT(display_vflips, i, vflip, ctx, st); + + rotation_set = rotation != DBL_MAX; + hflip_set = hflip != -1; + vflip_set = vflip != -1; + + if (!rotation_set && !hflip_set && !vflip_set) + return; + + buf = (int32_t *)av_stream_new_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, sizeof(int32_t) * 9); + if (!buf) { + av_log(NULL, AV_LOG_FATAL, "Failed to generate a display matrix!\n"); + exit_program(1); + } + + av_display_rotation_set(buf, + rotation_set ? -(rotation) : -0.0f); + + av_display_matrix_flip(buf, + hflip_set ? hflip : 0, + vflip_set ? vflip : 0); +} + +/* Add all the streams from the given input file to the demuxer */ +static void add_input_streams(const OptionsContext *o, Demuxer *d) +{ + InputFile *f = &d->f; + AVFormatContext *ic = f->ctx; + int i, ret; + + for (i = 0; i < ic->nb_streams; i++) { + AVStream *st = ic->streams[i]; + AVCodecParameters *par = st->codecpar; + InputStream *ist; + char *framerate = NULL, *hwaccel_device = NULL; + const char *hwaccel = NULL; + char *hwaccel_output_format = NULL; + char *codec_tag = NULL; + char *next; + char *discard_str = NULL; + const AVClass *cc = avcodec_get_class(); + const AVOption *discard_opt = av_opt_find(&cc, "skip_frame", NULL, + 0, AV_OPT_SEARCH_FAKE_OBJ); + + ist = ALLOC_ARRAY_ELEM(f->streams, f->nb_streams); + ist->st = st; + ist->file_index = f->index; + ist->discard = 1; + st->discard = AVDISCARD_ALL; + ist->nb_samples = 0; + ist->first_dts = AV_NOPTS_VALUE; + ist->min_pts = INT64_MAX; + ist->max_pts = INT64_MIN; + + ist->ts_scale = 1.0; + MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st); + + ist->autorotate = 1; + MATCH_PER_STREAM_OPT(autorotate, i, ist->autorotate, ic, st); + + MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st); + if (codec_tag) { + uint32_t tag = strtol(codec_tag, &next, 0); + if (*next) + tag = AV_RL32(codec_tag); + st->codecpar->codec_tag = tag; + } + + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + add_display_matrix_to_stream(o, ic, st); + + MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st); + MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, + hwaccel_output_format, ic, st); + + if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "cuvid")) { + av_log(NULL, AV_LOG_WARNING, + "WARNING: defaulting hwaccel_output_format to cuda for compatibility " + "with old commandlines. This behaviour is DEPRECATED and will be removed " + "in the future. Please explicitly set \"-hwaccel_output_format cuda\".\n"); + ist->hwaccel_output_format = AV_PIX_FMT_CUDA; + } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "qsv")) { + av_log(NULL, AV_LOG_WARNING, + "WARNING: defaulting hwaccel_output_format to qsv for compatibility " + "with old commandlines. This behaviour is DEPRECATED and will be removed " + "in the future. Please explicitly set \"-hwaccel_output_format qsv\".\n"); + ist->hwaccel_output_format = AV_PIX_FMT_QSV; + } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "mediacodec")) { + // There is no real AVHWFrameContext implementation. Set + // hwaccel_output_format to avoid av_hwframe_transfer_data error. + ist->hwaccel_output_format = AV_PIX_FMT_MEDIACODEC; + } else if (hwaccel_output_format) { + ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format); + if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) { + av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output " + "format: %s", hwaccel_output_format); + } + } else { + ist->hwaccel_output_format = AV_PIX_FMT_NONE; + } + + if (hwaccel) { + // The NVDEC hwaccels use a CUDA device, so remap the name here. + if (!strcmp(hwaccel, "nvdec") || !strcmp(hwaccel, "cuvid")) + hwaccel = "cuda"; + + if (!strcmp(hwaccel, "none")) + ist->hwaccel_id = HWACCEL_NONE; + else if (!strcmp(hwaccel, "auto")) + ist->hwaccel_id = HWACCEL_AUTO; + else { + enum AVHWDeviceType type = av_hwdevice_find_type_by_name(hwaccel); + if (type != AV_HWDEVICE_TYPE_NONE) { + ist->hwaccel_id = HWACCEL_GENERIC; + ist->hwaccel_device_type = type; + } + + if (!ist->hwaccel_id) { + av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n", + hwaccel); + av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: "); + type = AV_HWDEVICE_TYPE_NONE; + while ((type = av_hwdevice_iterate_types(type)) != + AV_HWDEVICE_TYPE_NONE) + av_log(NULL, AV_LOG_FATAL, "%s ", + av_hwdevice_get_type_name(type)); + av_log(NULL, AV_LOG_FATAL, "\n"); + exit_program(1); + } + } + } + + MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st); + if (hwaccel_device) { + ist->hwaccel_device = av_strdup(hwaccel_device); + if (!ist->hwaccel_device) + report_and_exit(AVERROR(ENOMEM)); + } + + ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE; + } + + ist->dec = choose_decoder(o, ic, st, ist->hwaccel_id, ist->hwaccel_device_type); + ist->decoder_opts = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ic, st, ist->dec); + + ist->reinit_filters = -1; + MATCH_PER_STREAM_OPT(reinit_filters, i, ist->reinit_filters, ic, st); + + MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st); + ist->user_set_discard = AVDISCARD_NONE; + + if ((o->video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) || + (o->audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || + (o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) || + (o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA)) + ist->user_set_discard = AVDISCARD_ALL; + + if (discard_str && av_opt_eval_int(&cc, discard_opt, discard_str, &ist->user_set_discard) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing discard %s.\n", + discard_str); + exit_program(1); + } + + ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE; + ist->prev_pkt_pts = AV_NOPTS_VALUE; + + ist->dec_ctx = avcodec_alloc_context3(ist->dec); + if (!ist->dec_ctx) + report_and_exit(AVERROR(ENOMEM)); + + ret = avcodec_parameters_to_context(ist->dec_ctx, par); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); + exit_program(1); + } + + ist->decoded_frame = av_frame_alloc(); + if (!ist->decoded_frame) + report_and_exit(AVERROR(ENOMEM)); + + ist->pkt = av_packet_alloc(); + if (!ist->pkt) + report_and_exit(AVERROR(ENOMEM)); + + if (o->bitexact) + ist->dec_ctx->flags |= AV_CODEC_FLAG_BITEXACT; + + switch (par->codec_type) { + case AVMEDIA_TYPE_VIDEO: + // avformat_find_stream_info() doesn't set this for us anymore. + ist->dec_ctx->framerate = st->avg_frame_rate; + + MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st); + if (framerate && av_parse_video_rate(&ist->framerate, + framerate) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing framerate %s.\n", + framerate); + exit_program(1); + } + + ist->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st); + + ist->framerate_guessed = av_guess_frame_rate(ic, st, NULL); + + break; + case AVMEDIA_TYPE_AUDIO: + ist->guess_layout_max = INT_MAX; + MATCH_PER_STREAM_OPT(guess_layout_max, i, ist->guess_layout_max, ic, st); + guess_input_channel_layout(ist); + break; + case AVMEDIA_TYPE_DATA: + case AVMEDIA_TYPE_SUBTITLE: { + char *canvas_size = NULL; + MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st); + MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st); + if (canvas_size && + av_parse_video_size(&ist->dec_ctx->width, &ist->dec_ctx->height, canvas_size) < 0) { + av_log(NULL, AV_LOG_FATAL, "Invalid canvas size: %s.\n", canvas_size); + exit_program(1); + } + break; + } + case AVMEDIA_TYPE_ATTACHMENT: + case AVMEDIA_TYPE_UNKNOWN: + break; + default: + abort(); + } + + ist->par = avcodec_parameters_alloc(); + if (!ist->par) + report_and_exit(AVERROR(ENOMEM)); + + ret = avcodec_parameters_from_context(ist->par, ist->dec_ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); + exit_program(1); + } + } +} + +static void dump_attachment(AVStream *st, const char *filename) +{ + int ret; + AVIOContext *out = NULL; + const AVDictionaryEntry *e; + + if (!st->codecpar->extradata_size) { + av_log(NULL, AV_LOG_WARNING, "No extradata to dump in stream #%d:%d.\n", + nb_input_files - 1, st->index); + return; + } + if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) + filename = e->value; + if (!*filename) { + av_log(NULL, AV_LOG_FATAL, "No filename specified and no 'filename' tag" + "in stream #%d:%d.\n", nb_input_files - 1, st->index); + exit_program(1); + } + + assert_file_overwrite(filename); + + if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &int_cb, NULL)) < 0) { + av_log(NULL, AV_LOG_FATAL, "Could not open file %s for writing.\n", + filename); + exit_program(1); + } + + avio_write(out, st->codecpar->extradata, st->codecpar->extradata_size); + avio_flush(out); + avio_close(out); +} + +int ifile_open(const OptionsContext *o, const char *filename) +{ + Demuxer *d; + InputFile *f; + AVFormatContext *ic; + const AVInputFormat *file_iformat = NULL; + int err, i, ret; + int64_t timestamp; + AVDictionary *unused_opts = NULL; + const AVDictionaryEntry *e = NULL; + char * video_codec_name = NULL; + char * audio_codec_name = NULL; + char *subtitle_codec_name = NULL; + char * data_codec_name = NULL; + int scan_all_pmts_set = 0; + + int64_t start_time = o->start_time; + int64_t start_time_eof = o->start_time_eof; + int64_t stop_time = o->stop_time; + int64_t recording_time = o->recording_time; + + if (stop_time != INT64_MAX && recording_time != INT64_MAX) { + stop_time = INT64_MAX; + av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); + } + + if (stop_time != INT64_MAX && recording_time == INT64_MAX) { + int64_t start = start_time == AV_NOPTS_VALUE ? 0 : start_time; + if (stop_time <= start) { + av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); + exit_program(1); + } else { + recording_time = stop_time - start; + } + } + + if (o->format) { + if (!(file_iformat = av_find_input_format(o->format))) { + av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format); + exit_program(1); + } + } + + if (!strcmp(filename, "-")) + filename = "fd:"; + + stdin_interaction &= strncmp(filename, "pipe:", 5) && + strcmp(filename, "fd:") && + strcmp(filename, "/dev/stdin"); + + /* get default parameters from command line */ + ic = avformat_alloc_context(); + if (!ic) + report_and_exit(AVERROR(ENOMEM)); + if (o->nb_audio_sample_rate) { + av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0); + } + if (o->nb_audio_channels) { + const AVClass *priv_class; + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "ch_layout", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + char buf[32]; + snprintf(buf, sizeof(buf), "%dC", o->audio_channels[o->nb_audio_channels - 1].u.i); + av_dict_set(&o->g->format_opts, "ch_layout", buf, 0); + } + } + if (o->nb_audio_ch_layouts) { + const AVClass *priv_class; + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "ch_layout", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + av_dict_set(&o->g->format_opts, "ch_layout", o->audio_ch_layouts[o->nb_audio_ch_layouts - 1].u.str, 0); + } + } + if (o->nb_frame_rates) { + const AVClass *priv_class; + /* set the format-level framerate option; + * this is important for video grabbers, e.g. x11 */ + if (file_iformat && (priv_class = file_iformat->priv_class) && + av_opt_find(&priv_class, "framerate", NULL, 0, + AV_OPT_SEARCH_FAKE_OBJ)) { + av_dict_set(&o->g->format_opts, "framerate", + o->frame_rates[o->nb_frame_rates - 1].u.str, 0); + } + } + if (o->nb_frame_sizes) { + av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0); + } + if (o->nb_frame_pix_fmts) + av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0); + + MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v"); + MATCH_PER_TYPE_OPT(codec_names, str, audio_codec_name, ic, "a"); + MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s"); + MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d"); + + if (video_codec_name) + ic->video_codec = find_codec_or_die(NULL, video_codec_name , AVMEDIA_TYPE_VIDEO , 0); + if (audio_codec_name) + ic->audio_codec = find_codec_or_die(NULL, audio_codec_name , AVMEDIA_TYPE_AUDIO , 0); + if (subtitle_codec_name) + ic->subtitle_codec = find_codec_or_die(NULL, subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0); + if (data_codec_name) + ic->data_codec = find_codec_or_die(NULL, data_codec_name , AVMEDIA_TYPE_DATA , 0); + + ic->video_codec_id = video_codec_name ? ic->video_codec->id : AV_CODEC_ID_NONE; + ic->audio_codec_id = audio_codec_name ? ic->audio_codec->id : AV_CODEC_ID_NONE; + ic->subtitle_codec_id = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE; + ic->data_codec_id = data_codec_name ? ic->data_codec->id : AV_CODEC_ID_NONE; + + ic->flags |= AVFMT_FLAG_NONBLOCK; + if (o->bitexact) + ic->flags |= AVFMT_FLAG_BITEXACT; + ic->interrupt_callback = int_cb; + + if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { + av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); + scan_all_pmts_set = 1; + } + /* open the input file with generic avformat function */ + err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); + if (err < 0) { + print_error(filename, err); + if (err == AVERROR_PROTOCOL_NOT_FOUND) + av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename); + exit_program(1); + } + if (scan_all_pmts_set) + av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); + remove_avoptions(&o->g->format_opts, o->g->codec_opts); + assert_avoptions(o->g->format_opts); + + /* apply forced codec ids */ + for (i = 0; i < ic->nb_streams; i++) + choose_decoder(o, ic, ic->streams[i], HWACCEL_NONE, AV_HWDEVICE_TYPE_NONE); + + if (o->find_stream_info) { + AVDictionary **opts = setup_find_stream_info_opts(ic, o->g->codec_opts); + int orig_nb_streams = ic->nb_streams; + + /* If not enough info to get the stream parameters, we decode the + first frames to get it. (used in mpeg case for example) */ + ret = avformat_find_stream_info(ic, opts); + + for (i = 0; i < orig_nb_streams; i++) + av_dict_free(&opts[i]); + av_freep(&opts); + + if (ret < 0) { + av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename); + if (ic->nb_streams == 0) { + avformat_close_input(&ic); + exit_program(1); + } + } + } + + if (start_time != AV_NOPTS_VALUE && start_time_eof != AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_WARNING, "Cannot use -ss and -sseof both, using -ss for %s\n", filename); + start_time_eof = AV_NOPTS_VALUE; + } + + if (start_time_eof != AV_NOPTS_VALUE) { + if (start_time_eof >= 0) { + av_log(NULL, AV_LOG_ERROR, "-sseof value must be negative; aborting\n"); + exit_program(1); + } + if (ic->duration > 0) { + start_time = start_time_eof + ic->duration; + if (start_time < 0) { + av_log(NULL, AV_LOG_WARNING, "-sseof value seeks to before start of file %s; ignored\n", filename); + start_time = AV_NOPTS_VALUE; + } + } else + av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename); + } + timestamp = (start_time == AV_NOPTS_VALUE) ? 0 : start_time; + /* add the stream start time */ + if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE) + timestamp += ic->start_time; + + /* if seeking requested, we execute it */ + if (start_time != AV_NOPTS_VALUE) { + int64_t seek_timestamp = timestamp; + + if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) { + int dts_heuristic = 0; + for (i=0; inb_streams; i++) { + const AVCodecParameters *par = ic->streams[i]->codecpar; + if (par->video_delay) { + dts_heuristic = 1; + break; + } + } + if (dts_heuristic) { + seek_timestamp -= 3*AV_TIME_BASE / 23; + } + } + ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", + filename, (double)timestamp / AV_TIME_BASE); + } + } + + d = allocate_array_elem(&input_files, sizeof(*d), &nb_input_files); + f = &d->f; + + f->ctx = ic; + f->index = nb_input_files - 1; + f->start_time = start_time; + f->recording_time = recording_time; + f->input_sync_ref = o->input_sync_ref; + f->input_ts_offset = o->input_ts_offset; + f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); + f->rate_emu = o->rate_emu; + f->accurate_seek = o->accurate_seek; + d->loop = o->loop; + d->duration = 0; + d->time_base = (AVRational){ 1, 1 }; + + f->readrate = o->readrate ? o->readrate : 0.0; + if (f->readrate < 0.0f) { + av_log(NULL, AV_LOG_ERROR, "Option -readrate for Input #%d is %0.3f; it must be non-negative.\n", f->index, f->readrate); + exit_program(1); + } + if (f->readrate && f->rate_emu) { + av_log(NULL, AV_LOG_WARNING, "Both -readrate and -re set for Input #%d. Using -readrate %0.3f.\n", f->index, f->readrate); + f->rate_emu = 0; + } + + d->thread_queue_size = o->thread_queue_size; + + /* update the current parameters so that they match the one of the input stream */ + add_input_streams(o, d); + + /* dump the file content */ + av_dump_format(ic, f->index, filename, 0); + + /* check if all codec options have been used */ + unused_opts = strip_specifiers(o->g->codec_opts); + for (i = 0; i < f->nb_streams; i++) { + e = NULL; + while ((e = av_dict_iterate(f->streams[i]->decoder_opts, e))) + av_dict_set(&unused_opts, e->key, NULL, 0); + } + + e = NULL; + while ((e = av_dict_iterate(unused_opts, e))) { + const AVClass *class = avcodec_get_class(); + const AVOption *option = av_opt_find(&class, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + const AVClass *fclass = avformat_get_class(); + const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + if (!option || foption) + continue; + + + if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) { + av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " + "input file #%d (%s) is not a decoding option.\n", e->key, + option->help ? option->help : "", f->index, + filename); + exit_program(1); + } + + av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " + "input file #%d (%s) has not been used for any stream. The most " + "likely reason is either wrong type (e.g. a video option with " + "no video streams) or that it is a private option of some decoder " + "which was not actually used for any stream.\n", e->key, + option->help ? option->help : "", f->index, filename); + } + av_dict_free(&unused_opts); + + for (i = 0; i < o->nb_dump_attachment; i++) { + int j; + + for (j = 0; j < ic->nb_streams; j++) { + AVStream *st = ic->streams[j]; + + if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1) + dump_attachment(st, o->dump_attachment[i].u.str); + } + } + + return 0; +} diff --git a/linux/src/fftools_ffmpeg_filter.c b/linux/src/fftools_ffmpeg_filter.c index 46edae5..73d7763 100644 --- a/linux/src/fftools_ffmpeg_filter.c +++ b/linux/src/fftools_ffmpeg_filter.c @@ -1,6 +1,7 @@ /* * ffmpeg filter configuration * Copyright (c) 2018 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,12 @@ * We manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 08.2018 @@ -69,8 +76,9 @@ static const enum AVPixelFormat *get_compliance_normal_pix_fmts(const AVCodec *c } } -enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, - const AVCodec *codec, enum AVPixelFormat target) +enum AVPixelFormat +choose_pixel_fmt(const AVCodec *codec, enum AVPixelFormat target, + int strict_std_compliance) { if (codec && codec->pix_fmts) { const enum AVPixelFormat *p = codec->pix_fmts; @@ -79,7 +87,7 @@ enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, int has_alpha = desc ? desc->nb_components % 2 == 0 : 0; enum AVPixelFormat best= AV_PIX_FMT_NONE; - if (enc_ctx->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { + if (strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { p = get_compliance_normal_pix_fmts(codec, p); } for (; *p != AV_PIX_FMT_NONE; p++) { @@ -106,6 +114,7 @@ enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *enc_ctx, static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) { OutputStream *ost = ofilter->ost; + AVCodecContext *enc = ost->enc_ctx; const AVDictionaryEntry *strict_dict = av_dict_get(ost->encoder_opts, "strict", NULL, 0); if (strict_dict) // used by choose_pixel_fmt() and below @@ -119,13 +128,14 @@ static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) return av_get_pix_fmt_name(ost->enc_ctx->pix_fmt); } if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { - return av_get_pix_fmt_name(choose_pixel_fmt(ost->st, ost->enc_ctx, ost->enc, ost->enc_ctx->pix_fmt)); - } else if (ost->enc && ost->enc->pix_fmts) { + return av_get_pix_fmt_name(choose_pixel_fmt(enc->codec, enc->pix_fmt, + ost->enc_ctx->strict_std_compliance)); + } else if (enc->codec->pix_fmts) { const enum AVPixelFormat *p; - p = ost->enc->pix_fmts; + p = enc->codec->pix_fmts; if (ost->enc_ctx->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) { - p = get_compliance_normal_pix_fmts(ost->enc, p); + p = get_compliance_normal_pix_fmts(enc->codec, p); } for (; *p != AV_PIX_FMT_NONE; p++) { @@ -133,7 +143,7 @@ static const char *choose_pix_fmts(OutputFilter *ofilter, AVBPrint *bprint) av_bprintf(bprint, "%s%c", name, p[1] == AV_PIX_FMT_NONE ? '\0' : '|'); } if (!av_bprint_is_complete(bprint)) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); return bprint->str; } else return NULL; @@ -197,7 +207,7 @@ int init_simple_filtergraph(InputStream *ist, OutputStream *ost) InputFilter *ifilter; if (!fg) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); fg->index = nb_filtergraphs; ofilter = ALLOC_ARRAY_ELEM(fg->outputs, fg->nb_outputs); @@ -214,7 +224,7 @@ int init_simple_filtergraph(InputStream *ist, OutputStream *ost) ifilter->frame_queue = av_fifo_alloc2(8, sizeof(AVFrame*), AV_FIFO_FLAG_AUTO_GROW); if (!ifilter->frame_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); GROW_ARRAY(ist->filters, ist->nb_filters); ist->filters[ist->nb_filters - 1] = ifilter; @@ -238,7 +248,7 @@ static char *describe_filter_link(FilterGraph *fg, AVFilterInOut *inout, int in) res = av_asprintf("%s:%s", ctx->filter->name, avfilter_pad_get_name(pads, inout->pad_idx)); if (!res) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); return res; } @@ -285,7 +295,7 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) "matches no streams.\n", p, fg->graph_desc); exit_program(1); } - ist = input_streams[input_files[file_idx]->ist_index + st->index]; + ist = input_files[file_idx]->streams[st->index]; if (ist->user_set_discard == AVDISCARD_ALL) { av_log(NULL, AV_LOG_FATAL, "Stream specifier '%s' in filtergraph description %s " "matches a disabled input stream.\n", p, fg->graph_desc); @@ -293,14 +303,13 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) } } else { /* find the first unused stream of corresponding type */ - for (i = 0; i < nb_input_streams; i++) { - ist = input_streams[i]; + for (ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { if (ist->user_set_discard == AVDISCARD_ALL) continue; if (ist->dec_ctx->codec_type == type && ist->discard) break; } - if (i == nb_input_streams) { + if (!ist) { av_log(NULL, AV_LOG_FATAL, "Cannot find a matching stream for " "unlabeled input pad %d on filter %s\n", in->pad_idx, in->filter_ctx->name); @@ -323,12 +332,162 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in) ifilter->frame_queue = av_fifo_alloc2(8, sizeof(AVFrame*), AV_FIFO_FLAG_AUTO_GROW); if (!ifilter->frame_queue) - exit_program(1); + report_and_exit(AVERROR(ENOMEM)); GROW_ARRAY(ist->filters, ist->nb_filters); ist->filters[ist->nb_filters - 1] = ifilter; } +static int read_binary(const char *path, uint8_t **data, int *len) +{ + AVIOContext *io = NULL; + int64_t fsize; + int ret; + + *data = NULL; + *len = 0; + + ret = avio_open2(&io, path, AVIO_FLAG_READ, &int_cb, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Cannot open file '%s': %s\n", + path, av_err2str(ret)); + return ret; + } + + fsize = avio_size(io); + if (fsize < 0 || fsize > INT_MAX) { + av_log(NULL, AV_LOG_ERROR, "Cannot obtain size of file %s\n", path); + ret = AVERROR(EIO); + goto fail; + } + + *data = av_malloc(fsize); + if (!*data) { + ret = AVERROR(ENOMEM); + goto fail; + } + + ret = avio_read(io, *data, fsize); + if (ret != fsize) { + av_log(NULL, AV_LOG_ERROR, "Error reading file %s\n", path); + ret = ret < 0 ? ret : AVERROR(EIO); + goto fail; + } + + *len = fsize; + + return 0; +fail: + avio_close(io); + av_freep(data); + *len = 0; + return ret; +} + +static int filter_opt_apply(AVFilterContext *f, const char *key, const char *val) +{ + const AVOption *o = NULL; + int ret; + + ret = av_opt_set(f, key, val, AV_OPT_SEARCH_CHILDREN); + if (ret >= 0) + return 0; + + if (ret == AVERROR_OPTION_NOT_FOUND && key[0] == '/') + o = av_opt_find(f, key + 1, NULL, 0, AV_OPT_SEARCH_CHILDREN); + if (!o) + goto err_apply; + + // key is a valid option name prefixed with '/' + // interpret value as a path from which to load the actual option value + key++; + + if (o->type == AV_OPT_TYPE_BINARY) { + uint8_t *data; + int len; + + ret = read_binary(val, &data, &len); + if (ret < 0) + goto err_load; + + ret = av_opt_set_bin(f, key, data, len, AV_OPT_SEARCH_CHILDREN); + av_freep(&data); + } else { + char *data = file_read(val); + if (!data) { + ret = AVERROR(EIO); + goto err_load; + } + + ret = av_opt_set(f, key, data, AV_OPT_SEARCH_CHILDREN); + av_freep(&data); + } + if (ret < 0) + goto err_apply; + + return 0; + +err_apply: + av_log(NULL, AV_LOG_ERROR, + "Error applying option '%s' to filter '%s': %s\n", + key, f->filter->name, av_err2str(ret)); + return ret; +err_load: + av_log(NULL, AV_LOG_ERROR, + "Error loading value for option '%s' from file '%s'\n", + key, val); + return ret; +} + +static int graph_opts_apply(AVFilterGraphSegment *seg) +{ + for (size_t i = 0; i < seg->nb_chains; i++) { + AVFilterChain *ch = seg->chains[i]; + + for (size_t j = 0; j < ch->nb_filters; j++) { + AVFilterParams *p = ch->filters[j]; + const AVDictionaryEntry *e = NULL; + + av_assert0(p->filter); + + while ((e = av_dict_iterate(p->opts, e))) { + int ret = filter_opt_apply(p->filter, e->key, e->value); + if (ret < 0) + return ret; + } + + av_dict_free(&p->opts); + } + } + + return 0; +} + +static int graph_parse(AVFilterGraph *graph, const char *desc, + AVFilterInOut **inputs, AVFilterInOut **outputs) +{ + AVFilterGraphSegment *seg; + int ret; + + ret = avfilter_graph_segment_parse(graph, desc, 0, &seg); + if (ret < 0) + return ret; + + ret = avfilter_graph_segment_create_filters(seg, 0); + if (ret < 0) + goto fail; + + ret = graph_opts_apply(seg); + if (ret < 0) + goto fail; + + ret = avfilter_graph_segment_apply(seg, 0, inputs, outputs); + +fail: + avfilter_graph_segment_free(&seg); + return ret; +} + int init_complex_filtergraph(FilterGraph *fg) { AVFilterInOut *inputs, *outputs, *cur; @@ -342,7 +501,7 @@ int init_complex_filtergraph(FilterGraph *fg) return AVERROR(ENOMEM); graph->nb_threads = 1; - ret = avfilter_graph_parse2(graph, fg->graph_desc, &inputs, &outputs); + ret = graph_parse(graph, fg->graph_desc, &inputs, &outputs); if (ret < 0) goto fail; @@ -467,8 +626,7 @@ static int configure_output_video_filter(FilterGraph *fg, OutputFilter *ofilter, snprintf(args, sizeof(args), "%d:%d", ofilter->width, ofilter->height); - while ((e = av_dict_get(ost->sws_dict, "", e, - AV_DICT_IGNORE_SUFFIX))) { + while ((e = av_dict_iterate(ost->sws_dict, e))) { av_strlcatf(args, sizeof(args), ":%s=%s", e->key, e->value); } @@ -575,6 +733,7 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, pad_idx = 0; \ } while (0) av_bprint_init(&args, 0, AV_BPRINT_SIZE_UNLIMITED); +#if FFMPEG_OPT_MAP_CHANNEL if (ost->audio_channels_mapped) { AVChannelLayout mapped_layout = { 0 }; int i; @@ -587,6 +746,7 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, AUTO_INSERT_FILTER("-map_channel", "pan", args.str); av_bprint_clear(&args); } +#endif if (codec->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) av_channel_layout_default(&codec->ch_layout, codec->ch_layout.nb_channels); @@ -620,11 +780,11 @@ static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, if (ost->apad && of->shortest) { int i; - for (i=0; ictx->nb_streams; i++) - if (of->ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + for (i = 0; i < of->nb_streams; i++) + if (of->streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) break; - if (ictx->nb_streams) { + if (i < of->nb_streams) { AUTO_INSERT_FILTER("-apad", "apad", ost->apad); } } @@ -751,7 +911,7 @@ static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter, } if (!fr.num) - fr = av_guess_frame_rate(input_files[ist->file_index]->ctx, ist->st, NULL); + fr = ist->framerate_guessed; if (ist->dec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { ret = sub2video_prepare(ist, ifilter); @@ -904,40 +1064,6 @@ static int configure_input_audio_filter(FilterGraph *fg, InputFilter *ifilter, last_filter = filt_ctx; \ } while (0) - if (audio_sync_method > 0) { - char args[256] = {0}; - - av_strlcatf(args, sizeof(args), "async=%d", audio_sync_method); - if (audio_drift_threshold != 0.1) - av_strlcatf(args, sizeof(args), ":min_hard_comp=%f", audio_drift_threshold); - if (!fg->reconfiguration) - av_strlcatf(args, sizeof(args), ":first_pts=0"); - AUTO_INSERT_FILTER_INPUT("-async", "aresample", args); - } - -// if (ost->audio_channels_mapped) { -// int i; -// AVBPrint pan_buf; -// av_bprint_init(&pan_buf, 256, 8192); -// av_bprintf(&pan_buf, "0x%"PRIx64, -// av_get_default_channel_layout(ost->audio_channels_mapped)); -// for (i = 0; i < ost->audio_channels_mapped; i++) -// if (ost->audio_channels_map[i] != -1) -// av_bprintf(&pan_buf, ":c%d=c%d", i, ost->audio_channels_map[i]); -// AUTO_INSERT_FILTER_INPUT("-map_channel", "pan", pan_buf.str); -// av_bprint_finalize(&pan_buf, NULL); -// } - - if (audio_volume != 256) { - char args[256]; - - av_log(NULL, AV_LOG_WARNING, "-vol has been deprecated. Use the volume " - "audio filter instead.\n"); - - snprintf(args, sizeof(args), "%f", audio_volume / 256.); - AUTO_INSERT_FILTER_INPUT("-vol", "volume", args); - } - snprintf(name, sizeof(name), "trim for input stream %d:%d", ist->file_index, ist->st->index); if (copy_ts) { @@ -1020,44 +1146,39 @@ int configure_filtergraph(FilterGraph *fg) if (simple) { OutputStream *ost = fg->outputs[0]->ost; - char args[512]; - const AVDictionaryEntry *e = NULL; if (filter_nbthreads) { ret = av_opt_set(fg->graph, "threads", filter_nbthreads, 0); if (ret < 0) goto fail; } else { + const AVDictionaryEntry *e = NULL; e = av_dict_get(ost->encoder_opts, "threads", NULL, 0); if (e) av_opt_set(fg->graph, "threads", e->value, 0); } - args[0] = 0; - e = NULL; - while ((e = av_dict_get(ost->sws_dict, "", e, - AV_DICT_IGNORE_SUFFIX))) { - av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); - } - if (strlen(args)) { - args[strlen(args)-1] = 0; - fg->graph->scale_sws_opts = av_strdup(args); + if (av_dict_count(ost->sws_dict)) { + ret = av_dict_get_string(ost->sws_dict, + &fg->graph->scale_sws_opts, + '=', ':'); + if (ret < 0) + goto fail; } - args[0] = 0; - e = NULL; - while ((e = av_dict_get(ost->swr_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) { - av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value); + if (av_dict_count(ost->swr_opts)) { + char *args; + ret = av_dict_get_string(ost->swr_opts, &args, '=', ':'); + if (ret < 0) + goto fail; + av_opt_set(fg->graph, "aresample_swr_opts", args, 0); + av_free(args); } - if (strlen(args)) - args[strlen(args)-1] = 0; - av_opt_set(fg->graph, "aresample_swr_opts", args, 0); } else { fg->graph->nb_threads = filter_complex_nbthreads; } - if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0) + if ((ret = graph_parse(fg->graph, graph_desc, &inputs, &outputs)) < 0) goto fail; ret = hw_device_setup_for_filter(fg); @@ -1131,16 +1252,8 @@ int configure_filtergraph(FilterGraph *fg) for (i = 0; i < fg->nb_outputs; i++) { OutputStream *ost = fg->outputs[i]->ost; - if (!ost->enc) { - /* identical to the same check in ffmpeg.c, needed because - complex filter graphs are initialized earlier */ - av_log(NULL, AV_LOG_ERROR, "Encoder (codec %s) not found for output stream #%d:%d\n", - avcodec_get_name(ost->st->codecpar->codec_id), ost->file_index, ost->index); - ret = AVERROR(EINVAL); - goto fail; - } - if (ost->enc->type == AVMEDIA_TYPE_AUDIO && - !(ost->enc->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + if (ost->enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO && + !(ost->enc_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) av_buffersink_set_frame_size(ost->filter->filter, ost->enc_ctx->frame_size); } diff --git a/linux/src/fftools_ffmpeg_hw.c b/linux/src/fftools_ffmpeg_hw.c index 8e5427a..42f16ec 100644 --- a/linux/src/fftools_ffmpeg_hw.c +++ b/linux/src/fftools_ffmpeg_hw.c @@ -1,5 +1,6 @@ /* - * Copyright (c) 2018 Taner Sener + * Copyright (c) 2018-2019 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -23,6 +24,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 12.2019 @@ -357,7 +364,7 @@ int hw_device_setup_for_decode(InputStream *ist) if (ist->hwaccel_id == HWACCEL_AUTO) { ist->hwaccel_device_type = dev->type; } else if (ist->hwaccel_device_type != dev->type) { - av_log(ist->dec_ctx, AV_LOG_ERROR, "Invalid hwaccel device " + av_log(NULL, AV_LOG_ERROR, "Invalid hwaccel device " "specified for decoder: device %s of type %s is not " "usable with hwaccel %s.\n", dev->name, av_hwdevice_get_type_name(dev->type), @@ -408,7 +415,7 @@ int hw_device_setup_for_decode(InputStream *ist) type = config->device_type; dev = hw_device_get_by_type(type); if (dev) { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with existing device %s.\n", av_hwdevice_get_type_name(type), dev->name); } @@ -426,12 +433,12 @@ int hw_device_setup_for_decode(InputStream *ist) continue; } if (ist->hwaccel_device) { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with new device created " "from %s.\n", av_hwdevice_get_type_name(type), ist->hwaccel_device); } else { - av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto " + av_log(NULL, AV_LOG_INFO, "Using auto " "hwaccel type %s with new default device.\n", av_hwdevice_get_type_name(type)); } @@ -439,7 +446,7 @@ int hw_device_setup_for_decode(InputStream *ist) if (dev) { ist->hwaccel_device_type = type; } else { - av_log(ist->dec_ctx, AV_LOG_INFO, "Auto hwaccel " + av_log(NULL, AV_LOG_INFO, "Auto hwaccel " "disabled: no device found.\n"); ist->hwaccel_id = HWACCEL_NONE; return 0; @@ -447,7 +454,7 @@ int hw_device_setup_for_decode(InputStream *ist) } if (!dev) { - av_log(ist->dec_ctx, AV_LOG_ERROR, "No device available " + av_log(NULL, AV_LOG_ERROR, "No device available " "for decoder: device type %s needed for codec %s.\n", av_hwdevice_get_type_name(type), ist->dec->name); return err; @@ -479,7 +486,7 @@ int hw_device_setup_for_encode(OutputStream *ost) } for (i = 0;; i++) { - config = avcodec_get_hw_config(ost->enc, i); + config = avcodec_get_hw_config(ost->enc_ctx->codec, i); if (!config) break; @@ -490,7 +497,7 @@ int hw_device_setup_for_encode(OutputStream *ost) av_log(ost->enc_ctx, AV_LOG_VERBOSE, "Using input " "frames context (format %s) with %s encoder.\n", av_get_pix_fmt_name(ost->enc_ctx->pix_fmt), - ost->enc->name); + ost->enc_ctx->codec->name); ost->enc_ctx->hw_frames_ctx = av_buffer_ref(frames_ref); if (!ost->enc_ctx->hw_frames_ctx) return AVERROR(ENOMEM); @@ -505,7 +512,7 @@ int hw_device_setup_for_encode(OutputStream *ost) if (dev) { av_log(ost->enc_ctx, AV_LOG_VERBOSE, "Using device %s " "(type %s) with %s encoder.\n", dev->name, - av_hwdevice_get_type_name(dev->type), ost->enc->name); + av_hwdevice_get_type_name(dev->type), ost->enc_ctx->codec->name); ost->enc_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref); if (!ost->enc_ctx->hw_device_ctx) return AVERROR(ENOMEM); diff --git a/linux/src/fftools_ffmpeg_mux.c b/linux/src/fftools_ffmpeg_mux.c index 9728feb..0a3d1e7 100644 --- a/linux/src/fftools_ffmpeg_mux.c +++ b/linux/src/fftools_ffmpeg_mux.c @@ -1,6 +1,7 @@ /* * This file is part of FFmpeg. * Copyright (c) 2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,108 +23,101 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop the ffmpeg-kit library. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - want_sdp marked as thread-local + * - ms_from_ost migrated from ffmpeg_mux.c and marked as non-static + * * ffmpeg-kit changes by Taner Sener * * 09.2022 * -------------------------------------------------------- * - fftools_ prefix added to fftools headers * - using main_ffmpeg_return_code instead of main_return_code + * - printf replaced with av_log statements */ +#include #include #include #include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" +#include "fftools_objpool.h" +#include "fftools_sync_queue.h" +#include "fftools_thread_queue.h" #include "libavutil/fifo.h" #include "libavutil/intreadwrite.h" #include "libavutil/log.h" #include "libavutil/mem.h" #include "libavutil/timestamp.h" +#include "libavutil/thread.h" #include "libavcodec/packet.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" -static void close_all_output_streams(OutputStream *ost, OSTFinished this_stream, OSTFinished others) +__thread int want_sdp = 1; + +MuxStream *ms_from_ost(OutputStream *ost) { - int i; - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost2 = output_streams[i]; - ost2->finished |= ost == ost2 ? this_stream : others; + return (MuxStream*)ost; +} + +static Muxer *mux_from_of(OutputFile *of) +{ + return (Muxer*)of; +} + +static int64_t filesize(AVIOContext *pb) +{ + int64_t ret = -1; + + if (pb) { + ret = avio_size(pb); + if (ret <= 0) // FIXME improve avio_size() so it works with non seekable output too + ret = avio_tell(pb); } + + return ret; } -void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, - int unqueue) +static int write_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) { - AVFormatContext *s = of->ctx; + MuxStream *ms = ms_from_ost(ost); + AVFormatContext *s = mux->fc; AVStream *st = ost->st; + int64_t fs; + uint64_t frame_num; int ret; - /* - * Audio encoders may split the packets -- #frames in != #packets out. - * But there is no reordering, so we can limit the number of output packets - * by simply dropping them here. - * Counting encoded video frames needs to be done separately because of - * reordering, see do_video_out(). - * Do not count the packet when unqueued because it has been counted when queued. - */ - if (!(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->encoding_needed) && !unqueue) { - if (ost->frame_number >= ost->max_frames) { - av_packet_unref(pkt); - return; - } - ost->frame_number++; - } - - if (!of->header_written) { - AVPacket *tmp_pkt; - /* the muxer is not initialized yet, buffer the packet */ - if (!av_fifo_can_write(ost->muxing_queue)) { - size_t cur_size = av_fifo_can_read(ost->muxing_queue); - unsigned int are_we_over_size = - (ost->muxing_queue_data_size + pkt->size) > ost->muxing_queue_data_threshold; - size_t limit = are_we_over_size ? ost->max_muxing_queue_size : SIZE_MAX; - size_t new_size = FFMIN(2 * cur_size, limit); - - if (new_size <= cur_size) { - av_log(NULL, AV_LOG_ERROR, - "Too many packets buffered for output stream %d:%d.\n", - ost->file_index, ost->st->index); - exit_program(1); - } - ret = av_fifo_grow2(ost->muxing_queue, new_size - cur_size); - if (ret < 0) - exit_program(1); - } - ret = av_packet_make_refcounted(pkt); - if (ret < 0) - exit_program(1); - tmp_pkt = av_packet_alloc(); - if (!tmp_pkt) - exit_program(1); - av_packet_move_ref(tmp_pkt, pkt); - ost->muxing_queue_data_size += tmp_pkt->size; - av_fifo_write(ost->muxing_queue, &tmp_pkt, 1); - return; + fs = filesize(s->pb); + atomic_store(&mux->last_filesize, fs); + if (fs >= mux->limit_filesize) { + ret = AVERROR_EOF; + goto fail; } - if ((st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->vsync_method == VSYNC_DROP) || - (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_sync_method < 0)) + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ost->vsync_method == VSYNC_DROP) pkt->pts = pkt->dts = AV_NOPTS_VALUE; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (ost->frame_rate.num && ost->is_cfr) { if (pkt->duration > 0) - av_log(NULL, AV_LOG_WARNING, "Overriding packet duration by frame rate, this should not happen\n"); + av_log(ost, AV_LOG_WARNING, "Overriding packet duration by frame rate, this should not happen\n"); pkt->duration = av_rescale_q(1, av_inv_q(ost->frame_rate), - ost->mux_timebase); + pkt->time_base); } } - av_packet_rescale_ts(pkt, ost->mux_timebase, ost->st->time_base); + av_packet_rescale_ts(pkt, pkt->time_base, ost->st->time_base); + pkt->time_base = ost->st->time_base; if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) { if (pkt->dts != AV_NOPTS_VALUE && @@ -133,25 +127,26 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, pkt->dts, pkt->pts, ost->file_index, ost->st->index); pkt->pts = - pkt->dts = pkt->pts + pkt->dts + ost->last_mux_dts + 1 - - FFMIN3(pkt->pts, pkt->dts, ost->last_mux_dts + 1) - - FFMAX3(pkt->pts, pkt->dts, ost->last_mux_dts + 1); + pkt->dts = pkt->pts + pkt->dts + ms->last_mux_dts + 1 + - FFMIN3(pkt->pts, pkt->dts, ms->last_mux_dts + 1) + - FFMAX3(pkt->pts, pkt->dts, ms->last_mux_dts + 1); } if ((st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) && pkt->dts != AV_NOPTS_VALUE && - ost->last_mux_dts != AV_NOPTS_VALUE) { - int64_t max = ost->last_mux_dts + !(s->oformat->flags & AVFMT_TS_NONSTRICT); + ms->last_mux_dts != AV_NOPTS_VALUE) { + int64_t max = ms->last_mux_dts + !(s->oformat->flags & AVFMT_TS_NONSTRICT); if (pkt->dts < max) { int loglevel = max - pkt->dts > 2 || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? AV_LOG_WARNING : AV_LOG_DEBUG; if (exit_on_error) loglevel = AV_LOG_ERROR; av_log(s, loglevel, "Non-monotonous DTS in output stream " "%d:%d; previous: %"PRId64", current: %"PRId64"; ", - ost->file_index, ost->st->index, ost->last_mux_dts, pkt->dts); + ost->file_index, ost->st->index, ms->last_mux_dts, pkt->dts); if (exit_on_error) { - av_log(NULL, AV_LOG_FATAL, "aborting.\n"); - exit_program(1); + ret = AVERROR(EINVAL); + goto fail; } + av_log(s, loglevel, "changing to %"PRId64". This may result " "in incorrect timestamps in the output file.\n", max); @@ -161,17 +156,17 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, } } } - ost->last_mux_dts = pkt->dts; + ms->last_mux_dts = pkt->dts; - ost->data_size += pkt->size; - ost->packets_written++; + ost->data_size_mux += pkt->size; + frame_num = atomic_fetch_add(&ost->packets_written, 1); pkt->stream_index = ost->index; if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "muxer <- type:%s " + av_log(ost, AV_LOG_INFO, "muxer <- type:%s " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s duration:%s duration_time:%s size:%d\n", - av_get_media_type_string(ost->enc_ctx->codec_type), + av_get_media_type_string(st->codecpar->codec_type), av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ost->st->time_base), av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ost->st->time_base), av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &ost->st->time_base), @@ -179,12 +174,307 @@ void of_write_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, ); } + if (ms->stats.io) + enc_stats_write(ost, &ms->stats, NULL, pkt, frame_num); + ret = av_interleaved_write_frame(s, pkt); if (ret < 0) { print_error("av_interleaved_write_frame()", ret); - main_ffmpeg_return_code = 1; - close_all_output_streams(ost, MUXER_FINISHED | ENCODER_FINISHED, ENCODER_FINISHED); + goto fail; } + + return 0; +fail: + av_packet_unref(pkt); + return ret; +} + +static int sync_queue_process(Muxer *mux, OutputStream *ost, AVPacket *pkt, int *stream_eof) +{ + OutputFile *of = &mux->of; + + if (ost->sq_idx_mux >= 0) { + int ret = sq_send(mux->sq_mux, ost->sq_idx_mux, SQPKT(pkt)); + if (ret < 0) { + if (ret == AVERROR_EOF) + *stream_eof = 1; + + return ret; + } + + while (1) { + ret = sq_receive(mux->sq_mux, -1, SQPKT(mux->sq_pkt)); + if (ret < 0) + return (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) ? 0 : ret; + + ret = write_packet(mux, of->streams[ret], + mux->sq_pkt); + if (ret < 0) + return ret; + } + } else if (pkt) + return write_packet(mux, ost, pkt); + + return 0; +} + +static void thread_set_name(OutputFile *of) +{ + char name[16]; + snprintf(name, sizeof(name), "mux%d:%s", of->index, of->format->name); + ff_thread_setname(name); +} + +static void *muxer_thread(void *arg) +{ + Muxer *mux = arg; + OutputFile *of = &mux->of; + AVPacket *pkt = NULL; + int ret = 0; + + pkt = av_packet_alloc(); + if (!pkt) { + ret = AVERROR(ENOMEM); + goto finish; + } + + thread_set_name(of); + + while (1) { + OutputStream *ost; + int stream_idx, stream_eof = 0; + + ret = tq_receive(mux->tq, &stream_idx, pkt); + if (stream_idx < 0) { + av_log(mux, AV_LOG_VERBOSE, "All streams finished\n"); + ret = 0; + break; + } + + ost = of->streams[stream_idx]; + ret = sync_queue_process(mux, ost, ret < 0 ? NULL : pkt, &stream_eof); + av_packet_unref(pkt); + if (ret == AVERROR_EOF && stream_eof) + tq_receive_finish(mux->tq, stream_idx); + else if (ret < 0) { + av_log(mux, AV_LOG_ERROR, "Error muxing a packet\n"); + break; + } + } + +finish: + av_packet_free(&pkt); + + for (unsigned int i = 0; i < mux->fc->nb_streams; i++) + tq_receive_finish(mux->tq, i); + + av_log(mux, AV_LOG_VERBOSE, "Terminating muxer thread\n"); + + return (void*)(intptr_t)ret; +} + +static int thread_submit_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) +{ + int ret = 0; + + if (!pkt || ost->finished & MUXER_FINISHED) + goto finish; + + ret = tq_send(mux->tq, ost->index, pkt); + if (ret < 0) + goto finish; + + return 0; + +finish: + if (pkt) + av_packet_unref(pkt); + + ost->finished |= MUXER_FINISHED; + tq_send_finish(mux->tq, ost->index); + return ret == AVERROR_EOF ? 0 : ret; +} + +static int queue_packet(Muxer *mux, OutputStream *ost, AVPacket *pkt) +{ + MuxStream *ms = ms_from_ost(ost); + AVPacket *tmp_pkt = NULL; + int ret; + + if (!av_fifo_can_write(ms->muxing_queue)) { + size_t cur_size = av_fifo_can_read(ms->muxing_queue); + size_t pkt_size = pkt ? pkt->size : 0; + unsigned int are_we_over_size = + (ms->muxing_queue_data_size + pkt_size) > ms->muxing_queue_data_threshold; + size_t limit = are_we_over_size ? ms->max_muxing_queue_size : SIZE_MAX; + size_t new_size = FFMIN(2 * cur_size, limit); + + if (new_size <= cur_size) { + av_log(ost, AV_LOG_ERROR, + "Too many packets buffered for output stream %d:%d.\n", + ost->file_index, ost->st->index); + return AVERROR(ENOSPC); + } + ret = av_fifo_grow2(ms->muxing_queue, new_size - cur_size); + if (ret < 0) + return ret; + } + + if (pkt) { + ret = av_packet_make_refcounted(pkt); + if (ret < 0) + return ret; + + tmp_pkt = av_packet_alloc(); + if (!tmp_pkt) + return AVERROR(ENOMEM); + + av_packet_move_ref(tmp_pkt, pkt); + ms->muxing_queue_data_size += tmp_pkt->size; + } + av_fifo_write(ms->muxing_queue, &tmp_pkt, 1); + + return 0; +} + +static int submit_packet(Muxer *mux, AVPacket *pkt, OutputStream *ost) +{ + int ret; + + if (mux->tq) { + return thread_submit_packet(mux, ost, pkt); + } else { + /* the muxer is not initialized yet, buffer the packet */ + ret = queue_packet(mux, ost, pkt); + if (ret < 0) { + if (pkt) + av_packet_unref(pkt); + return ret; + } + } + + return 0; +} + +void of_output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof) +{ + Muxer *mux = mux_from_of(of); + MuxStream *ms = ms_from_ost(ost); + const char *err_msg; + int ret = 0; + + if (!eof && pkt->dts != AV_NOPTS_VALUE) + ost->last_mux_dts = av_rescale_q(pkt->dts, pkt->time_base, AV_TIME_BASE_Q); + + /* apply the output bitstream filters */ + if (ms->bsf_ctx) { + int bsf_eof = 0; + + ret = av_bsf_send_packet(ms->bsf_ctx, eof ? NULL : pkt); + if (ret < 0) { + err_msg = "submitting a packet for bitstream filtering"; + goto fail; + } + + while (!bsf_eof) { + ret = av_bsf_receive_packet(ms->bsf_ctx, pkt); + if (ret == AVERROR(EAGAIN)) + return; + else if (ret == AVERROR_EOF) + bsf_eof = 1; + else if (ret < 0) { + err_msg = "applying bitstream filters to a packet"; + goto fail; + } + + ret = submit_packet(mux, bsf_eof ? NULL : pkt, ost); + if (ret < 0) + goto mux_fail; + } + } else { + ret = submit_packet(mux, eof ? NULL : pkt, ost); + if (ret < 0) + goto mux_fail; + } + + return; + +mux_fail: + err_msg = "submitting a packet to the muxer"; + +fail: + av_log(ost, AV_LOG_ERROR, "Error %s\n", err_msg); + if (exit_on_error) + exit_program(1); + +} + +static int thread_stop(Muxer *mux) +{ + void *ret; + + if (!mux || !mux->tq) + return 0; + + for (unsigned int i = 0; i < mux->fc->nb_streams; i++) + tq_send_finish(mux->tq, i); + + pthread_join(mux->thread, &ret); + + tq_free(&mux->tq); + + return (int)(intptr_t)ret; +} + +static void pkt_move(void *dst, void *src) +{ + av_packet_move_ref(dst, src); +} + +static int thread_start(Muxer *mux) +{ + AVFormatContext *fc = mux->fc; + ObjPool *op; + int ret; + + op = objpool_alloc_packets(); + if (!op) + return AVERROR(ENOMEM); + + mux->tq = tq_alloc(fc->nb_streams, mux->thread_queue_size, op, pkt_move); + if (!mux->tq) { + objpool_free(&op); + return AVERROR(ENOMEM); + } + + ret = pthread_create(&mux->thread, NULL, muxer_thread, (void*)mux); + if (ret) { + tq_free(&mux->tq); + return AVERROR(ret); + } + + /* flush the muxing queues */ + for (int i = 0; i < fc->nb_streams; i++) { + OutputStream *ost = mux->of.streams[i]; + MuxStream *ms = ms_from_ost(ost); + AVPacket *pkt; + + /* try to improve muxing time_base (only possible if nothing has been written yet) */ + if (!av_fifo_can_read(ms->muxing_queue)) + ost->mux_timebase = ost->st->time_base; + + while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0) { + ret = thread_submit_packet(mux, ost, pkt); + if (pkt) { + ms->muxing_queue_data_size -= pkt->size; + av_packet_free(&pkt); + } + if (ret < 0) + return ret; + } + } + + return 0; } static int print_sdp(void) @@ -196,16 +486,16 @@ static int print_sdp(void) AVFormatContext **avc; for (i = 0; i < nb_output_files; i++) { - if (!output_files[i]->header_written) + if (!mux_from_of(output_files[i])->header_written) return 0; } avc = av_malloc_array(nb_output_files, sizeof(*avc)); if (!avc) - exit_program(1); + return AVERROR(ENOMEM); for (i = 0, j = 0; i < nb_output_files; i++) { - if (!strcmp(output_files[i]->ctx->oformat->name, "rtp")) { - avc[j] = output_files[i]->ctx; + if (!strcmp(output_files[i]->format->name, "rtp")) { + avc[j] = mux_from_of(output_files[i])->fc; j++; } } @@ -221,7 +511,7 @@ static int print_sdp(void) goto fail; if (!sdp_filename) { - printf("SDP:\n%s\n", sdp); + av_log(NULL, AV_LOG_ERROR, "SDP:\n%s\n", sdp); fflush(stdout); } else { ret = avio_open2(&sdp_pb, sdp_filename, AVIO_FLAG_WRITE, &int_cb, NULL); @@ -235,34 +525,36 @@ static int print_sdp(void) av_freep(&sdp_filename); } + // SDP successfully written, allow muxer threads to start + ret = 1; + fail: av_freep(&avc); return ret; } -/* open the muxer when all the streams are initialized */ -int of_check_init(OutputFile *of) +int mux_check_init(Muxer *mux) { + OutputFile *of = &mux->of; + AVFormatContext *fc = mux->fc; int ret, i; - for (i = 0; i < of->ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; + for (i = 0; i < fc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; if (!ost->initialized) return 0; } - ret = avformat_write_header(of->ctx, &of->opts); + ret = avformat_write_header(fc, &mux->opts); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, - "Could not write header for output file #%d " - "(incorrect codec parameters ?): %s\n", - of->index, av_err2str(ret)); + av_log(mux, AV_LOG_ERROR, "Could not write header (incorrect codec " + "parameters ?): %s\n", av_err2str(ret)); return ret; } //assert_avoptions(of->opts); - of->header_written = 1; + mux->header_written = 1; - av_dump_format(of->ctx, of->index, of->ctx->url, 1); + av_dump_format(fc, of->index, fc->url, 1); nb_output_dumped++; if (sdp_filename || want_sdp) { @@ -270,62 +562,220 @@ int of_check_init(OutputFile *of) if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error writing the SDP.\n"); return ret; + } else if (ret == 1) { + /* SDP is written only after all the muxers are ready, so now we + * start ALL the threads */ + for (i = 0; i < nb_output_files; i++) { + ret = thread_start(mux_from_of(output_files[i])); + if (ret < 0) + return ret; + } } + } else { + ret = thread_start(mux_from_of(of)); + if (ret < 0) + return ret; } - /* flush the muxing queues */ - for (i = 0; i < of->ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - AVPacket *pkt; + return 0; +} - /* try to improve muxing time_base (only possible if nothing has been written yet) */ - if (!av_fifo_can_read(ost->muxing_queue)) - ost->mux_timebase = ost->st->time_base; +static int bsf_init(MuxStream *ms) +{ + OutputStream *ost = &ms->ost; + AVBSFContext *ctx = ms->bsf_ctx; + int ret; - while (av_fifo_read(ost->muxing_queue, &pkt, 1) >= 0) { - ost->muxing_queue_data_size -= pkt->size; - of_write_packet(of, pkt, ost, 1); - av_packet_free(&pkt); - } + if (!ctx) + return 0; + + ret = avcodec_parameters_copy(ctx->par_in, ost->st->codecpar); + if (ret < 0) + return ret; + + ctx->time_base_in = ost->st->time_base; + + ret = av_bsf_init(ctx); + if (ret < 0) { + av_log(ms, AV_LOG_ERROR, "Error initializing bitstream filter: %s\n", + ctx->filter->name); + return ret; } + ret = avcodec_parameters_copy(ost->st->codecpar, ctx->par_out); + if (ret < 0) + return ret; + ost->st->time_base = ctx->time_base_out; + return 0; } +int of_stream_init(OutputFile *of, OutputStream *ost) +{ + Muxer *mux = mux_from_of(of); + MuxStream *ms = ms_from_ost(ost); + int ret; + + if (ost->sq_idx_mux >= 0) + sq_set_tb(mux->sq_mux, ost->sq_idx_mux, ost->mux_timebase); + + /* initialize bitstream filters for the output stream + * needs to be done here, because the codec id for streamcopy is not + * known until now */ + ret = bsf_init(ms); + if (ret < 0) + return ret; + + ost->initialized = 1; + + return mux_check_init(mux); +} + int of_write_trailer(OutputFile *of) { + Muxer *mux = mux_from_of(of); + AVFormatContext *fc = mux->fc; int ret; - if (!of->header_written) { - av_log(NULL, AV_LOG_ERROR, - "Nothing was written into output file %d (%s), because " - "at least one of its streams received no packets.\n", - of->index, of->ctx->url); + if (!mux->tq) { + av_log(mux, AV_LOG_ERROR, + "Nothing was written into output file, because " + "at least one of its streams received no packets.\n"); return AVERROR(EINVAL); } - ret = av_write_trailer(of->ctx); + ret = thread_stop(mux); + if (ret < 0) + main_ffmpeg_return_code = ret; + + ret = av_write_trailer(fc); if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error writing trailer of %s: %s\n", of->ctx->url, av_err2str(ret)); + av_log(mux, AV_LOG_ERROR, "Error writing trailer: %s\n", av_err2str(ret)); return ret; } + mux->last_filesize = filesize(fc->pb); + + if (!(of->format->flags & AVFMT_NOFILE)) { + ret = avio_closep(&fc->pb); + if (ret < 0) { + av_log(mux, AV_LOG_ERROR, "Error closing file: %s\n", av_err2str(ret)); + return ret; + } + } + return 0; } +static void ost_free(OutputStream **post) +{ + OutputStream *ost = *post; + MuxStream *ms; + + if (!ost) + return; + ms = ms_from_ost(ost); + + if (ost->logfile) { + if (fclose(ost->logfile)) + av_log(ms, AV_LOG_ERROR, + "Error closing logfile, loss of information possible: %s\n", + av_err2str(AVERROR(errno))); + ost->logfile = NULL; + } + + if (ms->muxing_queue) { + AVPacket *pkt; + while (av_fifo_read(ms->muxing_queue, &pkt, 1) >= 0) + av_packet_free(&pkt); + av_fifo_freep2(&ms->muxing_queue); + } + + av_bsf_free(&ms->bsf_ctx); + + av_frame_free(&ost->filtered_frame); + av_frame_free(&ost->sq_frame); + av_frame_free(&ost->last_frame); + av_packet_free(&ost->pkt); + av_dict_free(&ost->encoder_opts); + + av_freep(&ost->kf.pts); + av_expr_free(ost->kf.pexpr); + + av_freep(&ost->avfilter); + av_freep(&ost->logfile_prefix); + av_freep(&ost->apad); + +#if FFMPEG_OPT_MAP_CHANNEL + av_freep(&ost->audio_channels_map); + ost->audio_channels_mapped = 0; +#endif + + av_dict_free(&ost->sws_dict); + av_dict_free(&ost->swr_opts); + + if (ost->enc_ctx) + av_freep(&ost->enc_ctx->stats_in); + avcodec_free_context(&ost->enc_ctx); + + for (int i = 0; i < ost->enc_stats_pre.nb_components; i++) + av_freep(&ost->enc_stats_pre.components[i].str); + av_freep(&ost->enc_stats_pre.components); + + for (int i = 0; i < ost->enc_stats_post.nb_components; i++) + av_freep(&ost->enc_stats_post.components[i].str); + av_freep(&ost->enc_stats_post.components); + + for (int i = 0; i < ms->stats.nb_components; i++) + av_freep(&ms->stats.components[i].str); + av_freep(&ms->stats.components); + + av_freep(post); +} + +static void fc_close(AVFormatContext **pfc) +{ + AVFormatContext *fc = *pfc; + + if (!fc) + return; + + if (!(fc->oformat->flags & AVFMT_NOFILE)) + avio_closep(&fc->pb); + avformat_free_context(fc); + + *pfc = NULL; +} + void of_close(OutputFile **pof) { OutputFile *of = *pof; - AVFormatContext *s; + Muxer *mux; if (!of) return; + mux = mux_from_of(of); + + thread_stop(mux); - s = of->ctx; - if (s && s->oformat && !(s->oformat->flags & AVFMT_NOFILE)) - avio_closep(&s->pb); - avformat_free_context(s); - av_dict_free(&of->opts); + sq_free(&of->sq_encode); + sq_free(&mux->sq_mux); + + for (int i = 0; i < of->nb_streams; i++) + ost_free(&of->streams[i]); + av_freep(&of->streams); + + av_dict_free(&mux->opts); + + av_packet_free(&mux->sq_pkt); + + fc_close(&mux->fc); av_freep(pof); } + +int64_t of_filesize(OutputFile *of) +{ + Muxer *mux = mux_from_of(of); + return atomic_load(&mux->last_filesize); +} diff --git a/linux/src/fftools_ffmpeg_mux.h b/linux/src/fftools_ffmpeg_mux.h new file mode 100644 index 0000000..c1c7abd --- /dev/null +++ b/linux/src/fftools_ffmpeg_mux.h @@ -0,0 +1,165 @@ +/* + * Muxer internal APIs - should not be included outside of ffmpeg_mux* + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_mux.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - want_sdp made thread-local + * - EncStatsFile declaration migrated from ffmpeg_mux_init.c + * - WARN_MULTIPLE_OPT_USAGE, MATCH_PER_STREAM_OPT, MATCH_PER_TYPE_OPT, SPECIFIER_OPT_FMT declarations migrated from + * ffmpeg.h + * - ms_from_ost migrated to ffmpeg_mux.c + */ + +#ifndef FFTOOLS_FFMPEG_MUX_H +#define FFTOOLS_FFMPEG_MUX_H + +#include +#include + +#include "fftools_thread_queue.h" + +#include "libavformat/avformat.h" + +#include "libavcodec/packet.h" + +#include "libavutil/dict.h" +#include "libavutil/fifo.h" +#include "libavutil/thread.h" + +#define SPECIFIER_OPT_FMT_str "%s" +#define SPECIFIER_OPT_FMT_i "%i" +#define SPECIFIER_OPT_FMT_i64 "%"PRId64 +#define SPECIFIER_OPT_FMT_ui64 "%"PRIu64 +#define SPECIFIER_OPT_FMT_f "%f" +#define SPECIFIER_OPT_FMT_dbl "%lf" + +#define WARN_MULTIPLE_OPT_USAGE(name, type, so, st)\ +{\ + char namestr[128] = "";\ + const char *spec = so->specifier && so->specifier[0] ? so->specifier : "";\ + for (int _i = 0; opt_name_##name[_i]; _i++)\ + av_strlcatf(namestr, sizeof(namestr), "-%s%s", opt_name_##name[_i], opt_name_##name[_i+1] ? (opt_name_##name[_i+2] ? ", " : " or ") : "");\ + av_log(NULL, AV_LOG_WARNING, "Multiple %s options specified for stream %d, only the last option '-%s%s%s "SPECIFIER_OPT_FMT_##type"' will be used.\n",\ + namestr, st->index, opt_name_##name[0], spec[0] ? ":" : "", spec, so->u.type);\ +} + +#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\ +{\ + int _ret, _matches = 0;\ + SpecifierOpt *so;\ + for (int _i = 0; _i < o->nb_ ## name; _i++) {\ + char *spec = o->name[_i].specifier;\ + if ((_ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\ + outvar = o->name[_i].u.type;\ + so = &o->name[_i];\ + _matches++;\ + } else if (_ret < 0)\ + exit_program(1);\ + }\ + if (_matches > 1)\ + WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\ +} + +#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\ +{\ + int i;\ + for (i = 0; i < o->nb_ ## name; i++) {\ + char *spec = o->name[i].specifier;\ + if (!strcmp(spec, mediatype))\ + outvar = o->name[i].u.type;\ + }\ +} + +typedef struct MuxStream { + OutputStream ost; + + // name used for logging + char log_name[32]; + + /* the packets are buffered here until the muxer is ready to be initialized */ + AVFifo *muxing_queue; + + AVBSFContext *bsf_ctx; + + EncStats stats; + + int64_t max_frames; + + /* + * The size of the AVPackets' buffers in queue. + * Updated when a packet is either pushed or pulled from the queue. + */ + size_t muxing_queue_data_size; + + int max_muxing_queue_size; + + /* Threshold after which max_muxing_queue_size will be in effect */ + size_t muxing_queue_data_threshold; + + /* dts of the last packet sent to the muxer, in the stream timebase + * used for making up missing dts values */ + int64_t last_mux_dts; +} MuxStream; + +typedef struct Muxer { + OutputFile of; + + // name used for logging + char log_name[32]; + + AVFormatContext *fc; + + pthread_t thread; + ThreadQueue *tq; + + AVDictionary *opts; + + int thread_queue_size; + + /* filesize limit expressed in bytes */ + int64_t limit_filesize; + atomic_int_least64_t last_filesize; + int header_written; + + SyncQueue *sq_mux; + AVPacket *sq_pkt; +} Muxer; + +typedef struct EncStatsFile { + char *path; + AVIOContext *io; +} EncStatsFile; + +/* whether we want to print an SDP, set in of_open() */ +extern __thread int want_sdp; + +int mux_check_init(Muxer *mux); + +#endif /* FFTOOLS_FFMPEG_MUX_H */ diff --git a/linux/src/fftools_ffmpeg_mux_init.c b/linux/src/fftools_ffmpeg_mux_init.c new file mode 100644 index 0000000..522f8b8 --- /dev/null +++ b/linux/src/fftools_ffmpeg_mux_init.c @@ -0,0 +1,2414 @@ +/* + * Muxer/output file setup. + * Copyright (c) 2023 ARTHENICA LTD + * + * This file is part of FFmpeg. + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of ffmpeg_mux_init.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * - EncStatsFile declaration migrated to ffmpeg_mux.h + * - extern ms_from_ost added + * - "class" member field renamed as clazz + */ + +#include + +#include "fftools_cmdutils.h" +#include "fftools_ffmpeg.h" +#include "fftools_ffmpeg_mux.h" +#include "fftools_fopen_utf8.h" + +#include "libavformat/avformat.h" +#include "libavformat/avio.h" + +#include "libavcodec/avcodec.h" + +#include "libavfilter/avfilter.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/avutil.h" +#include "libavutil/bprint.h" +#include "libavutil/dict.h" +#include "libavutil/getenv_utf8.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/log.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/pixdesc.h" + +#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass" + +static const char *const opt_name_apad[] = {"apad", NULL}; +static const char *const opt_name_autoscale[] = {"autoscale", NULL}; +static const char *const opt_name_bits_per_raw_sample[] = {"bits_per_raw_sample", NULL}; +static const char *const opt_name_bitstream_filters[] = {"bsf", "absf", "vbsf", NULL}; +static const char *const opt_name_copy_initial_nonkeyframes[] = {"copyinkf", NULL}; +static const char *const opt_name_copy_prior_start[] = {"copypriorss", NULL}; +static const char *const opt_name_disposition[] = {"disposition", NULL}; +static const char *const opt_name_enc_time_bases[] = {"enc_time_base", NULL}; +static const char *const opt_name_enc_stats_pre[] = {"enc_stats_pre", NULL}; +static const char *const opt_name_enc_stats_post[] = {"enc_stats_post", NULL}; +static const char *const opt_name_mux_stats[] = {"mux_stats", NULL}; +static const char *const opt_name_enc_stats_pre_fmt[] = {"enc_stats_pre_fmt", NULL}; +static const char *const opt_name_enc_stats_post_fmt[] = {"enc_stats_post_fmt", NULL}; +static const char *const opt_name_mux_stats_fmt[] = {"mux_stats_fmt", NULL}; +static const char *const opt_name_filters[] = {"filter", "af", "vf", NULL}; +static const char *const opt_name_filter_scripts[] = {"filter_script", NULL}; +static const char *const opt_name_fix_sub_duration_heartbeat[] = {"fix_sub_duration_heartbeat", NULL}; +static const char *const opt_name_fps_mode[] = {"fps_mode", NULL}; +static const char *const opt_name_force_fps[] = {"force_fps", NULL}; +static const char *const opt_name_forced_key_frames[] = {"forced_key_frames", NULL}; +static const char *const opt_name_frame_aspect_ratios[] = {"aspect", NULL}; +static const char *const opt_name_intra_matrices[] = {"intra_matrix", NULL}; +static const char *const opt_name_inter_matrices[] = {"inter_matrix", NULL}; +static const char *const opt_name_chroma_intra_matrices[] = {"chroma_intra_matrix", NULL}; +static const char *const opt_name_max_frame_rates[] = {"fpsmax", NULL}; +static const char *const opt_name_max_frames[] = {"frames", "aframes", "vframes", "dframes", NULL}; +static const char *const opt_name_max_muxing_queue_size[] = {"max_muxing_queue_size", NULL}; +static const char *const opt_name_muxing_queue_data_threshold[] = {"muxing_queue_data_threshold", NULL}; +static const char *const opt_name_pass[] = {"pass", NULL}; +static const char *const opt_name_passlogfiles[] = {"passlogfile", NULL}; +static const char *const opt_name_presets[] = {"pre", "apre", "vpre", "spre", NULL}; +static const char *const opt_name_qscale[] = {"q", "qscale", NULL}; +static const char *const opt_name_rc_overrides[] = {"rc_override", NULL}; +static const char *const opt_name_time_bases[] = {"time_base", NULL}; +static const char *const opt_name_audio_channels[] = {"ac", NULL}; +static const char *const opt_name_audio_ch_layouts[] = {"channel_layout", "ch_layout", NULL}; +static const char *const opt_name_audio_sample_rate[] = {"ar", NULL}; +static const char *const opt_name_frame_sizes[] = {"s", NULL}; +static const char *const opt_name_frame_pix_fmts[] = {"pix_fmt", NULL}; +static const char *const opt_name_sample_fmts[] = {"sample_fmt", NULL}; + +extern MuxStream *ms_from_ost(OutputStream *ost); + +static int check_opt_bitexact(void *ctx, const AVDictionary *opts, + const char *opt_name, int flag) +{ + const AVDictionaryEntry *e = av_dict_get(opts, opt_name, NULL, 0); + + if (e) { + const AVOption *o = av_opt_find(ctx, opt_name, NULL, 0, 0); + int val = 0; + if (!o) + return 0; + av_opt_eval_flags(ctx, o, e->value, &val); + return !!(val & flag); + } + return 0; +} + +static int choose_encoder(const OptionsContext *o, AVFormatContext *s, + OutputStream *ost, const AVCodec **enc) +{ + enum AVMediaType type = ost->st->codecpar->codec_type; + char *codec_name = NULL; + + *enc = NULL; + + if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) { + MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st); + if (!codec_name) { + ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->url, + NULL, ost->st->codecpar->codec_type); + *enc = avcodec_find_encoder(ost->st->codecpar->codec_id); + if (!*enc) { + av_log(ost, AV_LOG_FATAL, "Automatic encoder selection failed " + "Default encoder for format %s (codec %s) is " + "probably disabled. Please choose an encoder manually.\n", + s->oformat->name, avcodec_get_name(ost->st->codecpar->codec_id)); + return AVERROR_ENCODER_NOT_FOUND; + } + } else if (strcmp(codec_name, "copy")) { + *enc = find_codec_or_die(ost, codec_name, ost->st->codecpar->codec_type, 1); + ost->st->codecpar->codec_id = (*enc)->id; + } + } + + return 0; +} + +static char *get_line(AVIOContext *s, AVBPrint *bprint) +{ + char c; + + while ((c = avio_r8(s)) && c != '\n') + av_bprint_chars(bprint, c, 1); + + if (!av_bprint_is_complete(bprint)) + report_and_exit(AVERROR(ENOMEM)); + return bprint->str; +} + +static int get_preset_file_2(const char *preset_name, const char *codec_name, AVIOContext **s) +{ + int i, ret = -1; + char filename[1000]; + char *env_avconv_datadir = getenv_utf8("AVCONV_DATADIR"); + char *env_home = getenv_utf8("HOME"); + const char *base[3] = { env_avconv_datadir, + env_home, + AVCONV_DATADIR, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(base) && ret < 0; i++) { + if (!base[i]) + continue; + if (codec_name) { + snprintf(filename, sizeof(filename), "%s%s/%s-%s.avpreset", base[i], + i != 1 ? "" : "/.avconv", codec_name, preset_name); + ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); + } + if (ret < 0) { + snprintf(filename, sizeof(filename), "%s%s/%s.avpreset", base[i], + i != 1 ? "" : "/.avconv", preset_name); + ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); + } + } + freeenv_utf8(env_home); + freeenv_utf8(env_avconv_datadir); + return ret; +} + +__thread EncStatsFile *enc_stats_files; +__thread int nb_enc_stats_files; + +static int enc_stats_get_file(AVIOContext **io, const char *path) +{ + EncStatsFile *esf; + int ret; + + for (int i = 0; i < nb_enc_stats_files; i++) + if (!strcmp(path, enc_stats_files[i].path)) { + *io = enc_stats_files[i].io; + return 0; + } + + GROW_ARRAY(enc_stats_files, nb_enc_stats_files); + + esf = &enc_stats_files[nb_enc_stats_files - 1]; + + ret = avio_open2(&esf->io, path, AVIO_FLAG_WRITE, &int_cb, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening stats file '%s': %s\n", + path, av_err2str(ret)); + return ret; + } + + esf->path = av_strdup(path); + if (!esf->path) + return AVERROR(ENOMEM); + + *io = esf->io; + + return 0; +} + +void of_enc_stats_close(void) +{ + for (int i = 0; i < nb_enc_stats_files; i++) { + av_freep(&enc_stats_files[i].path); + avio_closep(&enc_stats_files[i].io); + } + av_freep(&enc_stats_files); + nb_enc_stats_files = 0; +} + +static int unescape(char **pdst, size_t *dst_len, + const char **pstr, char delim) +{ + const char *str = *pstr; + char *dst; + size_t len, idx; + + *pdst = NULL; + + len = strlen(str); + if (!len) + return 0; + + dst = av_malloc(len + 1); + if (!dst) + return AVERROR(ENOMEM); + + for (idx = 0; *str; idx++, str++) { + if (str[0] == '\\' && str[1]) + str++; + else if (*str == delim) + break; + + dst[idx] = *str; + } + if (!idx) { + av_freep(&dst); + return 0; + } + + dst[idx] = 0; + + *pdst = dst; + *dst_len = idx; + *pstr = str; + + return 0; +} + +static int enc_stats_init(OutputStream *ost, EncStats *es, int pre, + const char *path, const char *fmt_spec) +{ + static const struct { + enum EncStatsType type; + const char *str; + int pre_only:1; + int post_only:1; + int need_input_data:1; + } fmt_specs[] = { + { ENC_STATS_FILE_IDX, "fidx" }, + { ENC_STATS_STREAM_IDX, "sidx" }, + { ENC_STATS_FRAME_NUM, "n" }, + { ENC_STATS_FRAME_NUM_IN, "ni", 0, 0, 1 }, + { ENC_STATS_TIMEBASE, "tb" }, + { ENC_STATS_TIMEBASE_IN, "tbi", 0, 0, 1 }, + { ENC_STATS_PTS, "pts" }, + { ENC_STATS_PTS_TIME, "t" }, + { ENC_STATS_PTS_IN, "ptsi", 0, 0, 1 }, + { ENC_STATS_PTS_TIME_IN, "ti", 0, 0, 1 }, + { ENC_STATS_DTS, "dts", 0, 1 }, + { ENC_STATS_DTS_TIME, "dt", 0, 1 }, + { ENC_STATS_SAMPLE_NUM, "sn", 1 }, + { ENC_STATS_NB_SAMPLES, "samp", 1 }, + { ENC_STATS_PKT_SIZE, "size", 0, 1 }, + { ENC_STATS_BITRATE, "br", 0, 1 }, + { ENC_STATS_AVG_BITRATE, "abr", 0, 1 }, + }; + const char *next = fmt_spec; + + int ret; + + while (*next) { + EncStatsComponent *c; + char *val; + size_t val_len; + + // get the sequence up until next opening brace + ret = unescape(&val, &val_len, &next, '{'); + if (ret < 0) + return ret; + + if (val) { + GROW_ARRAY(es->components, es->nb_components); + + c = &es->components[es->nb_components - 1]; + c->type = ENC_STATS_LITERAL; + c->str = val; + c->str_len = val_len; + } + + if (!*next) + break; + next++; + + // get the part inside braces + ret = unescape(&val, &val_len, &next, '}'); + if (ret < 0) + return ret; + + if (!val) { + av_log(NULL, AV_LOG_ERROR, + "Empty formatting directive in: %s\n", fmt_spec); + return AVERROR(EINVAL); + } + + if (!*next) { + av_log(NULL, AV_LOG_ERROR, + "Missing closing brace in: %s\n", fmt_spec); + ret = AVERROR(EINVAL); + goto fail; + } + next++; + + GROW_ARRAY(es->components, es->nb_components); + c = &es->components[es->nb_components - 1]; + + for (size_t i = 0; i < FF_ARRAY_ELEMS(fmt_specs); i++) { + if (!strcmp(val, fmt_specs[i].str)) { + if ((pre && fmt_specs[i].post_only) || (!pre && fmt_specs[i].pre_only)) { + av_log(NULL, AV_LOG_ERROR, + "Format directive '%s' may only be used %s-encoding\n", + val, pre ? "post" : "pre"); + ret = AVERROR(EINVAL); + goto fail; + } + + c->type = fmt_specs[i].type; + + if (fmt_specs[i].need_input_data) { + if (ost->ist) + ost->ist->want_frame_data = 1; + else { + av_log(ost, AV_LOG_WARNING, + "Format directive '%s' is unavailable, because " + "this output stream has no associated input stream\n", + val); + } + } + + break; + } + } + + if (!c->type) { + av_log(NULL, AV_LOG_ERROR, "Invalid format directive: %s\n", val); + ret = AVERROR(EINVAL); + goto fail; + } + +fail: + av_freep(&val); + if (ret < 0) + return ret; + } + + ret = enc_stats_get_file(&es->io, path); + if (ret < 0) + return ret; + + return 0; +} + +static const char *output_stream_item_name(void *obj) +{ + const MuxStream *ms = obj; + + return ms->log_name; +} + +static const AVClass output_stream_class = { + .class_name = "OutputStream", + .version = LIBAVUTIL_VERSION_INT, + .item_name = output_stream_item_name, + .category = AV_CLASS_CATEGORY_MUXER, +}; + +static MuxStream *mux_stream_alloc(Muxer *mux, enum AVMediaType type) +{ + const char *type_str = av_get_media_type_string(type); + MuxStream *ms = allocate_array_elem(&mux->of.streams, sizeof(*ms), + &mux->of.nb_streams); + + ms->ost.file_index = mux->of.index; + ms->ost.index = mux->of.nb_streams - 1; + + ms->ost.clazz = &output_stream_class; + + snprintf(ms->log_name, sizeof(ms->log_name), "%cost#%d:%d", + type_str ? *type_str : '?', mux->of.index, ms->ost.index); + + return ms; +} + +static OutputStream *new_output_stream(Muxer *mux, const OptionsContext *o, + enum AVMediaType type, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + MuxStream *ms; + OutputStream *ost; + const AVCodec *enc; + AVStream *st = avformat_new_stream(oc, NULL); + int ret = 0; + const char *bsfs = NULL, *time_base = NULL; + char *next, *codec_tag = NULL; + double qscale = -1; + int i; + + if (!st) + report_and_exit(AVERROR(ENOMEM)); + + if (oc->nb_streams - 1 < o->nb_streamid_map) + st->id = o->streamid_map[oc->nb_streams - 1]; + + ms = mux_stream_alloc(mux, type); + ost = &ms->ost; + + ms->muxing_queue = av_fifo_alloc2(8, sizeof(AVPacket*), 0); + if (!ms->muxing_queue) + report_and_exit(AVERROR(ENOMEM)); + ms->last_mux_dts = AV_NOPTS_VALUE; + + ost->st = st; + ost->ist = ist; + ost->kf.ref_pts = AV_NOPTS_VALUE; + st->codecpar->codec_type = type; + + ret = choose_encoder(o, oc, ost, &enc); + if (ret < 0) { + av_log(ost, AV_LOG_FATAL, "Error selecting an encoder\n"); + exit_program(1); + } + + if (enc) { + ost->enc_ctx = avcodec_alloc_context3(enc); + if (!ost->enc_ctx) + report_and_exit(AVERROR(ENOMEM)); + + av_strlcat(ms->log_name, "/", sizeof(ms->log_name)); + av_strlcat(ms->log_name, enc->name, sizeof(ms->log_name)); + } else { + av_strlcat(ms->log_name, "/copy", sizeof(ms->log_name)); + } + + ost->filtered_frame = av_frame_alloc(); + if (!ost->filtered_frame) + report_and_exit(AVERROR(ENOMEM)); + + ost->pkt = av_packet_alloc(); + if (!ost->pkt) + report_and_exit(AVERROR(ENOMEM)); + + if (ost->enc_ctx) { + AVCodecContext *enc = ost->enc_ctx; + AVIOContext *s = NULL; + char *buf = NULL, *arg = NULL, *preset = NULL; + const char *enc_stats_pre = NULL, *enc_stats_post = NULL, *mux_stats = NULL; + + ost->encoder_opts = filter_codec_opts(o->g->codec_opts, enc->codec_id, + oc, st, enc->codec); + + MATCH_PER_STREAM_OPT(presets, str, preset, oc, st); + ost->autoscale = 1; + MATCH_PER_STREAM_OPT(autoscale, i, ost->autoscale, oc, st); + if (preset && (!(ret = get_preset_file_2(preset, enc->codec->name, &s)))) { + AVBPrint bprint; + av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); + do { + av_bprint_clear(&bprint); + buf = get_line(s, &bprint); + if (!buf[0] || buf[0] == '#') + continue; + if (!(arg = strchr(buf, '='))) { + av_log(ost, AV_LOG_FATAL, "Invalid line found in the preset file.\n"); + exit_program(1); + } + *arg++ = 0; + av_dict_set(&ost->encoder_opts, buf, arg, AV_DICT_DONT_OVERWRITE); + } while (!s->eof_reached); + av_bprint_finalize(&bprint, NULL); + avio_closep(&s); + } + if (ret) { + av_log(ost, AV_LOG_FATAL, + "Preset %s specified, but could not be opened.\n", preset); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(enc_stats_pre, str, enc_stats_pre, oc, st); + if (enc_stats_pre && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(enc_stats_pre_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ost->enc_stats_pre, 1, enc_stats_pre, format); + if (ret < 0) + exit_program(1); + } + + MATCH_PER_STREAM_OPT(enc_stats_post, str, enc_stats_post, oc, st); + if (enc_stats_post && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(enc_stats_post_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ost->enc_stats_post, 0, enc_stats_post, format); + if (ret < 0) + exit_program(1); + } + + MATCH_PER_STREAM_OPT(mux_stats, str, mux_stats, oc, st); + if (mux_stats && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + const char *format = "{fidx} {sidx} {n} {t}"; + + MATCH_PER_STREAM_OPT(mux_stats_fmt, str, format, oc, st); + + ret = enc_stats_init(ost, &ms->stats, 0, mux_stats, format); + if (ret < 0) + exit_program(1); + } + } else { + ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL); + } + + + if (o->bitexact) { + ost->bitexact = 1; + } else if (ost->enc_ctx) { + ost->bitexact = check_opt_bitexact(ost->enc_ctx, ost->encoder_opts, "flags", + AV_CODEC_FLAG_BITEXACT); + } + + MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st); + if (time_base) { + AVRational q; + if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || + q.num <= 0 || q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); + exit_program(1); + } + st->time_base = q; + } + + MATCH_PER_STREAM_OPT(enc_time_bases, str, time_base, oc, st); + if (time_base) { + AVRational q; + if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || + q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); + exit_program(1); + } + ost->enc_timebase = q; + } + + ms->max_frames = INT64_MAX; + MATCH_PER_STREAM_OPT(max_frames, i64, ms->max_frames, oc, st); + for (i = 0; inb_max_frames; i++) { + char *p = o->max_frames[i].specifier; + if (!*p && type != AVMEDIA_TYPE_VIDEO) { + av_log(ost, AV_LOG_WARNING, "Applying unspecific -frames to non video streams, maybe you meant -vframes ?\n"); + break; + } + } + + ost->copy_prior_start = -1; + MATCH_PER_STREAM_OPT(copy_prior_start, i, ost->copy_prior_start, oc ,st); + + MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st); + if (bsfs && *bsfs) { + ret = av_bsf_list_parse_str(bsfs, &ms->bsf_ctx); + if (ret < 0) { + av_log(ost, AV_LOG_ERROR, "Error parsing bitstream filter sequence '%s': %s\n", bsfs, av_err2str(ret)); + exit_program(1); + } + } + + MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st); + if (codec_tag) { + uint32_t tag = strtol(codec_tag, &next, 0); + if (*next) + tag = AV_RL32(codec_tag); + ost->st->codecpar->codec_tag = tag; + if (ost->enc_ctx) + ost->enc_ctx->codec_tag = tag; + } + + MATCH_PER_STREAM_OPT(qscale, dbl, qscale, oc, st); + if (ost->enc_ctx && qscale >= 0) { + ost->enc_ctx->flags |= AV_CODEC_FLAG_QSCALE; + ost->enc_ctx->global_quality = FF_QP2LAMBDA * qscale; + } + + ms->max_muxing_queue_size = 128; + MATCH_PER_STREAM_OPT(max_muxing_queue_size, i, ms->max_muxing_queue_size, oc, st); + + ms->muxing_queue_data_threshold = 50*1024*1024; + MATCH_PER_STREAM_OPT(muxing_queue_data_threshold, i, ms->muxing_queue_data_threshold, oc, st); + + MATCH_PER_STREAM_OPT(bits_per_raw_sample, i, ost->bits_per_raw_sample, + oc, st); + + MATCH_PER_STREAM_OPT(fix_sub_duration_heartbeat, i, ost->fix_sub_duration_heartbeat, + oc, st); + + if (oc->oformat->flags & AVFMT_GLOBALHEADER && ost->enc_ctx) + ost->enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + av_dict_copy(&ost->sws_dict, o->g->sws_dict, 0); + + av_dict_copy(&ost->swr_opts, o->g->swr_opts, 0); + if (ost->enc_ctx && av_get_exact_bits_per_sample(ost->enc_ctx->codec_id) == 24) + av_dict_set(&ost->swr_opts, "output_sample_bits", "24", 0); + + if (ost->ist) { + ost->ist->discard = 0; + ost->ist->st->discard = ost->ist->user_set_discard; + } + ost->last_mux_dts = AV_NOPTS_VALUE; + ost->last_filter_pts = AV_NOPTS_VALUE; + + MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, + ost->copy_initial_nonkeyframes, oc, st); + + return ost; +} + +static char *get_ost_filters(const OptionsContext *o, AVFormatContext *oc, + OutputStream *ost) +{ + AVStream *st = ost->st; + + if (ost->filters_script && ost->filters) { + av_log(ost, AV_LOG_ERROR, "Both -filter and -filter_script set\n"); + exit_program(1); + } + + if (ost->filters_script) + return file_read(ost->filters_script); + else if (ost->filters) + return av_strdup(ost->filters); + + return av_strdup(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? + "null" : "anull"); +} + +static void check_streamcopy_filters(const OptionsContext *o, AVFormatContext *oc, + OutputStream *ost, enum AVMediaType type) +{ + if (ost->filters_script || ost->filters) { + av_log(ost, AV_LOG_ERROR, + "%s '%s' was defined, but codec copy was selected.\n" + "Filtering and streamcopy cannot be used together.\n", + ost->filters ? "Filtergraph" : "Filtergraph script", + ost->filters ? ost->filters : ost->filters_script); + exit_program(1); + } +} + +static void parse_matrix_coeffs(void *logctx, uint16_t *dest, const char *str) +{ + int i; + const char *p = str; + for (i = 0;; i++) { + dest[i] = atoi(p); + if (i == 63) + break; + p = strchr(p, ','); + if (!p) { + av_log(logctx, AV_LOG_FATAL, + "Syntax error in matrix \"%s\" at coeff %d\n", str, i); + exit_program(1); + } + p++; + } +} + +static OutputStream *new_video_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + AVStream *st; + OutputStream *ost; + char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_VIDEO, ist); + st = ost->st; + + MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st); + if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(max_frame_rates, str, max_frame_rate, oc, st); + if (max_frame_rate && av_parse_video_rate(&ost->max_frame_rate, max_frame_rate) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid maximum framerate value: %s\n", max_frame_rate); + exit_program(1); + } + + if (frame_rate && max_frame_rate) { + av_log(ost, AV_LOG_ERROR, "Only one of -fpsmax and -r can be set for a stream.\n"); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st); + if (frame_aspect_ratio) { + AVRational q; + if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 || + q.num <= 0 || q.den <= 0) { + av_log(ost, AV_LOG_FATAL, "Invalid aspect ratio: %s\n", frame_aspect_ratio); + exit_program(1); + } + ost->frame_aspect_ratio = q; + } + + MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); + + if (ost->enc_ctx) { + AVCodecContext *video_enc = ost->enc_ctx; + const char *p = NULL, *fps_mode = NULL; + char *frame_size = NULL; + char *frame_pix_fmt = NULL; + char *intra_matrix = NULL, *inter_matrix = NULL; + char *chroma_intra_matrix = NULL; + int do_pass = 0; + int i; + + MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); + if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st); + if (frame_pix_fmt && *frame_pix_fmt == '+') { + ost->keep_pix_fmt = 1; + if (!*++frame_pix_fmt) + frame_pix_fmt = NULL; + } + if (frame_pix_fmt && (video_enc->pix_fmt = av_get_pix_fmt(frame_pix_fmt)) == AV_PIX_FMT_NONE) { + av_log(ost, AV_LOG_FATAL, "Unknown pixel format requested: %s.\n", frame_pix_fmt); + exit_program(1); + } + st->sample_aspect_ratio = video_enc->sample_aspect_ratio; + + MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st); + if (intra_matrix) { + if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64))) + report_and_exit(AVERROR(ENOMEM)); + parse_matrix_coeffs(ost, video_enc->intra_matrix, intra_matrix); + } + MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st); + if (chroma_intra_matrix) { + uint16_t *p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64); + if (!p) + report_and_exit(AVERROR(ENOMEM)); + video_enc->chroma_intra_matrix = p; + parse_matrix_coeffs(ost, p, chroma_intra_matrix); + } + MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st); + if (inter_matrix) { + if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64))) + report_and_exit(AVERROR(ENOMEM)); + parse_matrix_coeffs(ost, video_enc->inter_matrix, inter_matrix); + } + + MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st); + for (i = 0; p; i++) { + int start, end, q; + int e = sscanf(p, "%d,%d,%d", &start, &end, &q); + if (e != 3) { + av_log(ost, AV_LOG_FATAL, "error parsing rc_override\n"); + exit_program(1); + } + video_enc->rc_override = + av_realloc_array(video_enc->rc_override, + i + 1, sizeof(RcOverride)); + if (!video_enc->rc_override) { + av_log(ost, AV_LOG_FATAL, "Could not (re)allocate memory for rc_override.\n"); + exit_program(1); + } + video_enc->rc_override[i].start_frame = start; + video_enc->rc_override[i].end_frame = end; + if (q > 0) { + video_enc->rc_override[i].qscale = q; + video_enc->rc_override[i].quality_factor = 1.0; + } + else { + video_enc->rc_override[i].qscale = 0; + video_enc->rc_override[i].quality_factor = -q/100.0; + } + p = strchr(p, '/'); + if (p) p++; + } + video_enc->rc_override_count = i; + +#if FFMPEG_OPT_PSNR + if (do_psnr) { + av_log(ost, AV_LOG_WARNING, "The -psnr option is deprecated, use -flags +psnr\n"); + video_enc->flags|= AV_CODEC_FLAG_PSNR; + } +#endif + + /* two pass mode */ + MATCH_PER_STREAM_OPT(pass, i, do_pass, oc, st); + if (do_pass) { + if (do_pass & 1) { + video_enc->flags |= AV_CODEC_FLAG_PASS1; + av_dict_set(&ost->encoder_opts, "flags", "+pass1", AV_DICT_APPEND); + } + if (do_pass & 2) { + video_enc->flags |= AV_CODEC_FLAG_PASS2; + av_dict_set(&ost->encoder_opts, "flags", "+pass2", AV_DICT_APPEND); + } + } + + MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st); + if (ost->logfile_prefix && + !(ost->logfile_prefix = av_strdup(ost->logfile_prefix))) + report_and_exit(AVERROR(ENOMEM)); + + if (do_pass) { + int ost_idx = -1; + char logfilename[1024]; + FILE *f; + + /* compute this stream's global index */ + for (int i = 0; i <= ost->file_index; i++) + ost_idx += output_files[i]->nb_streams; + + snprintf(logfilename, sizeof(logfilename), "%s-%d.log", + ost->logfile_prefix ? ost->logfile_prefix : + DEFAULT_PASS_LOGFILENAME_PREFIX, + ost_idx); + if (!strcmp(ost->enc_ctx->codec->name, "libx264")) { + av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE); + } else { + if (video_enc->flags & AV_CODEC_FLAG_PASS2) { + char *logbuffer = file_read(logfilename); + + if (!logbuffer) { + av_log(ost, AV_LOG_FATAL, "Error reading log file '%s' for pass-2 encoding\n", + logfilename); + exit_program(1); + } + video_enc->stats_in = logbuffer; + } + if (video_enc->flags & AV_CODEC_FLAG_PASS1) { + f = fopen_utf8(logfilename, "wb"); + if (!f) { + av_log(ost, AV_LOG_FATAL, + "Cannot write log file '%s' for pass-1 encoding: %s\n", + logfilename, strerror(errno)); + exit_program(1); + } + ost->logfile = f; + } + } + } + + MATCH_PER_STREAM_OPT(force_fps, i, ost->force_fps, oc, st); + + ost->top_field_first = -1; + MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st); + + ost->vsync_method = video_sync_method; + MATCH_PER_STREAM_OPT(fps_mode, str, fps_mode, oc, st); + if (fps_mode) + parse_and_set_vsync(fps_mode, &ost->vsync_method, ost->file_index, ost->index, 0); + + if ((ost->frame_rate.num || ost->max_frame_rate.num) && + !(ost->vsync_method == VSYNC_AUTO || + ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR)) { + av_log(ost, AV_LOG_FATAL, "One of -r/-fpsmax was specified " + "together a non-CFR -vsync/-fps_mode. This is contradictory.\n"); + exit_program(1); + } + + if (ost->vsync_method == VSYNC_AUTO) { + if (ost->frame_rate.num || ost->max_frame_rate.num) { + ost->vsync_method = VSYNC_CFR; + } else if (!strcmp(oc->oformat->name, "avi")) { + ost->vsync_method = VSYNC_VFR; + } else { + ost->vsync_method = (oc->oformat->flags & AVFMT_VARIABLE_FPS) ? + ((oc->oformat->flags & AVFMT_NOTIMESTAMPS) ? + VSYNC_PASSTHROUGH : VSYNC_VFR) : + VSYNC_CFR; + } + + if (ost->ist && ost->vsync_method == VSYNC_CFR) { + const InputFile *ifile = input_files[ost->ist->file_index]; + + if (ifile->nb_streams == 1 && ifile->input_ts_offset == 0) + ost->vsync_method = VSYNC_VSCFR; + } + + if (ost->vsync_method == VSYNC_CFR && copy_ts) { + ost->vsync_method = VSYNC_VSCFR; + } + } + ost->is_cfr = (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR); + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit_program(1); + + ost->last_frame = av_frame_alloc(); + if (!ost->last_frame) + report_and_exit(AVERROR(ENOMEM)); + } else + check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_VIDEO); + + return ost; +} + +static OutputStream *new_audio_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVFormatContext *oc = mux->fc; + AVStream *st; + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_AUDIO, ist); + st = ost->st; + + + MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); + MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); + + if (ost->enc_ctx) { + AVCodecContext *audio_enc = ost->enc_ctx; + int channels = 0; + char *layout = NULL; + char *sample_fmt = NULL; + + MATCH_PER_STREAM_OPT(audio_channels, i, channels, oc, st); + if (channels) { + audio_enc->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC; + audio_enc->ch_layout.nb_channels = channels; + } + + MATCH_PER_STREAM_OPT(audio_ch_layouts, str, layout, oc, st); + if (layout) { + if (av_channel_layout_from_string(&audio_enc->ch_layout, layout) < 0) { +#if FF_API_OLD_CHANNEL_LAYOUT + uint64_t mask; + AV_NOWARN_DEPRECATED({ + mask = av_get_channel_layout(layout); + }) + if (!mask) { +#endif + av_log(ost, AV_LOG_FATAL, "Unknown channel layout: %s\n", layout); + exit_program(1); +#if FF_API_OLD_CHANNEL_LAYOUT + } + av_log(ost, AV_LOG_WARNING, "Channel layout '%s' uses a deprecated syntax.\n", + layout); + av_channel_layout_from_mask(&audio_enc->ch_layout, mask); +#endif + } + } + + MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st); + if (sample_fmt && + (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) { + av_log(ost, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt); + exit_program(1); + } + + MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st); + + MATCH_PER_STREAM_OPT(apad, str, ost->apad, oc, st); + ost->apad = av_strdup(ost->apad); + + ost->avfilter = get_ost_filters(o, oc, ost); + if (!ost->avfilter) + exit_program(1); + +#if FFMPEG_OPT_MAP_CHANNEL + /* check for channel mapping for this audio stream */ + for (int n = 0; n < o->nb_audio_channel_maps; n++) { + AudioChannelMap *map = &o->audio_channel_maps[n]; + if ((map->ofile_idx == -1 || ost->file_index == map->ofile_idx) && + (map->ostream_idx == -1 || ost->st->index == map->ostream_idx)) { + InputStream *ist; + + if (map->channel_idx == -1) { + ist = NULL; + } else if (!ost->ist) { + av_log(ost, AV_LOG_FATAL, "Cannot determine input stream for channel mapping %d.%d\n", + ost->file_index, ost->st->index); + continue; + } else { + ist = ost->ist; + } + + if (!ist || (ist->file_index == map->file_idx && ist->st->index == map->stream_idx)) { + if (av_reallocp_array(&ost->audio_channels_map, + ost->audio_channels_mapped + 1, + sizeof(*ost->audio_channels_map) + ) < 0 ) + report_and_exit(AVERROR(ENOMEM)); + + ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx; + } + } + } +#endif + } else + check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_AUDIO); + + return ost; +} + +static OutputStream *new_data_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_DATA, ist); + if (ost->enc_ctx) { + av_log(ost, AV_LOG_FATAL, "Data stream encoding not supported yet (only streamcopy)\n"); + exit_program(1); + } + + return ost; +} + +static OutputStream *new_unknown_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_UNKNOWN, ist); + if (ost->enc_ctx) { + av_log(ost, AV_LOG_FATAL, "Unknown stream encoding not supported yet (only streamcopy)\n"); + exit_program(1); + } + + return ost; +} + +static OutputStream *new_attachment_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + OutputStream *ost = new_output_stream(mux, o, AVMEDIA_TYPE_ATTACHMENT, ist); + ost->finished = 1; + return ost; +} + +static OutputStream *new_subtitle_stream(Muxer *mux, const OptionsContext *o, InputStream *ist) +{ + AVStream *st; + OutputStream *ost; + + ost = new_output_stream(mux, o, AVMEDIA_TYPE_SUBTITLE, ist); + st = ost->st; + + if (ost->enc_ctx) { + AVCodecContext *subtitle_enc = ost->enc_ctx; + char *frame_size = NULL; + + MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, mux->fc, st); + if (frame_size && av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size) < 0) { + av_log(ost, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); + exit_program(1); + } + } + + return ost; +} + +static void init_output_filter(OutputFilter *ofilter, const OptionsContext *o, + Muxer *mux) +{ + OutputStream *ost; + + switch (ofilter->type) { + case AVMEDIA_TYPE_VIDEO: ost = new_video_stream(mux, o, NULL); break; + case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream(mux, o, NULL); break; + default: + av_log(mux, AV_LOG_FATAL, "Only video and audio filters are supported " + "currently.\n"); + exit_program(1); + } + + ost->filter = ofilter; + + ofilter->ost = ost; + ofilter->format = -1; + + if (!ost->enc_ctx) { + av_log(ost, AV_LOG_ERROR, "Streamcopy requested for output stream fed " + "from a complex filtergraph. Filtering and streamcopy " + "cannot be used together.\n"); + exit_program(1); + } + + if (ost->avfilter && (ost->filters || ost->filters_script)) { + const char *opt = ost->filters ? "-vf/-af/-filter" : "-filter_script"; + av_log(ost, AV_LOG_ERROR, + "%s '%s' was specified through the %s option " + "for output stream %d:%d, which is fed from a complex filtergraph.\n" + "%s and -filter_complex cannot be used together for the same stream.\n", + ost->filters ? "Filtergraph" : "Filtergraph script", + ost->filters ? ost->filters : ost->filters_script, + opt, ost->file_index, ost->index, opt); + exit_program(1); + } + + avfilter_inout_free(&ofilter->out_tmp); +} + +static void map_auto_video(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + InputStream *best_ist = NULL; + int best_score = 0; + int qcr; + + /* video: highest resolution */ + if (av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_VIDEO) == AV_CODEC_ID_NONE) + return; + + qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0); + for (int j = 0; j < nb_input_files; j++) { + InputFile *ifile = input_files[j]; + InputStream *file_best_ist = NULL; + int file_best_score = 0; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int score; + + if (ist->user_set_discard == AVDISCARD_ALL || + ist->st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + + score = ist->st->codecpar->width * ist->st->codecpar->height + + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) + + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); + if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + score = 1; + + if (score > file_best_score) { + if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + continue; + file_best_score = score; + file_best_ist = ist; + } + } + if (file_best_ist) { + if((qcr == MKTAG('A', 'P', 'I', 'C')) || + !(file_best_ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) + file_best_score -= 5000000*!!(file_best_ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (file_best_score > best_score) { + best_score = file_best_score; + best_ist = file_best_ist; + } + } + } + if (best_ist) + new_video_stream(mux, o, best_ist); +} + +static void map_auto_audio(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + InputStream *best_ist = NULL; + int best_score = 0; + + /* audio: most channels */ + if (av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_AUDIO) == AV_CODEC_ID_NONE) + return; + + for (int j = 0; j < nb_input_files; j++) { + InputFile *ifile = input_files[j]; + InputStream *file_best_ist = NULL; + int file_best_score = 0; + for (int i = 0; i < ifile->nb_streams; i++) { + InputStream *ist = ifile->streams[i]; + int score; + + if (ist->user_set_discard == AVDISCARD_ALL || + ist->st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) + continue; + + score = ist->st->codecpar->ch_layout.nb_channels + + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) + + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (score > file_best_score) { + file_best_score = score; + file_best_ist = ist; + } + } + if (file_best_ist) { + file_best_score -= 5000000*!!(file_best_ist->st->disposition & AV_DISPOSITION_DEFAULT); + if (file_best_score > best_score) { + best_score = file_best_score; + best_ist = file_best_ist; + } + } + } + if (best_ist) + new_audio_stream(mux, o, best_ist); +} + +static void map_auto_subtitle(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + char *subtitle_codec_name = NULL; + + /* subtitles: pick first */ + MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s"); + if (!avcodec_find_encoder(oc->oformat->subtitle_codec) && !subtitle_codec_name) + return; + + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { + AVCodecDescriptor const *input_descriptor = + avcodec_descriptor_get(ist->st->codecpar->codec_id); + AVCodecDescriptor const *output_descriptor = NULL; + AVCodec const *output_codec = + avcodec_find_encoder(oc->oformat->subtitle_codec); + int input_props = 0, output_props = 0; + if (ist->user_set_discard == AVDISCARD_ALL) + continue; + if (output_codec) + output_descriptor = avcodec_descriptor_get(output_codec->id); + if (input_descriptor) + input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (output_descriptor) + output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); + if (subtitle_codec_name || + input_props & output_props || + // Map dvb teletext which has neither property to any output subtitle encoder + (input_descriptor && output_descriptor && + (!input_descriptor->props || + !output_descriptor->props))) { + new_subtitle_stream(mux, o, ist); + break; + } + } +} + +static void map_auto_data(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + /* Data only if codec id match */ + enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, oc->url, NULL, AVMEDIA_TYPE_DATA); + + if (codec_id == AV_CODEC_ID_NONE) + return; + + for (InputStream *ist = ist_iter(NULL); ist; ist = ist_iter(ist)) { + if (ist->user_set_discard == AVDISCARD_ALL) + continue; + if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA && + ist->st->codecpar->codec_id == codec_id ) + new_data_stream(mux, o, ist); + } +} + +static void map_manual(Muxer *mux, const OptionsContext *o, const StreamMap *map) +{ + InputStream *ist; + + if (map->disabled) + return; + + if (map->linklabel) { + FilterGraph *fg; + OutputFilter *ofilter = NULL; + int j, k; + + for (j = 0; j < nb_filtergraphs; j++) { + fg = filtergraphs[j]; + for (k = 0; k < fg->nb_outputs; k++) { + AVFilterInOut *out = fg->outputs[k]->out_tmp; + if (out && !strcmp(out->name, map->linklabel)) { + ofilter = fg->outputs[k]; + goto loop_end; + } + } + } +loop_end: + if (!ofilter) { + av_log(mux, AV_LOG_FATAL, "Output with label '%s' does not exist " + "in any defined filter graph, or was already used elsewhere.\n", map->linklabel); + exit_program(1); + } + init_output_filter(ofilter, o, mux); + } else { + ist = input_files[map->file_index]->streams[map->stream_index]; + if (ist->user_set_discard == AVDISCARD_ALL) { + av_log(mux, AV_LOG_FATAL, "Stream #%d:%d is disabled and cannot be mapped.\n", + map->file_index, map->stream_index); + exit_program(1); + } + if(o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) + return; + if(o-> audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + return; + if(o-> video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + return; + if(o-> data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA) + return; + + switch (ist->st->codecpar->codec_type) { + case AVMEDIA_TYPE_VIDEO: new_video_stream (mux, o, ist); break; + case AVMEDIA_TYPE_AUDIO: new_audio_stream (mux, o, ist); break; + case AVMEDIA_TYPE_SUBTITLE: new_subtitle_stream (mux, o, ist); break; + case AVMEDIA_TYPE_DATA: new_data_stream (mux, o, ist); break; + case AVMEDIA_TYPE_ATTACHMENT: new_attachment_stream(mux, o, ist); break; + case AVMEDIA_TYPE_UNKNOWN: + if (copy_unknown_streams) { + new_unknown_stream (mux, o, ist); + break; + } + default: + av_log(mux, ignore_unknown_streams ? AV_LOG_WARNING : AV_LOG_FATAL, + "Cannot map stream #%d:%d - unsupported type.\n", + map->file_index, map->stream_index); + if (!ignore_unknown_streams) { + av_log(mux, AV_LOG_FATAL, + "If you want unsupported types ignored instead " + "of failing, please use the -ignore_unknown option\n" + "If you want them copied, please use -copy_unknown\n"); + exit_program(1); + } + } + } +} + +static void of_add_attachments(Muxer *mux, const OptionsContext *o) +{ + OutputStream *ost; + int err; + + for (int i = 0; i < o->nb_attachments; i++) { + AVIOContext *pb; + uint8_t *attachment; + const char *p; + int64_t len; + + if ((err = avio_open2(&pb, o->attachments[i], AVIO_FLAG_READ, &int_cb, NULL)) < 0) { + av_log(mux, AV_LOG_FATAL, "Could not open attachment file %s.\n", + o->attachments[i]); + exit_program(1); + } + if ((len = avio_size(pb)) <= 0) { + av_log(mux, AV_LOG_FATAL, "Could not get size of the attachment %s.\n", + o->attachments[i]); + exit_program(1); + } + if (len > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE || + !(attachment = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE))) { + av_log(mux, AV_LOG_FATAL, "Attachment %s too large.\n", + o->attachments[i]); + exit_program(1); + } + avio_read(pb, attachment, len); + memset(attachment + len, 0, AV_INPUT_BUFFER_PADDING_SIZE); + + ost = new_attachment_stream(mux, o, NULL); + ost->attachment_filename = o->attachments[i]; + ost->st->codecpar->extradata = attachment; + ost->st->codecpar->extradata_size = len; + + p = strrchr(o->attachments[i], '/'); + av_dict_set(&ost->st->metadata, "filename", (p && *p) ? p + 1 : o->attachments[i], AV_DICT_DONT_OVERWRITE); + avio_closep(&pb); + } +} + +static void create_streams(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + int auto_disable_v = o->video_disable; + int auto_disable_a = o->audio_disable; + int auto_disable_s = o->subtitle_disable; + int auto_disable_d = o->data_disable; + + /* create streams for all unlabeled output pads */ + for (int i = 0; i < nb_filtergraphs; i++) { + FilterGraph *fg = filtergraphs[i]; + for (int j = 0; j < fg->nb_outputs; j++) { + OutputFilter *ofilter = fg->outputs[j]; + + if (!ofilter->out_tmp || ofilter->out_tmp->name) + continue; + + switch (ofilter->type) { + case AVMEDIA_TYPE_VIDEO: auto_disable_v = 1; break; + case AVMEDIA_TYPE_AUDIO: auto_disable_a = 1; break; + case AVMEDIA_TYPE_SUBTITLE: auto_disable_s = 1; break; + } + init_output_filter(ofilter, o, mux); + } + } + + if (!o->nb_stream_maps) { + /* pick the "best" stream of each type */ + if (!auto_disable_v) + map_auto_video(mux, o); + if (!auto_disable_a) + map_auto_audio(mux, o); + if (!auto_disable_s) + map_auto_subtitle(mux, o); + if (!auto_disable_d) + map_auto_data(mux, o); + } else { + for (int i = 0; i < o->nb_stream_maps; i++) + map_manual(mux, o, &o->stream_maps[i]); + } + + of_add_attachments(mux, o); + + if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) { + av_dump_format(oc, nb_output_files - 1, oc->url, 1); + av_log(mux, AV_LOG_ERROR, "Output file does not contain any stream\n"); + exit_program(1); + } +} + +static int setup_sync_queues(Muxer *mux, AVFormatContext *oc, int64_t buf_size_us) +{ + OutputFile *of = &mux->of; + int nb_av_enc = 0, nb_interleaved = 0; + int limit_frames = 0, limit_frames_av_enc = 0; + +#define IS_AV_ENC(ost, type) \ + (ost->enc_ctx && (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) +#define IS_INTERLEAVED(type) (type != AVMEDIA_TYPE_ATTACHMENT) + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + ost->sq_idx_encode = -1; + ost->sq_idx_mux = -1; + + nb_interleaved += IS_INTERLEAVED(type); + nb_av_enc += IS_AV_ENC(ost, type); + + limit_frames |= ms->max_frames < INT64_MAX; + limit_frames_av_enc |= (ms->max_frames < INT64_MAX) && IS_AV_ENC(ost, type); + } + + if (!((nb_interleaved > 1 && of->shortest) || + (nb_interleaved > 0 && limit_frames))) + return 0; + + /* if we have more than one encoded audio/video streams, or at least + * one encoded audio/video stream is frame-limited, then we + * synchronize them before encoding */ + if ((of->shortest && nb_av_enc > 1) || limit_frames_av_enc) { + of->sq_encode = sq_alloc(SYNC_QUEUE_FRAMES, buf_size_us); + if (!of->sq_encode) + return AVERROR(ENOMEM); + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (!IS_AV_ENC(ost, type)) + continue; + + ost->sq_idx_encode = sq_add_stream(of->sq_encode, + of->shortest || ms->max_frames < INT64_MAX); + if (ost->sq_idx_encode < 0) + return ost->sq_idx_encode; + + ost->sq_frame = av_frame_alloc(); + if (!ost->sq_frame) + return AVERROR(ENOMEM); + + if (ms->max_frames != INT64_MAX) + sq_limit_frames(of->sq_encode, ost->sq_idx_encode, ms->max_frames); + } + } + + /* if there are any additional interleaved streams, then ALL the streams + * are also synchronized before sending them to the muxer */ + if (nb_interleaved > nb_av_enc) { + mux->sq_mux = sq_alloc(SYNC_QUEUE_PACKETS, buf_size_us); + if (!mux->sq_mux) + return AVERROR(ENOMEM); + + mux->sq_pkt = av_packet_alloc(); + if (!mux->sq_pkt) + return AVERROR(ENOMEM); + + for (int i = 0; i < oc->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + MuxStream *ms = ms_from_ost(ost); + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (!IS_INTERLEAVED(type)) + continue; + + ost->sq_idx_mux = sq_add_stream(mux->sq_mux, + of->shortest || ms->max_frames < INT64_MAX); + if (ost->sq_idx_mux < 0) + return ost->sq_idx_mux; + + if (ms->max_frames != INT64_MAX) + sq_limit_frames(mux->sq_mux, ost->sq_idx_mux, ms->max_frames); + } + } + +#undef IS_AV_ENC +#undef IS_INTERLEAVED + + return 0; +} + +static void of_add_programs(Muxer *mux, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + /* process manually set programs */ + for (int i = 0; i < o->nb_program; i++) { + const char *p = o->program[i].u.str; + int progid = i+1; + AVProgram *program; + + while(*p) { + const char *p2 = av_get_token(&p, ":"); + const char *to_dealloc = p2; + char *key; + if (!p2) + break; + + if(*p) p++; + + key = av_get_token(&p2, "="); + if (!key || !*p2) { + av_freep(&to_dealloc); + av_freep(&key); + break; + } + p2++; + + if (!strcmp(key, "program_num")) + progid = strtol(p2, NULL, 0); + av_freep(&to_dealloc); + av_freep(&key); + } + + program = av_new_program(oc, progid); + if (!program) + report_and_exit(AVERROR(ENOMEM)); + + p = o->program[i].u.str; + while(*p) { + const char *p2 = av_get_token(&p, ":"); + const char *to_dealloc = p2; + char *key; + if (!p2) + break; + if(*p) p++; + + key = av_get_token(&p2, "="); + if (!key) { + av_log(mux, AV_LOG_FATAL, + "No '=' character in program string %s.\n", + p2); + exit_program(1); + } + if (!*p2) + exit_program(1); + p2++; + + if (!strcmp(key, "title")) { + av_dict_set(&program->metadata, "title", p2, 0); + } else if (!strcmp(key, "program_num")) { + } else if (!strcmp(key, "st")) { + int st_num = strtol(p2, NULL, 0); + av_program_add_stream_index(oc, progid, st_num); + } else { + av_log(mux, AV_LOG_FATAL, "Unknown program key %s.\n", key); + exit_program(1); + } + av_freep(&to_dealloc); + av_freep(&key); + } + } +} + +/** + * Parse a metadata specifier passed as 'arg' parameter. + * @param arg metadata string to parse + * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram) + * @param index for type c/p, chapter/program index is written here + * @param stream_spec for type s, the stream specifier is written here + */ +static void parse_meta_type(void *logctx, const char *arg, + char *type, int *index, const char **stream_spec) +{ + if (*arg) { + *type = *arg; + switch (*arg) { + case 'g': + break; + case 's': + if (*(++arg) && *arg != ':') { + av_log(logctx, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg); + exit_program(1); + } + *stream_spec = *arg == ':' ? arg + 1 : ""; + break; + case 'c': + case 'p': + if (*(++arg) == ':') + *index = strtol(++arg, NULL, 0); + break; + default: + av_log(logctx, AV_LOG_FATAL, "Invalid metadata type %c.\n", *arg); + exit_program(1); + } + } else + *type = 'g'; +} + +static void of_add_metadata(OutputFile *of, AVFormatContext *oc, + const OptionsContext *o) +{ + for (int i = 0; i < o->nb_metadata; i++) { + AVDictionary **m; + char type, *val; + const char *stream_spec; + int index = 0, ret = 0; + + val = strchr(o->metadata[i].u.str, '='); + if (!val) { + av_log(of, AV_LOG_FATAL, "No '=' character in metadata string %s.\n", + o->metadata[i].u.str); + exit_program(1); + } + *val++ = 0; + + parse_meta_type(of, o->metadata[i].specifier, &type, &index, &stream_spec); + if (type == 's') { + for (int j = 0; j < oc->nb_streams; j++) { + OutputStream *ost = of->streams[j]; + if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) { +#if FFMPEG_ROTATION_METADATA + if (!strcmp(o->metadata[i].u.str, "rotate")) { + char *tail; + double theta = av_strtod(val, &tail); + if (!*tail) { + ost->rotate_overridden = 1; + ost->rotate_override_value = theta; + } + + av_log(ost, AV_LOG_WARNING, + "Conversion of a 'rotate' metadata key to a " + "proper display matrix rotation is deprecated. " + "See -display_rotation for setting rotation " + "instead."); + } else { +#endif + av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); +#if FFMPEG_ROTATION_METADATA + } +#endif + } else if (ret < 0) + exit_program(1); + } + } else { + switch (type) { + case 'g': + m = &oc->metadata; + break; + case 'c': + if (index < 0 || index >= oc->nb_chapters) { + av_log(of, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); + exit_program(1); + } + m = &oc->chapters[index]->metadata; + break; + case 'p': + if (index < 0 || index >= oc->nb_programs) { + av_log(of, AV_LOG_FATAL, "Invalid program index %d in metadata specifier.\n", index); + exit_program(1); + } + m = &oc->programs[index]->metadata; + break; + default: + av_log(of, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); + exit_program(1); + } + av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); + } + } +} + +static void set_channel_layout(OutputFilter *f, OutputStream *ost) +{ + const AVCodec *c = ost->enc_ctx->codec; + int i, err; + + if (ost->enc_ctx->ch_layout.order != AV_CHANNEL_ORDER_UNSPEC) { + /* Pass the layout through for all orders but UNSPEC */ + err = av_channel_layout_copy(&f->ch_layout, &ost->enc_ctx->ch_layout); + if (err < 0) + report_and_exit(AVERROR(ENOMEM)); + return; + } + + /* Requested layout is of order UNSPEC */ + if (!c->ch_layouts) { + /* Use the default native layout for the requested amount of channels when the + encoder doesn't have a list of supported layouts */ + av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); + return; + } + /* Encoder has a list of supported layouts. Pick the first layout in it with the + same amount of channels as the requested layout */ + for (i = 0; c->ch_layouts[i].nb_channels; i++) { + if (c->ch_layouts[i].nb_channels == ost->enc_ctx->ch_layout.nb_channels) + break; + } + if (c->ch_layouts[i].nb_channels) { + /* Use it if one is found */ + err = av_channel_layout_copy(&f->ch_layout, &c->ch_layouts[i]); + if (err < 0) + report_and_exit(AVERROR(ENOMEM)); + return; + } + /* If no layout for the amount of channels requested was found, use the default + native layout for it. */ + av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); +} + +static int copy_chapters(InputFile *ifile, OutputFile *ofile, AVFormatContext *os, + int copy_metadata) +{ + AVFormatContext *is = ifile->ctx; + AVChapter **tmp; + int i; + + tmp = av_realloc_f(os->chapters, is->nb_chapters + os->nb_chapters, sizeof(*os->chapters)); + if (!tmp) + return AVERROR(ENOMEM); + os->chapters = tmp; + + for (i = 0; i < is->nb_chapters; i++) { + AVChapter *in_ch = is->chapters[i], *out_ch; + int64_t start_time = (ofile->start_time == AV_NOPTS_VALUE) ? 0 : ofile->start_time; + int64_t ts_off = av_rescale_q(start_time - ifile->ts_offset, + AV_TIME_BASE_Q, in_ch->time_base); + int64_t rt = (ofile->recording_time == INT64_MAX) ? INT64_MAX : + av_rescale_q(ofile->recording_time, AV_TIME_BASE_Q, in_ch->time_base); + + + if (in_ch->end < ts_off) + continue; + if (rt != INT64_MAX && in_ch->start > rt + ts_off) + break; + + out_ch = av_mallocz(sizeof(AVChapter)); + if (!out_ch) + return AVERROR(ENOMEM); + + out_ch->id = in_ch->id; + out_ch->time_base = in_ch->time_base; + out_ch->start = FFMAX(0, in_ch->start - ts_off); + out_ch->end = FFMIN(rt, in_ch->end - ts_off); + + if (copy_metadata) + av_dict_copy(&out_ch->metadata, in_ch->metadata, 0); + + os->chapters[os->nb_chapters++] = out_ch; + } + return 0; +} + +static int copy_metadata(Muxer *mux, AVFormatContext *ic, + const char *outspec, const char *inspec, + int *metadata_global_manual, int *metadata_streams_manual, + int *metadata_chapters_manual, const OptionsContext *o) +{ + AVFormatContext *oc = mux->fc; + AVDictionary **meta_in = NULL; + AVDictionary **meta_out = NULL; + int i, ret = 0; + char type_in, type_out; + const char *istream_spec = NULL, *ostream_spec = NULL; + int idx_in = 0, idx_out = 0; + + parse_meta_type(mux, inspec, &type_in, &idx_in, &istream_spec); + parse_meta_type(mux, outspec, &type_out, &idx_out, &ostream_spec); + + if (type_in == 'g' || type_out == 'g') + *metadata_global_manual = 1; + if (type_in == 's' || type_out == 's') + *metadata_streams_manual = 1; + if (type_in == 'c' || type_out == 'c') + *metadata_chapters_manual = 1; + + /* ic is NULL when just disabling automatic mappings */ + if (!ic) + return 0; + +#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ + if ((index) < 0 || (index) >= (nb_elems)) {\ + av_log(mux, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\ + (desc), (index));\ + exit_program(1);\ + } + +#define SET_DICT(type, meta, context, index)\ + switch (type) {\ + case 'g':\ + meta = &context->metadata;\ + break;\ + case 'c':\ + METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\ + meta = &context->chapters[index]->metadata;\ + break;\ + case 'p':\ + METADATA_CHECK_INDEX(index, context->nb_programs, "program")\ + meta = &context->programs[index]->metadata;\ + break;\ + case 's':\ + break; /* handled separately below */ \ + default: av_assert0(0);\ + }\ + + SET_DICT(type_in, meta_in, ic, idx_in); + SET_DICT(type_out, meta_out, oc, idx_out); + + /* for input streams choose first matching stream */ + if (type_in == 's') { + for (i = 0; i < ic->nb_streams; i++) { + if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) { + meta_in = &ic->streams[i]->metadata; + break; + } else if (ret < 0) + exit_program(1); + } + if (!meta_in) { + av_log(mux, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec); + exit_program(1); + } + } + + if (type_out == 's') { + for (i = 0; i < oc->nb_streams; i++) { + if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) { + meta_out = &oc->streams[i]->metadata; + av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); + } else if (ret < 0) + exit_program(1); + } + } else + av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); + + return 0; +} + +static void copy_meta(Muxer *mux, const OptionsContext *o) +{ + OutputFile *of = &mux->of; + AVFormatContext *oc = mux->fc; + int chapters_input_file = o->chapters_input_file; + int metadata_global_manual = 0; + int metadata_streams_manual = 0; + int metadata_chapters_manual = 0; + + /* copy metadata */ + for (int i = 0; i < o->nb_metadata_map; i++) { + char *p; + int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0); + + if (in_file_index >= nb_input_files) { + av_log(mux, AV_LOG_FATAL, "Invalid input file index %d while " + "processing metadata maps\n", in_file_index); + exit_program(1); + } + copy_metadata(mux, + in_file_index >= 0 ? input_files[in_file_index]->ctx : NULL, + o->metadata_map[i].specifier, *p ? p + 1 : p, + &metadata_global_manual, &metadata_streams_manual, + &metadata_chapters_manual, o); + } + + /* copy chapters */ + if (chapters_input_file >= nb_input_files) { + if (chapters_input_file == INT_MAX) { + /* copy chapters from the first input file that has them*/ + chapters_input_file = -1; + for (int i = 0; i < nb_input_files; i++) + if (input_files[i]->ctx->nb_chapters) { + chapters_input_file = i; + break; + } + } else { + av_log(mux, AV_LOG_FATAL, "Invalid input file index %d in chapter mapping.\n", + chapters_input_file); + exit_program(1); + } + } + if (chapters_input_file >= 0) + copy_chapters(input_files[chapters_input_file], of, oc, + !metadata_chapters_manual); + + /* copy global metadata by default */ + if (!metadata_global_manual && nb_input_files){ + av_dict_copy(&oc->metadata, input_files[0]->ctx->metadata, + AV_DICT_DONT_OVERWRITE); + if (of->recording_time != INT64_MAX) + av_dict_set(&oc->metadata, "duration", NULL, 0); + av_dict_set(&oc->metadata, "creation_time", NULL, 0); + av_dict_set(&oc->metadata, "company_name", NULL, 0); + av_dict_set(&oc->metadata, "product_name", NULL, 0); + av_dict_set(&oc->metadata, "product_version", NULL, 0); + } + if (!metadata_streams_manual) + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + if (!ost->ist) /* this is true e.g. for attached files */ + continue; + av_dict_copy(&ost->st->metadata, ost->ist->st->metadata, AV_DICT_DONT_OVERWRITE); + if (ost->enc_ctx) { + av_dict_set(&ost->st->metadata, "encoder", NULL, 0); + } + } +} + +static int set_dispositions(Muxer *mux, const OptionsContext *o) +{ + OutputFile *of = &mux->of; + AVFormatContext *ctx = mux->fc; + + int nb_streams[AVMEDIA_TYPE_NB] = { 0 }; + int have_default[AVMEDIA_TYPE_NB] = { 0 }; + int have_manual = 0; + int ret = 0; + + const char **dispositions; + + dispositions = av_calloc(ctx->nb_streams, sizeof(*dispositions)); + if (!dispositions) + return AVERROR(ENOMEM); + + // first, copy the input dispositions + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + nb_streams[ost->st->codecpar->codec_type]++; + + MATCH_PER_STREAM_OPT(disposition, str, dispositions[i], ctx, ost->st); + + have_manual |= !!dispositions[i]; + + if (ost->ist) { + ost->st->disposition = ost->ist->st->disposition; + + if (ost->st->disposition & AV_DISPOSITION_DEFAULT) + have_default[ost->st->codecpar->codec_type] = 1; + } + } + + if (have_manual) { + // process manually set dispositions - they override the above copy + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + const char *disp = dispositions[i]; + + if (!disp) + continue; + + ret = av_opt_set(ost->st, "disposition", disp, 0); + if (ret < 0) + goto finish; + } + } else { + // For each media type with more than one stream, find a suitable stream to + // mark as default, unless one is already marked default. + // "Suitable" means the first of that type, skipping attached pictures. + for (int i = 0; i < ctx->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + enum AVMediaType type = ost->st->codecpar->codec_type; + + if (nb_streams[type] < 2 || have_default[type] || + ost->st->disposition & AV_DISPOSITION_ATTACHED_PIC) + continue; + + ost->st->disposition |= AV_DISPOSITION_DEFAULT; + have_default[type] = 1; + } + } + +finish: + av_freep(&dispositions); + + return ret; +} + +const char *const forced_keyframes_const_names[] = { + "n", + "n_forced", + "prev_forced_n", + "prev_forced_t", + "t", + NULL +}; + +static int compare_int64(const void *a, const void *b) +{ + return FFDIFFSIGN(*(const int64_t *)a, *(const int64_t *)b); +} + +static void parse_forced_key_frames(KeyframeForceCtx *kf, const Muxer *mux, + const char *spec) +{ + const char *p; + int n = 1, i, size, index = 0; + int64_t t, *pts; + + for (p = spec; *p; p++) + if (*p == ',') + n++; + size = n; + pts = av_malloc_array(size, sizeof(*pts)); + if (!pts) + report_and_exit(AVERROR(ENOMEM)); + + p = spec; + for (i = 0; i < n; i++) { + char *next = strchr(p, ','); + + if (next) + *next++ = 0; + + if (!memcmp(p, "chapters", 8)) { + AVChapter * const *ch = mux->fc->chapters; + unsigned int nb_ch = mux->fc->nb_chapters; + int j; + + if (nb_ch > INT_MAX - size || + !(pts = av_realloc_f(pts, size += nb_ch - 1, + sizeof(*pts)))) + report_and_exit(AVERROR(ENOMEM)); + t = p[8] ? parse_time_or_die("force_key_frames", p + 8, 1) : 0; + + for (j = 0; j < nb_ch; j++) { + const AVChapter *c = ch[j]; + av_assert1(index < size); + pts[index++] = av_rescale_q(c->start, c->time_base, + AV_TIME_BASE_Q) + t; + } + + } else { + av_assert1(index < size); + pts[index++] = parse_time_or_die("force_key_frames", p, 1); + } + + p = next; + } + + av_assert0(index == size); + qsort(pts, size, sizeof(*pts), compare_int64); + kf->nb_pts = size; + kf->pts = pts; +} + +static int process_forced_keyframes(Muxer *mux, const OptionsContext *o) +{ + for (int i = 0; i < mux->of.nb_streams; i++) { + OutputStream *ost = mux->of.streams[i]; + const char *forced_keyframes = NULL; + + MATCH_PER_STREAM_OPT(forced_key_frames, str, forced_keyframes, mux->fc, ost->st); + + if (!(ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + ost->enc_ctx && forced_keyframes)) + continue; + + if (!strncmp(forced_keyframes, "expr:", 5)) { + int ret = av_expr_parse(&ost->kf.pexpr, forced_keyframes + 5, + forced_keyframes_const_names, NULL, NULL, NULL, NULL, 0, NULL); + if (ret < 0) { + av_log(ost, AV_LOG_ERROR, + "Invalid force_key_frames expression '%s'\n", forced_keyframes + 5); + return ret; + } + ost->kf.expr_const_values[FKF_N] = 0; + ost->kf.expr_const_values[FKF_N_FORCED] = 0; + ost->kf.expr_const_values[FKF_PREV_FORCED_N] = NAN; + ost->kf.expr_const_values[FKF_PREV_FORCED_T] = NAN; + + // Don't parse the 'forced_keyframes' in case of 'keep-source-keyframes', + // parse it only for static kf timings + } else if (!strcmp(forced_keyframes, "source")) { + ost->kf.type = KF_FORCE_SOURCE; + } else if (!strcmp(forced_keyframes, "source_no_drop")) { + ost->kf.type = KF_FORCE_SOURCE_NO_DROP; + } else { + parse_forced_key_frames(&ost->kf, mux, forced_keyframes); + } + } + + return 0; +} + +static void validate_enc_avopt(Muxer *mux, const AVDictionary *codec_avopt) +{ + const AVClass *class = avcodec_get_class(); + const AVClass *fclass = avformat_get_class(); + const OutputFile *of = &mux->of; + + AVDictionary *unused_opts; + const AVDictionaryEntry *e; + + unused_opts = strip_specifiers(codec_avopt); + for (int i = 0; i < of->nb_streams; i++) { + e = NULL; + while ((e = av_dict_iterate(of->streams[i]->encoder_opts, e))) + av_dict_set(&unused_opts, e->key, NULL, 0); + } + + e = NULL; + while ((e = av_dict_iterate(unused_opts, e))) { + const AVOption *option = av_opt_find(&class, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + if (!option || foption) + continue; + + if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) { + av_log(mux, AV_LOG_ERROR, "Codec AVOption %s (%s) is not an " + "encoding option.\n", e->key, option->help ? option->help : ""); + exit_program(1); + } + + // gop_timecode is injected by generic code but not always used + if (!strcmp(e->key, "gop_timecode")) + continue; + + av_log(mux, AV_LOG_WARNING, "Codec AVOption %s (%s) has not been used " + "for any stream. The most likely reason is either wrong type " + "(e.g. a video option with no video streams) or that it is a " + "private option of some encoder which was not actually used for " + "any stream.\n", e->key, option->help ? option->help : ""); + } + av_dict_free(&unused_opts); +} + +static const char *output_file_item_name(void *obj) +{ + const Muxer *mux = obj; + + return mux->log_name; +} + +static const AVClass output_file_class = { + .class_name = "OutputFile", + .version = LIBAVUTIL_VERSION_INT, + .item_name = output_file_item_name, + .category = AV_CLASS_CATEGORY_MUXER, +}; + +static Muxer *mux_alloc(void) +{ + Muxer *mux = allocate_array_elem(&output_files, sizeof(*mux), &nb_output_files); + + mux->of.clazz = &output_file_class; + mux->of.index = nb_output_files - 1; + + snprintf(mux->log_name, sizeof(mux->log_name), "out#%d", mux->of.index); + + return mux; +} + +int of_open(const OptionsContext *o, const char *filename) +{ + Muxer *mux; + AVFormatContext *oc; + int err; + OutputFile *of; + + int64_t recording_time = o->recording_time; + int64_t stop_time = o->stop_time; + + mux = mux_alloc(); + of = &mux->of; + + if (stop_time != INT64_MAX && recording_time != INT64_MAX) { + stop_time = INT64_MAX; + av_log(mux, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); + } + + if (stop_time != INT64_MAX && recording_time == INT64_MAX) { + int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; + if (stop_time <= start_time) { + av_log(mux, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); + exit_program(1); + } else { + recording_time = stop_time - start_time; + } + } + + of->recording_time = recording_time; + of->start_time = o->start_time; + of->shortest = o->shortest; + + mux->thread_queue_size = o->thread_queue_size > 0 ? o->thread_queue_size : 8; + mux->limit_filesize = o->limit_filesize; + av_dict_copy(&mux->opts, o->g->format_opts, 0); + + if (!strcmp(filename, "-")) + filename = "pipe:"; + + err = avformat_alloc_output_context2(&oc, NULL, o->format, filename); + if (!oc) { + print_error(filename, err); + exit_program(1); + } + mux->fc = oc; + + av_strlcat(mux->log_name, "/", sizeof(mux->log_name)); + av_strlcat(mux->log_name, oc->oformat->name, sizeof(mux->log_name)); + + if (strcmp(oc->oformat->name, "rtp")) + want_sdp = 0; + + of->format = oc->oformat; + if (recording_time != INT64_MAX) + oc->duration = recording_time; + + oc->interrupt_callback = int_cb; + + if (o->bitexact) { + oc->flags |= AVFMT_FLAG_BITEXACT; + of->bitexact = 1; + } else { + of->bitexact = check_opt_bitexact(oc, mux->opts, "fflags", + AVFMT_FLAG_BITEXACT); + } + + /* create all output streams for this file */ + create_streams(mux, o); + + /* check if all codec options have been used */ + validate_enc_avopt(mux, o->g->codec_opts); + + /* set the decoding_needed flags and create simple filtergraphs */ + for (int i = 0; i < of->nb_streams; i++) { + OutputStream *ost = of->streams[i]; + + if (ost->enc_ctx && ost->ist) { + InputStream *ist = ost->ist; + ist->decoding_needed |= DECODING_FOR_OST; + ist->processing_needed = 1; + + if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || + ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + err = init_simple_filtergraph(ist, ost); + if (err < 0) { + av_log(ost, AV_LOG_ERROR, + "Error initializing a simple filtergraph\n"); + exit_program(1); + } + } + } else if (ost->ist) { + ost->ist->processing_needed = 1; + } + + /* set the filter output constraints */ + if (ost->filter) { + const AVCodec *c = ost->enc_ctx->codec; + OutputFilter *f = ost->filter; + switch (ost->enc_ctx->codec_type) { + case AVMEDIA_TYPE_VIDEO: + f->frame_rate = ost->frame_rate; + f->width = ost->enc_ctx->width; + f->height = ost->enc_ctx->height; + if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { + f->format = ost->enc_ctx->pix_fmt; + } else { + f->formats = c->pix_fmts; + } + break; + case AVMEDIA_TYPE_AUDIO: + if (ost->enc_ctx->sample_fmt != AV_SAMPLE_FMT_NONE) { + f->format = ost->enc_ctx->sample_fmt; + } else { + f->formats = c->sample_fmts; + } + if (ost->enc_ctx->sample_rate) { + f->sample_rate = ost->enc_ctx->sample_rate; + } else { + f->sample_rates = c->supported_samplerates; + } + if (ost->enc_ctx->ch_layout.nb_channels) { + set_channel_layout(f, ost); + } else if (c->ch_layouts) { + f->ch_layouts = c->ch_layouts; + } + break; + } + } + } + + /* check filename in case of an image number is expected */ + if (oc->oformat->flags & AVFMT_NEEDNUMBER) { + if (!av_filename_number_test(oc->url)) { + print_error(oc->url, AVERROR(EINVAL)); + exit_program(1); + } + } + + if (!(oc->oformat->flags & AVFMT_NOFILE)) { + /* test if it already exists to avoid losing precious files */ + assert_file_overwrite(filename); + + /* open the file */ + if ((err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, + &oc->interrupt_callback, + &mux->opts)) < 0) { + print_error(filename, err); + exit_program(1); + } + } else if (strcmp(oc->oformat->name, "image2")==0 && !av_filename_number_test(filename)) + assert_file_overwrite(filename); + + if (o->mux_preload) { + av_dict_set_int(&mux->opts, "preload", o->mux_preload*AV_TIME_BASE, 0); + } + oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); + + /* copy metadata and chapters from input files */ + copy_meta(mux, o); + + of_add_programs(mux, o); + of_add_metadata(of, oc, o); + + err = set_dispositions(mux, o); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error setting output stream dispositions\n"); + exit_program(1); + } + + // parse forced keyframe specifications; + // must be done after chapters are created + err = process_forced_keyframes(mux, o); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error processing forced keyframes\n"); + exit_program(1); + } + + err = setup_sync_queues(mux, oc, o->shortest_buf_duration * AV_TIME_BASE); + if (err < 0) { + av_log(mux, AV_LOG_FATAL, "Error setting up output sync queues\n"); + exit_program(1); + } + + of->url = filename; + + /* write the header for files with no streams */ + if (of->format->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) { + int ret = mux_check_init(mux); + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/linux/src/fftools_ffmpeg_opt.c b/linux/src/fftools_ffmpeg_opt.c index 12c0e23..0ae6c67 100644 --- a/linux/src/fftools_ffmpeg_opt.c +++ b/linux/src/fftools_ffmpeg_opt.c @@ -1,6 +1,7 @@ /* * ffmpeg option parsing * Copyright (c) 2018-2020 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,14 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - include fftools_ffmpeg_mux.h added + * - fftools header names updated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 08.2020 @@ -60,8 +69,10 @@ #endif #include "fftools_ffmpeg.h" -#include "fftools_fopen_utf8.h" +#include "fftools_ffmpeg_mux.h" #include "fftools_cmdutils.h" +#include "fftools_opt_common.h" +#include "fftools_sync_queue.h" #include "libavformat/avformat.h" @@ -75,7 +86,7 @@ #include "libavutil/avutil.h" #include "libavutil/bprint.h" #include "libavutil/channel_layout.h" -#include "libavutil/getenv_utf8.h" +#include "libavutil/display.h" #include "libavutil/intreadwrite.h" #include "libavutil/fifo.h" #include "libavutil/mathematics.h" @@ -84,99 +95,10 @@ #include "libavutil/pixdesc.h" #include "libavutil/pixfmt.h" -#define DEFAULT_PASS_LOGFILENAME_PREFIX "ffmpeg2pass" - -#define SPECIFIER_OPT_FMT_str "%s" -#define SPECIFIER_OPT_FMT_i "%i" -#define SPECIFIER_OPT_FMT_i64 "%"PRId64 -#define SPECIFIER_OPT_FMT_ui64 "%"PRIu64 -#define SPECIFIER_OPT_FMT_f "%f" -#define SPECIFIER_OPT_FMT_dbl "%lf" - -static const char *const opt_name_codec_names[] = {"c", "codec", "acodec", "vcodec", "scodec", "dcodec", NULL}; -static const char *const opt_name_audio_channels[] = {"ac", NULL}; -static const char *const opt_name_audio_ch_layouts[] = {"channel_layout", "ch_layout", NULL}; -static const char *const opt_name_audio_sample_rate[] = {"ar", NULL}; -static const char *const opt_name_frame_rates[] = {"r", NULL}; -static const char *const opt_name_max_frame_rates[] = {"fpsmax", NULL}; -static const char *const opt_name_frame_sizes[] = {"s", NULL}; -static const char *const opt_name_frame_pix_fmts[] = {"pix_fmt", NULL}; -static const char *const opt_name_ts_scale[] = {"itsscale", NULL}; -static const char *const opt_name_hwaccels[] = {"hwaccel", NULL}; -static const char *const opt_name_hwaccel_devices[] = {"hwaccel_device", NULL}; -static const char *const opt_name_hwaccel_output_formats[] = {"hwaccel_output_format", NULL}; -static const char *const opt_name_autorotate[] = {"autorotate", NULL}; -static const char *const opt_name_autoscale[] = {"autoscale", NULL}; -static const char *const opt_name_max_frames[] = {"frames", "aframes", "vframes", "dframes", NULL}; -static const char *const opt_name_bitstream_filters[] = {"bsf", "absf", "vbsf", NULL}; -static const char *const opt_name_codec_tags[] = {"tag", "atag", "vtag", "stag", NULL}; -static const char *const opt_name_sample_fmts[] = {"sample_fmt", NULL}; -static const char *const opt_name_qscale[] = {"q", "qscale", NULL}; -static const char *const opt_name_forced_key_frames[] = {"forced_key_frames", NULL}; -static const char *const opt_name_fps_mode[] = {"fps_mode", NULL}; -static const char *const opt_name_force_fps[] = {"force_fps", NULL}; -static const char *const opt_name_frame_aspect_ratios[] = {"aspect", NULL}; -static const char *const opt_name_rc_overrides[] = {"rc_override", NULL}; -static const char *const opt_name_intra_matrices[] = {"intra_matrix", NULL}; -static const char *const opt_name_inter_matrices[] = {"inter_matrix", NULL}; -static const char *const opt_name_chroma_intra_matrices[] = {"chroma_intra_matrix", NULL}; -static const char *const opt_name_top_field_first[] = {"top", NULL}; -static const char *const opt_name_presets[] = {"pre", "apre", "vpre", "spre", NULL}; -static const char *const opt_name_copy_initial_nonkeyframes[] = {"copyinkf", NULL}; -static const char *const opt_name_copy_prior_start[] = {"copypriorss", NULL}; -static const char *const opt_name_filters[] = {"filter", "af", "vf", NULL}; -static const char *const opt_name_filter_scripts[] = {"filter_script", NULL}; -static const char *const opt_name_reinit_filters[] = {"reinit_filter", NULL}; -static const char *const opt_name_fix_sub_duration[] = {"fix_sub_duration", NULL}; -static const char *const opt_name_canvas_sizes[] = {"canvas_size", NULL}; -static const char *const opt_name_pass[] = {"pass", NULL}; -static const char *const opt_name_passlogfiles[] = {"passlogfile", NULL}; -static const char *const opt_name_max_muxing_queue_size[] = {"max_muxing_queue_size", NULL}; -static const char *const opt_name_muxing_queue_data_threshold[] = {"muxing_queue_data_threshold", NULL}; -static const char *const opt_name_guess_layout_max[] = {"guess_layout_max", NULL}; -static const char *const opt_name_apad[] = {"apad", NULL}; -static const char *const opt_name_discard[] = {"discard", NULL}; -static const char *const opt_name_disposition[] = {"disposition", NULL}; -static const char *const opt_name_time_bases[] = {"time_base", NULL}; -static const char *const opt_name_enc_time_bases[] = {"enc_time_base", NULL}; -static const char *const opt_name_bits_per_raw_sample[] = {"bits_per_raw_sample", NULL}; - -#define WARN_MULTIPLE_OPT_USAGE(name, type, so, st)\ -{\ - char namestr[128] = "";\ - const char *spec = so->specifier && so->specifier[0] ? so->specifier : "";\ - for (i = 0; opt_name_##name[i]; i++)\ - av_strlcatf(namestr, sizeof(namestr), "-%s%s", opt_name_##name[i], opt_name_##name[i+1] ? (opt_name_##name[i+2] ? ", " : " or ") : "");\ - av_log(NULL, AV_LOG_WARNING, "Multiple %s options specified for stream %d, only the last option '-%s%s%s "SPECIFIER_OPT_FMT_##type"' will be used.\n",\ - namestr, st->index, opt_name_##name[0], spec[0] ? ":" : "", spec, so->u.type);\ -} - -#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\ -{\ - int i, ret, matches = 0;\ - SpecifierOpt *so;\ - for (i = 0; i < o->nb_ ## name; i++) {\ - char *spec = o->name[i].specifier;\ - if ((ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\ - outvar = o->name[i].u.type;\ - so = &o->name[i];\ - matches++;\ - } else if (ret < 0)\ - exit_program(1);\ - }\ - if (matches > 1)\ - WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\ -} - -#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\ -{\ - int i;\ - for (i = 0; i < o->nb_ ## name; i++) {\ - char *spec = o->name[i].specifier;\ - if (!strcmp(spec, mediatype))\ - outvar = o->name[i].u.type;\ - }\ -} +const char *const opt_name_codec_names[] = {"c", "codec", "acodec", "vcodec", "scodec", "dcodec", NULL}; +const char *const opt_name_frame_rates[] = {"r", NULL}; +const char *const opt_name_codec_tags[] = {"tag", "atag", "vtag", "stag", NULL}; +const char *const opt_name_top_field_first[] = {"top", NULL}; __thread HWDevice *filter_hw_device; @@ -187,8 +109,6 @@ __thread float audio_drift_threshold = 0.1; __thread float dts_delta_threshold = 10; __thread float dts_error_threshold = 3600*30; -__thread int audio_volume = 256; -__thread int audio_sync_method = 0; __thread enum VideoSyncMethod video_sync_method = VSYNC_AUTO; __thread float frame_drop_threshold = 0; __thread int do_benchmark = 0; @@ -214,12 +134,12 @@ __thread int64_t stats_period = 500000; __thread int file_overwrite = 0; __thread int no_file_overwrite = 0; +#if FFMPEG_OPT_PSNR __thread int do_psnr = 0; -__thread int input_stream_potentially_available = 0; +#endif __thread int ignore_unknown_streams = 0; __thread int copy_unknown_streams = 0; __thread int recast_media = 0; -__thread int find_stream_info = 1; extern __thread OptionDef *ffmpeg_options; @@ -250,7 +170,9 @@ void uninit_options(OptionsContext *o) for (i = 0; i < o->nb_stream_maps; i++) av_freep(&o->stream_maps[i].linklabel); av_freep(&o->stream_maps); +#if FFMPEG_OPT_MAP_CHANNEL av_freep(&o->audio_channel_maps); +#endif av_freep(&o->streamid_map); av_freep(&o->attachments); } @@ -264,11 +186,13 @@ void init_options(OptionsContext *o) o->start_time = AV_NOPTS_VALUE; o->start_time_eof = AV_NOPTS_VALUE; o->recording_time = INT64_MAX; - o->limit_filesize = UINT64_MAX; + o->limit_filesize = INT64_MAX; o->chapters_input_file = INT_MAX; o->accurate_seek = 1; o->thread_queue_size = -1; o->input_sync_ref = -1; + o->find_stream_info = 1; + o->shortest_buf_duration = 10.f; } int show_hwaccels(void *optctx, const char *opt, const char *arg) @@ -284,12 +208,12 @@ int show_hwaccels(void *optctx, const char *opt, const char *arg) } /* return a copy of the input with the stream specifiers removed from the keys */ -AVDictionary *strip_specifiers(AVDictionary *dict) +AVDictionary *strip_specifiers(const AVDictionary *dict) { const AVDictionaryEntry *e = NULL; AVDictionary *ret = NULL; - while ((e = av_dict_get(dict, "", e, AV_DICT_IGNORE_SUFFIX))) { + while ((e = av_dict_iterate(dict, e))) { char *p = strchr(e->key, ':'); if (p) @@ -321,6 +245,44 @@ int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_id return 0; } +/* Correct input file start times based on enabled streams */ +void correct_input_start_times(void) +{ + for (int i = 0; i < nb_input_files; i++) { + InputFile *ifile = input_files[i]; + AVFormatContext *is = ifile->ctx; + int64_t new_start_time = INT64_MAX, diff, abs_start_seek; + + ifile->start_time_effective = is->start_time; + + if (is->start_time == AV_NOPTS_VALUE || + !(is->iformat->flags & AVFMT_TS_DISCONT)) + continue; + + for (int j = 0; j < is->nb_streams; j++) { + AVStream *st = is->streams[j]; + if(st->discard == AVDISCARD_ALL || st->start_time == AV_NOPTS_VALUE) + continue; + new_start_time = FFMIN(new_start_time, av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q)); + } + + diff = new_start_time - is->start_time; + if (diff) { + av_log(NULL, AV_LOG_VERBOSE, "Correcting start time of Input #%d by %"PRId64" us.\n", i, diff); + ifile->start_time_effective = new_start_time; + if (copy_ts && start_at_zero) + ifile->ts_offset = -new_start_time; + else if (!copy_ts) { + abs_start_seek = is->start_time + ((ifile->start_time != AV_NOPTS_VALUE) ? ifile->start_time : 0); + ifile->ts_offset = abs_start_seek > new_start_time ? -abs_start_seek : -new_start_time; + } else if (copy_ts) + ifile->ts_offset = 0; + + ifile->ts_offset += ifile->input_ts_offset; + } + } +} + int apply_sync_offsets(void) { for (int i = 0; i < nb_input_files; i++) { @@ -349,9 +311,9 @@ int apply_sync_offsets(void) if (self->ctx->start_time_realtime != AV_NOPTS_VALUE && ref->ctx->start_time_realtime != AV_NOPTS_VALUE) { self_start_time = self->ctx->start_time_realtime; ref_start_time = ref->ctx->start_time_realtime; - } else if (self->ctx->start_time != AV_NOPTS_VALUE && ref->ctx->start_time != AV_NOPTS_VALUE) { - self_start_time = self->ctx->start_time; - ref_start_time = ref->ctx->start_time; + } else if (self->start_time_effective != AV_NOPTS_VALUE && ref->start_time_effective != AV_NOPTS_VALUE) { + self_start_time = self->start_time_effective; + ref_start_time = ref->start_time_effective; } else { start_times_set = 0; } @@ -383,7 +345,7 @@ int opt_filter_threads(void *optctx, const char *opt, const char *arg) int opt_abort_on(void *optctx, const char *opt, const char *arg) { static const AVOption opts[] = { - { "abort_on" , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, (double)INT64_MAX, .unit = "flags" }, + { "abort_on" , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, (double)INT64_MAX, .unit = "flags" }, { "empty_output" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = ABORT_ON_FLAG_EMPTY_OUTPUT }, .unit = "flags" }, { "empty_output_stream", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM }, .unit = "flags" }, { NULL }, @@ -443,9 +405,10 @@ int opt_map(void *optctx, const char *opt, const char *arg) OptionsContext *o = optctx; StreamMap *m = NULL; int i, negative = 0, file_idx, disabled = 0; - int sync_file_idx = -1, sync_stream_idx = 0; - char *p, *sync; - char *map; +#if FFMPEG_OPT_MAP_SYNC + char *sync; +#endif + char *map, *p; char *allow_unused; if (*arg == '-') { @@ -456,33 +419,13 @@ int opt_map(void *optctx, const char *opt, const char *arg) if (!map) return AVERROR(ENOMEM); +#if FFMPEG_OPT_MAP_SYNC /* parse sync stream first, just pick first matching stream */ if ((sync = strchr(map, ','))) { *sync = 0; - sync_file_idx = strtol(sync + 1, &sync, 0); - if (sync_file_idx >= nb_input_files || sync_file_idx < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid sync file index: %d.\n", sync_file_idx); - exit_program(1); - } - if (*sync) - sync++; - for (i = 0; i < input_files[sync_file_idx]->nb_streams; i++) - if (check_stream_specifier(input_files[sync_file_idx]->ctx, - input_files[sync_file_idx]->ctx->streams[i], sync) == 1) { - sync_stream_idx = i; - break; - } - if (i == input_files[sync_file_idx]->nb_streams) { - av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s does not " - "match any streams.\n", arg); - exit_program(1); - } - if (input_streams[input_files[sync_file_idx]->ist_index + sync_stream_idx]->user_set_discard == AVDISCARD_ALL) { - av_log(NULL, AV_LOG_FATAL, "Sync stream specification in map %s matches a disabled input " - "stream.\n", arg); - exit_program(1); - } + av_log(NULL, AV_LOG_WARNING, "Specifying a sync stream is deprecated and has no effect\n"); } +#endif if (map[0] == '[') { @@ -518,7 +461,7 @@ int opt_map(void *optctx, const char *opt, const char *arg) if (check_stream_specifier(input_files[file_idx]->ctx, input_files[file_idx]->ctx->streams[i], *p == ':' ? p + 1 : p) <= 0) continue; - if (input_streams[input_files[file_idx]->ist_index + i]->user_set_discard == AVDISCARD_ALL) { + if (input_files[file_idx]->streams[i]->user_set_discard == AVDISCARD_ALL) { disabled = 1; continue; } @@ -527,14 +470,6 @@ int opt_map(void *optctx, const char *opt, const char *arg) m->file_index = file_idx; m->stream_index = i; - - if (sync_file_idx >= 0) { - m->sync_file_index = sync_file_idx; - m->sync_stream_index = sync_stream_idx; - } else { - m->sync_file_index = file_idx; - m->sync_stream_index = i; - } } } @@ -564,6 +499,7 @@ int opt_attach(void *optctx, const char *opt, const char *arg) return 0; } +#if FFMPEG_OPT_MAP_CHANNEL int opt_map_channel(void *optctx, const char *opt, const char *arg) { OptionsContext *o = optctx; @@ -572,6 +508,12 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) AudioChannelMap *m; char *allow_unused; char *mapchan; + + av_log(NULL, AV_LOG_WARNING, + "The -%s option is deprecated and will be removed. " + "It can be replaced by the 'pan' filter, or in some cases by " + "combinations of 'channelsplit', 'channelmap', 'amerge' filters.\n", opt); + mapchan = av_strdup(arg); if (!mapchan) return AVERROR(ENOMEM); @@ -625,7 +567,7 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) if ((allow_unused = strchr(mapchan, '?'))) *allow_unused = 0; if (m->channel_idx < 0 || m->channel_idx >= st->codecpar->ch_layout.nb_channels || - input_streams[input_files[m->file_idx]->ist_index + m->stream_idx]->user_set_discard == AVDISCARD_ALL) { + input_files[m->file_idx]->streams[m->stream_idx]->user_set_discard == AVDISCARD_ALL) { if (allow_unused) { av_log(NULL, AV_LOG_VERBOSE, "mapchan: invalid audio channel #%d.%d.%d\n", m->file_idx, m->stream_idx, m->channel_idx); @@ -640,6 +582,7 @@ int opt_map_channel(void *optctx, const char *opt, const char *arg) av_free(mapchan); return 0; } +#endif int opt_sdp_file(void *optctx, const char *opt, const char *arg) { @@ -709,130 +652,6 @@ int opt_filter_hw_device(void *optctx, const char *opt, const char *arg) return 0; } -/** - * Parse a metadata specifier passed as 'arg' parameter. - * @param arg metadata string to parse - * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram) - * @param index for type c/p, chapter/program index is written here - * @param stream_spec for type s, the stream specifier is written here - */ -void parse_meta_type(char *arg, char *type, int *index, const char **stream_spec) -{ - if (*arg) { - *type = *arg; - switch (*arg) { - case 'g': - break; - case 's': - if (*(++arg) && *arg != ':') { - av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg); - exit_program(1); - } - *stream_spec = *arg == ':' ? arg + 1 : ""; - break; - case 'c': - case 'p': - if (*(++arg) == ':') - *index = strtol(++arg, NULL, 0); - break; - default: - av_log(NULL, AV_LOG_FATAL, "Invalid metadata type %c.\n", *arg); - exit_program(1); - } - } else - *type = 'g'; -} - -int fftools_copy_metadata(char *outspec, char *inspec, AVFormatContext *oc, AVFormatContext *ic, OptionsContext *o) -{ - AVDictionary **meta_in = NULL; - AVDictionary **meta_out = NULL; - int i, ret = 0; - char type_in, type_out; - const char *istream_spec = NULL, *ostream_spec = NULL; - int idx_in = 0, idx_out = 0; - - parse_meta_type(inspec, &type_in, &idx_in, &istream_spec); - parse_meta_type(outspec, &type_out, &idx_out, &ostream_spec); - - if (!ic) { - if (type_out == 'g' || !*outspec) - o->metadata_global_manual = 1; - if (type_out == 's' || !*outspec) - o->metadata_streams_manual = 1; - if (type_out == 'c' || !*outspec) - o->metadata_chapters_manual = 1; - return 0; - } - - if (type_in == 'g' || type_out == 'g') - o->metadata_global_manual = 1; - if (type_in == 's' || type_out == 's') - o->metadata_streams_manual = 1; - if (type_in == 'c' || type_out == 'c') - o->metadata_chapters_manual = 1; - - /* ic is NULL when just disabling automatic mappings */ - if (!ic) - return 0; - -#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ - if ((index) < 0 || (index) >= (nb_elems)) {\ - av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\ - (desc), (index));\ - exit_program(1);\ - } - -#define SET_DICT(type, meta, context, index)\ - switch (type) {\ - case 'g':\ - meta = &context->metadata;\ - break;\ - case 'c':\ - METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\ - meta = &context->chapters[index]->metadata;\ - break;\ - case 'p':\ - METADATA_CHECK_INDEX(index, context->nb_programs, "program")\ - meta = &context->programs[index]->metadata;\ - break;\ - case 's':\ - break; /* handled separately below */ \ - default: av_assert0(0);\ - }\ - - SET_DICT(type_in, meta_in, ic, idx_in); - SET_DICT(type_out, meta_out, oc, idx_out); - - /* for input streams choose first matching stream */ - if (type_in == 's') { - for (i = 0; i < ic->nb_streams; i++) { - if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) { - meta_in = &ic->streams[i]->metadata; - break; - } else if (ret < 0) - exit_program(1); - } - if (!meta_in) { - av_log(NULL, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec); - exit_program(1); - } - } - - if (type_out == 's') { - for (i = 0; i < oc->nb_streams; i++) { - if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) { - meta_out = &oc->streams[i]->metadata; - av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); - } else if (ret < 0) - exit_program(1); - } - } else - av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); - - return 0; -} - int opt_recording_timestamp(void *optctx, const char *opt, const char *arg) { OptionsContext *o = optctx; @@ -848,7 +667,8 @@ int opt_recording_timestamp(void *optctx, const char *opt, const char *arg) return 0; } -const AVCodec *find_codec_or_die(const char *name, enum AVMediaType type, int encoder) +const AVCodec *find_codec_or_die(void *logctx, const char *name, + enum AVMediaType type, int encoder) { const AVCodecDescriptor *desc; const char *codec_string = encoder ? "encoder" : "decoder"; @@ -862,247 +682,21 @@ const AVCodec *find_codec_or_die(const char *name, enum AVMediaType type, int en codec = encoder ? avcodec_find_encoder(desc->id) : avcodec_find_decoder(desc->id); if (codec) - av_log(NULL, AV_LOG_VERBOSE, "Matched %s '%s' for codec '%s'.\n", + av_log(logctx, AV_LOG_VERBOSE, "Matched %s '%s' for codec '%s'.\n", codec_string, codec->name, desc->name); } if (!codec) { - av_log(NULL, AV_LOG_FATAL, "Unknown %s '%s'\n", codec_string, name); + av_log(logctx, AV_LOG_FATAL, "Unknown %s '%s'\n", codec_string, name); exit_program(1); } if (codec->type != type && !recast_media) { - av_log(NULL, AV_LOG_FATAL, "Invalid %s type '%s'\n", codec_string, name); + av_log(logctx, AV_LOG_FATAL, "Invalid %s type '%s'\n", codec_string, name); exit_program(1); } return codec; } -const AVCodec *choose_decoder(OptionsContext *o, AVFormatContext *s, AVStream *st) -{ - char *codec_name = NULL; - - MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st); - if (codec_name) { - const AVCodec *codec = find_codec_or_die(codec_name, st->codecpar->codec_type, 0); - st->codecpar->codec_id = codec->id; - if (recast_media && st->codecpar->codec_type != codec->type) - st->codecpar->codec_type = codec->type; - return codec; - } else - return avcodec_find_decoder(st->codecpar->codec_id); -} - -/* Add all the streams from the given input file to the global - * list of input streams. */ -void add_input_streams(OptionsContext *o, AVFormatContext *ic) -{ - int i, ret; - - for (i = 0; i < ic->nb_streams; i++) { - AVStream *st = ic->streams[i]; - AVCodecParameters *par = st->codecpar; - InputStream *ist; - char *framerate = NULL, *hwaccel_device = NULL; - const char *hwaccel = NULL; - char *hwaccel_output_format = NULL; - char *codec_tag = NULL; - char *next; - char *discard_str = NULL; - const AVClass *cc = avcodec_get_class(); - const AVOption *discard_opt = av_opt_find(&cc, "skip_frame", NULL, - 0, AV_OPT_SEARCH_FAKE_OBJ); - - ist = ALLOC_ARRAY_ELEM(input_streams, nb_input_streams); - ist->st = st; - ist->file_index = nb_input_files; - ist->discard = 1; - st->discard = AVDISCARD_ALL; - ist->nb_samples = 0; - ist->first_dts = AV_NOPTS_VALUE; - ist->min_pts = INT64_MAX; - ist->max_pts = INT64_MIN; - - ist->ts_scale = 1.0; - MATCH_PER_STREAM_OPT(ts_scale, dbl, ist->ts_scale, ic, st); - - ist->autorotate = 1; - MATCH_PER_STREAM_OPT(autorotate, i, ist->autorotate, ic, st); - - MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st); - if (codec_tag) { - uint32_t tag = strtol(codec_tag, &next, 0); - if (*next) - tag = AV_RL32(codec_tag); - st->codecpar->codec_tag = tag; - } - - ist->dec = choose_decoder(o, ic, st); - ist->decoder_opts = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ic, st, ist->dec); - - ist->reinit_filters = -1; - MATCH_PER_STREAM_OPT(reinit_filters, i, ist->reinit_filters, ic, st); - - MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st); - ist->user_set_discard = AVDISCARD_NONE; - - if ((o->video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) || - (o->audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || - (o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) || - (o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA)) - ist->user_set_discard = AVDISCARD_ALL; - - if (discard_str && av_opt_eval_int(&cc, discard_opt, discard_str, &ist->user_set_discard) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing discard %s.\n", - discard_str); - exit_program(1); - } - - ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE; - ist->prev_pkt_pts = AV_NOPTS_VALUE; - - ist->dec_ctx = avcodec_alloc_context3(ist->dec); - if (!ist->dec_ctx) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the decoder context.\n"); - exit_program(1); - } - - ret = avcodec_parameters_to_context(ist->dec_ctx, par); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); - exit_program(1); - } - - ist->decoded_frame = av_frame_alloc(); - if (!ist->decoded_frame) - exit_program(1); - - ist->pkt = av_packet_alloc(); - if (!ist->pkt) - exit_program(1); - - if (o->bitexact) - ist->dec_ctx->flags |= AV_CODEC_FLAG_BITEXACT; - - switch (par->codec_type) { - case AVMEDIA_TYPE_VIDEO: - if(!ist->dec) - ist->dec = avcodec_find_decoder(par->codec_id); - - // avformat_find_stream_info() doesn't set this for us anymore. - ist->dec_ctx->framerate = st->avg_frame_rate; - - MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st); - if (framerate && av_parse_video_rate(&ist->framerate, - framerate) < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing framerate %s.\n", - framerate); - exit_program(1); - } - - ist->top_field_first = -1; - MATCH_PER_STREAM_OPT(top_field_first, i, ist->top_field_first, ic, st); - - MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st); - MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, - hwaccel_output_format, ic, st); - - if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "cuvid")) { - av_log(NULL, AV_LOG_WARNING, - "WARNING: defaulting hwaccel_output_format to cuda for compatibility " - "with old commandlines. This behaviour is DEPRECATED and will be removed " - "in the future. Please explicitly set \"-hwaccel_output_format cuda\".\n"); - ist->hwaccel_output_format = AV_PIX_FMT_CUDA; - } else if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "qsv")) { - av_log(NULL, AV_LOG_WARNING, - "WARNING: defaulting hwaccel_output_format to qsv for compatibility " - "with old commandlines. This behaviour is DEPRECATED and will be removed " - "in the future. Please explicitly set \"-hwaccel_output_format qsv\".\n"); - ist->hwaccel_output_format = AV_PIX_FMT_QSV; - } else if (hwaccel_output_format) { - ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format); - if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output " - "format: %s", hwaccel_output_format); - } - } else { - ist->hwaccel_output_format = AV_PIX_FMT_NONE; - } - - if (hwaccel) { - // The NVDEC hwaccels use a CUDA device, so remap the name here. - if (!strcmp(hwaccel, "nvdec") || !strcmp(hwaccel, "cuvid")) - hwaccel = "cuda"; - - if (!strcmp(hwaccel, "none")) - ist->hwaccel_id = HWACCEL_NONE; - else if (!strcmp(hwaccel, "auto")) - ist->hwaccel_id = HWACCEL_AUTO; - else { - enum AVHWDeviceType type = av_hwdevice_find_type_by_name(hwaccel); - if (type != AV_HWDEVICE_TYPE_NONE) { - ist->hwaccel_id = HWACCEL_GENERIC; - ist->hwaccel_device_type = type; - } - - if (!ist->hwaccel_id) { - av_log(NULL, AV_LOG_FATAL, "Unrecognized hwaccel: %s.\n", - hwaccel); - av_log(NULL, AV_LOG_FATAL, "Supported hwaccels: "); - type = AV_HWDEVICE_TYPE_NONE; - while ((type = av_hwdevice_iterate_types(type)) != - AV_HWDEVICE_TYPE_NONE) - av_log(NULL, AV_LOG_FATAL, "%s ", - av_hwdevice_get_type_name(type)); - av_log(NULL, AV_LOG_FATAL, "\n"); - exit_program(1); - } - } - } - - MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st); - if (hwaccel_device) { - ist->hwaccel_device = av_strdup(hwaccel_device); - if (!ist->hwaccel_device) - exit_program(1); - } - - ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE; - - break; - case AVMEDIA_TYPE_AUDIO: - ist->guess_layout_max = INT_MAX; - MATCH_PER_STREAM_OPT(guess_layout_max, i, ist->guess_layout_max, ic, st); - guess_input_channel_layout(ist); - break; - case AVMEDIA_TYPE_DATA: - case AVMEDIA_TYPE_SUBTITLE: { - char *canvas_size = NULL; - if(!ist->dec) - ist->dec = avcodec_find_decoder(par->codec_id); - MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st); - MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st); - if (canvas_size && - av_parse_video_size(&ist->dec_ctx->width, &ist->dec_ctx->height, canvas_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid canvas size: %s.\n", canvas_size); - exit_program(1); - } - break; - } - case AVMEDIA_TYPE_ATTACHMENT: - case AVMEDIA_TYPE_UNKNOWN: - break; - default: - abort(); - } - - ret = avcodec_parameters_from_context(par, ist->dec_ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n"); - exit_program(1); - } - } -} - void assert_file_overwrite(const char *filename) { const char *proto_name = avio_find_protocol_name(filename); @@ -1145,2006 +739,134 @@ void assert_file_overwrite(const char *filename) } } -void dump_attachment(AVStream *st, const char *filename) +/* read file contents into a string */ +char *file_read(const char *filename) { - int ret; - AVIOContext *out = NULL; - const AVDictionaryEntry *e; + AVIOContext *pb = NULL; + int ret = avio_open(&pb, filename, AVIO_FLAG_READ); + AVBPrint bprint; + char *str; - if (!st->codecpar->extradata_size) { - av_log(NULL, AV_LOG_WARNING, "No extradata to dump in stream #%d:%d.\n", - nb_input_files - 1, st->index); - return; + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename); + return NULL; } - if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) - filename = e->value; - if (!*filename) { - av_log(NULL, AV_LOG_FATAL, "No filename specified and no 'filename' tag" - "in stream #%d:%d.\n", nb_input_files - 1, st->index); - exit_program(1); + + av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); + ret = avio_read_to_bprint(pb, &bprint, SIZE_MAX); + avio_closep(&pb); + if (ret < 0) { + av_bprint_finalize(&bprint, NULL); + return NULL; } + ret = av_bprint_finalize(&bprint, &str); + if (ret < 0) + return NULL; + return str; +} - assert_file_overwrite(filename); +/* arg format is "output-stream-index:streamid-value". */ +int opt_streamid(void *optctx, const char *opt, const char *arg) +{ + OptionsContext *o = optctx; + int idx; + char *p; + char idx_str[16]; - if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &int_cb, NULL)) < 0) { - av_log(NULL, AV_LOG_FATAL, "Could not open file %s for writing.\n", - filename); + av_strlcpy(idx_str, arg, sizeof(idx_str)); + p = strchr(idx_str, ':'); + if (!p) { + av_log(NULL, AV_LOG_FATAL, + "Invalid value '%s' for option '%s', required syntax is 'index:value'\n", + arg, opt); exit_program(1); } - - avio_write(out, st->codecpar->extradata, st->codecpar->extradata_size); - avio_flush(out); - avio_close(out); + *p++ = '\0'; + idx = parse_number_or_die(opt, idx_str, OPT_INT, 0, MAX_STREAMS-1); + o->streamid_map = grow_array(o->streamid_map, sizeof(*o->streamid_map), &o->nb_streamid_map, idx+1); + o->streamid_map[idx] = parse_number_or_die(opt, p, OPT_INT, 0, INT_MAX); + return 0; } -int open_input_file(OptionsContext *o, const char *filename) +int init_complex_filters(void) { - InputFile *f; - AVFormatContext *ic; - const AVInputFormat *file_iformat = NULL; - int err, i, ret; - int64_t timestamp; - AVDictionary *unused_opts = NULL; - const AVDictionaryEntry *e = NULL; - char * video_codec_name = NULL; - char * audio_codec_name = NULL; - char *subtitle_codec_name = NULL; - char * data_codec_name = NULL; - int scan_all_pmts_set = 0; - - if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) { - o->stop_time = INT64_MAX; - av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); - } - - if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) { - int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; - if (o->stop_time <= start_time) { - av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); - exit_program(1); - } else { - o->recording_time = o->stop_time - start_time; - } - } + int i, ret = 0; - if (o->format) { - if (!(file_iformat = av_find_input_format(o->format))) { - av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format); - exit_program(1); - } + for (i = 0; i < nb_filtergraphs; i++) { + ret = init_complex_filtergraph(filtergraphs[i]); + if (ret < 0) + return ret; } + return 0; +} - if (!strcmp(filename, "-")) - filename = "pipe:"; - - stdin_interaction &= strncmp(filename, "pipe:", 5) && - strcmp(filename, "/dev/stdin"); +int opt_target(void *optctx, const char *opt, const char *arg) +{ + const OptionDef *options = ffmpeg_options; + OptionsContext *o = optctx; + enum { PAL, NTSC, FILM, UNKNOWN } norm = UNKNOWN; + static const char *const frame_rates[] = { "25", "30000/1001", "24000/1001" }; - /* get default parameters from command line */ - ic = avformat_alloc_context(); - if (!ic) { - print_error(filename, AVERROR(ENOMEM)); - exit_program(1); - } - if (o->nb_audio_sample_rate) { - av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0); - } - if (o->nb_audio_channels) { - const AVClass *priv_class; - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "ch_layout", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - char buf[32]; - snprintf(buf, sizeof(buf), "%dC", o->audio_channels[o->nb_audio_channels - 1].u.i); - av_dict_set(&o->g->format_opts, "ch_layout", buf, 0); - } - } - if (o->nb_audio_ch_layouts) { - const AVClass *priv_class; - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "ch_layout", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - av_dict_set(&o->g->format_opts, "ch_layout", o->audio_ch_layouts[o->nb_audio_ch_layouts - 1].u.str, 0); - } - } - if (o->nb_frame_rates) { - const AVClass *priv_class; - /* set the format-level framerate option; - * this is important for video grabbers, e.g. x11 */ - if (file_iformat && (priv_class = file_iformat->priv_class) && - av_opt_find(&priv_class, "framerate", NULL, 0, - AV_OPT_SEARCH_FAKE_OBJ)) { - av_dict_set(&o->g->format_opts, "framerate", - o->frame_rates[o->nb_frame_rates - 1].u.str, 0); + if (!strncmp(arg, "pal-", 4)) { + norm = PAL; + arg += 4; + } else if (!strncmp(arg, "ntsc-", 5)) { + norm = NTSC; + arg += 5; + } else if (!strncmp(arg, "film-", 5)) { + norm = FILM; + arg += 5; + } else { + /* Try to determine PAL/NTSC by peeking in the input files */ + if (nb_input_files) { + int i, j; + for (j = 0; j < nb_input_files; j++) { + for (i = 0; i < input_files[j]->nb_streams; i++) { + AVStream *st = input_files[j]->ctx->streams[i]; + int64_t fr; + if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + continue; + fr = st->time_base.den * 1000LL / st->time_base.num; + if (fr == 25000) { + norm = PAL; + break; + } else if ((fr == 29970) || (fr == 23976)) { + norm = NTSC; + break; + } + } + if (norm != UNKNOWN) + break; + } } + if (norm != UNKNOWN) + av_log(NULL, AV_LOG_INFO, "Assuming %s for target.\n", norm == PAL ? "PAL" : "NTSC"); } - if (o->nb_frame_sizes) { - av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0); - } - if (o->nb_frame_pix_fmts) - av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0); - - MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v"); - MATCH_PER_TYPE_OPT(codec_names, str, audio_codec_name, ic, "a"); - MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s"); - MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d"); - - if (video_codec_name) - ic->video_codec = find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0); - if (audio_codec_name) - ic->audio_codec = find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0); - if (subtitle_codec_name) - ic->subtitle_codec = find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0); - if (data_codec_name) - ic->data_codec = find_codec_or_die(data_codec_name , AVMEDIA_TYPE_DATA , 0); - - ic->video_codec_id = video_codec_name ? ic->video_codec->id : AV_CODEC_ID_NONE; - ic->audio_codec_id = audio_codec_name ? ic->audio_codec->id : AV_CODEC_ID_NONE; - ic->subtitle_codec_id = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE; - ic->data_codec_id = data_codec_name ? ic->data_codec->id : AV_CODEC_ID_NONE; - - ic->flags |= AVFMT_FLAG_NONBLOCK; - if (o->bitexact) - ic->flags |= AVFMT_FLAG_BITEXACT; - ic->interrupt_callback = int_cb; - - if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { - av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); - scan_all_pmts_set = 1; - } - /* open the input file with generic avformat function */ - err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts); - if (err < 0) { - print_error(filename, err); - if (err == AVERROR_PROTOCOL_NOT_FOUND) - av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename); + + if (norm == UNKNOWN) { + av_log(NULL, AV_LOG_FATAL, "Could not determine norm (PAL/NTSC/NTSC-Film) for target.\n"); + av_log(NULL, AV_LOG_FATAL, "Please prefix target with \"pal-\", \"ntsc-\" or \"film-\",\n"); + av_log(NULL, AV_LOG_FATAL, "or set a framerate with \"-r xxx\".\n"); exit_program(1); } - if (scan_all_pmts_set) - av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); - remove_avoptions(&o->g->format_opts, o->g->codec_opts); - assert_avoptions(o->g->format_opts); - /* apply forced codec ids */ - for (i = 0; i < ic->nb_streams; i++) - choose_decoder(o, ic, ic->streams[i]); - - if (find_stream_info) { - AVDictionary **opts = setup_find_stream_info_opts(ic, o->g->codec_opts); - int orig_nb_streams = ic->nb_streams; + if (!strcmp(arg, "vcd")) { + opt_video_codec(o, "c:v", "mpeg1video"); + opt_audio_codec(o, "c:a", "mp2"); + parse_option(o, "f", "vcd", options); - /* If not enough info to get the stream parameters, we decode the - first frames to get it. (used in mpeg case for example) */ - ret = avformat_find_stream_info(ic, opts); + parse_option(o, "s", norm == PAL ? "352x288" : "352x240", options); + parse_option(o, "r", frame_rates[norm], options); + opt_default(NULL, "g", norm == PAL ? "15" : "18"); - for (i = 0; i < orig_nb_streams; i++) - av_dict_free(&opts[i]); - av_freep(&opts); + opt_default(NULL, "b:v", "1150000"); + opt_default(NULL, "maxrate:v", "1150000"); + opt_default(NULL, "minrate:v", "1150000"); + opt_default(NULL, "bufsize:v", "327680"); // 40*1024*8; - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename); - if (ic->nb_streams == 0) { - avformat_close_input(&ic); - exit_program(1); - } - } - } - - if (o->start_time != AV_NOPTS_VALUE && o->start_time_eof != AV_NOPTS_VALUE) { - av_log(NULL, AV_LOG_WARNING, "Cannot use -ss and -sseof both, using -ss for %s\n", filename); - o->start_time_eof = AV_NOPTS_VALUE; - } - - if (o->start_time_eof != AV_NOPTS_VALUE) { - if (o->start_time_eof >= 0) { - av_log(NULL, AV_LOG_ERROR, "-sseof value must be negative; aborting\n"); - exit_program(1); - } - if (ic->duration > 0) { - o->start_time = o->start_time_eof + ic->duration; - if (o->start_time < 0) { - av_log(NULL, AV_LOG_WARNING, "-sseof value seeks to before start of file %s; ignored\n", filename); - o->start_time = AV_NOPTS_VALUE; - } - } else - av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename); - } - timestamp = (o->start_time == AV_NOPTS_VALUE) ? 0 : o->start_time; - /* add the stream start time */ - if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE) - timestamp += ic->start_time; - - /* if seeking requested, we execute it */ - if (o->start_time != AV_NOPTS_VALUE) { - int64_t seek_timestamp = timestamp; - - if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) { - int dts_heuristic = 0; - for (i=0; inb_streams; i++) { - const AVCodecParameters *par = ic->streams[i]->codecpar; - if (par->video_delay) { - dts_heuristic = 1; - break; - } - } - if (dts_heuristic) { - seek_timestamp -= 3*AV_TIME_BASE / 23; - } - } - ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0); - if (ret < 0) { - av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n", - filename, (double)timestamp / AV_TIME_BASE); - } - } - - /* update the current parameters so that they match the one of the input stream */ - add_input_streams(o, ic); - - /* dump the file content */ - av_dump_format(ic, nb_input_files, filename, 0); - - f = ALLOC_ARRAY_ELEM(input_files, nb_input_files); - - f->ctx = ic; - f->ist_index = nb_input_streams - ic->nb_streams; - f->start_time = o->start_time; - f->recording_time = o->recording_time; - f->input_sync_ref = o->input_sync_ref; - f->input_ts_offset = o->input_ts_offset; - f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp); - f->nb_streams = ic->nb_streams; - f->rate_emu = o->rate_emu; - f->accurate_seek = o->accurate_seek; - f->loop = o->loop; - f->duration = 0; - f->time_base = (AVRational){ 1, 1 }; - - f->readrate = o->readrate ? o->readrate : 0.0; - if (f->readrate < 0.0f) { - av_log(NULL, AV_LOG_ERROR, "Option -readrate for Input #%d is %0.3f; it must be non-negative.\n", nb_input_files, f->readrate); - exit_program(1); - } - if (f->readrate && f->rate_emu) { - av_log(NULL, AV_LOG_WARNING, "Both -readrate and -re set for Input #%d. Using -readrate %0.3f.\n", nb_input_files, f->readrate); - f->rate_emu = 0; - } - - f->pkt = av_packet_alloc(); - if (!f->pkt) - exit_program(1); -#if HAVE_THREADS - f->thread_queue_size = o->thread_queue_size; -#endif - - /* check if all codec options have been used */ - unused_opts = strip_specifiers(o->g->codec_opts); - for (i = f->ist_index; i < nb_input_streams; i++) { - e = NULL; - while ((e = av_dict_get(input_streams[i]->decoder_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) - av_dict_set(&unused_opts, e->key, NULL, 0); - } - - e = NULL; - while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) { - const AVClass *class = avcodec_get_class(); - const AVOption *option = av_opt_find(&class, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - const AVClass *fclass = avformat_get_class(); - const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - if (!option || foption) - continue; - - - if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) { - av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " - "input file #%d (%s) is not a decoding option.\n", e->key, - option->help ? option->help : "", nb_input_files - 1, - filename); - exit_program(1); - } - - av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " - "input file #%d (%s) has not been used for any stream. The most " - "likely reason is either wrong type (e.g. a video option with " - "no video streams) or that it is a private option of some decoder " - "which was not actually used for any stream.\n", e->key, - option->help ? option->help : "", nb_input_files - 1, filename); - } - av_dict_free(&unused_opts); - - for (i = 0; i < o->nb_dump_attachment; i++) { - int j; - - for (j = 0; j < ic->nb_streams; j++) { - AVStream *st = ic->streams[j]; - - if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1) - dump_attachment(st, o->dump_attachment[i].u.str); - } - } - - input_stream_potentially_available = 1; - - return 0; -} - -char *get_line(AVIOContext *s, AVBPrint *bprint) -{ - char c; - - while ((c = avio_r8(s)) && c != '\n') - av_bprint_chars(bprint, c, 1); - - if (!av_bprint_is_complete(bprint)) { - av_log(NULL, AV_LOG_FATAL, "Could not alloc buffer for reading preset.\n"); - exit_program(1); - } - return bprint->str; -} - -int get_preset_file_2(const char *preset_name, const char *codec_name, AVIOContext **s) -{ - int i, ret = -1; - char filename[1000]; - char *env_avconv_datadir = getenv_utf8("AVCONV_DATADIR"); - char *env_home = getenv_utf8("HOME"); - const char *base[3] = { env_avconv_datadir, - env_home, - AVCONV_DATADIR, - }; - - for (i = 0; i < FF_ARRAY_ELEMS(base) && ret < 0; i++) { - if (!base[i]) - continue; - if (codec_name) { - snprintf(filename, sizeof(filename), "%s%s/%s-%s.avpreset", base[i], - i != 1 ? "" : "/.avconv", codec_name, preset_name); - ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); - } - if (ret < 0) { - snprintf(filename, sizeof(filename), "%s%s/%s.avpreset", base[i], - i != 1 ? "" : "/.avconv", preset_name); - ret = avio_open2(s, filename, AVIO_FLAG_READ, &int_cb, NULL); - } - } - freeenv_utf8(env_home); - freeenv_utf8(env_avconv_datadir); - return ret; -} - -int choose_encoder(OptionsContext *o, AVFormatContext *s, OutputStream *ost) -{ - enum AVMediaType type = ost->st->codecpar->codec_type; - char *codec_name = NULL; - - if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO || type == AVMEDIA_TYPE_SUBTITLE) { - MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st); - if (!codec_name) { - ost->st->codecpar->codec_id = av_guess_codec(s->oformat, NULL, s->url, - NULL, ost->st->codecpar->codec_type); - ost->enc = avcodec_find_encoder(ost->st->codecpar->codec_id); - if (!ost->enc) { - av_log(NULL, AV_LOG_FATAL, "Automatic encoder selection failed for " - "output stream #%d:%d. Default encoder for format %s (codec %s) is " - "probably disabled. Please choose an encoder manually.\n", - ost->file_index, ost->index, s->oformat->name, - avcodec_get_name(ost->st->codecpar->codec_id)); - return AVERROR_ENCODER_NOT_FOUND; - } - } else if (!strcmp(codec_name, "copy")) - ost->stream_copy = 1; - else { - ost->enc = find_codec_or_die(codec_name, ost->st->codecpar->codec_type, 1); - ost->st->codecpar->codec_id = ost->enc->id; - } - ost->encoding_needed = !ost->stream_copy; - } else { - /* no encoding supported for other media types */ - ost->stream_copy = 1; - ost->encoding_needed = 0; - } - - return 0; -} - -OutputStream *new_output_stream(OptionsContext *o, AVFormatContext *oc, enum AVMediaType type, int source_index) -{ - OutputStream *ost; - AVStream *st = avformat_new_stream(oc, NULL); - int idx = oc->nb_streams - 1, ret = 0; - const char *bsfs = NULL, *time_base = NULL; - char *next, *codec_tag = NULL; - double qscale = -1; - int i; - - if (!st) { - av_log(NULL, AV_LOG_FATAL, "Could not alloc stream.\n"); - exit_program(1); - } - - if (oc->nb_streams - 1 < o->nb_streamid_map) - st->id = o->streamid_map[oc->nb_streams - 1]; - - ost = ALLOC_ARRAY_ELEM(output_streams, nb_output_streams); - - ost->file_index = nb_output_files - 1; - ost->index = idx; - ost->st = st; - ost->forced_kf_ref_pts = AV_NOPTS_VALUE; - st->codecpar->codec_type = type; - - ret = choose_encoder(o, oc, ost); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "Error selecting an encoder for stream " - "%d:%d\n", ost->file_index, ost->index); - exit_program(1); - } - - ost->enc_ctx = avcodec_alloc_context3(ost->enc); - if (!ost->enc_ctx) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding context.\n"); - exit_program(1); - } - ost->enc_ctx->codec_type = type; - - ost->ref_par = avcodec_parameters_alloc(); - if (!ost->ref_par) { - av_log(NULL, AV_LOG_ERROR, "Error allocating the encoding parameters.\n"); - exit_program(1); - } - - ost->filtered_frame = av_frame_alloc(); - if (!ost->filtered_frame) - exit_program(1); - - ost->pkt = av_packet_alloc(); - if (!ost->pkt) - exit_program(1); - - if (ost->enc) { - AVIOContext *s = NULL; - char *buf = NULL, *arg = NULL, *preset = NULL; - - ost->encoder_opts = filter_codec_opts(o->g->codec_opts, ost->enc->id, oc, st, ost->enc); - - MATCH_PER_STREAM_OPT(presets, str, preset, oc, st); - ost->autoscale = 1; - MATCH_PER_STREAM_OPT(autoscale, i, ost->autoscale, oc, st); - if (preset && (!(ret = get_preset_file_2(preset, ost->enc->name, &s)))) { - AVBPrint bprint; - av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); - do { - av_bprint_clear(&bprint); - buf = get_line(s, &bprint); - if (!buf[0] || buf[0] == '#') - continue; - if (!(arg = strchr(buf, '='))) { - av_log(NULL, AV_LOG_FATAL, "Invalid line found in the preset file.\n"); - exit_program(1); - } - *arg++ = 0; - av_dict_set(&ost->encoder_opts, buf, arg, AV_DICT_DONT_OVERWRITE); - } while (!s->eof_reached); - av_bprint_finalize(&bprint, NULL); - avio_closep(&s); - } - if (ret) { - av_log(NULL, AV_LOG_FATAL, - "Preset %s specified for stream %d:%d, but could not be opened.\n", - preset, ost->file_index, ost->index); - exit_program(1); - } - } else { - ost->encoder_opts = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, NULL); - } - - - if (o->bitexact) - ost->enc_ctx->flags |= AV_CODEC_FLAG_BITEXACT; - - MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st); - if (time_base) { - AVRational q; - if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || - q.num <= 0 || q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); - exit_program(1); - } - st->time_base = q; - } - - MATCH_PER_STREAM_OPT(enc_time_bases, str, time_base, oc, st); - if (time_base) { - AVRational q; - if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 || - q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid time base: %s\n", time_base); - exit_program(1); - } - ost->enc_timebase = q; - } - - ost->max_frames = INT64_MAX; - MATCH_PER_STREAM_OPT(max_frames, i64, ost->max_frames, oc, st); - for (i = 0; inb_max_frames; i++) { - char *p = o->max_frames[i].specifier; - if (!*p && type != AVMEDIA_TYPE_VIDEO) { - av_log(NULL, AV_LOG_WARNING, "Applying unspecific -frames to non video streams, maybe you meant -vframes ?\n"); - break; - } - } - - ost->copy_prior_start = -1; - MATCH_PER_STREAM_OPT(copy_prior_start, i, ost->copy_prior_start, oc ,st); - - MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st); - if (bsfs && *bsfs) { - ret = av_bsf_list_parse_str(bsfs, &ost->bsf_ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error parsing bitstream filter sequence '%s': %s\n", bsfs, av_err2str(ret)); - exit_program(1); - } - } - - MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st); - if (codec_tag) { - uint32_t tag = strtol(codec_tag, &next, 0); - if (*next) - tag = AV_RL32(codec_tag); - ost->st->codecpar->codec_tag = - ost->enc_ctx->codec_tag = tag; - } - - MATCH_PER_STREAM_OPT(qscale, dbl, qscale, oc, st); - if (qscale >= 0) { - ost->enc_ctx->flags |= AV_CODEC_FLAG_QSCALE; - ost->enc_ctx->global_quality = FF_QP2LAMBDA * qscale; - } - - MATCH_PER_STREAM_OPT(disposition, str, ost->disposition, oc, st); - ost->disposition = av_strdup(ost->disposition); - - ost->max_muxing_queue_size = 128; - MATCH_PER_STREAM_OPT(max_muxing_queue_size, i, ost->max_muxing_queue_size, oc, st); - - ost->muxing_queue_data_size = 0; - - ost->muxing_queue_data_threshold = 50*1024*1024; - MATCH_PER_STREAM_OPT(muxing_queue_data_threshold, i, ost->muxing_queue_data_threshold, oc, st); - - MATCH_PER_STREAM_OPT(bits_per_raw_sample, i, ost->bits_per_raw_sample, - oc, st); - - if (oc->oformat->flags & AVFMT_GLOBALHEADER) - ost->enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - - av_dict_copy(&ost->sws_dict, o->g->sws_dict, 0); - - av_dict_copy(&ost->swr_opts, o->g->swr_opts, 0); - if (ost->enc && av_get_exact_bits_per_sample(ost->enc->id) == 24) - av_dict_set(&ost->swr_opts, "output_sample_bits", "24", 0); - - ost->source_index = source_index; - if (source_index >= 0) { - ost->sync_ist = input_streams[source_index]; - input_streams[source_index]->discard = 0; - input_streams[source_index]->st->discard = input_streams[source_index]->user_set_discard; - } - ost->last_mux_dts = AV_NOPTS_VALUE; - - ost->muxing_queue = av_fifo_alloc2(8, sizeof(AVPacket*), 0); - if (!ost->muxing_queue) - exit_program(1); - - MATCH_PER_STREAM_OPT(copy_initial_nonkeyframes, i, - ost->copy_initial_nonkeyframes, oc, st); - - return ost; -} - -void parse_matrix_coeffs(uint16_t *dest, const char *str) -{ - int i; - const char *p = str; - for (i = 0;; i++) { - dest[i] = atoi(p); - if (i == 63) - break; - p = strchr(p, ','); - if (!p) { - av_log(NULL, AV_LOG_FATAL, "Syntax error in matrix \"%s\" at coeff %d\n", str, i); - exit_program(1); - } - p++; - } -} - -/* read file contents into a string */ -char *fftools_read_file(const char *filename) -{ - AVIOContext *pb = NULL; - int ret = avio_open(&pb, filename, AVIO_FLAG_READ); - AVBPrint bprint; - char *str; - - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error opening file %s.\n", filename); - return NULL; - } - - av_bprint_init(&bprint, 0, AV_BPRINT_SIZE_UNLIMITED); - ret = avio_read_to_bprint(pb, &bprint, SIZE_MAX); - avio_closep(&pb); - if (ret < 0) { - av_bprint_finalize(&bprint, NULL); - return NULL; - } - ret = av_bprint_finalize(&bprint, &str); - if (ret < 0) - return NULL; - return str; -} - -char *get_ost_filters(OptionsContext *o, AVFormatContext *oc, - OutputStream *ost) -{ - AVStream *st = ost->st; - - if (ost->filters_script && ost->filters) { - av_log(NULL, AV_LOG_ERROR, "Both -filter and -filter_script set for " - "output stream #%d:%d.\n", nb_output_files, st->index); - exit_program(1); - } - - if (ost->filters_script) - return fftools_read_file(ost->filters_script); - else if (ost->filters) - return av_strdup(ost->filters); - - return av_strdup(st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? - "null" : "anull"); -} - -void check_streamcopy_filters(OptionsContext *o, AVFormatContext *oc, - const OutputStream *ost, enum AVMediaType type) -{ - if (ost->filters_script || ost->filters) { - av_log(NULL, AV_LOG_ERROR, - "%s '%s' was defined for %s output stream %d:%d but codec copy was selected.\n" - "Filtering and streamcopy cannot be used together.\n", - ost->filters ? "Filtergraph" : "Filtergraph script", - ost->filters ? ost->filters : ost->filters_script, - av_get_media_type_string(type), ost->file_index, ost->index); - exit_program(1); - } -} - -OutputStream *new_video_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - AVStream *st; - OutputStream *ost; - AVCodecContext *video_enc; - char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_VIDEO, source_index); - st = ost->st; - video_enc = ost->enc_ctx; - - MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st); - if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(max_frame_rates, str, max_frame_rate, oc, st); - if (max_frame_rate && av_parse_video_rate(&ost->max_frame_rate, max_frame_rate) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid maximum framerate value: %s\n", max_frame_rate); - exit_program(1); - } - - if (frame_rate && max_frame_rate) { - av_log(NULL, AV_LOG_ERROR, "Only one of -fpsmax and -r can be set for a stream.\n"); - exit_program(1); - } - - if ((frame_rate || max_frame_rate) && - video_sync_method == VSYNC_PASSTHROUGH) - av_log(NULL, AV_LOG_ERROR, "Using -vsync passthrough and -r/-fpsmax can produce invalid output files\n"); - - MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st); - if (frame_aspect_ratio) { - AVRational q; - if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 || - q.num <= 0 || q.den <= 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid aspect ratio: %s\n", frame_aspect_ratio); - exit_program(1); - } - ost->frame_aspect_ratio = q; - } - - MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); - MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); - - if (!ost->stream_copy) { - const char *p = NULL; - char *frame_size = NULL; - char *frame_pix_fmt = NULL; - char *intra_matrix = NULL, *inter_matrix = NULL; - char *chroma_intra_matrix = NULL; - int do_pass = 0; - int i; - - MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); - if (frame_size && av_parse_video_size(&video_enc->width, &video_enc->height, frame_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st); - if (frame_pix_fmt && *frame_pix_fmt == '+') { - ost->keep_pix_fmt = 1; - if (!*++frame_pix_fmt) - frame_pix_fmt = NULL; - } - if (frame_pix_fmt && (video_enc->pix_fmt = av_get_pix_fmt(frame_pix_fmt)) == AV_PIX_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Unknown pixel format requested: %s.\n", frame_pix_fmt); - exit_program(1); - } - st->sample_aspect_ratio = video_enc->sample_aspect_ratio; - - MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st); - if (intra_matrix) { - if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64))) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n"); - exit_program(1); - } - parse_matrix_coeffs(video_enc->intra_matrix, intra_matrix); - } - MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st); - if (chroma_intra_matrix) { - uint16_t *p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64); - if (!p) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for intra matrix.\n"); - exit_program(1); - } - video_enc->chroma_intra_matrix = p; - parse_matrix_coeffs(p, chroma_intra_matrix); - } - MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st); - if (inter_matrix) { - if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64))) { - av_log(NULL, AV_LOG_FATAL, "Could not allocate memory for inter matrix.\n"); - exit_program(1); - } - parse_matrix_coeffs(video_enc->inter_matrix, inter_matrix); - } - - MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st); - for (i = 0; p; i++) { - int start, end, q; - int e = sscanf(p, "%d,%d,%d", &start, &end, &q); - if (e != 3) { - av_log(NULL, AV_LOG_FATAL, "error parsing rc_override\n"); - exit_program(1); - } - video_enc->rc_override = - av_realloc_array(video_enc->rc_override, - i + 1, sizeof(RcOverride)); - if (!video_enc->rc_override) { - av_log(NULL, AV_LOG_FATAL, "Could not (re)allocate memory for rc_override.\n"); - exit_program(1); - } - video_enc->rc_override[i].start_frame = start; - video_enc->rc_override[i].end_frame = end; - if (q > 0) { - video_enc->rc_override[i].qscale = q; - video_enc->rc_override[i].quality_factor = 1.0; - } - else { - video_enc->rc_override[i].qscale = 0; - video_enc->rc_override[i].quality_factor = -q/100.0; - } - p = strchr(p, '/'); - if (p) p++; - } - video_enc->rc_override_count = i; - - if (do_psnr) - video_enc->flags|= AV_CODEC_FLAG_PSNR; - - /* two pass mode */ - MATCH_PER_STREAM_OPT(pass, i, do_pass, oc, st); - if (do_pass) { - if (do_pass & 1) { - video_enc->flags |= AV_CODEC_FLAG_PASS1; - av_dict_set(&ost->encoder_opts, "flags", "+pass1", AV_DICT_APPEND); - } - if (do_pass & 2) { - video_enc->flags |= AV_CODEC_FLAG_PASS2; - av_dict_set(&ost->encoder_opts, "flags", "+pass2", AV_DICT_APPEND); - } - } - - MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st); - if (ost->logfile_prefix && - !(ost->logfile_prefix = av_strdup(ost->logfile_prefix))) - exit_program(1); - - if (do_pass) { - char logfilename[1024]; - FILE *f; - - snprintf(logfilename, sizeof(logfilename), "%s-%d.log", - ost->logfile_prefix ? ost->logfile_prefix : - DEFAULT_PASS_LOGFILENAME_PREFIX, - nb_output_streams - 1); - if (!strcmp(ost->enc->name, "libx264")) { - av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE); - } else { - if (video_enc->flags & AV_CODEC_FLAG_PASS2) { - char *logbuffer = fftools_read_file(logfilename); - - if (!logbuffer) { - av_log(NULL, AV_LOG_FATAL, "Error reading log file '%s' for pass-2 encoding\n", - logfilename); - exit_program(1); - } - video_enc->stats_in = logbuffer; - } - if (video_enc->flags & AV_CODEC_FLAG_PASS1) { - f = fopen_utf8(logfilename, "wb"); - if (!f) { - av_log(NULL, AV_LOG_FATAL, - "Cannot write log file '%s' for pass-1 encoding: %s\n", - logfilename, strerror(errno)); - exit_program(1); - } - ost->logfile = f; - } - } - } - - MATCH_PER_STREAM_OPT(forced_key_frames, str, ost->forced_keyframes, oc, st); - if (ost->forced_keyframes) - ost->forced_keyframes = av_strdup(ost->forced_keyframes); - - MATCH_PER_STREAM_OPT(force_fps, i, ost->force_fps, oc, st); - - ost->top_field_first = -1; - MATCH_PER_STREAM_OPT(top_field_first, i, ost->top_field_first, oc, st); - - ost->vsync_method = video_sync_method; - MATCH_PER_STREAM_OPT(fps_mode, str, ost->fps_mode, oc, st); - if (ost->fps_mode) - parse_and_set_vsync(ost->fps_mode, &ost->vsync_method, ost->file_index, ost->index, 0); - - if (ost->vsync_method == VSYNC_AUTO) { - if (!strcmp(oc->oformat->name, "avi")) { - ost->vsync_method = VSYNC_VFR; - } else { - ost->vsync_method = (oc->oformat->flags & AVFMT_VARIABLE_FPS) ? - ((oc->oformat->flags & AVFMT_NOTIMESTAMPS) ? - VSYNC_PASSTHROUGH : VSYNC_VFR) : - VSYNC_CFR; - } - - if (ost->source_index >= 0 && ost->vsync_method == VSYNC_CFR) { - const InputStream *ist = input_streams[ost->source_index]; - const InputFile *ifile = input_files[ist->file_index]; - - if (ifile->nb_streams == 1 && ifile->input_ts_offset == 0) - ost->vsync_method = VSYNC_VSCFR; - } - - if (ost->vsync_method == VSYNC_CFR && copy_ts) { - ost->vsync_method = VSYNC_VSCFR; - } - } - ost->is_cfr = (ost->vsync_method == VSYNC_CFR || ost->vsync_method == VSYNC_VSCFR); - - ost->avfilter = get_ost_filters(o, oc, ost); - if (!ost->avfilter) - exit_program(1); - - ost->last_frame = av_frame_alloc(); - if (!ost->last_frame) - exit_program(1); - } - - if (ost->stream_copy) - check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_VIDEO); - - return ost; -} - -OutputStream *new_audio_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - int n; - AVStream *st; - OutputStream *ost; - AVCodecContext *audio_enc; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_AUDIO, source_index); - st = ost->st; - - audio_enc = ost->enc_ctx; - audio_enc->codec_type = AVMEDIA_TYPE_AUDIO; - - MATCH_PER_STREAM_OPT(filter_scripts, str, ost->filters_script, oc, st); - MATCH_PER_STREAM_OPT(filters, str, ost->filters, oc, st); - - if (!ost->stream_copy) { - int channels = 0; - char *layout = NULL; - char *sample_fmt = NULL; - - MATCH_PER_STREAM_OPT(audio_channels, i, channels, oc, st); - if (channels) { - audio_enc->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC; - audio_enc->ch_layout.nb_channels = channels; - } - - MATCH_PER_STREAM_OPT(audio_ch_layouts, str, layout, oc, st); - if (layout) { - if (av_channel_layout_from_string(&audio_enc->ch_layout, layout) < 0) { -#if FF_API_OLD_CHANNEL_LAYOUT - uint64_t mask; - AV_NOWARN_DEPRECATED({ - mask = av_get_channel_layout(layout); - }) - if (!mask) { -#endif - av_log(NULL, AV_LOG_FATAL, "Unknown channel layout: %s\n", layout); - exit_program(1); -#if FF_API_OLD_CHANNEL_LAYOUT - } - av_log(NULL, AV_LOG_WARNING, "Channel layout '%s' uses a deprecated syntax.\n", - layout); - av_channel_layout_from_mask(&audio_enc->ch_layout, mask); -#endif - } - } - - MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st); - if (sample_fmt && - (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) { - av_log(NULL, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt); - exit_program(1); - } - - MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st); - - MATCH_PER_STREAM_OPT(apad, str, ost->apad, oc, st); - ost->apad = av_strdup(ost->apad); - - ost->avfilter = get_ost_filters(o, oc, ost); - if (!ost->avfilter) - exit_program(1); - - /* check for channel mapping for this audio stream */ - for (n = 0; n < o->nb_audio_channel_maps; n++) { - AudioChannelMap *map = &o->audio_channel_maps[n]; - if ((map->ofile_idx == -1 || ost->file_index == map->ofile_idx) && - (map->ostream_idx == -1 || ost->st->index == map->ostream_idx)) { - InputStream *ist; - - if (map->channel_idx == -1) { - ist = NULL; - } else if (ost->source_index < 0) { - av_log(NULL, AV_LOG_FATAL, "Cannot determine input stream for channel mapping %d.%d\n", - ost->file_index, ost->st->index); - continue; - } else { - ist = input_streams[ost->source_index]; - } - - if (!ist || (ist->file_index == map->file_idx && ist->st->index == map->stream_idx)) { - if (av_reallocp_array(&ost->audio_channels_map, - ost->audio_channels_mapped + 1, - sizeof(*ost->audio_channels_map) - ) < 0 ) - exit_program(1); - - ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx; - } - } - } - } - - if (ost->stream_copy) - check_streamcopy_filters(o, oc, ost, AVMEDIA_TYPE_AUDIO); - - return ost; -} - -OutputStream *new_data_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_DATA, source_index); - if (!ost->stream_copy) { - av_log(NULL, AV_LOG_FATAL, "Data stream encoding not supported yet (only streamcopy)\n"); - exit_program(1); - } - - return ost; -} - -OutputStream *new_unknown_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_UNKNOWN, source_index); - if (!ost->stream_copy) { - av_log(NULL, AV_LOG_FATAL, "Unknown stream encoding not supported yet (only streamcopy)\n"); - exit_program(1); - } - - return ost; -} - -OutputStream *new_attachment_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - OutputStream *ost = new_output_stream(o, oc, AVMEDIA_TYPE_ATTACHMENT, source_index); - ost->stream_copy = 1; - ost->finished = 1; - return ost; -} - -OutputStream *new_subtitle_stream(OptionsContext *o, AVFormatContext *oc, int source_index) -{ - AVStream *st; - OutputStream *ost; - AVCodecContext *subtitle_enc; - - ost = new_output_stream(o, oc, AVMEDIA_TYPE_SUBTITLE, source_index); - st = ost->st; - subtitle_enc = ost->enc_ctx; - - subtitle_enc->codec_type = AVMEDIA_TYPE_SUBTITLE; - - if (!ost->stream_copy) { - char *frame_size = NULL; - - MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st); - if (frame_size && av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid frame size: %s.\n", frame_size); - exit_program(1); - } - } - - return ost; -} - -/* arg format is "output-stream-index:streamid-value". */ -int opt_streamid(void *optctx, const char *opt, const char *arg) -{ - OptionsContext *o = optctx; - int idx; - char *p; - char idx_str[16]; - - av_strlcpy(idx_str, arg, sizeof(idx_str)); - p = strchr(idx_str, ':'); - if (!p) { - av_log(NULL, AV_LOG_FATAL, - "Invalid value '%s' for option '%s', required syntax is 'index:value'\n", - arg, opt); - exit_program(1); - } - *p++ = '\0'; - idx = parse_number_or_die(opt, idx_str, OPT_INT, 0, MAX_STREAMS-1); - o->streamid_map = grow_array(o->streamid_map, sizeof(*o->streamid_map), &o->nb_streamid_map, idx+1); - o->streamid_map[idx] = parse_number_or_die(opt, p, OPT_INT, 0, INT_MAX); - return 0; -} - -int copy_chapters(InputFile *ifile, OutputFile *ofile, AVFormatContext *os, - int copy_metadata) -{ - AVFormatContext *is = ifile->ctx; - AVChapter **tmp; - int i; - - tmp = av_realloc_f(os->chapters, is->nb_chapters + os->nb_chapters, sizeof(*os->chapters)); - if (!tmp) - return AVERROR(ENOMEM); - os->chapters = tmp; - - for (i = 0; i < is->nb_chapters; i++) { - AVChapter *in_ch = is->chapters[i], *out_ch; - int64_t start_time = (ofile->start_time == AV_NOPTS_VALUE) ? 0 : ofile->start_time; - int64_t ts_off = av_rescale_q(start_time - ifile->ts_offset, - AV_TIME_BASE_Q, in_ch->time_base); - int64_t rt = (ofile->recording_time == INT64_MAX) ? INT64_MAX : - av_rescale_q(ofile->recording_time, AV_TIME_BASE_Q, in_ch->time_base); - - - if (in_ch->end < ts_off) - continue; - if (rt != INT64_MAX && in_ch->start > rt + ts_off) - break; - - out_ch = av_mallocz(sizeof(AVChapter)); - if (!out_ch) - return AVERROR(ENOMEM); - - out_ch->id = in_ch->id; - out_ch->time_base = in_ch->time_base; - out_ch->start = FFMAX(0, in_ch->start - ts_off); - out_ch->end = FFMIN(rt, in_ch->end - ts_off); - - if (copy_metadata) - av_dict_copy(&out_ch->metadata, in_ch->metadata, 0); - - os->chapters[os->nb_chapters++] = out_ch; - } - return 0; -} - -int set_dispositions(OutputFile *of, AVFormatContext *ctx) -{ - int nb_streams[AVMEDIA_TYPE_NB] = { 0 }; - int have_default[AVMEDIA_TYPE_NB] = { 0 }; - int have_manual = 0; - - // first, copy the input dispositions - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - - nb_streams[ost->st->codecpar->codec_type]++; - - have_manual |= !!ost->disposition; - - if (ost->source_index >= 0) { - ost->st->disposition = input_streams[ost->source_index]->st->disposition; - - if (ost->st->disposition & AV_DISPOSITION_DEFAULT) - have_default[ost->st->codecpar->codec_type] = 1; - } - } - - if (have_manual) { - // process manually set dispositions - they override the above copy - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - int ret; - - if (!ost->disposition) - continue; - -#if LIBAVFORMAT_VERSION_MAJOR >= 60 - ret = av_opt_set(ost->st, "disposition", ost->disposition, 0); -#else - { - const AVClass *class = av_stream_get_class(); - const AVOption *o = av_opt_find(&class, "disposition", NULL, 0, AV_OPT_SEARCH_FAKE_OBJ); - - av_assert0(o); - ret = av_opt_eval_flags(&class, o, ost->disposition, &ost->st->disposition); - } -#endif - - if (ret < 0) - return ret; - } - } else { - // For each media type with more than one stream, find a suitable stream to - // mark as default, unless one is already marked default. - // "Suitable" means the first of that type, skipping attached pictures. - for (int i = 0; i < ctx->nb_streams; i++) { - OutputStream *ost = output_streams[of->ost_index + i]; - enum AVMediaType type = ost->st->codecpar->codec_type; - - if (nb_streams[type] < 2 || have_default[type] || - ost->st->disposition & AV_DISPOSITION_ATTACHED_PIC) - continue; - - ost->st->disposition |= AV_DISPOSITION_DEFAULT; - have_default[type] = 1; - } - } - - return 0; -} - -void init_output_filter(OutputFilter *ofilter, OptionsContext *o, - AVFormatContext *oc) -{ - OutputStream *ost; - - switch (ofilter->type) { - case AVMEDIA_TYPE_VIDEO: ost = new_video_stream(o, oc, -1); break; - case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream(o, oc, -1); break; - default: - av_log(NULL, AV_LOG_FATAL, "Only video and audio filters are supported " - "currently.\n"); - exit_program(1); - } - - ost->filter = ofilter; - - ofilter->ost = ost; - ofilter->format = -1; - - if (ost->stream_copy) { - av_log(NULL, AV_LOG_ERROR, "Streamcopy requested for output stream %d:%d, " - "which is fed from a complex filtergraph. Filtering and streamcopy " - "cannot be used together.\n", ost->file_index, ost->index); - exit_program(1); - } - - if (ost->avfilter && (ost->filters || ost->filters_script)) { - const char *opt = ost->filters ? "-vf/-af/-filter" : "-filter_script"; - av_log(NULL, AV_LOG_ERROR, - "%s '%s' was specified through the %s option " - "for output stream %d:%d, which is fed from a complex filtergraph.\n" - "%s and -filter_complex cannot be used together for the same stream.\n", - ost->filters ? "Filtergraph" : "Filtergraph script", - ost->filters ? ost->filters : ost->filters_script, - opt, ost->file_index, ost->index, opt); - exit_program(1); - } - - avfilter_inout_free(&ofilter->out_tmp); -} - -int init_complex_filters(void) -{ - int i, ret = 0; - - for (i = 0; i < nb_filtergraphs; i++) { - ret = init_complex_filtergraph(filtergraphs[i]); - if (ret < 0) - return ret; - } - return 0; -} - -void set_channel_layout(OutputFilter *f, OutputStream *ost) -{ - int i, err; - - if (ost->enc_ctx->ch_layout.order != AV_CHANNEL_ORDER_UNSPEC) { - /* Pass the layout through for all orders but UNSPEC */ - err = av_channel_layout_copy(&f->ch_layout, &ost->enc_ctx->ch_layout); - if (err < 0) - exit_program(1); - return; - } - - /* Requested layout is of order UNSPEC */ - if (!ost->enc->ch_layouts) { - /* Use the default native layout for the requested amount of channels when the - encoder doesn't have a list of supported layouts */ - av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); - return; - } - /* Encoder has a list of supported layouts. Pick the first layout in it with the - same amount of channels as the requested layout */ - for (i = 0; ost->enc->ch_layouts[i].nb_channels; i++) { - if (ost->enc->ch_layouts[i].nb_channels == ost->enc_ctx->ch_layout.nb_channels) - break; - } - if (ost->enc->ch_layouts[i].nb_channels) { - /* Use it if one is found */ - err = av_channel_layout_copy(&f->ch_layout, &ost->enc->ch_layouts[i]); - if (err < 0) - exit_program(1); - return; - } - /* If no layout for the amount of channels requested was found, use the default - native layout for it. */ - av_channel_layout_default(&f->ch_layout, ost->enc_ctx->ch_layout.nb_channels); -} - -int open_output_file(OptionsContext *o, const char *filename) -{ - AVFormatContext *oc; - int i, j, err; - OutputFile *of; - OutputStream *ost; - InputStream *ist; - AVDictionary *unused_opts = NULL; - const AVDictionaryEntry *e = NULL; - - if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) { - o->stop_time = INT64_MAX; - av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n"); - } - - if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) { - int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time; - if (o->stop_time <= start_time) { - av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n"); - exit_program(1); - } else { - o->recording_time = o->stop_time - start_time; - } - } - - of = ALLOC_ARRAY_ELEM(output_files, nb_output_files); - - of->index = nb_output_files - 1; - of->ost_index = nb_output_streams; - of->recording_time = o->recording_time; - of->start_time = o->start_time; - of->limit_filesize = o->limit_filesize; - of->shortest = o->shortest; - av_dict_copy(&of->opts, o->g->format_opts, 0); - - if (!strcmp(filename, "-")) - filename = "pipe:"; - - err = avformat_alloc_output_context2(&oc, NULL, o->format, filename); - if (!oc) { - print_error(filename, err); - exit_program(1); - } - - of->ctx = oc; - of->format = oc->oformat; - if (o->recording_time != INT64_MAX) - oc->duration = o->recording_time; - - oc->interrupt_callback = int_cb; - - if (o->bitexact) { - oc->flags |= AVFMT_FLAG_BITEXACT; - } - - /* create streams for all unlabeled output pads */ - for (i = 0; i < nb_filtergraphs; i++) { - FilterGraph *fg = filtergraphs[i]; - for (j = 0; j < fg->nb_outputs; j++) { - OutputFilter *ofilter = fg->outputs[j]; - - if (!ofilter->out_tmp || ofilter->out_tmp->name) - continue; - - switch (ofilter->type) { - case AVMEDIA_TYPE_VIDEO: o->video_disable = 1; break; - case AVMEDIA_TYPE_AUDIO: o->audio_disable = 1; break; - case AVMEDIA_TYPE_SUBTITLE: o->subtitle_disable = 1; break; - } - init_output_filter(ofilter, o, oc); - } - } - - if (!o->nb_stream_maps) { - char *subtitle_codec_name = NULL; - /* pick the "best" stream of each type */ - - /* video: highest resolution */ - if (!o->video_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_VIDEO) != AV_CODEC_ID_NONE) { - int best_score = 0, idx = -1; - int qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0); - for (j = 0; j < nb_input_files; j++) { - InputFile *ifile = input_files[j]; - int file_best_score = 0, file_best_idx = -1; - for (i = 0; i < ifile->nb_streams; i++) { - int score; - ist = input_streams[ifile->ist_index + i]; - score = ist->st->codecpar->width * ist->st->codecpar->height - + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) - + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); - if (ist->user_set_discard == AVDISCARD_ALL) - continue; - if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - score = 1; - if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && - score > file_best_score) { - if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - continue; - file_best_score = score; - file_best_idx = ifile->ist_index + i; - } - } - if (file_best_idx >= 0) { - if((qcr == MKTAG('A', 'P', 'I', 'C')) || !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) - file_best_score -= 5000000*!!(input_streams[file_best_idx]->st->disposition & AV_DISPOSITION_DEFAULT); - if (file_best_score > best_score) { - best_score = file_best_score; - idx = file_best_idx; - } - } - } - if (idx >= 0) - new_video_stream(o, oc, idx); - } - - /* audio: most channels */ - if (!o->audio_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_AUDIO) != AV_CODEC_ID_NONE) { - int best_score = 0, idx = -1; - for (j = 0; j < nb_input_files; j++) { - InputFile *ifile = input_files[j]; - int file_best_score = 0, file_best_idx = -1; - for (i = 0; i < ifile->nb_streams; i++) { - int score; - ist = input_streams[ifile->ist_index + i]; - score = ist->st->codecpar->ch_layout.nb_channels - + 100000000 * !!(ist->st->event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS) - + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); - if (ist->user_set_discard == AVDISCARD_ALL) - continue; - if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && - score > file_best_score) { - file_best_score = score; - file_best_idx = ifile->ist_index + i; - } - } - if (file_best_idx >= 0) { - file_best_score -= 5000000*!!(input_streams[file_best_idx]->st->disposition & AV_DISPOSITION_DEFAULT); - if (file_best_score > best_score) { - best_score = file_best_score; - idx = file_best_idx; - } - } - } - if (idx >= 0) - new_audio_stream(o, oc, idx); - } - - /* subtitles: pick first */ - MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s"); - if (!o->subtitle_disable && (avcodec_find_encoder(oc->oformat->subtitle_codec) || subtitle_codec_name)) { - for (i = 0; i < nb_input_streams; i++) - if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { - AVCodecDescriptor const *input_descriptor = - avcodec_descriptor_get(input_streams[i]->st->codecpar->codec_id); - AVCodecDescriptor const *output_descriptor = NULL; - AVCodec const *output_codec = - avcodec_find_encoder(oc->oformat->subtitle_codec); - int input_props = 0, output_props = 0; - if (input_streams[i]->user_set_discard == AVDISCARD_ALL) - continue; - if (output_codec) - output_descriptor = avcodec_descriptor_get(output_codec->id); - if (input_descriptor) - input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (output_descriptor) - output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); - if (subtitle_codec_name || - input_props & output_props || - // Map dvb teletext which has neither property to any output subtitle encoder - (input_descriptor && output_descriptor && - (!input_descriptor->props || - !output_descriptor->props))) { - new_subtitle_stream(o, oc, i); - break; - } - } - } - /* Data only if codec id match */ - if (!o->data_disable ) { - enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_DATA); - for (i = 0; codec_id != AV_CODEC_ID_NONE && i < nb_input_streams; i++) { - if (input_streams[i]->user_set_discard == AVDISCARD_ALL) - continue; - if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_DATA - && input_streams[i]->st->codecpar->codec_id == codec_id ) - new_data_stream(o, oc, i); - } - } - } else { - for (i = 0; i < o->nb_stream_maps; i++) { - StreamMap *map = &o->stream_maps[i]; - - if (map->disabled) - continue; - - if (map->linklabel) { - FilterGraph *fg; - OutputFilter *ofilter = NULL; - int j, k; - - for (j = 0; j < nb_filtergraphs; j++) { - fg = filtergraphs[j]; - for (k = 0; k < fg->nb_outputs; k++) { - AVFilterInOut *out = fg->outputs[k]->out_tmp; - if (out && !strcmp(out->name, map->linklabel)) { - ofilter = fg->outputs[k]; - goto loop_end; - } - } - } -loop_end: - if (!ofilter) { - av_log(NULL, AV_LOG_FATAL, "Output with label '%s' does not exist " - "in any defined filter graph, or was already used elsewhere.\n", map->linklabel); - exit_program(1); - } - init_output_filter(ofilter, o, oc); - } else { - int src_idx = input_files[map->file_index]->ist_index + map->stream_index; - - ist = input_streams[input_files[map->file_index]->ist_index + map->stream_index]; - if (ist->user_set_discard == AVDISCARD_ALL) { - av_log(NULL, AV_LOG_FATAL, "Stream #%d:%d is disabled and cannot be mapped.\n", - map->file_index, map->stream_index); - exit_program(1); - } - if(o->subtitle_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) - continue; - if(o-> audio_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - continue; - if(o-> video_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) - continue; - if(o-> data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA) - continue; - - ost = NULL; - switch (ist->st->codecpar->codec_type) { - case AVMEDIA_TYPE_VIDEO: ost = new_video_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_AUDIO: ost = new_audio_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_SUBTITLE: ost = new_subtitle_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_DATA: ost = new_data_stream (o, oc, src_idx); break; - case AVMEDIA_TYPE_ATTACHMENT: ost = new_attachment_stream(o, oc, src_idx); break; - case AVMEDIA_TYPE_UNKNOWN: - if (copy_unknown_streams) { - ost = new_unknown_stream (o, oc, src_idx); - break; - } - default: - av_log(NULL, ignore_unknown_streams ? AV_LOG_WARNING : AV_LOG_FATAL, - "Cannot map stream #%d:%d - unsupported type.\n", - map->file_index, map->stream_index); - if (!ignore_unknown_streams) { - av_log(NULL, AV_LOG_FATAL, - "If you want unsupported types ignored instead " - "of failing, please use the -ignore_unknown option\n" - "If you want them copied, please use -copy_unknown\n"); - exit_program(1); - } - } - if (ost) - ost->sync_ist = input_streams[ input_files[map->sync_file_index]->ist_index - + map->sync_stream_index]; - } - } - } - - /* handle attached files */ - for (i = 0; i < o->nb_attachments; i++) { - AVIOContext *pb; - uint8_t *attachment; - const char *p; - int64_t len; - - if ((err = avio_open2(&pb, o->attachments[i], AVIO_FLAG_READ, &int_cb, NULL)) < 0) { - av_log(NULL, AV_LOG_FATAL, "Could not open attachment file %s.\n", - o->attachments[i]); - exit_program(1); - } - if ((len = avio_size(pb)) <= 0) { - av_log(NULL, AV_LOG_FATAL, "Could not get size of the attachment %s.\n", - o->attachments[i]); - exit_program(1); - } - if (len > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE || - !(attachment = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE))) { - av_log(NULL, AV_LOG_FATAL, "Attachment %s too large.\n", - o->attachments[i]); - exit_program(1); - } - avio_read(pb, attachment, len); - memset(attachment + len, 0, AV_INPUT_BUFFER_PADDING_SIZE); - - ost = new_attachment_stream(o, oc, -1); - ost->stream_copy = 0; - ost->attachment_filename = o->attachments[i]; - ost->st->codecpar->extradata = attachment; - ost->st->codecpar->extradata_size = len; - - p = strrchr(o->attachments[i], '/'); - av_dict_set(&ost->st->metadata, "filename", (p && *p) ? p + 1 : o->attachments[i], AV_DICT_DONT_OVERWRITE); - avio_closep(&pb); - } - - if (!oc->nb_streams && !(oc->oformat->flags & AVFMT_NOSTREAMS)) { - av_dump_format(oc, nb_output_files - 1, oc->url, 1); - av_log(NULL, AV_LOG_ERROR, "Output file #%d does not contain any stream\n", nb_output_files - 1); - exit_program(1); - } - - /* check if all codec options have been used */ - unused_opts = strip_specifiers(o->g->codec_opts); - for (i = of->ost_index; i < nb_output_streams; i++) { - e = NULL; - while ((e = av_dict_get(output_streams[i]->encoder_opts, "", e, - AV_DICT_IGNORE_SUFFIX))) - av_dict_set(&unused_opts, e->key, NULL, 0); - } - - e = NULL; - while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) { - const AVClass *class = avcodec_get_class(); - const AVOption *option = av_opt_find(&class, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - const AVClass *fclass = avformat_get_class(); - const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0, - AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); - if (!option || foption) - continue; - - - if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) { - av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for " - "output file #%d (%s) is not an encoding option.\n", e->key, - option->help ? option->help : "", nb_output_files - 1, - filename); - exit_program(1); - } - - // gop_timecode is injected by generic code but not always used - if (!strcmp(e->key, "gop_timecode")) - continue; - - av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for " - "output file #%d (%s) has not been used for any stream. The most " - "likely reason is either wrong type (e.g. a video option with " - "no video streams) or that it is a private option of some encoder " - "which was not actually used for any stream.\n", e->key, - option->help ? option->help : "", nb_output_files - 1, filename); - } - av_dict_free(&unused_opts); - - /* set the decoding_needed flags and create simple filtergraphs */ - for (i = of->ost_index; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - - if (ost->encoding_needed && ost->source_index >= 0) { - InputStream *ist = input_streams[ost->source_index]; - ist->decoding_needed |= DECODING_FOR_OST; - ist->processing_needed = 1; - - if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || - ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - err = init_simple_filtergraph(ist, ost); - if (err < 0) { - av_log(NULL, AV_LOG_ERROR, - "Error initializing a simple filtergraph between streams " - "%d:%d->%d:%d\n", ist->file_index, ost->source_index, - nb_output_files - 1, ost->st->index); - exit_program(1); - } - } - } else if (ost->stream_copy && ost->source_index >= 0) { - InputStream *ist = input_streams[ost->source_index]; - ist->processing_needed = 1; - } - - /* set the filter output constraints */ - if (ost->filter) { - OutputFilter *f = ost->filter; - switch (ost->enc_ctx->codec_type) { - case AVMEDIA_TYPE_VIDEO: - f->frame_rate = ost->frame_rate; - f->width = ost->enc_ctx->width; - f->height = ost->enc_ctx->height; - if (ost->enc_ctx->pix_fmt != AV_PIX_FMT_NONE) { - f->format = ost->enc_ctx->pix_fmt; - } else { - f->formats = ost->enc->pix_fmts; - } - break; - case AVMEDIA_TYPE_AUDIO: - if (ost->enc_ctx->sample_fmt != AV_SAMPLE_FMT_NONE) { - f->format = ost->enc_ctx->sample_fmt; - } else { - f->formats = ost->enc->sample_fmts; - } - if (ost->enc_ctx->sample_rate) { - f->sample_rate = ost->enc_ctx->sample_rate; - } else { - f->sample_rates = ost->enc->supported_samplerates; - } - if (ost->enc_ctx->ch_layout.nb_channels) { - set_channel_layout(f, ost); - } else if (ost->enc->ch_layouts) { - f->ch_layouts = ost->enc->ch_layouts; - } - break; - } - } - } - - /* check filename in case of an image number is expected */ - if (oc->oformat->flags & AVFMT_NEEDNUMBER) { - if (!av_filename_number_test(oc->url)) { - print_error(oc->url, AVERROR(EINVAL)); - exit_program(1); - } - } - - if (!(oc->oformat->flags & AVFMT_NOSTREAMS) && !input_stream_potentially_available) { - av_log(NULL, AV_LOG_ERROR, - "No input streams but output needs an input stream\n"); - exit_program(1); - } - - if (!(oc->oformat->flags & AVFMT_NOFILE)) { - /* test if it already exists to avoid losing precious files */ - assert_file_overwrite(filename); - - /* open the file */ - if ((err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, - &oc->interrupt_callback, - &of->opts)) < 0) { - print_error(filename, err); - exit_program(1); - } - } else if (strcmp(oc->oformat->name, "image2")==0 && !av_filename_number_test(filename)) - assert_file_overwrite(filename); - - if (o->mux_preload) { - av_dict_set_int(&of->opts, "preload", o->mux_preload*AV_TIME_BASE, 0); - } - oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); - - /* copy metadata */ - for (i = 0; i < o->nb_metadata_map; i++) { - char *p; - int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0); - - if (in_file_index >= nb_input_files) { - av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d while processing metadata maps\n", in_file_index); - exit_program(1); - } - fftools_copy_metadata(o->metadata_map[i].specifier, *p ? p + 1 : p, oc, - in_file_index >= 0 ? - input_files[in_file_index]->ctx : NULL, o); - } - - /* copy chapters */ - if (o->chapters_input_file >= nb_input_files) { - if (o->chapters_input_file == INT_MAX) { - /* copy chapters from the first input file that has them*/ - o->chapters_input_file = -1; - for (i = 0; i < nb_input_files; i++) - if (input_files[i]->ctx->nb_chapters) { - o->chapters_input_file = i; - break; - } - } else { - av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d in chapter mapping.\n", - o->chapters_input_file); - exit_program(1); - } - } - if (o->chapters_input_file >= 0) - copy_chapters(input_files[o->chapters_input_file], of, oc, - !o->metadata_chapters_manual); - - /* copy global metadata by default */ - if (!o->metadata_global_manual && nb_input_files){ - av_dict_copy(&oc->metadata, input_files[0]->ctx->metadata, - AV_DICT_DONT_OVERWRITE); - if(o->recording_time != INT64_MAX) - av_dict_set(&oc->metadata, "duration", NULL, 0); - av_dict_set(&oc->metadata, "creation_time", NULL, 0); - av_dict_set(&oc->metadata, "company_name", NULL, 0); - av_dict_set(&oc->metadata, "product_name", NULL, 0); - av_dict_set(&oc->metadata, "product_version", NULL, 0); - } - if (!o->metadata_streams_manual) - for (i = of->ost_index; i < nb_output_streams; i++) { - InputStream *ist; - if (output_streams[i]->source_index < 0) /* this is true e.g. for attached files */ - continue; - ist = input_streams[output_streams[i]->source_index]; - av_dict_copy(&output_streams[i]->st->metadata, ist->st->metadata, AV_DICT_DONT_OVERWRITE); - if (!output_streams[i]->stream_copy) { - av_dict_set(&output_streams[i]->st->metadata, "encoder", NULL, 0); - } - } - - /* process manually set programs */ - for (i = 0; i < o->nb_program; i++) { - const char *p = o->program[i].u.str; - int progid = i+1; - AVProgram *program; - - while(*p) { - const char *p2 = av_get_token(&p, ":"); - const char *to_dealloc = p2; - char *key; - if (!p2) - break; - - if(*p) p++; - - key = av_get_token(&p2, "="); - if (!key || !*p2) { - av_freep(&to_dealloc); - av_freep(&key); - break; - } - p2++; - - if (!strcmp(key, "program_num")) - progid = strtol(p2, NULL, 0); - av_freep(&to_dealloc); - av_freep(&key); - } - - program = av_new_program(oc, progid); - - p = o->program[i].u.str; - while(*p) { - const char *p2 = av_get_token(&p, ":"); - const char *to_dealloc = p2; - char *key; - if (!p2) - break; - if(*p) p++; - - key = av_get_token(&p2, "="); - if (!key) { - av_log(NULL, AV_LOG_FATAL, - "No '=' character in program string %s.\n", - p2); - exit_program(1); - } - if (!*p2) - exit_program(1); - p2++; - - if (!strcmp(key, "title")) { - av_dict_set(&program->metadata, "title", p2, 0); - } else if (!strcmp(key, "program_num")) { - } else if (!strcmp(key, "st")) { - int st_num = strtol(p2, NULL, 0); - av_program_add_stream_index(oc, progid, st_num); - } else { - av_log(NULL, AV_LOG_FATAL, "Unknown program key %s.\n", key); - exit_program(1); - } - av_freep(&to_dealloc); - av_freep(&key); - } - } - - /* process manually set metadata */ - for (i = 0; i < o->nb_metadata; i++) { - AVDictionary **m; - char type, *val; - const char *stream_spec; - int index = 0, j, ret = 0; - - val = strchr(o->metadata[i].u.str, '='); - if (!val) { - av_log(NULL, AV_LOG_FATAL, "No '=' character in metadata string %s.\n", - o->metadata[i].u.str); - exit_program(1); - } - *val++ = 0; - - parse_meta_type(o->metadata[i].specifier, &type, &index, &stream_spec); - if (type == 's') { - for (j = 0; j < oc->nb_streams; j++) { - ost = output_streams[nb_output_streams - oc->nb_streams + j]; - if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) { - if (!strcmp(o->metadata[i].u.str, "rotate")) { - char *tail; - double theta = av_strtod(val, &tail); - if (!*tail) { - ost->rotate_overridden = 1; - ost->rotate_override_value = theta; - } - } else { - av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); - } - } else if (ret < 0) - exit_program(1); - } - } - else { - switch (type) { - case 'g': - m = &oc->metadata; - break; - case 'c': - if (index < 0 || index >= oc->nb_chapters) { - av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); - exit_program(1); - } - m = &oc->chapters[index]->metadata; - break; - case 'p': - if (index < 0 || index >= oc->nb_programs) { - av_log(NULL, AV_LOG_FATAL, "Invalid program index %d in metadata specifier.\n", index); - exit_program(1); - } - m = &oc->programs[index]->metadata; - break; - default: - av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); - exit_program(1); - } - av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); - } - } - - err = set_dispositions(of, oc); - if (err < 0) { - av_log(NULL, AV_LOG_FATAL, "Error setting output stream dispositions\n"); - exit_program(1); - } - - return 0; -} - -int opt_target(void *optctx, const char *opt, const char *arg) -{ - const OptionDef *options = ffmpeg_options; - OptionsContext *o = optctx; - enum { PAL, NTSC, FILM, UNKNOWN } norm = UNKNOWN; - static const char *const frame_rates[] = { "25", "30000/1001", "24000/1001" }; - - if (!strncmp(arg, "pal-", 4)) { - norm = PAL; - arg += 4; - } else if (!strncmp(arg, "ntsc-", 5)) { - norm = NTSC; - arg += 5; - } else if (!strncmp(arg, "film-", 5)) { - norm = FILM; - arg += 5; - } else { - /* Try to determine PAL/NTSC by peeking in the input files */ - if (nb_input_files) { - int i, j; - for (j = 0; j < nb_input_files; j++) { - for (i = 0; i < input_files[j]->nb_streams; i++) { - AVStream *st = input_files[j]->ctx->streams[i]; - int64_t fr; - if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) - continue; - fr = st->time_base.den * 1000LL / st->time_base.num; - if (fr == 25000) { - norm = PAL; - break; - } else if ((fr == 29970) || (fr == 23976)) { - norm = NTSC; - break; - } - } - if (norm != UNKNOWN) - break; - } - } - if (norm != UNKNOWN) - av_log(NULL, AV_LOG_INFO, "Assuming %s for target.\n", norm == PAL ? "PAL" : "NTSC"); - } - - if (norm == UNKNOWN) { - av_log(NULL, AV_LOG_FATAL, "Could not determine norm (PAL/NTSC/NTSC-Film) for target.\n"); - av_log(NULL, AV_LOG_FATAL, "Please prefix target with \"pal-\", \"ntsc-\" or \"film-\",\n"); - av_log(NULL, AV_LOG_FATAL, "or set a framerate with \"-r xxx\".\n"); - exit_program(1); - } - - if (!strcmp(arg, "vcd")) { - opt_video_codec(o, "c:v", "mpeg1video"); - opt_audio_codec(o, "c:a", "mp2"); - parse_option(o, "f", "vcd", options); - - parse_option(o, "s", norm == PAL ? "352x288" : "352x240", options); - parse_option(o, "r", frame_rates[norm], options); - opt_default(NULL, "g", norm == PAL ? "15" : "18"); - - opt_default(NULL, "b:v", "1150000"); - opt_default(NULL, "maxrate:v", "1150000"); - opt_default(NULL, "minrate:v", "1150000"); - opt_default(NULL, "bufsize:v", "327680"); // 40*1024*8; - - opt_default(NULL, "b:a", "224000"); - parse_option(o, "ar", "44100", options); - parse_option(o, "ac", "2", options); + opt_default(NULL, "b:a", "224000"); + parse_option(o, "ar", "44100", options); + parse_option(o, "ac", "2", options); opt_default(NULL, "packetsize", "2324"); opt_default(NULL, "muxrate", "1411200"); // 2352 * 75 * 8; @@ -3437,15 +1159,13 @@ int opt_filter_complex(void *optctx, const char *opt, const char *arg) if (!fg->graph_desc) return AVERROR(ENOMEM); - input_stream_potentially_available = 1; - return 0; } int opt_filter_complex_script(void *optctx, const char *opt, const char *arg) { FilterGraph *fg; - char *graph_desc = fftools_read_file(arg); + char *graph_desc = file_read(arg); if (!graph_desc) return AVERROR(EINVAL); @@ -3453,8 +1173,6 @@ int opt_filter_complex_script(void *optctx, const char *opt, const char *arg) fg->index = nb_filtergraphs - 1; fg->graph_desc = graph_desc; - input_stream_potentially_available = 1; - return 0; } @@ -3549,7 +1267,7 @@ static const OptionGroupDef groups[] = { }; int open_files(OptionGroupList *l, const char *inout, - int (*open_file)(OptionsContext*, const char*)) + int (*open_file)(const OptionsContext*, const char*)) { int i, ret; @@ -3585,7 +1303,6 @@ int open_files(OptionGroupList *l, const char *inout, int ffmpeg_parse_options(int argc, char **argv) { OptionParseContext octx; - uint8_t error[128]; int ret; memset(&octx, 0, sizeof(octx)); @@ -3609,14 +1326,12 @@ int ffmpeg_parse_options(int argc, char **argv) term_init(); /* open input files */ - ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file); + ret = open_files(&octx.groups[GROUP_INFILE], "input", ifile_open); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening input files: "); goto fail; } - apply_sync_offsets(); - /* create the complex filtergraphs */ ret = init_complex_filters(); if (ret < 0) { @@ -3625,19 +1340,22 @@ int ffmpeg_parse_options(int argc, char **argv) } /* open output files */ - ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file); + ret = open_files(&octx.groups[GROUP_OUTFILE], "output", of_open); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening output files: "); goto fail; } + correct_input_start_times(); + + apply_sync_offsets(); + check_filter_outputs(); fail: uninit_parse_context(&octx); if (ret < 0) { - av_strerror(ret, error, sizeof(error)); - av_log(NULL, AV_LOG_FATAL, "%s\n", error); + av_log(NULL, AV_LOG_FATAL, "%s\n", av_err2str(ret)); } return ret; } diff --git a/linux/src/fftools_ffprobe.c b/linux/src/fftools_ffprobe.c index 769e430..dccd966 100644 --- a/linux/src/fftools_ffprobe.c +++ b/linux/src/fftools_ffprobe.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2007-2010 Stefano Sabatini - * Copyright (c) 2020 Taner Sener + * Copyright (c) 2020-2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -29,6 +30,13 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop mobile-ffmpeg and later ffmpeg-kit libraries. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + * * mobile-ffmpeg / ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -52,9 +60,11 @@ #include "libavutil/ffversion.h" #include +#include #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" +#include "libavutil/ambient_viewing_environment.h" #include "libavutil/avassert.h" #include "libavutil/avstring.h" #include "libavutil/bprint.h" @@ -163,6 +173,8 @@ typedef struct ReadInterval { __thread ReadInterval *read_intervals; __thread int read_intervals_nb = 0; +__thread int find_stream_info = 1; + /* section structure definition */ #define SECTION_MAX_NB_CHILDREN 10 @@ -626,6 +638,7 @@ static inline void writer_put_str_printf(WriterContext *wctx, const char *str) static inline void writer_printf_printf(WriterContext *wctx, const char *fmt, ...) { va_list ap; + va_start(ap, fmt); av_vlog(NULL, AV_LOG_STDERR, fmt, ap); va_end(ap); @@ -671,7 +684,7 @@ static int writer_open(WriterContext **wctx, const Writer *writer, const char *a goto fail; } - while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) { + while ((opt = av_dict_iterate(opts, opt))) { if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) { av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n", opt->key, opt->value); @@ -1907,12 +1920,14 @@ static void writer_register_all(void) writer_print_string(w, k, pbuf.str, 0); \ } while (0) -#define print_list_fmt(k, f, n, ...) do { \ +#define print_list_fmt(k, f, n, m, ...) do { \ av_bprint_clear(&pbuf); \ for (int idx = 0; idx < n; idx++) { \ - if (idx > 0) \ - av_bprint_chars(&pbuf, ' ', 1); \ - av_bprintf(&pbuf, f, __VA_ARGS__); \ + for (int idx2 = 0; idx2 < m; idx2++) { \ + if (idx > 0 || idx2 > 0) \ + av_bprint_chars(&pbuf, ' ', 1); \ + av_bprintf(&pbuf, f, __VA_ARGS__); \ + } \ } \ writer_print_string(w, k, pbuf.str, 0); \ } while (0) @@ -1953,7 +1968,7 @@ static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id return 0; writer_print_section_header(w, section_id); - while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) { + while ((tag = av_dict_iterate(tags, tag))) { if ((ret = print_str_validate(tag->key, tag->value)) < 0) break; } @@ -2023,7 +2038,7 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) const AVDOVIReshapingCurve *curve = &mapping->curves[c]; writer_print_section_header(w, SECTION_ID_FRAME_SIDE_DATA_COMPONENT); - print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, curve->pivots[idx]); + print_list_fmt("pivots", "%"PRIu16, curve->num_pivots, 1, curve->pivots[idx]); writer_print_section_header(w, SECTION_ID_FRAME_SIDE_DATA_PIECE_LIST); for (int i = 0; i < curve->num_pivots - 1; i++) { @@ -2035,7 +2050,7 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_str("mapping_idc_name", "polynomial"); print_int("poly_order", curve->poly_order[i]); print_list_fmt("poly_coef", "%"PRIi64, - curve->poly_order[i] + 1, + curve->poly_order[i] + 1, 1, curve->poly_coef[i][idx]); break; case AV_DOVI_MAPPING_MMR: @@ -2043,8 +2058,8 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_int("mmr_order", curve->mmr_order[i]); print_int("mmr_constant", curve->mmr_constant[i]); print_list_fmt("mmr_coef", "%"PRIi64, - curve->mmr_order[i] * 7, - curve->mmr_coef[i][0][idx]); + curve->mmr_order[i], 7, + curve->mmr_coef[i][idx][idx2]); break; default: print_str("mapping_idc_name", "unknown"); @@ -2082,15 +2097,15 @@ static void print_dovi_metadata(WriterContext *w, const AVDOVIMetadata *dovi) print_int("dm_metadata_id", color->dm_metadata_id); print_int("scene_refresh_flag", color->scene_refresh_flag); print_list_fmt("ycc_to_rgb_matrix", "%d/%d", - FF_ARRAY_ELEMS(color->ycc_to_rgb_matrix), + FF_ARRAY_ELEMS(color->ycc_to_rgb_matrix), 1, color->ycc_to_rgb_matrix[idx].num, color->ycc_to_rgb_matrix[idx].den); print_list_fmt("ycc_to_rgb_offset", "%d/%d", - FF_ARRAY_ELEMS(color->ycc_to_rgb_offset), + FF_ARRAY_ELEMS(color->ycc_to_rgb_offset), 1, color->ycc_to_rgb_offset[idx].num, color->ycc_to_rgb_offset[idx].den); print_list_fmt("rgb_to_lms_matrix", "%d/%d", - FF_ARRAY_ELEMS(color->rgb_to_lms_matrix), + FF_ARRAY_ELEMS(color->rgb_to_lms_matrix), 1, color->rgb_to_lms_matrix[idx].num, color->rgb_to_lms_matrix[idx].den); print_int("signal_eotf", color->signal_eotf); @@ -2276,6 +2291,17 @@ static void print_dynamic_hdr_vivid(WriterContext *w, const AVDynamicHDRVivid *m } } +static void print_ambient_viewing_environment(WriterContext *w, + const AVAmbientViewingEnvironment *env) +{ + if (!env) + return; + + print_q("ambient_illuminance", env->ambient_illuminance, '/'); + print_q("ambient_light_x", env->ambient_light_x, '/'); + print_q("ambient_light_y", env->ambient_light_y, '/'); +} + static void print_pkt_side_data(WriterContext *w, AVCodecParameters *par, const AVPacketSideData *side_data, @@ -2293,8 +2319,11 @@ static void print_pkt_side_data(WriterContext *w, writer_print_section_header(w, id_data); print_str("side_data_type", name ? name : "unknown"); if (sd->type == AV_PKT_DATA_DISPLAYMATRIX && sd->size >= 9*4) { + double rotation = av_display_rotation_get((int32_t *)sd->data); + if (isnan(rotation)) + rotation = 0; writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); - print_int("rotation", av_display_rotation_get((int32_t *)sd->data)); + print_int("rotation", rotation); } else if (sd->type == AV_PKT_DATA_STEREO3D) { const AVStereo3D *stereo = (AVStereo3D *)sd->data; print_str("type", av_stereo3d_type_name(stereo->type)); @@ -2506,8 +2535,12 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p print_val("size", pkt->size, unit_byte_str); if (pkt->pos != -1) print_fmt ("pos", "%"PRId64, pkt->pos); else print_str_opt("pos", "N/A"); - print_fmt("flags", "%c%c", pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_', - pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_'); + print_fmt("flags", "%c%c%c", pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_', + pkt->flags & AV_PKT_FLAG_DISCARD ? 'D' : '_', + pkt->flags & AV_PKT_FLAG_CORRUPT ? 'C' : '_'); + if (do_show_data) + writer_print_data(w, "data", pkt->data, pkt->size); + writer_print_data_hash(w, "data_hash", pkt->data, pkt->size); if (pkt->side_data_elems) { size_t size; @@ -2526,9 +2559,6 @@ static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int p SECTION_ID_PACKET_SIDE_DATA); } - if (do_show_data) - writer_print_data(w, "data", pkt->data, pkt->size); - writer_print_data_hash(w, "data_hash", pkt->data, pkt->size); writer_print_section_footer(w); av_bprint_finalize(&pbuf, NULL); @@ -2581,8 +2611,14 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, print_time("pkt_dts_time", frame->pkt_dts, &stream->time_base); print_ts ("best_effort_timestamp", frame->best_effort_timestamp); print_time("best_effort_timestamp_time", frame->best_effort_timestamp, &stream->time_base); +#if LIBAVUTIL_VERSION_MAJOR < 59 + AV_NOWARN_DEPRECATED( print_duration_ts ("pkt_duration", frame->pkt_duration); print_duration_time("pkt_duration_time", frame->pkt_duration, &stream->time_base); + ) +#endif + print_duration_ts ("duration", frame->duration); + print_duration_time("duration_time", frame->duration, &stream->time_base); if (frame->pkt_pos != -1) print_fmt ("pkt_pos", "%"PRId64, frame->pkt_pos); else print_str_opt("pkt_pos", "N/A"); if (frame->pkt_size != -1) print_val ("pkt_size", frame->pkt_size, unit_byte_str); @@ -2604,8 +2640,12 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, print_str_opt("sample_aspect_ratio", "N/A"); } print_fmt("pict_type", "%c", av_get_picture_type_char(frame->pict_type)); +#if LIBAVUTIL_VERSION_MAJOR < 59 + AV_NOWARN_DEPRECATED( print_int("coded_picture_number", frame->coded_picture_number); print_int("display_picture_number", frame->display_picture_number); + ) +#endif print_int("interlaced_frame", frame->interlaced_frame); print_int("top_field_first", frame->top_field_first); print_int("repeat_pict", frame->repeat_pict); @@ -2644,8 +2684,11 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, name = av_frame_side_data_name(sd->type); print_str("side_data_type", name ? name : "unknown"); if (sd->type == AV_FRAME_DATA_DISPLAYMATRIX && sd->size >= 9*4) { + double rotation = av_display_rotation_get((int32_t *)sd->data); + if (isnan(rotation)) + rotation = 0; writer_print_integers(w, "displaymatrix", sd->data, 9, " %11d", 3, 4, 1); - print_int("rotation", av_display_rotation_get((int32_t *)sd->data)); + print_int("rotation", rotation); } else if (sd->type == AV_FRAME_DATA_AFD && sd->size > 0) { print_int("active_format", *sd->data); } else if (sd->type == AV_FRAME_DATA_GOP_TIMECODE && sd->size >= 8) { @@ -2700,6 +2743,9 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, } else if (sd->type == AV_FRAME_DATA_DYNAMIC_HDR_VIVID) { AVDynamicHDRVivid *metadata = (AVDynamicHDRVivid *)sd->data; print_dynamic_hdr_vivid(w, metadata); + } else if (sd->type == AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT) { + print_ambient_viewing_environment( + w, (const AVAmbientViewingEnvironment *)sd->data); } writer_print_section_footer(w); } @@ -2714,7 +2760,7 @@ static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, static av_always_inline int process_frame(WriterContext *w, InputFile *ifile, - AVFrame *frame, AVPacket *pkt, + AVFrame *frame, const AVPacket *pkt, int *packet_new) { AVFormatContext *fmt_ctx = ifile->fmt_ctx; @@ -2858,9 +2904,10 @@ static int read_interval_packets(WriterContext *w, InputFile *ifile, } if (selected_streams[pkt->stream_index]) { AVRational tb = ifile->streams[pkt->stream_index].st->time_base; + int64_t pts = pkt->pts != AV_NOPTS_VALUE ? pkt->pts : pkt->dts; - if (pkt->pts != AV_NOPTS_VALUE) - *cur_ts = av_rescale_q(pkt->pts, tb, AV_TIME_BASE_Q); + if (pts != AV_NOPTS_VALUE) + *cur_ts = av_rescale_q(pts, tb, AV_TIME_BASE_Q); if (!has_start && *cur_ts != AV_NOPTS_VALUE) { start = *cur_ts; @@ -2894,7 +2941,7 @@ static int read_interval_packets(WriterContext *w, InputFile *ifile, } av_packet_unref(pkt); //Flush remaining frames that are cached in the decoder - for (i = 0; i < fmt_ctx->nb_streams; i++) { + for (i = 0; i < ifile->nb_streams; i++) { pkt->stream_index = i; if (do_read_frames) { while (process_frame(w, ifile, frame, pkt, &(int){1}) > 0); @@ -3052,6 +3099,8 @@ static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_id } print_int("bits_per_sample", av_get_bits_per_sample(par->codec_id)); + + print_int("initial_padding", par->initial_padding); break; case AVMEDIA_TYPE_SUBTITLE: @@ -3278,15 +3327,9 @@ static int show_format(WriterContext *w, InputFile *ifile) static void show_error(WriterContext *w, int err) { - char errbuf[128]; - const char *errbuf_ptr = errbuf; - - if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) - errbuf_ptr = strerror(AVUNERROR(err)); - writer_print_section_header(w, SECTION_ID_ERROR); print_int("code", err); - print_str("string", errbuf_ptr); + print_str("string", av_err2str(err)); writer_print_section_footer(w); } @@ -3299,10 +3342,8 @@ static int open_input_file(InputFile *ifile, const char *filename, int scan_all_pmts_set = 0; fmt_ctx = avformat_alloc_context(); - if (!fmt_ctx) { - print_error(filename, AVERROR(ENOMEM)); - exit_program(1); - } + if (!fmt_ctx) + report_and_exit(AVERROR(ENOMEM)); if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); @@ -3320,7 +3361,7 @@ static int open_input_file(InputFile *ifile, const char *filename, ifile->fmt_ctx = fmt_ctx; if (scan_all_pmts_set) av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); - while ((t = av_dict_get(format_opts, "", t, AV_DICT_IGNORE_SUFFIX))) + while ((t = av_dict_iterate(format_opts, t))) av_log(NULL, AV_LOG_WARNING, "Option %s skipped - not known to demuxer.\n", t->key); if (find_stream_info) { @@ -3718,7 +3759,7 @@ static void opt_input_file(void *optctx, const char *arg) exit_program(1); } if (!strcmp(arg, "-")) - arg = "pipe:"; + arg = "fd:"; input_filename = arg; } @@ -3737,7 +3778,7 @@ static void opt_output_file(void *optctx, const char *arg) exit_program(1); } if (!strcmp(arg, "-")) - arg = "pipe:"; + arg = "fd:"; output_filename = arg; } diff --git a/linux/src/fftools_objpool.c b/linux/src/fftools_objpool.c new file mode 100644 index 0000000..2884271 --- /dev/null +++ b/linux/src/fftools_objpool.c @@ -0,0 +1,145 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of objpool.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include + +#include "libavcodec/packet.h" + +#include "libavutil/common.h" +#include "libavutil/error.h" +#include "libavutil/frame.h" +#include "libavutil/mem.h" + +#include "fftools_objpool.h" + +struct ObjPool { + void *pool[32]; + unsigned int pool_count; + + ObjPoolCBAlloc alloc; + ObjPoolCBReset reset; + ObjPoolCBFree free; +}; + +ObjPool *objpool_alloc(ObjPoolCBAlloc cb_alloc, ObjPoolCBReset cb_reset, + ObjPoolCBFree cb_free) +{ + ObjPool *op = av_mallocz(sizeof(*op)); + + if (!op) + return NULL; + + op->alloc = cb_alloc; + op->reset = cb_reset; + op->free = cb_free; + + return op; +} + +void objpool_free(ObjPool **pop) +{ + ObjPool *op = *pop; + + if (!op) + return; + + for (unsigned int i = 0; i < op->pool_count; i++) + op->free(&op->pool[i]); + + av_freep(pop); +} + +int objpool_get(ObjPool *op, void **obj) +{ + if (op->pool_count) { + *obj = op->pool[--op->pool_count]; + op->pool[op->pool_count] = NULL; + } else + *obj = op->alloc(); + + return *obj ? 0 : AVERROR(ENOMEM); +} + +void objpool_release(ObjPool *op, void **obj) +{ + if (!*obj) + return; + + op->reset(*obj); + + if (op->pool_count < FF_ARRAY_ELEMS(op->pool)) + op->pool[op->pool_count++] = *obj; + else + op->free(obj); + + *obj = NULL; +} + +static void *alloc_packet(void) +{ + return av_packet_alloc(); +} +static void *alloc_frame(void) +{ + return av_frame_alloc(); +} + +static void reset_packet(void *obj) +{ + av_packet_unref(obj); +} +static void reset_frame(void *obj) +{ + av_frame_unref(obj); +} + +static void free_packet(void **obj) +{ + AVPacket *pkt = *obj; + av_packet_free(&pkt); + *obj = NULL; +} +static void free_frame(void **obj) +{ + AVFrame *frame = *obj; + av_frame_free(&frame); + *obj = NULL; +} + +ObjPool *objpool_alloc_packets(void) +{ + return objpool_alloc(alloc_packet, reset_packet, free_packet); +} +ObjPool *objpool_alloc_frames(void) +{ + return objpool_alloc(alloc_frame, reset_frame, free_frame); +} diff --git a/linux/src/fftools_objpool.h b/linux/src/fftools_objpool.h new file mode 100644 index 0000000..29cf2b0 --- /dev/null +++ b/linux/src/fftools_objpool.h @@ -0,0 +1,50 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of objpool.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_OBJPOOL_H +#define FFTOOLS_OBJPOOL_H + +typedef struct ObjPool ObjPool; + +typedef void* (*ObjPoolCBAlloc)(void); +typedef void (*ObjPoolCBReset)(void *); +typedef void (*ObjPoolCBFree)(void **); + +void objpool_free(ObjPool **op); +ObjPool *objpool_alloc(ObjPoolCBAlloc cb_alloc, ObjPoolCBReset cb_reset, + ObjPoolCBFree cb_free); +ObjPool *objpool_alloc_packets(void); +ObjPool *objpool_alloc_frames(void); + +int objpool_get(ObjPool *op, void **obj); +void objpool_release(ObjPool *op, void **obj); + +#endif // FFTOOLS_OBJPOOL_H diff --git a/linux/src/fftools_opt_common.c b/linux/src/fftools_opt_common.c index 759f1ae..e95c546 100644 --- a/linux/src/fftools_opt_common.c +++ b/linux/src/fftools_opt_common.c @@ -1,6 +1,7 @@ /* * Option handlers shared between the tools. * Copyright (c) 2022 Taner Sener + * Copyright (c) 2023 ARTHENICA LTD * * This file is part of FFmpeg. * @@ -24,6 +25,12 @@ * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied * by us to develop the ffmpeg-kit library. * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - time field in report_callback updated as double + * * ffmpeg-kit changes by Taner Sener * * 09.2022 @@ -88,7 +95,7 @@ static __thread FILE *report_file = NULL; static __thread int report_file_level = AV_LOG_DEBUG; extern void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, va_list vargs); -extern void (*report_callback)(int, float, float, int64_t, int, double, double); +extern void (*report_callback)(int, float, float, int64_t, double, double, double); extern __thread char *program_name; int show_license(void *optctx, const char *opt, const char *arg) diff --git a/linux/src/fftools_sync_queue.c b/linux/src/fftools_sync_queue.c new file mode 100644 index 0000000..cad5db3 --- /dev/null +++ b/linux/src/fftools_sync_queue.c @@ -0,0 +1,462 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of sync_queue.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/error.h" +#include "libavutil/fifo.h" +#include "libavutil/mathematics.h" +#include "libavutil/mem.h" + +#include "fftools_objpool.h" +#include "fftools_sync_queue.h" + +typedef struct SyncQueueStream { + AVFifo *fifo; + AVRational tb; + + /* stream head: largest timestamp seen */ + int64_t head_ts; + int limiting; + /* no more frames will be sent for this stream */ + int finished; + + uint64_t frames_sent; + uint64_t frames_max; +} SyncQueueStream; + +struct SyncQueue { + enum SyncQueueType type; + + /* no more frames will be sent for any stream */ + int finished; + /* sync head: the stream with the _smallest_ head timestamp + * this stream determines which frames can be output */ + int head_stream; + /* the finished stream with the smallest finish timestamp or -1 */ + int head_finished_stream; + + // maximum buffering duration in microseconds + int64_t buf_size_us; + + SyncQueueStream *streams; + unsigned int nb_streams; + + // pool of preallocated frames to avoid constant allocations + ObjPool *pool; +}; + +static void frame_move(const SyncQueue *sq, SyncQueueFrame dst, + SyncQueueFrame src) +{ + if (sq->type == SYNC_QUEUE_PACKETS) + av_packet_move_ref(dst.p, src.p); + else + av_frame_move_ref(dst.f, src.f); +} + +static int64_t frame_ts(const SyncQueue *sq, SyncQueueFrame frame) +{ + return (sq->type == SYNC_QUEUE_PACKETS) ? + frame.p->pts + frame.p->duration : + frame.f->pts + frame.f->duration; +} + +static int frame_null(const SyncQueue *sq, SyncQueueFrame frame) +{ + return (sq->type == SYNC_QUEUE_PACKETS) ? (frame.p == NULL) : (frame.f == NULL); +} + +static void finish_stream(SyncQueue *sq, unsigned int stream_idx) +{ + SyncQueueStream *st = &sq->streams[stream_idx]; + + st->finished = 1; + + if (st->limiting && st->head_ts != AV_NOPTS_VALUE) { + /* check if this stream is the new finished head */ + if (sq->head_finished_stream < 0 || + av_compare_ts(st->head_ts, st->tb, + sq->streams[sq->head_finished_stream].head_ts, + sq->streams[sq->head_finished_stream].tb) < 0) { + sq->head_finished_stream = stream_idx; + } + + /* mark as finished all streams that should no longer receive new frames, + * due to them being ahead of some finished stream */ + st = &sq->streams[sq->head_finished_stream]; + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st1 = &sq->streams[i]; + if (st != st1 && st1->head_ts != AV_NOPTS_VALUE && + av_compare_ts(st->head_ts, st->tb, st1->head_ts, st1->tb) <= 0) + st1->finished = 1; + } + } + + /* mark the whole queue as finished if all streams are finished */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + if (!sq->streams[i].finished) + return; + } + sq->finished = 1; +} + +static void queue_head_update(SyncQueue *sq) +{ + if (sq->head_stream < 0) { + /* wait for one timestamp in each stream before determining + * the queue head */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st = &sq->streams[i]; + if (st->limiting && st->head_ts == AV_NOPTS_VALUE) + return; + } + + // placeholder value, correct one will be found below + sq->head_stream = 0; + } + + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st_head = &sq->streams[sq->head_stream]; + SyncQueueStream *st_other = &sq->streams[i]; + if (st_other->limiting && st_other->head_ts != AV_NOPTS_VALUE && + av_compare_ts(st_other->head_ts, st_other->tb, + st_head->head_ts, st_head->tb) < 0) + sq->head_stream = i; + } +} + +/* update this stream's head timestamp */ +static void stream_update_ts(SyncQueue *sq, unsigned int stream_idx, int64_t ts) +{ + SyncQueueStream *st = &sq->streams[stream_idx]; + + if (ts == AV_NOPTS_VALUE || + (st->head_ts != AV_NOPTS_VALUE && st->head_ts >= ts)) + return; + + st->head_ts = ts; + + /* if this stream is now ahead of some finished stream, then + * this stream is also finished */ + if (sq->head_finished_stream >= 0 && + av_compare_ts(sq->streams[sq->head_finished_stream].head_ts, + sq->streams[sq->head_finished_stream].tb, + ts, st->tb) <= 0) + finish_stream(sq, stream_idx); + + /* update the overall head timestamp if it could have changed */ + if (st->limiting && + (sq->head_stream < 0 || sq->head_stream == stream_idx)) + queue_head_update(sq); +} + +/* If the queue for the given stream (or all streams when stream_idx=-1) + * is overflowing, trigger a fake heartbeat on lagging streams. + * + * @return 1 if heartbeat triggered, 0 otherwise + */ +static int overflow_heartbeat(SyncQueue *sq, int stream_idx) +{ + SyncQueueStream *st; + SyncQueueFrame frame; + int64_t tail_ts = AV_NOPTS_VALUE; + + /* if no stream specified, pick the one that is most ahead */ + if (stream_idx < 0) { + int64_t ts = AV_NOPTS_VALUE; + + for (int i = 0; i < sq->nb_streams; i++) { + st = &sq->streams[i]; + if (st->head_ts != AV_NOPTS_VALUE && + (ts == AV_NOPTS_VALUE || + av_compare_ts(ts, sq->streams[stream_idx].tb, + st->head_ts, st->tb) < 0)) { + ts = st->head_ts; + stream_idx = i; + } + } + /* no stream has a timestamp yet -> nothing to do */ + if (stream_idx < 0) + return 0; + } + + st = &sq->streams[stream_idx]; + + /* get the chosen stream's tail timestamp */ + for (size_t i = 0; tail_ts == AV_NOPTS_VALUE && + av_fifo_peek(st->fifo, &frame, 1, i) >= 0; i++) + tail_ts = frame_ts(sq, frame); + + /* overflow triggers when the tail is over specified duration behind the head */ + if (tail_ts == AV_NOPTS_VALUE || tail_ts >= st->head_ts || + av_rescale_q(st->head_ts - tail_ts, st->tb, AV_TIME_BASE_Q) < sq->buf_size_us) + return 0; + + /* signal a fake timestamp for all streams that prevent tail_ts from being output */ + tail_ts++; + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueStream *st1 = &sq->streams[i]; + int64_t ts; + + if (st == st1 || st1->finished || + (st1->head_ts != AV_NOPTS_VALUE && + av_compare_ts(tail_ts, st->tb, st1->head_ts, st1->tb) <= 0)) + continue; + + ts = av_rescale_q(tail_ts, st->tb, st1->tb); + if (st1->head_ts != AV_NOPTS_VALUE) + ts = FFMAX(st1->head_ts + 1, ts); + + stream_update_ts(sq, i, ts); + } + + return 1; +} + +int sq_send(SyncQueue *sq, unsigned int stream_idx, SyncQueueFrame frame) +{ + SyncQueueStream *st; + SyncQueueFrame dst; + int64_t ts; + int ret; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + av_assert0(st->tb.num > 0 && st->tb.den > 0); + + if (frame_null(sq, frame)) { + finish_stream(sq, stream_idx); + return 0; + } + if (st->finished) + return AVERROR_EOF; + + ret = objpool_get(sq->pool, (void**)&dst); + if (ret < 0) + return ret; + + frame_move(sq, dst, frame); + + ts = frame_ts(sq, dst); + + ret = av_fifo_write(st->fifo, &dst, 1); + if (ret < 0) { + frame_move(sq, frame, dst); + objpool_release(sq->pool, (void**)&dst); + return ret; + } + + stream_update_ts(sq, stream_idx, ts); + + st->frames_sent++; + if (st->frames_sent >= st->frames_max) + finish_stream(sq, stream_idx); + + return 0; +} + +static int receive_for_stream(SyncQueue *sq, unsigned int stream_idx, + SyncQueueFrame frame) +{ + SyncQueueStream *st_head = sq->head_stream >= 0 ? + &sq->streams[sq->head_stream] : NULL; + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + if (av_fifo_can_read(st->fifo)) { + SyncQueueFrame peek; + int64_t ts; + int cmp = 1; + + av_fifo_peek(st->fifo, &peek, 1, 0); + ts = frame_ts(sq, peek); + + /* check if this stream's tail timestamp does not overtake + * the overall queue head */ + if (ts != AV_NOPTS_VALUE && st_head) + cmp = av_compare_ts(ts, st->tb, st_head->head_ts, st_head->tb); + + /* We can release frames that do not end after the queue head. + * Frames with no timestamps are just passed through with no conditions. + */ + if (cmp <= 0 || ts == AV_NOPTS_VALUE) { + frame_move(sq, frame, peek); + objpool_release(sq->pool, (void**)&peek); + av_fifo_drain2(st->fifo, 1); + return 0; + } + } + + return (sq->finished || (st->finished && !av_fifo_can_read(st->fifo))) ? + AVERROR_EOF : AVERROR(EAGAIN); +} + +static int receive_internal(SyncQueue *sq, int stream_idx, SyncQueueFrame frame) +{ + int nb_eof = 0; + int ret; + + /* read a frame for a specific stream */ + if (stream_idx >= 0) { + ret = receive_for_stream(sq, stream_idx, frame); + return (ret < 0) ? ret : stream_idx; + } + + /* read a frame for any stream with available output */ + for (unsigned int i = 0; i < sq->nb_streams; i++) { + ret = receive_for_stream(sq, i, frame); + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { + nb_eof += (ret == AVERROR_EOF); + continue; + } + return (ret < 0) ? ret : i; + } + + return (nb_eof == sq->nb_streams) ? AVERROR_EOF : AVERROR(EAGAIN); +} + +int sq_receive(SyncQueue *sq, int stream_idx, SyncQueueFrame frame) +{ + int ret = receive_internal(sq, stream_idx, frame); + + /* try again if the queue overflowed and triggered a fake heartbeat + * for lagging streams */ + if (ret == AVERROR(EAGAIN) && overflow_heartbeat(sq, stream_idx)) + ret = receive_internal(sq, stream_idx, frame); + + return ret; +} + +int sq_add_stream(SyncQueue *sq, int limiting) +{ + SyncQueueStream *tmp, *st; + + tmp = av_realloc_array(sq->streams, sq->nb_streams + 1, sizeof(*sq->streams)); + if (!tmp) + return AVERROR(ENOMEM); + sq->streams = tmp; + + st = &sq->streams[sq->nb_streams]; + memset(st, 0, sizeof(*st)); + + st->fifo = av_fifo_alloc2(1, sizeof(SyncQueueFrame), AV_FIFO_FLAG_AUTO_GROW); + if (!st->fifo) + return AVERROR(ENOMEM); + + /* we set a valid default, so that a pathological stream that never + * receives even a real timebase (and no frames) won't stall all other + * streams forever; cf. overflow_heartbeat() */ + st->tb = (AVRational){ 1, 1 }; + st->head_ts = AV_NOPTS_VALUE; + st->frames_max = UINT64_MAX; + st->limiting = limiting; + + return sq->nb_streams++; +} + +void sq_set_tb(SyncQueue *sq, unsigned int stream_idx, AVRational tb) +{ + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + av_assert0(!av_fifo_can_read(st->fifo)); + + if (st->head_ts != AV_NOPTS_VALUE) + st->head_ts = av_rescale_q(st->head_ts, st->tb, tb); + + st->tb = tb; +} + +void sq_limit_frames(SyncQueue *sq, unsigned int stream_idx, uint64_t frames) +{ + SyncQueueStream *st; + + av_assert0(stream_idx < sq->nb_streams); + st = &sq->streams[stream_idx]; + + st->frames_max = frames; + if (st->frames_sent >= st->frames_max) + finish_stream(sq, stream_idx); +} + +SyncQueue *sq_alloc(enum SyncQueueType type, int64_t buf_size_us) +{ + SyncQueue *sq = av_mallocz(sizeof(*sq)); + + if (!sq) + return NULL; + + sq->type = type; + sq->buf_size_us = buf_size_us; + + sq->head_stream = -1; + sq->head_finished_stream = -1; + + sq->pool = (type == SYNC_QUEUE_PACKETS) ? objpool_alloc_packets() : + objpool_alloc_frames(); + if (!sq->pool) { + av_freep(&sq); + return NULL; + } + + return sq; +} + +void sq_free(SyncQueue **psq) +{ + SyncQueue *sq = *psq; + + if (!sq) + return; + + for (unsigned int i = 0; i < sq->nb_streams; i++) { + SyncQueueFrame frame; + while (av_fifo_read(sq->streams[i].fifo, &frame, 1) >= 0) + objpool_release(sq->pool, (void**)&frame); + + av_fifo_freep2(&sq->streams[i].fifo); + } + + av_freep(&sq->streams); + + objpool_free(&sq->pool); + + av_freep(psq); +} diff --git a/linux/src/fftools_sync_queue.h b/linux/src/fftools_sync_queue.h new file mode 100644 index 0000000..0fd7b3c --- /dev/null +++ b/linux/src/fftools_sync_queue.h @@ -0,0 +1,122 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of sync_queue.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_SYNC_QUEUE_H +#define FFTOOLS_SYNC_QUEUE_H + +#include + +#include "libavcodec/packet.h" + +#include "libavutil/frame.h" + +enum SyncQueueType { + SYNC_QUEUE_PACKETS, + SYNC_QUEUE_FRAMES, +}; + +typedef union SyncQueueFrame { + AVFrame *f; + AVPacket *p; +} SyncQueueFrame; + +#define SQFRAME(frame) ((SyncQueueFrame){ .f = (frame) }) +#define SQPKT(pkt) ((SyncQueueFrame){ .p = (pkt) }) + +typedef struct SyncQueue SyncQueue; + +/** + * Allocate a sync queue of the given type. + * + * @param buf_size_us maximum duration that will be buffered in microseconds + */ +SyncQueue *sq_alloc(enum SyncQueueType type, int64_t buf_size_us); +void sq_free(SyncQueue **sq); + +/** + * Add a new stream to the sync queue. + * + * @param limiting whether the stream is limiting, i.e. no other stream can be + * longer than this one + * @return + * - a non-negative stream index on success + * - a negative error code on error + */ +int sq_add_stream(SyncQueue *sq, int limiting); + +/** + * Set the timebase for the stream with index stream_idx. Should be called + * before sending any frames for this stream. + */ +void sq_set_tb(SyncQueue *sq, unsigned int stream_idx, AVRational tb); + +/** + * Limit the number of output frames for stream with index stream_idx + * to max_frames. + */ +void sq_limit_frames(SyncQueue *sq, unsigned int stream_idx, + uint64_t max_frames); + +/** + * Submit a frame for the stream with index stream_idx. + * + * On success, the sync queue takes ownership of the frame and will reset the + * contents of the supplied frame. On failure, the frame remains owned by the + * caller. + * + * Sending a frame with NULL contents marks the stream as finished. + * + * @return + * - 0 on success + * - AVERROR_EOF when no more frames should be submitted for this stream + * - another a negative error code on failure + */ +int sq_send(SyncQueue *sq, unsigned int stream_idx, SyncQueueFrame frame); + +/** + * Read a frame from the queue. + * + * @param stream_idx index of the stream to read a frame for. May be -1, then + * try to read a frame from any stream that is ready for + * output. + * @param frame output frame will be written here on success. The frame is owned + * by the caller. + * + * @return + * - a non-negative index of the stream to which the returned frame belongs + * - AVERROR(EAGAIN) when more frames need to be submitted to the queue + * - AVERROR_EOF when no more frames will be available for this stream (for any + * stream if stream_idx is -1) + * - another negative error code on failure + */ +int sq_receive(SyncQueue *sq, int stream_idx, SyncQueueFrame frame); + +#endif // FFTOOLS_SYNC_QUEUE_H diff --git a/linux/src/fftools_thread_queue.c b/linux/src/fftools_thread_queue.c new file mode 100644 index 0000000..e8eade5 --- /dev/null +++ b/linux/src/fftools_thread_queue.c @@ -0,0 +1,259 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of thread_queue.c file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + * - fftools header names updated + */ + +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/error.h" +#include "libavutil/fifo.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" +#include "libavutil/thread.h" + +#include "fftools_objpool.h" +#include "fftools_thread_queue.h" + +enum { + FINISHED_SEND = (1 << 0), + FINISHED_RECV = (1 << 1), +}; + +typedef struct FifoElem { + void *obj; + unsigned int stream_idx; +} FifoElem; + +struct ThreadQueue { + int *finished; + unsigned int nb_streams; + + AVFifo *fifo; + + ObjPool *obj_pool; + void (*obj_move)(void *dst, void *src); + + pthread_mutex_t lock; + pthread_cond_t cond; +}; + +void tq_free(ThreadQueue **ptq) +{ + ThreadQueue *tq = *ptq; + + if (!tq) + return; + + if (tq->fifo) { + FifoElem elem; + while (av_fifo_read(tq->fifo, &elem, 1) >= 0) + objpool_release(tq->obj_pool, &elem.obj); + } + av_fifo_freep2(&tq->fifo); + + objpool_free(&tq->obj_pool); + + av_freep(&tq->finished); + + pthread_cond_destroy(&tq->cond); + pthread_mutex_destroy(&tq->lock); + + av_freep(ptq); +} + +ThreadQueue *tq_alloc(unsigned int nb_streams, size_t queue_size, + ObjPool *obj_pool, void (*obj_move)(void *dst, void *src)) +{ + ThreadQueue *tq; + int ret; + + tq = av_mallocz(sizeof(*tq)); + if (!tq) + return NULL; + + ret = pthread_cond_init(&tq->cond, NULL); + if (ret) { + av_freep(&tq); + return NULL; + } + + ret = pthread_mutex_init(&tq->lock, NULL); + if (ret) { + pthread_cond_destroy(&tq->cond); + av_freep(&tq); + return NULL; + } + + tq->finished = av_calloc(nb_streams, sizeof(*tq->finished)); + if (!tq->finished) + goto fail; + tq->nb_streams = nb_streams; + + tq->fifo = av_fifo_alloc2(queue_size, sizeof(FifoElem), 0); + if (!tq->fifo) + goto fail; + + tq->obj_pool = obj_pool; + tq->obj_move = obj_move; + + return tq; +fail: + tq_free(&tq); + return NULL; +} + +int tq_send(ThreadQueue *tq, unsigned int stream_idx, void *data) +{ + int *finished; + int ret; + + av_assert0(stream_idx < tq->nb_streams); + finished = &tq->finished[stream_idx]; + + pthread_mutex_lock(&tq->lock); + + if (*finished & FINISHED_SEND) { + ret = AVERROR(EINVAL); + goto finish; + } + + while (!(*finished & FINISHED_RECV) && !av_fifo_can_write(tq->fifo)) + pthread_cond_wait(&tq->cond, &tq->lock); + + if (*finished & FINISHED_RECV) { + ret = AVERROR_EOF; + *finished |= FINISHED_SEND; + } else { + FifoElem elem = { .stream_idx = stream_idx }; + + ret = objpool_get(tq->obj_pool, &elem.obj); + if (ret < 0) + goto finish; + + tq->obj_move(elem.obj, data); + + ret = av_fifo_write(tq->fifo, &elem, 1); + av_assert0(ret >= 0); + pthread_cond_broadcast(&tq->cond); + } + +finish: + pthread_mutex_unlock(&tq->lock); + + return ret; +} + +static int receive_locked(ThreadQueue *tq, int *stream_idx, + void *data) +{ + FifoElem elem; + unsigned int nb_finished = 0; + + if (av_fifo_read(tq->fifo, &elem, 1) >= 0) { + tq->obj_move(data, elem.obj); + objpool_release(tq->obj_pool, &elem.obj); + *stream_idx = elem.stream_idx; + return 0; + } + + for (unsigned int i = 0; i < tq->nb_streams; i++) { + if (!(tq->finished[i] & FINISHED_SEND)) + continue; + + /* return EOF to the consumer at most once for each stream */ + if (!(tq->finished[i] & FINISHED_RECV)) { + tq->finished[i] |= FINISHED_RECV; + *stream_idx = i; + return AVERROR_EOF; + } + + nb_finished++; + } + + return nb_finished == tq->nb_streams ? AVERROR_EOF : AVERROR(EAGAIN); +} + +int tq_receive(ThreadQueue *tq, int *stream_idx, void *data) +{ + int ret; + + *stream_idx = -1; + + pthread_mutex_lock(&tq->lock); + + while (1) { + ret = receive_locked(tq, stream_idx, data); + if (ret == AVERROR(EAGAIN)) { + pthread_cond_wait(&tq->cond, &tq->lock); + continue; + } + + break; + } + + if (ret == 0) + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); + + return ret; +} + +void tq_send_finish(ThreadQueue *tq, unsigned int stream_idx) +{ + av_assert0(stream_idx < tq->nb_streams); + + pthread_mutex_lock(&tq->lock); + + /* mark the stream as send-finished; + * next time the consumer thread tries to read this stream it will get + * an EOF and recv-finished flag will be set */ + tq->finished[stream_idx] |= FINISHED_SEND; + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); +} + +void tq_receive_finish(ThreadQueue *tq, unsigned int stream_idx) +{ + av_assert0(stream_idx < tq->nb_streams); + + pthread_mutex_lock(&tq->lock); + + /* mark the stream as recv-finished; + * next time the producer thread tries to send for this stream, it will + * get an EOF and send-finished flag will be set */ + tq->finished[stream_idx] |= FINISHED_RECV; + pthread_cond_broadcast(&tq->cond); + + pthread_mutex_unlock(&tq->lock); +} diff --git a/linux/src/fftools_thread_queue.h b/linux/src/fftools_thread_queue.h new file mode 100644 index 0000000..b8736dd --- /dev/null +++ b/linux/src/fftools_thread_queue.h @@ -0,0 +1,94 @@ +/* + * This file is part of FFmpeg. + * Copyright (c) 2023 ARTHENICA LTD + * + * FFmpeg 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 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This file is the modified version of thread_queue.h file living in ffmpeg source code under the fftools folder. We + * manually update it each time we depend on a new ffmpeg version. Below you can see the list of changes applied + * by us to develop ffmpeg-kit library. + * + * ffmpeg-kit changes by ARTHENICA LTD + * + * 07.2023 + * -------------------------------------------------------- + * - FFmpeg 6.0 changes migrated + */ + +#ifndef FFTOOLS_THREAD_QUEUE_H +#define FFTOOLS_THREAD_QUEUE_H + +#include + +#include "fftools_objpool.h" + +typedef struct ThreadQueue ThreadQueue; + +/** + * Allocate a queue for sending data between threads. + * + * @param nb_streams number of streams for which a distinct EOF state is + * maintained + * @param queue_size number of items that can be stored in the queue without + * blocking + * @param obj_pool object pool that will be used to allocate items stored in the + * queue; the pool becomes owned by the queue + * @param callback that moves the contents between two data pointers + */ +ThreadQueue *tq_alloc(unsigned int nb_streams, size_t queue_size, + ObjPool *obj_pool, void (*obj_move)(void *dst, void *src)); +void tq_free(ThreadQueue **tq); + +/** + * Send an item for the given stream to the queue. + * + * @param data the item to send, its contents will be moved using the callback + * provided to tq_alloc(); on failure the item will be left + * untouched + * @return + * - 0 the item was successfully sent + * - AVERROR(ENOMEM) could not allocate an item for writing to the FIFO + * - AVERROR(EINVAL) the sending side has previously been marked as finished + * - AVERROR_EOF the receiving side has marked the given stream as finished + */ +int tq_send(ThreadQueue *tq, unsigned int stream_idx, void *data); +/** + * Mark the given stream finished from the sending side. + */ +void tq_send_finish(ThreadQueue *tq, unsigned int stream_idx); + +/** + * Read the next item from the queue. + * + * @param stream_idx the index of the stream that was processed or -1 will be + * written here + * @param data the data item will be written here on success using the + * callback provided to tq_alloc() + * @return + * - 0 a data item was successfully read; *stream_idx contains a non-negative + * stream index + * - AVERROR_EOF When *stream_idx is non-negative, this signals that the sending + * side has marked the given stream as finished. This will happen at most once + * for each stream. When *stream_idx is -1, all streams are done. + */ +int tq_receive(ThreadQueue *tq, int *stream_idx, void *data); +/** + * Mark the given stream finished from the receiving side. + */ +void tq_receive_finish(ThreadQueue *tq, unsigned int stream_idx); + +#endif // FFTOOLS_THREAD_QUEUE_H diff --git a/scripts/android/libiconv.sh b/scripts/android/libiconv.sh index 2d8173f..15535c7 100755 --- a/scripts/android/libiconv.sh +++ b/scripts/android/libiconv.sh @@ -1,7 +1,7 @@ #!/bin/bash # INIT SUBMODULES -${SED_INLINE} 's|git://git.savannah.gnu.org|https://github.com/tanersener|g' "${BASEDIR}"/src/"${LIB_NAME}"/.gitmodules || return 1 +${SED_INLINE} 's|git://git.savannah.gnu.org|https://github.com/arthenica|g' "${BASEDIR}"/src/"${LIB_NAME}"/.gitmodules || return 1 ./gitsub.sh pull || return 1 # ALWAYS CLEAN THE PREVIOUS BUILD diff --git a/scripts/source.sh b/scripts/source.sh index cbc4ab5..5aa71f0 100755 --- a/scripts/source.sh +++ b/scripts/source.sh @@ -33,8 +33,8 @@ get_library_source() { ;; ffmpeg) SOURCE_REPO_URL="https://github.com/arthenica/FFmpeg" - SOURCE_ID="65f96a965af415300f7533c75940eeae4e3a2287" - SOURCE_TYPE="COMMIT" + SOURCE_ID="n6.0" + SOURCE_TYPE="TAG" ;; fontconfig) SOURCE_REPO_URL="https://github.com/arthenica/fontconfig" diff --git a/tools/android/build.gradle b/tools/android/build.gradle index cf35c13..2917a43 100644 --- a/tools/android/build.gradle +++ b/tools/android/build.gradle @@ -2,14 +2,14 @@ apply plugin: 'com.android.library' android { namespace 'com.arthenica.ffmpegkit' - compileSdk 31 + compileSdk 33 ndkVersion "22.1.7171670" defaultConfig { minSdk 24 - targetSdk 31 - versionCode 240510 - versionName "5.1" + targetSdk 33 + versionCode 240600 + versionName "6.0" project.archivesBaseName = "ffmpeg-kit" consumerProguardFiles "consumer-rules.pro" } diff --git a/tools/android/build.lts.gradle b/tools/android/build.lts.gradle index 4bf3b86..f33b028 100644 --- a/tools/android/build.lts.gradle +++ b/tools/android/build.lts.gradle @@ -2,14 +2,14 @@ apply plugin: 'com.android.library' android { namespace 'com.arthenica.ffmpegkit' - compileSdk 31 + compileSdk 33 ndkVersion "22.1.7171670" defaultConfig { minSdk 16 - targetSdk 31 - versionCode 160510 - versionName "5.1.LTS" + targetSdk 33 + versionCode 160600 + versionName "6.0.LTS" project.archivesBaseName = "ffmpeg-kit" consumerProguardFiles "consumer-rules.pro" } diff --git a/tools/protocols/libavformat_file.c b/tools/protocols/libavformat_file.c index ccd07d9..6114d23 100644 --- a/tools/protocols/libavformat_file.c +++ b/tools/protocols/libavformat_file.c @@ -19,7 +19,7 @@ #include "libavutil/file.h" -static int64_t fd_seek(URLContext *h, int64_t pos, int whence) +static int64_t saf_seek(URLContext *h, int64_t pos, int whence) { FileContext *c = h->priv_data; int64_t ret; @@ -35,7 +35,7 @@ static int64_t fd_seek(URLContext *h, int64_t pos, int whence) return ret < 0 ? AVERROR(errno) : ret; } -static int fd_open(URLContext *h, const char *filename, int flags) +static int saf_open(URLContext *h, const char *filename, int flags) { FileContext *c = h->priv_data; int saf_id; @@ -79,7 +79,7 @@ static int fd_open(URLContext *h, const char *filename, int flags) return 0; } -static int fd_check(URLContext *h, int mask) +static int saf_check(URLContext *h, int mask) { int ret = 0; const char *filename = h->filename; @@ -112,7 +112,7 @@ static int fd_check(URLContext *h, int mask) return ret; } -static int fd_delete(URLContext *h) +static int saf_delete(URLContext *h) { #if HAVE_UNISTD_H int ret; @@ -135,7 +135,7 @@ static int fd_delete(URLContext *h) #endif /* HAVE_UNISTD_H */ } -static int fd_move(URLContext *h_src, URLContext *h_dst) +static int saf_move(URLContext *h_src, URLContext *h_dst) { const char *filename_src = h_src->filename; const char *filename_dst = h_dst->filename; @@ -148,7 +148,7 @@ static int fd_move(URLContext *h_src, URLContext *h_dst) return 0; } -static int fd_close(URLContext *h) +static int saf_close(URLContext *h) { FileContext *c = h->priv_data; @@ -169,15 +169,15 @@ static const AVClass saf_class = { const URLProtocol ff_saf_protocol = { .name = "saf", - .url_open = fd_open, + .url_open = saf_open, .url_read = file_read, .url_write = file_write, - .url_seek = fd_seek, - .url_close = fd_close, + .url_seek = saf_seek, + .url_close = saf_close, .url_get_file_handle = file_get_handle, - .url_check = fd_check, - .url_delete = fd_delete, - .url_move = fd_move, + .url_check = saf_check, + .url_delete = saf_delete, + .url_move = saf_move, .priv_data_size = sizeof(FileContext), .priv_data_class = &saf_class, .default_whitelist = "saf,crypto,data"