From 8d3766bb57169436ea97249a8fe0663497e28e17 Mon Sep 17 00:00:00 2001 From: Aaron Soulliere Date: Sat, 4 Jul 2026 01:51:37 -0400 Subject: [PATCH 01/29] chore: establish 5.1.4 build baseline and fix emulated-build OOM Reproduce the current (pre-upgrade) FFmpeg n5.1.4 WASM build as a rollback reference before the 7.1 upgrade effort. - build/ffmpeg.sh: cap FFmpeg `make` parallelism (FFMPEG_JOBS, default 4) so individual clang processes aren't OOM-killed when the amd64-only emsdk 3.1.40 toolchain runs under x86 emulation on Apple Silicon - .gitignore: ignore local buildx cache dirs (build-cache-st/-mt) - BASELINE-n5.1.4.md: record toolchain, external-lib pins, FFmpeg configure flags, artifact sizes, test results (ST 23 pass, MT wrapper 11 pass), and pre-existing harness gaps (broken serve script, MT-core main-thread test, Node test path) Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 4 +++ BASELINE-n5.1.4.md | 87 ++++++++++++++++++++++++++++++++++++++++++++++ build/ffmpeg.sh | 5 ++- 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 BASELINE-n5.1.4.md diff --git a/.gitignore b/.gitignore index f513c13805c..d74d2bb5759 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ dist /.nyc_output .DS_Store +# local buildx cache (see Makefile EXTRA_ARGS / CI cache steps) +build-cache-st/ +build-cache-mt/ + # ide .idea/ diff --git a/BASELINE-n5.1.4.md b/BASELINE-n5.1.4.md new file mode 100644 index 00000000000..6fbbfb4b30e --- /dev/null +++ b/BASELINE-n5.1.4.md @@ -0,0 +1,87 @@ +# Baseline snapshot — pre-upgrade rollback reference + +This records the exact state of the **current (pre-upgrade) `n5.1.4` build** so the +FFmpeg 7.1 upgrade (and every intermediate phase) can be diffed against a known-good +reference. Captured on the `chore/phase-0-baseline` branch. + +## Toolchain + +| Component | Version / pin | Source | +|----------------------|----------------------------------------|--------| +| FFmpeg | `n5.1.4` | `Dockerfile` `FFMPEG_VERSION` | +| Emscripten (emsdk) | `3.1.40` | `Dockerfile` base image (amd64-only) | +| Host build note | Built under **x86 emulation** on Apple Silicon (arm64); `emsdk:3.1.40` has no arm64 image | — | + +## External libraries (all from `github.com/ffmpegwasm/*` forks unless noted) + +| Library | Pin (branch/tag) | Notes for upgrade | +|------------|--------------------|-------------------| +| x264 | `4-cores` (branch) | **floating branch** — pin to SHA in Phase 4 | +| x265 | `3.4` | **DROP** in Phase 3 (HEVC encode; decode is native) | +| libvpx | `v1.13.1` | keep, consider bump | +| lame | `master` (branch) | **floating branch** — pin to SHA in Phase 4 | +| ogg | `v1.3.4` | **DROP** with vorbis/theora | +| theora | `v1.1.1` (2009) | **DROP** in Phase 3 | +| opus | `v1.3.1` | keep | +| vorbis | `v1.3.3` | **DROP** in Phase 3 | +| zlib | `v1.2.11` | **BUMP → 1.3.1** (CVE-2018-25032, CVE-2022-37434) | +| libwebp | `v1.3.2` | keep | +| freetype2 | `VER-2-10-4` (2020)| **BUMP** (CVEs) | +| fribidi | `v1.0.9` (upstream)| **BUMP → 1.0.13+** (CVE-2022-2530x) | +| harfbuzz | `5.2.0` (upstream) | **BUMP → 6.0+** (CVE-2023-25193) | +| libass | `0.15.0` | keep | +| zimg | `release-3.0.5` | keep | + +## FFmpeg configure (extracted from `packages/core/dist/umd/ffmpeg-core.wasm`) + +``` +--target-os=none --arch=x86_32 --enable-cross-compile +--disable-asm --disable-stripping --disable-programs --disable-doc --disable-debug +--disable-runtime-cpudetect --disable-autodetect +--nm=emnm --ar=emar --ranlib=emranlib --cc=emcc --cxx=em++ --objcc=emcc --dep-cc=emcc +--extra-cflags='-I/opt/include -O3 -msimd128' +--extra-cxxflags='-I/opt/include -O3 -msimd128' +--disable-pthreads --disable-w32threads --disable-os2threads # (ST variant) +--enable-gpl +--enable-libx264 --enable-libx265 --enable-libvpx --enable-libmp3lame +--enable-libtheora --enable-libvorbis --enable-libopus --enable-zlib +--enable-libwebp --enable-libfreetype --enable-libfribidi --enable-libass --enable-libzimg +``` + +Note: prod build enables `-msimd128` (Emscripten wasm SIMD); FFmpeg's own x86 asm stays +`--disable-asm`. (Corrects an earlier plan note that said SIMD was off.) + +## Artifacts + +| Artifact | Size | +|-------------------------------------------|---------| +| `packages/core/dist/umd/ffmpeg-core.wasm` | ~32.2 MB | +| `packages/core/dist/esm/ffmpeg-core.wasm` | ~32.2 MB | +| `packages/core/dist/{umd,esm}/ffmpeg-core.js` | ~112 KB | + +Target for the trimmed 7.1 build: smaller wasm after dropping x265/theora/vorbis. + +## Test results (baseline) + +Run via headless Chrome. MT suites require cross-origin isolation (COOP/COEP), served +here by a scratchpad COI server because the repo's `serve` script is broken (see gaps). + +| Suite | Result | Notes | +|--------------------------------|--------------|-------| +| `test:browser:core:st` | **12 passing** | ST core, full green | +| `test:browser:ffmpeg:st` | **11 passing** | ST wrapper, full green | +| `test:browser:ffmpeg:mt` | **11 passing** | **MT real-world path** (core runs in a Worker) — transcodes correctly | +| `test:browser:core:mt` | 8 passing / **4 failing** | ⚠️ pre-existing test-design limit — loads MT core on the page **main thread**; the 4 threaded ops fail with `Atomics.wait cannot be called in this context` (forbidden on a Window main thread by spec, unfixable by headers/flags) | +| `test:node:core:*` | ⚠️ pre-existing gap — UMD core needs browser globals (`self`, `location`); Node harness does not shim them | + +**Bottom line:** ST fully green; MT build functional and verified via the wrapper/Worker +path (the way it's actually used). The MT-core-direct and Node suites have pre-existing +harness limitations, not build defects. + +## Known baseline gaps (inform Phase 1) + +- **No transcode-correctness assertions** — tests only check exit code / non-empty / progress==1; a codec regression would pass silently. (Phase 1 adds PSNR/SSIM + stream-probe goldens.) +- **`serve` script is broken** — `package.json` `serve` passes `--headers '{...}'` to `http-server@14.1.1`, which has **no such option** (only `--cors`), so COOP/COEP headers are never sent and pages aren't cross-origin isolated. MT suites can't run against it. Phase 1 should replace it with a server that actually sets COOP/COEP (a minimal one is proven in `scratchpad/coi-server.js`). +- **MT core direct test unsupported by design** — `tests/ffmpeg-core-mt.test.html` runs the MT core on the page main thread; threaded ops hit `Atomics.wait` (illegal on main thread). Phase 1 fix: drive the core from a Worker in that test, or drop it in favor of the wrapper MT test. +- **Node test path broken** — needs `self`/`location`/`document` shims or an ESM-in-Node loader. +- **Build fragility on Apple Silicon** — amd64-only emsdk emulated; required 24 GiB Docker VM + `make -j4` cap (`build/ffmpeg.sh`) to avoid OOM. Phase 2 (native arm64 emsdk) retires this. diff --git a/build/ffmpeg.sh b/build/ffmpeg.sh index da89c451aea..2cef0c7b101 100755 --- a/build/ffmpeg.sh +++ b/build/ffmpeg.sh @@ -30,4 +30,7 @@ CONF_FLAGS=( ) emconfigure ./configure "${CONF_FLAGS[@]}" $@ -emmake make -j +# Cap parallelism: unbounded `make -j` spawns one clang per libavfilter TU and +# OOM-kills individual compilers when the toolchain runs under x86 emulation +# (amd64-only emsdk on arm64 hosts). Override with FFMPEG_JOBS if desired. +emmake make -j"${FFMPEG_JOBS:-4}" From bc8027918332ffb3e139e1eb9abcef4c788968a4 Mon Sep 17 00:00:00 2001 From: Aaron Soulliere Date: Sat, 4 Jul 2026 02:41:23 -0400 Subject: [PATCH 02/29] build: bump emsdk to 6.0.2 (native arm64) and drop x265 Move the toolchain off the amd64-only emsdk 3.1.40, which forced slow, OOM-prone x86 emulation on Apple Silicon. Only emscripten/emsdk:latest is multi-arch, so pin it by manifest-list digest (= Emscripten 6.0.2): reproducible builds that run native on both arm64 (local) and amd64 (CI). Drop x265: its x86-centric CMake passes `-march=`, which emsdk 6.0.2's newer clang rejects for the wasm target. x265 was already slated for removal (HEVC *encode* only; HEVC decode remains via FFmpeg's native decoder), so this both unblocks the toolchain bump and advances the trim. All other vendored libs build cleanly under 6.0.2. FFmpeg itself is not built here yet; the current n5.1.4 is superseded by the 7.1 upgrade (the pre-upgrade baseline is captured on chore/phase-0-baseline). Co-Authored-By: Claude Opus 4.8 (1M context) --- Dockerfile | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 51275c46464..82b27851acb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,11 @@ # syntax=docker/dockerfile-upstream:master-labs # Base emsdk image with environment variables. -FROM emscripten/emsdk:3.1.40 AS emsdk-base +# Pinned to emscripten/emsdk:latest by manifest-list digest = Emscripten 6.0.2. +# Rationale: only the `latest` tag is multi-arch (arm64+amd64); every versioned +# emsdk tag is amd64-only, which forced slow, OOM-prone x86 emulation on Apple +# Silicon. Pinning the list digest keeps builds reproducible AND native per-arch. +FROM emscripten/emsdk@sha256:644883f58ca15c38c8be59b3a727ba0eff347729bc31d50a3348a6c9ed92bc07 AS emsdk-base ARG EXTRA_CFLAGS ARG EXTRA_LDFLAGS ARG FFMPEG_ST @@ -27,13 +31,6 @@ ADD https://github.com/ffmpegwasm/x264.git#$X264_BRANCH /src COPY build/x264.sh /src/build.sh RUN bash -x /src/build.sh -# Build x265 -FROM emsdk-base AS x265-builder -ENV X265_BRANCH=3.4 -ADD https://github.com/ffmpegwasm/x265.git#$X265_BRANCH /src -COPY build/x265.sh /src/build.sh -RUN bash -x /src/build.sh - # Build libvpx FROM emsdk-base AS libvpx-builder ENV LIBVPX_BRANCH=v1.13.1 @@ -137,7 +134,6 @@ FROM emsdk-base AS ffmpeg-base RUN embuilder build sdl2 sdl2-mt ADD https://github.com/FFmpeg/FFmpeg.git#$FFMPEG_VERSION /src COPY --from=x264-builder $INSTALL_DIR $INSTALL_DIR -COPY --from=x265-builder $INSTALL_DIR $INSTALL_DIR COPY --from=libvpx-builder $INSTALL_DIR $INSTALL_DIR COPY --from=lame-builder $INSTALL_DIR $INSTALL_DIR COPY --from=opus-builder $INSTALL_DIR $INSTALL_DIR @@ -153,7 +149,6 @@ COPY build/ffmpeg.sh /src/build.sh RUN bash -x /src/build.sh \ --enable-gpl \ --enable-libx264 \ - --enable-libx265 \ --enable-libvpx \ --enable-libmp3lame \ --enable-libtheora \ @@ -174,7 +169,6 @@ COPY build/ffmpeg-wasm.sh build.sh # libraries to link ENV FFMPEG_LIBS \ -lx264 \ - -lx265 \ -lvpx \ -lmp3lame \ -logg \ From 8634391811b6dc53365dc48acc26f92cf539eb4b Mon Sep 17 00:00:00 2001 From: Aaron Soulliere Date: Sat, 4 Jul 2026 03:35:43 -0400 Subject: [PATCH 03/29] =?UTF-8?q?wip:=20port=20fftools=20to=20FFmpeg=207.1?= =?UTF-8?q?.5=20=E2=80=94=20MT=20core=20builds=20(runtime=20WIP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Toolchain + build now produce a statically-linked MT ffmpeg-core.wasm (~27.5 MB) reporting "version 7.1.5" (libavcodec 61). NOT yet functional at runtime — MT scheduler hangs during the test suite; re-entrancy, progress, and timeout bridges are not reimplemented for 7.x yet. Build/toolchain: - Dockerfile: FFMPEG_VERSION n5.1.4 -> n7.1.5 - build/ffmpeg.sh: scope -fPIC to FFmpeg configure (emsdk 6.0.2 probe fix) and strip *.so from the dep prefix so -l links statically (zlib's CMake emits libz.so despite BUILD_SHARED_LIBS=OFF, which emsdk 6.0.2 turned into a runtime dlopen) - build/ffmpeg-wasm.sh: add 7.x fftools (dec/demux/enc/mux_init/sched + objpool/sync_queue/thread_queue); add -I./compat/stdbit (C23 header) - build/fribidi.sh: serial install (c2man man-page race under -j) fftools (pristine 7.1.5 + minimal wasm patches): - vendored all 7.1.5 fftools/*.c/*.h (scheduler-based frontend) - ffmpeg.c: main() -> ffmpeg(); ffprobe.c: main() -> ffprobe() - ffprobe.c: rename program_name / program_birth_year / show_help_default (+_ffprobe) to avoid link collisions with ffmpeg.c - bind.js: capture _ffmpeg()/_ffprobe() return value into Module.ret (7.x removed exit_program(); main() returns the code directly) Still TODO: MT runtime hang; re-entrancy (init_globals equivalent); progress bridge; timeout; ST-core threading decision; lib version bumps + theora/vorbis trim. Co-Authored-By: Claude Opus 4.8 (1M context) --- Dockerfile | 3 +- build/ffmpeg-wasm.sh | 34 +- build/ffmpeg.sh | 18 +- build/fribidi.sh | 7 +- src/bind/ffmpeg/bind.js | 8 +- src/fftools/cmdutils.c | 876 +++++-- src/fftools/cmdutils.h | 261 +- src/fftools/ffmpeg.c | 4539 ++++----------------------------- src/fftools/ffmpeg.h | 1053 +++++--- src/fftools/ffmpeg_dec.c | 1769 +++++++++++++ src/fftools/ffmpeg_demux.c | 1994 +++++++++++++++ src/fftools/ffmpeg_enc.c | 926 +++++++ src/fftools/ffmpeg_filter.c | 3737 ++++++++++++++++++++------- src/fftools/ffmpeg_hw.c | 286 +-- src/fftools/ffmpeg_mux.c | 890 +++++-- src/fftools/ffmpeg_mux.h | 121 + src/fftools/ffmpeg_mux_init.c | 3395 ++++++++++++++++++++++++ src/fftools/ffmpeg_opt.c | 4109 ++++++++--------------------- src/fftools/ffmpeg_sched.c | 2651 +++++++++++++++++++ src/fftools/ffmpeg_sched.h | 504 ++++ src/fftools/ffmpeg_utils.h | 57 + src/fftools/ffplay.c | 556 ++-- src/fftools/ffplay_renderer.c | 836 ++++++ src/fftools/ffplay_renderer.h | 41 + src/fftools/ffprobe.c | 1455 +++++++---- src/fftools/fopen_utf8.h | 1 + src/fftools/objpool.c | 131 + src/fftools/objpool.h | 37 + src/fftools/opt_common.c | 194 +- src/fftools/opt_common.h | 62 +- src/fftools/sync_queue.c | 719 ++++++ src/fftools/sync_queue.h | 118 + src/fftools/thread_queue.c | 254 ++ src/fftools/thread_queue.h | 81 + 34 files changed, 21758 insertions(+), 9965 deletions(-) create mode 100644 src/fftools/ffmpeg_dec.c create mode 100644 src/fftools/ffmpeg_demux.c create mode 100644 src/fftools/ffmpeg_enc.c create mode 100644 src/fftools/ffmpeg_mux.h create mode 100644 src/fftools/ffmpeg_mux_init.c create mode 100644 src/fftools/ffmpeg_sched.c create mode 100644 src/fftools/ffmpeg_sched.h create mode 100644 src/fftools/ffmpeg_utils.h create mode 100644 src/fftools/ffplay_renderer.c create mode 100644 src/fftools/ffplay_renderer.h create mode 100644 src/fftools/objpool.c create mode 100644 src/fftools/objpool.h create mode 100644 src/fftools/sync_queue.c create mode 100644 src/fftools/sync_queue.h create mode 100644 src/fftools/thread_queue.c create mode 100644 src/fftools/thread_queue.h diff --git a/Dockerfile b/Dockerfile index 82b27851acb..1c124246650 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,8 +11,7 @@ ARG EXTRA_LDFLAGS ARG FFMPEG_ST ARG FFMPEG_MT ENV INSTALL_DIR=/opt -# We cannot upgrade to n6.0 as ffmpeg bin only supports multithread at the moment. -ENV FFMPEG_VERSION=n5.1.4 +ENV FFMPEG_VERSION=n7.1.5 ENV CFLAGS="-I$INSTALL_DIR/include $CFLAGS $EXTRA_CFLAGS" ENV CXXFLAGS="$CFLAGS" ENV LDFLAGS="-L$INSTALL_DIR/lib $LDFLAGS $CFLAGS $EXTRA_LDFLAGS" diff --git a/build/ffmpeg-wasm.sh b/build/ffmpeg-wasm.sh index faa2f725ca8..09e9c56425e 100755 --- a/build/ffmpeg-wasm.sh +++ b/build/ffmpeg-wasm.sh @@ -8,9 +8,10 @@ set -euo pipefail EXPORT_NAME="createFFmpegCore" CONF_FLAGS=( - -I. - -I./src/fftools - -I$INSTALL_DIR/include + -I. + -I./src/fftools + -I./compat/stdbit # FFmpeg 7.x fftools use C23 ; emsdk lacks it, use FFmpeg's compat fallback + -I$INSTALL_DIR/include -L$INSTALL_DIR/lib -Llibavcodec -Llibavdevice @@ -43,15 +44,24 @@ CONF_FLAGS=( -sEXPORTED_RUNTIME_METHODS=$(node src/bind/ffmpeg/export-runtime.js) # exported built-in functions -lworkerfs.js --pre-js src/bind/ffmpeg/bind.js # extra bindings, contains most of the ffmpeg.wasm javascript code - # ffmpeg source code - src/fftools/cmdutils.c - src/fftools/ffmpeg.c - src/fftools/ffmpeg_filter.c - src/fftools/ffmpeg_hw.c - src/fftools/ffmpeg_mux.c - src/fftools/ffmpeg_opt.c - src/fftools/opt_common.c - src/fftools/ffprobe.c + # ffmpeg source code (FFmpeg 7.x fftools: scheduler-based frontend adds + # ffmpeg_dec/enc/demux/mux_init/sched + objpool/sync_queue/thread_queue) + src/fftools/cmdutils.c + src/fftools/ffmpeg.c + src/fftools/ffmpeg_dec.c + src/fftools/ffmpeg_demux.c + src/fftools/ffmpeg_enc.c + src/fftools/ffmpeg_filter.c + src/fftools/ffmpeg_hw.c + src/fftools/ffmpeg_mux.c + src/fftools/ffmpeg_mux_init.c + src/fftools/ffmpeg_opt.c + src/fftools/ffmpeg_sched.c + src/fftools/objpool.c + src/fftools/opt_common.c + src/fftools/sync_queue.c + src/fftools/thread_queue.c + src/fftools/ffprobe.c ) emcc "${CONF_FLAGS[@]}" $@ diff --git a/build/ffmpeg.sh b/build/ffmpeg.sh index 2cef0c7b101..c81f25f49a2 100755 --- a/build/ffmpeg.sh +++ b/build/ffmpeg.sh @@ -2,6 +2,13 @@ set -euo pipefail +# Strip any shared libs from the dep prefix so FFmpeg (and the downstream core +# link, which inherits this stage) links everything statically. Some deps build +# a .so despite BUILD_SHARED_LIBS=OFF (e.g. zlib's CMake always emits libz.so), +# and emsdk 6.0.2's -l prefers the .so, turning it into a runtime dlopen +# (e.g. "404: libz.so") in a build that must be self-contained. +rm -f "$INSTALL_DIR"/lib/*.so "$INSTALL_DIR"/lib/*.so.* + CONF_FLAGS=( --target-os=none # disable target specific configs --arch=x86_32 # use x86_32 arch @@ -22,8 +29,15 @@ CONF_FLAGS=( --cxx=em++ --objcc=emcc --dep-cc=emcc - --extra-cflags="$CFLAGS" - --extra-cxxflags="$CXXFLAGS" + # -fPIC scoped to FFmpeg's build only. emsdk 6.0.2's wasm-ld rejects + # table-index relocations against function symbols in non-PIC objects + # (R_WASM_TABLE_INDEX_SLEB), which makes ./configure's lib-detection probes + # fail ("zlib requested but not found"). Keeping it here (not global) means + # the external libs stay static .a and the final core links statically — + # applying it globally made emscripten emit a MAIN_MODULE that tried to + # dlopen libz.so at runtime. + --extra-cflags="$CFLAGS -fPIC" + --extra-cxxflags="$CXXFLAGS -fPIC" # disable thread when FFMPEG_ST is NOT defined ${FFMPEG_ST:+ --disable-pthreads --disable-w32threads --disable-os2threads} diff --git a/build/fribidi.sh b/build/fribidi.sh index 27ed9994a8e..39754929bca 100755 --- a/build/fribidi.sh +++ b/build/fribidi.sh @@ -11,6 +11,9 @@ CONF_FLAGS=( --disable-debug ) emconfigure ./autogen.sh "${CONF_FLAGS[@]}" -# A hacky to fix "Too many symbolic links" error -emmake make install -j || true +# Install serially (no -j): fribidi's c2man man-page generation is broken under +# emsdk 6.0.2 and races the library install under -j, intermittently failing +# before libfribidi.a/headers land. Serial install does the lib subdir first, +# so the (ignored) doc failure can't clobber it. +emmake make install || true mkdir -p $INSTALL_DIR/lib/pkgconfig && cp fribidi.pc $INSTALL_DIR/lib/pkgconfig/ diff --git a/src/bind/ffmpeg/bind.js b/src/bind/ffmpeg/bind.js index 14eec22a047..1aa9f40f30c 100644 --- a/src/bind/ffmpeg/bind.js +++ b/src/bind/ffmpeg/bind.js @@ -55,7 +55,11 @@ function printErr(message) { function exec(..._args) { const args = [...Module["DEFAULT_ARGS"], ..._args]; try { - Module["_ffmpeg"](args.length, stringsToPtr(args)); + // FFmpeg 7.x removed exit_program(); main()/ffmpeg() now returns the exit + // code directly, so capture the return value instead of relying on a + // C-side EM_ASM setting Module.ret. The Aborted catch remains for the + // timeout path, which still abort()s. + Module["ret"] = Module["_ffmpeg"](args.length, stringsToPtr(args)); } catch (e) { if (!e.message.startsWith("Aborted")) { throw e; @@ -67,7 +71,7 @@ function exec(..._args) { function ffprobe(..._args) { const args = [...Module["DEFAULT_ARGS_FFPROBE"], ..._args]; try { - Module["_ffprobe"](args.length, stringsToPtr(args)); + Module["ret"] = Module["_ffprobe"](args.length, stringsToPtr(args)); } catch (e) { if (!e.message.startsWith("Aborted")) { throw e; diff --git a/src/fftools/cmdutils.c b/src/fftools/cmdutils.c index ff52890cc70..f1a41e049b8 100644 --- a/src/fftools/cmdutils.c +++ b/src/fftools/cmdutils.c @@ -24,7 +24,6 @@ #include #include #include -#include /* Include only the enabled headers since some compilers (namely, Sun Studio) will not omit unused inline functions and create undefined @@ -34,16 +33,14 @@ #include "compat/va_copy.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" -#include "libswscale/version.h" #include "libswresample/swresample.h" #include "libavutil/avassert.h" #include "libavutil/avstring.h" -#include "libavutil/channel_layout.h" +#include "libavutil/bprint.h" #include "libavutil/display.h" #include "libavutil/getenv_utf8.h" -#include "libavutil/mathematics.h" -#include "libavutil/imgutils.h" #include "libavutil/libm.h" +#include "libavutil/mem.h" #include "libavutil/parseutils.h" #include "libavutil/eval.h" #include "libavutil/dict.h" @@ -84,39 +81,8 @@ void init_dynload(void) #endif } -static void (*program_exit)(int ret); - -void register_exit(void (*cb)(int ret)) -{ - program_exit = cb; -} - -void exit_program(int ret) -{ - if (program_exit) - program_exit(ret); - - /* - * abort() is used instead of exit() because exit() not only - * terminates ffmpeg but also the whole node.js program, which - * is not ideal. - * - * abort() terminiates the ffmpeg with an JS exception - * - * RuntimeError: Aborted... - * - * This excpetion is catch and not visible to users. - * - */ - EM_ASM({ - Module.ret = $0; - }, ret); - abort(); - // exit(ret); -} - -double parse_number_or_die(const char *context, const char *numstr, int type, - double min, double max) +int parse_number(const char *context, const char *numstr, enum OptionType type, + double min, double max, double *dst) { char *tail; const char *error; @@ -125,31 +91,21 @@ double parse_number_or_die(const char *context, const char *numstr, int type, error = "Expected number for %s but found: %s\n"; else if (d < min || d > max) error = "The value for %s was %s which is not within %f - %f\n"; - else if (type == OPT_INT64 && (int64_t)d != d) + else if (type == OPT_TYPE_INT64 && (int64_t)d != d) error = "Expected int64 for %s but found %s\n"; - else if (type == OPT_INT && (int)d != d) + else if (type == OPT_TYPE_INT && (int)d != d) error = "Expected int for %s but found %s\n"; - else - return d; - av_log(NULL, AV_LOG_FATAL, error, context, numstr, min, max); - exit_program(1); - return 0; -} - -int64_t parse_time_or_die(const char *context, const char *timestr, - int is_duration) -{ - int64_t us; - if (av_parse_time(&us, timestr, is_duration) < 0) { - av_log(NULL, AV_LOG_FATAL, "Invalid %s specification for %s: %s\n", - is_duration ? "duration" : "date", context, timestr); - exit_program(1); + else { + *dst = d; + return 0; } - return us; + + av_log(NULL, AV_LOG_FATAL, error, context, numstr, min, max); + return AVERROR(EINVAL); } void show_help_options(const OptionDef *options, const char *msg, int req_flags, - int rej_flags, int alt_flags) + int rej_flags) { const OptionDef *po; int first; @@ -159,7 +115,6 @@ void show_help_options(const OptionDef *options, const char *msg, int req_flags, char buf[128]; if (((po->flags & req_flags) != req_flags) || - (alt_flags && !(po->flags & alt_flags)) || (po->flags & rej_flags)) continue; @@ -168,10 +123,15 @@ void show_help_options(const OptionDef *options, const char *msg, int req_flags, first = 0; } av_strlcpy(buf, po->name, sizeof(buf)); - if (po->argname) { - av_strlcat(buf, " ", sizeof(buf)); - av_strlcat(buf, po->argname, sizeof(buf)); - } + + if (po->flags & OPT_FLAG_PERSTREAM) + av_strlcat(buf, "[:]", sizeof(buf)); + else if (po->flags & OPT_FLAG_SPEC) + av_strlcat(buf, "[:]", sizeof(buf)); + + if (po->argname) + av_strlcatf(buf, sizeof(buf), " <%s>", po->argname); + printf("-%-17s %s\n", buf, po->help); } printf("\n"); @@ -192,6 +152,9 @@ void show_help_children(const AVClass *class, int flags) static const OptionDef *find_option(const OptionDef *po, const char *name) { + if (*name == '/') + name++; + while (po->name) { const char *end; if (av_strstart(name, po->name, &end) && (!*end || *end == ':')) @@ -265,59 +228,151 @@ static inline void prepare_app_arguments(int *argc_ptr, char ***argv_ptr) } #endif /* HAVE_COMMANDLINETOARGVW */ +static int opt_has_arg(const OptionDef *o) +{ + if (o->type == OPT_TYPE_BOOL) + return 0; + if (o->type == OPT_TYPE_FUNC) + return !!(o->flags & OPT_FUNC_ARG); + return 1; +} + static int write_option(void *optctx, const OptionDef *po, const char *opt, - const char *arg) + const char *arg, const OptionDef *defs) { /* new-style options contain an offset into optctx, old-style address of * a global var*/ - void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ? + void *dst = po->flags & OPT_FLAG_OFFSET ? (uint8_t *)optctx + po->u.off : po->u.dst_ptr; - int *dstcount; + char *arg_allocated = NULL; - if (po->flags & OPT_SPEC) { - SpecifierOpt **so = dst; + enum OptionType so_type = po->type; + + SpecifierOptList *sol = NULL; + double num; + int ret = 0; + + if (*opt == '/') { + opt++; + + if (!opt_has_arg(po)) { + av_log(NULL, AV_LOG_FATAL, + "Requested to load an argument from file for an option '%s'" + " which does not take an argument\n", + po->name); + return AVERROR(EINVAL); + } + + arg_allocated = file_read(arg); + if (!arg_allocated) { + av_log(NULL, AV_LOG_FATAL, + "Error reading the value for option '%s' from file: %s\n", + opt, arg); + return AVERROR(EINVAL); + } + + arg = arg_allocated; + } + + if (po->flags & OPT_FLAG_SPEC) { char *p = strchr(opt, ':'); char *str; - dstcount = (int *)(so + 1); - *so = grow_array(*so, sizeof(**so), dstcount, *dstcount + 1); + sol = dst; + ret = GROW_ARRAY(sol->opt, sol->nb_opt); + if (ret < 0) + goto finish; + str = av_strdup(p ? p + 1 : ""); - if (!str) - return AVERROR(ENOMEM); - (*so)[*dstcount - 1].specifier = str; - dst = &(*so)[*dstcount - 1].u; + if (!str) { + ret = AVERROR(ENOMEM); + goto finish; + } + sol->opt[sol->nb_opt - 1].specifier = str; + + if (po->flags & OPT_FLAG_PERSTREAM) { + ret = stream_specifier_parse(&sol->opt[sol->nb_opt - 1].stream_spec, + str, 0, NULL); + if (ret < 0) + goto finish; + } + + dst = &sol->opt[sol->nb_opt - 1].u; } - if (po->flags & OPT_STRING) { + if (po->type == OPT_TYPE_STRING) { char *str; - str = av_strdup(arg); + if (arg_allocated) { + str = arg_allocated; + arg_allocated = NULL; + } else + str = av_strdup(arg); av_freep(dst); - if (!str) - return AVERROR(ENOMEM); + + if (!str) { + ret = AVERROR(ENOMEM); + goto finish; + } + *(char **)dst = str; - } else if (po->flags & OPT_BOOL || po->flags & OPT_INT) { - *(int *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT_MIN, INT_MAX); - } else if (po->flags & OPT_INT64) { - *(int64_t *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX); - } else if (po->flags & OPT_TIME) { - *(int64_t *)dst = parse_time_or_die(opt, arg, 1); - } else if (po->flags & OPT_FLOAT) { - *(float *)dst = parse_number_or_die(opt, arg, OPT_FLOAT, -INFINITY, INFINITY); - } else if (po->flags & OPT_DOUBLE) { - *(double *)dst = parse_number_or_die(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY); - } else if (po->u.func_arg) { - int ret = po->u.func_arg(optctx, opt, arg); + } else if (po->type == OPT_TYPE_BOOL || po->type == OPT_TYPE_INT) { + ret = parse_number(opt, arg, OPT_TYPE_INT64, INT_MIN, INT_MAX, &num); + if (ret < 0) + goto finish; + + *(int *)dst = num; + so_type = OPT_TYPE_INT; + } else if (po->type == OPT_TYPE_INT64) { + ret = parse_number(opt, arg, OPT_TYPE_INT64, INT64_MIN, (double)INT64_MAX, &num); + if (ret < 0) + goto finish; + + *(int64_t *)dst = num; + } else if (po->type == OPT_TYPE_TIME) { + ret = av_parse_time(dst, arg, 1); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Invalid duration for option %s: %s\n", + opt, arg); + goto finish; + } + so_type = OPT_TYPE_INT64; + } else if (po->type == OPT_TYPE_FLOAT) { + ret = parse_number(opt, arg, OPT_TYPE_FLOAT, -INFINITY, INFINITY, &num); + if (ret < 0) + goto finish; + + *(float *)dst = num; + } else if (po->type == OPT_TYPE_DOUBLE) { + ret = parse_number(opt, arg, OPT_TYPE_DOUBLE, -INFINITY, INFINITY, &num); + if (ret < 0) + goto finish; + + *(double *)dst = num; + } else { + av_assert0(po->type == OPT_TYPE_FUNC && po->u.func_arg); + + ret = po->u.func_arg(optctx, opt, arg); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to set value '%s' for option '%s': %s\n", arg, opt, av_err2str(ret)); - return ret; + goto finish; } } - if (po->flags & OPT_EXIT) - exit_program(0); + if (po->flags & OPT_EXIT) { + ret = AVERROR_EXIT; + goto finish; + } - return 0; + if (sol) { + sol->type = so_type; + sol->opt_canon = (po->flags & OPT_HAS_CANON) ? + find_option(defs, po->u1.name_canon) : po; + } + +finish: + av_freep(&arg_allocated); + return ret; } int parse_option(void *optctx, const char *opt, const char *arg, @@ -325,7 +380,8 @@ int parse_option(void *optctx, const char *opt, const char *arg, { static const OptionDef opt_avoptions = { .name = "AVOption passthrough", - .flags = HAS_ARG, + .type = OPT_TYPE_FUNC, + .flags = OPT_FUNC_ARG, .u.func_arg = opt_default, }; @@ -336,9 +392,9 @@ int parse_option(void *optctx, const char *opt, const char *arg, if (!po->name && opt[0] == 'n' && opt[1] == 'o') { /* handle 'no' bool option */ po = find_option(options, opt + 2); - if ((po->name && (po->flags & OPT_BOOL))) + if ((po->name && po->type == OPT_TYPE_BOOL)) arg = "0"; - } else if (po->flags & OPT_BOOL) + } else if (po->type == OPT_TYPE_BOOL) arg = "1"; if (!po->name) @@ -347,20 +403,20 @@ int parse_option(void *optctx, const char *opt, const char *arg, av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'\n", opt); return AVERROR(EINVAL); } - if (po->flags & HAS_ARG && !arg) { + if (opt_has_arg(po) && !arg) { av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'\n", opt); return AVERROR(EINVAL); } - ret = write_option(optctx, po, opt, arg); + ret = write_option(optctx, po, opt, arg, options); if (ret < 0) return ret; - return !!(po->flags & HAS_ARG); + return opt_has_arg(po); } -void parse_options(void *optctx, int argc, char **argv, const OptionDef *options, - void (*parse_arg_function)(void *, const char*)) +int parse_options(void *optctx, int argc, char **argv, const OptionDef *options, + int (*parse_arg_function)(void *, const char*)) { const char *opt; int optindex, handleoptions = 1, ret; @@ -381,16 +437,21 @@ void parse_options(void *optctx, int argc, char **argv, const OptionDef *options opt++; if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0) - exit_program(1); + return ret; optindex += ret; } else { - if (parse_arg_function) - parse_arg_function(optctx, opt); + if (parse_arg_function) { + ret = parse_arg_function(optctx, opt); + if (ret < 0) + return ret; + } } } + + return 0; } -int parse_optgroup(void *optctx, OptionGroup *g) +int parse_optgroup(void *optctx, OptionGroup *g, const OptionDef *defs) { int i, ret; @@ -413,7 +474,7 @@ int parse_optgroup(void *optctx, OptionGroup *g) av_log(NULL, AV_LOG_DEBUG, "Applying option %s (%s) with argument %s.\n", o->key, o->opt->help, o->val); - ret = write_option(optctx, o->opt, o->key, o->val); + ret = write_option(optctx, o->opt, o->key, o->val, defs); if (ret < 0) return ret; } @@ -443,7 +504,7 @@ int locate_option(int argc, char **argv, const OptionDef *options, (po->name && !strcmp(optname, po->name))) return i; - if (!po->name || po->flags & HAS_ARG) + if (!po->name || opt_has_arg(po)) i++; } return 0; @@ -477,7 +538,14 @@ static void check_options(const OptionDef *po) { while (po->name) { if (po->flags & OPT_PERFILE) - av_assert0(po->flags & (OPT_INPUT | OPT_OUTPUT)); + av_assert0(po->flags & (OPT_INPUT | OPT_OUTPUT | OPT_DECODER)); + + if (po->type == OPT_TYPE_FUNC) + av_assert0(!(po->flags & (OPT_FLAG_OFFSET | OPT_FLAG_SPEC))); + + // OPT_FUNC_ARG can only be ser for OPT_TYPE_FUNC + av_assert0((po->type == OPT_TYPE_FUNC) || !(po->flags & OPT_FUNC_ARG)); + po++; } } @@ -523,7 +591,7 @@ static const AVOption *opt_find(void *obj, const char *name, const char *unit, return o; } -#define FLAGS (o->type == AV_OPT_TYPE_FLAGS && (arg[0]=='-' || arg[0]=='+')) ? AV_DICT_APPEND : 0 +#define FLAGS ((o->type == AV_OPT_TYPE_FLAGS && (arg[0]=='-' || arg[0]=='+')) ? AV_DICT_APPEND : 0) int opt_default(void *optctx, const char *opt, const char *arg) { const AVOption *o; @@ -616,13 +684,17 @@ static int match_group_separator(const OptionGroupDef *groups, int nb_groups, * @param group_idx which group definition should this group belong to * @param arg argument of the group delimiting option */ -static void finish_group(OptionParseContext *octx, int group_idx, - const char *arg) +static int finish_group(OptionParseContext *octx, int group_idx, + const char *arg) { OptionGroupList *l = &octx->groups[group_idx]; OptionGroup *g; + int ret; + + ret = GROW_ARRAY(l->groups, l->nb_groups); + if (ret < 0) + return ret; - GROW_ARRAY(l->groups, l->nb_groups); g = &l->groups[l->nb_groups - 1]; *g = octx->cur_group; @@ -639,41 +711,51 @@ static void finish_group(OptionParseContext *octx, int group_idx, swr_opts = NULL; memset(&octx->cur_group, 0, sizeof(octx->cur_group)); + + return ret; } /* * Add an option instance to currently parsed group. */ -static void add_opt(OptionParseContext *octx, const OptionDef *opt, - const char *key, const char *val) +static int add_opt(OptionParseContext *octx, const OptionDef *opt, + const char *key, const char *val) { - int global = !(opt->flags & (OPT_PERFILE | OPT_SPEC | OPT_OFFSET)); + int global = !(opt->flags & OPT_PERFILE); OptionGroup *g = global ? &octx->global_opts : &octx->cur_group; + int ret; + + ret = GROW_ARRAY(g->opts, g->nb_opts); + if (ret < 0) + return ret; - GROW_ARRAY(g->opts, g->nb_opts); g->opts[g->nb_opts - 1].opt = opt; g->opts[g->nb_opts - 1].key = key; g->opts[g->nb_opts - 1].val = val; + + return 0; } -static void init_parse_context(OptionParseContext *octx, - const OptionGroupDef *groups, int nb_groups) +static int init_parse_context(OptionParseContext *octx, + const OptionGroupDef *groups, int nb_groups) { static const OptionGroupDef global_group = { "global" }; int i; memset(octx, 0, sizeof(*octx)); - octx->nb_groups = nb_groups; - octx->groups = av_calloc(octx->nb_groups, sizeof(*octx->groups)); + octx->groups = av_calloc(nb_groups, sizeof(*octx->groups)); if (!octx->groups) - exit_program(1); + return AVERROR(ENOMEM); + octx->nb_groups = nb_groups; for (i = 0; i < octx->nb_groups; i++) octx->groups[i].group_def = &groups[i]; octx->global_opts.group_def = &global_group; octx->global_opts.arg = ""; + + return 0; } void uninit_parse_context(OptionParseContext *octx) @@ -705,19 +787,23 @@ int split_commandline(OptionParseContext *octx, int argc, char *argv[], const OptionDef *options, const OptionGroupDef *groups, int nb_groups) { + int ret; int optindex = 1; int dashdash = -2; /* perform system-dependent conversions for arguments list */ prepare_app_arguments(&argc, &argv); - init_parse_context(octx, groups, nb_groups); + ret = init_parse_context(octx, groups, nb_groups); + if (ret < 0) + return ret; + av_log(NULL, AV_LOG_DEBUG, "Splitting the commandline.\n"); while (optindex < argc) { const char *opt = argv[optindex++], *arg; const OptionDef *po; - int ret; + int group_idx; av_log(NULL, AV_LOG_DEBUG, "Reading option '%s' ...", opt); @@ -727,7 +813,10 @@ int split_commandline(OptionParseContext *octx, int argc, char *argv[], } /* unnamed group separators, e.g. output filename */ if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) { - finish_group(octx, 0, opt); + ret = finish_group(octx, 0, opt); + if (ret < 0) + return ret; + av_log(NULL, AV_LOG_DEBUG, " matched as %s.\n", groups[0].name); continue; } @@ -743,11 +832,15 @@ do { \ } while (0) /* named group separators, e.g. -i */ - if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) { + group_idx = match_group_separator(groups, nb_groups, opt); + if (group_idx >= 0) { GET_ARG(arg); - finish_group(octx, ret, arg); + ret = finish_group(octx, group_idx, arg); + if (ret < 0) + return ret; + av_log(NULL, AV_LOG_DEBUG, " matched as %s with argument '%s'.\n", - groups[ret].name, arg); + groups[group_idx].name, arg); continue; } @@ -757,13 +850,16 @@ do { \ if (po->flags & OPT_EXIT) { /* optional argument, e.g. -h */ arg = argv[optindex++]; - } else if (po->flags & HAS_ARG) { + } else if (opt_has_arg(po)) { GET_ARG(arg); } else { arg = "1"; } - add_opt(octx, po, opt, arg); + ret = add_opt(octx, po, opt, arg); + if (ret < 0) + return ret; + av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " "argument '%s'.\n", po->name, po->help, arg); continue; @@ -787,8 +883,11 @@ do { \ /* boolean -nofoo options */ if (opt[0] == 'n' && opt[1] == 'o' && (po = find_option(options, opt + 2)) && - po->name && po->flags & OPT_BOOL) { - add_opt(octx, po, opt, "0"); + po->name && po->type == OPT_TYPE_BOOL) { + ret = add_opt(octx, po, opt, "0"); + if (ret < 0) + return ret; + av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " "argument 0.\n", po->name, po->help); continue; @@ -807,16 +906,6 @@ do { \ return 0; } -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); -} - int read_yesno(void) { int c = getchar(); @@ -900,16 +989,366 @@ FILE *get_preset_file(char *filename, size_t filename_size, return f; } +int cmdutils_isalnum(char c) +{ + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'); +} + +void stream_specifier_uninit(StreamSpecifier *ss) +{ + av_freep(&ss->meta_key); + av_freep(&ss->meta_val); + av_freep(&ss->remainder); + + memset(ss, 0, sizeof(*ss)); +} + +int stream_specifier_parse(StreamSpecifier *ss, const char *spec, + int allow_remainder, void *logctx) +{ + char *endptr; + int ret; + + memset(ss, 0, sizeof(*ss)); + + ss->idx = -1; + ss->media_type = AVMEDIA_TYPE_UNKNOWN; + ss->stream_list = STREAM_LIST_ALL; + + av_log(logctx, AV_LOG_TRACE, "Parsing stream specifier: %s\n", spec); + + while (*spec) { + if (*spec <= '9' && *spec >= '0') { /* opt:index */ + ss->idx = strtol(spec, &endptr, 0); + + av_assert0(endptr > spec); + spec = endptr; + + av_log(logctx, AV_LOG_TRACE, + "Parsed index: %d; remainder: %s\n", ss->idx, spec); + + // this terminates the specifier + break; + } else if ((*spec == 'v' || *spec == 'a' || *spec == 's' || + *spec == 'd' || *spec == 't' || *spec == 'V') && + !cmdutils_isalnum(*(spec + 1))) { /* opt:[vasdtV] */ + if (ss->media_type != AVMEDIA_TYPE_UNKNOWN) { + av_log(logctx, AV_LOG_ERROR, "Stream type specified multiple times\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + switch (*spec++) { + case 'v': ss->media_type = AVMEDIA_TYPE_VIDEO; break; + case 'a': ss->media_type = AVMEDIA_TYPE_AUDIO; break; + case 's': ss->media_type = AVMEDIA_TYPE_SUBTITLE; break; + case 'd': ss->media_type = AVMEDIA_TYPE_DATA; break; + case 't': ss->media_type = AVMEDIA_TYPE_ATTACHMENT; break; + case 'V': ss->media_type = AVMEDIA_TYPE_VIDEO; + ss->no_apic = 1; break; + default: av_assert0(0); + } + + av_log(logctx, AV_LOG_TRACE, "Parsed media type: %s; remainder: %s\n", + av_get_media_type_string(ss->media_type), spec); + } else if (*spec == 'g' && *(spec + 1) == ':') { + if (ss->stream_list != STREAM_LIST_ALL) + goto multiple_stream_lists; + + spec += 2; + if (*spec == '#' || (*spec == 'i' && *(spec + 1) == ':')) { + ss->stream_list = STREAM_LIST_GROUP_ID; + + spec += 1 + (*spec == 'i'); + } else + ss->stream_list = STREAM_LIST_GROUP_IDX; + + ss->list_id = strtol(spec, &endptr, 0); + if (spec == endptr) { + av_log(logctx, AV_LOG_ERROR, "Expected stream group idx/ID, got: %s\n", spec); + ret = AVERROR(EINVAL); + goto fail; + } + spec = endptr; + + av_log(logctx, AV_LOG_TRACE, "Parsed stream group %s: %"PRId64"; remainder: %s\n", + ss->stream_list == STREAM_LIST_GROUP_ID ? "ID" : "index", ss->list_id, spec); + } else if (*spec == 'p' && *(spec + 1) == ':') { + if (ss->stream_list != STREAM_LIST_ALL) + goto multiple_stream_lists; + + ss->stream_list = STREAM_LIST_PROGRAM; + + spec += 2; + ss->list_id = strtol(spec, &endptr, 0); + if (spec == endptr) { + av_log(logctx, AV_LOG_ERROR, "Expected program ID, got: %s\n", spec); + ret = AVERROR(EINVAL); + goto fail; + } + spec = endptr; + + av_log(logctx, AV_LOG_TRACE, + "Parsed program ID: %"PRId64"; remainder: %s\n", ss->list_id, spec); + } else if (!strncmp(spec, "disp:", 5)) { + const AVClass *st_class = av_stream_get_class(); + const AVOption *o = av_opt_find(&st_class, "disposition", NULL, 0, AV_OPT_SEARCH_FAKE_OBJ); + char *disp = NULL; + size_t len; + + av_assert0(o); + + if (ss->disposition) { + av_log(logctx, AV_LOG_ERROR, "Multiple disposition specifiers\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + spec += 5; + + for (len = 0; cmdutils_isalnum(spec[len]) || + spec[len] == '_' || spec[len] == '+'; len++) + continue; + + disp = av_strndup(spec, len); + if (!disp) { + ret = AVERROR(ENOMEM); + goto fail; + } + + ret = av_opt_eval_flags(&st_class, o, disp, &ss->disposition); + av_freep(&disp); + if (ret < 0) { + av_log(logctx, AV_LOG_ERROR, "Invalid disposition specifier\n"); + goto fail; + } + + spec += len; + + av_log(logctx, AV_LOG_TRACE, + "Parsed disposition: 0x%x; remainder: %s\n", ss->disposition, spec); + } else if (*spec == '#' || + (*spec == 'i' && *(spec + 1) == ':')) { + if (ss->stream_list != STREAM_LIST_ALL) + goto multiple_stream_lists; + + ss->stream_list = STREAM_LIST_STREAM_ID; + + spec += 1 + (*spec == 'i'); + ss->list_id = strtol(spec, &endptr, 0); + if (spec == endptr) { + av_log(logctx, AV_LOG_ERROR, "Expected stream ID, got: %s\n", spec); + ret = AVERROR(EINVAL); + goto fail; + } + spec = endptr; + + av_log(logctx, AV_LOG_TRACE, + "Parsed stream ID: %"PRId64"; remainder: %s\n", ss->list_id, spec); + + // this terminates the specifier + break; + } else if (*spec == 'm' && *(spec + 1) == ':') { + av_assert0(!ss->meta_key && !ss->meta_val); + + spec += 2; + ss->meta_key = av_get_token(&spec, ":"); + if (!ss->meta_key) { + ret = AVERROR(ENOMEM); + goto fail; + } + if (*spec == ':') { + spec++; + ss->meta_val = av_get_token(&spec, ":"); + if (!ss->meta_val) { + ret = AVERROR(ENOMEM); + goto fail; + } + } + + av_log(logctx, AV_LOG_TRACE, + "Parsed metadata: %s:%s; remainder: %s", ss->meta_key, + ss->meta_val ? ss->meta_val : "", spec); + + // this terminates the specifier + break; + } else if (*spec == 'u' && (*(spec + 1) == '\0' || *(spec + 1) == ':')) { + ss->usable_only = 1; + spec++; + av_log(logctx, AV_LOG_ERROR, "Parsed 'usable only'\n"); + + // this terminates the specifier + break; + } else + break; + + if (*spec == ':') + spec++; + } + + if (*spec) { + if (!allow_remainder) { + av_log(logctx, AV_LOG_ERROR, + "Trailing garbage at the end of a stream specifier: %s\n", + spec); + ret = AVERROR(EINVAL); + goto fail; + } + + if (*spec == ':') + spec++; + + ss->remainder = av_strdup(spec); + if (!ss->remainder) { + ret = AVERROR(EINVAL); + goto fail; + } + } + + return 0; + +multiple_stream_lists: + av_log(logctx, AV_LOG_ERROR, + "Cannot combine multiple program/group designators in a " + "single stream specifier"); + ret = AVERROR(EINVAL); + +fail: + stream_specifier_uninit(ss); + return ret; +} + +unsigned stream_specifier_match(const StreamSpecifier *ss, + const AVFormatContext *s, const AVStream *st, + void *logctx) +{ + const AVStreamGroup *g = NULL; + const AVProgram *p = NULL; + int start_stream = 0, nb_streams; + int nb_matched = 0; + + switch (ss->stream_list) { + case STREAM_LIST_STREAM_ID: + // stream with given ID makes no sense and should be impossible to request + av_assert0(ss->idx < 0); + // return early if we know for sure the stream does not match + if (st->id != ss->list_id) + return 0; + start_stream = st->index; + nb_streams = st->index + 1; + break; + case STREAM_LIST_ALL: + start_stream = ss->idx >= 0 ? 0 : st->index; + nb_streams = st->index + 1; + break; + case STREAM_LIST_PROGRAM: + for (unsigned i = 0; i < s->nb_programs; i++) { + if (s->programs[i]->id == ss->list_id) { + p = s->programs[i]; + break; + } + } + if (!p) { + av_log(logctx, AV_LOG_WARNING, "No program with ID %"PRId64" exists," + " stream specifier can never match\n", ss->list_id); + return 0; + } + nb_streams = p->nb_stream_indexes; + break; + case STREAM_LIST_GROUP_ID: + for (unsigned i = 0; i < s->nb_stream_groups; i++) { + if (ss->list_id == s->stream_groups[i]->id) { + g = s->stream_groups[i]; + break; + } + } + // fall-through + case STREAM_LIST_GROUP_IDX: + if (ss->stream_list == STREAM_LIST_GROUP_IDX && + ss->list_id >= 0 && ss->list_id < s->nb_stream_groups) + g = s->stream_groups[ss->list_id]; + + if (!g) { + av_log(logctx, AV_LOG_WARNING, "No stream group with group %s %" + PRId64" exists, stream specifier can never match\n", + ss->stream_list == STREAM_LIST_GROUP_ID ? "ID" : "index", + ss->list_id); + return 0; + } + nb_streams = g->nb_streams; + break; + default: av_assert0(0); + } + + for (int i = start_stream; i < nb_streams; i++) { + const AVStream *candidate = s->streams[g ? g->streams[i]->index : + p ? p->stream_index[i] : i]; + + if (ss->media_type != AVMEDIA_TYPE_UNKNOWN && + (ss->media_type != candidate->codecpar->codec_type || + (ss->no_apic && (candidate->disposition & AV_DISPOSITION_ATTACHED_PIC)))) + continue; + + if (ss->meta_key) { + const AVDictionaryEntry *tag = av_dict_get(candidate->metadata, + ss->meta_key, NULL, 0); + + if (!tag) + continue; + if (ss->meta_val && strcmp(tag->value, ss->meta_val)) + continue; + } + + if (ss->usable_only) { + const AVCodecParameters *par = candidate->codecpar; + + switch (par->codec_type) { + case AVMEDIA_TYPE_AUDIO: + if (!par->sample_rate || !par->ch_layout.nb_channels || + par->format == AV_SAMPLE_FMT_NONE) + continue; + break; + case AVMEDIA_TYPE_VIDEO: + if (!par->width || !par->height || par->format == AV_PIX_FMT_NONE) + continue; + break; + case AVMEDIA_TYPE_UNKNOWN: + continue; + } + } + + if (ss->disposition && + (candidate->disposition & ss->disposition) != ss->disposition) + continue; + + if (st == candidate) + return ss->idx < 0 || ss->idx == nb_matched; + + nb_matched++; + } + + return 0; +} + int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec) { - int ret = avformat_match_stream_specifier(s, st, spec); + StreamSpecifier ss; + int ret; + + ret = stream_specifier_parse(&ss, spec, 0, NULL); if (ret < 0) - av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec); + return ret; + + ret = stream_specifier_match(&ss, s, st, NULL); + stream_specifier_uninit(&ss); return ret; } -AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, - AVFormatContext *s, AVStream *st, const AVCodec *codec) +int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id, + AVFormatContext *s, AVStream *st, const AVCodec *codec, + AVDictionary **dst, AVDictionary **opts_used) { AVDictionary *ret = NULL; const AVDictionaryEntry *t = NULL; @@ -918,10 +1357,6 @@ AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, char prefix = 0; const AVClass *cc = avcodec_get_class(); - if (!codec) - codec = s->oformat ? avcodec_find_encoder(codec_id) - : avcodec_find_decoder(codec_id); - switch (st->codecpar->codec_type) { case AVMEDIA_TYPE_VIDEO: prefix = 'v'; @@ -937,72 +1372,95 @@ 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, ':'); + int used = 0; /* check stream specification in opt name */ - if (p) - switch (check_stream_specifier(s, st, p + 1)) { - case 1: *p = 0; break; - case 0: continue; - default: exit_program(1); - } + if (p) { + int err = check_stream_specifier(s, st, p + 1); + if (err < 0) { + av_dict_free(&ret); + return err; + } else if (!err) + continue; + + *p = 0; + } if (av_opt_find(&cc, t->key, NULL, flags, AV_OPT_SEARCH_FAKE_OBJ) || !codec || ((priv_class = codec->priv_class) && av_opt_find(&priv_class, t->key, NULL, flags, - AV_OPT_SEARCH_FAKE_OBJ))) + AV_OPT_SEARCH_FAKE_OBJ))) { av_dict_set(&ret, t->key, t->value, 0); - else if (t->key[0] == prefix && + used = 1; + } else if (t->key[0] == prefix && av_opt_find(&cc, t->key + 1, NULL, flags, - AV_OPT_SEARCH_FAKE_OBJ)) + AV_OPT_SEARCH_FAKE_OBJ)) { av_dict_set(&ret, t->key + 1, t->value, 0); + used = 1; + } if (p) *p = ':'; + + if (used && opts_used) + av_dict_set(opts_used, t->key, "", 0); } - return ret; + + *dst = ret; + return 0; } -AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, - AVDictionary *codec_opts) +int setup_find_stream_info_opts(AVFormatContext *s, + AVDictionary *local_codec_opts, + AVDictionary ***dst) { - int i; + int ret; AVDictionary **opts; + *dst = NULL; + if (!s->nb_streams) - return NULL; + return 0; + 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) + return AVERROR(ENOMEM); + + for (int i = 0; i < s->nb_streams; i++) { + ret = filter_codec_opts(local_codec_opts, s->streams[i]->codecpar->codec_id, + s, s->streams[i], NULL, &opts[i], NULL); + if (ret < 0) + goto fail; } - 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); - return opts; + *dst = opts; + return 0; +fail: + for (int i = 0; i < s->nb_streams; i++) + av_dict_free(&opts[i]); + av_freep(&opts); + return ret; } -void *grow_array(void *array, int elem_size, int *size, int new_size) +int grow_array(void **array, int elem_size, int *size, int new_size) { if (new_size >= INT_MAX / elem_size) { av_log(NULL, AV_LOG_ERROR, "Array too big.\n"); - exit_program(1); + return AVERROR(ERANGE); } 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); - } + uint8_t *tmp = av_realloc_array(*array, new_size, elem_size); + if (!tmp) + return AVERROR(ENOMEM); memset(tmp + *size*elem_size, 0, (new_size-*size) * elem_size); *size = new_size; - return tmp; + *array = tmp; + return 0; } - return array; + return 0; } void *allocate_array_elem(void *ptr, size_t elem_size, int *nb_elems) @@ -1010,18 +1468,16 @@ 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) + return NULL; return new_elem; } -double get_rotation(int32_t *displaymatrix) +double get_rotation(const int32_t *displaymatrix) { double theta = 0; if (displaymatrix) - theta = -round(av_display_rotation_get((int32_t*) displaymatrix)); + theta = -round(av_display_rotation_get(displaymatrix)); theta -= 360*floor(theta/360 + 0.9/360); @@ -1033,3 +1489,49 @@ double get_rotation(int32_t *displaymatrix) return theta; } + +/* read file contents into a string */ +char *file_read(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; +} + +void remove_avoptions(AVDictionary **a, AVDictionary *b) +{ + const AVDictionaryEntry *t = NULL; + + while ((t = av_dict_iterate(b, t))) { + av_dict_set(a, t->key, NULL, AV_DICT_MATCH_CASE); + } +} + +int check_avoptions(AVDictionary *m) +{ + const AVDictionaryEntry *t = av_dict_iterate(m, NULL); + if (t) { + av_log(NULL, AV_LOG_FATAL, "Option %s not found.\n", t->key); + return AVERROR_OPTION_NOT_FOUND; + } + + return 0; +} diff --git a/src/fftools/cmdutils.h b/src/fftools/cmdutils.h index d87e162ccd6..316b6a8c64a 100644 --- a/src/fftools/cmdutils.h +++ b/src/fftools/cmdutils.h @@ -49,16 +49,6 @@ extern AVDictionary *swr_opts; extern AVDictionary *format_opts, *codec_opts; extern int hide_banner; -/** - * Register a program-specific cleanup routine. - */ -void register_exit(void (*cb)(int ret)); - -/** - * Wraps exit with a program-specific cleanup routine. - */ -void exit_program(int ret) av_noreturn; - /** * Initialize dynamic library loading */ @@ -87,10 +77,19 @@ int opt_default(void *optctx, const char *opt, const char *arg); */ int opt_timelimit(void *optctx, const char *opt, const char *arg); +enum OptionType { + OPT_TYPE_FUNC, + OPT_TYPE_BOOL, + OPT_TYPE_STRING, + OPT_TYPE_INT, + OPT_TYPE_INT64, + OPT_TYPE_FLOAT, + OPT_TYPE_DOUBLE, + OPT_TYPE_TIME, +}; + /** * Parse a string and return its corresponding value as a double. - * Exit from the application if the string cannot be correctly - * parsed or the corresponding value is invalid. * * @param context the context of the value to be set (e.g. the * corresponding command line option name) @@ -100,28 +99,73 @@ int opt_timelimit(void *optctx, const char *opt, const char *arg); * @param min the minimum valid accepted value * @param max the maximum valid accepted value */ -double parse_number_or_die(const char *context, const char *numstr, int type, - double min, double max); +int parse_number(const char *context, const char *numstr, enum OptionType type, + double min, double max, double *dst); + +enum StreamList { + STREAM_LIST_ALL, + STREAM_LIST_STREAM_ID, + STREAM_LIST_PROGRAM, + STREAM_LIST_GROUP_ID, + STREAM_LIST_GROUP_IDX, +}; + +typedef struct StreamSpecifier { + // trailing stream index - pick idx-th stream that matches + // all the other constraints; -1 when not present + int idx; + + // which stream list to consider + enum StreamList stream_list; + + // STREAM_LIST_STREAM_ID: stream ID + // STREAM_LIST_GROUP_IDX: group index + // STREAM_LIST_GROUP_ID: group ID + // STREAM_LIST_PROGRAM: program ID + int64_t list_id; + + // when not AVMEDIA_TYPE_UNKNOWN, consider only streams of this type + enum AVMediaType media_type; + uint8_t no_apic; + + uint8_t usable_only; + + int disposition; + + char *meta_key; + char *meta_val; + + char *remainder; +} StreamSpecifier; /** - * Parse a string specifying a time and return its corresponding - * value as a number of microseconds. Exit from the application if - * the string cannot be correctly parsed. + * Parse a stream specifier string into a form suitable for matching. * - * @param context the context of the value to be set (e.g. the - * corresponding command line option name) - * @param timestr the string to be parsed - * @param is_duration a flag which tells how to interpret timestr, if - * not zero timestr is interpreted as a duration, otherwise as a - * date - * - * @see av_parse_time() + * @param ss Parsed specifier will be stored here; must be uninitialized + * with stream_specifier_uninit() when no longer needed. + * @param spec String containing the stream specifier to be parsed. + * @param allow_remainder When 1, the part of spec that is left after parsing + * the stream specifier is stored into ss->remainder. + * When 0, any remainder will cause parsing to fail. + */ +int stream_specifier_parse(StreamSpecifier *ss, const char *spec, + int allow_remainder, void *logctx); + +/** + * @return 1 if st matches the parsed specifier, 0 if it does not */ -int64_t parse_time_or_die(const char *context, const char *timestr, - int is_duration); +unsigned stream_specifier_match(const StreamSpecifier *ss, + const AVFormatContext *s, const AVStream *st, + void *logctx); + +void stream_specifier_uninit(StreamSpecifier *ss); typedef struct SpecifierOpt { - char *specifier; /**< stream/chapter/program/... specifier */ + // original specifier or empty string + char *specifier; + // parsed specifier for OPT_FLAG_PERSTREAM options + StreamSpecifier stream_spec; + union { uint8_t *str; int i; @@ -132,31 +176,73 @@ typedef struct SpecifierOpt { } u; } SpecifierOpt; +typedef struct SpecifierOptList { + SpecifierOpt *opt; + int nb_opt; + + /* Canonical option definition that was parsed into this list. */ + const struct OptionDef *opt_canon; + /* Type corresponding to the field that should be used from SpecifierOpt.u. + * May not match the option type, e.g. OPT_TYPE_BOOL options are stored as + * int, so this field would be OPT_TYPE_INT for them */ + enum OptionType type; +} SpecifierOptList; + typedef struct OptionDef { const char *name; + enum OptionType type; int flags; -#define HAS_ARG 0x0001 -#define OPT_BOOL 0x0002 -#define OPT_EXPERT 0x0004 -#define OPT_STRING 0x0008 -#define OPT_VIDEO 0x0010 -#define OPT_AUDIO 0x0020 -#define OPT_INT 0x0080 -#define OPT_FLOAT 0x0100 -#define OPT_SUBTITLE 0x0200 -#define OPT_INT64 0x0400 -#define OPT_EXIT 0x0800 -#define OPT_DATA 0x1000 -#define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only). - implied by OPT_OFFSET or OPT_SPEC */ -#define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */ -#define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt. - Implies OPT_OFFSET. Next element after the offset is - an int containing element count in the array. */ -#define OPT_TIME 0x10000 -#define OPT_DOUBLE 0x20000 -#define OPT_INPUT 0x40000 -#define OPT_OUTPUT 0x80000 + +/* The OPT_TYPE_FUNC option takes an argument. + * Must not be used with other option types, as for those it holds: + * - OPT_TYPE_BOOL do not take an argument + * - all other types do + */ +#define OPT_FUNC_ARG (1 << 0) +/* Program will immediately exit after processing this option */ +#define OPT_EXIT (1 << 1) +/* Option is intended for advanced users. Only affects + * help output. + */ +#define OPT_EXPERT (1 << 2) +#define OPT_VIDEO (1 << 3) +#define OPT_AUDIO (1 << 4) +#define OPT_SUBTITLE (1 << 5) +#define OPT_DATA (1 << 6) +/* The option is per-file (currently ffmpeg-only). At least one of OPT_INPUT, + * OPT_OUTPUT, OPT_DECODER must be set when this flag is in use. + */ +#define OPT_PERFILE (1 << 7) + +/* Option is specified as an offset in a passed optctx. + * Always use as OPT_OFFSET in option definitions. */ +#define OPT_FLAG_OFFSET (1 << 8) +#define OPT_OFFSET (OPT_FLAG_OFFSET | OPT_PERFILE) + +/* Option is to be stored in a SpecifierOptList. + Always use as OPT_SPEC in option definitions. */ +#define OPT_FLAG_SPEC (1 << 9) +#define OPT_SPEC (OPT_FLAG_SPEC | OPT_OFFSET) + +/* Option applies per-stream (implies OPT_SPEC). */ +#define OPT_FLAG_PERSTREAM (1 << 10) +#define OPT_PERSTREAM (OPT_FLAG_PERSTREAM | OPT_SPEC) + +/* ffmpeg-only - specifies whether an OPT_PERFILE option applies to input, + * output, or both. */ +#define OPT_INPUT (1 << 11) +#define OPT_OUTPUT (1 << 12) + +/* This option is a "canonical" form, to which one or more alternatives + * exist. These alternatives are listed in u1.names_alt. */ +#define OPT_HAS_ALT (1 << 13) +/* This option is an alternative form of some other option, whose + * name is stored in u1.name_canon */ +#define OPT_HAS_CANON (1 << 14) + +/* ffmpeg-only - OPT_PERFILE may apply to standalone decoders */ +#define OPT_DECODER (1 << 15) + union { void *dst_ptr; int (*func_arg)(void *, const char *, const char *); @@ -164,6 +250,15 @@ typedef struct OptionDef { } u; const char *help; const char *argname; + + union { + /* Name of the canonical form of this option. + * Is valid when OPT_HAS_CANON is set. */ + const char *name_canon; + /* A NULL-terminated list of alternate forms of this option. + * Is valid when OPT_HAS_ALT is set. */ + const char * const *names_alt; + } u1; } OptionDef; /** @@ -173,10 +268,9 @@ typedef struct OptionDef { * @param msg title of this group. Only printed if at least one option matches. * @param req_flags print only options which have all those flags set. * @param rej_flags don't print options which have any of those flags set. - * @param alt_flags print only options that have at least one of those flags set */ void show_help_options(const OptionDef *options, const char *msg, int req_flags, - int rej_flags, int alt_flags); + int rej_flags); /** * Show help for all options with given flags in class and all its @@ -202,8 +296,8 @@ void show_help_default(const char *opt, const char *arg); * argument without a leading option name flag. NULL if such arguments do * not have to be processed. */ -void parse_options(void *optctx, int argc, char **argv, const OptionDef *options, - void (* parse_arg_function)(void *optctx, const char*)); +int parse_options(void *optctx, int argc, char **argv, const OptionDef *options, + int (* parse_arg_function)(void *optctx, const char*)); /** * Parse one given option. @@ -278,7 +372,7 @@ typedef struct OptionParseContext { * * @param optctx an app-specific options context. NULL for global options group */ -int parse_optgroup(void *optctx, OptionGroup *g); +int parse_optgroup(void *optctx, OptionGroup *g, const OptionDef *defs); /** * Split the commandline into an intermediate form convenient for further @@ -341,10 +435,14 @@ int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec); * @param st A stream from s for which the options should be filtered. * @param codec The particular codec for which the options should be filtered. * If null, the default one is looked up according to the codec id. - * @return a pointer to the created dictionary + * @param dst a pointer to the created dictionary + * @param opts_used if non-NULL, every option stored in dst is also stored here, + * with specifiers preserved + * @return a non-negative number on success, a negative error code on failure */ -AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, - AVFormatContext *s, AVStream *st, const AVCodec *codec); +int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id, + AVFormatContext *s, AVStream *st, const AVCodec *codec, + AVDictionary **dst, AVDictionary **opts_used); /** * Setup AVCodecContext options for avformat_find_stream_info(). @@ -353,12 +451,10 @@ AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, * contained in s. * Each dictionary will contain the options from codec_opts which can * be applied to the corresponding stream codec context. - * - * @return pointer to the created array of dictionaries. - * Calls exit() on failure. */ -AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, - AVDictionary *codec_opts); +int setup_find_stream_info_opts(AVFormatContext *s, + AVDictionary *codec_opts, + AVDictionary ***dst); /** * Print an error message to stderr, indicating filename and a human @@ -369,7 +465,10 @@ AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, * * @see av_strerror() */ -void print_error(const char *filename, int err); +static inline void print_error(const char *filename, int err) +{ + av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, av_err2str(err)); +} /** * Print the program banner to stderr. The banner contents depend on the @@ -407,49 +506,43 @@ FILE *get_preset_file(char *filename, size_t filename_size, /** * Realloc array to hold new_size elements of elem_size. - * Calls exit() on failure. * - * @param array array to reallocate + * @param array pointer to the array to reallocate, will be updated + * with a new pointer on success * @param elem_size size in bytes of each element * @param size new element count will be written here * @param new_size number of elements to place in reallocated array - * @return reallocated array + * @return a non-negative number on success, a negative error code on failure */ -void *grow_array(void *array, int elem_size, int *size, int new_size); +int grow_array(void **array, int elem_size, int *size, int new_size); /** * Atomically add a new element to an array of pointers, i.e. allocate * a new entry, reallocate the array of pointers and make the new last * member of this array point to the newly allocated buffer. - * Calls exit() on failure. * * @param array array of pointers to reallocate * @param elem_size size of the new element to allocate * @param nb_elems pointer to the number of elements of the array array; * *nb_elems will be incremented by one by this function. - * @return pointer to the newly allocated entry + * @return pointer to the newly allocated entry or NULL on failure */ void *allocate_array_elem(void *array, size_t elem_size, int *nb_elems); #define GROW_ARRAY(array, nb_elems)\ - array = grow_array(array, sizeof(*array), &nb_elems, nb_elems + 1) - -#define ALLOC_ARRAY_ELEM(array, nb_elems)\ - allocate_array_elem(&array, sizeof(*array[0]), &nb_elems) + grow_array((void**)&array, sizeof(*array), &nb_elems, nb_elems + 1) -#define GET_PIX_FMT_NAME(pix_fmt)\ - const char *name = av_get_pix_fmt_name(pix_fmt); +double get_rotation(const int32_t *displaymatrix); -#define GET_CODEC_NAME(id)\ - const char *name = avcodec_descriptor_get(id)->name; +/* read file contents into a string */ +char *file_read(const char *filename); -#define GET_SAMPLE_FMT_NAME(sample_fmt)\ - const char *name = av_get_sample_fmt_name(sample_fmt) +/* Remove keys in dictionary b from dictionary a */ +void remove_avoptions(AVDictionary **a, AVDictionary *b); -#define GET_SAMPLE_RATE_NAME(rate)\ - char name[16];\ - snprintf(name, sizeof(name), "%d", rate); +/* Check if any keys exist in dictionary m */ +int check_avoptions(AVDictionary *m); -double get_rotation(int32_t *displaymatrix); +int cmdutils_isalnum(char c); #endif /* FFTOOLS_CMDUTILS_H */ diff --git a/src/fftools/ffmpeg.c b/src/fftools/ffmpeg.c index cc0884ca8e3..1b6cd45bba6 100644 --- a/src/fftools/ffmpeg.c +++ b/src/fftools/ffmpeg.c @@ -24,15 +24,14 @@ */ #include "config.h" -#include -#include -#include -#include + #include #include #include #include -#include +#include +#include +#include #if HAVE_IO_H #include @@ -41,37 +40,6 @@ #include #endif -#include "libavformat/avformat.h" -#include "libavdevice/avdevice.h" -#include "libswresample/swresample.h" -#include "libavutil/opt.h" -#include "libavutil/channel_layout.h" -#include "libavutil/parseutils.h" -#include "libavutil/samplefmt.h" -#include "libavutil/fifo.h" -#include "libavutil/hwcontext.h" -#include "libavutil/internal.h" -#include "libavutil/intreadwrite.h" -#include "libavutil/dict.h" -#include "libavutil/display.h" -#include "libavutil/mathematics.h" -#include "libavutil/pixdesc.h" -#include "libavutil/avstring.h" -#include "libavutil/libm.h" -#include "libavutil/imgutils.h" -#include "libavutil/timestamp.h" -#include "libavutil/bprint.h" -#include "libavutil/time.h" -#include "libavutil/thread.h" -#include "libavutil/threadmessage.h" -#include "libavcodec/mathops.h" -#include "libavcodec/version.h" -#include "libavformat/os_support.h" - -# include "libavfilter/avfilter.h" -# include "libavfilter/buffersrc.h" -# include "libavfilter/buffersink.h" - #if HAVE_SYS_RESOURCE_H #include #include @@ -87,7 +55,6 @@ #include #endif - #if HAVE_SYS_SELECT_H #include #endif @@ -101,26 +68,24 @@ #include #endif -#include +#include "libavutil/bprint.h" +#include "libavutil/dict.h" +#include "libavutil/mem.h" +#include "libavutil/time.h" -#include "ffmpeg.h" -#include "cmdutils.h" +#include "libavformat/avformat.h" + +#include "libavdevice/avdevice.h" -#include "libavutil/avassert.h" +#include "cmdutils.h" +#include "ffmpeg.h" +#include "ffmpeg_sched.h" +#include "ffmpeg_utils.h" const char program_name[] = "ffmpeg"; const int program_birth_year = 2000; -static FILE *vstats_file; - -const char *const forced_keyframes_const_names[] = { - "n", - "n_forced", - "prev_forced_n", - "prev_forced_t", - "t", - NULL -}; +FILE *vstats_file; typedef struct BenchmarkTimeStamps { int64_t real_usec; @@ -130,34 +95,24 @@ typedef struct BenchmarkTimeStamps { static BenchmarkTimeStamps get_benchmark_time_stamps(void); static int64_t getmaxrss(void); -static int ifilter_has_all_input_formats(FilterGraph *fg); -static int64_t nb_frames_dup = 0; -static uint64_t dup_warning = 1000; -static int64_t nb_frames_drop = 0; -static int64_t decode_error_stat[2]; -unsigned nb_output_dumped = 0; - -int want_sdp = 1; +atomic_uint nb_output_dumped = 0; static BenchmarkTimeStamps current_time; AVIOContext *progress_avio = NULL; -static uint8_t *subtitle_out; - -InputStream **input_streams = NULL; -int nb_input_streams = 0; InputFile **input_files = NULL; int nb_input_files = 0; -OutputStream **output_streams = NULL; -int nb_output_streams = 0; OutputFile **output_files = NULL; int nb_output_files = 0; FilterGraph **filtergraphs; int nb_filtergraphs; +Decoder **decoders; +int nb_decoders; + #if HAVE_TERMIOS_H /* init terminal so that we can grab keys */ @@ -165,167 +120,6 @@ static struct termios oldtty; static int restore_tty; #endif -#if HAVE_THREADS -static void free_input_threads(void); -#endif - -/* sub2video hack: - Convert subtitles to video with alpha to insert them in filter graphs. - This is a temporary solution until libavfilter gets real subtitles support. - */ - -static int sub2video_get_blank_frame(InputStream *ist) -{ - int ret; - AVFrame *frame = ist->sub2video.frame; - - av_frame_unref(frame); - ist->sub2video.frame->width = ist->dec_ctx->width ? ist->dec_ctx->width : ist->sub2video.w; - ist->sub2video.frame->height = ist->dec_ctx->height ? ist->dec_ctx->height : ist->sub2video.h; - ist->sub2video.frame->format = AV_PIX_FMT_RGB32; - if ((ret = av_frame_get_buffer(frame, 0)) < 0) - return ret; - memset(frame->data[0], 0, frame->height * frame->linesize[0]); - return 0; -} - -static void sub2video_copy_rect(uint8_t *dst, int dst_linesize, int w, int h, - AVSubtitleRect *r) -{ - uint32_t *pal, *dst2; - uint8_t *src, *src2; - int x, y; - - if (r->type != SUBTITLE_BITMAP) { - av_log(NULL, AV_LOG_WARNING, "sub2video: non-bitmap subtitle\n"); - return; - } - if (r->x < 0 || r->x + r->w > w || r->y < 0 || r->y + r->h > h) { - av_log(NULL, AV_LOG_WARNING, "sub2video: rectangle (%d %d %d %d) overflowing %d %d\n", - r->x, r->y, r->w, r->h, w, h - ); - return; - } - - dst += r->y * dst_linesize + r->x * 4; - src = r->data[0]; - pal = (uint32_t *)r->data[1]; - for (y = 0; y < r->h; y++) { - dst2 = (uint32_t *)dst; - src2 = src; - for (x = 0; x < r->w; x++) - *(dst2++) = pal[*(src2++)]; - dst += dst_linesize; - src += r->linesize[0]; - } -} - -static void sub2video_push_ref(InputStream *ist, int64_t pts) -{ - AVFrame *frame = ist->sub2video.frame; - int i; - int ret; - - av_assert1(frame->data[0]); - ist->sub2video.last_pts = frame->pts = pts; - for (i = 0; i < ist->nb_filters; i++) { - ret = av_buffersrc_add_frame_flags(ist->filters[i]->filter, frame, - AV_BUFFERSRC_FLAG_KEEP_REF | - AV_BUFFERSRC_FLAG_PUSH); - if (ret != AVERROR_EOF && ret < 0) - av_log(NULL, AV_LOG_WARNING, "Error while add the frame to buffer source(%s).\n", - av_err2str(ret)); - } -} - -void sub2video_update(InputStream *ist, int64_t heartbeat_pts, AVSubtitle *sub) -{ - AVFrame *frame = ist->sub2video.frame; - int8_t *dst; - int dst_linesize; - int num_rects, i; - int64_t pts, end_pts; - - if (!frame) - return; - if (sub) { - pts = av_rescale_q(sub->pts + sub->start_display_time * 1000LL, - AV_TIME_BASE_Q, ist->st->time_base); - end_pts = av_rescale_q(sub->pts + sub->end_display_time * 1000LL, - AV_TIME_BASE_Q, ist->st->time_base); - num_rects = sub->num_rects; - } else { - /* If we are initializing the system, utilize current heartbeat - PTS as the start time, and show until the following subpicture - is received. Otherwise, utilize the previous subpicture's end time - as the fall-back value. */ - pts = ist->sub2video.initialize ? - heartbeat_pts : ist->sub2video.end_pts; - end_pts = INT64_MAX; - num_rects = 0; - } - if (sub2video_get_blank_frame(ist) < 0) { - av_log(ist->dec_ctx, AV_LOG_ERROR, - "Impossible to get a blank canvas.\n"); - return; - } - dst = frame->data [0]; - dst_linesize = frame->linesize[0]; - for (i = 0; i < num_rects; i++) - sub2video_copy_rect(dst, dst_linesize, frame->width, frame->height, sub->rects[i]); - sub2video_push_ref(ist, pts); - ist->sub2video.end_pts = end_pts; - ist->sub2video.initialize = 0; -} - -static void sub2video_heartbeat(InputStream *ist, int64_t pts) -{ - InputFile *infile = input_files[ist->file_index]; - int i, j, nb_reqs; - int64_t pts2; - - /* When a frame is read from a file, examine all sub2video streams in - the same file and send the sub2video frame again. Otherwise, decoded - 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]; - if (!ist2->sub2video.frame) - continue; - /* subtitles seem to be usually muxed ahead of other streams; - if not, subtracting a larger time here is necessary */ - pts2 = av_rescale_q(pts, ist->st->time_base, ist2->st->time_base) - 1; - /* do not send the heartbeat frame if the subtitle is already ahead */ - if (pts2 <= ist2->sub2video.last_pts) - continue; - if (pts2 >= ist2->sub2video.end_pts || ist2->sub2video.initialize) - /* if we have hit the end of the current displayed subpicture, - or if we need to initialize the system, update the - overlayed subpicture and its start/end times */ - sub2video_update(ist2, pts2 + 1, NULL); - for (j = 0, nb_reqs = 0; j < ist2->nb_filters; j++) - nb_reqs += av_buffersrc_get_nb_failed_requests(ist2->filters[j]->filter); - if (nb_reqs) - sub2video_push_ref(ist2, pts2); - } -} - -static void sub2video_flush(InputStream *ist) -{ - int i; - int ret; - - if (ist->sub2video.end_pts < INT64_MAX) - sub2video_update(ist, INT64_MAX, NULL); - for (i = 0; i < ist->nb_filters; i++) { - ret = av_buffersrc_add_frame(ist->filters[i]->filter, NULL); - if (ret != AVERROR_EOF && ret < 0) - av_log(NULL, AV_LOG_WARNING, "Flush the frame error.\n"); - } -} - -/* end of sub2video hack */ - static void term_exit_sigsafe(void) { #if HAVE_TERMIOS_H @@ -342,9 +136,8 @@ void term_exit(void) static volatile int received_sigterm = 0; static volatile int received_nb_signals = 0; -static atomic_int transcode_init_done = ATOMIC_VAR_INIT(0); +static atomic_int transcode_init_done = 0; static volatile int ffmpeg_exited = 0; -int main_return_code = 0; static int64_t copy_ts_first_pts = AV_NOPTS_VALUE; static void @@ -475,7 +268,7 @@ static int read_key(void) return n; } #elif HAVE_KBHIT -# if HAVE_PEEKNAMEDPIPE +# if HAVE_PEEKNAMEDPIPE && HAVE_GETSTDHANDLE static int is_pipe; static HANDLE input_handle; DWORD dw, nchars; @@ -492,8 +285,9 @@ static int read_key(void) } //Read it if(nchars != 0) { - read(0, &ch, 1); - return ch; + if (read(0, &ch, 1) == 1) + return ch; + return 0; }else{ return -1; } @@ -514,119 +308,24 @@ const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL }; static void ffmpeg_cleanup(int ret) { - int i, j; - if (do_benchmark) { - int maxrss = getmaxrss() / 1024; - av_log(NULL, AV_LOG_INFO, "bench: maxrss=%ikB\n", maxrss); + int64_t maxrss = getmaxrss() / 1024; + av_log(NULL, AV_LOG_INFO, "bench: maxrss=%"PRId64"KiB\n", maxrss); } - for (i = 0; i < nb_filtergraphs; i++) { - FilterGraph *fg = filtergraphs[i]; - avfilter_graph_free(&fg->graph); - for (j = 0; j < fg->nb_inputs; j++) { - InputFilter *ifilter = fg->inputs[j]; - struct InputStream *ist = ifilter->ist; - - if (ifilter->frame_queue) { - AVFrame *frame; - while (av_fifo_read(ifilter->frame_queue, &frame, 1) >= 0) - av_frame_free(&frame); - av_fifo_freep2(&ifilter->frame_queue); - } - av_freep(&ifilter->displaymatrix); - if (ist->sub2video.sub_queue) { - AVSubtitle sub; - while (av_fifo_read(ist->sub2video.sub_queue, &sub, 1) >= 0) - avsubtitle_free(&sub); - av_fifo_freep2(&ist->sub2video.sub_queue); - } - av_buffer_unref(&ifilter->hw_frames_ctx); - av_freep(&ifilter->name); - av_freep(&fg->inputs[j]); - } - av_freep(&fg->inputs); - for (j = 0; j < fg->nb_outputs; j++) { - OutputFilter *ofilter = fg->outputs[j]; - - avfilter_inout_free(&ofilter->out_tmp); - av_freep(&ofilter->name); - av_channel_layout_uninit(&ofilter->ch_layout); - av_freep(&fg->outputs[j]); - } - av_freep(&fg->outputs); - av_freep(&fg->graph_desc); - - av_freep(&filtergraphs[i]); - } + for (int i = 0; i < nb_filtergraphs; i++) + fg_free(&filtergraphs[i]); 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; + for (int i = 0; i < nb_output_files; i++) + of_free(&output_files[i]); - av_dict_free(&ost->sws_dict); - av_dict_free(&ost->swr_opts); + for (int i = 0; i < nb_input_files; i++) + ifile_close(&input_files[i]); - 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 (int i = 0; i < nb_decoders; i++) + dec_free(&decoders[i]); + av_freep(&decoders); if (vstats_file) { if (fclose(vstats_file)) @@ -635,11 +334,13 @@ static void ffmpeg_cleanup(int ret) av_err2str(AVERROR(errno))); } av_freep(&vstats_filename); + of_enc_stats_close(); + + hw_device_free_all(); + av_freep(&filter_nbthreads); - av_freep(&input_streams); av_freep(&input_files); - av_freep(&output_streams); av_freep(&output_files); uninit_opts(); @@ -656,1019 +357,281 @@ static void ffmpeg_cleanup(int ret) ffmpeg_exited = 1; } -void remove_avoptions(AVDictionary **a, AVDictionary *b) +OutputStream *ost_iter(OutputStream *prev) { - const AVDictionaryEntry *t = NULL; + int of_idx = prev ? prev->file->index : 0; + int ost_idx = prev ? prev->index + 1 : 0; - while ((t = av_dict_get(b, "", t, AV_DICT_IGNORE_SUFFIX))) { - av_dict_set(a, t->key, NULL, AV_DICT_MATCH_CASE); - } -} + 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]; -void assert_avoptions(AVDictionary *m) -{ - const AVDictionaryEntry *t; - if ((t = av_dict_get(m, "", NULL, AV_DICT_IGNORE_SUFFIX))) { - av_log(NULL, AV_LOG_FATAL, "Option %s not found.\n", t->key); - exit_program(1); + ost_idx = 0; } -} -static void abort_codec_experimental(const AVCodec *c, int encoder) -{ - exit_program(1); + return NULL; } -static void update_benchmark(const char *fmt, ...) +InputStream *ist_iter(InputStream *prev) { - if (do_benchmark_all) { - BenchmarkTimeStamps t = get_benchmark_time_stamps(); - va_list va; - char buf[1024]; - - if (fmt) { - va_start(va, fmt); - vsnprintf(buf, sizeof(buf), fmt, va); - va_end(va); - av_log(NULL, AV_LOG_INFO, - "bench: %8" PRIu64 " user %8" PRIu64 " sys %8" PRIu64 " real %s \n", - t.user_usec - current_time.user_usec, - t.sys_usec - current_time.sys_usec, - t.real_usec - current_time.real_usec, buf); - } - current_time = t; - } -} + int if_idx = prev ? prev->file->index : 0; + int ist_idx = prev ? prev->index + 1 : 0; -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; + 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]; - 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); + ist_idx = 0; } -} - -/* - * 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); - } + return NULL; } -static int check_recording_time(OutputStream *ost) +static void frame_data_free(void *opaque, uint8_t *data) { - OutputFile *of = output_files[ost->file_index]; + FrameData *fd = (FrameData *)data; - 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) { - close_output_stream(ost); - return 0; - } - return 1; + avcodec_parameters_free(&fd->par_enc); + + av_free(data); } -static double adjust_frame_pts_to_encoder_tb(OutputFile *of, OutputStream *ost, - AVFrame *frame) +static int frame_data_ensure(AVBufferRef **dst, int writable) { - 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; + AVBufferRef *src = *dst; - { - AVFilterContext *filter = ost->filter->filter; - - 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); - - 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 (!src || (writable && !av_buffer_is_writable(src))) { + FrameData *fd; -early_exit: + fd = av_mallocz(sizeof(*fd)); + if (!fd) + return AVERROR(ENOMEM); - 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", - float_pts, - enc ? enc->time_base.num : -1, - enc ? enc->time_base.den : -1); - } + *dst = av_buffer_create((uint8_t *)fd, sizeof(*fd), + frame_data_free, NULL, 0); + if (!*dst) { + av_buffer_unref(&src); + av_freep(&fd); + return AVERROR(ENOMEM); + } - return float_pts; -} + if (src) { + const FrameData *fd_src = (const FrameData *)src->data; -static int init_output_stream(OutputStream *ost, AVFrame *frame, - char *error, int error_len); + memcpy(fd, fd_src, sizeof(*fd)); + fd->par_enc = NULL; -static int init_output_stream_wrapper(OutputStream *ost, AVFrame *frame, - unsigned int fatal) -{ - int ret = AVERROR_BUG; - char error[1024] = {0}; + if (fd_src->par_enc) { + int ret = 0; - if (ost->initialized) - return 0; + fd->par_enc = avcodec_parameters_alloc(); + ret = fd->par_enc ? + avcodec_parameters_copy(fd->par_enc, fd_src->par_enc) : + AVERROR(ENOMEM); + if (ret < 0) { + av_buffer_unref(dst); + av_buffer_unref(&src); + return ret; + } + } - 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_buffer_unref(&src); + } else { + fd->dec.frame_num = UINT64_MAX; + fd->dec.pts = AV_NOPTS_VALUE; - if (fatal) - exit_program(1); + for (unsigned i = 0; i < FF_ARRAY_ELEMS(fd->wallclock); i++) + fd->wallclock[i] = INT64_MIN; + } } - return ret; + return 0; } -static double psnr(double d) +FrameData *frame_data(AVFrame *frame) { - return -10.0 * log10(d); + int ret = frame_data_ensure(&frame->opaque_ref, 1); + return ret < 0 ? NULL : (FrameData*)frame->opaque_ref->data; } -static void update_video_stats(OutputStream *ost, const AVPacket *pkt, int write_vstats) +const FrameData *frame_data_c(AVFrame *frame) { - const uint8_t *sd = av_packet_get_side_data(pkt, AV_PKT_DATA_QUALITY_STATS, - NULL); - AVCodecContext *enc = ost->enc_ctx; - int64_t frame_number; - double ti1, bitrate, avg_bitrate; - - ost->quality = sd ? AV_RL32(sd) : -1; - ost->pict_type = sd ? sd[4] : AV_PICTURE_TYPE_NONE; - - for (int i = 0; ierror); i++) { - if (sd && i < sd[5]) - ost->error[i] = AV_RL64(sd + 8 + 8*i); - else - ost->error[i] = -1; - } - - if (!write_vstats) - return; - - /* this is executed just the first time update_video_stats is called */ - if (!vstats_file) { - vstats_file = fopen(vstats_filename, "w"); - if (!vstats_file) { - perror("fopen"); - exit_program(1); - } - } - - frame_number = ost->packets_encoded; - if (vstats_version <= 1) { - fprintf(vstats_file, "frame= %5"PRId64" q= %2.1f ", frame_number, - ost->quality / (float)FF_QP2LAMBDA); - } else { - fprintf(vstats_file, "out= %2d st= %2d frame= %5"PRId64" q= %2.1f ", ost->file_index, ost->index, frame_number, - ost->quality / (float)FF_QP2LAMBDA); - } - - if (ost->error[0]>=0 && (enc->flags & AV_CODEC_FLAG_PSNR)) - fprintf(vstats_file, "PSNR= %6.2f ", psnr(ost->error[0] / (enc->width * enc->height * 255.0 * 255.0))); - - fprintf(vstats_file,"f_size= %6d ", pkt->size); - /* compute pts value */ - ti1 = pkt->dts * av_q2d(ost->mux_timebase); - if (ti1 < 0.01) - ti1 = 0.01; + int ret = frame_data_ensure(&frame->opaque_ref, 0); + return ret < 0 ? NULL : (const FrameData*)frame->opaque_ref->data; +} - bitrate = (pkt->size * 8) / av_q2d(enc->time_base) / 1000.0; - avg_bitrate = (double)(ost->data_size * 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); - fprintf(vstats_file, "type= %c\n", av_get_picture_type_char(ost->pict_type)); +FrameData *packet_data(AVPacket *pkt) +{ + int ret = frame_data_ensure(&pkt->opaque_ref, 1); + return ret < 0 ? NULL : (FrameData*)pkt->opaque_ref->data; } -static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame) +const FrameData *packet_data_c(AVPacket *pkt) { - AVCodecContext *enc = ost->enc_ctx; - AVPacket *pkt = ost->pkt; - const char *type_desc = av_get_media_type_string(enc->codec_type); - const char *action = frame ? "encode" : "flush"; - int ret; + int ret = frame_data_ensure(&pkt->opaque_ref, 0); + return ret < 0 ? NULL : (const FrameData*)pkt->opaque_ref->data; +} - if (frame) { - ost->frames_encoded++; +int check_avoptions_used(const AVDictionary *opts, const AVDictionary *opts_used, + void *logctx, int decode) +{ + const AVClass *class = avcodec_get_class(); + const AVClass *fclass = avformat_get_class(); - if (debug_ts) { - av_log(NULL, 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), - enc->time_base.num, enc->time_base.den); - } - } + const int flag = decode ? AV_OPT_FLAG_DECODING_PARAM : + AV_OPT_FLAG_ENCODING_PARAM; + const AVDictionaryEntry *e = NULL; - update_benchmark(NULL); + while ((e = av_dict_iterate(opts, e))) { + const AVOption *option, *foption; + char *optname, *p; - 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", - type_desc); - return ret; - } + if (av_dict_get(opts_used, e->key, NULL, 0)) + continue; - while (1) { - ret = avcodec_receive_packet(enc, pkt); - update_benchmark("%s_%s %d.%d", action, type_desc, - ost->file_index, ost->index); + optname = av_strdup(e->key); + if (!optname) + return AVERROR(ENOMEM); - /* 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); + p = strchr(optname, ':'); + if (p) + *p = 0; - if (ret == AVERROR(EAGAIN)) { - av_assert0(frame); // should never happen during flushing - return 0; - } else if (ret == AVERROR_EOF) { - output_packet(of, pkt, ost, 1); - return ret; - } else if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "%s encoding failed\n", type_desc); - return ret; - } + option = av_opt_find(&class, optname, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + foption = av_opt_find(&fclass, optname, NULL, 0, + AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ); + av_freep(&optname); + if (!option || foption) + continue; - if (debug_ts) { - av_log(NULL, 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, - av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &enc->time_base), - av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &enc->time_base), - av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base)); + if (!(option->flags & flag)) { + av_log(logctx, AV_LOG_ERROR, "Codec AVOption %s (%s) is not a %s " + "option.\n", e->key, option->help ? option->help : "", + decode ? "decoding" : "encoding"); + return AVERROR(EINVAL); } - av_packet_rescale_ts(pkt, enc->time_base, ost->mux_timebase); - - if (debug_ts) { - av_log(NULL, 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, - av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &enc->time_base), - av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &enc->time_base), - av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, &enc->time_base)); - } + av_log(logctx, 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 decoder which was not actually used " + "for any stream.\n", e->key, option->help ? option->help : ""); + } - if (enc->codec_type == AVMEDIA_TYPE_VIDEO) - update_video_stats(ost, pkt, !!vstats_filename); + return 0; +} - ost->packets_encoded++; +void update_benchmark(const char *fmt, ...) +{ + if (do_benchmark_all) { + BenchmarkTimeStamps t = get_benchmark_time_stamps(); + va_list va; + char buf[1024]; - output_packet(of, pkt, ost, 0); + if (fmt) { + va_start(va, fmt); + vsnprintf(buf, sizeof(buf), fmt, va); + va_end(va); + av_log(NULL, AV_LOG_INFO, + "bench: %8" PRIu64 " user %8" PRIu64 " sys %8" PRIu64 " real %s \n", + t.user_usec - current_time.user_usec, + t.sys_usec - current_time.sys_usec, + t.real_usec - current_time.real_usec, buf); + } + current_time = t; } - - av_assert0(0); } -static void do_audio_out(OutputFile *of, OutputStream *ost, - AVFrame *frame) +static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time, int64_t pts) { + AVBPrint buf, buf_script; + int64_t total_size = of_filesize(output_files[0]); + int vid; + double bitrate; + double speed; + static int64_t last_time = -1; + static int first_report = 1; + uint64_t nb_frames_dup = 0, nb_frames_drop = 0; + int mins, secs, us; + int64_t hours; + const char *hours_sign; int ret; + float t; - adjust_frame_pts_to_encoder_tb(of, ost, frame); - - if (!check_recording_time(ost)) + if (!print_stats && !is_last_report && !progress_avio) 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; - - ret = encode_frame(of, ost, frame); - if (ret < 0) - exit_program(1); -} - -static void do_subtitle_out(OutputFile *of, - OutputStream *ost, - AVSubtitle *sub) -{ - int subtitle_out_max_size = 1024 * 1024; - int subtitle_out_size, nb, i; - 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"); - if (exit_on_error) - exit_program(1); - return; + if (!is_last_report) { + if (last_time == -1) { + last_time = cur_time; + } + if (((cur_time - last_time) < stats_period && !first_report) || + (first_report && atomic_load(&nb_output_dumped) < nb_output_files)) + return; + last_time = cur_time; } - enc = ost->enc_ctx; + t = (cur_time-timer_start) / 1000000.0; + + vid = 0; + 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 float q = ost->enc ? atomic_load(&ost->quality) / (float) FF_QP2LAMBDA : -1; - 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); + if (vid && ost->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 && ost->type == AVMEDIA_TYPE_VIDEO) { + float fps; + uint64_t frame_number = atomic_load(&ost->packets_written); - /* Note: DVB subtitle need one packet to draw them and one other - packet to clear them */ - /* XXX: signal it in the codec context ? */ - if (enc->codec_id == AV_CODEC_ID_DVB_SUBTITLE) - nb = 2; - else - nb = 1; - - /* shift timestamp to honor -ss and make check_recording_time() work with -t */ - pts = sub->pts; - if (output_files[ost->file_index]->start_time != AV_NOPTS_VALUE) - pts -= output_files[ost->file_index]->start_time; - 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)) - return; + 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) + av_bprintf(&buf, "L"); - 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); - sub->end_display_time -= sub->start_display_time; - sub->start_display_time = 0; - if (i == 1) - sub->num_rects = 0; - - ost->frames_encoded++; - - subtitle_out_size = avcodec_encode_subtitle(enc, subtitle_out, - subtitle_out_max_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"); - exit_program(1); - } + if (ost->filter) { + nb_frames_dup = atomic_load(&ost->filter->nb_frames_dup); + nb_frames_drop = atomic_load(&ost->filter->nb_frames_drop); + } - 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); - 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); - else - pkt->pts += av_rescale_q(sub->end_display_time, (AVRational){ 1, 1000 }, ost->mux_timebase); + vid = 1; } - pkt->dts = pkt->pts; - output_packet(of, pkt, ost, 0); } -} -/* May modify/reset next_picture */ -static void do_video_out(OutputFile *of, - OutputStream *ost, - AVFrame *next_picture) -{ - int ret; - AVCodecContext *enc = ost->enc_ctx; - AVRational frame_rate; - int64_t nb_frames, nb0_frames, i; - double delta, delta0; - double duration = 0; - double sync_ipts = AV_NOPTS_VALUE; - InputStream *ist = NULL; - 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) - duration = 1/(av_q2d(frame_rate) * av_q2d(enc->time_base)); - - if(ist && ist->st->start_time != AV_NOPTS_VALUE && ist->first_dts != AV_NOPTS_VALUE && ost->frame_rate.num) - duration = FFMIN(duration, 1/(av_q2d(ost->frame_rate) * av_q2d(enc->time_base))); - - if (!ost->filters_script && - !ost->filters && - (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)); + if (copy_ts) { + if (copy_ts_first_pts == AV_NOPTS_VALUE && pts > 1) + copy_ts_first_pts = pts; + if (copy_ts_first_pts != AV_NOPTS_VALUE) + pts -= copy_ts_first_pts; } - 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]); - } 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); - } - } - - /* - * 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; - - if (nb0_frames == 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); - } - if (nb_frames > (nb0_frames && ost->last_dropped) + (nb_frames > nb0_frames)) { - if (nb_frames > dts_error_threshold * 30) { - av_log(NULL, 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); - if (nb_frames_dup > dup_warning) { - av_log(NULL, 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; - - /* 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]) { - in_picture = ost->last_frame; - } else - in_picture = next_picture; - - if (!in_picture) - return; - - in_picture->pts = ost->sync_opts; - - if (!check_recording_time(ost)) - 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; - } - - 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) - exit_program(1); - - ost->sync_opts++; - ost->frame_number++; - } - - av_frame_unref(ost->last_frame); - if (next_picture) - 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. - * - * @return 0 for success, <0 for severe errors - */ -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]; - OutputFile *of = output_files[ost->file_index]; - AVFilterContext *filter; - AVCodecContext *enc = ost->enc_ctx; - int ret = 0; - - if (!ost->filter || !ost->filter->graph->graph) - continue; - filter = ost->filter->filter; - - /* - * Unlike video, with audio the audio frame size matters. - * Currently we are fully reliant on the lavfi filter chain to - * do the buffering deed for us, and thus the frame size parameter - * needs to be set accordingly. Where does one get the required - * frame size? From the initialized AVCodecContext of an audio - * encoder. Thus, if we have gotten to an audio stream, initialize - * the encoder earlier than receiving the first AVFrame. - */ - if (av_buffersink_get_type(filter) == AVMEDIA_TYPE_AUDIO) - init_output_stream_wrapper(ost, NULL, 1); - - filtered_frame = ost->filtered_frame; - - while (1) { - ret = av_buffersink_get_frame_flags(filter, filtered_frame, - AV_BUFFERSINK_FLAG_NO_REQUEST); - if (ret < 0) { - if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { - av_log(NULL, AV_LOG_WARNING, - "Error in av_buffersink_get_frame_flags(): %s\n", av_err2str(ret)); - } else if (flush && ret == AVERROR_EOF) { - if (av_buffersink_get_type(filter) == AVMEDIA_TYPE_VIDEO) - do_video_out(of, ost, NULL); - } - break; - } - if (ost->finished) { - av_frame_unref(filtered_frame); - continue; - } - - switch (av_buffersink_get_type(filter)) { - case AVMEDIA_TYPE_VIDEO: - if (!ost->frame_aspect_ratio.num) - enc->sample_aspect_ratio = filtered_frame->sample_aspect_ratio; - - do_video_out(of, ost, filtered_frame); - break; - case AVMEDIA_TYPE_AUDIO: - if (!(enc->codec->capabilities & AV_CODEC_CAP_PARAM_CHANGE) && - enc->ch_layout.nb_channels != filtered_frame->ch_layout.nb_channels) { - av_log(NULL, AV_LOG_ERROR, - "Audio filter graph output is not normalized and encoder does not support parameter changes\n"); - break; - } - do_audio_out(of, ost, filtered_frame); - break; - default: - // TODO support subtitle filters - av_assert0(0); - } - - av_frame_unref(filtered_frame); - } - } - - return 0; -} - -static void print_final_stats(int64_t total_size) -{ - uint64_t video_size = 0, audio_size = 0, extra_size = 0, other_size = 0; - uint64_t subtitle_size = 0; - uint64_t data_size = 0; - float percent = -1.0; - int i, j; - int pass1_used = 1; - - 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; - } - 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)) - != AV_CODEC_FLAG_PASS1) - pass1_used = 0; - } - - if (data_size && total_size>0 && total_size >= data_size) - percent = 100.0 * (total_size - data_size) / data_size; - - 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); - 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++) { - InputFile *f = input_files[i]; - uint64_t total_packets = 0, total_size = 0; - - av_log(NULL, AV_LOG_VERBOSE, "Input file #%d (%s):\n", - 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; - - total_size += ist->data_size; - total_packets += ist->nb_packets; - - av_log(NULL, AV_LOG_VERBOSE, " Input stream #%d:%d (%s): ", - i, j, av_get_media_type_string(type)); - av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" packets read (%"PRIu64" bytes); ", - ist->nb_packets, ist->data_size); - - if (ist->decoding_needed) { - av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" frames decoded", - ist->frames_decoded); - if (type == AVMEDIA_TYPE_AUDIO) - av_log(NULL, AV_LOG_VERBOSE, " (%"PRIu64" samples)", ist->samples_decoded); - av_log(NULL, AV_LOG_VERBOSE, "; "); - } - - av_log(NULL, AV_LOG_VERBOSE, "\n"); - } - - av_log(NULL, AV_LOG_VERBOSE, " Total: %"PRIu64" packets (%"PRIu64" bytes) demuxed\n", - total_packets, total_size); - } - - for (i = 0; i < nb_output_files; i++) { - OutputFile *of = output_files[i]; - uint64_t total_packets = 0, total_size = 0; - - av_log(NULL, AV_LOG_VERBOSE, "Output file #%d (%s):\n", - i, of->ctx->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; - - total_size += ost->data_size; - total_packets += 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) { - av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" frames encoded", - ost->frames_encoded); - if (type == AVMEDIA_TYPE_AUDIO) - av_log(NULL, AV_LOG_VERBOSE, " (%"PRIu64" samples)", ost->samples_encoded); - av_log(NULL, AV_LOG_VERBOSE, "; "); - } - - av_log(NULL, AV_LOG_VERBOSE, "%"PRIu64" packets muxed (%"PRIu64" bytes); ", - ost->packets_written, ost->data_size); - - av_log(NULL, AV_LOG_VERBOSE, "\n"); - } - - av_log(NULL, AV_LOG_VERBOSE, " Total: %"PRIu64" packets (%"PRIu64" bytes) muxed\n", - total_packets, total_size); - } - if(video_size + data_size + audio_size + subtitle_size + extra_size == 0){ - av_log(NULL, AV_LOG_WARNING, "Output file is empty, nothing was encoded "); - if (pass1_used) { - av_log(NULL, AV_LOG_WARNING, "\n"); - } else { - av_log(NULL, AV_LOG_WARNING, "(check -ss / -t / -frames parameters if used)\n"); - } - } -} - -EM_JS(void, send_progress, (double progress, double time), { - Module.receiveProgress(progress, time); -}); - -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; - double bitrate; - double speed; - int64_t pts = INT64_MIN + 1; - static int64_t last_time = -1; - static int first_report = 1; - static int qp_histogram[52]; - int hours, mins, secs, us; - const char *hours_sign; - int ret; - float t; - - if (!print_stats && !is_last_report && !progress_avio) - return; - - if (!is_last_report) { - if (last_time == -1) { - last_time = cur_time; - } - if (((cur_time - last_time) < stats_period && !first_report) || - (first_report && nb_output_dumped < nb_output_files)) - return; - last_time = cur_time; - } - - 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) { - 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; - - 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) - 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 ((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; - double p; - char type[3] = { 'Y','U','V' }; - av_bprintf(&buf, "PSNR="); - for (j = 0; j < 3; j++) { - if (is_last_report) { - error = enc->error[j]; - scale = enc->width * enc->height * 255.0 * 255.0 * frame_number; - } else { - error = ost->error[j]; - scale = enc->width * enc->height * 255.0 * 255.0; - } - if (j) - scale /= 4; - error_sum += error; - scale_sum += scale; - p = psnr(error / scale); - av_bprintf(&buf, "%c:%2.2f ", type[j], p); - av_bprintf(&buf_script, "stream_%d_%d_psnr_%c=%2.2f\n", - ost->file_index, ost->index, type[j] | 32, p); - } - p = psnr(error_sum / scale_sum); - av_bprintf(&buf, "*:%2.2f ", psnr(error_sum / scale_sum)); - av_bprintf(&buf_script, "stream_%d_%d_psnr_all=%2.2f\n", - ost->file_index, ost->index, p); - } - 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 (copy_ts) { - if (copy_ts_first_pts == AV_NOPTS_VALUE && pts > 1) - copy_ts_first_pts = pts; - if (copy_ts_first_pts != AV_NOPTS_VALUE) - pts -= copy_ts_first_pts; - } - } - - if (is_last_report) - nb_frames_drop += ost->last_dropped; - } - - /* send_progress here only works when the duration of - * input and output file are the same, other cases (ex. trim) - * still WIP. - * - * TODO: support cases like trim. - */ - int64_t duration = -1; - int64_t pts_abs = FFABS(pts); - /* Use the longest duration among all input files. - */ - for (int i = 0; i < nb_input_files; i++) { - int64_t file_duration = input_files[i]->ctx->duration; - if (file_duration > duration) { - duration = file_duration; - } - } - send_progress((double)pts_abs / (double)duration, (double)pts_abs); - - secs = FFABS(pts) / AV_TIME_BASE; - us = FFABS(pts) % AV_TIME_BASE; - mins = secs / 60; - secs %= 60; - hours = mins / 60; - mins %= 60; + us = FFABS64U(pts) % AV_TIME_BASE; + secs = FFABS64U(pts) / AV_TIME_BASE % 60; + mins = FFABS64U(pts) / AV_TIME_BASE / 60 % 60; + hours = FFABS64U(pts) / AV_TIME_BASE / 3600; hours_sign = (pts < 0) ? "-" : ""; - bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; - speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; + bitrate = pts != AV_NOPTS_VALUE && pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; + speed = pts != AV_NOPTS_VALUE && 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); + else av_bprintf(&buf, "size=%8.0fKiB time=", total_size / 1024.0); if (pts == AV_NOPTS_VALUE) { av_bprintf(&buf, "N/A "); } else { - av_bprintf(&buf, "%s%02d:%02d:%02d.%02d ", + av_bprintf(&buf, "%s%02"PRId64":%02d:%02d.%02d ", hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE); } @@ -1689,2690 +652,216 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti } 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 (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 (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 (print_stats || is_last_report) { - // Always print a new line of message. - const char end = '\n'; //is_last_report ? '\n' : '\r'; - if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) { - fprintf(stderr, "%s %c", buf.str, end); - } else - av_log(NULL, AV_LOG_INFO, "%s %c", buf.str, end); - - fflush(stderr); - } - 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) { - // Make sure the progress is ended with 1. - if (pts_abs != duration) send_progress(1, (double)pts_abs); - print_final_stats(total_size); - } -} - -static int ifilter_parameters_from_codecpar(InputFilter *ifilter, AVCodecParameters *par) -{ - int ret; - - // We never got any input. Set a fake format, which will - // come from libavformat. - ifilter->format = par->format; - ifilter->sample_rate = par->sample_rate; - ifilter->width = par->width; - ifilter->height = par->height; - ifilter->sample_aspect_ratio = par->sample_aspect_ratio; - ret = av_channel_layout_copy(&ifilter->ch_layout, &par->ch_layout); - if (ret < 0) - return ret; - - return 0; -} - -static void flush_encoders(void) -{ - int i, ret; - - for (i = 0; i < nb_output_streams; i++) { - OutputStream *ost = output_streams[i]; - AVCodecContext *enc = ost->enc_ctx; - OutputFile *of = output_files[ost->file_index]; - - if (!ost->encoding_needed) - continue; - - // Try to enable encoding with no input frames. - // Maybe we should just let encoding fail instead. - 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); - - 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"); - exit_program(1); - } - } - - if (!ifilter_has_all_input_formats(fg)) - continue; - - ret = configure_filtergraph(fg); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error configuring filter graph\n"); - exit_program(1); - } - - finish_output_stream(ost); - } - - init_output_stream_wrapper(ost, NULL, 1); - } - - if (enc->codec_type != AVMEDIA_TYPE_VIDEO && enc->codec_type != AVMEDIA_TYPE_AUDIO) - continue; - - ret = encode_frame(of, ost, NULL); - if (ret != AVERROR_EOF) - exit_program(1); - } -} - -/* - * Check whether a packet from ist should be written into ost at this time - */ -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) - return 0; - - if (ost->finished & MUXER_FINISHED) - return 0; - - if (of->start_time != AV_NOPTS_VALUE && ist->pts < of->start_time) - return 0; - - return 1; -} - -static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *pkt) -{ - OutputFile *of = output_files[ost->file_index]; - InputFile *f = input_files [ist->file_index]; - int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; - int64_t ost_tb_start_time = av_rescale_q(start_time, AV_TIME_BASE_Q, ost->mux_timebase); - AVPacket *opkt = ost->pkt; - - av_packet_unref(opkt); - // EOF: flush output bitstream filters. - if (!pkt) { - output_packet(of, opkt, ost, 1); - return; - } - - if (!ost->streamcopy_started && !(pkt->flags & AV_PKT_FLAG_KEY) && - !ost->copy_initial_nonkeyframes) - 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)) - return; - } - - if (of->recording_time != INT64_MAX && - ist->pts >= of->recording_time + start_time) { - close_output_stream(ost); - return; - } - - if (f->recording_time != INT64_MAX) { - 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; - } - if (ist->pts >= f->recording_time + start_time) { - close_output_stream(ost); - return; - } - } - - if (av_packet_ref(opkt, pkt) < 0) - exit_program(1); - - if (pkt->pts != AV_NOPTS_VALUE) - opkt->pts = av_rescale_q(pkt->pts, ist->st->time_base, ost->mux_timebase) - ost_tb_start_time; - - if (pkt->dts == AV_NOPTS_VALUE) { - opkt->dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ost->mux_timebase); - } else if (ost->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - int duration = av_get_audio_frame_duration(ist->dec_ctx, pkt->size); - if(!duration) - duration = ist->dec_ctx->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); - /* 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 -= ost_tb_start_time; - - opkt->duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->mux_timebase); - - ost->sync_opts += opkt->duration; - - 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) - decode_error_stat[ret<0] ++; - - if (ret < 0 && exit_on_error) - exit_program(1); - - if (*got_output && ist) { - if (ist->decoded_frame->decode_error_flags || (ist->decoded_frame->flags & AV_FRAME_FLAG_CORRUPT)) { - av_log(NULL, exit_on_error ? AV_LOG_FATAL : AV_LOG_WARNING, - "%s: corrupt decoded frame in stream %d\n", input_files[ist->file_index]->ctx->url, ist->st->index); - if (exit_on_error) - exit_program(1); - } - } -} - -// Filters can be configured only if the formats of all inputs are known. -static int ifilter_has_all_input_formats(FilterGraph *fg) -{ - int i; - for (i = 0; i < fg->nb_inputs; i++) { - if (fg->inputs[i]->format < 0 && (fg->inputs[i]->type == AVMEDIA_TYPE_AUDIO || - fg->inputs[i]->type == AVMEDIA_TYPE_VIDEO)) - return 0; - } - return 1; -} - -static int ifilter_send_frame(InputFilter *ifilter, AVFrame *frame, int keep_reference) -{ - FilterGraph *fg = ifilter->graph; - AVFrameSideData *sd; - int need_reinit, ret; - int buffersrc_flags = AV_BUFFERSRC_FLAG_PUSH; - - if (keep_reference) - buffersrc_flags |= AV_BUFFERSRC_FLAG_KEEP_REF; - - /* determine if the parameters for this input changed */ - need_reinit = ifilter->format != frame->format; - - switch (ifilter->ist->st->codecpar->codec_type) { - case AVMEDIA_TYPE_AUDIO: - need_reinit |= ifilter->sample_rate != frame->sample_rate || - av_channel_layout_compare(&ifilter->ch_layout, &frame->ch_layout); - break; - case AVMEDIA_TYPE_VIDEO: - need_reinit |= ifilter->width != frame->width || - ifilter->height != frame->height; - break; - } - - if (!ifilter->ist->reinit_filters && fg->graph) - need_reinit = 0; - - if (!!ifilter->hw_frames_ctx != !!frame->hw_frames_ctx || - (ifilter->hw_frames_ctx && ifilter->hw_frames_ctx->data != frame->hw_frames_ctx->data)) - need_reinit = 1; - - if (sd = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) { - if (!ifilter->displaymatrix || memcmp(sd->data, ifilter->displaymatrix, sizeof(int32_t) * 9)) - need_reinit = 1; - } else if (ifilter->displaymatrix) - need_reinit = 1; - - if (need_reinit) { - ret = ifilter_parameters_from_frame(ifilter, frame); - if (ret < 0) - return ret; - } - - /* (re)init the graph if possible, otherwise buffer the frame and return */ - if (need_reinit || !fg->graph) { - if (!ifilter_has_all_input_formats(fg)) { - AVFrame *tmp = av_frame_clone(frame); - if (!tmp) - return AVERROR(ENOMEM); - - ret = av_fifo_write(ifilter->frame_queue, &tmp, 1); - if (ret < 0) - av_frame_free(&tmp); - - return ret; - } - - ret = reap_filters(1); - if (ret < 0 && ret != AVERROR_EOF) { - av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret)); - return ret; - } - - ret = configure_filtergraph(fg); - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n"); - return ret; - } - } - - ret = av_buffersrc_add_frame_flags(ifilter->filter, frame, buffersrc_flags); - if (ret < 0) { - if (ret != AVERROR_EOF) - av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret)); - return ret; - } - - return 0; -} - -static int ifilter_send_eof(InputFilter *ifilter, int64_t pts) -{ - int ret; - - ifilter->eof = 1; - - if (ifilter->filter) { - ret = av_buffersrc_close(ifilter->filter, pts, AV_BUFFERSRC_FLAG_PUSH); - if (ret < 0) - return ret; - } else { - // the filtergraph was never configured - if (ifilter->format < 0) { - ret = ifilter_parameters_from_codecpar(ifilter, ifilter->ist->st->codecpar); - if (ret < 0) - return ret; - } - if (ifilter->format < 0 && (ifilter->type == AVMEDIA_TYPE_AUDIO || ifilter->type == AVMEDIA_TYPE_VIDEO)) { - av_log(NULL, AV_LOG_ERROR, "Cannot determine format of input stream %d:%d after EOF\n", ifilter->ist->file_index, ifilter->ist->st->index); - return AVERROR_INVALIDDATA; - } - } - - return 0; -} - -// This does not quite work like avcodec_decode_audio4/avcodec_decode_video2. -// 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) -{ - int ret; - - *got_frame = 0; - - if (pkt) { - ret = avcodec_send_packet(avctx, pkt); - // In particular, we don't expect AVERROR(EAGAIN), because we read all - // decoded frames with avcodec_receive_frame() until done. - if (ret < 0 && ret != AVERROR_EOF) - return ret; - } - - ret = avcodec_receive_frame(avctx, frame); - if (ret < 0 && ret != AVERROR(EAGAIN)) - return ret; - if (ret >= 0) - *got_frame = 1; - - return 0; -} - -static int send_frame_to_filters(InputStream *ist, AVFrame *decoded_frame) -{ - int i, ret; - - av_assert1(ist->nb_filters > 0); /* ensure ret is initialized */ - for (i = 0; i < ist->nb_filters; i++) { - ret = ifilter_send_frame(ist->filters[i], decoded_frame, i < ist->nb_filters - 1); - if (ret == AVERROR_EOF) - ret = 0; /* ignore */ - if (ret < 0) { - av_log(NULL, AV_LOG_ERROR, - "Failed to inject frame into filter network: %s\n", av_err2str(ret)); - break; - } - } - return ret; -} - -static int decode_audio(InputStream *ist, AVPacket *pkt, int *got_output, - int *decode_failed) -{ - AVFrame *decoded_frame = ist->decoded_frame; - AVCodecContext *avctx = ist->dec_ctx; - int ret, err = 0; - AVRational decoded_frame_tb; - - update_benchmark(NULL); - ret = decode(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); - - if (!*got_output || ret < 0) - return ret; - - ist->samples_decoded += decoded_frame->nb_samples; - ist->frames_decoded++; - - /* 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; - ist->next_dts += ((int64_t)AV_TIME_BASE * decoded_frame->nb_samples) / - avctx->sample_rate; - - if (decoded_frame->pts != AV_NOPTS_VALUE) { - decoded_frame_tb = ist->st->time_base; - } else if (pkt && pkt->pts != AV_NOPTS_VALUE) { - decoded_frame->pts = pkt->pts; - decoded_frame_tb = ist->st->time_base; - }else { - decoded_frame->pts = ist->dts; - decoded_frame_tb = AV_TIME_BASE_Q; - } - if (pkt && pkt->duration && ist->prev_pkt_pts != AV_NOPTS_VALUE && - pkt->pts != AV_NOPTS_VALUE && pkt->pts - ist->prev_pkt_pts > pkt->duration) - ist->filter_in_rescale_delta_last = AV_NOPTS_VALUE; - if (pkt) - 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}); - ist->nb_samples = decoded_frame->nb_samples; - err = send_frame_to_filters(ist, decoded_frame); - - av_frame_unref(decoded_frame); - return err < 0 ? err : ret; -} - -static int decode_video(InputStream *ist, AVPacket *pkt, int *got_output, int64_t *duration_pts, int eof, - int *decode_failed) -{ - AVFrame *decoded_frame = ist->decoded_frame; - int i, ret = 0, err = 0; - int64_t best_effort_timestamp; - int64_t dts = AV_NOPTS_VALUE; - - // With fate-indeo3-2, we're getting 0-sized packets before EOF for some - // reason. This seems like a semi-critical bug. Don't trigger EOF, and - // skip the packet. - if (!eof && pkt && pkt->size == 0) - return 0; - - if (ist->dts != AV_NOPTS_VALUE) - dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base); - if (pkt) { - pkt->dts = dts; // ffmpeg.c probably shouldn't do this - } - - // The old code used to set dts on the drain packet, which does not work - // with the new API anymore. - if (eof) { - void *new = av_realloc_array(ist->dts_buffer, ist->nb_dts_buffer + 1, sizeof(ist->dts_buffer[0])); - if (!new) - return AVERROR(ENOMEM); - ist->dts_buffer = new; - ist->dts_buffer[ist->nb_dts_buffer++] = dts; - } - - update_benchmark(NULL); - ret = decode(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->dec_ctx->codec_id == AV_CODEC_ID_H264) { - ist->st->codecpar->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" - "If you want to help, upload a sample " - "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); - } - - if (ret != AVERROR_EOF) - check_decode_result(ist, got_output, ret); - - if (*got_output && ret >= 0) { - if (ist->dec_ctx->width != decoded_frame->width || - ist->dec_ctx->height != decoded_frame->height || - ist->dec_ctx->pix_fmt != decoded_frame->format) { - av_log(NULL, AV_LOG_DEBUG, "Frame parameters mismatch context %d,%d,%d != %d,%d,%d\n", - decoded_frame->width, - decoded_frame->height, - decoded_frame->format, - ist->dec_ctx->width, - ist->dec_ctx->height, - ist->dec_ctx->pix_fmt); - } - } - - if (!*got_output || ret < 0) - return ret; - - if(ist->top_field_first>=0) - decoded_frame->top_field_first = ist->top_field_first; - - ist->frames_decoded++; - - if (ist->hwaccel_retrieve_data && decoded_frame->format == ist->hwaccel_pix_fmt) { - err = ist->hwaccel_retrieve_data(ist->dec_ctx, decoded_frame); - 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; - - if (ist->framerate.num) - best_effort_timestamp = ist->cfr_next_pts++; - - if (eof && best_effort_timestamp == AV_NOPTS_VALUE && ist->nb_dts_buffer > 0) { - best_effort_timestamp = ist->dts_buffer[0]; - - for (i = 0; i < ist->nb_dts_buffer - 1; i++) - ist->dts_buffer[i] = ist->dts_buffer[i + 1]; - ist->nb_dts_buffer--; - } - - if(best_effort_timestamp != AV_NOPTS_VALUE) { - int64_t ts = av_rescale_q(decoded_frame->pts = best_effort_timestamp, ist->st->time_base, AV_TIME_BASE_Q); - - if (ts != AV_NOPTS_VALUE) - ist->next_pts = ist->pts = ts; - } - - if (debug_ts) { - av_log(NULL, AV_LOG_INFO, "decoder -> ist_index:%d type:video " - "frame_pts:%s frame_pts_time:%s best_effort_ts:%"PRId64" best_effort_ts_time:%s keyframe:%d frame_type:%d time_base:%d/%d\n", - ist->st->index, av_ts2str(decoded_frame->pts), - av_ts2timestr(decoded_frame->pts, &ist->st->time_base), - best_effort_timestamp, - av_ts2timestr(best_effort_timestamp, &ist->st->time_base), - decoded_frame->key_frame, decoded_frame->pict_type, - ist->st->time_base.num, ist->st->time_base.den); - } - - if (ist->st->sample_aspect_ratio.num) - decoded_frame->sample_aspect_ratio = ist->st->sample_aspect_ratio; - - err = send_frame_to_filters(ist, decoded_frame); - -fail: - av_frame_unref(decoded_frame); - return err < 0 ? err : ret; -} - -static int transcode_subtitles(InputStream *ist, AVPacket *pkt, int *got_output, - int *decode_failed) -{ - AVSubtitle subtitle; - 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, - 1000, AV_TIME_BASE); - if (end < ist->prev_sub.subtitle.end_display_time) { - av_log(ist->dec_ctx, AV_LOG_DEBUG, - "Subtitle duration reduced from %"PRId32" to %d%s\n", - ist->prev_sub.subtitle.end_display_time, end, - end <= 0 ? ", dropping it" : ""); - ist->prev_sub.subtitle.end_display_time = end; - } - } - FFSWAP(int, *got_output, ist->prev_sub.got_output); - FFSWAP(int, ret, ist->prev_sub.ret); - FFSWAP(AVSubtitle, subtitle, ist->prev_sub.subtitle); - if (end <= 0) - goto out; - } - - if (!*got_output) - return ret; - - if (ist->sub2video.frame) { - 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); - - ret = av_fifo_write(ist->sub2video.sub_queue, &subtitle, 1); - if (ret < 0) - exit_program(1); - free_sub = 0; - } - - 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) - continue; - - do_subtitle_out(output_files[ost->file_index], ost, &subtitle); - } - -out: - if (free_sub) - avsubtitle_free(&subtitle); - return ret; -} - -static int send_filter_eof(InputStream *ist) -{ - int i, ret; - /* TODO keep pts also in stream time base to avoid converting back */ - int64_t pts = av_rescale_q_rnd(ist->pts, AV_TIME_BASE_Q, ist->st->time_base, - AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); - - for (i = 0; i < ist->nb_filters; i++) { - ret = ifilter_send_eof(ist->filters[i], pts); - if (ret < 0) - return ret; - } - return 0; -} - -/* 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; - int repeating = 0; - int eof_reached = 0; - - AVPacket *avpkt = ist->pkt; - - if (!ist->saw_first_ts) { - ist->first_dts = - ist->dts = ist->st->avg_frame_rate.num ? - ist->dec_ctx->has_b_frames * AV_TIME_BASE / av_q2d(ist->st->avg_frame_rate) : 0; - ist->pts = 0; - if (pkt && pkt->pts != AV_NOPTS_VALUE && !ist->decoding_needed) { - ist->first_dts = - ist->dts += av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); - ist->pts = ist->dts; //unused but better to set it to a value thats not totally wrong - } - ist->saw_first_ts = 1; - } - - if (ist->next_dts == AV_NOPTS_VALUE) - ist->next_dts = ist->dts; - if (ist->next_pts == AV_NOPTS_VALUE) - ist->next_pts = ist->pts; - - if (pkt) { - av_packet_unref(avpkt); - ret = av_packet_ref(avpkt, pkt); - if (ret < 0) - return ret; - } - - 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) - ist->next_pts = ist->pts = ist->dts; - } - - // while we have more to decode or while the decoder did output something on EOF - while (ist->decoding_needed) { - int64_t duration_dts = 0; - int64_t duration_pts = 0; - int got_output = 0; - int decode_failed = 0; - - ist->pts = ist->next_pts; - ist->dts = ist->next_dts; - - switch (ist->dec_ctx->codec_type) { - case AVMEDIA_TYPE_AUDIO: - ret = decode_audio (ist, repeating ? NULL : avpkt, &got_output, - &decode_failed); - av_packet_unref(avpkt); - break; - case AVMEDIA_TYPE_VIDEO: - ret = decode_video (ist, repeating ? NULL : avpkt, &got_output, &duration_pts, !pkt, - &decode_failed); - if (!repeating || !pkt || got_output) { - 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; - duration_dts = ((int64_t)AV_TIME_BASE * - ist->dec_ctx->framerate.den * ticks) / - ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; - } - - if(ist->dts != AV_NOPTS_VALUE && duration_dts) { - ist->next_dts += duration_dts; - }else - ist->next_dts = AV_NOPTS_VALUE; - } - - if (got_output) { - if (duration_pts > 0) { - ist->next_pts += av_rescale_q(duration_pts, ist->st->time_base, AV_TIME_BASE_Q); - } else { - ist->next_pts += duration_dts; - } - } - av_packet_unref(avpkt); - break; - case AVMEDIA_TYPE_SUBTITLE: - if (repeating) - break; - ret = transcode_subtitles(ist, avpkt, &got_output, &decode_failed); - if (!pkt && ret >= 0) - ret = AVERROR_EOF; - av_packet_unref(avpkt); - break; - default: - return -1; - } - - if (ret == AVERROR_EOF) { - eof_reached = 1; - break; - } - - if (ret < 0) { - if (decode_failed) { - av_log(NULL, AV_LOG_ERROR, "Error while decoding stream #%d:%d: %s\n", - ist->file_index, ist->st->index, av_err2str(ret)); - } else { - av_log(NULL, AV_LOG_FATAL, "Error while processing the decoded " - "data for stream #%d:%d\n", ist->file_index, ist->st->index); - } - if (!decode_failed || exit_on_error) - exit_program(1); - break; - } - - if (got_output) - ist->got_output = 1; - - if (!got_output) - break; - - // During draining, we might get multiple output frames in this loop. - // ffmpeg.c does not drain the filter chain on configuration changes, - // which means if we send multiple frames at once to the filters, and - // one of those frames changes configuration, the buffered frames will - // be lost. This can upset certain FATE tests. - // Decode only 1 frame per call on EOF to appease these FATE tests. - // The ideal solution would be to rewrite decoding to use the new - // decoding API in a better way. - if (!pkt) - break; - - repeating = 1; - } - - /* after flushing, send an EOF on all the filter inputs attached to the stream */ - /* except when looping we need to flush but not to send an EOF */ - if (!pkt && ist->decoding_needed && eof_reached && !no_eof) { - int ret = send_filter_eof(ist); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, "Error marking filters as finished\n"); - exit_program(1); - } - } - - /* handle stream copy */ - if (!ist->decoding_needed && pkt) { - ist->dts = ist->next_dts; - switch (ist->dec_ctx->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; - } else { - ist->next_dts += av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); - } - break; - case AVMEDIA_TYPE_VIDEO: - if (ist->framerate.num) { - // TODO: Remove work-around for c99-to-c89 issue 7 - AVRational time_base_q = AV_TIME_BASE_Q; - int64_t next_dts = av_rescale_q(ist->next_dts, time_base_q, av_inv_q(ist->framerate)); - ist->next_dts = av_rescale_q(next_dts + 1, av_inv_q(ist->framerate), time_base_q); - } 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; - 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; - } - break; - } - ist->pts = ist->dts; - ist->next_pts = ist->next_dts; - } 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) - continue; - - do_streamcopy(ist, ost, pkt); - } - - return !eof_reached; -} - -static enum AVPixelFormat get_format(AVCodecContext *s, const enum AVPixelFormat *pix_fmts) -{ - InputStream *ist = s->opaque; - const enum AVPixelFormat *p; - int ret; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(*p); - const AVCodecHWConfig *config = NULL; - int i; - - if (!(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) - break; - - if (ist->hwaccel_id == HWACCEL_GENERIC || - ist->hwaccel_id == HWACCEL_AUTO) { - for (i = 0;; i++) { - config = avcodec_get_hw_config(s->codec, i); - if (!config) - break; - if (!(config->methods & - AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) - continue; - if (config->pix_fmt == *p) - break; - } - } - if (config && config->device_type == ist->hwaccel_device_type) { - ret = hwaccel_decode_init(s); - if (ret < 0) { - if (ist->hwaccel_id == HWACCEL_GENERIC) { - av_log(NULL, AV_LOG_FATAL, - "%s hwaccel requested for input stream #%d:%d, " - "but cannot be initialized.\n", - av_hwdevice_get_type_name(config->device_type), - ist->file_index, ist->st->index); - return AV_PIX_FMT_NONE; - } - continue; - } - - ist->hwaccel_pix_fmt = *p; - break; - } - } - - return *p; -} - -static int init_input_stream(int ist_index, char *error, int error_len) -{ - int ret; - InputStream *ist = input_streams[ist_index]; - - if (ist->decoding_needed) { - const AVCodec *codec = ist->dec; - if (!codec) { - snprintf(error, error_len, "Decoder (codec %s) not found for input stream #%d:%d", - avcodec_get_name(ist->dec_ctx->codec_id), ist->file_index, ist->st->index); - return AVERROR(EINVAL); - } - - 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)) { - av_dict_set(&ist->decoder_opts, "compute_edt", "1", AV_DICT_DONT_OVERWRITE); - if (ist->decoding_needed & DECODING_FOR_FILTER) - av_log(NULL, AV_LOG_WARNING, "Warning using DVB subtitles for filtering and output at the same time is not fully supported, also see -compute_edt [0|1]\n"); - } - - /* Useful for subtitles retiming by lavf (FIXME), skipping samples in - * audio, and video decoders such as cuvid or mediacodec */ - ist->dec_ctx->pkt_timebase = ist->st->time_base; - - if (!av_dict_get(ist->decoder_opts, "threads", NULL, 0)) - av_dict_set(&ist->decoder_opts, "threads", "auto", 0); - /* Attached pics are sparse, therefore we would not want to delay their decoding till EOF. */ - if (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC) - av_dict_set(&ist->decoder_opts, "threads", "1", 0); - - ret = hw_device_setup_for_decode(ist); - if (ret < 0) { - snprintf(error, error_len, "Device setup failed for " - "decoder on input stream #%d:%d : %s", - ist->file_index, ist->st->index, av_err2str(ret)); - return ret; - } - - if ((ret = avcodec_open2(ist->dec_ctx, codec, &ist->decoder_opts)) < 0) { - if (ret == AVERROR_EXPERIMENTAL) - abort_codec_experimental(codec, 0); - - snprintf(error, error_len, - "Error while opening decoder for input stream " - "#%d:%d : %s", - ist->file_index, ist->st->index, av_err2str(ret)); - return ret; - } - assert_avoptions(ist->decoder_opts); - } - - ist->next_pts = AV_NOPTS_VALUE; - ist->next_dts = AV_NOPTS_VALUE; - - 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; - AVRational sar; - int i, ret; - uint32_t codec_tag = par_dst->codec_tag; - - av_assert0(ist && !ost->filter); - - ret = avcodec_parameters_to_context(ost->enc_ctx, ist->st->codecpar); - if (ret >= 0) - ret = av_opt_set_dict(ost->enc_ctx, &ost->encoder_opts); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, - "Error setting up codec context options.\n"); - return ret; - } - - ret = avcodec_parameters_from_context(par_src, ost->enc_ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, - "Error getting reference codec parameters.\n"); - return ret; - } - - 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; - } - - ret = avcodec_parameters_copy(par_dst, par_src); - if (ret < 0) - return ret; - - par_dst->codec_tag = codec_tag; - - if (!ost->frame_rate.num) - ost->frame_rate = ist->framerate; - - if (ost->frame_rate.num) - ost->st->avg_frame_rate = ost->frame_rate; - else - ost->st->avg_frame_rate = ist->st->avg_frame_rate; - - ret = avformat_transfer_internal_stream_timing_info(of->format, ost->st, ist->st, copy_tb); - if (ret < 0) - return ret; - - // copy timebase while removing common factors - if (ost->st->time_base.num <= 0 || ost->st->time_base.den <= 0) { - if (ost->frame_rate.num) - ost->st->time_base = av_inv_q(ost->frame_rate); - else - ost->st->time_base = av_add_q(av_stream_get_codec_timebase(ost->st), (AVRational){0, 1}); - } - - // copy estimated duration as a hint to the muxer - 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 (ist->st->nb_side_data) { - for (i = 0; i < ist->st->nb_side_data; i++) { - const AVPacketSideData *sd_src = &ist->st->side_data[i]; - uint8_t *dst_data; - - dst_data = av_stream_new_side_data(ost->st, sd_src->type, sd_src->size); - if (!dst_data) - return AVERROR(ENOMEM); - memcpy(dst_data, sd_src->data, sd_src->size); - } - } - - 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); - } - - switch (par_dst->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; - 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 " - "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; - ost->st->avg_frame_rate = ist->st->avg_frame_rate; - ost->st->r_frame_rate = ist->st->r_frame_rate; - break; - } - - ost->mux_timebase = ist->st->time_base; - - return 0; -} - -static void set_encoder_id(OutputFile *of, OutputStream *ost) -{ - const AVDictionaryEntry *e; - - 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 = av_mallocz(encoder_string_len); - if (!encoder_string) - exit_program(1); - - if (!(format_flags & AVFMT_FLAG_BITEXACT) && !(codec_flags & AV_CODEC_FLAG_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_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); - AVCodecContext *enc_ctx = ost->enc_ctx; - AVFormatContext *oc; - - if (ost->enc_timebase.num > 0) { - enc_ctx->time_base = ost->enc_timebase; - return; - } - - if (ost->enc_timebase.num < 0) { - if (ist) { - enc_ctx->time_base = ist->st->time_base; - return; - } - - oc = output_files[ost->file_index]->ctx; - av_log(oc, AV_LOG_WARNING, "Input stream data not available, using default time base\n"); - } - - enc_ctx->time_base = default_time_base; -} - -static int init_output_stream_encode(OutputStream *ost, AVFrame *frame) -{ - InputStream *ist = get_input_stream(ost); - 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); - - if (ist) { - dec_ctx = ist->dec_ctx; - } - - 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) { - ost->frame_rate = (AVRational){25, 1}; - av_log(NULL, 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); - } - - if (ost->max_frame_rate.num && - (av_q2d(ost->frame_rate) > av_q2d(ost->max_frame_rate) || - !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]; - } - // reduce frame rate for mpeg4 to be within the spec limits - if (enc_ctx->codec_id == AV_CODEC_ID_MPEG4) { - av_reduce(&ost->frame_rate.num, &ost->frame_rate.den, - ost->frame_rate.num, ost->frame_rate.den, 65535); - } - } - - switch (enc_ctx->codec_type) { - case AVMEDIA_TYPE_AUDIO: - enc_ctx->sample_fmt = av_buffersink_get_format(ost->filter->filter); - enc_ctx->sample_rate = av_buffersink_get_sample_rate(ost->filter->filter); - ret = av_buffersink_get_ch_layout(ost->filter->filter, &enc_ctx->ch_layout); - if (ret < 0) - return ret; - - if (ost->bits_per_raw_sample) - enc_ctx->bits_per_raw_sample = ost->bits_per_raw_sample; - else if (dec_ctx && ost->filter->graph->is_meta) - enc_ctx->bits_per_raw_sample = FFMIN(dec_ctx->bits_per_raw_sample, - av_get_bytes_per_sample(enc_ctx->sample_fmt) << 3); - - init_encoder_time_base(ost, av_make_q(1, enc_ctx->sample_rate)); - break; - - case AVMEDIA_TYPE_VIDEO: - init_encoder_time_base(ost, av_inv_q(ost->frame_rate)); - - if (!(enc_ctx->time_base.num && enc_ctx->time_base.den)) - enc_ctx->time_base = av_buffersink_get_time_base(ost->filter->filter); - 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"); - } - - enc_ctx->width = av_buffersink_get_w(ost->filter->filter); - enc_ctx->height = av_buffersink_get_h(ost->filter->filter); - enc_ctx->sample_aspect_ratio = ost->st->sample_aspect_ratio = - ost->frame_aspect_ratio.num ? // overridden by the -aspect cli option - av_mul_q(ost->frame_aspect_ratio, (AVRational){ enc_ctx->height, enc_ctx->width }) : - av_buffersink_get_sample_aspect_ratio(ost->filter->filter); - - enc_ctx->pix_fmt = av_buffersink_get_format(ost->filter->filter); - - if (ost->bits_per_raw_sample) - enc_ctx->bits_per_raw_sample = ost->bits_per_raw_sample; - else if (dec_ctx && ost->filter->graph->is_meta) - enc_ctx->bits_per_raw_sample = FFMIN(dec_ctx->bits_per_raw_sample, - av_pix_fmt_desc_get(enc_ctx->pix_fmt)->comp[0].depth); - - if (frame) { - enc_ctx->color_range = frame->color_range; - enc_ctx->color_primaries = frame->color_primaries; - enc_ctx->color_trc = frame->color_trc; - enc_ctx->colorspace = frame->colorspace; - enc_ctx->chroma_sample_location = frame->chroma_location; - } - - enc_ctx->framerate = ost->frame_rate; - - ost->st->avg_frame_rate = ost->frame_rate; - - // Field order: autodetection - if (frame) { - if (enc_ctx->flags & (AV_CODEC_FLAG_INTERLACED_DCT | AV_CODEC_FLAG_INTERLACED_ME) && - ost->top_field_first >= 0) - frame->top_field_first = !!ost->top_field_first; - - if (frame->interlaced_frame) { - if (enc_ctx->codec->id == AV_CODEC_ID_MJPEG) - enc_ctx->field_order = frame->top_field_first ? AV_FIELD_TT:AV_FIELD_BB; - else - enc_ctx->field_order = frame->top_field_first ? AV_FIELD_TB:AV_FIELD_BT; - } else - enc_ctx->field_order = AV_FIELD_PROGRESSIVE; - } - - // Field order: override - if (ost->top_field_first == 0) { - enc_ctx->field_order = AV_FIELD_BB; - } else if (ost->top_field_first == 1) { - 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; - } - break; - case AVMEDIA_TYPE_DATA: - break; - default: - abort(); - break; - } - - ost->mux_timebase = enc_ctx->time_base; - - return 0; -} - -static int init_output_stream(OutputStream *ost, AVFrame *frame, - char *error, int error_len) -{ - int ret = 0; - - if (ost->encoding_needed) { - const AVCodec *codec = ost->enc; - AVCodecContext *dec = NULL; - InputStream *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); - - ret = hw_device_setup_for_encode(ost); - if (ret < 0) { - snprintf(error, error_len, "Device setup failed for " - "encoder on output stream #%d:%d : %s", - ost->file_index, ost->index, av_err2str(ret)); - 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); - snprintf(error, error_len, - "Error while opening encoder for output stream #%d:%d - " - "maybe incorrect parameters such as bit_rate, rate, width or height", - ost->file_index, ost->index); - return ret; - } - if (ost->enc->type == AVMEDIA_TYPE_AUDIO && - !(ost->enc->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"); - - ret = avcodec_parameters_from_context(ost->st->codecpar, ost->enc_ctx); - if (ret < 0) { - av_log(NULL, AV_LOG_FATAL, - "Error initializing the output stream codec context.\n"); - exit_program(1); - } - - if (ost->enc_ctx->nb_coded_side_data) { - int i; - - for (i = 0; i < ost->enc_ctx->nb_coded_side_data; i++) { - const AVPacketSideData *sd_src = &ost->enc_ctx->coded_side_data[i]; - uint8_t *dst_data; - - dst_data = av_stream_new_side_data(ost->st, sd_src->type, sd_src->size); - if (!dst_data) - return AVERROR(ENOMEM); - memcpy(dst_data, sd_src->data, sd_src->size); - } - } - - /* - * Add global input side data. For now this is naive, and copies it - * from the input stream's global side data. All side data should - * really be funneled over AVFrame and libavfilter, then added back to - * packet side data, and then potentially using the first packet for - * global side data. - */ - if (ist) { - int i; - for (i = 0; i < ist->st->nb_side_data; i++) { - AVPacketSideData *sd = &ist->st->side_data[i]; - if (sd->type != AV_PKT_DATA_CPB_PROPERTIES) { - uint8_t *dst = av_stream_new_side_data(ost->st, sd->type, sd->size); - if (!dst) - 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); - } - } - } - - // copy timebase while removing common factors - if (ost->st->time_base.num <= 0 || ost->st->time_base.den <= 0) - ost->st->time_base = av_add_q(ost->enc_ctx->time_base, (AVRational){0, 1}); - - // 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) { - 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]); - 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; - 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++) { - 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(); - } - - /* 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); - } - goto dump_format; - } - - /* - * initialize stream copy and subtitle/data streams. - * Encoded AVFrame based streams will get initialized as follows: - * - when the first AVFrame is received in do_video_out - * - just before the first AVFrame is received in either transcode_step - * or reap_filters due to us requiring the filter chain buffer sink - * 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)) - continue; - - ret = init_output_stream_wrapper(output_streams[i], NULL, 0); - if (ret < 0) - goto dump_format; - } - - /* discard unused programs */ - for (i = 0; i < nb_input_files; i++) { - InputFile *ifile = input_files[i]; - for (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) { - discard = AVDISCARD_DEFAULT; - break; - } - p->discard = discard; - } - } - - /* 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++) { - 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 : "?", - ist->filters[j]->name); - if (nb_filtergraphs > 1) - av_log(NULL, AV_LOG_INFO, " (graph %d)", ist->filters[j]->graph->index); - av_log(NULL, AV_LOG_INFO, "\n"); - } - } - } - - for (i = 0; i < nb_output_streams; i++) { - ost = output_streams[i]; - - if (ost->attachment_filename) { - /* an attached file */ - av_log(NULL, AV_LOG_INFO, " File %s -> Stream #%d:%d\n", - ost->attachment_filename, ost->file_index, ost->index); - continue; - } - - if (ost->filter && !filtergraph_is_simple(ost->filter->graph)) { - /* output from a complex graph */ - av_log(NULL, AV_LOG_INFO, " %s", ost->filter->name); - if (nb_filtergraphs > 1) - 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 : "?"); - 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->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; - const char *decoder_name = "?"; - const char *in_codec_name = "?"; - const char *encoder_name = "?"; - const char *out_codec_name = "?"; - const AVCodecDescriptor *desc; - - if (in_codec) { - decoder_name = in_codec->name; - desc = avcodec_descriptor_get(in_codec->id); - if (desc) - in_codec_name = desc->name; - if (!strcmp(decoder_name, in_codec_name)) - decoder_name = "native"; - } - - if (out_codec) { - encoder_name = out_codec->name; - desc = avcodec_descriptor_get(out_codec->id); - if (desc) - out_codec_name = desc->name; - if (!strcmp(encoder_name, out_codec_name)) - encoder_name = "native"; - } - - av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))", - in_codec_name, decoder_name, - out_codec_name, encoder_name); - } - av_log(NULL, AV_LOG_INFO, "\n"); - } - - if (ret) { - av_log(NULL, AV_LOG_ERROR, "%s\n", error); - return ret; - } - - atomic_store(&transcode_init_done, 1); - - return 0; -} - -/* 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]); - continue; - } - - return 1; - } - - return 0; -} - -/** - * Select the output stream to process. - * - * @return selected output stream, or NULL if none available - */ -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); - - if (!ost->initialized && !ost->inputs_done) - return ost->unavailable ? NULL : ost; - - if (!ost->finished && opts < opts_min) { - opts_min = opts; - ost_min = ost->unavailable ? NULL : ost; - } - } - return ost_min; -} - -static void set_tty_echo(int on) -{ -#if HAVE_TERMIOS_H - struct termios tty; - if (tcgetattr(0, &tty) == 0) { - if (on) tty.c_lflag |= ECHO; - else tty.c_lflag &= ~ECHO; - tcsetattr(0, TCSANOW, &tty); - } -#endif -} - -static int check_keyboard_interaction(int64_t cur_time) -{ - int i, ret, key; - static int64_t last_time; - if (received_nb_signals) - return AVERROR_EXIT; - /* read_key() returns 0 on EOF */ - if (cur_time - last_time >= 100000) { - key = read_key(); - last_time = cur_time; - }else - key = -1; - if (key == 'q') { - av_log(NULL, AV_LOG_INFO, "\n\n[q] command received. Exiting.\n\n"); - return AVERROR_EXIT; - } - 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; - int k, n = 0; - fprintf(stderr, "\nEnter command: |all