PR #23542 opened by Nathan Lucas (nathanlucas) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23542 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23542.patch
### Background This is fixing audio dropouts heard by some users (including myself) while watching UHD Blu-Ray movies with TrueHD/Atmos that have been remuxed with tools like makemkv, trueHD audio passed-through using spdifenc. I can't say what the exact cause of the dropouts are. Downstream consumers of the MAT frames and the audio stream seem more or less senstive to timing depending on the setup. Ticket numbers are in the commit message. The main pattern I found is that the audio dropout locations are at the uhd bluray seamless branch locations, which I could confirm looking at the playlist on disc. In some movies, over several hundred truehd access units leading up to the branch, input_timing sometimes falls behind by as much as 1800 timing units relative to output_timing. 1800 actually shows up a lot in my traces. After the branch, input_timing snaps back to normal (usually) and there is a large gap in timing that is not corrected because the current sanity check is too tight and ignores it. At the sample rate used in these movies, 1 timing unit is 64 bytes. 1800 * 64 = 115200 bytes which is well above the current MAT_FRAME_SIZE / 2 (30712) limit. 115200 bytes in IEC/MAT timing is 37.5ms of IEC carrier. That lost carrier could be related to the dropouts as well. Increasing the limit to MAT_FRAME_SIZE * 2 is just enough to never drop padding (tested against over 280 movies). Related to this is that remuxes of some movie titles have truehd timing discontinuities at the branch points. spdifenc currently only uses input_timing to compute padding to maintain timing, but at the discontinuities that does not work and the calculated padding is wrong. The sanity check catches most of these but again drops padding to zero and still does not preserve timing. I've observed that the branch discontinuity seems to always restart with a major sync and restart header, so output_timing from the restart header can be used to detect the discontinuities and also to compute padding and preserve timing. Since input_timing is relative to output_timing, padding can be determined by the output_timing - input_timing difference before and after the discontinuity. I have also observed the 1800 timing unit gap at discontinuities and this method is computing sensible amounts of padding. Some interesting data to support the improvement/correctness: Below are three tables. The movies in them were remuxed using the latest version of makemkv a few weeks ago. The tables show how many MAT frames were expected by the end of the movie verses how many were actually produced. The number of IEC/MAT frames emitted by spdifenc is related to the padding that is computed so I think this is an interesting data point. Pay attention to the expected vs. actual delta. ``` // current upstream input_timing-based padding + MAT/2 cutoff Movie AUs MATs Expected Delta remainder_AUs padding_dropped Wrath of Khan Theatrical 8140434 339161 339183 -22 18 13 Wrath of Khan DC 8389833 349549 349575 -26 9 16 Martian Theatrical 10197138 424838 424881 -43 18 23 Martian Extended 10896787 453987 454033 -46 19 27 Alien Romulus 8570262 357094 357094 0 6 0 Avatar 11666406 486054 486100 -46 6 27 Avatar Way of Water 13865005 577651 577708 -57 13 33 Monsters University 7474067 311244 311417 -173 11 99 ``` The current upstream method with the MAT_FRAME_SIZE/2 sanity check drops a lot of padding in all but Alien Romulus which does not have that -1800 input_timing behavior. The other movies do have that behavior. Large amounts of padding/timing are dropped and they fall behind the expected output. Each MAT not emitted accounts for 20ms of IEC/MAT carrier time. ``` // input_timing only + MAT*2, no output_timing discontinuity handling Movie AUs MATs Expected Delta remainder_AUs padding_dropped Wrath of Khan Theatrical 8140434 339184 339183 +1 18 0 Wrath of Khan DC 8389833 349550 349575 -25 9 15 Martian theatrical 10197138 424880 424881 -1 18 0 Martian extended 10896787 453991 454033 -42 19 25 Alien Romulus 8570262 357094 357094 0 6 0 Avatar 11666406 486100 486100 0 6 0 Avatar Way of Water 13865005 577708 577708 0 13 0 Monsters University 7474067 311419 311417 +2 11 0 ``` If I only change the sanity limit to MAT_FRAME_SIZE*2 then movies without timing discontinuities look much better. The Martian Extended and Wrath of Khan have a lot of discontinuities that are not resolved in this case because input_timing-based padding does not there. ``` // output_timing discontinuity handling + MAT*2 Movie AUs MATs Expected Delta remainder_AUs discontinuities Wrath of Khan Theatrical 8140434 339184 339183 +1 18 0 Wrath of Khan DC 8389833 349576 349575 +1 9 16 Martian Theatrical 10197138 424880 424881 -1 18 0 Martian Extended 10896787 454032 454033 -1 19 25 Alien Romulus 8570262 357094 357094 0 6 0 Avatar 11666406 486100 486100 0 6 0 Avatar Way of Water 13865005 577708 577708 0 13 5 Monsters University 7474067 311419 311417 +2 11 0 ``` Using output_timing-input_timing before and after the discontinuity in those two movies resolves them as well. I have uploaded a histogram of padding values over 287 movies, 31 with output_timing discontinuities, that supports the sanity check move from MAT_FRAME_SIZE / 2 to MAT_FRAME_SIZE * 2. The top histogram shows input_timing-based padding in blue. Red is input_timing-based padding values at output_timing discontinuities (i.e. red padding is probably wrong). The bottom histogram shows input_timing-based padding in blue. Orange is padding that was computed at a discontinuity using using the output_timing-input_timing offset before and after the discontinuity. One curious thing worth noting is the large number of "red" padding that still lands just under MAT_FRAME_SIZE * 2. I've observed that sometimes the remux either adds or removes one truehd access unit at the branch. There technically is a discontinuity there but input_timing-based padding is reasonable in those cases. Either way, output_timing-input_timing method also works there. The last thing I want to add on this topic is that increasing the sanity check to MAT_FRAME_SIZE * 2 will allow up to 2 MAT frames to be completed by one AVPacket. Since likely nobody is expecting spdifenc to write more than one IEC/MAT frame at once, these changes will still only output one frame per AVPacket. When multiple are ready to write, they will be written out one per AVPacket. ### spdifenc context reset via AVOption Need comments on this change. Seeking will almost always cause a discontinuity event to get logged because the timing before and after the seek are discontinuous by nature. In rare cases, padding may be added if it doesn't fail the sanity check. The current MAT buffer will likely also contain truehd access units from before the seek which doesn't make sense to preserve after the seek. So I think players should reset the spdifenc context on seek but the only way to do that currently is to free the context and create a new one. I'm just trying to make the reset easier. Is it preferable for the user to destroy/recreate the context on seek, or should a one-shot AVOption to reset before the next AVPacket be provided? >From 24c76f5a966072eaa54c0a2c12df12a5fa6f08d4 Mon Sep 17 00:00:00 2001 From: Nathan Lucas <[email protected]> Date: Tue, 16 Jun 2026 07:25:30 -0600 Subject: [PATCH 1/2] avformat/spdifenc: add one-shot reset option Players may seek while reusing the same spdifenc contex. Some statefull passthrough formats such as TrueHD keep state across packets. After a seek, that state belongs to the old stream position and can cause the next packet to use stale timing or buffering state. Add a runtime "reset" AVOption. When set, spdifenc resets its muxer state before processing the next packet and then clears the option automatically. This lets callers discard stale seek state without tearing down and recreating the spdif muxer context. Signed-off-by: Nathan Lucas <[email protected]> --- libavformat/spdifenc.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c index ab3f73da0d..b0f96feadc 100644 --- a/libavformat/spdifenc.c +++ b/libavformat/spdifenc.c @@ -88,6 +88,7 @@ typedef struct IEC61937Context { int dtshd_fallback; #define SPDIF_FLAG_BIGENDIAN 0x01 int spdif_flags; + int reset_requested; /// function, which generates codec dependent header information. /// Sets data_type and pkt_offset, and length_code, out_bytes, out_buf if necessary @@ -99,6 +100,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 }, +{ "reset", "request one-shot muxer state reset before processing the next packet", offsetof(IEC61937Context, reset_requested), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_RUNTIME_PARAM }, { NULL }, }; @@ -109,6 +111,24 @@ static const AVClass spdif_class = { .version = LIBAVUTIL_VERSION_INT, }; +static void spdif_reset_state(AVFormatContext *s) +{ + IEC61937Context *ctx = s->priv_data; + + ctx->hd_buf_count = 0; + ctx->hd_buf_filled = 0; + ctx->hd_buf_idx = 0; + ctx->dtshd_skip = 0; + + ctx->truehd_prev_time = 0; + ctx->truehd_prev_size = 0; + + ctx->out_buf = NULL; + ctx->out_bytes = 0; + ctx->length_code = 0; + ctx->pkt_offset = 0; +} + static int spdif_header_ac3(AVFormatContext *s, AVPacket *pkt) { IEC61937Context *ctx = s->priv_data; @@ -628,6 +648,11 @@ static int spdif_write_packet(struct AVFormatContext *s, AVPacket *pkt) IEC61937Context *ctx = s->priv_data; int ret, padding; + if (ctx->reset_requested) { + spdif_reset_state(s); + ctx->reset_requested = 0; + } + ctx->out_buf = pkt->data; ctx->out_bytes = pkt->size; ctx->length_code = FFALIGN(pkt->size, 2) << 3; -- 2.52.0 >From 0cbcb55a0d562a5d043988068ac12697e934df90 Mon Sep 17 00:00:00 2001 From: Nathan Lucas <[email protected]> Date: Tue, 16 Jun 2026 08:05:20 -0600 Subject: [PATCH 2/2] avformat/spdifenc: preserve TrueHD MAT padding across branches Some TrueHD streams, particularly remuxes of seamless branching Blu-rays, contain input_timing gaps that require more padding than the current MAT_FRAME_SIZE / 2 limit allows. spdifenc treats this padding as invalid and drops it, breaking MAT timing and IEC 61937 carrier cadence, causing TrueHD/Atmos dropouts on some receivers. Allow larger valid padding gaps and queue completed MAT buffers so the muxer can preserve the carrier cadence while still writing at most one MAT buffer per input packet. Some branches also have discontinuous timing, so input_timing cannot be directly used to compute padding at those boundaries. When output_timing is available from a TrueHD restart header, use it to detect those discontinuities and compute padding from the change in output_timing - input_timing offset across the boundary instead. Fixes: https://trac.ffmpeg.org/ticket/9569 Fixes: https://trac.ffmpeg.org/ticket/10948 See also: https://github.com/mpv-player/mpv/issues/9659 See also: https://github.com/mpv-player/mpv/issues/13943 Signed-off-by: Nathan Lucas <[email protected]> --- libavformat/spdifenc.c | 287 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 264 insertions(+), 23 deletions(-) diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c index b0f96feadc..400f09e67d 100644 --- a/libavformat/spdifenc.c +++ b/libavformat/spdifenc.c @@ -54,9 +54,20 @@ #include "libavcodec/adts_parser.h" #include "libavcodec/dca.h" #include "libavcodec/dca_syncwords.h" +#include "libavcodec/get_bits.h" #include "libavutil/mem.h" #include "libavutil/opt.h" +/* + * With padding up to MAT_FRAME_SIZE*2 allowed, TrueHD/MLP can complete two MAT + * buffers with one AVPacket. A third is needed for the active buffer, so no + * less than three are required. Since no more than one MAT can be written per + * AVPacket, extra headroom has been given. + * + * E-AC-3 and DTS-HD only need hd_buf[0]. + */ +#define HD_BUF_COUNT 6 + typedef struct IEC61937Context { const AVClass *av_class; enum IEC61937DataType data_type;///< burst info - reference to type of payload of the data-burst @@ -71,17 +82,22 @@ typedef struct IEC61937Context { int use_preamble; ///< preamble enabled (disabled for exactly pre-padded DTS) int extra_bswap; ///< extra bswap for payload (for LE DTS => standard BE DTS) - uint8_t *hd_buf[2]; ///< allocated buffers to concatenate hd audio frames + uint8_t *hd_buf[HD_BUF_COUNT]; ///< allocated buffers to concatenate hd audio frames int hd_buf_size; ///< size of the hd audio buffer (eac3, dts4) int hd_buf_count; ///< number of frames in the hd audio buffer (eac3) int hd_buf_filled; ///< amount of bytes in the hd audio buffer (eac3, truehd) int hd_buf_idx; ///< active hd buffer index (truehd) + 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 uint16_t truehd_prev_time; ///< input_timing from the last frame int truehd_prev_size; ///< previous frame size in bytes, including any MAT codes int truehd_samples_per_frame; ///< samples per frame for padding calculation + 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 /* AVOptions: */ int dtshd_rate; @@ -118,10 +134,15 @@ static void spdif_reset_state(AVFormatContext *s) ctx->hd_buf_count = 0; ctx->hd_buf_filled = 0; ctx->hd_buf_idx = 0; + ctx->hd_buf_next_ready_idx = 0; + ctx->hd_buf_ready_count = 0; ctx->dtshd_skip = 0; ctx->truehd_prev_time = 0; ctx->truehd_prev_size = 0; + ctx->truehd_output_timing = 0; + ctx->truehd_output_timing_valid = 0; + ctx->truehd_oi_delta = 0; ctx->out_buf = NULL; ctx->out_bytes = 0; @@ -440,32 +461,162 @@ static const struct { MAT_CODE(MAT_FRAME_SIZE - sizeof(mat_end_code), mat_end_code), }; +/** + * Get the next index in the MAT buffer ring. + */ +static int truehd_next_mat_buffer(int idx) +{ + return (idx + 1) % HD_BUF_COUNT; +} + +/** + * Mark the current MAT buffer ready and advance to the next free buffer. + */ +static int truehd_enqueue_mat(AVFormatContext *s) +{ + IEC61937Context *ctx = s->priv_data; + + if (ctx->hd_buf_ready_count >= HD_BUF_COUNT - 1) { + av_log(s, AV_LOG_ERROR, "Too many completed TrueHD MAT frames pending\n"); + return AVERROR(EINVAL); + } + + ctx->hd_buf_ready_count++; + ctx->hd_buf_idx = truehd_next_mat_buffer(ctx->hd_buf_idx); + return 0; +} + +typedef struct TrueHDAccessUnitInfo { + uint16_t input_timing; + uint16_t output_timing; + int has_output_timing; + int samples_per_frame; +} TrueHDAccessUnitInfo; + +static int truehd_parse_access_unit(const uint8_t *au_data, int au_size, + TrueHDAccessUnitInfo *au) +{ + GetBitContext gb; + int major_sync_size = 28; + int ratebits; + int num_substreams; + int sync_word; + int ret; + int i; + const uint8_t *data; + int size; + + memset(au, 0, sizeof(*au)); + + if (au_size < 4) + return AVERROR_INVALIDDATA; + + data = au_data + 4; + size = au_size - 4; + + au->input_timing = AV_RB16(au_data + 2); + + if (size < 6 || AV_RB24(data) != 0xf8726f) + return 0; + + /* major sync unit, fetch sample rate */ + if (data[3] == 0xba) + ratebits = data[4] >> 4; + else if (data[3] == 0xbb) + ratebits = data[5] >> 4; + else + return AVERROR_INVALIDDATA; + au->samples_per_frame = 40 << (ratebits & 3); + + if (size < 27) + return 0; + + if (data[3] == 0xba && data[25] & 1) { + int ext_size = data[26] >> 4; + major_sync_size += 2 + ext_size * 2; + } + + if (major_sync_size > size) + return 0; + + ret = init_get_bits8(&gb, data + major_sync_size, size - major_sync_size); + if (ret < 0) + return ret; + + num_substreams = data[16] >> 4; + if (num_substreams <= 0) + return 0; + + for (i = 0; i < num_substreams; i++) { + int extra_word; + if (get_bits_left(&gb) < 16) + return 0; + extra_word = get_bits1(&gb); + skip_bits_long(&gb, 15); + if (!extra_word) + continue; + if (get_bits_left(&gb) < 16) + return 0; + skip_bits_long(&gb, 16); + } + + /* output timing is always at least found in the first substream if it + * is present at all, so only walk the first substream. + */ + if (get_bits_left(&gb) < 1) + return 0; + if (!get_bits1(&gb)) /* ! block_header_exists */ + return 0; + if (get_bits_left(&gb) < 1) + return 0; + if (!get_bits1(&gb)) /* ! restart_header_exists */ + return 0; + if (get_bits_left(&gb) < 13 + 1 + 16) + return 0; + + sync_word = get_bits(&gb, 13); + if (sync_word != (0x31ea >> 1)) + return 0; + skip_bits1(&gb); /* noise_type */ + + au->output_timing = get_bits(&gb, 16); + au->has_output_timing = 1; + + return 0; +} + +/** + * Interpret the modulo-2^16 difference of a and b as a signed delta. + * Only unambiguous when the true delta is < 2^15. + */ +static int u16_signed_delta(uint16_t a, uint16_t b) +{ + int delta = (uint16_t)(a - b); + return delta >= 0x8000 ? delta - 0x10000 : delta; +} + static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) { IEC61937Context *ctx = s->priv_data; uint8_t *hd_buf = ctx->hd_buf[ctx->hd_buf_idx]; - int ratebits; + TrueHDAccessUnitInfo au; int padding_remaining = 0; + int max_padding_per_packet = MAT_FRAME_SIZE * 2; + int output_discontinuity = 0; uint16_t input_timing; int total_frame_size = pkt->size; const uint8_t *dataptr = pkt->data; int data_remaining = pkt->size; int have_pkt = 0; int next_code_idx; + int ret; - if (pkt->size < 10) - return AVERROR_INVALIDDATA; + ret = truehd_parse_access_unit(pkt->data, pkt->size, &au); + if (ret < 0) + return ret; - if (AV_RB24(pkt->data + 4) == 0xf8726f) { - /* major sync unit, fetch sample rate */ - if (pkt->data[7] == 0xba) - ratebits = pkt->data[8] >> 4; - else if (pkt->data[7] == 0xbb) - ratebits = pkt->data[9] >> 4; - else - return AVERROR_INVALIDDATA; - - ctx->truehd_samples_per_frame = 40 << (ratebits & 3); + if (au.samples_per_frame) { + ctx->truehd_samples_per_frame = au.samples_per_frame; av_log(s, AV_LOG_TRACE, "TrueHD samples per frame: %d\n", ctx->truehd_samples_per_frame); } @@ -473,8 +624,72 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) if (!ctx->truehd_samples_per_frame) return AVERROR_INVALIDDATA; - input_timing = AV_RB16(pkt->data + 2); - if (ctx->truehd_prev_size) { + input_timing = au.input_timing; + + ctx->truehd_output_timing += ctx->truehd_samples_per_frame; + if (au.has_output_timing) { + if (ctx->truehd_output_timing_valid && + au.output_timing != ctx->truehd_output_timing) { + /* + * Each TrueHD access unit, output_timing increments by + * samples_per_frame, and input_timing nominally increments by the + * same amount. However if input_timing has fallen behind relative + * to output_timing at a discontinuity then additional padding can be + * added to preserve the correct MAT timing and IEC61937 carrier. So + * at the discontinuity compute padding based on the output-input + * timing delta relative to the new output-input delta. Negative or + * excessive padding will be ignored and logged. + * + * output_timing is always ahead by one frame period, so one period is + * always subtracted before comparison. + */ + int bytes_per_sample = 2560 / ctx->truehd_samples_per_frame; + uint16_t output_timing_minus_spf = au.output_timing - + ctx->truehd_samples_per_frame; + int previous_oi_delta = ctx->truehd_oi_delta; + int current_oi_delta = u16_signed_delta(output_timing_minus_spf, + input_timing); + int prev_padding = 0; + int discontinuity_padding = 0; + + output_discontinuity = 1; + discontinuity_padding = (previous_oi_delta - current_oi_delta) * + bytes_per_sample; + + if (ctx->truehd_prev_size) + prev_padding = 2560 - ctx->truehd_prev_size; + + padding_remaining = prev_padding + discontinuity_padding; + + if (padding_remaining < 0 || padding_remaining > max_padding_per_packet) { + avpriv_request_sample(s, + "Unusual TrueHD output_timing discontinuity: " + "expected %"PRIu16" got %"PRIu16", " + "timing delta %d => %d, prev padding %d, " + "discontinuity padding %d, " + "total padding %d ignored", + ctx->truehd_output_timing, au.output_timing, + previous_oi_delta, current_oi_delta, prev_padding, + discontinuity_padding, padding_remaining); + padding_remaining = 0; + } else { + av_log(s, AV_LOG_VERBOSE, + "TrueHD output_timing discontinuity: " + "expected %"PRIu16" got %"PRIu16", " + "timing delta %d => %d, prev padding %d, " + "discontinuity padding %d, " + "total padding %d\n", + ctx->truehd_output_timing, au.output_timing, + previous_oi_delta, current_oi_delta, prev_padding, + discontinuity_padding, padding_remaining); + } + } + + ctx->truehd_output_timing = au.output_timing; + ctx->truehd_output_timing_valid = 1; + } + + if (ctx->truehd_prev_size && !output_discontinuity) { uint16_t delta_samples = input_timing - ctx->truehd_prev_time; /* * One multiple-of-48kHz frame is 1/1200 sec and the IEC 61937 rate @@ -494,13 +709,20 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) delta_samples, delta_bytes); /* sanity check */ - if (padding_remaining < 0 || padding_remaining >= MAT_FRAME_SIZE / 2) { + if (padding_remaining < 0 || padding_remaining > max_padding_per_packet) { avpriv_request_sample(s, "Unusual frame timing: %"PRIu16" => %"PRIu16", %d samples/frame", ctx->truehd_prev_time, input_timing, ctx->truehd_samples_per_frame); padding_remaining = 0; } } + if (ctx->truehd_output_timing_valid) { + uint16_t output_timing_minus_spf = ctx->truehd_output_timing - + ctx->truehd_samples_per_frame; + ctx->truehd_oi_delta = u16_signed_delta(output_timing_minus_spf, + input_timing); + } + for (next_code_idx = 0; next_code_idx < FF_ARRAY_ELEMS(mat_codes); next_code_idx++) if (ctx->hd_buf_filled <= mat_codes[next_code_idx].pos) break; @@ -525,8 +747,10 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) /* this was the last code, move to the next MAT frame */ have_pkt = 1; - ctx->out_buf = hd_buf; - ctx->hd_buf_idx ^= 1; + ret = truehd_enqueue_mat(s); + if (ret < 0) + return ret; + hd_buf = ctx->hd_buf[ctx->hd_buf_idx]; ctx->hd_buf_filled = 0; @@ -580,10 +804,6 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) return 0; } - ctx->data_type = IEC61937_TRUEHD; - ctx->pkt_offset = MAT_PKT_OFFSET; - ctx->out_bytes = MAT_FRAME_SIZE; - ctx->length_code = MAT_FRAME_SIZE; return 0; } @@ -662,6 +882,27 @@ static int spdif_write_packet(struct AVFormatContext *s, AVPacket *pkt) 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; + } + if (!ctx->pkt_offset) return 0; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
