From 25fa80c8dfa6dd9ef4271abac94620458044a507 Mon Sep 17 00:00:00 2001
From: Carl Eugen Hoyos <ceffmpeg@gmail.com>
Date: Fri, 15 Feb 2019 01:11:52 +0100
Subject: [PATCH] lavf/spdifenc: Use a more flexible buffer model for
 TrueHD/MAT.

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

diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c
index 9514ff8..71708cf 100644
--- a/libavformat/spdifenc.c
+++ b/libavformat/spdifenc.c
@@ -76,6 +76,14 @@ 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
+    int ratebits;                   ///< TrueHD ratebits, needed to calculate frame spacing
+
     /* AVOptions: */
     int dtshd_rate;
     int dtshd_fallback;
@@ -382,56 +390,118 @@ 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)
+        if (AV_RB32(&pkt->data[4]) == 0xf8726fba) {
+            ctx->ratebits = pkt->data[8] >> 4;
+        } else {
+            ctx->pkt_offset = 0;
+            return 0;
+        }
+    if (ctx->last_ts >= 0) {
+        uint16_t distance = AV_RB16(&pkt->data[2]) - ctx->last_ts;
+        distance *= 64 >> ctx->ratebits;
+        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;
+    }
+    if (FFABS(ctx->space) >= MAT_HALF_FRAME / 4) {
+        av_log(s, AV_LOG_ERROR, "Invalid spacing: %d, resetting\n", ctx->space);
+        ctx->space = -pkt->size;
+    }
+    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;
+        }
+        return 0;
+    }
 
+    av_assert0(ctx->buf == ctx->hd_buf + MAT_FRAME_SIZE - sizeof(mat_end_code));
+    // 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;
+        if (ctx->last_frame_size >= MAT_HALF_FRAME / 4) {
+            av_log(s, AV_LOG_ERROR, "Invalid remaining number of bytes: %d\n", ctx->last_frame_size);
+            return AVERROR(EINVAL);
+        }
+        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 +535,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 +560,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

