[FFmpeg-devel] [PATCHv2 1/2] mpegtsenc: Add support for muxing Opus in MPEG-TS

Sebastian Dröge slomo at coaxion.net
Sun Nov 1 22:44:49 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..0674064 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