This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 2db563fac2ad9e8509d4c5a699462eead5c32b8c Author: Nathan Lucas <[email protected]> AuthorDate: Tue Jun 16 08:05:20 2026 -0600 Commit: Nathan Lucas <[email protected]> CommitDate: Sat Jul 25 20:12:16 2026 -0600 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 | 273 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 252 insertions(+), 21 deletions(-) diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c index 16eebda01c..0b8d8c5886 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; @@ -396,6 +412,7 @@ static int spdif_header_aac(AVFormatContext *s, AVPacket *pkt) */ #define MAT_PKT_OFFSET 61440 #define MAT_FRAME_SIZE 61424 +#define TRUEHD_NOMINAL_AU_SPACING 2560 static const uint8_t mat_start_code[20] = { 0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01, 0x01, 0x80, 0x00, 0x56, 0xA5, 0x3B, 0xF4, 0x81, 0x83, @@ -420,32 +437,155 @@ 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; +} + 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; - 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; + ret = truehd_parse_access_unit(pkt->data, pkt->size, &au); + if (ret < 0) + return ret; - 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); } @@ -453,8 +593,73 @@ 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 = TRUEHD_NOMINAL_AU_SPACING / + 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 = (int16_t)(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 = TRUEHD_NOMINAL_AU_SPACING - 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 @@ -465,7 +670,8 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt) * * 2560 is divisible by truehd_samples_per_frame. */ - int delta_bytes = delta_samples * 2560 / ctx->truehd_samples_per_frame; + int delta_bytes = delta_samples * TRUEHD_NOMINAL_AU_SPACING / + ctx->truehd_samples_per_frame; /* padding needed before this frame */ padding_remaining = delta_bytes - ctx->truehd_prev_size; @@ -474,13 +680,19 @@ 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 = (int16_t)(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; @@ -505,8 +717,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; @@ -560,10 +774,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; } @@ -637,6 +847,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; _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
