diff mbox

[FFmpeg-devel] lavf/spdifenc: Use a more flexible buffer model for TrueHD/MAT

Message ID CAB0OVGqmXRJM_svrckn1exUQ_tAX-CRLs+TQz4na1JTTSYqs6g@mail.gmail.com
State Superseded
Headers show

Commit Message

Carl Eugen Hoyos Feb. 14, 2019, 7:11 p.m. UTC
Hi!

I created attached patch with a lot of help from Hendrik, fixes ticket #7731.

Please review, Carl Eugen

Comments

Carl Eugen Hoyos Feb. 14, 2019, 7:13 p.m. UTC | #1
2019-02-14 20:11 GMT+01:00, Carl Eugen Hoyos <ceffmpeg@gmail.com>:

> I created attached patch with a lot of help from Hendrik, fixes
> ticket #7731.

I forgot: I still need help to fix the frame distance calculation,
how is the frame rate supposed to be used?
Iiuc, the current code only works for 48kHz audio.

Thank you, Carl Eugen
Hendrik Leppkes Feb. 14, 2019, 11:16 p.m. UTC | #2
On Thu, Feb 14, 2019 at 8:14 PM Carl Eugen Hoyos <ceffmpeg@gmail.com> wrote:
>
> 2019-02-14 20:11 GMT+01:00, Carl Eugen Hoyos <ceffmpeg@gmail.com>:
>
> > I created attached patch with a lot of help from Hendrik, fixes
> > ticket #7731.
>
> I forgot: I still need help to fix the frame distance calculation,
> how is the frame rate supposed to be used?
> Iiuc, the current code only works for 48kHz audio.
>

You want to shift the 64 to the right by the ratebits from the TrueHD
header, see ff_mlp_read_major_sync where to find it.

- Hendrik
Hendrik Leppkes Feb. 14, 2019, 11:23 p.m. UTC | #3
On Thu, Feb 14, 2019 at 8:11 PM Carl Eugen Hoyos <ceffmpeg@gmail.com> wrote:
>
> Hi!
>
> I created attached patch with a lot of help from Hendrik, fixes ticket #7731.
>

Som general comments, since I can't replay inline in this mail client:

- The pseudo-timestamps are unsigned 16-bit, personally I would make
all types that reference them uint16_t and rely on unsigned overflow
behavior, which is fully valid and specified - no need to handle
negatives manually, etc.
- The distance between frames can be incredibly large in some streams,
I've seen it be larger then one whole MAT frame, and the output works
reliably (MAT start/middle/end codes need to be placed within the
padding space, of course).

There is a few more things I saw, but I'll respond to those from a
better client in a few days when I'm back home.

- Hendrik
diff mbox

Patch

From ce97425171b06b353cf9ec71e9ff6c7e7ec49422 Mon Sep 17 00:00:00 2001
From: Carl Eugen Hoyos <ceffmpeg@gmail.com>
Date: Thu, 14 Feb 2019 20:07:53 +0100
Subject: [PATCH] lavf/spdifenc: Use a more flexible buffer model for
 TrueHD/MAT.

Allows muxing large frames.
Fixes ticket #7731.
---
 libavformat/spdifenc.c |  138 ++++++++++++++++++++++++++++++++++++------------
 1 file changed, 104 insertions(+), 34 deletions(-)

diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c
index 9514ff8..59d0c27 100644
--- a/libavformat/spdifenc.c
+++ b/libavformat/spdifenc.c
@@ -76,6 +76,13 @@  typedef struct IEC61937Context {
 
     int dtshd_skip;                 ///< counter used for skipping DTS-HD frames
 
+    int last_ts;                    ///< timestamp of the last TrueHD frame to calculate spacing
+    int remaining;                  ///< bytes to the next mat code
+    uint8_t *buf;                   ///< pointer into the mat frame
+    uint8_t *last_frame;            ///< buffer for remaining bytes of incompletely written frame
+    int last_frame_size;
+    int space;                      ///< current delta of expected and actual frame spacing
+
     /* AVOptions: */
     int dtshd_rate;
     int dtshd_fallback;
@@ -382,56 +389,110 @@  static int spdif_header_aac(AVFormatContext *s, AVPacket *pkt)
 
 
 /*
- * It seems Dolby TrueHD frames have to be encapsulated in MAT frames before
+ * Dolby TrueHD frames have to be encapsulated in MAT frames before
  * they can be encapsulated in IEC 61937.
- * Here we encapsulate 24 TrueHD frames in a single MAT frame, padding them
- * to achieve constant rate.
- * The actual format of a MAT frame is unknown, but the below seems to work.
- * However, it seems it is not actually necessary for the 24 TrueHD frames to
- * be in an exact alignment with the MAT frame.
+ * A specific alignment is required to fulfill buffering requirements
+ * in some cases, while the average frame distance has to be constant
+ * actual frame distances vary depending on timestamps and frame sizes.
  */
 #define MAT_FRAME_SIZE          61424
 #define TRUEHD_FRAME_OFFSET     2560
-#define MAT_MIDDLE_CODE_OFFSET  -4
+#define MAT_HALF_FRAME          30688
+static const char mat_start_code[20] = { 0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01, 0x01, 0x80, 0x00, 0x56, 0xA5, 0x3B, 0xF4, 0x81 , 0x83, 0x49, 0x80, 0x77, 0xE0 };
+static const char mat_middle_code[12] = { 0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0 };
+static const char mat_end_code[16] = { 0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11 };
 
 static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt)
 {
     IEC61937Context *ctx = s->priv_data;
-    int mat_code_length = 0;
-    static const char mat_end_code[16] = { 0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11 };
-
-    if (!ctx->hd_buf_count) {
-        static const char mat_start_code[20] = { 0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01, 0x01, 0x80, 0x00, 0x56, 0xA5, 0x3B, 0xF4, 0x81, 0x83, 0x49, 0x80, 0x77, 0xE0 };
-        mat_code_length = sizeof(mat_start_code) + BURST_HEADER_SIZE;
-        memcpy(ctx->hd_buf, mat_start_code, sizeof(mat_start_code));
-
-    } else if (ctx->hd_buf_count == 12) {
-        static const char mat_middle_code[12] = { 0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0 };
-        mat_code_length = sizeof(mat_middle_code) + MAT_MIDDLE_CODE_OFFSET;
-        memcpy(&ctx->hd_buf[12 * TRUEHD_FRAME_OFFSET - BURST_HEADER_SIZE + MAT_MIDDLE_CODE_OFFSET],
-               mat_middle_code, sizeof(mat_middle_code));
-    }
 
-    if (pkt->size > TRUEHD_FRAME_OFFSET - mat_code_length) {
-        /* if such frames exist, we'd need some more complex logic to
-         * distribute the TrueHD frames in the MAT frame */
-        avpriv_request_sample(s, "Too large TrueHD frame of %d bytes",
-                              pkt->size);
-        return AVERROR_PATCHWELCOME;
+    if (pkt->size < 8 || pkt->size >= MAT_HALF_FRAME / 2) {
+        av_log(s, AV_LOG_ERROR, "Invalid frame size: %d\n", pkt->size);
+        return AVERROR(EINVAL);
+    }
+    if (ctx->last_ts == -1 && AV_RB16(&pkt->data[4]) != SYNCWORD1) {
+        ctx->pkt_offset = 0;
+        return 0;
+    }
+    if (ctx->last_ts >= 0) {
+        int distance = (AV_RB16(&pkt->data[2]) - ctx->last_ts);
+        if (distance < 0)
+            distance = 65536 + AV_RB16(&pkt->data[2]) - ctx->last_ts;
+        distance *= 64; //FIXME
+        if (distance > MAT_HALF_FRAME / 4) {
+            av_log(s, AV_LOG_ERROR, "Invalid frame distance %d, using %d\n", distance, TRUEHD_FRAME_OFFSET);
+            distance = TRUEHD_FRAME_OFFSET;
+        }
+        ctx->space += distance;
+    }
+    ctx->last_ts = AV_RB16(&pkt->data[2]);
+    if (ctx->buf == ctx->hd_buf) {
+        // write mat_start_code at start of output packet
+        memcpy(ctx->buf, mat_start_code, sizeof(mat_start_code));
+        ctx->buf += sizeof(mat_start_code);
+        memcpy(ctx->buf, ctx->last_frame, ctx->last_frame_size);
+        ctx->buf += ctx->last_frame_size;
+        ctx->remaining = MAT_HALF_FRAME - ctx->last_frame_size;
+        ctx->space -= ctx->last_frame_size + sizeof(mat_start_code);
+        ctx->last_frame_size = 0;
     }
 
-    memcpy(&ctx->hd_buf[ctx->hd_buf_count * TRUEHD_FRAME_OFFSET - BURST_HEADER_SIZE + mat_code_length],
-           pkt->data, pkt->size);
-    memset(&ctx->hd_buf[ctx->hd_buf_count * TRUEHD_FRAME_OFFSET - BURST_HEADER_SIZE + mat_code_length + pkt->size],
-           0, TRUEHD_FRAME_OFFSET - pkt->size - mat_code_length);
+    if (ctx->space > 0) {
+        int min = FFMIN(ctx->remaining, ctx->space);
+        memset(ctx->buf, 0, min);
+        ctx->buf += min;
+        ctx->remaining -= min;
+        ctx->space -= min;
+    }
 
-    if (++ctx->hd_buf_count < 24){
+    if (pkt->size <= ctx->remaining) {
+        memcpy(ctx->buf, pkt->data, pkt->size);
+        ctx->remaining -= pkt->size;
+        ctx->space     -= pkt->size;
+        ctx->buf += pkt->size;
         ctx->pkt_offset = 0;
         return 0;
     }
-    memcpy(&ctx->hd_buf[MAT_FRAME_SIZE - sizeof(mat_end_code)], mat_end_code, sizeof(mat_end_code));
-    ctx->hd_buf_count = 0;
+    memcpy(ctx->buf, pkt->data, ctx->remaining);
+    ctx->buf += ctx->remaining; // ctx->remaining is still needed after a mat_code was written below
+    ctx->space -= ctx->remaining;
+
+    if (ctx->buf == ctx->hd_buf + MAT_HALF_FRAME + sizeof(mat_start_code)) {
+        // write mat_middle_code always at the exact same position
+        memcpy(ctx->buf, mat_middle_code, sizeof(mat_middle_code));
+        ctx->buf += sizeof(mat_middle_code);
+        ctx->space -= sizeof(mat_middle_code);
+        if (ctx->space > 0 && !ctx->remaining) {
+            memset(ctx->buf, 0, ctx->space);
+            memcpy(ctx->buf + ctx->space, pkt->data, pkt->size);
+            ctx->buf += ctx->space + pkt->size;
+            ctx->remaining = MAT_HALF_FRAME - ctx->space - pkt->size;
+            ctx->space = -pkt->size;
+        } else {
+            memcpy(ctx->buf, pkt->data + ctx->remaining, pkt->size - ctx->remaining);
+            ctx->buf += pkt->size - ctx->remaining;
+            ctx->space -= pkt->size - ctx->remaining;
+            ctx->remaining = MAT_HALF_FRAME - pkt->size + ctx->remaining;
+        }
+    }
+
+    if (ctx->buf != ctx->hd_buf + MAT_FRAME_SIZE - sizeof(mat_end_code))
+        return 0;
 
+    // write mat_end_code exactly at the end of the mat frame
+    memcpy(ctx->buf, mat_end_code, sizeof(mat_end_code));
+    ctx->buf = ctx->hd_buf;
+    ctx->space -= sizeof(mat_end_code) + 16; // preamble and padding
+    if (ctx->space > 0) {
+        int min = FFMIN(ctx->space, sizeof(mat_start_code));
+        ctx->last_frame_size = pkt->size - ctx->remaining + ctx->space - min;
+        memset(ctx->last_frame, 0, ctx->space - min);
+        memcpy(ctx->last_frame + ctx->space - min, pkt->data + ctx->remaining, ctx->last_frame_size - ctx->space + min);
+    } else {
+        ctx->last_frame_size = pkt->size - ctx->remaining;
+        memcpy(ctx->last_frame, pkt->data + ctx->remaining, ctx->last_frame_size);
+    }
+    ctx->remaining   = 0;
     ctx->data_type   = IEC61937_TRUEHD;
     ctx->pkt_offset  = 61440;
     ctx->out_buf     = ctx->hd_buf;
@@ -465,9 +526,17 @@  static int spdif_write_header(AVFormatContext *s)
     case AV_CODEC_ID_TRUEHD:
     case AV_CODEC_ID_MLP:
         ctx->header_info = spdif_header_truehd;
+        ctx->space = sizeof(mat_start_code);
+        ctx->buf =
         ctx->hd_buf = av_malloc(MAT_FRAME_SIZE);
         if (!ctx->hd_buf)
             return AVERROR(ENOMEM);
+        ctx->last_frame = av_malloc(MAT_FRAME_SIZE);
+        if (!ctx->last_frame) {
+            av_freep(&ctx->hd_buf);
+            return AVERROR(ENOMEM);
+        }
+        ctx->last_ts = -1;
         break;
     default:
         avpriv_report_missing_feature(s, "Codec %d",
@@ -482,6 +551,7 @@  static int spdif_write_trailer(AVFormatContext *s)
     IEC61937Context *ctx = s->priv_data;
     av_freep(&ctx->buffer);
     av_freep(&ctx->hd_buf);
+    av_freep(&ctx->last_frame);
     return 0;
 }
 
-- 
1.7.10.4