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
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
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) \
* 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
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)
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) {
/* 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);
}
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);
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;
}
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;
}
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);
}
}
+ stream->discont = TRUE;
+
return GST_FLOW_OK;
}
{
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)
}
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);
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;
}
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;
}
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
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;
"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,
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:
/* 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;
* 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),
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;
* 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;
* 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 */
* 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) {
/* 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) {
&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);
* 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;
}
{
- 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;
* 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");
}
}
/* 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 */
/*
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 *
#include <gst/base/gstadapter.h>
#include <gst/base/gstdataqueue.h>
#include "gstmpdparser.h"
-#include "gstisoff.h"
+#include <gst/isoff/gstisoff.h>
#include <gst/uridownloader/gsturidownloader.h>
G_BEGIN_DECLS
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;
};
/**
/* 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 */
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
* 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,
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) {
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);
}
return FALSE;
}
+
/*
Duration Data Type
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;
#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;
#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;
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;
/* 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;
}
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 =
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;
}
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;
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;
}
/* 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);
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)
gsthls.h \
gsthlsdemux.h \
gsthlssink.h \
+ gsthlssink2.h \
gstm3u8playlist.h \
m3u8.h
{
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);
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);
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;
}
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;
}
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)
/* 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
/* FIXME: check locking, protected automatically by manifest_lock already? */
/* The master playlist with the available variant streams */
GstHLSMasterPlaylist *master;
+
GstHLSVariantStream *current_variant;
};
#include "gsthls.h"
#include "gsthlsdemux.h"
#include "gsthlssink.h"
+#include "gsthlssink2.h"
GST_DEBUG_CATEGORY (hls_debug);
if (!gst_hls_sink_plugin_init (plugin))
return FALSE;
+ if (!gst_hls_sink2_plugin_init (plugin))
+ return FALSE;
+
return TRUE;
}
--- /dev/null
+/* GStreamer
+ * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
+ * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * 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 <gst/pbutils/pbutils.h>
+#include <gst/video/video.h>
+#include <glib/gstdio.h>
+#include <memory.h>
+
+
+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 <alessandro.d@gmail.com>, "
+ "Sebastian Dröge <sebastian@centricular.com>");
+
+ 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 ());
+}
--- /dev/null
+/* GStreamer
+ * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
+ *
+ * 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 <gst/gst.h>
+
+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
g_list_free (self->files);
g_free (self->last_data);
+ g_mutex_clear (&self->lock);
g_free (self);
}
}
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++;
+ }
}
/*
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);
}
}
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;
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");
/* 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);
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
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
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)
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) \
--- /dev/null
+/* 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 <gst/gst.h>
+
+#ifndef GST_ADAPTIVE_DEMUX_API
+#define GST_ADAPTIVE_DEMUX_API GST_EXPORT
+#endif
+
+#endif /* __GST_ADAPTIVE_DEMUX_PRELUDE_H__ */
*
* 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
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);
GST_API_UNLOCK (demux);
return ret;
+ }
case GST_EVENT_EOS:{
+ GstAdaptiveDemuxClass *demux_class;
GstQuery *query;
gboolean query_res;
gboolean ret = TRUE;
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)) {
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",
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);
}
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;
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;
}
}
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);
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;
}
if (!start_preroll_streams) {
g_mutex_lock (&stream->fragment_download_lock);
stream->cancelled = FALSE;
+ stream->replaced = FALSE;
g_mutex_unlock (&stream->fragment_download_lock);
}
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);
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);
}
}
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);
* 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);
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;
}
}
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);
}
/* 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);
}
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);
} 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;
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);
return FALSE;
}
+ /* Try to re-use existing source element */
if (stream->src != NULL) {
gchar *old_protocol, *new_protocol;
gchar *old_uri;
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
/* 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,
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 */
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)) {
*/
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;
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;
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;
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;
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));
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 */
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;
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));
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;
}
/* 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)
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,
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);
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 */
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;
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
#include <gst/uridownloader/gsturidownloader.h>
+#include <gst/adaptivedemux/adaptive-demux-prelude.h>
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)
*/
#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)
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;
guint moving_index;
guint64 *fragment_bitrates;
+ /* QoS data */
+ GstClockTime qos_earliest_time;
+
GstAdaptiveDemuxStreamFragment fragment;
guint download_error_count;
* 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);
* 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);
* 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);
/**
* 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
*/
* 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);
/**
* 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);
* 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);
/**
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
--- /dev/null
+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)
#include <gst/base/gstbytereader.h>
#include <string.h>
-#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'
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)
}
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
}
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;
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))
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,
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;
{
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,
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))
break;
}
case GST_ISOFF_FOURCC_TRAF:{
- GstByteReader sub_reader;
GstTrafBox traf;
gst_byte_reader_get_sub_reader (reader, &sub_reader,
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)
{
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 */
GstMapInfo info;
guint32 fourcc;
+ INITIALIZE_DEBUG_CATEGORY;
if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) {
*consumed = 0;
return GST_ISOFF_PARSER_ERROR;
G_BEGIN_DECLS
+#ifndef GST_ISOFF_API
+#define GST_ISOFF_API GST_EXPORT
+#endif
+
typedef enum {
GST_ISOFF_PARSER_OK,
GST_ISOFF_PARSER_DONE,
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')
#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)
#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;
} 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
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;
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__ */
-
--- /dev/null
+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])
$(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) \
#include <glib-object.h>
#include <gst/gst.h>
+#include <gst/uridownloader/uridownloader-prelude.h>
G_BEGIN_DECLS
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
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;
}
/**
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__ */
--- /dev/null
+/* 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 <gst/gst.h>
+
+#ifndef GST_URI_DOWNLOADER_API
+#define GST_URI_DOWNLOADER_API GST_EXPORT
+#endif
+
+#endif /* __GST_URI_DOWNLOADER_PRELUDE_H__ */
%{_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