[FFmpeg-devel] [PATCH 1/2] mpegtsenc: Add support for muxing Opus in MPEG-TS
Sebastian Dröge
slomo at coaxion.net
Sun Nov 1 21:20:19 CET 2015
From: Sebastian Dröge <sebastian at centricular.com>
Signed-off-by: Sebastian Dröge <sebastian at centricular.com>
---
libavformat/mpegtsenc.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 179 insertions(+), 1 deletion(-)
diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index 4d74252..a7e78ac 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -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 >= 22 + 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 at 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;
}
}
}
--
2.6.2
More information about the ffmpeg-devel
mailing list