Hi Anssi,
Cc'ing you because you are listed as the maintainer for spdif*.

Could you please review the PR on Forgejo and consider merging it if it
looks good?  There is a FATE sample attached to the PR that needs to be
uploaded.  I have e-mailed [email protected] but it does not
appear to be uploaded yet.  Once that is done, I believe the PR is ready to
merge.  I don't intend to make any more changes unless requested or
needed.  If you are no longer maintaining this code, a pointer to the
appropriate reviewer would also be appreciated.

Thank you,
Nathan


On Sat, Jun 20, 2026 at 2:39 PM Nathan Lucas via ffmpeg-devel <
[email protected]> wrote:

> 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]
>
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to