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]

Reply via email to