diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c index 4d74252f13..96d277e042 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 >= 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 + */ +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; } } }