PR #24424 opened by piotrasd URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24424 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24424.patch
This series adds a full-payload policy for DTS-HD, extends type-IV muxing to coreless ExSS packets, and preserves pending TrueHD/MLP MAT data when the muxer is finalized. It also fixes an overflow in the existing DTS-HD fallback counter. The changes are confined to `spdifenc`, its option documentation, and regression tests. They do not add audio decoding, re-encoding, or a new output format. ### 1. Make the DTS-HD fallback counter safe for its advertised option range `dtshd_fallback_time` accepts values up to `INT_MAX`, but the existing calculation multiplies the sample rate by that duration using signed `int` arithmetic. The resulting frame count is also stored in an `int`. For example, at 48 kHz, a fallback duration of `33554432` seconds overflows the multiplication. In the exercised case, the counter becomes zero and an oversized frame fails to mux instead of taking the configured core fallback. UndefinedBehaviorSanitizer also reports the overflow. The fix uses a 64-bit multiplication and a 64-bit frame counter. Casting only the multiplication is not sufficient: the resulting frame count can itself exceed `INT_MAX`. The default duration and ordinary fallback behavior are unchanged. ### 2. Allow callers to reject DTS-HD core fallback The muxer currently falls back to the core when a DTS-HD packet exceeds the capacity of the selected carrier. That is useful when maintaining playback is more important than retaining the full encoded payload, but it does not suit callers that require complete DTS-HD transmission. The new `dtshd_strict` option makes that choice explicit: - `0`, the default: retain the existing fallback controlled by `dtshd_fallback_time`. - `1`: return an error if the complete DTS-HD packet cannot fit, rather than emit only its core. For the existing type-IV wrapper, the capacity check includes the eight-byte IEC preamble and the twelve-byte DTS-HD wrapper. The option does not raise `dtshd_rate`, change the carrier period, or make an oversized packet fit. This is an opt-in policy, not a claim that IEC requires strict mode or that FFmpeg's existing fallback is inherently invalid. ### 3. Support coreless ExSS packets with correctly interpreted timing The current muxer rejects `DCA_SYNCWORD_SUBSTREAM` packets as stray DTS-HD frames. However, extension-only streams are a defined DTS use case. [ETSI TS 102 114 V1.6.1](https://www.etsi.org/deliver/etsi_ts/102100_102199/102114/01.06.01_60/ts_102114v010601p.pdf), section 7.2, permits streams consisting of extension substreams when compatibility with older DTS decoders is not required. Section 7.5.2 describes extension-substream reference-clock and duration information; Table 7-14 describes the asset coding modes. The change accepts ExSS packets when type-IV output is selected and the caller supplies a usable packet duration. It does not infer duration from the sync word or add a decoder dependency. For coreless input, the carrier period is derived from elapsed time: ```text period = dtshd_rate × packet_duration × time_base.num / time_base.den ``` This follows the [AVPacket duration contract](https://github.com/FFmpeg/FFmpeg/blob/08cd8df29d17b4302a105aab80a41b885dd42663/libavcodec/packet.h): duration is expressed in the stream time base, not necessarily in audio samples. For example, `512` ticks at `1/48000` and `32` ticks at `1/3000` both represent approximately 10.667 ms. At a logical carrier rate of 768000 frames per second, both must produce an 8192-frame period, or 32768 bytes in the muxer's representation. The calculation checks for overflow, requires an exact integer period, and rejects periods outside the existing type-IV subtype set. Missing duration is not guessed. Core-bearing packets continue to use their parsed core timing; their period calculation also checks exact representability rather than silently truncating. The ExSS case is kept separate from little-endian and 14-bit core framing. Those forms still require normalization for HD output and retain their existing rejection in that mode. A zero parsed core size is not used by the dispatch as proof that a packet is ExSS. [IEC 61937-2:2021, Table 2](https://cdn.standards.iteh.ai/samples/103894/b95748149ab74305bb621367b9a2df03/IEC-61937-2-2021.pdf) identifies DTS type IV as data type 17, with `Pd` expressed in bytes. [IEC 61937-5](https://www.sis.se/api/document/preview/567818/) describes DTS carriage and the type-IV repetition-period field. This change retains FFmpeg's existing subtype mapping, wrapper, and length-code alignment; it does not introduce a different encapsulation. ### 4. Preserve pending MAT data at finalization The current TrueHD/MLP path can retain both completed MAT frames in its ready queue and coded data in an unfinished active frame. `spdifenc` has no trailer callback, so data remaining in either place is discarded when the context is closed. The completed-frame queue and timing behavior added by [2db563fac2ad9e8509d4c5a699462eead5c32b8c](https://github.com/FFmpeg/FFmpeg/commit/2db563fac2ad9e8509d4c5a699462eead5c32b8c) are retained. In particular, this series does not change the existing one-completed-MAT-per-input-packet behavior or replace the seamless-branch timing logic. At trailer time, the muxer now: 1. Writes all queued complete MAT frames in FIFO order. 2. Finishes the active frame if it contains coded payload, using the existing MAT markers and zero-fill layout. 3. Writes that final frame through the same ready-queue emission path. An active buffer containing only structural markers or timing padding does not generate an extra frame. Output errors are propagated, and the drain stops on failure. [IEC 61937-9:2017, section 5.3.1](https://cdn.standards.iteh.ai/samples/22795/80fd06255a29447e9a2d41989a9cfa47/IEC-61937-9-2017.pdf) requires each MAT burst payload to contain a single complete MAT frame, rather than fragments of one MAT frame spread across IEC bursts. Table 4 gives a repetition period of 15360 logical IEC60958 frames for the default Pc bit-12-zero mode. In FFmpeg's representation, that is 61440 bytes per burst period: ```text 8-byte preamble + 61424-byte MAT payload + 8-byte outer stuffing ``` That complete-frame requirement is distinct from the particular algorithm used to finish the MAT interior. This patch follows FFmpeg's existing marker and zero-fill convention; it does not claim that the inspected IEC text mandates that EOF padding recipe. The [Dolby TrueHD high-level bitstream description, 7 February 2018](https://web.archive.org/web/20240111175244/https://developer.dolby.com/globalassets/technology/dolby-truehd/dolbytruehdhighlevelbitstreamdescription.pdf), sections 2.6–2.7 and 4.1, explains FIFO buffering and `input_timing`/`output_timing`. Those are relevant to preserving the current packing cadence. The document is not a complete specification of the MAT interior, and the series does not treat it as one. ## Before and after The baseline below is **unchanged upstream master `08cd8df29d17b4302a105aab80a41b885dd42663`**, not an earlier version of this patch. The same encoded input and muxer parameters were used on both sides. Byte counts are carrier output after `av_write_trailer()`. | Case | Unchanged master | With the patch | What this demonstrates | |---|---|---|---| | Coreless ExSS, duration `512`, time base `1/48000`, carrier rate `768000` | `AVERROR_INVALIDDATA`; 0 bytes | Success; 32768 bytes, containing the complete 496-byte input payload | Adds the previously rejected coreless case | | Same ExSS payload, equivalent duration `32` at `1/3000` | `AVERROR_INVALIDDATA`; 0 bytes | Success; 32768 bytes, containing the complete payload | Timing is interpreted in stream time-base units | | Oversized DTS-HD AU with default fallback: 2508-byte input, 2012-byte core, 2048-byte carrier budget | Success; 2048-byte burst containing the 2012-byte core | Same result | Preserves existing default fallback | | Same oversized AU with `dtshd_strict=1` | Option unavailable | `AVERROR(EINVAL)`; 0 bytes | Allows a caller to reject payload reduction explicitly | | TrueHD, empty input | 0 bytes | 0 bytes | Does not add a payload-free final burst | | TrueHD, first input AU only | 0 bytes | 61440 bytes | Pending coded data is no longer discarded at finalization | | TrueHD, first 25 input AUs | 61440 bytes | 122880 bytes | Emits the remaining MAT data at EOF | | TrueHD, first 120 input AUs | 245760 bytes | 307200 bytes | Retains the final partial MAT after multiple complete bursts | The TrueHD rows use prefixes of `truehd/atmos.thd`. The ExSS payload was extracted from the first access unit of `dts/master_audio_7.1_24bit.dts`. These are libavformat API measurements; they do not depend on a particular playback device. For example, the 25-AU run produced: ```text before trailer after trailer unchanged master 61440 61440 patched master 61440 122880 ``` Output size alone would not establish correctness. The complete `truehd/atmos.thd` sample contains **128 access units**. After removing the carrier and MAT structural markers, an ordered byte-for-byte comparison against the source access units gives: | Payload check | Unchanged master | With the patch | |---|---:|---:| | Complete source AUs found in order | 120 of 128 | 128 of 128 | | Carrier output size | 307200 bytes | 368640 bytes | The patch therefore retains the final **eight access units**, not just an additional block of padding. All 128 source AUs are preserved byte-for-byte and in order, with only zero fill between them in the extracted MAT body. This is a bitstream-retention check, not an audio-decoder or receiver test. The sample-free API tests additionally check complete burst lengths, `Pc`/`Pd`, marker placement, queue order, and the absence of an extra burst at exact completion. The fallback-counter fix has a separate before/after reproduction. With `dtshd_fallback_time=33554432` and the oversized 48 kHz DTS-HD packet, the pre-fix counter calculation produced a signed-overflow diagnostic and `EINVAL` with no output. After the counter fix, it produces the expected 2048-byte core-bearing burst without sanitizer diagnostics. This comparison was run before and after that fix in the candidate tree; the overflowing calculation is inherited from the upstream baseline. It is not presented as a separate execution of the full baseline tree. ## Regression coverage Tested against master `08cd8df29d17b4302a105aab80a41b885dd42663`. The new sample-free libavformat API tests cover: - Default core fallback, strict rejection, fitting full-payload output, and large fallback durations. - Equivalent packet durations expressed in different time bases. - Missing or invalid duration, nonrepresentable periods, arithmetic bounds, and carrier overflow. - Rejection of unsupported core framing in HD mode, in both output byte orders. - Empty MAT output, partial endings at and beyond marker boundaries, exact completion, and payload spanning MAT frames. - FIFO draining when one input packet completes more than one MAT frame. - TrueHD and the shared MLP path, payload preservation, and finalization I/O errors. The synthetic headers exercise muxer behavior; they are not presented as decodable audio fixtures. Existing sample-based FATE cases supplement them, including the TrueHD branch-padding case. All 14 existing SPDIF FATE cases and both new API groups pass. The new API groups also pass with AddressSanitizer and UndefinedBehaviorSanitizer. An additional local sanitizer probe passed 274 capacity, time-base, byte-order, and MAT-marker boundary checks; that probe is supplementary evidence, not 274 new FATE tests. ### Why three FATE references change Finalization now adds one complete burst for each of these samples: | Sample | Previous output | New output | Difference | |---|---:|---:|---:| | `truehd/atmos.thd` | 307200 bytes | 368640 bytes | 61440 bytes | | `truehd/spdifenc-branch-padding.thd` | 307200 bytes | 368640 bytes | 61440 bytes | | `lossless-audio/luckynight-partial.mlp` | 22917120 bytes | 22978560 bytes | 61440 bytes | For all three samples, the entire previous output was verified to be an unchanged prefix of the new output before updating the references. The existing DTS, AAC, AC-3, E-AC-3, and MPEG-audio references are unchanged. The complete-file comparison can be reproduced with separate baseline and patched builds and the same FATE sample: ```sh SAMPLE="$SAMPLES/truehd/atmos.thd" "$BEFORE/ffmpeg" -v error -nostdin -i "$SAMPLE" \ -map 0:a:0 -c:a copy -f spdif before.spdif "$AFTER/ffmpeg" -v error -nostdin -i "$SAMPLE" \ -map 0:a:0 -c:a copy -f spdif after.spdif python3 - <<'PY' from pathlib import Path before = Path("before.spdif").read_bytes() after = Path("after.spdif").read_bytes() assert after.startswith(before), "Previously emitted bytes changed" assert len(after) - len(before) == 61440, "Expected one additional MAT burst" print(f"before={len(before)} after={len(after)} added={len(after) - len(before)}") PY ``` For `truehd/atmos.thd`, the recorded sizes are: ```text before=307200 after=368640 added=61440 ``` Repeat with the branch-padding and MLP samples above to check that their previously emitted data also remains unchanged. This size/prefix check complements the payload and framing assertions; it is not a substitute for them. ## Compatibility and scope - The existing DTS-HD fallback remains the default; strict mode is opt-in. - The exact-period check is stricter than the old truncating calculation for unusual carrier-rate combinations. This is not a claim that every possible default invocation is byte-for-byte unchanged. - Existing DTS fallback bounds checks are retained, including the `core_size <= packet size` guard from [6f80e2765492700622596af720534cef33dd31b4](https://github.com/FFmpeg/FFmpeg/commit/6f80e2765492700622596af720534cef33dd31b4). - No general DTS framing-normalization rewrite or new MAT format is included. - The standards links support the specific framing and timing points described above. Some linked IEC documents are previews; the tests are not a complete IEC/Dolby conformance assessment or a claim of compatibility with every receiver. ## Commits 1. `avformat/spdifenc: avoid overflow in the DTS-HD fallback counter` 2. `avformat/spdifenc: add an option to reject DTS-HD core fallback` 3. `avformat/spdifenc: support coreless DTS-HD type-IV output` 4. `avformat/spdifenc: preserve pending MAT data on finalization` From 964f539761c0e7c2a3906717628d4fd16b6f601b Mon Sep 17 00:00:00 2001 From: piotrasd <[email protected]> Date: Wed, 9 Sep 2026 00:34:09 +0100 Subject: [PATCH 1/4] avformat/spdifenc: avoid overflow in the DTS-HD fallback counter The fallback duration accepts values up to INT_MAX, but both the sample-rate multiplication and the resulting frame counter use int. At 48 kHz, a duration of 33554432 seconds overflows the multiplication and can make an oversized packet fail instead of using core fallback. Use 64-bit arithmetic and a 64-bit counter so the advertised option range is representable. Signed-off-by: piotrasd <[email protected]> --- libavformat/spdifenc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c index 0b8d8c5886..030b8c12ac 100644 --- a/libavformat/spdifenc.c +++ b/libavformat/spdifenc.c @@ -90,7 +90,7 @@ typedef struct IEC61937Context { int hd_buf_next_ready_idx; ///< oldest completed truehd MAT buffer ready to write (truehd) int hd_buf_ready_count; ///< number of completed TrueHD MAT buffers ready to write (truehd) - int dtshd_skip; ///< counter used for skipping DTS-HD frames + int64_t dtshd_skip; ///< counter used for skipping DTS-HD frames uint16_t truehd_prev_time; ///< input_timing from the last frame int truehd_prev_size; ///< previous frame size in bytes, including any MAT codes @@ -235,7 +235,7 @@ static int spdif_header_dts4(AVFormatContext *s, AVPacket *pkt, int core_size, av_log(s, AV_LOG_WARNING, "DTS-HD bitrate too high, " "temporarily sending core only\n"); if (ctx->dtshd_fallback > 0) - ctx->dtshd_skip = sample_rate * ctx->dtshd_fallback / (blocks << 5); + ctx->dtshd_skip = (int64_t)sample_rate * ctx->dtshd_fallback / (blocks << 5); else /* skip permanently (dtshd_fallback == -1) or just once * (dtshd_fallback == 0) */ -- 2.52.0 From df733f8168e177f9a66537165d6e7c7030874ad3 Mon Sep 17 00:00:00 2001 From: piotrasd <[email protected]> Date: Wed, 9 Sep 2026 00:34:10 +0100 Subject: [PATCH 2/4] avformat/spdifenc: add an option to reject DTS-HD core fallback Add dtshd_strict for callers that require the complete DTS-HD payload. Return an error when the packet and its wrapper exceed the selected carrier capacity, rather than falling back to the core. Keep the existing fallback policy as the default. The option does not increase the carrier rate or introduce a runtime policy-change contract. Signed-off-by: piotrasd <[email protected]> --- doc/muxers.texi | 16 ++++++++++++++++ libavformat/spdifenc.c | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/doc/muxers.texi b/doc/muxers.texi index 99584e2b8d..af1dffc648 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -3752,6 +3752,22 @@ Specify whether to remove all fragments when finished. Default 0 (do not remove) @end table +@section spdif + +IEC 61937 muxer for encapsulating compressed audio bitstreams. + +@subsection Options + +@table @option +@item dtshd_strict @var{boolean} +Return an error instead of falling back to the DTS core when a complete +DTS-HD frame exceeds the capacity of the carrier selected by +@option{dtshd_rate}. This option does not increase the carrier rate. + +The default is @code{0}, which retains the core fallback controlled by +@option{dtshd_fallback_time}. +@end table + @anchor{streamhash} @section streamhash diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c index 030b8c12ac..dcf5461ab1 100644 --- a/libavformat/spdifenc.c +++ b/libavformat/spdifenc.c @@ -102,6 +102,7 @@ typedef struct IEC61937Context { /* AVOptions: */ int dtshd_rate; int dtshd_fallback; + int dtshd_strict; #define SPDIF_FLAG_BIGENDIAN 0x01 int spdif_flags; @@ -115,6 +116,7 @@ static const AVOption options[] = { { "be", "output in big-endian format (for use as s16be)", 0, AV_OPT_TYPE_CONST, {.i64 = SPDIF_FLAG_BIGENDIAN}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, .unit = "spdif_flags" }, { "dtshd_rate", "mux complete DTS frames in HD mode at the specified IEC958 rate (in Hz, default 0=disabled)", offsetof(IEC61937Context, dtshd_rate), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 768000, AV_OPT_FLAG_ENCODING_PARAM }, { "dtshd_fallback_time", "min secs to strip HD for after an overflow (-1: till the end, default 60)", offsetof(IEC61937Context, dtshd_fallback), AV_OPT_TYPE_INT, {.i64 = 60}, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, +{ "dtshd_strict", "fail instead of stripping DTS-HD when an HD burst exceeds carrier capacity", offsetof(IEC61937Context, dtshd_strict), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM }, { NULL }, }; @@ -231,6 +233,11 @@ static int spdif_header_dts4(AVFormatContext *s, AVPacket *pkt, int core_size, * Audio stream into 192kHz IEC 60958 (which may or may not fit). */ if (sizeof(dtshd_start_code) + 2 + pkt_size > ctx->pkt_offset - BURST_HEADER_SIZE && core_size) { + if (ctx->dtshd_strict) { + av_log(s, AV_LOG_ERROR, "DTS-HD frame exceeds carrier capacity; " + "full-payload transmission is required\n"); + return AVERROR(EINVAL); + } if (!ctx->dtshd_skip) av_log(s, AV_LOG_WARNING, "DTS-HD bitrate too high, " "temporarily sending core only\n"); -- 2.52.0 From c1a4742298175b8bea9709dc315ab456e7eabb80 Mon Sep 17 00:00:00 2001 From: piotrasd <[email protected]> Date: Wed, 9 Sep 2026 00:34:10 +0100 Subject: [PATCH 3/4] avformat/spdifenc: support coreless DTS-HD type-IV output ExSS-only streams are permitted by ETSI TS 102 114 section 7.2, but the muxer currently rejects their sync word as a stray DTS-HD frame. Accept ExSS packets in type-IV mode when their packet duration maps exactly to a supported carrier period. Interpret duration in the stream time base rather than as an audio sample count, and check arithmetic bounds and capacity before emitting a burst. Keep unsupported little-endian and 14-bit core framing separate from ExSS. A zero parsed core size alone does not identify a coreless packet. Add sample-free API tests for fallback policy, large fallback durations, payload preservation, time-base equivalence and rejected inputs. Reference: https://www.etsi.org/deliver/etsi_ts/102100_102199/102114/01.06.01_60/ts_102114v010601p.pdf Signed-off-by: piotrasd <[email protected]> --- doc/muxers.texi | 5 + libavformat/Makefile | 1 + libavformat/spdifenc.c | 82 ++++++++++---- libavformat/tests/.gitignore | 1 + libavformat/tests/spdif_muxer.c | 192 ++++++++++++++++++++++++++++++++ tests/fate/spdif.mak | 10 +- 6 files changed, 269 insertions(+), 22 deletions(-) create mode 100644 libavformat/tests/spdif_muxer.c diff --git a/doc/muxers.texi b/doc/muxers.texi index af1dffc648..572a10753b 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -3756,6 +3756,11 @@ Specify whether to remove all fragments when finished. Default 0 (do not remove) IEC 61937 muxer for encapsulating compressed audio bitstreams. +Coreless DTS-HD input requires type-IV output selected with +@option{dtshd_rate}. When using libavformat, provide a positive packet +duration in the stream time base that maps exactly to a supported carrier +repetition period. + @subsection Options @table @option diff --git a/libavformat/Makefile b/libavformat/Makefile index 038e0afd41..84356dcd60 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -792,6 +792,7 @@ TESTPROGS-$(CONFIG_FIFO_MUXER) += $(FIFO-MUXER-TESTPROGS-yes) TESTPROGS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpdh TESTPROGS-$(CONFIG_HTTP_PROTOCOL) += http TESTPROGS-$(CONFIG_NETWORK) += noproxy +TESTPROGS-$(CONFIG_SPDIF_MUXER) += spdif_muxer TESTPROGS-$(CONFIG_SRTP) += srtp TESTPROGS-$(CONFIG_IMF_DEMUXER) += imf diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c index dcf5461ab1..839557805d 100644 --- a/libavformat/spdifenc.c +++ b/libavformat/spdifenc.c @@ -197,27 +197,63 @@ static int spdif_header_dts4(AVFormatContext *s, AVPacket *pkt, int core_size, IEC61937Context *ctx = s->priv_data; static const char dtshd_start_code[10] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe }; int pkt_size = pkt->size; + int frame_samples = 0; + int64_t period_num; + int64_t period_val; int period; int subtype; - if (!core_size) { - av_log(s, AV_LOG_ERROR, "HD mode not supported for this format\n"); + if (core_size) { + if (sample_rate <= 0 || blocks <= 0) { + av_log(s, AV_LOG_ERROR, "Unknown DTS frame timing for HD\n"); + return AVERROR_INVALIDDATA; + } + /* Core timing is bitstream-derived: blocks are 32-sample units. */ + frame_samples = blocks << 5; + period_num = (int64_t)ctx->dtshd_rate * frame_samples; + if (period_num % sample_rate) { + av_log(s, AV_LOG_ERROR, "DTS frame timing does not map to an HD period\n"); + return AVERROR(EINVAL); + } + period_val = period_num / sample_rate; + } else { + AVRational tb = s->streams[0]->time_base; + int64_t num; + + /* Coreless ExSS: AVPacket.duration is in stream time_base units. */ + if (pkt->duration <= 0 || tb.num <= 0 || tb.den <= 0) { + av_log(s, AV_LOG_ERROR, "Unknown DTS frame timing for HD\n"); + return AVERROR_INVALIDDATA; + } + if (pkt->duration > INT64_MAX / tb.num) { + av_log(s, AV_LOG_ERROR, "DTS frame period exceeds HD limits\n"); + return AVERROR(EINVAL); + } + num = pkt->duration * (int64_t)tb.num; + if (ctx->dtshd_rate > 0 && num > INT64_MAX / ctx->dtshd_rate) { + av_log(s, AV_LOG_ERROR, "DTS frame period exceeds HD limits\n"); + return AVERROR(EINVAL); + } + num *= ctx->dtshd_rate; + if (num % tb.den) { + av_log(s, AV_LOG_ERROR, "DTS frame timing does not map to an HD period\n"); + return AVERROR(EINVAL); + } + period_val = num / tb.den; + } + + if (period_val <= 0 || period_val > INT_MAX) { + av_log(s, AV_LOG_ERROR, "DTS frame period exceeds HD limits\n"); return AVERROR(EINVAL); } - - if (!sample_rate) { - av_log(s, AV_LOG_ERROR, "Unknown DTS sample rate for HD\n"); - return AVERROR_INVALIDDATA; - } - - period = ctx->dtshd_rate * (blocks << 5) / sample_rate; + period = (int)period_val; subtype = spdif_dts4_subtype(period); if (subtype < 0) { av_log(s, AV_LOG_ERROR, "Specified HD rate of %d Hz would require an " "impossible repetition period of %d for the current DTS stream" - " (blocks = %d, sample rate = %d)\n", ctx->dtshd_rate, period, - blocks << 5, sample_rate); + " (frame_samples = %d, sample rate = %d)\n", ctx->dtshd_rate, period, + frame_samples, sample_rate); return AVERROR(EINVAL); } @@ -230,10 +266,11 @@ static int spdif_header_dts4(AVFormatContext *s, AVPacket *pkt, int core_size, * repetition period setting, strip DTS-HD until a good amount * of consecutive non-overflowing HD frames have been observed. * This generally only happens if the caller is cramming a Master - * Audio stream into 192kHz IEC 60958 (which may or may not fit). */ + * Audio stream into 192kHz IEC 60958 (which may or may not fit). + * Opt-in dtshd_strict rejects stripping; coreless has nothing to strip. */ if (sizeof(dtshd_start_code) + 2 + pkt_size - > ctx->pkt_offset - BURST_HEADER_SIZE && core_size) { - if (ctx->dtshd_strict) { + > ctx->pkt_offset - BURST_HEADER_SIZE) { + if (ctx->dtshd_strict || !core_size) { av_log(s, AV_LOG_ERROR, "DTS-HD frame exceeds carrier capacity; " "full-payload transmission is required\n"); return AVERROR(EINVAL); @@ -242,7 +279,7 @@ static int spdif_header_dts4(AVFormatContext *s, AVPacket *pkt, int core_size, av_log(s, AV_LOG_WARNING, "DTS-HD bitrate too high, " "temporarily sending core only\n"); if (ctx->dtshd_fallback > 0) - ctx->dtshd_skip = (int64_t)sample_rate * ctx->dtshd_fallback / (blocks << 5); + ctx->dtshd_skip = (int64_t)sample_rate * ctx->dtshd_fallback / frame_samples; else /* skip permanently (dtshd_fallback == -1) or just once * (dtshd_fallback == 0) */ @@ -304,10 +341,9 @@ static int spdif_header_dts(AVFormatContext *s, AVPacket *pkt) ctx->extra_bswap = 1; break; case DCA_SYNCWORD_SUBSTREAM: - /* We only handle HD frames that are paired with core. However, - sometimes DTS-HD streams with core have a stray HD frame without - core in the beginning of the stream. */ - av_log(s, AV_LOG_ERROR, "stray DTS-HD frame\n"); + if (ctx->dtshd_rate) + return spdif_header_dts4(s, pkt, 0, 0, 0); + av_log(s, AV_LOG_ERROR, "DTS-HD frame requires type IV output\n"); return AVERROR_INVALIDDATA; default: av_log(s, AV_LOG_ERROR, "bad DTS syncword 0x%"PRIx32"\n", syncword_dts); @@ -315,9 +351,13 @@ static int spdif_header_dts(AVFormatContext *s, AVPacket *pkt) } blocks++; - if (ctx->dtshd_rate) - /* DTS type IV output requested */ + if (ctx->dtshd_rate) { + if (syncword_dts != DCA_SYNCWORD_CORE_BE) { + av_log(s, AV_LOG_ERROR, "DTS-HD output requires big-endian 16-bit core framing\n"); + return AVERROR(EINVAL); + } return spdif_header_dts4(s, pkt, core_size, sample_rate, blocks); + } switch (blocks) { case 512 >> 5: ctx->data_type = IEC61937_DTS1; break; diff --git a/libavformat/tests/.gitignore b/libavformat/tests/.gitignore index 2be3cc8fac..5ee65c3054 100644 --- a/libavformat/tests/.gitignore +++ b/libavformat/tests/.gitignore @@ -10,3 +10,4 @@ /url /seek_utils /http +/spdif_muxer diff --git a/libavformat/tests/spdif_muxer.c b/libavformat/tests/spdif_muxer.c new file mode 100644 index 0000000000..a3f8a09bf1 --- /dev/null +++ b/libavformat/tests/spdif_muxer.c @@ -0,0 +1,192 @@ +/* + * IEC 61937 muxer API regression tests + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <string.h> + +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavformat/avformat.h" + +#define CHECK(c) do { if (!(c)) { \ + fprintf(stderr, "line %d: %s\n", __LINE__, #c); exit(1); \ +} } while (0) + +static AVFormatContext *open_mux(enum AVCodecID codec, AVRational tb, + int rate, int strict, int be) +{ + AVFormatContext *s = NULL; + AVStream *st; + CHECK(avformat_alloc_output_context2(&s, NULL, "spdif", NULL) >= 0); + st = avformat_new_stream(s, NULL); + CHECK(st); + st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + st->codecpar->codec_id = codec; + st->codecpar->sample_rate = 48000; + av_channel_layout_default(&st->codecpar->ch_layout, 2); + st->time_base = tb; + if (codec == AV_CODEC_ID_DTS) { + CHECK(av_opt_set_int(s->priv_data, "dtshd_rate", rate, 0) >= 0); + if (strict >= 0) + CHECK(av_opt_set_int(s->priv_data, "dtshd_strict", strict, 0) >= 0); + } + if (be) + CHECK(av_opt_set(s->priv_data, "spdif_flags", "be", 0) >= 0); + CHECK(avio_open_dyn_buf(&s->pb) >= 0); + CHECK(avformat_write_header(s, NULL) >= 0); + return s; +} + +static int append(AVFormatContext *s, const uint8_t *data, int size, int64_t duration) +{ + AVPacket *p = av_packet_alloc(); + int ret; + CHECK(p && av_new_packet(p, size) >= 0); + memcpy(p->data, data, size); + p->duration = duration; + ret = av_write_frame(s, p); + av_packet_free(&p); + return ret; +} + +static int close_mux(AVFormatContext *s, uint8_t **out) +{ + int size; + CHECK(av_write_trailer(s) >= 0); + size = avio_close_dyn_buf(s->pb, out); + avformat_free_context(s); + return size; +} + +static void unswap(uint8_t *data, int size) +{ + for (int i = 0; i + 1 < size; i += 2) { + uint8_t t = data[i]; + data[i] = data[i + 1]; + data[i + 1] = t; + } +} + +static void check_dts(uint8_t *burst, int size, const uint8_t *au, int au_size, + int period, int subtype) +{ + static const uint8_t prefix[10] = { 1, 0, 0, 0, 0, 0, 0, 0, 0xfe, 0xfe }; + CHECK(size == period); + CHECK(AV_RL16(burst) == 0xf872 && AV_RL16(burst + 2) == 0x4e1f); + CHECK(AV_RL16(burst + 4) == (0x11 | subtype << 8)); + CHECK(AV_RL16(burst + 6) == ((au_size + 12 + 23) & ~15) - 8); + unswap(burst + 8, size - 8); + CHECK(!memcmp(burst + 8, prefix, sizeof(prefix))); + CHECK(AV_RB16(burst + 18) == au_size); + CHECK(!memcmp(burst + 20, au, au_size)); + for (int i = 20 + au_size; i < size; i++) + CHECK(!burst[i]); +} + +static void test_dts(void) +{ + static const uint8_t core_header[] = { + 0x7f, 0xfe, 0x80, 0x01, 0x7c, 0x3c, 0x7d, 0xb0, 0x34 + }; + uint8_t core[2508], exss[496], *out, *reference; + AVFormatContext *s; + int size, ref_size; + + memset(core, 0x5a, sizeof(core)); + memcpy(core, core_header, sizeof(core_header)); + AV_WB32(core + 2012, 0x64582025); + memcpy(exss, core + 2012, sizeof(exss)); + + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 48000}, 48000, -1, 0); + CHECK(append(s, core, sizeof(core), 512) >= 0); + size = close_mux(s, &out); + check_dts(out, size, core, 2012, 2048, 0); + av_free(out); + + /* The advertised fallback duration must not overflow its frame counter. */ + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 48000}, 48000, 0, 0); + CHECK(av_opt_set_int(s->priv_data, "dtshd_fallback_time", 33554432, 0) >= 0); + CHECK(append(s, core, sizeof(core), 512) >= 0); + size = close_mux(s, &out); + check_dts(out, size, core, 2012, 2048, 0); + av_free(out); + + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 48000}, 48000, 1, 0); + CHECK(append(s, core, sizeof(core), 512) == AVERROR(EINVAL)); + CHECK(close_mux(s, &out) == 0); + av_free(out); + + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 48000}, 768000, 1, 0); + CHECK(append(s, core, sizeof(core), 512) >= 0); + size = close_mux(s, &out); + check_dts(out, size, core, sizeof(core), 32768, 4); + av_free(out); + + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 48000}, 768000, 1, 0); + CHECK(append(s, exss, sizeof(exss), 512) >= 0); + ref_size = close_mux(s, &reference); + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 3000}, 768000, 1, 0); + CHECK(append(s, exss, sizeof(exss), 32) >= 0); + size = close_mux(s, &out); + CHECK(size == ref_size && !memcmp(out, reference, size)); + check_dts(out, size, exss, sizeof(exss), 32768, 4); + av_free(out); + av_free(reference); + + /* Unknown duration, fractional period, unsupported period, and overflow. */ + for (int i = 0; i < 4; i++) { + static const int64_t durations[] = { 0, 1, 3, INT64_MAX }; + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 90000}, 768000, 0, 0); + CHECK(append(s, exss, sizeof(exss), durations[i]) < 0); + CHECK(close_mux(s, &out) == 0); + av_free(out); + } + + /* Coreless data cannot fit a core-only capacity fallback. */ + AV_WB32(core, 0x64582025); + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 48000}, 48000, 0, 0); + CHECK(append(s, core, sizeof(core), 512) < 0); + CHECK(close_mux(s, &out) == 0); + av_free(out); + + /* These core framings are not ExSS and still need HD normalization. */ + for (int i = 0; i < 3; i++) { + static const uint32_t sync[] = { 0xfe7f0180, 0x1fffe800, 0xff1f00e8 }; + AV_WB32(core, sync[i]); + for (int be = 0; be < 2; be++) { + s = open_mux(AV_CODEC_ID_DTS, (AVRational){1, 48000}, 768000, 0, be); + CHECK(append(s, core, sizeof(core), 512) < 0); + CHECK(close_mux(s, &out) == 0); + av_free(out); + } + } +} + +int main(int argc, char **argv) +{ + av_log_set_level(AV_LOG_QUIET); + CHECK(argc == 2); + if (!strcmp(argv[1], "dts")) + test_dts(); + else + CHECK(0); + return 0; +} diff --git a/tests/fate/spdif.mak b/tests/fate/spdif.mak index 9928096883..9d48ce2747 100644 --- a/tests/fate/spdif.mak +++ b/tests/fate/spdif.mak @@ -1,3 +1,11 @@ +FATE_SPDIF_API-$(CONFIG_SPDIF_MUXER) += fate-spdif-dts-api + +fate-spdif-dts-api: libavformat/tests/spdif_muxer$(EXESUF) +fate-spdif-dts-api: CMD = run libavformat/tests/spdif_muxer$(EXESUF) dts +fate-spdif-dts-api: CMP = null + +FATE-$(CONFIG_AVFORMAT) += $(FATE_SPDIF_API-yes) + # This pads the AAC frames to 16 bit words (the actual size is # still available in the ADTS headers). FATE_SPDIF_REMUX-$(call ALLYES, AAC_DEMUXER AAC_DECODER) += fate-spdif-aac-remux @@ -47,4 +55,4 @@ fate-spdif-truehd-branch-padding: CMD = md5 -i $(TARGET_SAMPLES)/truehd/spdifenc # and switch the md5 tests to remux tests? FATE_SPDIF-$(call REMUX, SPDIF) += $(FATE_SPDIF_REMUX-yes) FATE_SAMPLES_FFMPEG += $(FATE_SPDIF-yes) -fate-spdif: $(FATE_SPDIF-yes) +fate-spdif: $(FATE_SPDIF-yes) $(FATE_SPDIF_API-yes) -- 2.52.0 From 75679cb578d5d3cf8cd282592cbb0af5cf2707b0 Mon Sep 17 00:00:00 2001 From: piotrasd <[email protected]> Date: Wed, 9 Sep 2026 00:34:10 +0100 Subject: [PATCH 4/4] avformat/spdifenc: preserve pending MAT data on finalization Drain completed TrueHD/MLP MAT frames in FIFO order at trailer time. Then finish the active frame if it contains coded payload, using the existing marker and zero-fill layout and the same burst emission path. Do not emit an extra frame for a structural or padding-only buffer. Retain the current ready queue, seamless-branch timing handling and one-completed-MAT-per-input-packet behavior. Stop draining on output errors. Add API tests for empty, partial, exact and split frame endings, queued frames, both byte orders and finalization errors. Update the three TrueHD/MLP FATE references: each gains one 61440-byte final burst, with all previously emitted bytes unchanged. Signed-off-by: piotrasd <[email protected]> --- doc/muxers.texi | 4 + libavformat/spdifenc.c | 135 ++++++++++++++++----- libavformat/tests/spdif_muxer.c | 129 ++++++++++++++++++++ tests/fate/spdif.mak | 6 +- tests/ref/fate/spdif-mlp | 2 +- tests/ref/fate/spdif-truehd | 2 +- tests/ref/fate/spdif-truehd-branch-padding | 2 +- 7 files changed, 243 insertions(+), 37 deletions(-) diff --git a/doc/muxers.texi b/doc/muxers.texi index 572a10753b..230f1dcb2a 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -3756,6 +3756,10 @@ Specify whether to remove all fragments when finished. Default 0 (do not remove) IEC 61937 muxer for encapsulating compressed audio bitstreams. +For TrueHD and MLP, muxer finalization writes any queued complete MAT +frames and completes the final partial MAT frame if coded payload remains. +An empty input does not produce a MAT frame. + Coreless DTS-HD input requires type-IV output selected with @option{dtshd_rate}. When using libavformat, provide a positive packet duration in the stream time base that maps exactly to a supported carrier diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c index 839557805d..6089a76c8a 100644 --- a/libavformat/spdifenc.c +++ b/libavformat/spdifenc.c @@ -98,6 +98,7 @@ typedef struct IEC61937Context { uint16_t truehd_output_timing; ///< expected output_timing for truehd restart headers int truehd_output_timing_valid; ///< restart header output_timing has been read int truehd_oi_delta; ///< signed (output_timing-samples_per_frame)-input_timing + int truehd_active_has_payload; ///< active MAT contains coded AU bytes (not markers/padding only) /* AVOptions: */ int dtshd_rate; @@ -770,6 +771,7 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) hd_buf = ctx->hd_buf[ctx->hd_buf_idx]; ctx->hd_buf_filled = 0; + ctx->truehd_active_has_payload = 0; /* inter-frame gap has to be counted as well, add it */ code_len_remaining += MAT_PKT_OFFSET - MAT_FRAME_SIZE; @@ -805,6 +807,7 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) memcpy(hd_buf + ctx->hd_buf_filled, dataptr, data_to_insert); ctx->hd_buf_filled += data_to_insert; + ctx->truehd_active_has_payload = 1; dataptr += data_to_insert; data_remaining -= data_to_insert; } @@ -880,40 +883,10 @@ static av_always_inline void spdif_put_16(IEC61937Context *ctx, avio_wl16(pb, val); } -static int spdif_write_packet(struct AVFormatContext *s, AVPacket *pkt) +static int spdif_write_burst(AVFormatContext *s) { IEC61937Context *ctx = s->priv_data; - int ret, padding; - - ctx->out_buf = pkt->data; - ctx->out_bytes = pkt->size; - ctx->length_code = FFALIGN(pkt->size, 2) << 3; - ctx->use_preamble = 1; - ctx->extra_bswap = 0; - - ret = ctx->header_info(s, pkt); - if (ret < 0) - return ret; - - if (ctx->header_info == spdif_header_truehd) { - /* TrueHD may complete more than one MAT buffer in one AVPacket. At - * most, write one completed buffer per AVPacket. - */ - int ready_idx; - - if (!ctx->hd_buf_ready_count) - return 0; - - ready_idx = ctx->hd_buf_next_ready_idx; - ctx->hd_buf_next_ready_idx = truehd_next_mat_buffer(ctx->hd_buf_next_ready_idx); - ctx->hd_buf_ready_count--; - - ctx->out_buf = ctx->hd_buf[ready_idx]; - ctx->out_bytes = MAT_FRAME_SIZE; - ctx->data_type = IEC61937_TRUEHD; - ctx->length_code = MAT_FRAME_SIZE; - ctx->pkt_offset = MAT_PKT_OFFSET; - } + int padding; if (!ctx->pkt_offset) return 0; @@ -950,7 +923,102 @@ static int spdif_write_packet(struct AVFormatContext *s, AVPacket *pkt) av_log(s, AV_LOG_DEBUG, "type=%x len=%i pkt_offset=%i\n", ctx->data_type, ctx->out_bytes, ctx->pkt_offset); - return 0; + return s->pb->error < 0 ? s->pb->error : 0; +} + +/** + * Emit the oldest completed TrueHD MAT buffer as one IEC 61937 burst. + */ +static int truehd_write_ready_burst(AVFormatContext *s) +{ + IEC61937Context *ctx = s->priv_data; + int ready_idx; + + if (!ctx->hd_buf_ready_count) + return 0; + + ready_idx = ctx->hd_buf_next_ready_idx; + ctx->hd_buf_next_ready_idx = truehd_next_mat_buffer(ctx->hd_buf_next_ready_idx); + ctx->hd_buf_ready_count--; + + ctx->out_buf = ctx->hd_buf[ready_idx]; + ctx->out_bytes = MAT_FRAME_SIZE; + ctx->data_type = IEC61937_TRUEHD; + ctx->length_code = MAT_FRAME_SIZE; + ctx->pkt_offset = MAT_PKT_OFFSET; + ctx->use_preamble = 1; + ctx->extra_bswap = 0; + + return spdif_write_burst(s); +} + + +static int spdif_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + IEC61937Context *ctx = s->priv_data; + int ret; + + ctx->out_buf = pkt->data; + ctx->out_bytes = pkt->size; + ctx->length_code = FFALIGN(pkt->size, 2) << 3; + ctx->use_preamble = 1; + ctx->extra_bswap = 0; + + ret = ctx->header_info(s, pkt); + if (ret < 0) + return ret; + + if (ctx->header_info == spdif_header_truehd) { + /* TrueHD may complete more than one MAT buffer in one AVPacket. At + * most, write one completed buffer per AVPacket; the trailer drains + * any remainder. + */ + if (!ctx->hd_buf_ready_count) + return 0; + return truehd_write_ready_burst(s); + } + + return spdif_write_burst(s); +} + +static int spdif_write_trailer(AVFormatContext *s) +{ + IEC61937Context *ctx = s->priv_data; + uint8_t *hd_buf; + int ret; + + if (ctx->header_info != spdif_header_truehd) + return 0; + + while (ctx->hd_buf_ready_count) { + ret = truehd_write_ready_burst(s); + if (ret < 0) + return ret; + } + + /* A new active buffer can contain only markers and timing padding. */ + if (!ctx->truehd_active_has_payload) + return 0; + + hd_buf = ctx->hd_buf[ctx->hd_buf_idx]; + for (int i = 0; i < FF_ARRAY_ELEMS(mat_codes); i++) { + if (ctx->hd_buf_filled > mat_codes[i].pos) + continue; + memset(hd_buf + ctx->hd_buf_filled, 0, + mat_codes[i].pos - ctx->hd_buf_filled); + memcpy(hd_buf + mat_codes[i].pos, mat_codes[i].code, mat_codes[i].len); + ctx->hd_buf_filled = mat_codes[i].pos + mat_codes[i].len; + } + + if (ctx->hd_buf_filled != MAT_FRAME_SIZE) + return AVERROR_BUG; + + ret = truehd_enqueue_mat(s); + if (ret < 0) + return ret; + ctx->hd_buf_filled = 0; + ctx->truehd_active_has_payload = 0; + return truehd_write_ready_burst(s); } const FFOutputFormat ff_spdif_muxer = { @@ -963,6 +1031,7 @@ const FFOutputFormat ff_spdif_muxer = { .p.subtitle_codec = AV_CODEC_ID_NONE, .write_header = spdif_write_header, .write_packet = spdif_write_packet, + .write_trailer = spdif_write_trailer, .deinit = spdif_deinit, .p.flags = AVFMT_NOTIMESTAMPS, .p.priv_class = &spdif_class, diff --git a/libavformat/tests/spdif_muxer.c b/libavformat/tests/spdif_muxer.c index a3f8a09bf1..a2c0b32f0f 100644 --- a/libavformat/tests/spdif_muxer.c +++ b/libavformat/tests/spdif_muxer.c @@ -30,6 +30,25 @@ fprintf(stderr, "line %d: %s\n", __LINE__, #c); exit(1); \ } } while (0) +#define MAT_BYTES 61424 +#define BURST_BYTES 61440 +#define BODY_BYTES (MAT_BYTES - 20 - 12 - 16) + +/* These synthetic headers exercise muxing, not audio decoding. */ +static void make_au(uint8_t *p, int size, int timing, int major) +{ + for (int i = 0; i < size; i++) + p[i] = (i * 17 + 37) & 255; + AV_WB16(p, size / 2); + AV_WB16(p + 2, timing); + if (major) { + AV_WB32(p + 4, major == 2 ? 0xf8726fbb : 0xf8726fba); + p[8] = p[9] = 0; + if (size > 20) + p[20] = 0; /* No restart header to parse in this synthetic AU. */ + } +} + static AVFormatContext *open_mux(enum AVCodecID codec, AVRational tb, int rate, int strict, int be) { @@ -180,12 +199,122 @@ static void test_dts(void) } } +/* Check complete bursts and return the ordered bytes between MAT markers. */ +static uint8_t *mat_body(uint8_t *out, int size, int be, int count) +{ + static const uint8_t start[] = { + 0x07, 0x9e, 0, 3, 0x84, 1, 1, 1, 0x80, 0, + 0x56, 0xa5, 0x3b, 0xf4, 0x81, 0x83, 0x49, 0x80, 0x77, 0xe0 + }; + static const uint8_t middle[] = { + 0xc3, 0xc1, 0x42, 0x49, 0x3b, 0xfa, 0x82, 0x83, 0x49, 0x80, 0x77, 0xe0 + }; + static const uint8_t end[] = { + 0xc3, 0xc2, 0xc0, 0xc4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x97, 0x11 + }; + uint8_t *body; + CHECK(size == count * BURST_BYTES); + body = av_malloc(count * BODY_BYTES); + CHECK(body); + if (!be) + unswap(out, size); + for (int n = 0; n < count; n++) { + uint8_t *p = out + n * BURST_BYTES, *m = p + 8; + CHECK(AV_RB16(p) == 0xf872 && AV_RB16(p + 2) == 0x4e1f); + CHECK(AV_RB16(p + 4) == 0x16 && AV_RB16(p + 6) == MAT_BYTES); + CHECK(!memcmp(m, start, sizeof(start))); + CHECK(!memcmp(m + 30708, middle, sizeof(middle))); + CHECK(!memcmp(m + 61408, end, sizeof(end))); + memcpy(body + n * BODY_BYTES, m + 20, 30688); + memcpy(body + n * BODY_BYTES + 30688, m + 30720, 30688); + for (int i = 8 + MAT_BYTES; i < BURST_BYTES; i++) + CHECK(!p[i]); + } + return body; +} + +static int fail_write(void *opaque, const uint8_t *buf, int size) +{ + return AVERROR(EIO); +} + +static void test_mat(void) +{ + uint8_t *au = av_malloc(BODY_BYTES + 128), *out, *body; + AVFormatContext *s; + int size; + CHECK(au); + s = open_mux(AV_CODEC_ID_TRUEHD, (AVRational){1, 48000}, 0, -1, 0); + CHECK(close_mux(s, &out) == 0); + av_free(out); + + /* End before/after the middle marker, at completion, and across a MAT. */ + for (int n = 0; n < 6; n++) { + static const int sizes[] = { 48, 30688, 30700, BODY_BYTES - 2, + BODY_BYTES, BODY_BYTES + 128 }; + int au_size = sizes[n]; + int count = au_size > BODY_BYTES ? 2 : 1; + make_au(au, au_size, 0, 1); + for (int be = 0; be < 2; be++) { + s = open_mux(AV_CODEC_ID_TRUEHD, (AVRational){1, 48000}, 0, -1, be); + CHECK(append(s, au, au_size, 0) >= 0); + size = close_mux(s, &out); + body = mat_body(out, size, be, count); + CHECK(!memcmp(body, au, au_size)); + for (int i = au_size; i < count * BODY_BYTES; i++) + CHECK(!body[i]); + av_free(body); + av_free(out); + } + } + + /* One packet creates two complete MATs. Preserve FIFO order at EOF. */ + make_au(au, 16, 0, 1); + s = open_mux(AV_CODEC_ID_TRUEHD, (AVRational){1, 48000}, 0, -1, 0); + CHECK(append(s, au, 16, 0) >= 0); + make_au(au + 16, 16, 1920, 0); + CHECK(append(s, au + 16, 16, 0) >= 0); + size = close_mux(s, &out); + body = mat_body(out, size, 0, 3); + CHECK(!memcmp(body, au, 16)); + CHECK(!memcmp(body + 2 * BODY_BYTES, au + 16, 16)); + for (int i = 16; i < 2 * BODY_BYTES; i++) + CHECK(!body[i]); + av_free(body); + av_free(out); + + make_au(au, 32, 0, 2); + s = open_mux(AV_CODEC_ID_MLP, (AVRational){1, 48000}, 0, -1, 0); + CHECK(append(s, au, 32, 0) >= 0); + size = close_mux(s, &out); + body = mat_body(out, size, 0, 1); + CHECK(!memcmp(body, au, 32)); + av_free(body); + av_free(out); + + /* A failed output must not be reported as successful finalization. */ + s = open_mux(AV_CODEC_ID_TRUEHD, (AVRational){1, 48000}, 0, -1, 0); + CHECK(avio_close_dyn_buf(s->pb, &out) == 0); + av_free(out); + s->pb = avio_alloc_context(av_malloc(4096), 4096, 1, NULL, NULL, fail_write, NULL); + CHECK(s->pb && s->pb->buffer); + make_au(au, 48, 0, 1); + CHECK(append(s, au, 48, 0) >= 0); + CHECK(av_write_trailer(s) == AVERROR(EIO)); + av_freep(&s->pb->buffer); + avio_context_free(&s->pb); + avformat_free_context(s); + av_free(au); +} + int main(int argc, char **argv) { av_log_set_level(AV_LOG_QUIET); CHECK(argc == 2); if (!strcmp(argv[1], "dts")) test_dts(); + else if (!strcmp(argv[1], "mat")) + test_mat(); else CHECK(0); return 0; diff --git a/tests/fate/spdif.mak b/tests/fate/spdif.mak index 9d48ce2747..805baf6ac7 100644 --- a/tests/fate/spdif.mak +++ b/tests/fate/spdif.mak @@ -1,9 +1,13 @@ -FATE_SPDIF_API-$(CONFIG_SPDIF_MUXER) += fate-spdif-dts-api +FATE_SPDIF_API-$(CONFIG_SPDIF_MUXER) += fate-spdif-dts-api fate-spdif-truehd-api fate-spdif-dts-api: libavformat/tests/spdif_muxer$(EXESUF) fate-spdif-dts-api: CMD = run libavformat/tests/spdif_muxer$(EXESUF) dts fate-spdif-dts-api: CMP = null +fate-spdif-truehd-api: libavformat/tests/spdif_muxer$(EXESUF) +fate-spdif-truehd-api: CMD = run libavformat/tests/spdif_muxer$(EXESUF) mat +fate-spdif-truehd-api: CMP = null + FATE-$(CONFIG_AVFORMAT) += $(FATE_SPDIF_API-yes) # This pads the AAC frames to 16 bit words (the actual size is diff --git a/tests/ref/fate/spdif-mlp b/tests/ref/fate/spdif-mlp index 20dfcb1bb8..c488dec0bb 100644 --- a/tests/ref/fate/spdif-mlp +++ b/tests/ref/fate/spdif-mlp @@ -1 +1 @@ -6deb20b45c83b0edce9b91a44a76137c +58ed597d82e85a0410045700c90d259a diff --git a/tests/ref/fate/spdif-truehd b/tests/ref/fate/spdif-truehd index 5bb0f0ed5b..4f8ed3f47c 100644 --- a/tests/ref/fate/spdif-truehd +++ b/tests/ref/fate/spdif-truehd @@ -1 +1 @@ -ed9617614478a05b3f94a74d3f1061d9 +7493a44de65d88c353f7b16de44fc2e2 diff --git a/tests/ref/fate/spdif-truehd-branch-padding b/tests/ref/fate/spdif-truehd-branch-padding index 688606cbfc..db2bd43171 100644 --- a/tests/ref/fate/spdif-truehd-branch-padding +++ b/tests/ref/fate/spdif-truehd-branch-padding @@ -1 +1 @@ -08e36c5ef0cc54e48eb2cc7a898e72c4 +6e8b4d1910feac95f3658a232e5c4a32 -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
