mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-25 16:52:31 +00:00
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:
parent
79c4a338e4
commit
01509cdf92
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user