[FFmpeg-devel] [PATCH]lavf/spdifenc: Use a more flexible buffer model for TrueHD/MAT
Carl Eugen Hoyos
ceffmpeg at gmail.com
Fri Feb 15 02:14:23 EET 2019
2019-02-15 1:12 GMT+01:00, Carl Eugen Hoyos <ceffmpeg at gmail.com>:
> 2019-02-15 0:23 GMT+01:00, Hendrik Leppkes <h.leppkes at gmail.com>:
>> On Thu, Feb 14, 2019 at 8:11 PM Carl Eugen Hoyos <ceffmpeg at 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.
>
> I had already changed this (only for the actual timestamp difference),
> thank you!
>
>> - 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).
>
> That is not supported yet, could you provide such a problematic sample?
> (Including the output if possible)
>
> New patch attached, Carl Eugen
And inlined to simplify reviewing:
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, 0x1
1 };
-
- 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, 0x8
1, 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;
}
More information about the ffmpeg-devel
mailing list