From 3e27aeb2fc38b347df4641010d018bf39feb1c71 Mon Sep 17 00:00:00 2001 From: Eunhae Choi Date: Tue, 19 Feb 2019 16:05:19 +0900 Subject: [PATCH] adaptivedemux: apply 1.14.4 upstream code - apply the upstream 1.14.4 release code of adaptivedemux, dashdemux, hlsdemux and uridownloader to use latest playback code Change-Id: If3270872c26059b81c9eab8fbbe239f251d9e675 --- configure.ac | 1 + ext/dash/Makefile.am | 3 +- ext/dash/gstdashdemux.c | 905 ++++++++++++++++++--- ext/dash/gstdashdemux.h | 22 +- ext/dash/gstmpdparser.c | 81 +- ext/dash/gstmpdparser.h | 4 +- ext/hls/Makefile.am | 2 + ext/hls/gsthlsdemux.c | 37 +- ext/hls/gsthlsdemux.h | 5 + ext/hls/gsthlsplugin.c | 4 + ext/hls/gsthlssink2.c | 478 +++++++++++ ext/hls/gsthlssink2.h | 69 ++ ext/hls/m3u8.c | 234 +++--- gst-libs/gst/Makefile.am | 6 +- gst-libs/gst/adaptivedemux/Makefile.am | 2 +- .../gst/adaptivedemux/adaptive-demux-prelude.h | 31 + gst-libs/gst/adaptivedemux/gstadaptivedemux.c | 338 ++++++-- gst-libs/gst/adaptivedemux/gstadaptivedemux.h | 44 +- gst-libs/gst/isoff/Makefile.am | 24 + {ext/dash => gst-libs/gst/isoff}/gstisoff.c | 463 ++++++++++- {ext/dash => gst-libs/gst/isoff}/gstisoff.h | 101 ++- gst-libs/gst/isoff/meson.build | 21 + gst-libs/gst/uridownloader/Makefile.am | 2 +- gst-libs/gst/uridownloader/gstfragment.h | 11 + gst-libs/gst/uridownloader/gsturidownloader.c | 7 +- gst-libs/gst/uridownloader/gsturidownloader.h | 16 +- gst-libs/gst/uridownloader/uridownloader-prelude.h | 31 + packaging/gst-plugins-bad.spec | 1 + 28 files changed, 2608 insertions(+), 335 deletions(-) create mode 100644 ext/hls/gsthlssink2.c create mode 100644 ext/hls/gsthlssink2.h create mode 100644 gst-libs/gst/adaptivedemux/adaptive-demux-prelude.h create mode 100644 gst-libs/gst/isoff/Makefile.am rename {ext/dash => gst-libs/gst/isoff}/gstisoff.c (57%) rename {ext/dash => gst-libs/gst/isoff}/gstisoff.h (75%) create mode 100644 gst-libs/gst/isoff/meson.build create mode 100644 gst-libs/gst/uridownloader/uridownloader-prelude.h diff --git a/configure.ac b/configure.ac index 3cf76f9..2bc75e3 100644 --- a/configure.ac +++ b/configure.ac @@ -3601,6 +3601,7 @@ gst-libs/gst/gl/x11/Makefile gst-libs/gst/gl/viv-fb/Makefile gst-libs/gst/insertbin/Makefile gst-libs/gst/interfaces/Makefile +gst-libs/gst/isoff/Makefile gst-libs/gst/codecparsers/Makefile gst-libs/gst/mpegts/Makefile gst-libs/gst/uridownloader/Makefile diff --git a/ext/dash/Makefile.am b/ext/dash/Makefile.am index 5197c34..5109e9e 100644 --- a/ext/dash/Makefile.am +++ b/ext/dash/Makefile.am @@ -4,14 +4,12 @@ plugin_LTLIBRARIES = libgstdashdemux.la libgstdashdemux_la_SOURCES = \ gstmpdparser.c \ gstdashdemux.c \ - gstisoff.c \ gstplugin.c # headers we need but don't want installed noinst_HEADERS = \ gstmpdparser.h \ gstdashdemux.h \ - gstisoff.h \ gstdash_debug.h # compiler and linker flags used to compile this plugin, set in configure.ac @@ -24,6 +22,7 @@ libgstdashdemux_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) \ libgstdashdemux_la_LIBADD = \ $(top_builddir)/gst-libs/gst/uridownloader/libgsturidownloader-@GST_API_VERSION@.la \ $(top_builddir)/gst-libs/gst/adaptivedemux/libgstadaptivedemux-@GST_API_VERSION@.la \ + $(top_builddir)/gst-libs/gst/isoff/libgstisoff-@GST_API_VERSION@.la \ $(GST_PLUGINS_BASE_LIBS) \ -lgsttag-$(GST_API_VERSION) \ $(GST_BASE_LIBS) \ diff --git a/ext/dash/gstdashdemux.c b/ext/dash/gstdashdemux.c index 2436502..2e4923c 100644 --- a/ext/dash/gstdashdemux.c +++ b/ext/dash/gstdashdemux.c @@ -139,6 +139,139 @@ * adapt quickly to bandwidth changes, we will not be able to rely * on downstream buffering, and will instead manage an internal queue. * + * + * Keyframe trick-mode implementation: + * + * When requested (with GST_SEEK_FLAG_TRICKMODE_KEY_UNIT) and if the format + * is supported (ISOBMFF profiles), dashdemux can download only keyframes + * in order to provide fast forward/reverse playback without exceeding the + * available bandwith/cpu/memory usage. + * + * This is done in two parts: + * 1) Parsing ISOBMFF atoms to detect the location of keyframes and only + * download/push those. + * 2) Deciding what the ideal next keyframe to download is in order to + * provide as many keyframes as possible without rebuffering. + * + * * Keyframe-only downloads: + * + * For each beginning of fragment, the fragment header will be parsed in + * gst_dash_demux_parse_isobmff() and then the information (offset, pts...) + * of each keyframe will be stored in moof_sync_samples. + * + * gst_dash_demux_stream_update_fragment_info() will specify the range + * start and end of the current keyframe, which will cause GstAdaptiveDemux + * to do a new upstream range request. + * + * When advancing, if there are still some keyframes in the current + * fragment, gst_dash_demux_stream_advance_fragment() will call + * gst_dash_demux_stream_advance_sync_sample() which decides what the next + * keyframe to get will be (it can be in reverse order for example, or + * might not be the *next* keyframe but one further as explained below). + * + * If no more keyframes are available in the current fragment, dash will + * advance to the next fragment (just like in the normal case) or to a + * fragment much further away (as explained below). + * + * + * * Deciding the optimal "next" keyframe/fragment to download: + * + * The main reason for doing keyframe-only downloads is for trick-modes + * (i.e. being able to do fast reverse/forward playback with limited + * bandwith/cpu/memory). + * + * Downloading all keyframes might not be the optimal solution, especially + * at high playback rates, since the time taken to download the keyframe + * might exceed the available running time between two displayed frames + * (i.e. all frames would end up arriving late). This would cause severe + * rebuffering. + * + * Note: The values specified below can be in either the segment running + * time or in absolute values. Where position values need to be converted + * to segment running time the "running_time(val)" notation is used, and + * where running time need ot be converted to segment poisition the + * "position(val)" notation is used. + * + * The goal instead is to be able to download/display as many frames as + * possible for a given playback rate. For that the implementation will + * take into account: + * * The requested playback rate and segment + * * The average time to request and download a keyframe (in running time) + * * The current position of dashdemux in the stream + * * The current downstream (i.e. sink) position (in running time) + * + * To reach this goal we consider that there is some amount of buffering + * (in time) between dashdemux and the display sink. While we do not know + * the exact amount of buffering available, a safe and reasonable assertion + * is that there is at least a second (in running time). + * + * The average time to request and fully download a keyframe (with or + * without fragment header) is obtained by averaging the + * GstAdaptiveDemuxStream->last_download_time and is stored in + * GstDashDemuxStream->average_download_time. Those values include the + * network latency and full download time, which are more interesting and + * correct than just bitrates (with small download sizes, the impact of the + * network latency is much higher). + * + * The current position is calculated based on the fragment timestamp and + * the current keyframe index within that fragment. It is stored in + * GstDashDemuxStream->actual_position. + * + * The downstream position of the pipeline is obtained via QoS events and + * is stored in GstAdaptiveDemuxStream->qos_earliest_time (note: it's a + * running time value). + * + * The estimated buffering level between dashdemux and downstream is + * therefore: + * buffering_level = running_time(actual_position) - qos_earliest_time + * + * In order to avoid rebuffering, we want to ensure that the next keyframe + * (including potential fragment header) we request will be download, demuxed + * and decoded in time so that it is not late. That next keyframe time is + * called the "target_time" and is calculated whenever we have finished + * pushing a keyframe downstream. + * + * One simple observation at this point is that we *need* to make sure that + * the target time is chosen such that: + * running_time(target_time) > qos_earliest_time + average_download_time + * + * i.e. we chose a target time which will be greater than the time at which + * downstream will be once we request and download the keyframe (otherwise + * we're guaranteed to be late). + * + * This would provide the highest number of displayed frames per + * second, but it is just a *minimal* value and is not enough as-is, + * since it doesn't take into account the following items which could + * cause frames to arrive late (and therefore rebuffering): + * * Network jitter (i.e. by how much the download time can fluctuate) + * * Network stalling + * * Different keyframe sizes (and therefore download time) + * * Decoding speed + * + * Instead, we adjust the target time calculation based on the + * buffering_level. + * + * The smaller the buffering level is (i.e. the closer we are between + * current and downstream), the more aggresively we skip forward (and + * guarantee the keyframe will be downloaded, decoded and displayed in + * time). And the higher the buffering level, the least aggresivelly + * we need to skip forward (and therefore display more frames per + * second). + * + * Right now the threshold for agressive switching is set to 3 + * average_download_time. Below that buffering level we set the target time + * to at least 3 average_download_time distance beyond the + * qos_earliest_time. + * + * If we are above that buffering level we set the target time to: + * position(running_time(position) + average_download_time) + * + * The logic is therefore: + * WHILE(!EOS) + * Calculate target_time + * Advance to keyframe/fragment for that target_time + * Adaptivedemux downloads that keyframe/fragment + * */ #ifdef HAVE_CONFIG_H @@ -734,9 +867,7 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux) stream->is_isobmff = gst_structure_has_name (s, "video/quicktime") || gst_structure_has_name (s, "audio/x-m4a"); stream->first_sync_sample_always_after_moof = TRUE; - if (stream->is_isobmff - || gst_mpd_client_has_isoff_ondemand_profile (demux->client)) - stream->adapter = gst_adapter_new (); + stream->adapter = gst_adapter_new (); gst_adaptive_demux_stream_set_caps (GST_ADAPTIVE_DEMUX_STREAM_CAST (stream), caps); if (tags) @@ -745,6 +876,10 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux) stream->index = i; stream->pending_seek_ts = GST_CLOCK_TIME_NONE; stream->sidx_position = GST_CLOCK_TIME_NONE; + stream->actual_position = GST_CLOCK_TIME_NONE; + stream->target_time = GST_CLOCK_TIME_NONE; + /* Set a default average keyframe download time of a quarter of a second */ + stream->average_download_time = 250 * GST_MSECOND; if (active_stream->cur_adapt_set && active_stream->cur_adapt_set->RepresentationBase && active_stream->cur_adapt_set->RepresentationBase->ContentProtection) { @@ -1151,6 +1286,7 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream) /* Reset chunk size if any */ stream->fragment.chunk_size = 0; + dashstream->current_fragment_keyframe_distance = GST_CLOCK_TIME_NONE; if (GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER (stream) && isombff) { gst_dash_demux_stream_update_headers_info (stream); @@ -1164,8 +1300,7 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream) } if (dashstream->moof_sync_samples - && GST_ADAPTIVE_DEMUX (dashdemux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) { GstDashStreamSyncSample *sync_sample = &g_array_index (dashstream->moof_sync_samples, GstDashStreamSyncSample, dashstream->current_sync_sample); @@ -1173,12 +1308,39 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream) gst_mpd_client_get_next_fragment (dashdemux->client, dashstream->index, &fragment); + if (isombff && dashstream->sidx_position != GST_CLOCK_TIME_NONE + && SIDX (dashstream)->entries) { + GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream); + dashstream->current_fragment_timestamp = fragment.timestamp = entry->pts; + dashstream->current_fragment_duration = fragment.duration = + entry->duration; + } else { + dashstream->current_fragment_timestamp = fragment.timestamp; + dashstream->current_fragment_duration = fragment.duration; + } + + dashstream->current_fragment_keyframe_distance = + fragment.duration / dashstream->moof_sync_samples->len; + dashstream->actual_position = + fragment.timestamp + + dashstream->current_sync_sample * + dashstream->current_fragment_keyframe_distance; + if (stream->segment.rate < 0.0) + dashstream->actual_position += + dashstream->current_fragment_keyframe_distance; + dashstream->actual_position = + MIN (dashstream->actual_position, + fragment.timestamp + fragment.duration); + stream->fragment.uri = fragment.uri; stream->fragment.timestamp = GST_CLOCK_TIME_NONE; stream->fragment.duration = GST_CLOCK_TIME_NONE; stream->fragment.range_start = sync_sample->start_offset; stream->fragment.range_end = sync_sample->end_offset; + GST_DEBUG_OBJECT (stream->pad, "Actual position %" GST_TIME_FORMAT, + GST_TIME_ARGS (dashstream->actual_position)); + return GST_FLOW_OK; } @@ -1200,22 +1362,34 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream) 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; + dashstream->actual_position = stream->fragment.timestamp = entry->pts; + dashstream->current_fragment_timestamp = stream->fragment.timestamp = + entry->pts; + dashstream->current_fragment_duration = stream->fragment.duration = + entry->duration; if (stream->demux->segment.rate < 0.0) { stream->fragment.range_end = stream->fragment.range_start + entry->size - 1; + dashstream->actual_position += entry->duration; } else { stream->fragment.range_end = fragment.range_end; } } else { - stream->fragment.timestamp = fragment.timestamp; - stream->fragment.duration = fragment.duration; + dashstream->actual_position = stream->fragment.timestamp = + fragment.timestamp; + dashstream->current_fragment_timestamp = fragment.timestamp; + dashstream->current_fragment_duration = stream->fragment.duration = + fragment.duration; + if (stream->demux->segment.rate < 0.0) + dashstream->actual_position += fragment.duration; stream->fragment.range_start = MAX (fragment.range_start, dashstream->sidx_base_offset); stream->fragment.range_end = fragment.range_end; } + GST_DEBUG_OBJECT (stream->pad, "Actual position %" GST_TIME_FORMAT, + GST_TIME_ARGS (dashstream->actual_position)); + return GST_FLOW_OK; } @@ -1334,6 +1508,7 @@ gst_dash_demux_stream_seek (GstAdaptiveDemuxStream * stream, gboolean forward, g_array_free (dashstream->moof_sync_samples, TRUE); dashstream->moof_sync_samples = NULL; dashstream->current_sync_sample = -1; + dashstream->target_time = GST_CLOCK_TIME_NONE; is_isobmff = gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client); @@ -1381,6 +1556,8 @@ gst_dash_demux_stream_seek (GstAdaptiveDemuxStream * stream, gboolean forward, } } + stream->discont = TRUE; + return GST_FLOW_OK; } @@ -1389,9 +1566,8 @@ gst_dash_demux_stream_has_next_sync_sample (GstAdaptiveDemuxStream * stream) { GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream; - if (dashstream->moof_sync_samples - && GST_ADAPTIVE_DEMUX (stream->demux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + if (dashstream->moof_sync_samples && + GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) { if (stream->demux->segment.rate > 0.0) { if (dashstream->current_sync_sample + 1 < dashstream->moof_sync_samples->len) @@ -1423,32 +1599,100 @@ gst_dash_demux_stream_has_next_subfragment (GstAdaptiveDemuxStream * stream) } static gboolean -gst_dash_demux_stream_advance_sync_sample (GstAdaptiveDemuxStream * stream) +gst_dash_demux_stream_advance_sync_sample (GstAdaptiveDemuxStream * stream, + GstClockTime target_time) { GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream; gboolean fragment_finished = FALSE; + guint idx = -1; + + if (GST_CLOCK_TIME_IS_VALID (target_time)) { + GST_LOG_OBJECT (stream->pad, + "target_time:%" GST_TIME_FORMAT " fragment ts %" GST_TIME_FORMAT + " average keyframe dist: %" GST_TIME_FORMAT + " current keyframe dist: %" GST_TIME_FORMAT + " fragment duration:%" GST_TIME_FORMAT, + GST_TIME_ARGS (target_time), + GST_TIME_ARGS (dashstream->current_fragment_timestamp), + GST_TIME_ARGS (dashstream->keyframe_average_distance), + GST_TIME_ARGS (dashstream->current_fragment_keyframe_distance), + GST_TIME_ARGS (stream->fragment.duration)); - if (dashstream->moof_sync_samples - && GST_ADAPTIVE_DEMUX (stream->demux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { if (stream->demux->segment.rate > 0.0) { + idx = + (target_time - + dashstream->current_fragment_timestamp) / + dashstream->current_fragment_keyframe_distance; + + /* Prevent getting stuck in a loop due to rounding errors */ + if (idx == dashstream->current_sync_sample) + idx++; + } else { + GstClockTime end_time = + dashstream->current_fragment_timestamp + + dashstream->current_fragment_duration; + + if (end_time < target_time) { + idx = dashstream->moof_sync_samples->len; + } else { + idx = + (end_time - + target_time) / dashstream->current_fragment_keyframe_distance; + if (idx == dashstream->moof_sync_samples->len) { + dashstream->current_sync_sample = -1; + fragment_finished = TRUE; + goto beach; + } + idx = dashstream->moof_sync_samples->len - 1 - idx; + } + + /* Prevent getting stuck in a loop due to rounding errors */ + if (idx == dashstream->current_sync_sample) { + if (idx == 0) { + dashstream->current_sync_sample = -1; + fragment_finished = TRUE; + goto beach; + } + + idx--; + } + } + } + + GST_DEBUG_OBJECT (stream->pad, + "Advancing sync sample #%d target #%d", + dashstream->current_sync_sample, idx); + + if (idx != -1 && idx >= dashstream->moof_sync_samples->len) { + dashstream->current_sync_sample = -1; + fragment_finished = TRUE; + goto beach; + } + + if (stream->demux->segment.rate > 0.0) { + /* Try to get the sync sample for the target time */ + if (idx != -1) { + dashstream->current_sync_sample = idx; + } else { dashstream->current_sync_sample++; if (dashstream->current_sync_sample >= dashstream->moof_sync_samples->len) { fragment_finished = TRUE; } + } + } else { + if (idx != -1) { + dashstream->current_sync_sample = idx; + } else if (dashstream->current_sync_sample == -1) { + dashstream->current_sync_sample = dashstream->moof_sync_samples->len - 1; + } else if (dashstream->current_sync_sample == 0) { + dashstream->current_sync_sample = -1; + fragment_finished = TRUE; } else { - if (dashstream->current_sync_sample == -1) { - dashstream->current_sync_sample = - dashstream->moof_sync_samples->len - 1; - } else if (dashstream->current_sync_sample == 0) { - dashstream->current_sync_sample = -1; - fragment_finished = TRUE; - } else { - dashstream->current_sync_sample--; - } + dashstream->current_sync_sample--; } } +beach: GST_DEBUG_OBJECT (stream->pad, "Advancing sync sample #%d fragment_finished:%d", dashstream->current_sync_sample, fragment_finished); @@ -1504,9 +1748,8 @@ gst_dash_demux_stream_has_next_fragment (GstAdaptiveDemuxStream * stream) GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux); GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream; - if (dashstream->moof_sync_samples - && GST_ADAPTIVE_DEMUX (dashdemux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + if (dashstream->moof_sync_samples && + GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) { if (gst_dash_demux_stream_has_next_sync_sample (stream)) return TRUE; } @@ -1520,19 +1763,329 @@ gst_dash_demux_stream_has_next_fragment (GstAdaptiveDemuxStream * stream) dashstream->active_stream, stream->demux->segment.rate > 0.0); } +/* The goal here is to figure out, once we have pushed a keyframe downstream, + * what the next ideal keyframe to download is. + * + * This is done based on: + * * the current internal position (i.e. actual_position) + * * the reported downstream position (QoS feedback) + * * the average keyframe download time (average_download_time) + */ +static GstClockTime +gst_dash_demux_stream_get_target_time (GstDashDemux * dashdemux, + GstAdaptiveDemuxStream * stream, GstClockTime cur_position, + GstClockTime min_skip) +{ + GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream; + GstClockTime cur_running, min_running, min_position; + GstClockTimeDiff diff; + GstClockTime ret = cur_position; + GstClockTime deadline; + GstClockTime earliest_time = GST_CLOCK_TIME_NONE; + + g_assert (min_skip > 0); + + /* minimum stream position we have to skip to */ + if (stream->segment.rate > 0) + min_position = cur_position + min_skip; + else if (cur_position < min_skip) + min_position = 0; + else + min_position = cur_position - min_skip; + + /* Use current clock time or the QoS earliest time, whichever is further in + * the future. The QoS time is only updated on every QoS event and + * especially not if e.g. a videodecoder or converter drops a frame further + * downstream. + * + * We only use the times if we ever received a QoS event since the last + * flush, as otherwise base_time and clock might not be correct because of a + * still pre-rolling sink + */ + if (stream->qos_earliest_time != GST_CLOCK_TIME_NONE) { + GstClock *clock; + + clock = gst_element_get_clock (GST_ELEMENT_CAST (dashdemux)); + + if (clock) { + GstClockTime base_time; + GstClockTime now_time; + + base_time = gst_element_get_base_time (GST_ELEMENT_CAST (dashdemux)); + now_time = gst_clock_get_time (clock); + if (now_time > base_time) + now_time -= base_time; + else + now_time = 0; + + gst_object_unref (clock); + + earliest_time = MAX (now_time, stream->qos_earliest_time); + } else { + earliest_time = stream->qos_earliest_time; + } + } + + /* our current position in running time */ + cur_running = + gst_segment_to_running_time (&stream->segment, GST_FORMAT_TIME, + cur_position); + + /* the minimum position we have to skip to in running time */ + min_running = + gst_segment_to_running_time (&stream->segment, GST_FORMAT_TIME, + min_position); + + GST_DEBUG_OBJECT (stream->pad, + "position: current %" GST_TIME_FORMAT " min next %" GST_TIME_FORMAT, + GST_TIME_ARGS (cur_position), GST_TIME_ARGS (min_position)); + GST_DEBUG_OBJECT (stream->pad, + "running time: current %" GST_TIME_FORMAT " min next %" GST_TIME_FORMAT + " earliest %" GST_TIME_FORMAT, GST_TIME_ARGS (cur_running), + GST_TIME_ARGS (min_running), GST_TIME_ARGS (earliest_time)); + + /* Take configured maximum video bandwidth and framerate into account */ + { + GstClockTime min_run_dist, min_frame_dist, diff = 0; + guint max_fps_n, max_fps_d; + + min_run_dist = min_skip / ABS (stream->segment.rate); + + if (dashdemux->max_video_framerate_n != 0) { + max_fps_n = dashdemux->max_video_framerate_n; + max_fps_d = dashdemux->max_video_framerate_d; + } else { + /* more than 10 fps is not very useful if we're skipping anyway */ + max_fps_n = 10; + max_fps_d = 1; + } + + min_frame_dist = gst_util_uint64_scale_ceil (GST_SECOND, + max_fps_d, max_fps_n); + + GST_DEBUG_OBJECT (stream->pad, + "Have max framerate %d/%d - Min dist %" GST_TIME_FORMAT + ", min requested dist %" GST_TIME_FORMAT, + max_fps_n, max_fps_d, + GST_TIME_ARGS (min_run_dist), GST_TIME_ARGS (min_frame_dist)); + if (min_frame_dist > min_run_dist) + diff = MAX (diff, min_frame_dist - min_run_dist); + + if (dashdemux->max_bitrate != 0) { + guint64 max_bitrate = gst_util_uint64_scale_ceil (GST_SECOND, + 8 * dashstream->keyframe_average_size, + dashstream->keyframe_average_distance) * ABS (stream->segment.rate); + + if (max_bitrate > dashdemux->max_bitrate) { + min_frame_dist = gst_util_uint64_scale_ceil (GST_SECOND, + 8 * dashstream->keyframe_average_size, + dashdemux->max_bitrate) * ABS (stream->segment.rate); + + GST_DEBUG_OBJECT (stream->pad, + "Have max bitrate %u - Min dist %" GST_TIME_FORMAT + ", min requested dist %" GST_TIME_FORMAT, dashdemux->max_bitrate, + GST_TIME_ARGS (min_run_dist), GST_TIME_ARGS (min_frame_dist)); + if (min_frame_dist > min_run_dist) + diff = MAX (diff, min_frame_dist - min_run_dist); + } + } + + if (diff > 0) { + GST_DEBUG_OBJECT (stream->pad, + "Skipping further ahead by %" GST_TIME_FORMAT, GST_TIME_ARGS (diff)); + min_running += diff; + } + } + + if (earliest_time == GST_CLOCK_TIME_NONE) { + GstClockTime run_key_dist; + + run_key_dist = + dashstream->keyframe_average_distance / ABS (stream->segment.rate); + + /* If we don't have downstream information (such as at startup or + * without live sinks), just get the next time by taking the minimum + * amount we have to skip ahead + * Except if it takes us longer to download */ + if (run_key_dist > dashstream->average_download_time) + ret = + gst_segment_position_from_running_time (&stream->segment, + GST_FORMAT_TIME, min_running); + else + ret = gst_segment_position_from_running_time (&stream->segment, + GST_FORMAT_TIME, + min_running - run_key_dist + dashstream->average_download_time); + + GST_DEBUG_OBJECT (stream->pad, + "Advancing to %" GST_TIME_FORMAT " (was %" GST_TIME_FORMAT ")", + GST_TIME_ARGS (ret), GST_TIME_ARGS (min_position)); + + goto out; + } + + /* Figure out the difference, in running time, between where we are and + * where downstream is */ + diff = min_running - earliest_time; + GST_LOG_OBJECT (stream->pad, + "min_running %" GST_TIME_FORMAT " diff %" GST_STIME_FORMAT + " average_download %" GST_TIME_FORMAT, GST_TIME_ARGS (min_running), + GST_STIME_ARGS (diff), GST_TIME_ARGS (dashstream->average_download_time)); + + /* Have at least 500ms or 3 keyframes safety between current position and downstream */ + deadline = MAX (500 * GST_MSECOND, 3 * dashstream->average_download_time); + + /* The furthest away we are from the current position, the least we need to advance */ + if (diff < 0 || diff < deadline) { + /* Force skipping (but not more than 1s ahead) */ + ret = + gst_segment_position_from_running_time (&stream->segment, + GST_FORMAT_TIME, earliest_time + MIN (deadline, GST_SECOND)); + GST_DEBUG_OBJECT (stream->pad, + "MUST SKIP to at least %" GST_TIME_FORMAT " (was %" GST_TIME_FORMAT ")", + GST_TIME_ARGS (ret), GST_TIME_ARGS (min_position)); + } else if (diff < 4 * dashstream->average_download_time) { + /* Go forward a bit less aggresively (and at most 1s forward) */ + ret = gst_segment_position_from_running_time (&stream->segment, + GST_FORMAT_TIME, min_running + MIN (GST_SECOND, + 2 * dashstream->average_download_time)); + GST_DEBUG_OBJECT (stream->pad, + "MUST SKIP to at least %" GST_TIME_FORMAT " (was %" GST_TIME_FORMAT ")", + GST_TIME_ARGS (ret), GST_TIME_ARGS (min_position)); + } else { + /* Get the next position satisfying the download time */ + ret = gst_segment_position_from_running_time (&stream->segment, + GST_FORMAT_TIME, min_running); + GST_DEBUG_OBJECT (stream->pad, + "Advance to %" GST_TIME_FORMAT " (was %" GST_TIME_FORMAT ")", + GST_TIME_ARGS (ret), GST_TIME_ARGS (min_position)); + } + +out: + + { + GstClockTime cur_skip = + (cur_position < ret) ? ret - cur_position : cur_position - ret; + + if (dashstream->average_skip_size == 0) { + dashstream->average_skip_size = cur_skip; + } else { + dashstream->average_skip_size = + (cur_skip + 3 * dashstream->average_skip_size) / 4; + } + + if (dashstream->average_skip_size > + cur_skip + dashstream->keyframe_average_distance + && dashstream->average_skip_size > min_skip) { + if (stream->segment.rate > 0) + ret = cur_position + dashstream->average_skip_size; + else if (cur_position > dashstream->average_skip_size) + ret = cur_position - dashstream->average_skip_size; + else + ret = 0; + } + } + + return ret; +} + static GstFlowReturn gst_dash_demux_stream_advance_fragment (GstAdaptiveDemuxStream * stream) { GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream; GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (stream->demux); + GstClockTime target_time = GST_CLOCK_TIME_NONE; + GstClockTime previous_position; + GstFlowReturn ret; GST_DEBUG_OBJECT (stream->pad, "Advance fragment"); + /* Update download statistics */ + if (dashstream->moof_sync_samples && + GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux) && + GST_CLOCK_TIME_IS_VALID (stream->last_download_time)) { + if (GST_CLOCK_TIME_IS_VALID (dashstream->average_download_time)) { + dashstream->average_download_time = + (3 * dashstream->average_download_time + + stream->last_download_time) / 4; + } else { + dashstream->average_download_time = stream->last_download_time; + } + + GST_DEBUG_OBJECT (stream->pad, + "Download time last: %" GST_TIME_FORMAT " average: %" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->last_download_time), + GST_TIME_ARGS (dashstream->average_download_time)); + } + + previous_position = dashstream->actual_position; + + /* Update internal position */ + if (GST_CLOCK_TIME_IS_VALID (dashstream->actual_position)) { + GstClockTime dur; + if (dashstream->moof_sync_samples + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) { + GST_LOG_OBJECT (stream->pad, "current sync sample #%d", + dashstream->current_sync_sample); + if (dashstream->current_sync_sample == -1) { + dur = 0; + } else if (dashstream->current_sync_sample < + dashstream->moof_sync_samples->len) { + dur = dashstream->current_fragment_keyframe_distance; + } else { + if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client) && + dashstream->sidx_position != GST_CLOCK_TIME_NONE + && SIDX (dashstream)->entries) { + GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream); + dur = entry->duration; + } else { + dur = + dashstream->current_fragment_timestamp + + dashstream->current_fragment_duration - + dashstream->actual_position; + } + } + } else if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client) && + dashstream->sidx_position != GST_CLOCK_TIME_NONE + && SIDX (dashstream)->entries) { + GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dashstream); + dur = entry->duration; + } else { + dur = stream->fragment.duration; + } + + if (dashstream->moof_sync_samples + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) { + /* We just downloaded the header, we actually use the previous + * target_time now as it was not used up yet */ + if (dashstream->current_sync_sample == -1) + target_time = dashstream->target_time; + else + target_time = + gst_dash_demux_stream_get_target_time (dashdemux, stream, + dashstream->actual_position, dur); + dashstream->actual_position = target_time; + } else { + /* Adjust based on direction */ + if (stream->demux->segment.rate > 0.0) + dashstream->actual_position += dur; + else if (dashstream->actual_position >= dur) + dashstream->actual_position -= dur; + else + dashstream->actual_position = 0; + } + + GST_DEBUG_OBJECT (stream->pad, "Actual position %" GST_TIME_FORMAT, + GST_TIME_ARGS (dashstream->actual_position)); + } + dashstream->target_time = target_time; + + GST_DEBUG_OBJECT (stream->pad, "target_time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (target_time)); + /* If downloading only keyframes, switch to the next one or fall through */ - if (dashstream->moof_sync_samples - && GST_ADAPTIVE_DEMUX (dashdemux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { - if (gst_dash_demux_stream_advance_sync_sample (stream)) + if (dashstream->moof_sync_samples && + GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) { + if (gst_dash_demux_stream_advance_sync_sample (stream, target_time)) return GST_FLOW_OK; } @@ -1548,21 +2101,81 @@ gst_dash_demux_stream_advance_fragment (GstAdaptiveDemuxStream * stream) dashstream->moof_sync_samples = NULL; dashstream->current_sync_sample = -1; - if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) { - if (gst_dash_demux_stream_advance_subfragment (stream)) - return GST_FLOW_OK; - } + /* Check if we just need to 'advance' to the next fragment, or if we + * need to skip by more. */ + if (GST_CLOCK_TIME_IS_VALID (target_time) + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux) && + dashstream->active_stream->mimeType == GST_STREAM_VIDEO) { + GstClockTime actual_ts; + GstSeekFlags flags = 0; + + /* Key-unit trick mode, seek to fragment containing target time + * + * We first try seeking without snapping. As above code to skip keyframes + * in the current fragment was not successful, we should go at least one + * fragment ahead. Due to rounding errors we could end up at the same + * fragment again here, in which case we retry seeking with the SNAP_AFTER + * flag. + * + * We don't always set that flag as we would then end up one further + * fragment in the future in all good cases. + */ + while (TRUE) { + ret = + gst_dash_demux_stream_seek (stream, (stream->segment.rate > 0), flags, + target_time, &actual_ts); + + if (ret != GST_FLOW_OK) { + GST_WARNING_OBJECT (stream->pad, "Failed to seek to %" GST_TIME_FORMAT, + GST_TIME_ARGS (target_time)); + /* Give up */ + if (flags != 0) + break; - gst_isoff_sidx_parser_clear (&dashstream->sidx_parser); - gst_isoff_sidx_parser_init (&dashstream->sidx_parser); - dashstream->sidx_base_offset = 0; - dashstream->sidx_position = GST_CLOCK_TIME_NONE; - dashstream->allow_sidx = TRUE; - if (dashstream->adapter) - gst_adapter_clear (dashstream->adapter); + /* Retry with skipping ahead */ + flags |= GST_SEEK_FLAG_SNAP_AFTER; + continue; + } - return gst_mpd_client_advance_segment (dashdemux->client, - dashstream->active_stream, stream->demux->segment.rate > 0.0); + GST_DEBUG_OBJECT (stream->pad, + "Skipped to %" GST_TIME_FORMAT " (wanted %" GST_TIME_FORMAT ", was %" + GST_TIME_FORMAT ")", GST_TIME_ARGS (actual_ts), + GST_TIME_ARGS (target_time), GST_TIME_ARGS (previous_position)); + + if ((stream->segment.rate > 0 && actual_ts <= previous_position) || + (stream->segment.rate < 0 && actual_ts >= previous_position)) { + /* Give up */ + if (flags != 0) + break; + + /* Retry with forcing skipping ahead */ + flags |= GST_SEEK_FLAG_SNAP_AFTER; + + continue; + } + + /* All good */ + break; + } + } else { + /* Normal mode, advance to the next fragment */ + if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client)) { + if (gst_dash_demux_stream_advance_subfragment (stream)) + return GST_FLOW_OK; + } + + if (dashstream->adapter) + gst_adapter_clear (dashstream->adapter); + + gst_isoff_sidx_parser_clear (&dashstream->sidx_parser); + dashstream->sidx_base_offset = 0; + dashstream->sidx_position = GST_CLOCK_TIME_NONE; + dashstream->allow_sidx = TRUE; + + ret = gst_mpd_client_advance_segment (dashdemux->client, + dashstream->active_stream, stream->demux->segment.rate > 0.0); + } + return ret; } static gboolean @@ -1582,6 +2195,12 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream, goto end; } + /* In key-frame trick mode don't change bitrates */ + if (GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (demux)) { + GST_DEBUG_OBJECT (demux, "In key-frame trick mode, not changing bitrates"); + goto end; + } + /* retrieve representation list */ if (active_stream->cur_adapt_set) rep_list = active_stream->cur_adapt_set->Representations; @@ -1603,20 +2222,20 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream, "Trying to change to bitrate under : %" G_GUINT64_FORMAT, bitrate); /* get representation index with current max_bandwidth */ - if ((base_demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) || + if (GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (base_demux) || ABS (base_demux->segment.rate) <= 1.0) { new_index = - gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, (gint)bitrate, + gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, bitrate, demux->max_video_width, demux->max_video_height); } else { new_index = gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, - (gint)(bitrate / ABS (base_demux->segment.rate)), demux->max_video_width, + bitrate / ABS (base_demux->segment.rate), demux->max_video_width, demux->max_video_height); } #else /* get representation index with current max_bandwidth */ - if ((base_demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) || + if (GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (base_demux) || ABS (base_demux->segment.rate) <= 1.0) { new_index = gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, bitrate, @@ -1694,6 +2313,7 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream, g_array_free (dashstream->moof_sync_samples, TRUE); dashstream->moof_sync_samples = NULL; dashstream->current_sync_sample = -1; + dashstream->target_time = GST_CLOCK_TIME_NONE; } end: @@ -1789,7 +2409,9 @@ gst_dash_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) /* Update the current sequence on all streams */ for (iter = streams; iter; iter = g_list_next (iter)) { GstAdaptiveDemuxStream *stream = iter->data; + GstDashDemuxStream *dashstream = iter->data; + dashstream->average_skip_size = 0; if (gst_dash_demux_stream_seek (stream, rate >= 0, 0, target_pos, NULL) != GST_FLOW_OK) return FALSE; @@ -1905,6 +2527,12 @@ gst_dash_demux_update_manifest_data (GstAdaptiveDemux * demux, * 10 microseconds to get back to the correct segment. The errors are * usually on the order of nanoseconds so it should be enough. */ + + /* _get_next_fragment_timestamp() returned relative timestamp to + * corresponding period start, but _client_stream_seek expects absolute + * MPD time. */ + ts += gst_mpd_parser_get_period_start_time (dashdemux->client); + GST_DEBUG_OBJECT (GST_ADAPTIVE_DEMUX_STREAM_PAD (demux_stream), "Current position: %" GST_TIME_FORMAT ", updating to %" GST_TIME_FORMAT, GST_TIME_ARGS (ts), @@ -2027,6 +2655,9 @@ gst_dash_demux_stream_fragment_start (GstAdaptiveDemux * demux, GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux); GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream; + GST_LOG_OBJECT (stream->pad, "Actual position %" GST_TIME_FORMAT, + GST_TIME_ARGS (dashstream->actual_position)); + dashstream->current_index_header_or_data = 0; dashstream->current_offset = -1; @@ -2037,7 +2668,7 @@ gst_dash_demux_stream_fragment_start (GstAdaptiveDemux * demux, * buffer. We need offsets to be consistent between moof and mdat */ if (dashstream->is_isobmff && dashdemux->allow_trickmode_key_units - && (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (demux) && dashstream->active_stream->mimeType == GST_STREAM_VIDEO) stream->discont = TRUE; @@ -2058,15 +2689,14 @@ gst_dash_demux_stream_fragment_finished (GstAdaptiveDemux * demux, * buffer. We need offsets to be consistent between moof and mdat */ if (dashstream->is_isobmff && dashdemux->allow_trickmode_key_units - && (demux->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (demux) && dashstream->active_stream->mimeType == GST_STREAM_VIDEO) stream->discont = TRUE; /* Only handle fragment advancing specifically for SIDX if we're not * in key unit mode */ if (!(dashstream->moof_sync_samples - && GST_ADAPTIVE_DEMUX (dashdemux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (dashdemux)) && gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client) && dashstream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) { /* fragment is advanced on data_received when byte limits are reached */ @@ -2096,8 +2726,7 @@ gst_dash_demux_need_another_chunk (GstAdaptiveDemuxStream * stream) * random guess for the moof size */ if (dashstream->is_isobmff - && (GST_ADAPTIVE_DEMUX (stream->demux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux) && dashstream->active_stream->mimeType == GST_STREAM_VIDEO && !stream->downloading_header && !stream->downloading_index && dashdemux->allow_trickmode_key_units) { @@ -2108,20 +2737,41 @@ gst_dash_demux_need_another_chunk (GstAdaptiveDemuxStream * stream) /* Do we have the first fourcc already or are we in the middle */ if (dashstream->isobmff_parser.current_fourcc == 0) { stream->fragment.chunk_size += dashstream->moof_average_size; - if (dashstream->first_sync_sample_always_after_moof) - stream->fragment.chunk_size += - dashstream->first_sync_sample_average_size; + if (dashstream->first_sync_sample_always_after_moof) { + gboolean first = FALSE; + /* Check if we'll really need that first sample */ + if (GST_CLOCK_TIME_IS_VALID (dashstream->target_time)) { + first = + ((dashstream->target_time - + dashstream->current_fragment_timestamp) / + dashstream->keyframe_average_distance) == 0 ? TRUE : FALSE; + } else if (stream->segment.rate > 0) { + first = TRUE; + } + + if (first) + stream->fragment.chunk_size += dashstream->keyframe_average_size; + } } if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client) && dashstream->sidx_parser.sidx.entries) { - guint64 sidx_end_offset = + guint64 sidx_start_offset = dashstream->sidx_base_offset + - SIDX_CURRENT_ENTRY (dashstream)->offset + - SIDX_CURRENT_ENTRY (dashstream)->size; - guint64 downloaded_end_offset = - dashstream->current_offset + - gst_adapter_available (dashstream->adapter); + SIDX_CURRENT_ENTRY (dashstream)->offset; + guint64 sidx_end_offset = + sidx_start_offset + SIDX_CURRENT_ENTRY (dashstream)->size; + guint64 downloaded_end_offset; + + if (dashstream->current_offset == GST_CLOCK_TIME_NONE) { + downloaded_end_offset = sidx_start_offset; + } else { + downloaded_end_offset = + dashstream->current_offset + + gst_adapter_available (dashstream->adapter); + } + + downloaded_end_offset = MAX (downloaded_end_offset, sidx_start_offset); if (stream->fragment.chunk_size + downloaded_end_offset > sidx_end_offset) { @@ -2137,7 +2787,9 @@ gst_dash_demux_need_another_chunk (GstAdaptiveDemuxStream * stream) &g_array_index (dashstream->moof_sync_samples, GstDashStreamSyncSample, 0); guint64 end_offset = sync_sample->end_offset + 1; - guint64 downloaded_end_offset = + guint64 downloaded_end_offset; + + downloaded_end_offset = dashstream->current_offset + gst_adapter_available (dashstream->adapter); @@ -2170,11 +2822,11 @@ gst_dash_demux_need_another_chunk (GstAdaptiveDemuxStream * stream) * trickmodes while doing chunked downloading. In that case * just download from here to the end now */ if (dashstream->moof - && (GST_ADAPTIVE_DEMUX (stream->demux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS)) + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) { stream->fragment.chunk_size = -1; - else + } else { stream->fragment.chunk_size = 0; + } } return stream->fragment.chunk_size != 0; @@ -2568,31 +3220,80 @@ gst_dash_demux_find_sync_samples (GstAdaptiveDemux * demux, } { - GstDashStreamSyncSample *sync_sample = - &g_array_index (dash_stream->moof_sync_samples, GstDashStreamSyncSample, - 0); - guint size = sync_sample->end_offset + 1 - sync_sample->start_offset; - - if (dash_stream->first_sync_sample_average_size) { - if (dash_stream->first_sync_sample_average_size < size) - dash_stream->first_sync_sample_average_size = - (size * 3 + dash_stream->first_sync_sample_average_size) / 4; - else - dash_stream->first_sync_sample_average_size = - (size + dash_stream->first_sync_sample_average_size * 3) / 4; - } else { - dash_stream->first_sync_sample_average_size = size; + GstDashStreamSyncSample *sync_sample; + guint i; + guint size; + GstClockTime current_keyframe_distance; + + for (i = 0; i < dash_stream->moof_sync_samples->len; i++) { + sync_sample = + &g_array_index (dash_stream->moof_sync_samples, + GstDashStreamSyncSample, i); + size = sync_sample->end_offset + 1 - sync_sample->start_offset; + + if (dash_stream->keyframe_average_size) { + /* Over-estimate the keyframe size */ + if (dash_stream->keyframe_average_size < size) + dash_stream->keyframe_average_size = + (size * 3 + dash_stream->keyframe_average_size) / 4; + else + dash_stream->keyframe_average_size = + (size + dash_stream->keyframe_average_size * 3) / 4; + } else { + dash_stream->keyframe_average_size = size; + } + + if (i == 0) { + if (dash_stream->moof_offset + dash_stream->moof_size + 8 < + sync_sample->start_offset) { + dash_stream->first_sync_sample_after_moof = FALSE; + dash_stream->first_sync_sample_always_after_moof = FALSE; + } else { + dash_stream->first_sync_sample_after_moof = + (dash_stream->moof_sync_samples->len == 1 + || demux->segment.rate > 0.0); + } + } } - if (dash_stream->moof_offset + dash_stream->moof_size + 8 < - sync_sample->start_offset) { - dash_stream->first_sync_sample_after_moof = FALSE; - dash_stream->first_sync_sample_always_after_moof = FALSE; + g_assert (stream->fragment.duration != 0); + g_assert (stream->fragment.duration != GST_CLOCK_TIME_NONE); + + if (gst_mpd_client_has_isoff_ondemand_profile (dashdemux->client) + && dash_stream->sidx_position != GST_CLOCK_TIME_NONE + && SIDX (dash_stream)->entries) { + GstSidxBoxEntry *entry = SIDX_CURRENT_ENTRY (dash_stream); + current_keyframe_distance = + entry->duration / dash_stream->moof_sync_samples->len; } else { - dash_stream->first_sync_sample_after_moof = - (dash_stream->moof_sync_samples->len == 1 - || demux->segment.rate > 0.0); + current_keyframe_distance = + stream->fragment.duration / dash_stream->moof_sync_samples->len; } + dash_stream->current_fragment_keyframe_distance = current_keyframe_distance; + + if (dash_stream->keyframe_average_distance) { + /* Under-estimate the keyframe distance */ + if (dash_stream->keyframe_average_distance > current_keyframe_distance) + dash_stream->keyframe_average_distance = + (dash_stream->keyframe_average_distance * 3 + + current_keyframe_distance) / 4; + else + dash_stream->keyframe_average_distance = + (dash_stream->keyframe_average_distance + + current_keyframe_distance * 3) / 4; + } else { + dash_stream->keyframe_average_distance = current_keyframe_distance; + } + + GST_DEBUG_OBJECT (stream->pad, + "average keyframe sample size: %" G_GUINT64_FORMAT, + dash_stream->keyframe_average_size); + GST_DEBUG_OBJECT (stream->pad, + "average keyframe distance: %" GST_TIME_FORMAT " (%" GST_TIME_FORMAT + ")", GST_TIME_ARGS (dash_stream->keyframe_average_distance), + GST_TIME_ARGS (current_keyframe_distance)); + GST_DEBUG_OBJECT (stream->pad, "first sync sample after moof: %d", + dash_stream->first_sync_sample_after_moof); } return TRUE; @@ -2634,15 +3335,29 @@ gst_dash_demux_handle_isobmff (GstAdaptiveDemux * demux, * reuse the data if the sync sample follows the moof */ if (dash_stream->active_stream->mimeType == GST_STREAM_VIDEO && gst_dash_demux_find_sync_samples (demux, stream) && - GST_ADAPTIVE_DEMUX (stream->demux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { + GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) { + guint idx = -1; + + if (GST_CLOCK_TIME_IS_VALID (dash_stream->target_time)) { + idx = + (dash_stream->target_time - + dash_stream->current_fragment_timestamp) / + dash_stream->current_fragment_keyframe_distance; + } else if (stream->segment.rate > 0) { + idx = 0; + } + + GST_DEBUG_OBJECT (stream->pad, "target %" GST_TIME_FORMAT " idx %d", + GST_TIME_ARGS (dash_stream->target_time), idx); + /* Figure out target time */ - if (dash_stream->first_sync_sample_after_moof) { + if (dash_stream->first_sync_sample_after_moof && idx == 0) { /* If we're here, don't throw away data but collect sync * sample while we're at it below. We're doing chunked * downloading so might need to adjust the next chunk size for * the remainder */ dash_stream->current_sync_sample = 0; + GST_DEBUG_OBJECT (stream->pad, "Using first keyframe after header"); } } @@ -2701,9 +3416,7 @@ gst_dash_demux_handle_isobmff (GstAdaptiveDemux * demux, /* We're actually running in key-units trick mode */ if (dash_stream->active_stream->mimeType == GST_STREAM_VIDEO && dash_stream->moof_sync_samples - && GST_ADAPTIVE_DEMUX (stream->demux)-> - segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) { - + && GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (stream->demux)) { if (dash_stream->current_sync_sample == -1) { /* We're doing chunked downloading and wait for finishing the current * chunk so we can jump to the first keyframe */ @@ -2989,7 +3702,7 @@ struct Rfc5322TimeZone /* Parse an RFC5322 (section 3.3) date-time from the Date: field in the - HTTP response. + HTTP response. See https://tools.ietf.org/html/rfc5322#section-3.3 */ static GstDateTime * diff --git a/ext/dash/gstdashdemux.h b/ext/dash/gstdashdemux.h index d547d29..fdbbf31 100644 --- a/ext/dash/gstdashdemux.h +++ b/ext/dash/gstdashdemux.h @@ -35,7 +35,7 @@ #include #include #include "gstmpdparser.h" -#include "gstisoff.h" +#include #include G_BEGIN_DECLS @@ -94,8 +94,24 @@ struct _GstDashDemuxStream GArray *moof_sync_samples; guint current_sync_sample; - guint64 moof_average_size, first_sync_sample_average_size; + guint64 moof_average_size; + guint64 keyframe_average_size; + guint64 keyframe_average_distance; gboolean first_sync_sample_after_moof, first_sync_sample_always_after_moof; + + /* Internal position value, at the keyframe/entry level */ + GstClockTime actual_position; + /* Timestamp of the beginning of the current fragment */ + GstClockTime current_fragment_timestamp; + GstClockTime current_fragment_duration; + GstClockTime current_fragment_keyframe_distance; + + /* Average keyframe download time (only in trickmode-key-units) */ + GstClockTime average_download_time; + /* Cached target time (only in trickmode-key-units) */ + GstClockTime target_time; + /* Average skip-ahead time (only in trickmode-key-units) */ + GstClockTime average_skip_size; }; /** @@ -119,7 +135,7 @@ struct _GstDashDemux /* Properties */ GstClockTime max_buffering_time; /* Maximum buffering time accumulated during playback */ - guint64 max_bitrate; /* max of bitrate supported by target decoder */ + guint max_bitrate; /* max of bitrate supported by target decoder */ gint max_video_width, max_video_height; gint max_video_framerate_n, max_video_framerate_d; gchar* default_presentation_delay; /* presentation time delay if MPD@suggestedPresentationDelay is not present */ diff --git a/ext/dash/gstmpdparser.c b/ext/dash/gstmpdparser.c index 5188feb..7604606 100644 --- a/ext/dash/gstmpdparser.c +++ b/ext/dash/gstmpdparser.c @@ -849,7 +849,7 @@ error: The dateTime data type is used to specify a date and a time. - The dateTime is specified in the following form "YYYY-MM-DDThh:mm:ss" where: + The lexical form of xs:dateTime is YYYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm] * YYYY indicates the year * MM indicates the month @@ -859,7 +859,7 @@ error: * mm indicates the minute * ss indicates the second - Note: All components are required! + The time zone may be specified as Z (UTC) or (+|-)hh:mm */ static gboolean gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node, @@ -871,6 +871,8 @@ gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node, gint year, month, day, hour, minute; gdouble second; gboolean exists = FALSE; + gfloat tzoffset = 0.0; + gint gmt_offset_hour = -99, gmt_offset_min = -99; prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); if (prop_string) { @@ -920,9 +922,50 @@ gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node, GST_LOG (" - %s: %4d/%02d/%02d %02d:%02d:%09.6lf", property_name, year, month, day, hour, minute, second); + if (strrchr (str, '+') || strrchr (str, '-')){ + /* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */ + gint gmt_offset = -1; + gchar *plus_pos = NULL; + gchar *neg_pos = NULL; + gchar *pos = NULL; + + GST_LOG ("Checking for timezone information"); + + /* check if there is timezone info */ + plus_pos = strrchr (str, '+'); + neg_pos = strrchr (str, '-'); + if (plus_pos) + pos = plus_pos + 1; + else if (neg_pos) + pos = neg_pos + 1; + + if (pos && strlen (pos) >= 3) { + gint ret_tz; + if (pos[2] == ':') + ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min); + else + ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min); + + GST_DEBUG ("Parsing timezone: %s", pos); + + if (ret_tz == 2) { + if (neg_pos != NULL && neg_pos + 1 == pos) { + gmt_offset_hour *= -1; + gmt_offset_min *= -1; + } + gmt_offset = gmt_offset_hour * 60 + gmt_offset_min; + + tzoffset = gmt_offset / 60.0; + + GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset); + } else + GST_WARNING ("Failed to parse timezone information"); + } + } + exists = TRUE; *property_value = - gst_date_time_new (0, year, month, day, hour, minute, second); + gst_date_time_new (tzoffset, year, month, day, hour, minute, second); xmlFree (prop_string); } @@ -935,6 +978,7 @@ error: return FALSE; } + /* Duration Data Type @@ -2434,7 +2478,7 @@ gst_mpdparser_representation_get_mimetype (GstAdaptationSetNode * adapt_set, return GST_STREAM_AUDIO; if (strncmp_ext (mime, "video") == 0) return GST_STREAM_VIDEO; - if (strncmp_ext (mime, "application") == 0) + if (strncmp_ext (mime, "application") == 0 || strncmp_ext (mime, "text") == 0) return GST_STREAM_APPLICATION; return GST_STREAM_UNKNOWN; @@ -2556,13 +2600,13 @@ gst_mpdparser_get_rep_idx_with_min_bandwidth (GList * Representations) #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations, - gint max_bandwidth, gint max_video_width, gint max_video_height) + gint64 max_bandwidth, gint max_video_width, gint max_video_height) { GList *list = NULL, *best = NULL, *min_rep = NULL; GstRepresentationNode *representation; gint best_bandwidth = 0, min_bandwidth = 0; - GST_DEBUG ("[b]%d [w]%d [h]%d", max_bandwidth, max_video_width, max_video_height); + GST_DEBUG ("[b]%"G_GINT64_FORMAT" [w]%d [h]%d", max_bandwidth, max_video_width, max_video_height); if (Representations == NULL) return -1; @@ -2601,14 +2645,14 @@ gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations, #else gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations, - gint max_bandwidth, gint max_video_width, gint max_video_height, gint + gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint max_video_framerate_n, gint max_video_framerate_d) { GList *list = NULL, *best = NULL; GstRepresentationNode *representation; gint best_bandwidth = 0; - GST_DEBUG ("max_bandwidth = %i", max_bandwidth); + GST_DEBUG ("max_bandwidth = %" G_GINT64_FORMAT, max_bandwidth); if (Representations == NULL) return -1; @@ -3816,8 +3860,8 @@ gst_mpd_client_fetch_on_load_external_resources (GstMpdClient * client) gst_mpd_client_fetch_external_adaptation_set (client, period, adapt_set); - prev = l->prev; - period->AdaptationSets = g_list_delete_link (period->AdaptationSets, l); + prev = m->prev; + period->AdaptationSets = g_list_delete_link (period->AdaptationSets, m); gst_mpdparser_free_adaptation_set_node (adapt_set); adapt_set = NULL; @@ -3837,9 +3881,9 @@ gst_mpd_client_fetch_on_load_external_resources (GstMpdClient * client) /* Update our iterator to the first new adapt_set if any, or the next */ if (prev) - l = prev->next; + m = prev->next; else - l = period->AdaptationSets; + m = period->AdaptationSets; continue; } @@ -4846,10 +4890,6 @@ gst_mpd_client_setup_streaming (GstMpdClient * client, GST_DEBUG ("0. Current stream %p", stream); - /* retrieve representation list */ - if (stream->cur_adapt_set != NULL) - rep_list = stream->cur_adapt_set->Representations; - #if 0 /* fast start */ representation = @@ -4868,14 +4908,14 @@ gst_mpd_client_setup_streaming (GstMpdClient * client, if (!representation) { GST_WARNING ("No valid representation in the MPD file, aborting..."); - g_slice_free (GstActiveStream, stream); + gst_mpdparser_free_active_stream (stream); return FALSE; } stream->mimeType = gst_mpdparser_representation_get_mimetype (adapt_set, representation); if (stream->mimeType == GST_STREAM_UNKNOWN) { GST_WARNING ("Unknown mime type in the representation, aborting..."); - g_slice_free (GstActiveStream, stream); + gst_mpdparser_free_active_stream (stream); return FALSE; } @@ -5859,7 +5899,8 @@ gst_mpd_client_active_stream_contains_subtitles (GstActiveStream * stream) if (!mimeType) mimeType = stream->cur_adapt_set->RepresentationBase->mimeType; - if (g_strcmp0 (mimeType, "application/ttml+xml") == 0) + if (g_strcmp0 (mimeType, "application/ttml+xml") == 0 || + g_strcmp0 (mimeType, "text/vtt") == 0) return TRUE; adapt_set_codecs = stream->cur_adapt_set->RepresentationBase->codecs; @@ -5880,6 +5921,8 @@ gst_mpdparser_mimetype_to_caps (const gchar * mimeType) return "video/quicktime"; } else if (strcmp (mimeType, "audio/mp4") == 0) { return "audio/x-m4a"; + } else if (strcmp (mimeType, "text/vtt") == 0) { + return "application/x-subtitle-vtt"; } else return mimeType; } diff --git a/ext/dash/gstmpdparser.h b/ext/dash/gstmpdparser.h index cf06e00..85eae03 100644 --- a/ext/dash/gstmpdparser.h +++ b/ext/dash/gstmpdparser.h @@ -572,9 +572,9 @@ gboolean gst_mpd_client_has_previous_period (GstMpdClient * client); /* Representation selection */ #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION -gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint max_bandwidth, gint max_video_width, gint max_video_height); +gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint64 max_bandwidth, gint max_video_width, gint max_video_height); #else -gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint max_bandwidth, gint max_video_width, gint max_video_height, gint max_video_framerate_n, gint max_video_framerate_d); +gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint max_video_framerate_n, gint max_video_framerate_d); #endif gint gst_mpdparser_get_rep_idx_with_min_bandwidth (GList * Representations); diff --git a/ext/hls/Makefile.am b/ext/hls/Makefile.am index 8314ff0..111e9d4 100644 --- a/ext/hls/Makefile.am +++ b/ext/hls/Makefile.am @@ -7,6 +7,7 @@ libgsthls_la_SOURCES = \ gsthlsdemux-util.c \ gsthlsplugin.c \ gsthlssink.c \ + gsthlssink2.c \ gstm3u8playlist.c libgsthls_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(LIBGCRYPT_CFLAGS) $(NETTLE_CFLAGS) $(OPENSSL_CFLAGS) @@ -23,5 +24,6 @@ noinst_HEADERS = \ gsthls.h \ gsthlsdemux.h \ gsthlssink.h \ + gsthlssink2.h \ gstm3u8playlist.h \ m3u8.h diff --git a/ext/hls/gsthlsdemux.c b/ext/hls/gsthlsdemux.c index 1f7bf4e..8482a5a 100644 --- a/ext/hls/gsthlsdemux.c +++ b/ext/hls/gsthlsdemux.c @@ -915,19 +915,17 @@ gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux, { GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); // FIXME: pass HlsStream into function GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); - GstMapInfo info; GstClockTime first_pcr, last_pcr; GstTagList *tags; if (buffer == NULL) return GST_FLOW_OK; - gst_buffer_map (buffer, &info, GST_MAP_READ); - if (G_UNLIKELY (hls_stream->do_typefind)) { GstCaps *caps = NULL; guint buffer_size; GstTypeFindProbability prob = GST_TYPE_FIND_NONE; + GstMapInfo info; if (hls_stream->pending_typefind_buffer) buffer = gst_buffer_append (hls_stream->pending_typefind_buffer, buffer); @@ -979,11 +977,11 @@ gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux, gst_adaptive_demux_stream_set_caps (stream, caps); #endif hls_stream->do_typefind = FALSE; + + gst_buffer_unmap (buffer, &info); } g_assert (hls_stream->pending_typefind_buffer == NULL); - gst_buffer_unmap (buffer, &info); - // Accumulate this buffer if (hls_stream->pending_pcr_buffer) { buffer = gst_buffer_append (hls_stream->pending_pcr_buffer, buffer); @@ -1819,11 +1817,17 @@ static gboolean gst_hls_demux_stream_decrypt_start (GstHLSDemuxStream * stream, const guint8 * key_data, const guint8 * iv_data) { + EVP_CIPHER_CTX *ctx; +#if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_CIPHER_CTX_init (&stream->aes_ctx); - if (!EVP_DecryptInit_ex (&stream->aes_ctx, EVP_aes_128_cbc (), NULL, key_data, - iv_data)) + ctx = &stream->aes_ctx; +#else + stream->aes_ctx = EVP_CIPHER_CTX_new (); + ctx = stream->aes_ctx; +#endif + if (!EVP_DecryptInit_ex (ctx, EVP_aes_128_cbc (), NULL, key_data, iv_data)) return FALSE; - EVP_CIPHER_CTX_set_padding (&stream->aes_ctx, 0); + EVP_CIPHER_CTX_set_padding (ctx, 0); return TRUE; } @@ -1832,15 +1836,21 @@ decrypt_fragment (GstHLSDemuxStream * stream, gsize length, const guint8 * encrypted_data, guint8 * decrypted_data) { int len, flen = 0; + EVP_CIPHER_CTX *ctx; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + ctx = &stream->aes_ctx; +#else + ctx = stream->aes_ctx; +#endif if (G_UNLIKELY (length > G_MAXINT || length % 16 != 0)) return FALSE; len = (int) length; - if (!EVP_DecryptUpdate (&stream->aes_ctx, decrypted_data, &len, - encrypted_data, len)) + if (!EVP_DecryptUpdate (ctx, decrypted_data, &len, encrypted_data, len)) return FALSE; - EVP_DecryptFinal_ex (&stream->aes_ctx, decrypted_data + len, &flen); + EVP_DecryptFinal_ex (ctx, decrypted_data + len, &flen); g_return_val_if_fail (len + flen == length, FALSE); return TRUE; } @@ -1848,7 +1858,12 @@ decrypt_fragment (GstHLSDemuxStream * stream, gsize length, static void gst_hls_demux_stream_decrypt_end (GstHLSDemuxStream * stream) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_CIPHER_CTX_cleanup (&stream->aes_ctx); +#else + EVP_CIPHER_CTX_free (stream->aes_ctx); + stream->aes_ctx = NULL; +#endif } #elif defined(HAVE_NETTLE) diff --git a/ext/hls/gsthlsdemux.h b/ext/hls/gsthlsdemux.h index 2007489..bb04c82 100644 --- a/ext/hls/gsthlsdemux.h +++ b/ext/hls/gsthlsdemux.h @@ -104,7 +104,11 @@ struct _GstHLSDemuxStream /* decryption tooling */ #if defined(HAVE_OPENSSL) +# if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_CIPHER_CTX aes_ctx; +# else + EVP_CIPHER_CTX *aes_ctx; +# endif #elif defined(HAVE_NETTLE) struct CBC_CTX (struct aes_ctx, AES_BLOCK_SIZE) aes_ctx; #else @@ -144,6 +148,7 @@ struct _GstHLSDemux /* FIXME: check locking, protected automatically by manifest_lock already? */ /* The master playlist with the available variant streams */ GstHLSMasterPlaylist *master; + GstHLSVariantStream *current_variant; }; diff --git a/ext/hls/gsthlsplugin.c b/ext/hls/gsthlsplugin.c index 552f514..fc26588 100644 --- a/ext/hls/gsthlsplugin.c +++ b/ext/hls/gsthlsplugin.c @@ -7,6 +7,7 @@ #include "gsthls.h" #include "gsthlsdemux.h" #include "gsthlssink.h" +#include "gsthlssink2.h" GST_DEBUG_CATEGORY (hls_debug); @@ -22,6 +23,9 @@ hls_init (GstPlugin * plugin) if (!gst_hls_sink_plugin_init (plugin)) return FALSE; + if (!gst_hls_sink2_plugin_init (plugin)) + return FALSE; + return TRUE; } diff --git a/ext/hls/gsthlssink2.c b/ext/hls/gsthlssink2.c new file mode 100644 index 0000000..64a1642 --- /dev/null +++ b/ext/hls/gsthlssink2.c @@ -0,0 +1,478 @@ +/* GStreamer + * Copyright (C) 2011 Alessandro Decina + * Copyright (C) 2017 Sebastian Dröge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-hlssink + * @title: hlssink + * + * HTTP Live Streaming sink/server + * + * ## Example launch line + * |[ + * gst-launch-1.0 videotestsrc is-live=true ! x264enc ! hlssink max-files=5 + * ]| + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gsthlssink2.h" +#include +#include +#include +#include + + +GST_DEBUG_CATEGORY_STATIC (gst_hls_sink2_debug); +#define GST_CAT_DEFAULT gst_hls_sink2_debug + +#define DEFAULT_LOCATION "segment%05d.ts" +#define DEFAULT_PLAYLIST_LOCATION "playlist.m3u8" +#define DEFAULT_PLAYLIST_ROOT NULL +#define DEFAULT_MAX_FILES 10 +#define DEFAULT_TARGET_DURATION 15 +#define DEFAULT_PLAYLIST_LENGTH 5 + +#define GST_M3U8_PLAYLIST_VERSION 3 + +enum +{ + PROP_0, + PROP_LOCATION, + PROP_PLAYLIST_LOCATION, + PROP_PLAYLIST_ROOT, + PROP_MAX_FILES, + PROP_TARGET_DURATION, + PROP_PLAYLIST_LENGTH +}; + +static GstStaticPadTemplate video_template = GST_STATIC_PAD_TEMPLATE ("video", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); +static GstStaticPadTemplate audio_template = GST_STATIC_PAD_TEMPLATE ("audio", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY); + +#define gst_hls_sink2_parent_class parent_class +G_DEFINE_TYPE (GstHlsSink2, gst_hls_sink2, GST_TYPE_BIN); + +static void gst_hls_sink2_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * spec); +static void gst_hls_sink2_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * spec); +static void gst_hls_sink2_handle_message (GstBin * bin, GstMessage * message); +static void gst_hls_sink2_reset (GstHlsSink2 * sink); +static GstStateChangeReturn +gst_hls_sink2_change_state (GstElement * element, GstStateChange trans); +static GstPad *gst_hls_sink2_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name, const GstCaps * caps); +static void gst_hls_sink2_release_pad (GstElement * element, GstPad * pad); + +static void +gst_hls_sink2_dispose (GObject * object) +{ + GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object); + + G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink); +} + +static void +gst_hls_sink2_finalize (GObject * object) +{ + GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object); + + g_free (sink->location); + g_free (sink->playlist_location); + g_free (sink->playlist_root); + if (sink->playlist) + gst_m3u8_playlist_free (sink->playlist); + + g_queue_foreach (&sink->old_locations, (GFunc) g_free, NULL); + g_queue_clear (&sink->old_locations); + + G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink); +} + +static void +gst_hls_sink2_class_init (GstHlsSink2Class * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + GstBinClass *bin_class; + + gobject_class = (GObjectClass *) klass; + element_class = GST_ELEMENT_CLASS (klass); + bin_class = GST_BIN_CLASS (klass); + + gst_element_class_add_static_pad_template (element_class, &video_template); + gst_element_class_add_static_pad_template (element_class, &audio_template); + + gst_element_class_set_static_metadata (element_class, + "HTTP Live Streaming sink", "Sink", "HTTP Live Streaming sink", + "Alessandro Decina , " + "Sebastian Dröge "); + + element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_sink2_change_state); + element_class->request_new_pad = + GST_DEBUG_FUNCPTR (gst_hls_sink2_request_new_pad); + element_class->release_pad = GST_DEBUG_FUNCPTR (gst_hls_sink2_release_pad); + + bin_class->handle_message = gst_hls_sink2_handle_message; + + gobject_class->dispose = gst_hls_sink2_dispose; + gobject_class->finalize = gst_hls_sink2_finalize; + gobject_class->set_property = gst_hls_sink2_set_property; + gobject_class->get_property = gst_hls_sink2_get_property; + + g_object_class_install_property (gobject_class, PROP_LOCATION, + g_param_spec_string ("location", "File Location", + "Location of the file to write", DEFAULT_LOCATION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PLAYLIST_LOCATION, + g_param_spec_string ("playlist-location", "Playlist Location", + "Location of the playlist to write", DEFAULT_PLAYLIST_LOCATION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PLAYLIST_ROOT, + g_param_spec_string ("playlist-root", "Playlist Root", + "Location of the playlist to write", DEFAULT_PLAYLIST_ROOT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_MAX_FILES, + g_param_spec_uint ("max-files", "Max files", + "Maximum number of files to keep on disk. Once the maximum is reached," + "old files start to be deleted to make room for new ones.", + 0, G_MAXUINT, DEFAULT_MAX_FILES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_TARGET_DURATION, + g_param_spec_uint ("target-duration", "Target duration", + "The target duration in seconds of a segment/file. " + "(0 - disabled, useful for management of segment duration by the " + "streaming server)", + 0, G_MAXUINT, DEFAULT_TARGET_DURATION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_PLAYLIST_LENGTH, + g_param_spec_uint ("playlist-length", "Playlist length", + "Length of HLS playlist. To allow players to conform to section 6.3.3 " + "of the HLS specification, this should be at least 3. If set to 0, " + "the playlist will be infinite.", + 0, G_MAXUINT, DEFAULT_PLAYLIST_LENGTH, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_hls_sink2_init (GstHlsSink2 * sink) +{ + GstElement *mux; + + sink->location = g_strdup (DEFAULT_LOCATION); + sink->playlist_location = g_strdup (DEFAULT_PLAYLIST_LOCATION); + sink->playlist_root = g_strdup (DEFAULT_PLAYLIST_ROOT); + sink->playlist_length = DEFAULT_PLAYLIST_LENGTH; + sink->max_files = DEFAULT_MAX_FILES; + sink->target_duration = DEFAULT_TARGET_DURATION; + g_queue_init (&sink->old_locations); + + sink->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL); + gst_bin_add (GST_BIN (sink), sink->splitmuxsink); + + mux = gst_element_factory_make ("mpegtsmux", NULL); + g_object_set (sink->splitmuxsink, "location", sink->location, "max-size-time", + ((GstClockTime) sink->target_duration * GST_SECOND), + "send-keyframe-requests", TRUE, "muxer", mux, NULL); + + GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK); + + gst_hls_sink2_reset (sink); +} + +static void +gst_hls_sink2_reset (GstHlsSink2 * sink) +{ + sink->index = 0; + + if (sink->playlist) + gst_m3u8_playlist_free (sink->playlist); + sink->playlist = + gst_m3u8_playlist_new (GST_M3U8_PLAYLIST_VERSION, sink->playlist_length, + FALSE); + + g_queue_foreach (&sink->old_locations, (GFunc) g_free, NULL); + g_queue_clear (&sink->old_locations); +} + +static void +gst_hls_sink2_write_playlist (GstHlsSink2 * sink) +{ + char *playlist_content; + GError *error = NULL; + + playlist_content = gst_m3u8_playlist_render (sink->playlist); + if (!g_file_set_contents (sink->playlist_location, + playlist_content, -1, &error)) { + GST_ERROR ("Failed to write playlist: %s", error->message); + GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE, + (("Failed to write playlist '%s'."), error->message), (NULL)); + g_error_free (error); + error = NULL; + } + g_free (playlist_content); + +} + +static void +gst_hls_sink2_handle_message (GstBin * bin, GstMessage * message) +{ + GstHlsSink2 *sink = GST_HLS_SINK2_CAST (bin); + + switch (message->type) { + case GST_MESSAGE_ELEMENT: + { + const GstStructure *s = gst_message_get_structure (message); + if (message->src == GST_OBJECT_CAST (sink->splitmuxsink)) { + if (gst_structure_has_name (s, "splitmuxsink-fragment-opened")) { + g_free (sink->current_location); + sink->current_location = + g_strdup (gst_structure_get_string (s, "location")); + gst_structure_get_clock_time (s, "running-time", + &sink->current_running_time_start); + } else if (gst_structure_has_name (s, "splitmuxsink-fragment-closed")) { + GstClockTime running_time; + gchar *entry_location; + + g_assert (strcmp (sink->current_location, gst_structure_get_string (s, + "location")) == 0); + + gst_structure_get_clock_time (s, "running-time", &running_time); + + GST_INFO_OBJECT (sink, "COUNT %d", sink->index); + if (sink->playlist_root == NULL) { + entry_location = g_path_get_basename (sink->current_location); + } else { + gchar *name = g_path_get_basename (sink->current_location); + entry_location = g_build_filename (sink->playlist_root, name, NULL); + g_free (name); + } + + gst_m3u8_playlist_add_entry (sink->playlist, entry_location, + NULL, running_time - sink->current_running_time_start, + sink->index++, FALSE); + g_free (entry_location); + + gst_hls_sink2_write_playlist (sink); + + g_queue_push_tail (&sink->old_locations, + g_strdup (sink->current_location)); + + while (g_queue_get_length (&sink->old_locations) > + g_queue_get_length (sink->playlist->entries)) { + gchar *old_location = g_queue_pop_head (&sink->old_locations); + g_remove (old_location); + g_free (old_location); + } + } + } + break; + } + case GST_MESSAGE_EOS:{ + sink->playlist->end_list = TRUE; + gst_hls_sink2_write_playlist (sink); + break; + } + default: + break; + } + + GST_BIN_CLASS (parent_class)->handle_message (bin, message); +} + +static GstPad * +gst_hls_sink2_request_new_pad (GstElement * element, GstPadTemplate * templ, + const gchar * name, const GstCaps * caps) +{ + GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element); + GstPad *pad, *peer; + gboolean is_audio; + + g_return_val_if_fail (strcmp (templ->name_template, "audio") == 0 + || strcmp (templ->name_template, "video") == 0, NULL); + g_return_val_if_fail (strcmp (templ->name_template, "audio") != 0 + || !sink->audio_sink, NULL); + g_return_val_if_fail (strcmp (templ->name_template, "video") != 0 + || !sink->video_sink, NULL); + + is_audio = strcmp (templ->name_template, "audio") == 0; + + peer = + gst_element_get_request_pad (sink->splitmuxsink, + is_audio ? "audio_0" : "video"); + if (!peer) + return NULL; + + pad = gst_ghost_pad_new_from_template (templ->name_template, peer, templ); + gst_pad_set_active (pad, TRUE); + gst_element_add_pad (element, pad); + gst_object_unref (peer); + + if (is_audio) + sink->audio_sink = pad; + else + sink->video_sink = pad; + + return pad; +} + +static void +gst_hls_sink2_release_pad (GstElement * element, GstPad * pad) +{ + GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element); + GstPad *peer; + + g_return_if_fail (pad == sink->audio_sink || pad == sink->video_sink); + + peer = gst_pad_get_peer (pad); + if (peer) { + gst_element_release_request_pad (sink->splitmuxsink, pad); + gst_object_unref (peer); + } + + gst_object_ref (pad); + gst_element_remove_pad (element, pad); + gst_pad_set_active (pad, FALSE); + if (pad == sink->audio_sink) + sink->audio_sink = NULL; + else + sink->video_sink = NULL; + + gst_object_unref (pad); +} + +static GstStateChangeReturn +gst_hls_sink2_change_state (GstElement * element, GstStateChange trans) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element); + + switch (trans) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!sink->splitmuxsink) { + return GST_STATE_CHANGE_FAILURE; + } + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans); + + switch (trans) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + case GST_STATE_CHANGE_READY_TO_NULL: + gst_hls_sink2_reset (sink); + break; + default: + break; + } + + return ret; +} + +static void +gst_hls_sink2_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object); + + switch (prop_id) { + case PROP_LOCATION: + g_free (sink->location); + sink->location = g_value_dup_string (value); + if (sink->splitmuxsink) + g_object_set (sink->splitmuxsink, "location", sink->location, NULL); + break; + case PROP_PLAYLIST_LOCATION: + g_free (sink->playlist_location); + sink->playlist_location = g_value_dup_string (value); + break; + case PROP_PLAYLIST_ROOT: + g_free (sink->playlist_root); + sink->playlist_root = g_value_dup_string (value); + break; + case PROP_MAX_FILES: + sink->max_files = g_value_get_uint (value); + break; + case PROP_TARGET_DURATION: + sink->target_duration = g_value_get_uint (value); + if (sink->splitmuxsink) { + g_object_set (sink->splitmuxsink, "max-size-time", + ((GstClockTime) sink->target_duration * GST_SECOND), NULL); + } + break; + case PROP_PLAYLIST_LENGTH: + sink->playlist_length = g_value_get_uint (value); + sink->playlist->window_size = sink->playlist_length; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_hls_sink2_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object); + + switch (prop_id) { + case PROP_LOCATION: + g_value_set_string (value, sink->location); + break; + case PROP_PLAYLIST_LOCATION: + g_value_set_string (value, sink->playlist_location); + break; + case PROP_PLAYLIST_ROOT: + g_value_set_string (value, sink->playlist_root); + break; + case PROP_MAX_FILES: + g_value_set_uint (value, sink->max_files); + break; + case PROP_TARGET_DURATION: + g_value_set_uint (value, sink->target_duration); + break; + case PROP_PLAYLIST_LENGTH: + g_value_set_uint (value, sink->playlist_length); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gboolean +gst_hls_sink2_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_hls_sink2_debug, "hlssink2", 0, "HlsSink2"); + return gst_element_register (plugin, "hlssink2", GST_RANK_NONE, + gst_hls_sink2_get_type ()); +} diff --git a/ext/hls/gsthlssink2.h b/ext/hls/gsthlssink2.h new file mode 100644 index 0000000..effefd6 --- /dev/null +++ b/ext/hls/gsthlssink2.h @@ -0,0 +1,69 @@ +/* GStreamer + * Copyright (C) 2011 Alessandro Decina + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef _GST_HLS_SINK2_H_ +#define _GST_HLS_SINK2_H_ + +#include "gstm3u8playlist.h" +#include + +G_BEGIN_DECLS + +#define GST_TYPE_HLS_SINK2 (gst_hls_sink2_get_type()) +#define GST_HLS_SINK2(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_HLS_SINK2,GstHlsSink2)) +#define GST_HLS_SINK2_CAST(obj) ((GstHlsSink2 *) obj) +#define GST_HLS_SINK2_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_HLS_SINK2,GstHlsSink2Class)) +#define GST_IS_HLS_SINK2(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_HLS_SINK2)) +#define GST_IS_HLS_SINK2_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_HLS_SINK2)) + +typedef struct _GstHlsSink2 GstHlsSink2; +typedef struct _GstHlsSink2Class GstHlsSink2Class; + +struct _GstHlsSink2 +{ + GstBin bin; + + GstElement *splitmuxsink; + GstPad *audio_sink, *video_sink; + + gchar *location; + gchar *playlist_location; + gchar *playlist_root; + guint playlist_length; + gint max_files; + gint target_duration; + + GstM3U8Playlist *playlist; + guint index; + + gchar *current_location; + GstClockTime current_running_time_start; + GQueue old_locations; +}; + +struct _GstHlsSink2Class +{ + GstBinClass bin_class; +}; + +GType gst_hls_sink2_get_type (void); +gboolean gst_hls_sink2_plugin_init (GstPlugin * plugin); + +G_END_DECLS + +#endif diff --git a/ext/hls/m3u8.c b/ext/hls/m3u8.c index 0e74175..d74977b 100644 --- a/ext/hls/m3u8.c +++ b/ext/hls/m3u8.c @@ -111,6 +111,7 @@ gst_m3u8_unref (GstM3U8 * self) g_list_free (self->files); g_free (self->last_data); + g_mutex_clear (&self->lock); g_free (self); } } @@ -309,139 +310,138 @@ gst_hls_variant_stream_compare_by_bitrate (gconstpointer a, gconstpointer b) return vs_a->bandwidth - vs_b->bandwidth; } +/* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not, + * the client SHOULD halt playback (6.3.4), which is what we do then. */ static gboolean -gst_m3u8_update_check_consistent_media_seqnums (GstM3U8 * self, - gboolean have_mediasequence, GList * previous_files) +check_media_seqnums (GstM3U8 * self, GList * previous_files) { - if (!previous_files) + GList *l, *m; + GstM3U8MediaFile *f1 = NULL, *f2 = NULL; + + g_return_val_if_fail (previous_files, FALSE); + + if (!self->files) { + /* Empty playlists are trivially consistent */ return TRUE; + } - /* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not, - * the client SHOULD halt playback (6.3.4), which is what we do then. - * - * If we don't have MEDIA-SEQUENCE, we check URIs in the previous and - * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new - * playlist in relation to the old. That is, same URIs get the same number - * and later URIs get higher numbers */ - if (have_mediasequence) { - GList *l, *m; - GstM3U8MediaFile *f1 = NULL, *f2 = NULL; - - /* Find first case of higher/equal sequence number in new playlist or - * same URI. From there on we can linearly step ahead */ - for (l = self->files; l; l = l->next) { - gboolean match = FALSE; + /* Find first case of higher/equal sequence number in new playlist. + * From there on we can linearly step ahead */ + for (l = self->files; l; l = l->next) { + gboolean match = FALSE; - f1 = l->data; - for (m = previous_files; m; m = m->next) { - f2 = m->data; + f1 = l->data; + for (m = previous_files; m; m = m->next) { + f2 = m->data; - if (f1->sequence >= f2->sequence || g_str_equal (f1->uri, f2->uri)) { - match = TRUE; - break; - } - } - if (match) + if (f1->sequence >= f2->sequence) { + match = TRUE; break; + } } + if (match) + break; + } - if (!l) { - /* No match, no sequence in the new playlist was higher than - * any in the old, and no URI was found again. This is bad! */ - GST_ERROR ("Media sequences inconsistent, ignoring"); - return FALSE; - } else { - g_assert (f1 != NULL); - g_assert (f2 != NULL); - - for (; l && m; l = l->next, m = m->next) { - f1 = l->data; - f2 = m->data; - - if (f1->sequence == f2->sequence) { - if (!g_str_equal (f1->uri, f2->uri)) { - /* Same sequence, different URI. This is bad! */ - GST_ERROR ("Media sequences inconsistent, ignoring"); - return FALSE; - } else { - /* Good case, we advance and check the next one */ - } - } else if (g_str_equal (f1->uri, f2->uri)) { - /* Same URIs but different sequences, this is bad! */ - GST_ERROR ("Media sequences inconsistent, ignoring"); - return FALSE; - } else { - /* Not same URI, not same sequence but by construction sequence - * must be higher in the new one. All good in that case, if it - * isn't then this means that sequence numbers are decreasing - * or files were inserted */ - if (f1->sequence < f2->sequence) { - GST_ERROR ("Media sequences inconsistent, ignoring"); - return FALSE; - } - } - } + /* We must have seen at least one entry on each list */ + g_assert (f1 != NULL); + g_assert (f2 != NULL); + + if (!l) { + /* No match, no sequence in the new playlist was higher than + * any in the old. This is bad! */ + GST_ERROR ("Media sequence doesn't continue: last new %" G_GINT64_FORMAT + " < last old %" G_GINT64_FORMAT, f1->sequence, f2->sequence); + return FALSE; + } + + for (; l && m; l = l->next, m = m->next) { + f1 = l->data; + f2 = m->data; - /* All good if we're getting here */ + if (f1->sequence == f2->sequence && !g_str_equal (f1->uri, f2->uri)) { + /* Same sequence, different URI. This is bad! */ + GST_ERROR ("Media URIs inconsistent (sequence %" G_GINT64_FORMAT + "): had '%s', got '%s'", f1->sequence, f2->uri, f1->uri); + return FALSE; + } else if (f1->sequence < f2->sequence) { + /* Not same sequence but by construction sequence must be higher in the + * new one. All good in that case, if it isn't then this means that + * sequence numbers are decreasing or files were inserted */ + GST_ERROR ("Media sequences inconsistent: %" G_GINT64_FORMAT " < %" + G_GINT64_FORMAT ": URIs new '%s' old '%s'", f1->sequence, + f2->sequence, f1->uri, f2->uri); + return FALSE; } - } else { - GList *l, *m; - GstM3U8MediaFile *f1 = NULL, *f2 = NULL; - gint64 mediasequence; + } - for (l = self->files; l; l = l->next) { - gboolean match = FALSE; + /* All good if we're getting here */ + return TRUE; +} - f1 = l->data; - for (m = previous_files; m; m = m->next) { - f2 = m->data; +/* If we don't have MEDIA-SEQUENCE, we check URIs in the previous and + * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new + * playlist in relation to the old. That is, same URIs get the same number + * and later URIs get higher numbers */ +static void +generate_media_seqnums (GstM3U8 * self, GList * previous_files) +{ + GList *l, *m; + GstM3U8MediaFile *f1 = NULL, *f2 = NULL; + gint64 mediasequence; - if (g_str_equal (f1->uri, f2->uri)) { - match = TRUE; - break; - } - } + g_return_if_fail (previous_files); - if (match) - break; - } + /* Find first case of same URI in new playlist. + * From there on we can linearly step ahead */ + for (l = self->files; l; l = l->next) { + gboolean match = FALSE; - if (!l) { - /* No match, this means f2 is the last item in the previous playlist - * and we have to start our new playlist at that sequence */ - mediasequence = f2->sequence + 1; + f1 = l->data; + for (m = previous_files; m; m = m->next) { + f2 = m->data; - for (l = self->files; l; l = l->next) { - f1 = l->data; - f1->sequence = mediasequence; - mediasequence++; + if (g_str_equal (f1->uri, f2->uri)) { + match = TRUE; + break; } - } else { - /* Match, check that all following ones are matching too and continue - * sequence numbers from there on */ + } + + if (match) + break; + } - mediasequence = f2->sequence; + if (l) { + /* Match, check that all following ones are matching too and continue + * sequence numbers from there on */ - for (; l; l = l->next) { - f1 = l->data; - f2 = m ? m->data : NULL; + mediasequence = f2->sequence; - f1->sequence = mediasequence; - mediasequence++; + for (; l && m; l = l->next, m = m->next) { + f1 = l->data; + f2 = m->data; - if (f2) { - if (!g_str_equal (f1->uri, f2->uri)) { - GST_WARNING ("Inconsistent URIs after playlist update"); - } - } + f1->sequence = mediasequence; + mediasequence++; - if (m) - m = m->next; + if (!g_str_equal (f1->uri, f2->uri)) { + GST_WARNING ("Inconsistent URIs after playlist update: '%s' != '%s'", + f1->uri, f2->uri); } } + } else { + /* No match, this means f2 is the last item in the previous playlist + * and we have to start our new playlist at that sequence */ + mediasequence = f2->sequence + 1; + l = self->files; } - return TRUE; + for (; l; l = l->next) { + f1 = l->data; + + f1->sequence = mediasequence; + mediasequence++; + } } /* @@ -694,8 +694,13 @@ gst_m3u8_update (GstM3U8 * self, gchar * data) self->files = g_list_reverse (self->files); if (previous_files) { - gboolean consistent = gst_m3u8_update_check_consistent_media_seqnums (self, - have_mediasequence, previous_files); + gboolean consistent = TRUE; + + if (have_mediasequence) { + consistent = check_media_seqnums (self, previous_files); + } else { + generate_media_seqnums (self, previous_files); + } g_list_foreach (previous_files, (GFunc) gst_m3u8_media_file_unref, NULL); g_list_free (previous_files); @@ -1000,7 +1005,7 @@ gst_m3u8_advance_fragment (GstM3U8 * m3u8, gboolean forward) } } if (m3u8->current_file) { - /* Store duration of the fragment we're using to update the position + /* Store duration of the fragment we're using to update the position * the next time we advance */ m3u8->current_file_duration = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration; @@ -1530,8 +1535,15 @@ gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri) data += stream->iframe ? 26 : 18; while (data && parse_attributes (&data, &a, &v)) { if (g_str_equal (a, "BANDWIDTH")) { + if (!stream->bandwidth) { + if (!int_from_string (v, NULL, &stream->bandwidth)) + GST_WARNING ("Error while reading BANDWIDTH"); + } + } else if (g_str_equal (a, "AVERAGE-BANDWIDTH")) { + GST_DEBUG + ("AVERAGE-BANDWIDTH attribute available. Using it as stream bandwidth"); if (!int_from_string (v, NULL, &stream->bandwidth)) - GST_WARNING ("Error while reading BANDWIDTH"); + GST_WARNING ("Error while reading AVERAGE-BANDWIDTH"); } else if (g_str_equal (a, "PROGRAM-ID")) { if (!int_from_string (v, NULL, &stream->program_id)) GST_WARNING ("Error while reading PROGRAM-ID"); @@ -1866,7 +1878,7 @@ gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist * /* variant lists are sorted low to high, so iterate from highest to lowest */ if (current_variant == NULL || !current_variant->iframe) - l = g_list_last (playlist->variants); /* highest */ + l = g_list_last (playlist->variants); else l = g_list_last (playlist->iframe_variants); diff --git a/gst-libs/gst/Makefile.am b/gst-libs/gst/Makefile.am index ec9cd41..ab02f47 100644 --- a/gst-libs/gst/Makefile.am +++ b/gst-libs/gst/Makefile.am @@ -18,7 +18,7 @@ if TV_PROFILE SUBDIRS = codecparsers mpegts $(WAYLAND_DIR) else SUBDIRS = uridownloader adaptivedemux interfaces basecamerabinsrc codecparsers \ - insertbin mpegts base video audio player allocators $(GL_DIR) $(WAYLAND_DIR) \ + insertbin mpegts base video audio player isoff allocators $(GL_DIR) $(WAYLAND_DIR) \ $(OPENCV_DIR) endif @@ -28,7 +28,7 @@ if TV_PROFILE DIST_SUBDIRS = codecparsers mpegts wayland else DIST_SUBDIRS = uridownloader adaptivedemux interfaces gl basecamerabinsrc \ - codecparsers insertbin mpegts wayland opencv base video audio player allocators + codecparsers insertbin mpegts wayland opencv base video audio player isoff allocators endif #dependencies @@ -43,7 +43,7 @@ INDEPENDENT_SUBDIRS = \ else INDEPENDENT_SUBDIRS = \ interfaces basecamerabinsrc codecparsers insertbin uridownloader \ - mpegts base player allocators $(GL_DIR) $(WAYLAND_DIR) $(OPENCV_DIR) + mpegts base player isoff allocators $(GL_DIR) $(WAYLAND_DIR) $(OPENCV_DIR) endif .PHONY: independent-subdirs $(INDEPENDENT_SUBDIRS) diff --git a/gst-libs/gst/adaptivedemux/Makefile.am b/gst-libs/gst/adaptivedemux/Makefile.am index 74d7c42..2af0517 100644 --- a/gst-libs/gst/adaptivedemux/Makefile.am +++ b/gst-libs/gst/adaptivedemux/Makefile.am @@ -8,7 +8,7 @@ libgstadaptivedemux_@GST_API_VERSION@_la_SOURCES = \ libgstadaptivedemux_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/gst/adaptivedemux -noinst_HEADERS = gstadaptivedemux.h +noinst_HEADERS = gstadaptivedemux.h adaptive-demux-prelude.h libgstadaptivedemux_@GST_API_VERSION@_la_CFLAGS = \ $(GST_PLUGINS_BAD_CFLAGS) \ diff --git a/gst-libs/gst/adaptivedemux/adaptive-demux-prelude.h b/gst-libs/gst/adaptivedemux/adaptive-demux-prelude.h new file mode 100644 index 0000000..402a78f --- /dev/null +++ b/gst-libs/gst/adaptivedemux/adaptive-demux-prelude.h @@ -0,0 +1,31 @@ +/* GStreamer AdaptiveDemux Library + * Copyright (C) 2018 GStreamer developers + * + * adaptive-demux-prelude.h: prelude include header for gst-adaptivedemux library + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_ADAPTIVE_DEMUX_PRELUDE_H__ +#define __GST_ADAPTIVE_DEMUX_PRELUDE_H__ + +#include + +#ifndef GST_ADAPTIVE_DEMUX_API +#define GST_ADAPTIVE_DEMUX_API GST_EXPORT +#endif + +#endif /* __GST_ADAPTIVE_DEMUX_PRELUDE_H__ */ diff --git a/gst-libs/gst/adaptivedemux/gstadaptivedemux.c b/gst-libs/gst/adaptivedemux/gstadaptivedemux.c index 70a47bc..93ce139 100644 --- a/gst-libs/gst/adaptivedemux/gstadaptivedemux.c +++ b/gst-libs/gst/adaptivedemux/gstadaptivedemux.c @@ -31,7 +31,7 @@ * * Adaptive formats (HLS, DASH, MSS) are composed of a manifest file and * a set of fragments. The manifest describes the available media and - * the sequence of fragments to use. Each fragments contains a small + * the sequence of fragments to use. Each fragment contains a small * part of the media (typically only a few seconds). It is possible for * the manifest to have the same media available in different configurations * (bitrates for example) so that the client can select the one that @@ -688,11 +688,10 @@ gst_adaptive_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX_CAST (parent); - GstAdaptiveDemuxClass *demux_class; gboolean ret; switch (event->type) { - case GST_EVENT_FLUSH_STOP: + case GST_EVENT_FLUSH_STOP:{ GST_API_LOCK (demux); GST_MANIFEST_LOCK (demux); @@ -704,7 +703,9 @@ gst_adaptive_demux_sink_event (GstPad * pad, GstObject * parent, GST_API_UNLOCK (demux); return ret; + } case GST_EVENT_EOS:{ + GstAdaptiveDemuxClass *demux_class; GstQuery *query; gboolean query_res; gboolean ret = TRUE; @@ -1245,10 +1246,14 @@ gst_adaptive_demux_prepare_streams (GstAdaptiveDemux * demux, if (!gst_adaptive_demux_prepare_stream (demux, GST_ADAPTIVE_DEMUX_STREAM_CAST (stream))) { /* TODO act on error */ + GST_FIXME_OBJECT (stream->pad, + "Do something on failure to expose stream"); } if (first_and_live) { - /* TODO we only need the first timestamp, maybe create a simple function */ + /* TODO we only need the first timestamp, maybe create a simple function to + * get the current PTS of a fragment ? */ + GST_DEBUG_OBJECT (demux, "Calling update_fragment_info"); gst_adaptive_demux_stream_update_fragment_info (demux, stream); if (GST_CLOCK_TIME_IS_VALID (min_pts)) { @@ -1377,6 +1382,7 @@ gst_adaptive_demux_prepare_streams (GstAdaptiveDemux * demux, stream->pending_segment = gst_event_new_segment (&stream->segment); gst_event_set_seqnum (stream->pending_segment, demux->priv->segment_seqnum); + stream->qos_earliest_time = GST_CLOCK_TIME_NONE; GST_DEBUG_OBJECT (demux, "Prepared segment %" GST_SEGMENT_FORMAT " for stream %p", @@ -1451,6 +1457,7 @@ gst_adaptive_demux_expose_streams (GstAdaptiveDemux * demux) gst_task_stop (stream->download_task); g_mutex_lock (&stream->fragment_download_lock); stream->cancelled = TRUE; + stream->replaced = TRUE; g_cond_signal (&stream->fragment_download_cond); g_mutex_unlock (&stream->fragment_download_lock); } @@ -1493,6 +1500,7 @@ gst_adaptive_demux_stream_new (GstAdaptiveDemux * demux, GstPad * pad) stream->fragment_bitrates = g_malloc0 (sizeof (guint64) * NUM_LOOKBACK_FRAGMENTS); gst_pad_set_element_private (pad, stream); + stream->qos_earliest_time = GST_CLOCK_TIME_NONE; g_mutex_lock (&demux->priv->preroll_lock); stream->do_block = TRUE; @@ -1692,11 +1700,12 @@ gst_adaptive_demux_update_streams_segment (GstAdaptiveDemux * demux, seg_evt = gst_event_new_segment (&stream->segment); gst_event_set_seqnum (seg_evt, demux->priv->segment_seqnum); gst_event_replace (&stream->pending_segment, seg_evt); - GST_DEBUG_OBJECT (stream->pad, "Pending segment now %" GST_SEGMENT_FORMAT, - &stream->pending_segment); + GST_DEBUG_OBJECT (stream->pad, "Pending segment now %" GST_PTR_FORMAT, + stream->pending_segment); gst_event_unref (seg_evt); /* Make sure the first buffer after a seek has the discont flag */ stream->discont = TRUE; + stream->qos_earliest_time = GST_CLOCK_TIME_NONE; } } @@ -1743,46 +1752,111 @@ gst_adaptive_demux_handle_seek_event (GstAdaptiveDemux * demux, GstPad * pad, if (format != GST_FORMAT_TIME) { GST_MANIFEST_UNLOCK (demux); GST_API_UNLOCK (demux); + GST_WARNING_OBJECT (demux, + "Adaptive demuxers only support TIME-based seeking"); + gst_event_unref (event); + return FALSE; + } + + if (flags & GST_SEEK_FLAG_SEGMENT) { + GST_FIXME_OBJECT (demux, "Handle segment seeks"); + GST_MANIFEST_UNLOCK (demux); + GST_API_UNLOCK (demux); gst_event_unref (event); return FALSE; } + seqnum = gst_event_get_seqnum (event); + if (gst_adaptive_demux_is_live (demux)) { gint64 range_start, range_stop; + gboolean changed = FALSE; + gboolean start_valid = TRUE, stop_valid = TRUE; + if (!gst_adaptive_demux_get_live_seek_range (demux, &range_start, &range_stop)) { GST_MANIFEST_UNLOCK (demux); GST_API_UNLOCK (demux); gst_event_unref (event); + GST_WARNING_OBJECT (demux, "Failure getting the live seek ranges"); return FALSE; } - if (!(flags & GST_SEEK_FLAG_ACCURATE)) { - /* If the accurate flag is not set, we allow seeking before the start - * to map to the start for live cases, since those can return a "moving - * target" based on wall time. - */ - if (start < range_start) { - guint64 dt = range_start - start; - GST_DEBUG_OBJECT (demux, - "Non accurate seek before live stream start, offsetting by %" - GST_TIME_FORMAT, GST_TIME_ARGS (dt)); - start = range_start; - if (stop != GST_CLOCK_TIME_NONE) - stop += dt; - } + GST_DEBUG_OBJECT (demux, + "Live range is %" GST_STIME_FORMAT " %" GST_STIME_FORMAT, + GST_STIME_ARGS (range_start), GST_STIME_ARGS (range_stop)); + + /* Handle relative positioning for live streams (relative to the range_stop) */ + if (start_type == GST_SEEK_TYPE_END) { + start = range_stop + start; + start_type = GST_SEEK_TYPE_SET; + changed = TRUE; + } + if (stop_type == GST_SEEK_TYPE_END) { + stop = range_stop + stop; + stop_type = GST_SEEK_TYPE_SET; + changed = TRUE; + } + + /* Adjust the requested start/stop position if it falls beyond the live + * seek range. + * The only case where we don't adjust is for the starting point of + * an accurate seek (start if forward and stop if backwards) + */ + if (start_type == GST_SEEK_TYPE_SET && start < range_start && + (rate < 0 || !(flags & GST_SEEK_FLAG_ACCURATE))) { + GST_DEBUG_OBJECT (demux, + "seek before live stream start, setting to range start: %" + GST_TIME_FORMAT, GST_TIME_ARGS (range_start)); + start = range_start; + changed = TRUE; + } + /* truncate stop position also if set */ + if (stop_type == GST_SEEK_TYPE_SET && stop > range_stop && + (rate > 0 || !(flags & GST_SEEK_FLAG_ACCURATE))) { + GST_DEBUG_OBJECT (demux, + "seek ending after live start, adjusting to: %" + GST_TIME_FORMAT, GST_TIME_ARGS (range_stop)); + stop = range_stop; + changed = TRUE; } - if (start < range_start || start >= range_stop) { + if (start_type == GST_SEEK_TYPE_SET && GST_CLOCK_TIME_IS_VALID (start) && + (start < range_start || start > range_stop)) { + GST_WARNING_OBJECT (demux, + "Seek to invalid position start:%" GST_STIME_FORMAT + " out of seekable range (%" GST_STIME_FORMAT " - %" GST_STIME_FORMAT + ")", GST_STIME_ARGS (start), GST_STIME_ARGS (range_start), + GST_STIME_ARGS (range_stop)); + start_valid = FALSE; + } + if (stop_type == GST_SEEK_TYPE_SET && GST_CLOCK_TIME_IS_VALID (stop) && + (stop < range_start || stop > range_stop)) { + GST_WARNING_OBJECT (demux, + "Seek to invalid position stop:%" GST_STIME_FORMAT + " out of seekable range (%" GST_STIME_FORMAT " - %" GST_STIME_FORMAT + ")", GST_STIME_ARGS (stop), GST_STIME_ARGS (range_start), + GST_STIME_ARGS (range_stop)); + stop_valid = FALSE; + } + + /* If the seek position is still outside of the seekable range, refuse the seek */ + if (!start_valid || !stop_valid) { GST_MANIFEST_UNLOCK (demux); GST_API_UNLOCK (demux); - GST_WARNING_OBJECT (demux, "Seek to invalid position"); gst_event_unref (event); return FALSE; } - } - seqnum = gst_event_get_seqnum (event); + /* Re-create seek event with changed/updated values */ + if (changed) { + gst_event_unref (event); + event = + gst_event_new_seek (rate, format, flags, + start_type, start, stop_type, stop); + gst_event_set_seqnum (event, seqnum); + } + } GST_DEBUG_OBJECT (demux, "seek event, %" GST_PTR_FORMAT, event); @@ -1974,6 +2048,28 @@ gst_adaptive_demux_src_event (GstPad * pad, GstObject * parent, gst_event_unref (event); return TRUE; } + case GST_EVENT_QOS:{ + GstAdaptiveDemuxStream *stream; + + GST_MANIFEST_LOCK (demux); + stream = gst_adaptive_demux_find_stream_for_pad (demux, pad); + + if (stream) { + GstClockTimeDiff diff; + GstClockTime timestamp; + + gst_event_parse_qos (event, NULL, NULL, &diff, ×tamp); + /* Only take into account lateness if late */ + if (diff > 0) + stream->qos_earliest_time = timestamp + 2 * diff; + else + stream->qos_earliest_time = timestamp; + GST_DEBUG_OBJECT (stream->pad, "qos_earliest_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->qos_earliest_time)); + } + GST_MANIFEST_UNLOCK (demux); + break; + } default: break; } @@ -2141,6 +2237,7 @@ gst_adaptive_demux_start_tasks (GstAdaptiveDemux * demux, if (!start_preroll_streams) { g_mutex_lock (&stream->fragment_download_lock); stream->cancelled = FALSE; + stream->replaced = FALSE; g_mutex_unlock (&stream->fragment_download_lock); } @@ -2158,6 +2255,7 @@ gst_adaptive_demux_stop_manifest_update_task (GstAdaptiveDemux * demux) gst_task_stop (demux->priv->updates_task); g_mutex_lock (&demux->priv->updates_timed_lock); + GST_DEBUG_OBJECT (demux, "requesting stop of the manifest update task"); demux->priv->stop_updates_task = TRUE; g_cond_signal (&demux->priv->updates_timed_cond); g_mutex_unlock (&demux->priv->updates_timed_lock); @@ -2176,6 +2274,7 @@ gst_adaptive_demux_start_manifest_update_task (GstAdaptiveDemux * demux) g_mutex_unlock (&demux->priv->updates_timed_lock); /* Task to periodically update the manifest */ if (demux_class->requires_periodical_playlist_update (demux)) { + GST_DEBUG_OBJECT (demux, "requesting start of the manifest update task"); gst_task_start (demux->priv->updates_task); } } @@ -2190,20 +2289,27 @@ gst_adaptive_demux_start_manifest_update_task (GstAdaptiveDemux * demux) static void gst_adaptive_demux_stop_tasks (GstAdaptiveDemux * demux, gboolean stop_updates) { + int i; GList *iter; + GList *list_to_process; GST_LOG_OBJECT (demux, "Stopping tasks"); if (stop_updates) gst_adaptive_demux_stop_manifest_update_task (demux); - for (iter = demux->streams; iter; iter = g_list_next (iter)) { - GstAdaptiveDemuxStream *stream = iter->data; - g_mutex_lock (&stream->fragment_download_lock); - stream->cancelled = TRUE; - gst_task_stop (stream->download_task); - g_cond_signal (&stream->fragment_download_cond); - g_mutex_unlock (&stream->fragment_download_lock); + list_to_process = demux->streams; + for (i = 0; i < 2; ++i) { + for (iter = list_to_process; iter; iter = g_list_next (iter)) { + GstAdaptiveDemuxStream *stream = iter->data; + + g_mutex_lock (&stream->fragment_download_lock); + stream->cancelled = TRUE; + gst_task_stop (stream->download_task); + g_cond_signal (&stream->fragment_download_cond); + g_mutex_unlock (&stream->fragment_download_lock); + } + list_to_process = demux->prepared_streams; } GST_MANIFEST_UNLOCK (demux); @@ -2221,23 +2327,27 @@ gst_adaptive_demux_stop_tasks (GstAdaptiveDemux * demux, gboolean stop_updates) * object. Even if we temporarily release manifest_lock, the demux->streams * cannot change and iter cannot be invalidated. */ - for (iter = demux->streams; iter; iter = g_list_next (iter)) { - GstAdaptiveDemuxStream *stream = iter->data; - GstElement *src = stream->src; + list_to_process = demux->streams; + for (i = 0; i < 2; ++i) { + for (iter = list_to_process; iter; iter = g_list_next (iter)) { + GstAdaptiveDemuxStream *stream = iter->data; + GstElement *src = stream->src; - GST_MANIFEST_UNLOCK (demux); + GST_MANIFEST_UNLOCK (demux); - if (src) { - gst_element_set_locked_state (src, TRUE); - gst_element_set_state (src, GST_STATE_READY); - } + if (src) { + gst_element_set_locked_state (src, TRUE); + gst_element_set_state (src, GST_STATE_READY); + } - /* stream->download_task value never changes, so it is safe to read it - * outside critical section - */ - gst_task_join (stream->download_task); + /* stream->download_task value never changes, so it is safe to read it + * outside critical section + */ + gst_task_join (stream->download_task); - GST_MANIFEST_LOCK (demux); + GST_MANIFEST_LOCK (demux); + } + list_to_process = demux->prepared_streams; } GST_MANIFEST_UNLOCK (demux); @@ -2246,11 +2356,16 @@ gst_adaptive_demux_stop_tasks (GstAdaptiveDemux * demux, gboolean stop_updates) GST_MANIFEST_LOCK (demux); - for (iter = demux->streams; iter; iter = g_list_next (iter)) { - GstAdaptiveDemuxStream *stream = iter->data; + list_to_process = demux->streams; + for (i = 0; i < 2; ++i) { + for (iter = list_to_process; iter; iter = g_list_next (iter)) { + GstAdaptiveDemuxStream *stream = iter->data; - stream->download_error_count = 0; - stream->need_header = TRUE; + stream->download_error_count = 0; + stream->need_header = TRUE; + stream->qos_earliest_time = GST_CLOCK_TIME_NONE; + } + list_to_process = demux->prepared_streams; } } @@ -2493,6 +2608,9 @@ gst_adaptive_demux_stream_push_buffer (GstAdaptiveDemuxStream * stream, GstEvent *pending_caps = NULL, *pending_segment = NULL, *pending_tags = NULL; GList *pending_events = NULL; + /* FIXME : + * This is duplicating *exactly* the same thing as what is done at the beginning + * of _src_chain if starting_fragment is TRUE */ if (stream->first_fragment_buffer) { GstClockTime offset = gst_adaptive_demux_stream_get_presentation_offset (demux, stream); @@ -2645,6 +2763,9 @@ gst_adaptive_demux_stream_push_buffer (GstAdaptiveDemuxStream * stream, } /* Wait for preroll if blocking */ + GST_DEBUG_OBJECT (stream->pad, + "About to push buffer of size %" G_GSIZE_FORMAT, + gst_buffer_get_size (buffer)); ret = gst_pad_push (stream->pad, buffer); @@ -2720,6 +2841,11 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) } g_mutex_unlock (&stream->fragment_download_lock); + /* starting_fragment is set to TRUE at the beginning of + * _stream_download_fragment() + * /!\ If there is a header/index being downloaded, then this will + * be TRUE for the first one ... but FALSE for the remaining ones, + * including the *actual* fragment ! */ if (stream->starting_fragment) { GstClockTime offset = gst_adaptive_demux_stream_get_presentation_offset (demux, stream); @@ -2757,6 +2883,15 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) } else { GST_BUFFER_PTS (buffer) = GST_CLOCK_TIME_NONE; } + + /* downloading_first_buffer is set to TRUE in download_uri() just before + * activating the source (i.e. requesting a given URI) + * + * The difference with starting_fragment is that this will be called + * for *all* first buffers (of index, and header, and fragment) + * + * ... to then only do something useful (in this block) for actual + * fragments... */ if (stream->downloading_first_buffer) { gint64 chunk_size = 0; @@ -2896,6 +3031,10 @@ _src_event (GstPad * pad, GstObject * parent, GstEvent * event) gst_adaptive_demux_eos_handling (stream); + /* FIXME ? + * _eos_handling() calls fragment_download_finish() which does the + * same thing as below. + * Could this cause races ? */ g_mutex_lock (&stream->fragment_download_lock); stream->download_finished = TRUE; g_cond_signal (&stream->fragment_download_cond); @@ -3055,6 +3194,7 @@ gst_adaptive_demux_stream_update_source (GstAdaptiveDemuxStream * stream, return FALSE; } + /* Try to re-use existing source element */ if (stream->src != NULL) { gchar *old_protocol, *new_protocol; gchar *old_uri; @@ -3256,7 +3396,7 @@ gst_ad_stream_src_to_ready_cb (GstPad * pad, GstPadProbeInfo * info, g_cond_signal (&stream->fragment_download_cond); g_mutex_unlock (&stream->fragment_download_lock); - return GST_PAD_PROBE_OK; + return GST_PAD_PROBE_REMOVE; } #ifndef GST_DISABLE_GST_DEBUG @@ -3273,6 +3413,8 @@ uritype (GstAdaptiveDemuxStream * s) /* must be called with manifest_lock taken. * Can temporarily release manifest_lock + * + * Will return when URI is fully downloaded (or aborted/errored) */ static GstFlowReturn gst_adaptive_demux_stream_download_uri (GstAdaptiveDemux * demux, @@ -3297,6 +3439,7 @@ gst_adaptive_demux_stream_download_uri (GstAdaptiveDemux * demux, GST_MANIFEST_UNLOCK (demux); if (gst_element_set_state (stream->src, GST_STATE_READY) != GST_STATE_CHANGE_FAILURE) { + /* If ranges are specified, seek to it */ if (start != 0 || end != -1) { /* HTTP ranges are inclusive, GStreamer segments are exclusive for the * stop position */ @@ -3355,12 +3498,19 @@ gst_adaptive_demux_stream_download_uri (GstAdaptiveDemux * demux, ret = stream->last_ret = GST_FLOW_FLUSHING; return ret; } + /* download_finished is only set: + * * in ::fragment_download_finish() + * * if EOS is received on the _src pad + * */ while (!stream->cancelled && !stream->download_finished) { g_cond_wait (&stream->fragment_download_cond, &stream->fragment_download_lock); } g_mutex_unlock (&stream->fragment_download_lock); + GST_DEBUG_OBJECT (stream->pad, + "Finished Waiting for %s download: %s", uritype (stream), uri); + GST_MANIFEST_LOCK (demux); g_mutex_lock (&stream->fragment_download_lock); if (G_UNLIKELY (stream->cancelled)) { @@ -3385,6 +3535,7 @@ gst_adaptive_demux_stream_download_uri (GstAdaptiveDemux * demux, */ GST_MANIFEST_UNLOCK (demux); } else { + GST_MANIFEST_UNLOCK (demux); if (stream->last_ret == GST_FLOW_OK) stream->last_ret = GST_FLOW_CUSTOM_ERROR; ret = GST_FLOW_CUSTOM_ERROR; @@ -3478,10 +3629,17 @@ gst_adaptive_demux_stream_download_fragment (GstAdaptiveDemuxStream * stream) guint http_status; guint last_status_code; + /* FIXME : */ + /* THERE ARE THREE DIFFERENT VARIABLES FOR THE "BEGINNING" OF A FRAGMENT ! */ stream->starting_fragment = TRUE; stream->last_ret = GST_FLOW_OK; stream->first_fragment_buffer = TRUE; + GST_DEBUG_OBJECT (stream->pad, "Downloading %s%s%s", + stream->fragment.uri ? "FRAGMENT " : "", + stream->fragment.header_uri ? "HEADER " : "", + stream->fragment.index_uri ? "INDEX" : ""); + if (stream->fragment.uri == NULL && stream->fragment.header_uri == NULL && stream->fragment.index_uri == NULL) goto no_url_error; @@ -3504,8 +3662,10 @@ again: stream->last_ret = GST_FLOW_OK; http_status = 200; + /* Download the actual fragment, either in fragments or in one go */ if (klass->need_another_chunk && klass->need_another_chunk (stream) && stream->fragment.chunk_size != 0) { + /* Handle chunk downloading */ gint64 range_start, range_end, chunk_start, chunk_end; guint64 download_total_bytes; gint chunk_size = stream->fragment.chunk_size; @@ -3598,7 +3758,9 @@ again: last_status_code, stream->download_error_count); live = gst_adaptive_demux_is_live (demux); - if (!retried_once && ((last_status_code / 100 == 4 && live) || last_status_code / 100 == 5)) { /* 4xx/5xx */ + if (!retried_once && ((last_status_code / 100 == 4 && live) + || last_status_code / 100 == 5)) { + /* 4xx/5xx */ /* if current position is before available start, switch to next */ if (!gst_adaptive_demux_stream_has_next_fragment (demux, stream)) goto flushing; @@ -3616,6 +3778,7 @@ again: ret = gst_adaptive_demux_eos_handling (stream); GST_DEBUG_OBJECT (stream->pad, "finish_fragment: %s", gst_flow_get_name (ret)); + GST_DEBUG_OBJECT (demux, "Calling update_fragment_info"); ret = gst_adaptive_demux_stream_update_fragment_info (demux, stream); GST_DEBUG_OBJECT (stream->pad, "finish_fragment: %s", gst_flow_get_name (ret)); @@ -3666,9 +3829,7 @@ again: GST_DEBUG_OBJECT (stream->pad, "Converting error of live stream to EOS"); return GST_FLOW_EOS; } - } - - else if (!gst_adaptive_demux_stream_has_next_fragment (demux, stream)) { + } else if (!gst_adaptive_demux_stream_has_next_fragment (demux, stream)) { /* If this is the last fragment, consider failures EOS and not actual * errors. Due to rounding errors in the durations, the last fragment * might not actually exist */ @@ -3766,6 +3927,8 @@ gst_adaptive_demux_stream_download_loop (GstAdaptiveDemuxStream * stream) g_mutex_unlock (&stream->fragment_download_lock); } + /* Restarting download, figure out new position + * FIXME : Move this to a separate function ? */ if (G_UNLIKELY (stream->restart_download)) { GstEvent *seg_event; GstClockTime cur, ts = 0; @@ -3840,6 +4003,8 @@ gst_adaptive_demux_stream_download_loop (GstAdaptiveDemuxStream * stream) live = gst_adaptive_demux_is_live (demux); + /* Get information about the fragment to download */ + GST_DEBUG_OBJECT (demux, "Calling update_fragment_info"); ret = gst_adaptive_demux_stream_update_fragment_info (demux, stream); GST_DEBUG_OBJECT (stream->pad, "Fragment info update result: %d %s", ret, gst_flow_get_name (ret)); @@ -3921,20 +4086,27 @@ gst_adaptive_demux_stream_download_loop (GstAdaptiveDemuxStream * stream) if (!demux_class->requires_periodical_playlist_update (demux)) { ret = gst_adaptive_demux_update_manifest (demux); break; - } else if (gst_adaptive_demux_stream_wait_manifest_update (demux, - stream)) { + /* Wait only if we can ensure current manifest has been expired. + * The meaning "we have next period" *WITH* EOS is that, current + * period has been ended but we can continue to the next period */ + } else if (!gst_adaptive_demux_has_next_period (demux) && + gst_adaptive_demux_stream_wait_manifest_update (demux, stream)) { goto end; } gst_task_stop (stream->download_task); + if (stream->replaced) { + goto end; + } } else { gst_task_stop (stream->download_task); - if (gst_adaptive_demux_combine_flows (demux) == GST_FLOW_EOS) { - if (gst_adaptive_demux_has_next_period (demux)) { - GST_DEBUG_OBJECT (stream->pad, - "Next period available, not sending EOS"); - gst_adaptive_demux_advance_period (demux); - ret = GST_FLOW_OK; - } + } + + if (gst_adaptive_demux_combine_flows (demux) == GST_FLOW_EOS) { + if (gst_adaptive_demux_has_next_period (demux)) { + GST_DEBUG_OBJECT (stream->pad, + "Next period available, not sending EOS"); + gst_adaptive_demux_advance_period (demux); + ret = GST_FLOW_OK; } } break; @@ -4259,6 +4431,9 @@ gst_adaptive_demux_stream_has_next_fragment (GstAdaptiveDemux * demux, } /* must be called with manifest_lock taken */ +/* Called from: + * the ::finish_fragment() handlers when an *actual* fragment is done + * */ GstFlowReturn gst_adaptive_demux_stream_advance_fragment (GstAdaptiveDemux * demux, GstAdaptiveDemuxStream * stream, GstClockTime duration) @@ -4285,10 +4460,17 @@ gst_adaptive_demux_stream_advance_fragment_unlocked (GstAdaptiveDemux * demux, g_return_val_if_fail (klass->stream_advance_fragment != NULL, GST_FLOW_ERROR); + GST_LOG_OBJECT (stream->pad, + "timestamp %" GST_TIME_FORMAT " duration:%" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->fragment.timestamp), GST_TIME_ARGS (duration)); + stream->download_error_count = 0; g_clear_error (&stream->last_error); /* FIXME - url has no indication of byte ranges for subsegments */ + /* FIXME : All those time statistics are biased, since they are calculated + * *AFTER* the queue2, which might be blocking. They should ideally be + * calculated *before* queue2 in the uri_handler_probe */ gst_element_post_message (GST_ELEMENT_CAST (demux), gst_message_new_element (GST_OBJECT_CAST (demux), gst_structure_new (GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME, @@ -4397,6 +4579,7 @@ gst_adaptive_demux_stream_update_fragment_info (GstAdaptiveDemux * demux, GstAdaptiveDemuxStream * stream) { GstAdaptiveDemuxClass *klass = GST_ADAPTIVE_DEMUX_GET_CLASS (demux); + GstFlowReturn ret; g_return_val_if_fail (klass->stream_update_fragment_info != NULL, GST_FLOW_ERROR); @@ -4406,7 +4589,24 @@ gst_adaptive_demux_stream_update_fragment_info (GstAdaptiveDemux * demux, stream->fragment.bitrate = 0; stream->fragment.finished = FALSE; - return klass->stream_update_fragment_info (stream); + GST_LOG_OBJECT (stream->pad, "position %" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->segment.position)); + + ret = klass->stream_update_fragment_info (stream); + + GST_LOG_OBJECT (stream->pad, "ret:%s uri:%s", gst_flow_get_name (ret), + stream->fragment.uri); + if (ret == GST_FLOW_OK) { + GST_LOG_OBJECT (stream->pad, + "timestamp %" GST_TIME_FORMAT " duration:%" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->fragment.timestamp), + GST_TIME_ARGS (stream->fragment.duration)); + GST_LOG_OBJECT (stream->pad, + "range start:%" G_GINT64_FORMAT " end:%" G_GINT64_FORMAT, + stream->fragment.range_start, stream->fragment.range_end); + } + + return ret; } /* must be called with manifest_lock taken */ @@ -4490,6 +4690,18 @@ gst_adaptive_demux_update_manifest (GstAdaptiveDemux * demux) GST_DEBUG_OBJECT (demux, "Duration unknown, can not send the duration message"); } + + /* If a manifest changes it's liveness or periodic updateness, we need + * to start/stop the manifest update task appropriately */ + /* Keep this condition in sync with the one in + * gst_adaptive_demux_start_manifest_update_task() + */ + if (gst_adaptive_demux_is_live (demux) && + klass->requires_periodical_playlist_update (demux)) { + gst_adaptive_demux_start_manifest_update_task (demux); + } else { + gst_adaptive_demux_stop_manifest_update_task (demux); + } } return ret; diff --git a/gst-libs/gst/adaptivedemux/gstadaptivedemux.h b/gst-libs/gst/adaptivedemux/gstadaptivedemux.h index b422812..1c09c26 100644 --- a/gst-libs/gst/adaptivedemux/gstadaptivedemux.h +++ b/gst-libs/gst/adaptivedemux/gstadaptivedemux.h @@ -25,6 +25,7 @@ #include #include #include +#include G_BEGIN_DECLS @@ -38,7 +39,7 @@ G_BEGIN_DECLS (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_ADAPTIVE_DEMUX,GstAdaptiveDemuxClass)) #define GST_IS_ADAPTIVE_DEMUX(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ADAPTIVE_DEMUX)) -#define GST_IS_ADAPTIVE_DEMUX_CLASS(klass) \ +#define GST_IS_ADAPTIVE_DEMUX_CLASS(obj) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ADAPTIVE_DEMUX)) #define GST_ADAPTIVE_DEMUX_CAST(obj) ((GstAdaptiveDemux *)obj) @@ -59,6 +60,8 @@ G_BEGIN_DECLS */ #define GST_ADAPTIVE_DEMUX_SINK_PAD(obj) (((GstAdaptiveDemux *) (obj))->sinkpad) +#define GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS(obj) ((((GstAdaptiveDemux*)(obj))->segment.flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) == GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) + #define GST_ADAPTIVE_DEMUX_STREAM_PAD(obj) (((GstAdaptiveDemuxStream *) (obj))->pad) #define GST_ADAPTIVE_DEMUX_STREAM_NEED_HEADER(obj) (((GstAdaptiveDemuxStream *) (obj))->need_header) @@ -173,7 +176,8 @@ struct _GstAdaptiveDemuxStream GMutex fragment_download_lock; GCond fragment_download_cond; gboolean download_finished; /* protected by fragment_download_lock */ - gboolean cancelled; /* protected by fragment_download_lock */ + gboolean cancelled; /* protected by fragment_download_lock */ + gboolean replaced; /* replaced in a bitrate switch (used with cancelled) */ gboolean src_at_ready; /* protected by fragment_download_lock */ gboolean starting_fragment; gboolean first_fragment_buffer; @@ -195,6 +199,9 @@ struct _GstAdaptiveDemuxStream guint moving_index; guint64 *fragment_bitrates; + /* QoS data */ + GstClockTime qos_earliest_time; + GstAdaptiveDemuxStreamFragment fragment; guint download_error_count; @@ -276,7 +283,7 @@ struct _GstAdaptiveDemuxClass * Parse the manifest and add the created streams using * gst_adaptive_demux_stream_new() * - * Returns: #TRUE if successful + * Returns: %TRUE if successful */ gboolean (*process_manifest) (GstAdaptiveDemux * demux, GstBuffer * manifest); @@ -337,7 +344,7 @@ struct _GstAdaptiveDemuxClass * The demuxer should seek on all its streams to the specified position * in the seek event * - * Returns: #TRUE if successful + * Returns: %TRUE if successful */ gboolean (*seek) (GstAdaptiveDemux * demux, GstEvent * seek); @@ -350,7 +357,7 @@ struct _GstAdaptiveDemuxClass * this function is called to verify if there is a new period to be played * in sequence. * - * Returns: #TRUE if there is another period + * Returns: %TRUE if there is another period */ gboolean (*has_next_period) (GstAdaptiveDemux * demux); /** @@ -371,7 +378,7 @@ struct _GstAdaptiveDemuxClass * need_another_chunk: * @stream: #GstAdaptiveDemuxStream * - * If chunked downloading is used (chunk_size != 0) this is called once as + * If chunked downloading is used (chunk_size != 0) this is called once a * chunk is finished to decide whether more has to be downloaded or not. * May update chunk_size to a different value */ @@ -399,7 +406,7 @@ struct _GstAdaptiveDemuxClass * needs a caps change it should set the new caps using * gst_adaptive_demux_stream_set_caps(). * - * Returns: #TRUE if the stream changed bitrate, #FALSE otherwise + * Returns: %TRUE if the stream changed bitrate, %FALSE otherwise */ gboolean (*stream_select_bitrate) (GstAdaptiveDemuxStream * stream, guint64 bitrate); /** @@ -410,7 +417,7 @@ struct _GstAdaptiveDemuxClass * to download the fragment. This is useful to avoid downloading a fragment that * isn't available yet. * - * Returns: The waiting time in microsseconds + * Returns: The waiting time in microseconds */ gint64 (*stream_get_fragment_waiting_time) (GstAdaptiveDemuxStream * stream); @@ -423,7 +430,7 @@ struct _GstAdaptiveDemuxClass * of a new fragment. Can be used to reset/init internal state that is * needed before each fragment, like decryption engines. * - * Returns: #TRUE if successful. + * Returns: %TRUE if successful. */ gboolean (*start_fragment) (GstAdaptiveDemux * demux, GstAdaptiveDemuxStream * stream); /** @@ -501,30 +508,49 @@ struct _GstAdaptiveDemuxClass gboolean (*requires_periodical_playlist_update) (GstAdaptiveDemux * demux); }; +GST_ADAPTIVE_DEMUX_API GType gst_adaptive_demux_get_type (void); +GST_ADAPTIVE_DEMUX_API void gst_adaptive_demux_set_stream_struct_size (GstAdaptiveDemux * demux, gsize struct_size); +GST_ADAPTIVE_DEMUX_API GstAdaptiveDemuxStream *gst_adaptive_demux_stream_new (GstAdaptiveDemux * demux, GstPad * pad); + +GST_ADAPTIVE_DEMUX_API GstAdaptiveDemuxStream *gst_adaptive_demux_find_stream_for_pad (GstAdaptiveDemux * demux, GstPad * pad); + +GST_ADAPTIVE_DEMUX_API void gst_adaptive_demux_stream_set_caps (GstAdaptiveDemuxStream * stream, GstCaps * caps); + +GST_ADAPTIVE_DEMUX_API void gst_adaptive_demux_stream_set_tags (GstAdaptiveDemuxStream * stream, GstTagList * tags); + +GST_ADAPTIVE_DEMUX_API void gst_adaptive_demux_stream_fragment_clear (GstAdaptiveDemuxStreamFragment * f); +GST_ADAPTIVE_DEMUX_API GstFlowReturn gst_adaptive_demux_stream_push_buffer (GstAdaptiveDemuxStream * stream, GstBuffer * buffer); + +GST_ADAPTIVE_DEMUX_API GstFlowReturn gst_adaptive_demux_stream_advance_fragment (GstAdaptiveDemux * demux, GstAdaptiveDemuxStream * stream, GstClockTime duration); + +GST_ADAPTIVE_DEMUX_API void gst_adaptive_demux_stream_queue_event (GstAdaptiveDemuxStream * stream, GstEvent * event); +GST_ADAPTIVE_DEMUX_API GstClockTime gst_adaptive_demux_get_monotonic_time (GstAdaptiveDemux * demux); + +GST_ADAPTIVE_DEMUX_API GDateTime *gst_adaptive_demux_get_client_now_utc (GstAdaptiveDemux * demux); #ifdef TIZEN_FEATURE_AVOID_PAD_SWITCHING diff --git a/gst-libs/gst/isoff/Makefile.am b/gst-libs/gst/isoff/Makefile.am new file mode 100644 index 0000000..b99acae --- /dev/null +++ b/gst-libs/gst/isoff/Makefile.am @@ -0,0 +1,24 @@ +lib_LTLIBRARIES = libgstisoff-@GST_API_VERSION@.la + +libgstisoff_@GST_API_VERSION@_la_SOURCES = \ + gstisoff.c + +libgstisoff_@GST_API_VERSION@includedir = \ + $(includedir)/gstreamer-@GST_API_VERSION@/gst/isoff + +libgstisoff_@GST_API_VERSION@include_HEADERS = \ + gstisoff.h + +libgstisoff_@GST_API_VERSION@_la_CFLAGS = \ + $(GST_PLUGINS_BAD_CFLAGS) \ + -DGST_USE_UNSTABLE_API \ + $(GST_CFLAGS) + +libgstisoff_@GST_API_VERSION@_la_LIBADD = \ + $(GST_BASE_LIBS) \ + $(GST_LIBS) + +libgstisoff_@GST_API_VERSION@_la_LDFLAGS = \ + $(GST_LIB_LDFLAGS) \ + $(GST_ALL_LDFLAGS) \ + $(GST_LT_LDFLAGS) diff --git a/ext/dash/gstisoff.c b/gst-libs/gst/isoff/gstisoff.c similarity index 57% rename from ext/dash/gstisoff.c rename to gst-libs/gst/isoff/gstisoff.c index f305b7c..e6f0cfb 100644 --- a/ext/dash/gstisoff.c +++ b/gst-libs/gst/isoff/gstisoff.c @@ -26,11 +26,30 @@ #include #include -#include "gstdash_debug.h" -#define GST_CAT_DEFAULT gst_dash_demux_debug +GST_DEBUG_CATEGORY_STATIC (gst_isoff_debug); +#define GST_CAT_DEFAULT gst_isoff_debug -/* gst_isoff_parse_box: +static gboolean initialized = FALSE; + +#define INITIALIZE_DEBUG_CATEGORY \ + if (!initialized) { \ + GST_DEBUG_CATEGORY_INIT (gst_isoff_debug, "isoff", 0, \ + "ISO File Format parsing library"); \ + initialized = TRUE; \ + } + +static const guint8 tfrf_uuid[] = { + 0xd4, 0x80, 0x7e, 0xf2, 0xca, 0x39, 0x46, 0x95, + 0x8e, 0x54, 0x26, 0xcb, 0x9e, 0x46, 0xa7, 0x9f +}; + +static const guint8 tfxd_uuid[] = { + 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6, + 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2 +}; + +/* gst_isoff_parse_box_header: * @reader: * @type: type that was found at the current position * @extended_type: (allow-none): extended type if type=='uuid' @@ -49,6 +68,7 @@ gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type, guint header_start_offset; guint32 size_field; + INITIALIZE_DEBUG_CATEGORY; header_start_offset = gst_byte_reader_get_pos (reader); if (gst_byte_reader_get_remaining (reader) < 8) @@ -92,10 +112,27 @@ gst_isoff_trun_box_clear (GstTrunBox * trun) } static void +gst_isoff_tfrf_box_free (GstTfrfBox * tfrf) +{ + if (tfrf->entries) + g_array_free (tfrf->entries, TRUE); + + g_free (tfrf); +} + +static void gst_isoff_traf_box_clear (GstTrafBox * traf) { if (traf->trun) g_array_free (traf->trun, TRUE); + + if (traf->tfrf) + gst_isoff_tfrf_box_free (traf->tfrf); + + g_free (traf->tfxd); + traf->trun = NULL; + traf->tfrf = NULL; + traf->tfxd = NULL; } static gboolean @@ -222,6 +259,146 @@ error: } static gboolean +gst_isoff_tfdt_box_parse (GstTfdtBox * tfdt, GstByteReader * reader) +{ + gint8 version; + + memset (tfdt, 0, sizeof (*tfdt)); + + if (gst_byte_reader_get_remaining (reader) < 4) + return FALSE; + + version = gst_byte_reader_get_uint8_unchecked (reader); + + if (!gst_byte_reader_skip (reader, 3)) + return FALSE; + + if (version == 1) { + if (!gst_byte_reader_get_uint64_be (reader, &tfdt->decode_time)) + return FALSE; + } else { + guint32 dec_time = 0; + if (!gst_byte_reader_get_uint32_be (reader, &dec_time)) + return FALSE; + tfdt->decode_time = dec_time; + } + + return TRUE; +} + +static gboolean +gst_isoff_tfxd_box_parse (GstTfxdBox * tfxd, GstByteReader * reader) +{ + guint8 version; + guint32 flags = 0; + guint64 absolute_time = 0; + guint64 absolute_duration = 0; + + memset (tfxd, 0, sizeof (*tfxd)); + + if (gst_byte_reader_get_remaining (reader) < 4) + return FALSE; + + if (!gst_byte_reader_get_uint8 (reader, &version)) { + GST_ERROR ("Error getting box's version field"); + return FALSE; + } + + if (!gst_byte_reader_get_uint24_be (reader, &flags)) { + GST_ERROR ("Error getting box's flags field"); + return FALSE; + } + + tfxd->version = version; + tfxd->flags = flags; + + if (gst_byte_reader_get_remaining (reader) < ((version & 0x01) ? 16 : 8)) + return FALSE; + + if (version & 0x01) { + gst_byte_reader_get_uint64_be (reader, &absolute_time); + gst_byte_reader_get_uint64_be (reader, &absolute_duration); + } else { + guint32 time = 0; + guint32 duration = 0; + gst_byte_reader_get_uint32_be (reader, &time); + gst_byte_reader_get_uint32_be (reader, &duration); + absolute_time = time; + absolute_duration = duration; + } + + tfxd->time = absolute_time; + tfxd->duration = absolute_duration; + + return TRUE; +} + +static gboolean +gst_isoff_tfrf_box_parse (GstTfrfBox * tfrf, GstByteReader * reader) +{ + guint8 version; + guint32 flags = 0; + guint8 fragment_count = 0; + guint8 index = 0; + + memset (tfrf, 0, sizeof (*tfrf)); + + if (gst_byte_reader_get_remaining (reader) < 4) + return FALSE; + + if (!gst_byte_reader_get_uint8 (reader, &version)) { + GST_ERROR ("Error getting box's version field"); + return FALSE; + } + + if (!gst_byte_reader_get_uint24_be (reader, &flags)) { + GST_ERROR ("Error getting box's flags field"); + return FALSE; + } + + tfrf->version = version; + tfrf->flags = flags; + + if (!gst_byte_reader_get_uint8 (reader, &fragment_count)) + return FALSE; + + tfrf->entries_count = fragment_count; + tfrf->entries = + g_array_sized_new (FALSE, FALSE, sizeof (GstTfrfBoxEntry), + tfrf->entries_count); + + for (index = 0; index < fragment_count; index++) { + GstTfrfBoxEntry entry = { 0, }; + guint64 absolute_time = 0; + guint64 absolute_duration = 0; + if (gst_byte_reader_get_remaining (reader) < ((version & 0x01) ? 16 : 8)) + return FALSE; + + if (version & 0x01) { + if (!gst_byte_reader_get_uint64_be (reader, &absolute_time) || + !gst_byte_reader_get_uint64_be (reader, &absolute_duration)) { + return FALSE; + } + } else { + guint32 time = 0; + guint32 duration = 0; + if (!gst_byte_reader_get_uint32_be (reader, &time) || + !gst_byte_reader_get_uint32_be (reader, &duration)) { + return FALSE; + } + absolute_time = time; + absolute_duration = duration; + } + entry.time = absolute_time; + entry.duration = absolute_duration; + + g_array_append_val (tfrf->entries, entry); + } + + return TRUE; +} + +static gboolean gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader) { gboolean had_tfhd = FALSE; @@ -231,21 +408,23 @@ gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader) g_array_set_clear_func (traf->trun, (GDestroyNotify) gst_isoff_trun_box_clear); + traf->tfdt.decode_time = GST_CLOCK_TIME_NONE; + while (gst_byte_reader_get_remaining (reader) > 0) { guint32 fourcc; guint header_size; guint64 size; + GstByteReader sub_reader; + guint8 extended_type[16] = { 0, }; - if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size, - &size)) + if (!gst_isoff_parse_box_header (reader, &fourcc, extended_type, + &header_size, &size)) goto error; if (gst_byte_reader_get_remaining (reader) < size - header_size) goto error; switch (fourcc) { case GST_ISOFF_FOURCC_TFHD:{ - GstByteReader sub_reader; - gst_byte_reader_get_sub_reader (reader, &sub_reader, size - header_size); if (!gst_isoff_tfhd_box_parse (&traf->tfhd, &sub_reader)) @@ -253,8 +432,14 @@ gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader) had_tfhd = TRUE; break; } + case GST_ISOFF_FOURCC_TFDT:{ + gst_byte_reader_get_sub_reader (reader, &sub_reader, + size - header_size); + if (!gst_isoff_tfdt_box_parse (&traf->tfdt, &sub_reader)) + goto error; + break; + } case GST_ISOFF_FOURCC_TRUN:{ - GstByteReader sub_reader; GstTrunBox trun; gst_byte_reader_get_sub_reader (reader, &sub_reader, @@ -265,6 +450,27 @@ gst_isoff_traf_box_parse (GstTrafBox * traf, GstByteReader * reader) g_array_append_val (traf->trun, trun); break; } + case GST_ISOFF_FOURCC_UUID:{ + /* smooth-streaming specific */ + if (memcmp (extended_type, tfrf_uuid, 16) == 0) { + traf->tfrf = g_new0 (GstTfrfBox, 1); + gst_byte_reader_get_sub_reader (reader, &sub_reader, + size - header_size); + + if (!gst_isoff_tfrf_box_parse (traf->tfrf, &sub_reader)) + goto error; + } else if (memcmp (extended_type, tfxd_uuid, 16) == 0) { + traf->tfxd = g_new0 (GstTfxdBox, 1); + gst_byte_reader_get_sub_reader (reader, &sub_reader, + size - header_size); + + if (!gst_isoff_tfxd_box_parse (traf->tfxd, &sub_reader)) + goto error; + } else { + gst_byte_reader_skip (reader, size - header_size); + } + break; + } default: gst_byte_reader_skip (reader, size - header_size); break; @@ -287,7 +493,10 @@ gst_isoff_moof_box_parse (GstByteReader * reader) { GstMoofBox *moof; gboolean had_mfhd = FALSE; + GstByteReader sub_reader; + + INITIALIZE_DEBUG_CATEGORY; moof = g_new0 (GstMoofBox, 1); moof->traf = g_array_new (FALSE, FALSE, sizeof (GstTrafBox)); g_array_set_clear_func (moof->traf, @@ -306,8 +515,6 @@ gst_isoff_moof_box_parse (GstByteReader * reader) switch (fourcc) { case GST_ISOFF_FOURCC_MFHD:{ - GstByteReader sub_reader; - gst_byte_reader_get_sub_reader (reader, &sub_reader, size - header_size); if (!gst_isoff_mfhd_box_parse (&moof->mfhd, &sub_reader)) @@ -316,7 +523,6 @@ gst_isoff_moof_box_parse (GstByteReader * reader) break; } case GST_ISOFF_FOURCC_TRAF:{ - GstByteReader sub_reader; GstTrafBox traf; gst_byte_reader_get_sub_reader (reader, &sub_reader, @@ -350,6 +556,239 @@ gst_isoff_moof_box_free (GstMoofBox * moof) g_free (moof); } +static gboolean +gst_isoff_mdhd_box_parse (GstMdhdBox * mdhd, GstByteReader * reader) +{ + guint8 version; + + memset (mdhd, 0, sizeof (*mdhd)); + + if (gst_byte_reader_get_remaining (reader) < 4) + return FALSE; + + version = gst_byte_reader_get_uint8_unchecked (reader); + + if (!gst_byte_reader_skip (reader, 3)) + return FALSE; + + /* skip {creation, modification}_time, we don't have interest */ + if (version == 1) { + if (!gst_byte_reader_skip (reader, 16)) + return FALSE; + } else { + if (!gst_byte_reader_skip (reader, 8)) + return FALSE; + } + + if (!gst_byte_reader_get_uint32_be (reader, &mdhd->timescale)) + return FALSE; + + return TRUE; +} + +static gboolean +gst_isoff_hdlr_box_parse (GstHdlrBox * hdlr, GstByteReader * reader) +{ + memset (hdlr, 0, sizeof (*hdlr)); + + if (gst_byte_reader_get_remaining (reader) < 4) + return FALSE; + + /* version & flag */ + if (!gst_byte_reader_skip (reader, 4)) + return FALSE; + + /* pre_defined = 0 */ + if (!gst_byte_reader_skip (reader, 4)) + return FALSE; + + if (!gst_byte_reader_get_uint32_le (reader, &hdlr->handler_type)) + return FALSE; + + return TRUE; +} + +static gboolean +gst_isoff_mdia_box_parse (GstMdiaBox * mdia, GstByteReader * reader) +{ + gboolean had_mdhd = FALSE, had_hdlr = FALSE; + while (gst_byte_reader_get_remaining (reader) > 0) { + guint32 fourcc; + guint header_size; + guint64 size; + GstByteReader sub_reader; + + if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size, + &size)) + return FALSE; + if (gst_byte_reader_get_remaining (reader) < size - header_size) + return FALSE; + + switch (fourcc) { + case GST_ISOFF_FOURCC_MDHD:{ + gst_byte_reader_get_sub_reader (reader, &sub_reader, + size - header_size); + if (!gst_isoff_mdhd_box_parse (&mdia->mdhd, &sub_reader)) + return FALSE; + + had_mdhd = TRUE; + break; + } + case GST_ISOFF_FOURCC_HDLR:{ + gst_byte_reader_get_sub_reader (reader, &sub_reader, + size - header_size); + if (!gst_isoff_hdlr_box_parse (&mdia->hdlr, &sub_reader)) + return FALSE; + + had_hdlr = TRUE; + break; + } + default: + gst_byte_reader_skip (reader, size - header_size); + break; + } + } + + if (!had_mdhd || !had_hdlr) + return FALSE; + + return TRUE; +} + +static gboolean +gst_isoff_tkhd_box_parse (GstTkhdBox * tkhd, GstByteReader * reader) +{ + guint8 version; + + memset (tkhd, 0, sizeof (*tkhd)); + + if (gst_byte_reader_get_remaining (reader) < 4) + return FALSE; + + if (!gst_byte_reader_get_uint8 (reader, &version)) + return FALSE; + + if (!gst_byte_reader_skip (reader, 3)) + return FALSE; + + /* skip {creation, modification}_time, we don't have interest */ + if (version == 1) { + if (!gst_byte_reader_skip (reader, 16)) + return FALSE; + } else { + if (!gst_byte_reader_skip (reader, 8)) + return FALSE; + } + + if (!gst_byte_reader_get_uint32_be (reader, &tkhd->track_id)) + return FALSE; + + return TRUE; +} + +static gboolean +gst_isoff_trak_box_parse (GstTrakBox * trak, GstByteReader * reader) +{ + gboolean had_mdia = FALSE, had_tkhd = FALSE; + while (gst_byte_reader_get_remaining (reader) > 0) { + guint32 fourcc; + guint header_size; + guint64 size; + GstByteReader sub_reader; + + if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size, + &size)) + return FALSE; + if (gst_byte_reader_get_remaining (reader) < size - header_size) + return FALSE; + + switch (fourcc) { + case GST_ISOFF_FOURCC_MDIA:{ + gst_byte_reader_get_sub_reader (reader, &sub_reader, + size - header_size); + if (!gst_isoff_mdia_box_parse (&trak->mdia, &sub_reader)) + return FALSE; + + had_mdia = TRUE; + break; + } + case GST_ISOFF_FOURCC_TKHD:{ + gst_byte_reader_get_sub_reader (reader, &sub_reader, + size - header_size); + if (!gst_isoff_tkhd_box_parse (&trak->tkhd, &sub_reader)) + return FALSE; + + had_tkhd = TRUE; + break; + } + default: + gst_byte_reader_skip (reader, size - header_size); + break; + } + } + + if (!had_tkhd || !had_mdia) + return FALSE; + + return TRUE; +} + +GstMoovBox * +gst_isoff_moov_box_parse (GstByteReader * reader) +{ + GstMoovBox *moov; + gboolean had_trak = FALSE; + moov = g_new0 (GstMoovBox, 1); + moov->trak = g_array_new (FALSE, FALSE, sizeof (GstTrakBox)); + + while (gst_byte_reader_get_remaining (reader) > 0) { + guint32 fourcc; + guint header_size; + guint64 size; + + if (!gst_isoff_parse_box_header (reader, &fourcc, NULL, &header_size, + &size)) + goto error; + if (gst_byte_reader_get_remaining (reader) < size - header_size) + goto error; + + switch (fourcc) { + case GST_ISOFF_FOURCC_TRAK:{ + GstByteReader sub_reader; + GstTrakBox trak; + + gst_byte_reader_get_sub_reader (reader, &sub_reader, + size - header_size); + if (!gst_isoff_trak_box_parse (&trak, &sub_reader)) + goto error; + + had_trak = TRUE; + g_array_append_val (moov->trak, trak); + break; + } + default: + gst_byte_reader_skip (reader, size - header_size); + break; + } + } + + if (!had_trak) + goto error; + + return moov; + +error: + gst_isoff_moov_box_free (moov); + return NULL; +} + +void +gst_isoff_moov_box_free (GstMoovBox * moov) +{ + g_array_free (moov->trak, TRUE); + g_free (moov); +} + void gst_isoff_sidx_parser_init (GstSidxParser * parser) { @@ -390,6 +829,7 @@ gst_isoff_sidx_parser_parse (GstSidxParser * parser, GstIsoffParserResult res = GST_ISOFF_PARSER_OK; gsize remaining; + INITIALIZE_DEBUG_CATEGORY; switch (parser->status) { case GST_ISOFF_SIDX_PARSER_INIT: /* Try again once we have enough data for the FullBox header */ @@ -492,6 +932,7 @@ gst_isoff_sidx_parser_add_buffer (GstSidxParser * parser, GstBuffer * buffer, GstMapInfo info; guint32 fourcc; + INITIALIZE_DEBUG_CATEGORY; if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) { *consumed = 0; return GST_ISOFF_PARSER_ERROR; diff --git a/ext/dash/gstisoff.h b/gst-libs/gst/isoff/gstisoff.h similarity index 75% rename from ext/dash/gstisoff.h rename to gst-libs/gst/isoff/gstisoff.h index cf9ee1c..437859b 100644 --- a/ext/dash/gstisoff.h +++ b/gst-libs/gst/isoff/gstisoff.h @@ -30,6 +30,10 @@ G_BEGIN_DECLS +#ifndef GST_ISOFF_API +#define GST_ISOFF_API GST_EXPORT +#endif + typedef enum { GST_ISOFF_PARSER_OK, GST_ISOFF_PARSER_DONE, @@ -37,6 +41,7 @@ typedef enum { GST_ISOFF_PARSER_ERROR } GstIsoffParserResult; +GST_ISOFF_API gboolean gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type, guint8 extended_type[16], guint * header_size, guint64 * size); #define GST_ISOFF_FOURCC_UUID GST_MAKE_FOURCC('u','u','i','d') @@ -45,9 +50,20 @@ gboolean gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type, gui #define GST_ISOFF_FOURCC_TFHD GST_MAKE_FOURCC('t','f','h','d') #define GST_ISOFF_FOURCC_TRUN GST_MAKE_FOURCC('t','r','u','n') #define GST_ISOFF_FOURCC_TRAF GST_MAKE_FOURCC('t','r','a','f') +#define GST_ISOFF_FOURCC_TFDT GST_MAKE_FOURCC('t','f','d','t') #define GST_ISOFF_FOURCC_MDAT GST_MAKE_FOURCC('m','d','a','t') +#define GST_ISOFF_FOURCC_MOOV GST_MAKE_FOURCC('m','o','o','v') +#define GST_ISOFF_FOURCC_TRAK GST_MAKE_FOURCC('t','r','a','k') +#define GST_ISOFF_FOURCC_TKHD GST_MAKE_FOURCC('t','k','h','d') +#define GST_ISOFF_FOURCC_MDIA GST_MAKE_FOURCC('m','d','i','a') +#define GST_ISOFF_FOURCC_MDHD GST_MAKE_FOURCC('m','d','h','d') +#define GST_ISOFF_FOURCC_HDLR GST_MAKE_FOURCC('h','d','l','r') #define GST_ISOFF_FOURCC_SIDX GST_MAKE_FOURCC('s','i','d','x') +/* handler type */ +#define GST_ISOFF_FOURCC_SOUN GST_MAKE_FOURCC('s','o','u','n') +#define GST_ISOFF_FOURCC_VIDE GST_MAKE_FOURCC('v','i','d','e') + #define GST_ISOFF_SAMPLE_FLAGS_IS_LEADING(flags) (((flags) >> 26) & 0x03) #define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_DEPENDS_ON(flags) (((flags) >> 24) & 0x03) #define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_IS_DEPENDED_ON(flags) (((flags) >> 22) & 0x03) @@ -56,6 +72,32 @@ gboolean gst_isoff_parse_box_header (GstByteReader * reader, guint32 * type, gui #define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_IS_NON_SYNC_SAMPLE(flags) (((flags) >> 16) & 0x01) #define GST_ISOFF_SAMPLE_FLAGS_SAMPLE_DEGRADATION_PRIORITY(flags) (((flags) >> 0) & 0x0f) +/* Smooth-Streaming specific boxes */ +typedef struct _GstTfxdBox +{ + guint8 version; + guint32 flags; + + guint64 time; + guint64 duration; +} GstTfxdBox; + +typedef struct _GstTfrfBoxEntry +{ + guint64 time; + guint64 duration; +} GstTfrfBoxEntry; + +typedef struct _GstTfrfBox +{ + guint8 version; + guint32 flags; + + gint entries_count; + GArray *entries; +} GstTfrfBox; + +/* Common boxes */ typedef struct _GstMfhdBox { guint32 sequence_number; @@ -122,10 +164,20 @@ typedef struct _GstTrunSample } sample_composition_time_offset; } GstTrunSample; +typedef struct _GstTdftBox +{ + guint64 decode_time; +} GstTfdtBox; + typedef struct _GstTrafBox { GstTfhdBox tfhd; + GstTfdtBox tfdt; GArray *trun; + + /* smooth-streaming specific */ + GstTfrfBox *tfrf; + GstTfxdBox *tfxd; } GstTrafBox; typedef struct _GstMoofBox @@ -134,9 +186,50 @@ typedef struct _GstMoofBox GArray *traf; } GstMoofBox; +GST_ISOFF_API GstMoofBox * gst_isoff_moof_box_parse (GstByteReader *reader); + +GST_ISOFF_API void gst_isoff_moof_box_free (GstMoofBox *moof); +typedef struct _GstTkhdBox +{ + guint32 track_id; +} GstTkhdBox; + +typedef struct _GstMdhdBox +{ + guint32 timescale; +} GstMdhdBox; + +typedef struct _GstHdlrBox +{ + guint32 handler_type; +} GstHdlrBox; + +typedef struct _GstMdiaBox +{ + GstMdhdBox mdhd; + GstHdlrBox hdlr; +} GstMdiaBox; + +typedef struct _GstTrakBox +{ + GstTkhdBox tkhd; + GstMdiaBox mdia; +} GstTrakBox; + +typedef struct _GstMoovBox +{ + GArray *trak; +} GstMoovBox; + +GST_ISOFF_API +GstMoovBox * gst_isoff_moov_box_parse (GstByteReader *reader); + +GST_ISOFF_API +void gst_isoff_moov_box_free (GstMoovBox *moov); + typedef struct _GstSidxBoxEntry { gboolean ref_type; @@ -185,12 +278,18 @@ typedef struct _GstSidxParser GstSidxBox sidx; } GstSidxParser; +GST_ISOFF_API void gst_isoff_sidx_parser_init (GstSidxParser * parser); + +GST_ISOFF_API void gst_isoff_sidx_parser_clear (GstSidxParser * parser); + +GST_ISOFF_API GstIsoffParserResult gst_isoff_sidx_parser_parse (GstSidxParser * parser, GstByteReader * reader, guint * consumed); + +GST_ISOFF_API GstIsoffParserResult gst_isoff_sidx_parser_add_buffer (GstSidxParser * parser, GstBuffer * buf, guint * consumed); G_END_DECLS #endif /* __GST_ISOFF_H__ */ - diff --git a/gst-libs/gst/isoff/meson.build b/gst-libs/gst/isoff/meson.build new file mode 100644 index 0000000..8666ea1 --- /dev/null +++ b/gst-libs/gst/isoff/meson.build @@ -0,0 +1,21 @@ +isoff_sources = [ + 'gstisoff.c', +] +isoff_headers = [ + 'gstisoff.h', +] +install_headers(isoff_headers, subdir : 'gstreamer-1.0/gst/isoff') + +gstisoff = library('gstisoff-' + api_version, + isoff_sources, + c_args : gst_plugins_bad_args + [ '-DGST_USE_UNSTABLE_API' ], + include_directories : [configinc, libsinc], + version : libversion, + soversion : soversion, + install : true, + dependencies : [gstbase_dep], +) + +gstisoff_dep = declare_dependency(link_with : gstisoff, + include_directories : [libsinc], + dependencies : [gstbase_dep]) diff --git a/gst-libs/gst/uridownloader/Makefile.am b/gst-libs/gst/uridownloader/Makefile.am index 91b5187..d12257a 100644 --- a/gst-libs/gst/uridownloader/Makefile.am +++ b/gst-libs/gst/uridownloader/Makefile.am @@ -7,7 +7,7 @@ libgsturidownloader_@GST_API_VERSION@includedir = \ $(includedir)/gstreamer-@GST_API_VERSION@/gst/uridownloader libgsturidownloader_@GST_API_VERSION@include_HEADERS = \ - gstfragment.h gsturidownloader.h gsturidownloader_debug.h + gstfragment.h gsturidownloader.h gsturidownloader_debug.h uridownloader-prelude.h libgsturidownloader_@GST_API_VERSION@_la_CFLAGS = \ $(GST_PLUGINS_BAD_CFLAGS) \ diff --git a/gst-libs/gst/uridownloader/gstfragment.h b/gst-libs/gst/uridownloader/gstfragment.h index a8e0a54..517d155 100644 --- a/gst-libs/gst/uridownloader/gstfragment.h +++ b/gst-libs/gst/uridownloader/gstfragment.h @@ -24,6 +24,7 @@ #include #include +#include G_BEGIN_DECLS @@ -65,12 +66,22 @@ struct _GstFragmentClass GObjectClass parent_class; }; +GST_URI_DOWNLOADER_API GType gst_fragment_get_type (void); +GST_URI_DOWNLOADER_API GstBuffer * gst_fragment_get_buffer (GstFragment *fragment); + +GST_URI_DOWNLOADER_API void gst_fragment_set_caps (GstFragment * fragment, GstCaps * caps); + +GST_URI_DOWNLOADER_API GstCaps * gst_fragment_get_caps (GstFragment * fragment); + +GST_URI_DOWNLOADER_API gboolean gst_fragment_add_buffer (GstFragment *fragment, GstBuffer *buffer); + +GST_URI_DOWNLOADER_API GstFragment * gst_fragment_new (void); G_END_DECLS diff --git a/gst-libs/gst/uridownloader/gsturidownloader.c b/gst-libs/gst/uridownloader/gsturidownloader.c index cb1e1b8..be53f9a 100644 --- a/gst-libs/gst/uridownloader/gsturidownloader.c +++ b/gst-libs/gst/uridownloader/gsturidownloader.c @@ -154,7 +154,12 @@ gst_uri_downloader_finalize (GObject * object) GstUriDownloader * gst_uri_downloader_new (void) { - return g_object_new (GST_TYPE_URI_DOWNLOADER, NULL); + GstUriDownloader *downloader; + + downloader = g_object_new (GST_TYPE_URI_DOWNLOADER, NULL); + gst_object_ref_sink (downloader); + + return downloader; } /** diff --git a/gst-libs/gst/uridownloader/gsturidownloader.h b/gst-libs/gst/uridownloader/gsturidownloader.h index c5ef0b2..97582e4 100644 --- a/gst-libs/gst/uridownloader/gsturidownloader.h +++ b/gst-libs/gst/uridownloader/gsturidownloader.h @@ -58,20 +58,34 @@ struct _GstUriDownloaderClass gpointer _gst_reserved[GST_PADDING]; }; +GST_URI_DOWNLOADER_API GType gst_uri_downloader_get_type (void); +GST_URI_DOWNLOADER_API GstUriDownloader * gst_uri_downloader_new (void); + +GST_URI_DOWNLOADER_API void gst_uri_downloader_set_parent (GstUriDownloader * downloader, GstElement * parent); + #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION +GST_URI_DOWNLOADER_API GstFragment * gst_uri_downloader_fetch_uri (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gchar* user_agent, gchar** cookies, gint max_retry , gint timeout , gboolean compress, gboolean refresh, gboolean allow_cache, GError ** err); + +GST_URI_DOWNLOADER_API GstFragment * gst_uri_downloader_fetch_uri_with_range (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gchar* user_agent, gchar** cookies, gint max_retry , gint timeout , gboolean compress, gboolean refresh, gboolean allow_cache, gint64 range_start, gint64 range_end, GError ** err); #else +GST_URI_DOWNLOADER_API GstFragment * gst_uri_downloader_fetch_uri (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gboolean compress, gboolean refresh, gboolean allow_cache, GError ** err); + +GST_URI_DOWNLOADER_API GstFragment * gst_uri_downloader_fetch_uri_with_range (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gboolean compress, gboolean refresh, gboolean allow_cache, gint64 range_start, gint64 range_end, GError ** err); #endif + +GST_URI_DOWNLOADER_API void gst_uri_downloader_reset (GstUriDownloader *downloader); + +GST_URI_DOWNLOADER_API void gst_uri_downloader_cancel (GstUriDownloader *downloader); -void gst_uri_downloader_free (GstUriDownloader *downloader); G_END_DECLS #endif /* __GSTURIDOWNLOADER_H__ */ diff --git a/gst-libs/gst/uridownloader/uridownloader-prelude.h b/gst-libs/gst/uridownloader/uridownloader-prelude.h new file mode 100644 index 0000000..f5ad584 --- /dev/null +++ b/gst-libs/gst/uridownloader/uridownloader-prelude.h @@ -0,0 +1,31 @@ +/* GStreamer UriDownloader Library + * Copyright (C) 2018 GStreamer developers + * + * uridownloader-prelude.h: prelude include header for gst-uridownloader library + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_URI_DOWNLOADER_PRELUDE_H__ +#define __GST_URI_DOWNLOADER_PRELUDE_H__ + +#include + +#ifndef GST_URI_DOWNLOADER_API +#define GST_URI_DOWNLOADER_API GST_EXPORT +#endif + +#endif /* __GST_URI_DOWNLOADER_PRELUDE_H__ */ diff --git a/packaging/gst-plugins-bad.spec b/packaging/gst-plugins-bad.spec index 4b52d07..3229bc2 100644 --- a/packaging/gst-plugins-bad.spec +++ b/packaging/gst-plugins-bad.spec @@ -208,6 +208,7 @@ rm -rf $RPM_BUILD_ROOT %{_libdir}/libgsturidownloader-%{gst_branch}.so.0* %{_libdir}/libgstadaptivedemux-%{gst_branch}.so.0* %{_libdir}/libgstgl-%{gst_branch}.so.0* +%{_libdir}/libgstisoff-%{gst_branch}.so.0* %endif %{_libdir}/gstreamer-%{gst_branch}/libgstdebugutilsbad.so %{_libdir}/gstreamer-%{gst_branch}/libgstmpegtsdemux.so -- 2.7.4