tsdemux: Add support for Opus

Code partially based on
  https://git.videolan.org/?p=ffmpeg.git;a=commit;h=74141f693ded2fbf75af56fff309d2db35183635
and based on the spec draft at
  https://wiki.xiph.org/OpusTS

Makes it possible to demux
  http://www.obe.tv/Downloads/opus.ts

https://bugzilla.gnome.org/show_bug.cgi?id=757049
This commit is contained in:
Sebastian Dröge 2015-10-24 14:27:43 +03:00
parent 23a9e4323a
commit 1e785a3778
3 changed files with 393 additions and 25 deletions

View file

@ -16,7 +16,7 @@ libgstmpegtsdemux_la_LIBADD = \
$(top_builddir)/gst-libs/gst/codecparsers/libgstcodecparsers-$(GST_API_VERSION).la \
$(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_API_VERSION) \
-lgstpbutils-@GST_API_VERSION@ \
$(GST_BASE_LIBS) $(GST_LIBS)
$(GST_BASE_LIBS) $(GST_LIBS) $(LIBM)
libgstmpegtsdemux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
libgstmpegtsdemux_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)

View file

@ -232,5 +232,6 @@
#define DRF_ID_ETV1 0x45545631
#define DRF_ID_HEVC 0x48455643
#define DRF_ID_KLVA 0x4b4c5641 /* defined in RP217 */
#define DRF_ID_OPUS 0x4f707573
#endif /* __GST_MPEG_DESC_H__ */

View file

@ -37,6 +37,7 @@
#include <glib.h>
#include <gst/tag/tag.h>
#include <gst/pbutils/pbutils.h>
#include <gst/base/base.h>
#include "mpegtsbase.h"
#include "tsdemux.h"
@ -46,7 +47,8 @@
#include "pesparse.h"
#include <gst/codecparsers/gsth264parser.h>
#include <gst/codecparsers/gstmpegvideoparser.h>
#include <gst/base/gstbytewriter.h>
#include <math.h>
/*
* tsdemux
@ -230,6 +232,7 @@ struct _TSDemuxStream
"mute = (boolean) { FALSE, TRUE }; " \
"audio/x-ac3; audio/x-eac3;" \
"audio/x-dts;" \
"audio/x-opus;" \
"audio/x-private-ts-lpcm" \
)
@ -1189,6 +1192,211 @@ create_pad_for_stream (MpegTSBase * base, MpegTSBaseStream * bstream,
is_audio = TRUE;
caps = gst_caps_new_empty_simple ("audio/x-smpte-302m");
break;
case DRF_ID_OPUS:
desc = mpegts_get_descriptor_from_stream (bstream,
GST_MTS_DESC_DVB_EXTENSION);
if (desc != NULL && desc->tag_extension == 0x80 && desc->length >= 1) { /* User defined (provisional Opus) */
guint8 channel_config_code;
GstByteReader br;
/* skip tag, length and tag_extension */
gst_byte_reader_init (&br, desc->data + 3, desc->length - 1);
channel_config_code = gst_byte_reader_get_uint8_unchecked (&br);
if ((channel_config_code & 0x8f) <= 8) {
static const guint8 coupled_stream_counts[9] = {
1, 0, 1, 1, 2, 2, 2, 3, 3
};
static const guint8 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 guint8 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},
};
guint8 codecdata[22 + 256] = {
'O', 'p', 'u', 's',
'H', 'e', 'a', 'd',
1, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0,
};
GstBuffer *codec_data_buf;
guint channels;
GValue v_arr = G_VALUE_INIT;
GValue v_buf = G_VALUE_INIT;
GstTagList *tags;
gint codecdata_len = -1;
channels = channel_config_code ? (channel_config_code & 0x0f) : 2;
codecdata[9] = channels;
if (channel_config_code == 0 || channel_config_code == 0x80) {
/* Dual Mono */
codecdata[18] = 255;
if (channel_config_code == 0) {
codecdata[19] = 1;
codecdata[20] = 1;
} else {
codecdata[19] = 2;
codecdata[20] = 0;
}
memcpy (&codecdata[21], channel_map_a[1], channels);
codecdata_len = 24;
} else if (channel_config_code <= 8) {
codecdata[18] = (channels > 2) ? 1 : 0;
codecdata[19] =
channel_config_code -
coupled_stream_counts[channel_config_code];
codecdata[20] = coupled_stream_counts[channel_config_code];
memcpy (&codecdata[21], channel_map_a[channels - 1], channels);
if (codecdata[18] == 0)
codecdata_len = 19;
else
codecdata_len = 21 + channels;
} else if (channel_config_code >= 0x82
&& channel_config_code <= 0x88) {
codecdata[18] = 1;
codecdata[19] = channels;
codecdata[20] = 0;
memcpy (&codecdata[21], channel_map_b[channels - 1], channels);
codecdata_len = 21 + channels;
} else if (channel_config_code == 0x81) {
guint8 channel_count, mapping_family;
if (gst_byte_reader_get_remaining (&br) < 2) {
GST_WARNING_OBJECT (demux,
"Invalid Opus descriptor with extended channel configuration");
break;
}
channel_count = gst_byte_reader_get_uint8_unchecked (&br);
mapping_family = gst_byte_reader_get_uint8_unchecked (&br);
/* Overwrite values from above */
if (channel_count == 0) {
GST_WARNING_OBJECT (demux,
"Invalid Opus descriptor with extended channel configuration");
break;
}
channels = channel_count;
codecdata[9] = channels;
if (mapping_family == 0 && channel_count <= 2) {
codecdata[18] = 0;
codecdata[19] =
channel_count - coupled_stream_counts[channel_count];
codecdata[20] = coupled_stream_counts[channel_count];
codecdata_len = 19;
} else {
GstBitReader breader;
guint8 stream_count_minus_one, coupled_stream_count;
gint stream_count_minus_one_len, coupled_stream_count_len;
gint channel_mapping_len, i;
codecdata[18] = mapping_family;
gst_bit_reader_init (&breader,
gst_byte_reader_get_data_unchecked
(&br, gst_byte_reader_get_remaining
(&br)), gst_byte_reader_get_remaining (&br));
stream_count_minus_one_len = ceil (log2 (channel_count));
if (!gst_bit_reader_get_bits_uint8 (&breader,
&stream_count_minus_one,
stream_count_minus_one_len)) {
GST_WARNING_OBJECT (demux,
"Invalid Opus descriptor with extended channel configuration");
break;
}
codecdata[19] = stream_count_minus_one + 1;
coupled_stream_count_len =
ceil (log2 (stream_count_minus_one_len + 2));
if (!gst_bit_reader_get_bits_uint8 (&breader,
&coupled_stream_count, coupled_stream_count_len)) {
GST_WARNING_OBJECT (demux,
"Invalid Opus descriptor with extended channel configuration");
break;
}
codecdata[20] = coupled_stream_count;
channel_mapping_len =
ceil (log2 (stream_count_minus_one + 1 +
coupled_stream_count + 1));
for (i = 0; i < channel_count; i++) {
if (!gst_bit_reader_get_bits_uint8 (&breader,
&codecdata[21 + i], channel_mapping_len)) {
GST_WARNING_OBJECT (demux,
"Invalid Opus descriptor with extended channel configuration");
break;
}
}
/* error above */
if (i != channel_count)
break;
codecdata_len = 22 + channel_count;
}
} else {
g_assert_not_reached ();
}
if (codecdata_len != -1) {
is_audio = TRUE;
template = gst_static_pad_template_get (&audio_template);
name = g_strdup_printf ("audio_%04x", bstream->pid);
caps = gst_caps_new_empty_simple ("audio/x-opus");
g_value_init (&v_arr, GST_TYPE_ARRAY);
g_value_init (&v_buf, GST_TYPE_BUFFER);
codec_data_buf =
gst_buffer_new_wrapped (g_memdup (codecdata, codecdata_len),
codecdata_len);
gst_value_take_buffer (&v_buf, codec_data_buf);
gst_value_array_append_and_take_value (&v_arr, &v_buf);
tags = gst_tag_list_new_empty ();
g_value_init (&v_buf, GST_TYPE_BUFFER);
codec_data_buf =
gst_tag_list_to_vorbiscomment_buffer (tags,
(const guint8 *) "OpusTags", 8, "No comments");
gst_tag_list_unref (tags);
gst_value_take_buffer (&v_buf, codec_data_buf);
gst_value_array_append_and_take_value (&v_arr, &v_buf);
gst_caps_set_value (caps, "streamheader", &v_arr);
g_value_unset (&v_arr);
}
} else {
GST_WARNING_OBJECT (demux,
"unexpected channel config code 0x%02x", channel_config_code);
}
} else {
GST_WARNING_OBJECT (demux, "Opus, but no extension descriptor");
}
break;
case DRF_ID_HEVC:
is_video = TRUE;
caps = gst_caps_new_simple ("video/x-h265",
@ -2116,14 +2324,108 @@ gst_ts_demux_check_and_sync_streams (GstTSDemux * demux, GstClockTime time)
}
}
static GstBufferList *
parse_opus_access_unit (TSDemuxStream * stream)
{
GstByteReader reader;
GstBufferList *buffer_list = NULL;
buffer_list = gst_buffer_list_new ();
gst_byte_reader_init (&reader, stream->data, stream->current_size);
do {
GstBuffer *buffer;
guint16 id;
guint au_size = 0;
guint8 b;
gboolean start_trim_flag, end_trim_flag, control_extension_flag;
guint16 start_trim = 0, end_trim = 0;
guint8 *packet_data;
guint packet_size;
if (!gst_byte_reader_get_uint16_be (&reader, &id))
goto error;
/* No control header */
if ((id >> 5) != 0x3ff)
goto error;
do {
if (!gst_byte_reader_get_uint8 (&reader, &b))
goto error;
au_size += b;
} while (b == 0xff);
start_trim_flag = (id >> 4) & 0x1;
end_trim_flag = (id >> 3) & 0x1;
control_extension_flag = (id >> 2) & 0x1;
if (start_trim_flag) {
if (!gst_byte_reader_get_uint16_be (&reader, &start_trim))
goto error;
start_trim >>= 3;
}
if (end_trim_flag) {
if (!gst_byte_reader_get_uint16_be (&reader, &end_trim))
goto error;
end_trim >>= 3;
}
if (control_extension_flag) {
if (!gst_byte_reader_get_uint8 (&reader, &b))
goto error;
if (!gst_byte_reader_skip (&reader, b))
goto error;
}
packet_size = au_size;
/* FIXME: this should be
* packet_size = au_size - gst_byte_reader_get_pos (&reader);
* but ffmpeg and the only available sample stream from obe.tv
* are not including the control header size in au_size
*/
if (gst_byte_reader_get_remaining (&reader) < packet_size)
goto error;
if (!gst_byte_reader_dup_data (&reader, packet_size, &packet_data))
goto error;
buffer = gst_buffer_new_wrapped (packet_data, packet_size);
gst_buffer_list_add (buffer_list, buffer);
/* FIXME: Do something with start_trim and end_trim */
if (start_trim != 0 || end_trim != 0)
GST_FIXME
("Handling of Opus start_trim (%u) and end_trim (%u) not implemented",
start_trim, end_trim);
} while (gst_byte_reader_get_remaining (&reader) > 0);
g_free (stream->data);
stream->data = NULL;
stream->current_size = 0;
return buffer_list;
error:
{
GST_ERROR ("Failed to parse Opus access unit");
g_free (stream->data);
stream->data = NULL;
stream->current_size = 0;
gst_buffer_list_unref (buffer_list);
return NULL;
}
}
static GstFlowReturn
gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream)
{
GstFlowReturn res = GST_FLOW_OK;
#ifndef GST_DISABLE_GST_DEBUG
MpegTSBaseStream *bs = (MpegTSBaseStream *) stream;
#endif
GstBuffer *buffer = NULL;
GstBufferList *buffer_list = NULL;
GST_DEBUG_OBJECT (stream->pad,
"stream:%p, pid:0x%04x stream_type:%d state:%d", stream, bs->pid,
@ -2158,7 +2460,24 @@ gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream)
GST_DEBUG_OBJECT (stream->pad,
"Got Keyframe, ready to go at %" GST_TIME_FORMAT,
GST_TIME_ARGS (stream->pts));
buffer = gst_buffer_new_wrapped (stream->data, stream->current_size);
if (bs->stream_type == GST_MPEGTS_STREAM_TYPE_PRIVATE_PES_PACKETS &&
bs->registration_id == DRF_ID_OPUS) {
buffer_list = parse_opus_access_unit (stream);
if (!buffer_list) {
res = GST_FLOW_ERROR;
goto beach;
}
if (gst_buffer_list_length (buffer_list) == 1) {
buffer = gst_buffer_ref (gst_buffer_list_get (buffer_list, 0));
gst_buffer_list_unref (buffer_list);
buffer_list = NULL;
}
} else {
buffer = gst_buffer_new_wrapped (stream->data, stream->current_size);
}
stream->seeked_pts = stream->pts;
stream->seeked_dts = stream->dts;
stream->needs_keyframe = FALSE;
@ -2176,15 +2495,45 @@ gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream)
goto beach;
}
} else {
buffer = gst_buffer_new_wrapped (stream->data, stream->current_size);
if (bs->stream_type == GST_MPEGTS_STREAM_TYPE_PRIVATE_PES_PACKETS &&
bs->registration_id == DRF_ID_OPUS) {
buffer_list = parse_opus_access_unit (stream);
if (!buffer_list) {
res = GST_FLOW_ERROR;
goto beach;
}
if (gst_buffer_list_length (buffer_list) == 1) {
buffer = gst_buffer_ref (gst_buffer_list_get (buffer_list, 0));
gst_buffer_list_unref (buffer_list);
buffer_list = NULL;
}
} else {
buffer = gst_buffer_new_wrapped (stream->data, stream->current_size);
}
if (G_UNLIKELY (stream->pending_ts && !check_pending_buffers (demux))) {
PendingBuffer *pend;
pend = g_slice_new0 (PendingBuffer);
pend->buffer = buffer;
pend->pts = stream->raw_pts;
pend->dts = stream->raw_dts;
stream->pending = g_list_append (stream->pending, pend);
if (buffer) {
PendingBuffer *pend;
pend = g_slice_new0 (PendingBuffer);
pend->buffer = buffer;
pend->pts = stream->raw_pts;
pend->dts = stream->raw_dts;
stream->pending = g_list_append (stream->pending, pend);
} else {
guint i, n;
n = gst_buffer_list_length (buffer_list);
for (i = 0; i < n; i++) {
PendingBuffer *pend;
pend = g_slice_new0 (PendingBuffer);
pend->buffer = gst_buffer_ref (gst_buffer_list_get (buffer_list, i));
pend->pts = i == 0 ? stream->raw_pts : -1;
pend->dts = i == 0 ? stream->raw_dts : -1;
stream->pending = g_list_append (stream->pending, pend);
}
gst_buffer_list_unref (buffer_list);
}
GST_DEBUG ("Not enough information to push buffers yet, storing buffer");
goto beach;
}
@ -2226,34 +2575,52 @@ gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream)
"(seeked PTS: %" GST_TIME_FORMAT " DTS: %" GST_TIME_FORMAT ")",
GST_TIME_ARGS (stream->pts), GST_TIME_ARGS (stream->dts),
GST_TIME_ARGS (stream->seeked_pts), GST_TIME_ARGS (stream->seeked_dts));
gst_buffer_unref (buffer);
if (buffer)
gst_buffer_unref (buffer);
if (buffer_list)
gst_buffer_list_unref (buffer_list);
goto beach;
}
GST_DEBUG_OBJECT (stream->pad, "stream->pts %" GST_TIME_FORMAT,
GST_TIME_ARGS (stream->pts));
/* Decorate buffer or first buffer of the buffer list */
if (buffer_list)
buffer = gst_buffer_list_get (buffer_list, 0);
if (GST_CLOCK_TIME_IS_VALID (stream->pts))
GST_BUFFER_PTS (buffer) = stream->pts;
if (GST_CLOCK_TIME_IS_VALID (stream->dts))
GST_BUFFER_DTS (buffer) = stream->dts;
GST_DEBUG_OBJECT (stream->pad,
"Pushing buffer with PTS: %" GST_TIME_FORMAT " , DTS: %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_PTS (buffer)),
GST_TIME_ARGS (GST_BUFFER_DTS (buffer)));
if (stream->discont)
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
stream->discont = FALSE;
if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (buffer)))
demux->segment.position = GST_BUFFER_DTS (buffer);
else if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (buffer)))
demux->segment.position = GST_BUFFER_PTS (buffer);
if (buffer_list)
buffer = NULL;
res = gst_pad_push (stream->pad, buffer);
/* Record that a buffer was pushed */
stream->nb_out_buffers += 1;
GST_DEBUG_OBJECT (stream->pad,
"Pushing buffer%s with PTS: %" GST_TIME_FORMAT " , DTS: %"
GST_TIME_FORMAT, (buffer_list ? "list" : ""), GST_TIME_ARGS (stream->pts),
GST_TIME_ARGS (stream->dts));
if (GST_CLOCK_TIME_IS_VALID (stream->dts))
demux->segment.position = stream->dts;
else if (GST_CLOCK_TIME_IS_VALID (stream->pts))
demux->segment.position = stream->pts;
if (buffer) {
res = gst_pad_push (stream->pad, buffer);
/* Record that a buffer was pushed */
stream->nb_out_buffers += 1;
} else {
guint n = gst_buffer_list_length (buffer_list);
res = gst_pad_push_list (stream->pad, buffer_list);
/* Record that a buffer was pushed */
stream->nb_out_buffers += n;
}
GST_DEBUG_OBJECT (stream->pad, "Returned %s", gst_flow_get_name (res));
res = gst_flow_combiner_update_flow (demux->flowcombiner, res);
GST_DEBUG_OBJECT (stream->pad, "combined %s", gst_flow_get_name (res));