mirror of https://git.ffmpeg.org/ffmpeg.git
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;
|
uint8_t *payload;
|
||||||
AVFormatContext *amux;
|
AVFormatContext *amux;
|
||||||
AVRational user_tb;
|
AVRational user_tb;
|
||||||
|
|
||||||
|
/* For Opus */
|
||||||
|
int opus_queued_samples;
|
||||||
} MpegTSWriteStream;
|
} MpegTSWriteStream;
|
||||||
|
|
||||||
static void mpegts_write_pat(AVFormatContext *s)
|
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:
|
case AV_CODEC_ID_TRUEHD:
|
||||||
stream_type = STREAM_TYPE_AUDIO_TRUEHD;
|
stream_type = STREAM_TYPE_AUDIO_TRUEHD;
|
||||||
break;
|
break;
|
||||||
|
case AV_CODEC_ID_OPUS:
|
||||||
|
stream_type = STREAM_TYPE_PRIVATE_DATA;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
stream_type = STREAM_TYPE_PRIVATE_DATA;
|
stream_type = STREAM_TYPE_PRIVATE_DATA;
|
||||||
break;
|
break;
|
||||||
|
@ -340,6 +346,82 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
|
||||||
*q++ = 'S';
|
*q++ = 'S';
|
||||||
*q++ = 'D';
|
*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) {
|
if (lang) {
|
||||||
char *p;
|
char *p;
|
||||||
|
@ -1261,6 +1343,58 @@ static int check_hevc_startcode(AVFormatContext *s, const AVStream *st, const AV
|
||||||
return 0;
|
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)
|
static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
|
||||||
{
|
{
|
||||||
AVStream *st = s->streams[pkt->stream_index];
|
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;
|
MpegTSWriteStream *ts_st = st->priv_data;
|
||||||
const int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) * 2;
|
const int64_t delay = av_rescale(s->max_delay, 90000, AV_TIME_BASE) * 2;
|
||||||
int64_t dts = pkt->dts, pts = pkt->pts;
|
int64_t dts = pkt->dts, pts = pkt->pts;
|
||||||
|
int opus_samples = 0;
|
||||||
|
|
||||||
if (ts->reemit_pat_pmt) {
|
if (ts->reemit_pat_pmt) {
|
||||||
av_log(s, AV_LOG_WARNING,
|
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);
|
int ret = check_hevc_startcode(s, st, pkt);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
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) {
|
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 ||
|
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 &&
|
(dts != AV_NOPTS_VALUE && ts_st->payload_dts != AV_NOPTS_VALUE &&
|
||||||
av_compare_ts(dts - ts_st->payload_dts, st->time_base,
|
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,
|
mpegts_write_pes(s, st, ts_st->payload, ts_st->payload_size,
|
||||||
ts_st->payload_pts, ts_st->payload_dts,
|
ts_st->payload_pts, ts_st->payload_dts,
|
||||||
ts_st->payload_flags & AV_PKT_FLAG_KEY);
|
ts_st->payload_flags & AV_PKT_FLAG_KEY);
|
||||||
ts_st->payload_size = 0;
|
ts_st->payload_size = 0;
|
||||||
|
ts_st->opus_queued_samples = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st->codec->codec_type != AVMEDIA_TYPE_AUDIO || size > ts->pes_payload_size) {
|
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
|
// for video and subtitle, write a single pes packet
|
||||||
mpegts_write_pes(s, st, buf, size, pts, dts,
|
mpegts_write_pes(s, st, buf, size, pts, dts,
|
||||||
pkt->flags & AV_PKT_FLAG_KEY);
|
pkt->flags & AV_PKT_FLAG_KEY);
|
||||||
|
ts_st->opus_queued_samples = 0;
|
||||||
av_free(data);
|
av_free(data);
|
||||||
return 0;
|
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);
|
memcpy(ts_st->payload + ts_st->payload_size, buf, size);
|
||||||
ts_st->payload_size += size;
|
ts_st->payload_size += size;
|
||||||
|
ts_st->opus_queued_samples += opus_samples;
|
||||||
|
|
||||||
av_free(data);
|
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_pts, ts_st->payload_dts,
|
||||||
ts_st->payload_flags & AV_PKT_FLAG_KEY);
|
ts_st->payload_flags & AV_PKT_FLAG_KEY);
|
||||||
ts_st->payload_size = 0;
|
ts_st->payload_size = 0;
|
||||||
|
ts_st->opus_queued_samples = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue