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(-)
@@ -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