mpegtsenc: Add support for muxing Opus in MPEG-TS

Signed-off-by: Sebastian Dröge <sebastian@centricular.com>
Previous version reviewed-by: Kieran Kunhya <kierank@obe.tv>
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
This commit is contained in:
Sebastian Dröge 2015-11-05 23:35:42 +01:00 committed by Michael Niedermayer
parent 79c4a338e4
commit 01509cdf92
1 changed files with 179 additions and 1 deletions

View File

@ -227,6 +227,9 @@ typedef struct MpegTSWriteStream {
uint8_t *payload;
AVFormatContext *amux;
AVRational user_tb;
/* For Opus */
int opus_queued_samples;
} MpegTSWriteStream;
static void mpegts_write_pat(AVFormatContext *s)
@ -314,6 +317,9 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
case AV_CODEC_ID_TRUEHD:
stream_type = STREAM_TYPE_AUDIO_TRUEHD;
break;
case AV_CODEC_ID_OPUS:
stream_type = STREAM_TYPE_PRIVATE_DATA;
break;
default:
stream_type = STREAM_TYPE_PRIVATE_DATA;
break;
@ -340,6 +346,82 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
*q++ = 'S';
*q++ = 'D';
}
if (st->codec->codec_id==AV_CODEC_ID_OPUS) {
/* 6 bytes registration descriptor, 4 bytes Opus audio descriptor */
if (q - data > SECTION_LENGTH - 6 - 4) {
err = 1;
break;
}
*q++ = 0x05; /* MPEG-2 registration descriptor*/
*q++ = 4;
*q++ = 'O';
*q++ = 'p';
*q++ = 'u';
*q++ = 's';
*q++ = 0x7f; /* DVB extension descriptor */
*q++ = 2;
*q++ = 0x80;
if (st->codec->extradata && st->codec->extradata_size >= 19) {
if (st->codec->extradata[18] == 0 && st->codec->channels <= 2) {
/* RTP mapping family */
*q++ = st->codec->channels;
} else if (st->codec->extradata[18] == 1 && st->codec->channels <= 8 &&
st->codec->extradata_size >= 21 + st->codec->channels) {
static const uint8_t coupled_stream_counts[9] = {
1, 0, 1, 1, 2, 2, 2, 3, 3
};
static const uint8_t channel_map_a[8][8] = {
{0},
{0, 1},
{0, 2, 1},
{0, 1, 2, 3},
{0, 4, 1, 2, 3},
{0, 4, 1, 2, 3, 5},
{0, 4, 1, 2, 3, 5, 6},
{0, 6, 1, 2, 3, 4, 5, 7},
};
static const uint8_t channel_map_b[8][8] = {
{0},
{0, 1},
{0, 1, 2},
{0, 1, 2, 3},
{0, 1, 2, 3, 4},
{0, 1, 2, 3, 4, 5},
{0, 1, 2, 3, 4, 5, 6},
{0, 1, 2, 3, 4, 5, 6, 7},
};
/* Vorbis mapping family */
if (st->codec->extradata[19] == st->codec->channels - coupled_stream_counts[st->codec->channels] &&
st->codec->extradata[20] == coupled_stream_counts[st->codec->channels] &&
memcmp(&st->codec->extradata[21], channel_map_a[st->codec->channels], st->codec->channels) == 0) {
*q++ = st->codec->channels;
} else if (st->codec->channels >= 2 && st->codec->extradata[19] == st->codec->channels &&
st->codec->extradata[20] == 0 &&
memcmp(&st->codec->extradata[21], channel_map_b[st->codec->channels], st->codec->channels) == 0) {
*q++ = st->codec->channels | 0x80;
} else {
/* Unsupported, could write an extended descriptor here */
av_log(s, AV_LOG_ERROR, "Unsupported Opus Vorbis-style channel mapping");
*q++ = 0xff;
}
} else {
/* Unsupported */
av_log(s, AV_LOG_ERROR, "Unsupported Opus channel mapping for family %d", st->codec->extradata[18]);
*q++ = 0xff;
}
} else if (st->codec->channels <= 2) {
/* Assume RTP mapping family */
*q++ = st->codec->channels;
} else {
/* Unsupported */
av_log(s, AV_LOG_ERROR, "Unsupported Opus channel mapping");
*q++ = 0xff;
}
}
if (lang) {
char *p;
@ -1261,6 +1343,58 @@ static int check_hevc_startcode(AVFormatContext *s, const AVStream *st, const AV
return 0;
}
/* Based on GStreamer's gst-plugins-base/ext/ogg/gstoggstream.c
* Released under the LGPL v2.1+, written by
* Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
*/
static int opus_get_packet_samples(AVFormatContext *s, AVPacket *pkt)
{
static const int durations[32] = {
480, 960, 1920, 2880, /* Silk NB */
480, 960, 1920, 2880, /* Silk MB */
480, 960, 1920, 2880, /* Silk WB */
480, 960, /* Hybrid SWB */
480, 960, /* Hybrid FB */
120, 240, 480, 960, /* CELT NB */
120, 240, 480, 960, /* CELT NB */
120, 240, 480, 960, /* CELT NB */
120, 240, 480, 960, /* CELT NB */
};
int toc, frame_duration, nframes, duration;
if (pkt->size < 1)
return 0;
toc = pkt->data[0];
frame_duration = durations[toc >> 3];
switch (toc & 3) {
case 0:
nframes = 1;
break;
case 1:
nframes = 2;
break;
case 2:
nframes = 2;
break;
case 3:
if (pkt->size < 2)
return 0;
nframes = pkt->data[1] & 63;
break;
}
duration = nframes * frame_duration;
if (duration > 5760) {
av_log(s, AV_LOG_WARNING,
"Opus packet duration > 120 ms, invalid");
return 0;
}
return duration;
}
static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
{
AVStream *st = s->streams[pkt->stream_index];
@ -1271,6 +1405,7 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
MpegTSWriteStream *ts_st = st->priv_data;
const int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) * 2;
int64_t dts = pkt->dts, pts = pkt->pts;
int opus_samples = 0;
if (ts->reemit_pat_pmt) {
av_log(s, AV_LOG_WARNING,
@ -1370,6 +1505,44 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
int ret = check_hevc_startcode(s, st, pkt);
if (ret < 0)
return ret;
} else if (st->codec->codec_id == AV_CODEC_ID_OPUS) {
if (pkt->size < 2) {
av_log(s, AV_LOG_ERROR, "Opus packet too short\n");
return AVERROR_INVALIDDATA;
}
/* Add Opus control header */
if ((AV_RB16(pkt->data) >> 5) != 0x3ff) {
int i, n;
opus_samples = opus_get_packet_samples(s, pkt);
data = av_malloc(pkt->size + 2 + pkt->size / 255 + 1);
if (!data)
return AVERROR(ENOMEM);
/* TODO: Write trim if needed */
data[0] = 0x7f;
data[1] = 0xe0;
n = pkt->size;
i = 2;
do {
data[i] = FFMIN(n, 255);
n -= 255;
i++;
} while (n >= 0);
av_assert0(2 + pkt->size / 255 + 1 == i);
memcpy(data + i, pkt->data, pkt->size);
buf = data;
size = pkt->size + 2 + pkt->size / 255 + 1;
} else {
/* TODO: Can we get TS formatted data here? If so we will
* need to count the samples of that too! */
av_log(s, AV_LOG_WARNING, "Got MPEG-TS formatted Opus data, unhandled");
}
}
if (pkt->dts != AV_NOPTS_VALUE) {
@ -1390,11 +1563,13 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
if (ts_st->payload_size && (ts_st->payload_size + size > ts->pes_payload_size ||
(dts != AV_NOPTS_VALUE && ts_st->payload_dts != AV_NOPTS_VALUE &&
av_compare_ts(dts - ts_st->payload_dts, st->time_base,
s->max_delay, AV_TIME_BASE_Q) >= 0))) {
s->max_delay, AV_TIME_BASE_Q) >= 0) ||
ts_st->opus_queued_samples + opus_samples >= 5760 /* 120ms */)) {
mpegts_write_pes(s, st, ts_st->payload, ts_st->payload_size,
ts_st->payload_pts, ts_st->payload_dts,
ts_st->payload_flags & AV_PKT_FLAG_KEY);
ts_st->payload_size = 0;
ts_st->opus_queued_samples = 0;
}
if (st->codec->codec_type != AVMEDIA_TYPE_AUDIO || size > ts->pes_payload_size) {
@ -1402,6 +1577,7 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
// for video and subtitle, write a single pes packet
mpegts_write_pes(s, st, buf, size, pts, dts,
pkt->flags & AV_PKT_FLAG_KEY);
ts_st->opus_queued_samples = 0;
av_free(data);
return 0;
}
@ -1414,6 +1590,7 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
memcpy(ts_st->payload + ts_st->payload_size, buf, size);
ts_st->payload_size += size;
ts_st->opus_queued_samples += opus_samples;
av_free(data);
@ -1433,6 +1610,7 @@ static void mpegts_write_flush(AVFormatContext *s)
ts_st->payload_pts, ts_st->payload_dts,
ts_st->payload_flags & AV_PKT_FLAG_KEY);
ts_st->payload_size = 0;
ts_st->opus_queued_samples = 0;
}
}
}