mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-23 07:38:16 +00:00
dashdemux: implement ISOBMFF profile handling
The ISOBMFF profile allows definind subsegments in a segment. At those subsegment boundaries the client can switch from one representation to another as they have aligned indexes. To handle those the 'sidx' index is parsed from the stream and the entries point to pts/offset of the samples in the stream. Knowing that the entries are aligned in the different representation allows the client to switch mid fragment. In this profile a single fragment is used per representation and the subsegments are contained in this fragment. To notify the superclass about the subsegment boundary the chunk_received function returns a special flow return that indicates that. In this case, the super class will check if a more suitable bitrate is available and will change to the same subsegment in this new representation. It also requires special handling of the position in the stream as the fragment advancing is now done by incrementing the index of the subsegment. It will only advance to the next fragment once all subsegments have been downloaded. https://bugzilla.gnome.org/show_bug.cgi?id=741248
This commit is contained in:
parent
f5b98f24c2
commit
3055bb331a
2 changed files with 264 additions and 38 deletions
|
@ -200,6 +200,8 @@ static GstFlowReturn gst_dash_demux_stream_seek (GstAdaptiveDemuxStream *
|
|||
stream, GstClockTime ts);
|
||||
static GstFlowReturn
|
||||
gst_dash_demux_stream_advance_fragment (GstAdaptiveDemuxStream * stream);
|
||||
static void
|
||||
gst_dash_demux_stream_advance_subfragment (GstAdaptiveDemuxStream * stream);
|
||||
static gboolean gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream *
|
||||
stream, guint64 bitrate);
|
||||
static gint64
|
||||
|
@ -222,6 +224,10 @@ static GstCaps *gst_dash_demux_get_input_caps (GstDashDemux * demux,
|
|||
GstActiveStream * stream);
|
||||
static GstPad *gst_dash_demux_create_pad (GstDashDemux * demux);
|
||||
|
||||
#define SIDX(s) (&(s)->sidx_parser.sidx)
|
||||
#define SIDX_ENTRY(s,i) (&(SIDX(s)->entries[(i)]))
|
||||
#define SIDX_CURRENT_ENTRY(s) SIDX_ENTRY(s, SIDX(s)->entry_index)
|
||||
|
||||
#define gst_dash_demux_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstDashDemux, gst_dash_demux, GST_TYPE_ADAPTIVE_DEMUX,
|
||||
GST_DEBUG_CATEGORY_INIT (gst_dash_demux_debug, "dashdemux", 0,
|
||||
|
@ -462,6 +468,7 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux)
|
|||
gst_adaptive_demux_stream_set_tags (GST_ADAPTIVE_DEMUX_STREAM_CAST
|
||||
(stream), tags);
|
||||
stream->index = i;
|
||||
stream->pending_seek_ts = GST_CLOCK_TIME_NONE;
|
||||
gst_isoff_sidx_parser_init (&stream->sidx_parser);
|
||||
}
|
||||
|
||||
|
@ -746,24 +753,16 @@ gst_dash_demux_get_input_caps (GstDashDemux * demux, GstActiveStream * stream)
|
|||
}
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
|
||||
static void
|
||||
gst_dash_demux_stream_update_headers_info (GstAdaptiveDemuxStream * stream)
|
||||
{
|
||||
GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
|
||||
GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
|
||||
GstClockTime ts;
|
||||
GstMediaFragmentInfo fragment;
|
||||
gchar *path = NULL;
|
||||
|
||||
gst_adaptive_demux_stream_fragment_clear (&stream->fragment);
|
||||
|
||||
if (gst_mpd_client_get_next_fragment_timestamp (dashdemux->client,
|
||||
dashstream->index, &ts)) {
|
||||
if (GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER (stream)) {
|
||||
gst_mpd_client_get_next_header (dashdemux->client,
|
||||
&path, dashstream->index,
|
||||
&stream->fragment.header_range_start,
|
||||
&stream->fragment.header_range_end);
|
||||
&stream->fragment.header_range_start, &stream->fragment.header_range_end);
|
||||
|
||||
if (path != NULL && strncmp (path, "http://", 7) != 0) {
|
||||
stream->fragment.header_uri =
|
||||
|
@ -777,8 +776,7 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
|
|||
|
||||
gst_mpd_client_get_next_header_index (dashdemux->client,
|
||||
&path, dashstream->index,
|
||||
&stream->fragment.index_range_start,
|
||||
&stream->fragment.index_range_end);
|
||||
&stream->fragment.index_range_start, &stream->fragment.index_range_end);
|
||||
|
||||
if (path != NULL && strncmp (path, "http://", 7) != 0) {
|
||||
stream->fragment.index_uri =
|
||||
|
@ -788,16 +786,60 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
|
|||
} else {
|
||||
stream->fragment.index_uri = path;
|
||||
}
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
|
||||
{
|
||||
GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
|
||||
GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
|
||||
GstClockTime ts;
|
||||
GstMediaFragmentInfo fragment;
|
||||
gboolean isombff;
|
||||
|
||||
gst_adaptive_demux_stream_fragment_clear (&stream->fragment);
|
||||
|
||||
isombff = gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client);
|
||||
|
||||
if (GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER (stream) && isombff) {
|
||||
gst_dash_demux_stream_update_headers_info (stream);
|
||||
dashstream->sidx_base_offset = stream->fragment.index_range_end + 1;
|
||||
if (dashstream->sidx_index != 0) {
|
||||
/* request only the index to be downloaded as we need to reposition the
|
||||
* stream to a subsegment */
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (gst_mpd_client_get_next_fragment_timestamp (dashdemux->client,
|
||||
dashstream->index, &ts)) {
|
||||
if (GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER (stream)) {
|
||||
gst_dash_demux_stream_update_headers_info (stream);
|
||||
}
|
||||
|
||||
gst_mpd_client_get_next_fragment (dashdemux->client, dashstream->index,
|
||||
&fragment);
|
||||
|
||||
stream->fragment.uri = fragment.uri;
|
||||
if (isombff && dashstream->sidx_index != 0) {
|
||||
GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream);
|
||||
stream->fragment.range_start =
|
||||
dashstream->sidx_base_offset + entry->offset;
|
||||
stream->fragment.timestamp = entry->pts;
|
||||
stream->fragment.duration = entry->duration;
|
||||
if (stream->demux->segment.rate < 0.0) {
|
||||
stream->fragment.range_end =
|
||||
stream->fragment.range_start + entry->size - 1;
|
||||
} else {
|
||||
stream->fragment.range_end = fragment.range_end;
|
||||
}
|
||||
} else {
|
||||
stream->fragment.timestamp = fragment.timestamp;
|
||||
stream->fragment.duration = fragment.duration;
|
||||
stream->fragment.range_start = fragment.range_start;
|
||||
stream->fragment.range_start =
|
||||
MAX (fragment.range_start, dashstream->sidx_base_offset);
|
||||
stream->fragment.range_end = fragment.range_end;
|
||||
}
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
@ -805,22 +847,90 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
|
|||
return GST_FLOW_EOS;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_dash_demux_stream_sidx_seek (GstDashDemuxStream * dashstream,
|
||||
GstClockTime ts)
|
||||
{
|
||||
GstSidxBox *sidx = SIDX (dashstream);
|
||||
gint i;
|
||||
|
||||
/* TODO optimize to a binary search */
|
||||
for (i = 0; i < sidx->entries_count; i++) {
|
||||
if (sidx->entries[i].pts + sidx->entries[i].duration >= ts)
|
||||
break;
|
||||
}
|
||||
sidx->entry_index = i;
|
||||
dashstream->sidx_index = i;
|
||||
if (i < sidx->entries_count)
|
||||
dashstream->sidx_current_remaining = sidx->entries[i].size;
|
||||
else
|
||||
dashstream->sidx_current_remaining = 0;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_dash_demux_stream_seek (GstAdaptiveDemuxStream * stream, GstClockTime ts)
|
||||
{
|
||||
GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
|
||||
GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
|
||||
|
||||
if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) {
|
||||
if (dashstream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
|
||||
gst_dash_demux_stream_sidx_seek (dashstream, ts);
|
||||
} else {
|
||||
/* no index yet, seek when we have it */
|
||||
dashstream->pending_seek_ts = ts;
|
||||
}
|
||||
}
|
||||
|
||||
gst_mpd_client_stream_seek (dashdemux->client, dashstream->active_stream, ts);
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_dash_demux_stream_advance_subfragment (GstAdaptiveDemuxStream * stream)
|
||||
{
|
||||
GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
|
||||
|
||||
GstSidxBox *sidx = SIDX (dashstream);
|
||||
gboolean fragment_finished = TRUE;
|
||||
|
||||
if (stream->demux->segment.rate > 0.0) {
|
||||
sidx->entry_index++;
|
||||
if (sidx->entry_index < sidx->entries_count) {
|
||||
fragment_finished = FALSE;
|
||||
}
|
||||
} else {
|
||||
sidx->entry_index--;
|
||||
if (sidx->entry_index >= 0) {
|
||||
fragment_finished = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fragment_finished) {
|
||||
dashstream->sidx_current_remaining = sidx->entries[sidx->entry_index].size;
|
||||
}
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_dash_demux_stream_advance_fragment (GstAdaptiveDemuxStream * stream)
|
||||
{
|
||||
GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream;
|
||||
GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux);
|
||||
|
||||
if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) {
|
||||
GstSidxBox *sidx = SIDX (dashstream);
|
||||
|
||||
if (stream->demux->segment.rate > 0.0) {
|
||||
if (sidx->entry_index < sidx->entries_count) {
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
} else {
|
||||
if (sidx->entry_index >= 0) {
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gst_mpd_client_advance_segment (dashdemux->client,
|
||||
dashstream->active_stream, stream->demux->segment.rate > 0.0);
|
||||
}
|
||||
|
@ -872,11 +982,33 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream,
|
|||
caps = gst_dash_demux_get_input_caps (demux, active_stream);
|
||||
gst_adaptive_demux_stream_set_caps (stream, caps);
|
||||
ret = TRUE;
|
||||
|
||||
} else {
|
||||
GST_WARNING_OBJECT (demux, "Can not switch representation, aborting...");
|
||||
}
|
||||
}
|
||||
|
||||
if (gst_mpd_client_has_isoff_ondemand_profile (demux->client)) {
|
||||
|
||||
/* a new subsegment is going to start, cleanup any pending data from the
|
||||
* previous one */
|
||||
dashstream->sidx_index = SIDX (dashstream)->entry_index;
|
||||
if (dashstream->pending_buffer) {
|
||||
gst_buffer_unref (dashstream->pending_buffer);
|
||||
dashstream->pending_buffer = NULL;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
/* TODO cache indexes to avoid re-downloading and parsing */
|
||||
/* if we switched, we need a new index */
|
||||
gst_isoff_sidx_parser_clear (&dashstream->sidx_parser);
|
||||
gst_isoff_sidx_parser_init (&dashstream->sidx_parser);
|
||||
} else {
|
||||
dashstream->sidx_current_remaining =
|
||||
SIDX_ENTRY (dashstream, dashstream->sidx_index)->size;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
@ -938,8 +1070,17 @@ gst_dash_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek)
|
|||
/* Update the current sequence on all streams */
|
||||
for (iter = demux->streams; iter; iter = g_list_next (iter)) {
|
||||
GstDashDemuxStream *dashstream = iter->data;
|
||||
gst_mpd_client_stream_seek (dashdemux->client, dashstream->active_stream,
|
||||
target_pos);
|
||||
|
||||
if (flags & GST_SEEK_FLAG_FLUSH) {
|
||||
gst_isoff_sidx_parser_clear (&dashstream->sidx_parser);
|
||||
gst_isoff_sidx_parser_init (&dashstream->sidx_parser);
|
||||
}
|
||||
|
||||
if (dashstream->pending_buffer) {
|
||||
gst_buffer_unref (dashstream->pending_buffer);
|
||||
dashstream->pending_buffer = NULL;
|
||||
}
|
||||
gst_dash_demux_stream_seek (iter->data, target_pos);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -1109,15 +1250,92 @@ gst_dash_demux_advance_period (GstAdaptiveDemux * demux)
|
|||
gst_mpd_client_set_segment_index_for_all_streams (dashdemux->client, 0);
|
||||
}
|
||||
|
||||
static GstBuffer *
|
||||
_gst_buffer_split (GstBuffer ** buffer, gint offset, gsize size)
|
||||
{
|
||||
GstBuffer *newbuf = gst_buffer_copy_region (*buffer,
|
||||
GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_META
|
||||
| GST_BUFFER_COPY_MEMORY, offset, size - offset);
|
||||
|
||||
gst_buffer_resize (*buffer, 0, offset);
|
||||
|
||||
return newbuf;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_dash_demux_chunk_received (GstAdaptiveDemux * demux,
|
||||
GstAdaptiveDemuxStream * stream, GstBuffer ** chunk)
|
||||
{
|
||||
GstDashDemuxStream *dash_stream = (GstDashDemuxStream *) stream;
|
||||
if (stream->downloading_index) {
|
||||
gst_isoff_sidx_parser_add_buffer (&dash_stream->sidx_parser, *chunk);
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
|
||||
if (*chunk == NULL) {
|
||||
if (dash_stream->pending_buffer) {
|
||||
*chunk = dash_stream->pending_buffer;
|
||||
dash_stream->pending_buffer = NULL;
|
||||
}
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
if (dash_stream->pending_buffer) {
|
||||
*chunk = gst_buffer_append (dash_stream->pending_buffer, *chunk);
|
||||
dash_stream->pending_buffer = NULL;
|
||||
}
|
||||
|
||||
if (stream->downloading_index
|
||||
&& dash_stream->sidx_parser.status != GST_ISOFF_SIDX_PARSER_FINISHED) {
|
||||
GstIsoffParserResult res;
|
||||
guint consumed;
|
||||
|
||||
res =
|
||||
gst_isoff_sidx_parser_add_buffer (&dash_stream->sidx_parser, *chunk,
|
||||
&consumed);
|
||||
|
||||
if (res == GST_ISOFF_PARSER_ERROR) {
|
||||
} else if (res == GST_ISOFF_PARSER_UNEXPECTED) {
|
||||
/* this is not a 'sidx' index, just skip it and continue playback */
|
||||
} else {
|
||||
/* when finished, prepare for real data streaming */
|
||||
if (dash_stream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
|
||||
if (GST_CLOCK_TIME_IS_VALID (dash_stream->pending_seek_ts)) {
|
||||
gst_dash_demux_stream_sidx_seek (dash_stream,
|
||||
dash_stream->pending_seek_ts);
|
||||
dash_stream->pending_seek_ts = GST_CLOCK_TIME_NONE;
|
||||
} else {
|
||||
SIDX (dash_stream)->entry_index = dash_stream->sidx_index;
|
||||
}
|
||||
dash_stream->sidx_current_remaining =
|
||||
SIDX_CURRENT_ENTRY (dash_stream)->size;
|
||||
} else if (consumed < gst_buffer_get_size (*chunk)) {
|
||||
dash_stream->pending_buffer = _gst_buffer_split (chunk, consumed, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check our position in subsegments */
|
||||
if (!stream->downloading_index
|
||||
&& dash_stream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
|
||||
gsize size = gst_buffer_get_size (*chunk);
|
||||
|
||||
GST_LOG_OBJECT (stream->pad,
|
||||
"Received buffer of size: %" G_GSIZE_FORMAT
|
||||
" - remaining in subsegment: %" G_GSIZE_FORMAT, size,
|
||||
dash_stream->sidx_current_remaining);
|
||||
if (size < dash_stream->sidx_current_remaining) {
|
||||
dash_stream->sidx_current_remaining -= size;
|
||||
} else if (size >= dash_stream->sidx_current_remaining) {
|
||||
if (size > dash_stream->sidx_current_remaining) {
|
||||
dash_stream->pending_buffer =
|
||||
_gst_buffer_split (chunk, dash_stream->sidx_current_remaining,
|
||||
size);
|
||||
}
|
||||
|
||||
gst_dash_demux_stream_advance_subfragment (stream);
|
||||
ret = GST_ADAPTIVE_DEMUX_FLOW_SUBSEGMENT_END;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1126,4 +1344,6 @@ gst_dash_demux_stream_free (GstAdaptiveDemuxStream * stream)
|
|||
GstDashDemuxStream *dash_stream = (GstDashDemuxStream *) stream;
|
||||
|
||||
gst_isoff_sidx_parser_clear (&dash_stream->sidx_parser);
|
||||
if (dash_stream->pending_buffer)
|
||||
gst_buffer_unref (dash_stream->pending_buffer);
|
||||
}
|
||||
|
|
|
@ -65,8 +65,14 @@ struct _GstDashDemuxStream
|
|||
|
||||
GstMediaFragmentInfo current_fragment;
|
||||
|
||||
GstBuffer *pending_buffer;
|
||||
|
||||
/* index parsing */
|
||||
GstSidxParser sidx_parser;
|
||||
gsize sidx_current_remaining;
|
||||
gint sidx_index;
|
||||
gint64 sidx_base_offset;
|
||||
GstClockTime pending_seek_ts;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue