diff --git a/docs/design/design-decodebin.txt b/docs/design/design-decodebin.txt index 6506a4e974..7ca84b6cad 100644 --- a/docs/design/design-decodebin.txt +++ b/docs/design/design-decodebin.txt @@ -255,40 +255,20 @@ for baseline profile, and software fallback for main/high profile; or a DSP codec only supporting certain resolutions, with a software fallback for unusual resolutions). So if decodebin just plugged the most highest-ranking decoder, that decoder might not be be able to handle the actual stream later -on, which would yield in an error (this is a data flow error then which would +on, which would yield an error (this is a data flow error then which would be hard to intercept and avoid in decodebin). In other words, we can't solve this issue by plugging a decoder right away with the parser. -So decodebin need to communicate to the parser the set of available decoder +So decodebin needs to communicate to the parser the set of available decoder caps (which would contain the relevant capabilities/restrictions such as supported profiles, resolutions, etc.), after the usual "autoplug-*" signal filtering/sorting of course. -This could be done in multiple ways, e.g. +This is done by plugging a capsfilter element right after the parser, and +constructing set of filter caps from the list of available decoders (one +appends at the end just the name(s) of the caps structures from the parser +pad template caps to function as an 'ANY other' caps equivalent). This let +the parser negotiate to a supported stream format in the same way as with +the static pipeline mentioned above, but of course incur some overhead +through the additional capsfilter element. - - plug a capsfilter element right after the parser, and construct - a set of filter caps from the list of available decoders (one - could append at the end just the name(s) of the caps structures - from the parser pad template caps to function as an 'ANY other' - caps equivalent). This would let the parser negotiate to a - supported stream format in the same way as with the static - pipeline mentioned above, but of course incur some overhead - through the additional capsfilter element. - - - one could add a filter-caps equivalent property to the parsers - (and/or GstBaseParse class) (e.g. "prefered-caps" or so). - - - one could add some kind of "fixate-caps" or "fixate-format" - signal to such parsers - -Alternatively, one could simply make all decoders incorporate parsers, so -that always all formats are supported. This is problematic for other reasons -though (e.g. we would not be able to detect the profile in all cases then -before plugging a decoder, which would make it hard to just play the audio -part of a stream and not the video if a suitable decoder was missing, for -example). - -Additional considerations: the same problem exists with sinks that support -non-raw formats. Consider, for example, an audio sink that accepts DTS audio, -but only the 14-bit variant, not the 16-bit variant (or only native endiannes). -Ideally dcaparse would convert into the required stream format here. diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index 9847b55dc1..31d4255d78 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -47,11 +47,30 @@ #define CHUNKSIZE (8500) /* this is out of vorbisfile */ +/* we hope we get a granpos within this many bytes off the end */ +#define DURATION_CHUNK_OFFSET (64*1024) + +/* stop duration checks within this much of EOS */ +#define EOS_AVOIDANCE_THRESHOLD 8192 + #define GST_FLOW_LIMIT GST_FLOW_CUSTOM_ERROR +#define GST_FLOW_SKIP_PUSH GST_FLOW_CUSTOM_SUCCESS_1 #define GST_CHAIN_LOCK(ogg) g_mutex_lock((ogg)->chain_lock) #define GST_CHAIN_UNLOCK(ogg) g_mutex_unlock((ogg)->chain_lock) +#define GST_PUSH_LOCK(ogg) \ + do { \ + GST_TRACE_OBJECT(ogg, "Push lock"); \ + g_mutex_lock((ogg)->push_lock); \ + } while(0) + +#define GST_PUSH_UNLOCK(ogg) \ + do { \ + GST_TRACE_OBJECT(ogg, "Push unlock"); \ + g_mutex_unlock((ogg)->push_lock); \ + } while(0) + GST_DEBUG_CATEGORY (gst_ogg_demux_debug); GST_DEBUG_CATEGORY (gst_ogg_demux_setup_debug); #define GST_CAT_DEFAULT gst_ogg_demux_debug @@ -101,6 +120,7 @@ static gboolean gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, GstOggChain * chain); static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, GstEvent * event); +static void gst_ogg_pad_mark_discont (GstOggPad * pad); static void gst_ogg_chain_mark_discont (GstOggChain * chain); static gboolean gst_ogg_demux_perform_seek (GstOggDemux * ogg, @@ -123,6 +143,11 @@ static void gst_ogg_demux_sync_streams (GstOggDemux * ogg); GstCaps *gst_ogg_demux_set_header_on_caps (GstOggDemux * ogg, GstCaps * caps, GList * headers); +static gboolean gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event); +static gboolean gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, + GstEvent * event); +static gboolean gst_ogg_demux_check_duration_push (GstOggDemux * ogg, + GstSeekFlags flags, GstEvent * event); GType gst_ogg_pad_get_type (void); G_DEFINE_TYPE (GstOggPad, gst_ogg_pad, GST_TYPE_PAD); @@ -162,6 +187,10 @@ gst_ogg_pad_init (GstOggPad * pad) pad->continued = NULL; pad->map.headers = NULL; pad->map.queued = NULL; + + pad->map.granulerate_n = 0; + pad->map.granulerate_d = 0; + pad->map.granuleshift = -1; } static void @@ -295,8 +324,7 @@ gst_ogg_pad_src_query (GstPad * pad, GstQuery * query) GstOggPad *pad = g_array_index (ogg->current_chain->streams, GstOggPad *, i); - seekable |= (pad->map.index != NULL && pad->map.n_index != 0); - + seekable = TRUE; if (pad->map.index != NULL && pad->map.n_index != 0) { GstOggIndex *idx; GstClockTime idx_time; @@ -309,6 +337,8 @@ gst_ogg_pad_src_query (GstPad * pad, GstQuery * query) stop = idx_time; else stop = MAX (idx_time, stop); + } else { + stop = -1; /* we've no clue, sadly, without seeking */ } } } @@ -446,6 +476,22 @@ gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet, cret = GST_FLOW_OK; + GST_PUSH_LOCK (ogg); + if (!ogg->pullmode && ogg->push_state == PUSH_PLAYING + && ogg->push_time_length == GST_CLOCK_TIME_NONE + && !ogg->push_disable_seeking) { + if (!ogg->building_chain) { + /* we got all headers, now try to get duration */ + if (!gst_ogg_demux_check_duration_push (ogg, GST_SEEK_FLAG_FLUSH, NULL)) { + GST_PUSH_UNLOCK (ogg); + return GST_FLOW_OK; + } + } + GST_PUSH_UNLOCK (ogg); + return GST_FLOW_OK; + } + GST_PUSH_UNLOCK (ogg); + GST_DEBUG_OBJECT (ogg, "%p streaming to peer serial %08x", pad, pad->map.serialno); @@ -695,6 +741,36 @@ gst_ogg_demux_collect_start_time (GstOggDemux * ogg, GstOggChain * chain) return start_time; } +static GstClockTime +gst_ogg_demux_collect_sync_time (GstOggDemux * ogg, GstOggChain * chain) +{ + gint i; + GstClockTime sync_time = GST_CLOCK_TIME_NONE; + + if (!chain) { + GST_WARNING_OBJECT (ogg, "No chain!"); + return GST_CLOCK_TIME_NONE; + } + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + if (pad->map.is_sparse) + continue; + + if (pad->push_sync_time == GST_CLOCK_TIME_NONE) { + sync_time = GST_CLOCK_TIME_NONE; + break; + } else { + if (sync_time == GST_CLOCK_TIME_NONE) + sync_time = pad->push_sync_time; + else + sync_time = MAX (sync_time, pad->push_sync_time); + } + } + return sync_time; +} + /* submit a packet to the oggpad, this function will run the * typefind code for the pad if this is the first packet for this * stream @@ -829,7 +905,10 @@ gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) pad->start_time = gst_ogg_stream_granule_to_time (&pad->map, start_granule); - GST_DEBUG ("start time %" G_GINT64_FORMAT, pad->start_time); + GST_DEBUG_OBJECT (ogg, + "start time %" GST_TIME_FORMAT " (%" GST_TIME_FORMAT ") for %s", + GST_TIME_ARGS (pad->start_time), GST_TIME_ARGS (pad->start_time), + gst_ogg_stream_get_media_type (&pad->map)); } else { packet->granulepos = gst_ogg_stream_granule_to_granulepos (&pad->map, pad->map.accumulated_granule, pad->keyframe_granule); @@ -880,12 +959,31 @@ gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) /* create the newsegment event we are going to send out */ gst_segment_init (&segment, GST_FORMAT_TIME); - segment.rate = ogg->segment.rate; - segment.applied_rate = ogg->segment.applied_rate; - segment.start = start_time; - segment.stop = chain->segment_stop; - segment.time = segment_time; - event = gst_event_new_segment (&segment); + + GST_PUSH_LOCK (ogg); + if (!ogg->pullmode && ogg->push_state == PUSH_LINEAR2) { + /* if we are fast forwarding to the actual seek target, + ensure previous frames are clipped */ + GST_DEBUG_OBJECT (ogg, + "Resynced, starting segment at %" GST_TIME_FORMAT + ", start_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_seek_time_original_target), + GST_TIME_ARGS (start_time)); + segment.rate = ogg->push_seek_rate; + segment.start = ogg->push_seek_time_original_target; + segment.stop = -1; + segment.time = ogg->push_seek_time_original_target; + event = gst_event_new_segment (&segment); + ogg->push_state = PUSH_PLAYING; + } else { + segment.rate = ogg->segment.rate; + segment.applied_rate = ogg->segment.applied_rate; + segment.start = start_time; + segment.stop = chain->segment_stop; + segment.time = segment_time; + event = gst_event_new_segment (&segment); + } + GST_PUSH_UNLOCK (ogg); ogg->resync = FALSE; } @@ -962,7 +1060,11 @@ gst_ogg_pad_stream_out (GstOggPad * pad, gint npackets) break; case -1: GST_LOG_OBJECT (ogg, "packetout discont"); - gst_ogg_chain_mark_discont (pad->chain); + if (!pad->map.is_sparse) { + gst_ogg_chain_mark_discont (pad->chain); + } else { + gst_ogg_pad_mark_discont (pad); + } break; case 1: GST_LOG_OBJECT (ogg, "packetout gave packet of size %ld", packet.bytes); @@ -999,6 +1101,424 @@ could_not_submit: } } +static void +gst_ogg_demux_setup_bisection_bounds (GstOggDemux * ogg) +{ + if (ogg->push_last_seek_time >= ogg->push_seek_time_target) { + GST_DEBUG_OBJECT (ogg, "We overshot by %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_last_seek_time - ogg->push_seek_time_target)); + ogg->push_offset1 = ogg->push_last_seek_offset; + ogg->push_time1 = ogg->push_last_seek_time; + } else { + GST_DEBUG_OBJECT (ogg, "We undershot by %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_seek_time_target - ogg->push_last_seek_time)); + ogg->push_offset0 = ogg->push_last_seek_offset; + ogg->push_time0 = ogg->push_last_seek_time; + } +} + +static gint64 +gst_ogg_demux_estimate_bisection_target (GstOggDemux * ogg) +{ + gint64 best; + gint64 segment_bitrate; + + /* we might not know the length of the stream in time, + so push_time1 might not be set */ + GST_DEBUG_OBJECT (ogg, + "push time 1: %" GST_TIME_FORMAT ", dbytes %" G_GINT64_FORMAT, + GST_TIME_ARGS (ogg->push_time1), ogg->push_offset1 - ogg->push_offset0); + if (ogg->push_time1 == GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (ogg, + "New segment to consider: bytes %" G_GINT64_FORMAT " %" G_GINT64_FORMAT + ", time %" GST_TIME_FORMAT " (open ended)", ogg->push_offset0, + ogg->push_offset1, GST_TIME_ARGS (ogg->push_time0)); + if (ogg->push_last_seek_time == ogg->push_start_time) { + /* if we're at start and don't know the end time, we can't estimate + bitrate, so get the nominal declared bitrate as a failsafe, or some + random constant which will be discarded after we made a (probably + dire) first guess */ + segment_bitrate = (ogg->bitrate > 0 ? ogg->bitrate : 1000); + } else { + segment_bitrate = + gst_util_uint64_scale (ogg->push_last_seek_offset - 0, + 8 * GST_SECOND, ogg->push_last_seek_time - ogg->push_start_time); + } + best = + ogg->push_offset0 + + gst_util_uint64_scale (ogg->push_seek_time_target - ogg->push_time0, + segment_bitrate, 8 * GST_SECOND); + } else { + GST_DEBUG_OBJECT (ogg, + "New segment to consider: bytes %" G_GINT64_FORMAT " %" G_GINT64_FORMAT + ", time %" GST_TIME_FORMAT " %" GST_TIME_FORMAT, ogg->push_offset0, + ogg->push_offset1, GST_TIME_ARGS (ogg->push_time0), + GST_TIME_ARGS (ogg->push_time1)); + if (ogg->push_time0 == ogg->push_time1) { + best = ogg->push_offset0; + } else { + segment_bitrate = + gst_util_uint64_scale (ogg->push_offset1 - ogg->push_offset0, + 8 * GST_SECOND, ogg->push_time1 - ogg->push_time0); + GST_DEBUG_OBJECT (ogg, + "Local bitrate on the %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT + " segment: %" G_GINT64_FORMAT, GST_TIME_ARGS (ogg->push_time0), + GST_TIME_ARGS (ogg->push_time1), segment_bitrate); + best = + ogg->push_offset0 + + gst_util_uint64_scale (ogg->push_seek_time_target - ogg->push_time0, + segment_bitrate, 8 * GST_SECOND); + } + } + + /* offset by typical page size */ + best -= CHUNKSIZE; + if (best < ogg->push_offset0) + best = ogg->push_offset0; + if (best < 0) + best = 0; + + return best; +} + +static void +gst_ogg_demux_record_keyframe_time (GstOggDemux * ogg, GstOggPad * pad, + ogg_int64_t granpos) +{ + gint64 kf_granule; + GstClockTime kf_time; + + kf_granule = gst_ogg_stream_granulepos_to_key_granule (&pad->map, granpos); + kf_time = gst_ogg_stream_granule_to_time (&pad->map, kf_granule); + + pad->push_kf_time = kf_time; +} + +/* returns the earliest keyframe time for all non sparse pads in the chain, + * if known, and GST_CLOCK_TIME_NONE if not */ +static GstClockTime +gst_ogg_demux_get_earliest_keyframe_time (GstOggDemux * ogg) +{ + GstClockTime t = GST_CLOCK_TIME_NONE; + GstOggChain *chain = ogg->building_chain; + int i; + + if (!chain) { + GST_WARNING_OBJECT (ogg, "No chain!"); + return GST_CLOCK_TIME_NONE; + } + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + if (pad->map.is_sparse) + continue; + if (pad->push_kf_time == GST_CLOCK_TIME_NONE) + return GST_CLOCK_TIME_NONE; + if (t == GST_CLOCK_TIME_NONE || pad->push_kf_time < t) + t = pad->push_kf_time; + } + + return t; +} + +/* MUST be called with the push lock locked, and will unlock it + regardless of return value. */ +static GstFlowReturn +gst_ogg_demux_seek_back_after_push_duration_check_unlock (GstOggDemux * ogg) +{ + GstEvent *event; + + /* Get the delayed event, if any */ + event = ogg->push_mode_seek_delayed_event; + ogg->push_mode_seek_delayed_event = NULL; + + ogg->push_state = PUSH_PLAYING; + + GST_PUSH_UNLOCK (ogg); + + if (event) { + /* If there is one, perform it */ + gst_ogg_demux_perform_seek_push (ogg, event); + } else { + /* If there wasn't, seek back at start to start normal playback */ + GST_INFO_OBJECT (ogg, "Seeking back to 0 after duration check"); + event = gst_event_new_seek (1.0, GST_FORMAT_BYTES, + GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 1, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + if (!gst_pad_push_event (ogg->sinkpad, event)) { + GST_WARNING_OBJECT (ogg, "Failed seeking back to start"); + return GST_FLOW_ERROR; + } + } + + return GST_FLOW_OK; +} + +static gboolean +gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) +{ + GstOggDemux *ogg = pad->ogg; + ogg_int64_t granpos = ogg_page_granulepos (page); + + GST_PUSH_LOCK (ogg); + if (granpos >= 0) { + if (ogg->push_start_time == GST_CLOCK_TIME_NONE) { + ogg->push_start_time = + gst_ogg_stream_get_start_time_for_granulepos (&pad->map, granpos); + GST_DEBUG_OBJECT (ogg, "Stream start time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_start_time)); + } + ogg->push_time_offset = + gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); + if (ogg->push_time_offset > 0) { + GST_DEBUG_OBJECT (ogg, "Bitrate since start: %" G_GUINT64_FORMAT, + gst_util_uint64_scale (ogg->push_byte_offset, 8 * GST_SECOND, + ogg->push_time_offset)); + } + + if (ogg->push_state == PUSH_DURATION) { + GstClockTime t = + gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); + + if (ogg->total_time == GST_CLOCK_TIME_NONE || t > ogg->total_time) { + GST_DEBUG_OBJECT (ogg, "New total time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (t)); + ogg->total_time = t; + ogg->push_time_length = t; + } + + /* If we were determining the duration of the stream, we're now done, + and can get back to sending the original event we delayed. + We stop a bit before the end of the stream, as if we get a EOS + event and there is a queue2 upstream (such as when using playbin2), + it will pause the task *after* we come back from the EOS handler, + so we cannot prevent the pausing by issuing a seek. */ + if (ogg->push_byte_offset + EOS_AVOIDANCE_THRESHOLD >= + ogg->push_byte_length) { + GstMessage *message; + GstFlowReturn res; + + /* tell the pipeline we've just found out the duration */ + GST_INFO_OBJECT (ogg, "New duration found: %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->total_time)); + message = + gst_message_new_duration (GST_OBJECT (ogg), GST_FORMAT_TIME, + ogg->total_time); + gst_element_post_message (GST_ELEMENT (ogg), message); + + GST_DEBUG_OBJECT (ogg, + "We're close enough to the end, and we're scared " + "to get too close, seeking back to start"); + + res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg); + if (res != GST_FLOW_OK) + return res; + return GST_FLOW_SKIP_PUSH; + } else { + GST_PUSH_UNLOCK (ogg); + } + return GST_FLOW_SKIP_PUSH; + } + } + + /* if we're seeking, look at time, and decide what to do */ + if (ogg->push_state != PUSH_PLAYING && ogg->push_state != PUSH_LINEAR2) { + GstClockTime t; + gint64 best = -1; + GstEvent *sevent; + int res; + gboolean close_enough; + + /* ignore -1 granpos when seeking, we want to sync on a real granpos */ + if (granpos < 0) { + GST_PUSH_UNLOCK (ogg); + if (ogg_stream_pagein (&pad->map.stream, page) != 0) + goto choked; + return GST_FLOW_SKIP_PUSH; + } + + t = gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); + + if (ogg->push_state == PUSH_BISECT1 || ogg->push_state == PUSH_BISECT2) { + GstClockTime sync_time; + + if (pad->push_sync_time == GST_CLOCK_TIME_NONE) + pad->push_sync_time = t; + GST_DEBUG_OBJECT (ogg, "Got timestamp %" GST_TIME_FORMAT " for %s", + GST_TIME_ARGS (t), gst_ogg_stream_get_media_type (&pad->map)); + sync_time = gst_ogg_demux_collect_sync_time (ogg, ogg->building_chain); + if (sync_time == GST_CLOCK_TIME_NONE) { + GST_PUSH_UNLOCK (ogg); + GST_DEBUG_OBJECT (ogg, + "Not enough timing info collected for sync, waiting for more"); + if (ogg_stream_pagein (&pad->map.stream, page) != 0) + goto choked; + return GST_FLOW_SKIP_PUSH; + } + ogg->push_last_seek_time = sync_time; + + GST_DEBUG_OBJECT (ogg, + "Bisection just seeked at %" G_GINT64_FORMAT ", time %" + GST_TIME_FORMAT ", target was %" GST_TIME_FORMAT, + ogg->push_last_seek_offset, + GST_TIME_ARGS (ogg->push_last_seek_time), + GST_TIME_ARGS (ogg->push_seek_time_target)); + + if (ogg->push_time1 != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (ogg, + "Interval was %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT " (%" + G_GINT64_FORMAT "), time %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT + " (%" GST_TIME_FORMAT ")", ogg->push_offset0, ogg->push_offset1, + ogg->push_offset1 - ogg->push_offset0, + GST_TIME_ARGS (ogg->push_time0), GST_TIME_ARGS (ogg->push_time1), + GST_TIME_ARGS (ogg->push_time1 - ogg->push_time0)); + } else { + GST_DEBUG_OBJECT (ogg, + "Interval was %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT " (%" + G_GINT64_FORMAT "), time %" GST_TIME_FORMAT " - unknown", + ogg->push_offset0, ogg->push_offset1, + ogg->push_offset1 - ogg->push_offset0, + GST_TIME_ARGS (ogg->push_time0)); + } + + gst_ogg_demux_setup_bisection_bounds (ogg); + + best = gst_ogg_demux_estimate_bisection_target (ogg); + + if (ogg->push_seek_time_target == 0) { + GST_DEBUG_OBJECT (ogg, "Seeking to 0, deemed close enough"); + close_enough = (ogg->push_last_seek_time == 0); + } else { + /* TODO: make this dependent on framerate ? */ + GstClockTime threshold = GST_SECOND / 2; + + /* We want to be within half a second before the target */ + if (threshold > ogg->push_seek_time_target) + threshold = ogg->push_seek_time_target; + close_enough = ogg->push_last_seek_time < ogg->push_seek_time_target + && ogg->push_last_seek_time >= + ogg->push_seek_time_target - threshold; + GST_DEBUG_OBJECT (ogg, + "testing if we're close enough: %" GST_TIME_FORMAT " <= %" + GST_TIME_FORMAT " < %" GST_TIME_FORMAT " ? %s", + GST_TIME_ARGS (ogg->push_seek_time_target - threshold), + GST_TIME_ARGS (ogg->push_last_seek_time), + GST_TIME_ARGS (ogg->push_seek_time_target), + close_enough ? "Yes" : "No"); + } + + if (close_enough || best == ogg->push_last_seek_offset) { + if (ogg->push_state == PUSH_BISECT1) { + /* we now know the time segment we'll have to search for + the second bisection */ + ogg->push_time0 = ogg->push_start_time; + ogg->push_offset0 = 0; + + GST_DEBUG_OBJECT (ogg, + "Seek to %" GST_TIME_FORMAT + " (%lx) done, now gathering pages for all non-sparse streams", + GST_TIME_ARGS (ogg->push_seek_time_target), (long) granpos); + ogg->push_state = PUSH_LINEAR1; + } else { + /* If we're asked for an accurate seek, we'll go forward till + we get to the original seek target time, else we'll just drop + here at the keyframe */ + if (ogg->push_seek_flags & GST_SEEK_FLAG_ACCURATE) { + GST_INFO_OBJECT (ogg, + "Seek to keyframe at %" GST_TIME_FORMAT " done (we're at %" + GST_TIME_FORMAT "), skipping to original target (%" + GST_TIME_FORMAT ")", + GST_TIME_ARGS (ogg->push_seek_time_target), + GST_TIME_ARGS (sync_time), + GST_TIME_ARGS (ogg->push_seek_time_original_target)); + ogg->push_state = PUSH_LINEAR2; + } else { + GST_DEBUG_OBJECT (ogg, "Seek to keyframe done, playing"); + + /* we're synced to the seek target, so flush stream and stuff + any queued pages into the stream so we start decoding there */ + ogg->push_state = PUSH_PLAYING; + } + GST_INFO_OBJECT (ogg, "Bisection needed %d + %d steps", + ogg->push_bisection_steps[0], ogg->push_bisection_steps[1]); + } + } + } else if (ogg->push_state == PUSH_LINEAR1) { + if (pad->push_kf_time == GST_CLOCK_TIME_NONE) { + GstClockTime earliest_keyframe_time; + + gst_ogg_demux_record_keyframe_time (ogg, pad, granpos); + GST_DEBUG_OBJECT (ogg, + "Previous keyframe for %s stream at %" GST_TIME_FORMAT, + gst_ogg_stream_get_media_type (&pad->map), + GST_TIME_ARGS (pad->push_kf_time)); + earliest_keyframe_time = gst_ogg_demux_get_earliest_keyframe_time (ogg); + if (earliest_keyframe_time != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (ogg, + "All non sparse streams now have a previous keyframe time," + "bisecting again to %" GST_TIME_FORMAT, + GST_TIME_ARGS (earliest_keyframe_time)); + ogg->push_seek_time_target = earliest_keyframe_time; + + ogg->push_state = PUSH_BISECT2; + best = gst_ogg_demux_estimate_bisection_target (ogg); + } + } + } + + if (ogg->push_state == PUSH_BISECT1 || ogg->push_state == PUSH_BISECT2) { + gint i; + + ogg_sync_reset (&ogg->sync); + for (i = 0; i < ogg->building_chain->streams->len; i++) { + GstOggPad *pad = + g_array_index (ogg->building_chain->streams, GstOggPad *, i); + + pad->push_sync_time = GST_CLOCK_TIME_NONE; + ogg_stream_reset (&pad->map.stream); + } + + GST_DEBUG_OBJECT (ogg, + "seeking to %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT, best, + (gint64) - 1); + /* do seek */ + g_assert (best != -1); + ogg->push_bisection_steps[ogg->push_state == PUSH_BISECT2 ? 1 : 0]++; + sevent = + gst_event_new_seek (ogg->push_seek_rate, GST_FORMAT_BYTES, + ogg->push_seek_flags, GST_SEEK_TYPE_SET, best, + GST_SEEK_TYPE_NONE, -1); + + GST_PUSH_UNLOCK (ogg); + res = gst_pad_push_event (ogg->sinkpad, sevent); + if (!res) { + /* We failed to send the seek event, notify the pipeline */ + GST_ELEMENT_ERROR (ogg, RESOURCE, SEEK, (NULL), ("Failed to seek")); + return GST_FLOW_ERROR; + } + return GST_FLOW_SKIP_PUSH; + } + + if (ogg->push_state != PUSH_PLAYING) { + GST_PUSH_UNLOCK (ogg); + return GST_FLOW_SKIP_PUSH; + } + } + GST_PUSH_UNLOCK (ogg); + + return GST_FLOW_OK; + +choked: + { + GST_WARNING_OBJECT (ogg, + "ogg stream choked on page (serial %08x), " + "resetting stream", pad->map.serialno); + gst_ogg_pad_reset (pad); + /* we continue to recover */ + return GST_FLOW_SKIP_PUSH; + } +} + /* submit a page to an oggpad, this function will then submit all * the packets in the page. */ @@ -1011,7 +1531,7 @@ gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page) ogg = pad->ogg; - /* for negative rates we read pages backwards and must therefore be carefull + /* for negative rates we read pages backwards and must therefore be careful * with continued pages */ if (ogg->segment.rate < 0.0) { gint npackets; @@ -1037,6 +1557,15 @@ gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page) } } + /* keep track of time in push mode */ + if (!ogg->pullmode) { + result = gst_ogg_pad_handle_push_mode_state (pad, page); + if (result == GST_FLOW_SKIP_PUSH) + return GST_FLOW_OK; + if (result != GST_FLOW_OK) + return result; + } + if (ogg_stream_pagein (&pad->map.stream, page) != 0) goto choked; @@ -1273,7 +1802,6 @@ static gboolean gst_ogg_demux_sink_activate_push (GstPad * sinkpad, gboolean active); static GstStateChangeReturn gst_ogg_demux_change_state (GstElement * element, GstStateChange transition); -static gboolean gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event); static void gst_ogg_print (GstOggDemux * demux); @@ -1320,6 +1848,7 @@ gst_ogg_demux_init (GstOggDemux * ogg) gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad); ogg->chain_lock = g_mutex_new (); + ogg->push_lock = g_mutex_new (); ogg->chains = g_array_new (FALSE, TRUE, sizeof (GstOggChain *)); ogg->newsegment = NULL; @@ -1334,6 +1863,7 @@ gst_ogg_demux_finalize (GObject * object) g_array_free (ogg->chains, TRUE); g_mutex_free (ogg->chain_lock); + g_mutex_free (ogg->push_lock); ogg_sync_clear (&ogg->sync); if (ogg->newsegment) @@ -1359,6 +1889,7 @@ gst_ogg_demux_reset_streams (GstOggDemux * ogg) stream->map.accumulated_granule = 0; } ogg->building_chain = chain; + GST_DEBUG_OBJECT (ogg, "Resetting current chain"); ogg->current_chain = NULL; ogg->resync = TRUE; } @@ -1379,16 +1910,55 @@ gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event) GST_DEBUG_OBJECT (ogg, "got a flush stop event"); ogg_sync_reset (&ogg->sync); res = gst_ogg_demux_send_event (ogg, event); - gst_ogg_demux_reset_streams (ogg); + if (ogg->pullmode || ogg->push_state != PUSH_DURATION) { + /* it's starting to feel reaaaally dirty :( + if we're on a spliced seek to get duration, don't reset streams, + we'll need them for the delayed seek */ + gst_ogg_demux_reset_streams (ogg); + } break; case GST_EVENT_SEGMENT: GST_DEBUG_OBJECT (ogg, "got a new segment event"); + { + GstSegment segment; + + gst_event_copy_segment (event, &segment); + + if (segment.format == GST_FORMAT_BYTES) { + GST_PUSH_LOCK (ogg); + ogg->push_byte_offset = segment.start; + ogg->push_last_seek_offset = segment.start; + GST_PUSH_UNLOCK (ogg); + } else { + GST_WARNING_OBJECT (ogg, "unexpected segment format: %s", + gst_format_get_name (segment.format)); + } + } gst_event_unref (event); res = TRUE; break; case GST_EVENT_EOS: { GST_DEBUG_OBJECT (ogg, "got an EOS event"); +#if 0 + /* This would be what is needed (recover from EOS by going on to + the next step (issue the delayed seek)), but it does not work + if there is a queue2 upstream - see more details comment in + gst_ogg_pad_submit_page. + If I could find a way to bypass queue2 behavior, this should + be enabled. */ + GST_PUSH_LOCK (ogg); + if (ogg->push_state == PUSH_DURATION) { + GST_DEBUG_OBJECT (ogg, "Got EOS while determining length"); + res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg); + if (res != GST_FLOW_OK) { + GST_DEBUG_OBJECT (ogg, "Error seeking back after duration check: %d", + res); + } + break; + } + GST_PUSH_UNLOCK (ogg); +#endif res = gst_ogg_demux_send_event (ogg, event); if (ogg->current_chain == NULL) { GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), @@ -1427,6 +1997,12 @@ gst_ogg_demux_submit_buffer (GstOggDemux * ogg, GstBuffer * buffer) if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0)) goto write_failed; + if (!ogg->pullmode) { + GST_PUSH_LOCK (ogg); + ogg->push_byte_offset += size; + GST_PUSH_UNLOCK (ogg); + } + done: gst_buffer_unref (buffer); @@ -1697,11 +2273,9 @@ gst_ogg_demux_deactivate_current_chain (GstOggDemux * ogg) pad->added = FALSE; } - /* if we cannot seek back to the chain, we can destroy the chain - * completely */ - if (!ogg->pullmode) { - gst_ogg_chain_free (chain); - } + /* With push mode seeking implemented, we can now seek back to the chain, + so we do not destroy it */ + GST_DEBUG_OBJECT (ogg, "Resetting current chain"); ogg->current_chain = NULL; return TRUE; @@ -1748,6 +2322,23 @@ gst_ogg_demux_set_header_on_caps (GstOggDemux * ogg, GstCaps * caps, return caps; } +static void +gst_ogg_demux_push_queued_buffers (GstOggDemux * ogg, GstOggPad * pad) +{ + GList *walk; + + /* push queued packets */ + for (walk = pad->map.queued; walk; walk = g_list_next (walk)) { + ogg_packet *p = walk->data; + + gst_ogg_demux_chain_peer (pad, p, TRUE); + _ogg_packet_free (p); + } + /* and free the queued buffers */ + g_list_free (pad->map.queued); + pad->map.queued = NULL; +} + static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, GstEvent * event) @@ -1760,6 +2351,11 @@ gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, if (chain == ogg->current_chain) { if (event) gst_event_unref (event); + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + gst_ogg_demux_push_queued_buffers (ogg, pad); + } return TRUE; } @@ -1801,6 +2397,7 @@ gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, /* after adding the new pads, remove the old pads */ gst_ogg_demux_deactivate_current_chain (ogg); + GST_DEBUG_OBJECT (ogg, "Setting current chain to %p", chain); ogg->current_chain = chain; /* we are finished now */ @@ -1845,16 +2442,7 @@ gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, } GST_DEBUG_OBJECT (ogg, "pushing queued buffers"); - /* push queued packets */ - for (walk = pad->map.queued; walk; walk = g_list_next (walk)) { - ogg_packet *p = walk->data; - - gst_ogg_demux_chain_peer (pad, p, TRUE); - _ogg_packet_free (p); - } - /* and free the queued buffers */ - g_list_free (pad->map.queued); - pad->map.queued = NULL; + gst_ogg_demux_push_queued_buffers (ogg, pad); } return TRUE; } @@ -2461,6 +3049,83 @@ no_chain: } } +static gboolean +gst_ogg_demux_get_duration_push (GstOggDemux * ogg, int flags) +{ + /* In push mode, we get to the end of the stream to get the duration */ + gint64 position; + GstEvent *sevent; + gboolean res; + + /* A full Ogg page can be almost 64 KB. There's no guarantee that there'll be a + granpos there, but it's fairly likely */ + position = + ogg->push_byte_length - DURATION_CHUNK_OFFSET - EOS_AVOIDANCE_THRESHOLD; + if (position < 0) + position = 0; + + GST_DEBUG_OBJECT (ogg, + "Getting duration, seeking near the end, to %" G_GINT64_FORMAT, position); + ogg->push_state = PUSH_DURATION; + /* do not read the last byte */ + sevent = gst_event_new_seek (1.0, GST_FORMAT_BYTES, flags, GST_SEEK_TYPE_SET, + position, GST_SEEK_TYPE_SET, ogg->push_byte_length - 1); + res = gst_pad_push_event (ogg->sinkpad, sevent); + if (res) { + GST_DEBUG_OBJECT (ogg, "Seek succesful"); + return TRUE; + } else { + GST_INFO_OBJECT (ogg, "Seek failed, duration will stay unknown"); + ogg->push_state = PUSH_PLAYING; + return FALSE; + } +} + +static gboolean +gst_ogg_demux_check_duration_push (GstOggDemux * ogg, GstSeekFlags flags, + GstEvent * event) +{ + if (ogg->push_byte_length < 0) { + GstPad *peer; + + GST_DEBUG_OBJECT (ogg, "Trying to find byte/time length"); + if ((peer = gst_pad_get_peer (ogg->sinkpad)) != NULL) { + gint64 length; + int res; + + res = gst_pad_query_duration (peer, GST_FORMAT_BYTES, &length); + if (res && length > 0) { + ogg->push_byte_length = length; + GST_DEBUG_OBJECT (ogg, + "File byte length %" G_GINT64_FORMAT, ogg->push_byte_length); + } + res = gst_pad_query_duration (peer, GST_FORMAT_TIME, &length); + gst_object_unref (peer); + if (res && length >= 0) { + ogg->push_time_length = length; + GST_DEBUG_OBJECT (ogg, "File time length %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_time_length)); + } else if (!ogg->push_disable_seeking) { + gboolean res; + + res = gst_ogg_demux_get_duration_push (ogg, flags); + if (res) { + GST_DEBUG_OBJECT (ogg, + "File time length unknown, trying to determine"); + ogg->push_mode_seek_delayed_event = NULL; + if (event) { + GST_DEBUG_OBJECT (ogg, + "Let me intercept this innocent looking seek request"); + ogg->push_mode_seek_delayed_event = gst_event_copy (event); + } + return FALSE; + } + } + } + } + return TRUE; +} + static gboolean gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, GstEvent * event) { @@ -2474,6 +3139,9 @@ gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, GstEvent * event) GstEvent *sevent; GstOggChain *chain; gint64 best, best_time; + gint i; + + GST_DEBUG_OBJECT (ogg, "Push mode seek request received"); gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); @@ -2483,39 +3151,129 @@ gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, GstEvent * event) goto error; } + if (start_type != GST_SEEK_TYPE_SET) { + GST_DEBUG_OBJECT (ogg, "can only seek to a SET target"); + goto error; + } + + if (!(flags & GST_SEEK_FLAG_FLUSH)) { + GST_DEBUG_OBJECT (ogg, "can only do flushing seeks"); + goto error; + } + + GST_DEBUG_OBJECT (ogg, "Push mode seek request: %" GST_TIME_FORMAT, + GST_TIME_ARGS (start)); + chain = ogg->current_chain; - if (!chain) + if (!chain) { + GST_WARNING_OBJECT (ogg, "No chain to seek on"); + goto error; + } + + /* start accessing push_* members */ + GST_PUSH_LOCK (ogg); + + /* not if we disabled seeking (chained streams) */ + if (ogg->push_disable_seeking) { + GST_DEBUG_OBJECT (ogg, "Seeking disabled"); + goto error_locked; + } + + /* not when we're trying to work out duration */ + if (ogg->push_state == PUSH_DURATION) { + GST_DEBUG_OBJECT (ogg, "Busy working out duration, try again later"); + goto error_locked; + } + + /* actually, not if we're doing any seeking already */ + if (ogg->push_state != PUSH_PLAYING) { + GST_DEBUG_OBJECT (ogg, "Already doing some seeking, try again later"); + goto error_locked; + } + + /* on the first seek, get length if we can */ + if (!gst_ogg_demux_check_duration_push (ogg, flags, event)) { + GST_PUSH_UNLOCK (ogg); return FALSE; + } if (do_index_search (ogg, chain, 0, -1, 0, -1, start, &best, &best_time)) { /* the index gave some result */ GST_DEBUG_OBJECT (ogg, "found offset %" G_GINT64_FORMAT " with time %" G_GUINT64_FORMAT, best, best_time); - start = best; - } else if ((bitrate = ogg->bitrate) > 0) { - /* try with bitrate convert the seek positions to bytes */ - if (start_type != GST_SEEK_TYPE_NONE) { - start = gst_util_uint64_scale (start, bitrate, 8 * GST_SECOND); - } - if (stop_type != GST_SEEK_TYPE_NONE) { - stop = gst_util_uint64_scale (stop, bitrate, 8 * GST_SECOND); - } } else { - /* we don't know */ - res = FALSE; + if (ogg->push_time_length > 0) { + /* if we know the time length, we know the full segment bitrate */ + GST_DEBUG_OBJECT (ogg, "Using real file bitrate"); + bitrate = + gst_util_uint64_scale (ogg->push_byte_length, 8 * GST_SECOND, + ogg->push_time_length); + } else if (ogg->push_time_offset > 0) { + /* get a first approximation using known bitrate to the current position */ + GST_DEBUG_OBJECT (ogg, "Using file bitrate so far"); + bitrate = + gst_util_uint64_scale (ogg->push_byte_offset, 8 * GST_SECOND, + ogg->push_time_offset); + } else if (ogg->bitrate > 0) { + /* nominal bitrate is better than nothing, even if it lies often */ + GST_DEBUG_OBJECT (ogg, "Using nominal bitrate"); + bitrate = ogg->bitrate; + } else { + /* meh */ + GST_DEBUG_OBJECT (ogg, + "At stream start, and no nominal bitrate, using some random magic " + "number to seed"); + /* the bisection, once started, should give us a better approximation */ + bitrate = 1000; + } + best = gst_util_uint64_scale (start, bitrate, 8 * GST_SECOND); } - if (res) { - GST_DEBUG_OBJECT (ogg, - "seeking to %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT, start, stop); - /* do seek */ - sevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, - start_type, start, stop_type, stop); + /* offset by typical page length, and ensure our best guess is within + reasonable bounds */ + best -= CHUNKSIZE; + if (best < 0) + best = 0; + if (ogg->push_byte_length > 0 && best >= ogg->push_byte_length) + best = ogg->push_byte_length - 1; - res = gst_pad_push_event (ogg->sinkpad, sevent); + /* set up bisection search */ + ogg->push_offset0 = 0; + ogg->push_offset1 = ogg->push_byte_length - 1; + ogg->push_time0 = ogg->push_start_time; + ogg->push_time1 = ogg->push_time_length; + ogg->push_seek_time_target = start; + ogg->push_seek_time_original_target = start; + ogg->push_state = PUSH_BISECT1; + + /* reset pad push mode seeking state */ + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + pad->push_kf_time = GST_CLOCK_TIME_NONE; + pad->push_sync_time = GST_CLOCK_TIME_NONE; } + GST_DEBUG_OBJECT (ogg, + "Setting up bisection search for %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT + " (time %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT ")", ogg->push_offset0, + ogg->push_offset1, GST_TIME_ARGS (ogg->push_time0), + GST_TIME_ARGS (ogg->push_time1)); + GST_DEBUG_OBJECT (ogg, + "Target time is %" GST_TIME_FORMAT ", best first guess is %" + G_GINT64_FORMAT, GST_TIME_ARGS (ogg->push_seek_time_target), best); + + ogg->push_seek_rate = rate; + ogg->push_seek_flags = flags; + ogg->push_mode_seek_delayed_event = NULL; + ogg->push_bisection_steps[0] = 1; + ogg->push_bisection_steps[1] = 0; + sevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, + start_type, best, GST_SEEK_TYPE_NONE, -1); + + GST_PUSH_UNLOCK (ogg); + res = gst_pad_push_event (ogg->sinkpad, sevent); + return res; /* ERRORS */ @@ -2524,6 +3282,10 @@ error: GST_DEBUG_OBJECT (ogg, "seek failed"); return FALSE; } + +error_locked: + GST_PUSH_UNLOCK (ogg); + goto error; } static gboolean @@ -3119,11 +3881,31 @@ gst_ogg_demux_handle_page (GstOggDemux * ogg, ogg_page * page) if (pad) { result = gst_ogg_pad_submit_page (pad, page); } else { - /* no pad. This means an ogg page without bos has been seen for this - * serialno. we just ignore it but post a warning... */ - GST_ELEMENT_WARNING (ogg, STREAM, DECODE, - (NULL), ("unknown ogg pad for serial %08x detected", serialno)); - return GST_FLOW_OK; + GST_PUSH_LOCK (ogg); + if (!ogg->pullmode && !ogg->push_disable_seeking) { + /* no pad while probing for duration, we must have a chained stream, + and we don't support them, so back off */ + GST_INFO_OBJECT (ogg, "We seem to have a chained stream, we won't seek"); + if (ogg->push_state == PUSH_DURATION) { + GstFlowReturn res; + + res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg); + if (res != GST_FLOW_OK) + return res; + } + + /* only once we seeked back */ + GST_PUSH_LOCK (ogg); + ogg->push_disable_seeking = TRUE; + } else { + GST_PUSH_UNLOCK (ogg); + /* no pad. This means an ogg page without bos has been seen for this + * serialno. we just ignore it but post a warning... */ + GST_ELEMENT_WARNING (ogg, STREAM, DECODE, + (NULL), ("unknown ogg pad for serial %08x detected", serialno)); + return GST_FLOW_OK; + } + GST_PUSH_UNLOCK (ogg); } return result; @@ -3148,8 +3930,11 @@ gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer) ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad)); - GST_DEBUG_OBJECT (ogg, "chain"); + GST_DEBUG_OBJECT (ogg, "enter"); result = gst_ogg_demux_submit_buffer (ogg, buffer); + if (result < 0) { + GST_DEBUG_OBJECT (ogg, "gst_ogg_demux_submit_buffer returned %d", result); + } while (result == GST_FLOW_OK) { ogg_page page; @@ -3163,11 +3948,15 @@ gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer) GST_DEBUG_OBJECT (ogg, "discont in page found, continuing"); } else { result = gst_ogg_demux_handle_page (ogg, &page); + if (result < 0) { + GST_DEBUG_OBJECT (ogg, "gst_ogg_demux_handle_page returned %d", result); + } } } if (ret == 0 || result == GST_FLOW_OK) { gst_ogg_demux_sync_streams (ogg); } + GST_DEBUG_OBJECT (ogg, "leave with %d", result); return result; } @@ -3177,6 +3966,9 @@ gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event) GstOggChain *chain = ogg->current_chain; gboolean res = TRUE; + if (!chain) + chain = ogg->building_chain; + if (chain) { gint i; @@ -3187,6 +3979,8 @@ gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event) GST_DEBUG_OBJECT (pad, "Pushing event %" GST_PTR_FORMAT, event); res &= gst_pad_push_event (GST_PAD (pad), event); } + } else { + GST_WARNING_OBJECT (ogg, "No chain to forward event to"); } gst_event_unref (event); @@ -3605,6 +4399,14 @@ gst_ogg_demux_change_state (GstElement * element, GstStateChange transition) ogg->running = FALSE; ogg->bitrate = 0; ogg->total_time = -1; + GST_PUSH_LOCK (ogg); + ogg->push_byte_offset = 0; + ogg->push_byte_length = -1; + ogg->push_time_length = GST_CLOCK_TIME_NONE; + ogg->push_time_offset = GST_CLOCK_TIME_NONE; + ogg->push_disable_seeking = FALSE; + ogg->push_state = PUSH_PLAYING; + GST_PUSH_UNLOCK (ogg); gst_segment_init (&ogg->segment, GST_FORMAT_TIME); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: diff --git a/ext/ogg/gstoggdemux.h b/ext/ogg/gstoggdemux.h index 2a0ba2b5d1..050caf20de 100644 --- a/ext/ogg/gstoggdemux.h +++ b/ext/ogg/gstoggdemux.h @@ -113,6 +113,10 @@ struct _GstOggPad gboolean is_eos; gboolean added; + + /* push mode seeking */ + GstClockTime push_kf_time; + GstClockTime push_sync_time; }; struct _GstOggPadClass @@ -161,6 +165,29 @@ struct _GstOggDemux gint64 basetime; gint64 prestime; + /* push mode seeking support */ + GMutex *push_lock; /* we need the lock to protect the push mode variables */ + gint64 push_byte_offset; /* where were are at in the stream, in bytes */ + gint64 push_byte_length; /* length in bytes of the stream, -1 if unknown */ + GstClockTime push_time_length; /* length in time of the stream */ + GstClockTime push_start_time; /* start time of the stream */ + GstClockTime push_time_offset; /* where were are at in the stream, in time */ + enum { PUSH_PLAYING, PUSH_DURATION, PUSH_BISECT1, PUSH_LINEAR1, PUSH_BISECT2, PUSH_LINEAR2 } push_state; + + GstClockTime push_seek_time_original_target; + GstClockTime push_seek_time_target; + gint64 push_last_seek_offset; + GstClockTime push_last_seek_time; + gint64 push_offset0, push_offset1; /* bisection search offset bounds */ + GstClockTime push_time0, push_time1; /* bisection search time bounds */ + + double push_seek_rate; + GstSeekFlags push_seek_flags; + GstEvent *push_mode_seek_delayed_event; + gboolean push_disable_seeking; + + gint push_bisection_steps[2]; + /* ogg stuff */ ogg_sync_state sync; }; diff --git a/ext/ogg/gstoggstream.c b/ext/ogg/gstoggstream.c index 912b3e3cc6..e20c568f26 100644 --- a/ext/ogg/gstoggstream.c +++ b/ext/ogg/gstoggstream.c @@ -1153,13 +1153,23 @@ gst_ogg_map_add_fisbone (GstOggStream * pad, GstOggStream * skel_pad, pad->have_fisbone = TRUE; - /* we just overwrite whatever was set before by the format-specific setup */ - pad->granulerate_n = GST_READ_UINT64_LE (data); - pad->granulerate_d = GST_READ_UINT64_LE (data + 8); + /* We don't overwrite whatever was set before by the format-specific + setup: skeleton contains wrong information sometimes, and the codec + headers are authoritative. + So we only gather information that was not already filled out by + the mapper setup. This should hopefully allow handling unknown + streams a bit better, while not trashing correct setup from bad + skeleton data. */ + if (pad->granulerate_n == 0 || pad->granulerate_d == 0) { + pad->granulerate_n = GST_READ_UINT64_LE (data); + pad->granulerate_d = GST_READ_UINT64_LE (data + 8); + } + if (pad->granuleshift < 0) { + pad->granuleshift = GST_READ_UINT8 (data + 28); + } start_granule = GST_READ_UINT64_LE (data + 16); pad->preroll = GST_READ_UINT32_LE (data + 24); - pad->granuleshift = GST_READ_UINT8 (data + 28); start_time = granulepos_to_granule_default (pad, start_granule); diff --git a/ext/pango/gstbasetextoverlay.c b/ext/pango/gstbasetextoverlay.c index 7df78f4112..936da8d0ab 100644 --- a/ext/pango/gstbasetextoverlay.c +++ b/ext/pango/gstbasetextoverlay.c @@ -999,6 +999,8 @@ gst_base_text_overlay_src_query (GstPad * pad, GstQuery * query) GstBaseTextOverlay *overlay = NULL; overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) + return FALSE; ret = gst_pad_peer_query (overlay->video_sinkpad, query); @@ -1014,6 +1016,10 @@ gst_base_text_overlay_src_event (GstPad * pad, GstEvent * event) GstBaseTextOverlay *overlay = NULL; overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) { + gst_event_unref (event); + return FALSE; + } switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK:{ @@ -1076,6 +1082,8 @@ gst_base_text_overlay_getcaps (GstPad * pad, GstCaps * filter) GstCaps *caps; overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) + return gst_caps_copy (gst_pad_get_pad_template_caps (pad)); if (pad == overlay->srcpad) otherpad = overlay->video_sinkpad; @@ -2102,6 +2110,8 @@ gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer) GstBaseTextOverlay *overlay; overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) + return GST_PAD_LINK_REFUSED; GST_DEBUG_OBJECT (overlay, "Text pad linked"); @@ -2134,6 +2144,10 @@ gst_base_text_overlay_text_event (GstPad * pad, GstEvent * event) GstBaseTextOverlay *overlay = NULL; overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) { + gst_event_unref (event); + return FALSE; + } GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); @@ -2224,6 +2238,10 @@ gst_base_text_overlay_video_event (GstPad * pad, GstEvent * event) GstBaseTextOverlay *overlay = NULL; overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) { + gst_event_unref (event); + return FALSE; + } GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); diff --git a/ext/theora/gsttheoraenc.c b/ext/theora/gsttheoraenc.c index c23c19715d..40cba12580 100644 --- a/ext/theora/gsttheoraenc.c +++ b/ext/theora/gsttheoraenc.c @@ -360,17 +360,17 @@ gst_theora_enc_class_init (GstTheoraEncClass * klass) THEORA_DEF_VP3_COMPATIBLE, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_DROP_FRAMES, - g_param_spec_boolean ("drop-frames", "VP3 Compatible", + g_param_spec_boolean ("drop-frames", "Drop Frames", "Allow or disallow frame dropping", THEORA_DEF_DROP_FRAMES, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CAP_OVERFLOW, - g_param_spec_boolean ("cap-overflow", "VP3 Compatible", + g_param_spec_boolean ("cap-overflow", "Cap Overflow", "Enable capping of bit reservoir overflows", THEORA_DEF_CAP_OVERFLOW, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CAP_UNDERFLOW, - g_param_spec_boolean ("cap-underflow", "VP3 Compatible", + g_param_spec_boolean ("cap-underflow", "Cap Underflow", "Enable capping of bit reservoir underflows", THEORA_DEF_CAP_UNDERFLOW, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); diff --git a/gst-libs/gst/audio/audio.h b/gst-libs/gst/audio/audio.h index 7f872477fe..b76badaf5f 100644 --- a/gst-libs/gst/audio/audio.h +++ b/gst-libs/gst/audio/audio.h @@ -290,6 +290,14 @@ struct _GstAudioInfo { #define GST_AUDIO_INFO_DEPTH(i) (GST_AUDIO_FORMAT_INFO_DEPTH((i)->finfo)) #define GST_AUDIO_INFO_BPS(info) (GST_AUDIO_INFO_DEPTH(info) >> 3) +#define GST_AUDIO_INFO_IS_INTEGER(i) (GST_AUDIO_FORMAT_INFO_IS_INTEGER((i)->finfo)) +#define GST_AUDIO_INFO_IS_FLOAT(i) (GST_AUDIO_FORMAT_INFO_IS_FLOAT((i)->finfo)) +#define GST_AUDIO_INFO_IS_SIGNED(i) (GST_AUDIO_FORMAT_INFO_IS_SIGNED((i)->finfo)) + +#define GST_AUDIO_INFO_ENDIANNESS(i) (GST_AUDIO_FORMAT_INFO_ENDIANNES((i)->finfo)) +#define GST_AUDIO_INFO_IS_LITTLE_ENDIAN(i) (GST_AUDIO_FORMAT_INFO_IS_LITTLE_ENDIAN((i)->finfo)) +#define GST_AUDIO_INFO_IS_BIG_ENDIAN(i) (GST_AUDIO_FORMAT_INFO_IS_BIG_ENDIAN((i)->finfo)) + #define GST_AUDIO_INFO_FLAGS(info) ((info)->flags) #define GST_AUDIO_INFO_HAS_DEFAULT_POSITIONS(info) ((info)->flags & GST_AUDIO_FLAG_DEFAULT_POSITIONS) diff --git a/gst-libs/gst/audio/gstaudiodecoder.c b/gst-libs/gst/audio/gstaudiodecoder.c index 0ed218686c..cf92dd47c5 100644 --- a/gst-libs/gst/audio/gstaudiodecoder.c +++ b/gst-libs/gst/audio/gstaudiodecoder.c @@ -54,7 +54,7 @@ * * As of configuration stage, and throughout processing, GstAudioDecoder * provides various (context) parameters, e.g. describing the format of - * output audio data (valid when output caps have been caps) or current parsing state. + * output audio data (valid when output caps have been set) or current parsing state. * Conversely, subclass can and should configure context to inform * base class of its expectation w.r.t. buffer handling. * diff --git a/gst-libs/gst/audio/gstaudioencoder.c b/gst-libs/gst/audio/gstaudioencoder.c index 532e183d95..5048905d73 100644 --- a/gst-libs/gst/audio/gstaudioencoder.c +++ b/gst-libs/gst/audio/gstaudioencoder.c @@ -941,6 +941,8 @@ audio_info_is_equal (GstAudioInfo * from, GstAudioInfo * to) { if (from == to) return TRUE; + if (from->finfo == NULL || to->finfo == NULL) + return FALSE; if (GST_AUDIO_INFO_FORMAT (from) != GST_AUDIO_INFO_FORMAT (to)) return FALSE; if (GST_AUDIO_INFO_RATE (from) != GST_AUDIO_INFO_RATE (to)) @@ -985,7 +987,7 @@ gst_audio_encoder_sink_setcaps (GstAudioEncoder * enc, GstCaps * caps) if (!gst_audio_info_from_caps (&state, caps)) goto refuse_caps; - changed = audio_info_is_equal (&state, &ctx->info); + changed = !audio_info_is_equal (&state, &ctx->info); if (changed) { GstClockTime old_min_latency; @@ -1086,6 +1088,18 @@ gst_audio_encoder_proxy_getcaps (GstAudioEncoder * enc, GstCaps * caps) gst_structure_set_value (s, "rate", val); if ((val = gst_structure_get_value (allowed_s, "channels"))) gst_structure_set_value (s, "channels", val); + /* following might also make sense for some encoded formats, + * e.g. wavpack */ + if ((val = gst_structure_get_value (allowed_s, "width"))) + gst_structure_set_value (s, "width", val); + if ((val = gst_structure_get_value (allowed_s, "depth"))) + gst_structure_set_value (s, "depth", val); + if ((val = gst_structure_get_value (allowed_s, "endianness"))) + gst_structure_set_value (s, "endianness", val); + if ((val = gst_structure_get_value (allowed_s, "signed"))) + gst_structure_set_value (s, "signed", val); + if ((val = gst_structure_get_value (allowed_s, "channel-positions"))) + gst_structure_set_value (s, "channel-positions", val); gst_caps_merge_structure (filter_caps, s); } diff --git a/gst-libs/gst/interfaces/colorbalance.c b/gst-libs/gst/interfaces/colorbalance.c index 9f6e07eb46..89fbfb3950 100644 --- a/gst-libs/gst/interfaces/colorbalance.c +++ b/gst-libs/gst/interfaces/colorbalance.c @@ -127,7 +127,11 @@ gst_color_balance_class_init (GstColorBalanceClass * klass) const GList * gst_color_balance_list_channels (GstColorBalance * balance) { - GstColorBalanceClass *klass = GST_COLOR_BALANCE_GET_CLASS (balance); + GstColorBalanceClass *klass; + + g_return_val_if_fail (GST_IS_COLOR_BALANCE (balance), NULL); + + klass = GST_COLOR_BALANCE_GET_CLASS (balance); if (klass->list_channels) { return klass->list_channels (balance); @@ -178,7 +182,11 @@ gint gst_color_balance_get_value (GstColorBalance * balance, GstColorBalanceChannel * channel) { - GstColorBalanceClass *klass = GST_COLOR_BALANCE_GET_CLASS (balance); + GstColorBalanceClass *klass; + + g_return_val_if_fail (GST_IS_COLOR_BALANCE (balance), 0); + + klass = GST_COLOR_BALANCE_GET_CLASS (balance); if (klass->get_value) { return klass->get_value (balance, channel); @@ -200,7 +208,12 @@ gst_color_balance_get_value (GstColorBalance * balance, GstColorBalanceType gst_color_balance_get_balance_type (GstColorBalance * balance) { - GstColorBalanceClass *klass = GST_COLOR_BALANCE_GET_CLASS (balance); + GstColorBalanceClass *klass; + + g_return_val_if_fail (GST_IS_COLOR_BALANCE (balance), + GST_COLOR_BALANCE_SOFTWARE); + + klass = GST_COLOR_BALANCE_GET_CLASS (balance); return klass->balance_type; } @@ -220,6 +233,9 @@ void gst_color_balance_value_changed (GstColorBalance * balance, GstColorBalanceChannel * channel, gint value) { + + g_return_if_fail (GST_IS_COLOR_BALANCE (balance)); + g_signal_emit (G_OBJECT (balance), gst_color_balance_signals[VALUE_CHANGED], 0, channel, value); diff --git a/gst-libs/gst/pbutils/gstdiscoverer.h b/gst-libs/gst/pbutils/gstdiscoverer.h index a0055777d5..611bc89419 100644 --- a/gst-libs/gst/pbutils/gstdiscoverer.h +++ b/gst-libs/gst/pbutils/gstdiscoverer.h @@ -258,7 +258,7 @@ struct _GstDiscoverer { struct _GstDiscovererClass { GObjectClass parentclass; - /*< signals >*/ + /* signals */ void (*finished) (GstDiscoverer *discoverer); void (*starting) (GstDiscoverer *discoverer); void (*discovered) (GstDiscoverer *discoverer, diff --git a/gst-libs/gst/rtsp/gstrtspdefs.c b/gst-libs/gst/rtsp/gstrtspdefs.c index 9935c670b2..ee74b9830c 100644 --- a/gst-libs/gst/rtsp/gstrtspdefs.c +++ b/gst-libs/gst/rtsp/gstrtspdefs.c @@ -177,6 +177,9 @@ static struct rtsp_header rtsp_headers[] = { {"X-Server-IP-Address", FALSE}, {"X-Sessioncookie", FALSE}, + /* Since 0.10.36 */ + {"RTCP-Interval", FALSE}, + {NULL, FALSE} }; diff --git a/gst-libs/gst/rtsp/gstrtspdefs.h b/gst-libs/gst/rtsp/gstrtspdefs.h index d67babd1c0..3727bf29a6 100644 --- a/gst-libs/gst/rtsp/gstrtspdefs.h +++ b/gst-libs/gst/rtsp/gstrtspdefs.h @@ -333,6 +333,9 @@ typedef enum { GST_RTSP_HDR_X_SERVER_IP_ADDRESS, /* X-Server-IP-Address */ GST_RTSP_HDR_X_SESSIONCOOKIE, /* X-Sessioncookie */ + /* Since 0.10.36 */ + GST_RTSP_HDR_RTCP_INTERVAL, /* RTCP-Interval */ + GST_RTSP_HDR_LAST } GstRTSPHeaderField; diff --git a/gst-libs/gst/tag/tag.h b/gst-libs/gst/tag/tag.h index 65fddd1760..69a3ba4d94 100644 --- a/gst-libs/gst/tag/tag.h +++ b/gst-libs/gst/tag/tag.h @@ -445,7 +445,8 @@ GType gst_tag_image_type_get_type (void); /** * GST_TAG_ID3V2_HEADER_SIZE: * - * ID3V2 header size considered minimum input for some functions. + * ID3V2 header size considered minimum input for some functions such as + * gst_tag_list_from_id3v2_tag() and gst_tag_get_id3v2_tag_size() for example. * * Since: 0.10.36 */ diff --git a/gst/adder/gstadder.c b/gst/adder/gstadder.c index 7ed48cd55f..d5f9907745 100644 --- a/gst/adder/gstadder.c +++ b/gst/adder/gstadder.c @@ -1174,8 +1174,7 @@ gst_adder_collected (GstCollectPads * pads, gpointer user_data) if (event) { if (!gst_pad_push_event (adder->srcpad, event)) { - GST_WARNING_OBJECT (adder->srcpad, "Sending event %p (%s) failed.", - event, GST_EVENT_TYPE_NAME (event)); + GST_WARNING_OBJECT (adder->srcpad, "Sending event failed"); } } else { GST_WARNING_OBJECT (adder->srcpad, "Creating new segment event for " diff --git a/gst/playback/gstdecodebin2.c b/gst/playback/gstdecodebin2.c index 1563bedef9..1c256e9651 100644 --- a/gst/playback/gstdecodebin2.c +++ b/gst/playback/gstdecodebin2.c @@ -426,7 +426,6 @@ static void gst_decode_group_free (GstDecodeGroup * group); static GstDecodeGroup *gst_decode_group_new (GstDecodeBin * dbin, GstDecodeChain * chain); static gboolean gst_decode_chain_is_complete (GstDecodeChain * chain); -static gboolean gst_decode_chain_handle_eos (GstDecodeChain * chain); static gboolean gst_decode_chain_expose (GstDecodeChain * chain, GList ** endpads, gboolean * missing_plugin); static gboolean gst_decode_chain_is_drained (GstDecodeChain * chain); @@ -1340,6 +1339,7 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, GstElementFactory *factory; const gchar *classification; gboolean is_parser_converter = FALSE; + gboolean res; GST_DEBUG_OBJECT (dbin, "Pad %s:%s caps:%" GST_PTR_FORMAT, GST_DEBUG_PAD_NAME (pad), caps); @@ -1384,10 +1384,17 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, dpad = gst_decode_pad_new (dbin, pad, chain); /* 1. Emit 'autoplug-continue' the result will tell us if this pads needs - * further autoplugging. */ - g_signal_emit (G_OBJECT (dbin), - gst_decode_bin_signals[SIGNAL_AUTOPLUG_CONTINUE], 0, dpad, caps, - &apcontinue); + * further autoplugging. Only do this for fixed caps, for unfixed caps + * we will later come here again from the notify::caps handler. The + * problem with unfixed caps is that we can reliably tell if the output + * is e.g. accepted by a sink because only parts of the possible final + * caps might be accepted by the sink. */ + if (gst_caps_is_fixed (caps)) + g_signal_emit (G_OBJECT (dbin), + gst_decode_bin_signals[SIGNAL_AUTOPLUG_CONTINUE], 0, dpad, caps, + &apcontinue); + else + apcontinue = TRUE; /* 1.a if autoplug-continue is FALSE or caps is a raw format, goto pad_is_final */ if ((!apcontinue) || are_final_caps (dbin, caps)) @@ -1448,7 +1455,7 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, /* At this point we have a potential decoder, but we might not need it * if it doesn't match the output caps */ - if (!dbin->expose_allstreams) { + if (!dbin->expose_allstreams && gst_caps_is_fixed (caps)) { guint i; const GList *tmps; gboolean dontuse = FALSE; @@ -1566,7 +1573,7 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, /* 1.h else continue autoplugging something from the list. */ GST_LOG_OBJECT (pad, "Let's continue discovery on this pad"); - connect_pad (dbin, src, dpad, pad, caps, factories, chain); + res = connect_pad (dbin, src, dpad, pad, caps, factories, chain); /* Need to unref the capsfilter srcpad here if * we inserted a capsfilter */ @@ -1576,6 +1583,9 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, gst_object_unref (dpad); g_value_array_free (factories); + if (!res) + goto unknown_type; + return; expose_pad: @@ -3033,95 +3043,152 @@ out: return complete; } +static gboolean +drain_and_switch_chains (GstDecodeChain * chain, GstDecodePad * drainpad, + gboolean * last_group, gboolean * drained, gboolean * switched); +/* drain_and_switch_chains/groups: + * + * CALL WITH CHAIN LOCK (or group parent) TAKEN ! + * + * Goes down the chains/groups until it finds the chain + * to which the drainpad belongs. + * + * It marks that pad/chain as drained and then will figure + * out which group to switch to or not. + * + * last_chain will be set to TRUE if the group to which the + * pad belongs is the last one. + * + * drained will be set to TRUE if the chain/group is drained. + * + * Returns: TRUE if the chain contained the target pad */ +static gboolean +drain_and_switch_group (GstDecodeGroup * group, GstDecodePad * drainpad, + gboolean * last_group, gboolean * drained, gboolean * switched) +{ + gboolean handled = FALSE; + gboolean alldrained = TRUE; + GList *tmp; + + GST_DEBUG ("Checking group %p (target pad %s:%s)", + group, GST_DEBUG_PAD_NAME (drainpad)); + + /* Definitely can't be in drained groups */ + if (G_UNLIKELY (group->drained)) { + goto beach; + } + + /* Figure out if all our chains are drained with the + * new information */ + for (tmp = group->children; tmp; tmp = tmp->next) { + GstDecodeChain *chain = (GstDecodeChain *) tmp->data; + gboolean subdrained = FALSE; + + handled |= + drain_and_switch_chains (chain, drainpad, last_group, &subdrained, + switched); + if (!subdrained) + alldrained = FALSE; + } + +beach: + GST_DEBUG ("group %p (last_group:%d, drained:%d, switched:%d, handled:%d)", + group, *last_group, alldrained, *switched, handled); + *drained = alldrained; + return handled; +} + +static gboolean +drain_and_switch_chains (GstDecodeChain * chain, GstDecodePad * drainpad, + gboolean * last_group, gboolean * drained, gboolean * switched) +{ + gboolean handled = FALSE; + GstDecodeBin *dbin = chain->dbin; + + GST_DEBUG ("Checking chain %p (target pad %s:%s)", + chain, GST_DEBUG_PAD_NAME (drainpad)); + + CHAIN_MUTEX_LOCK (chain); + + if (chain->endpad) { + /* Check if we're reached the target endchain */ + if (chain == drainpad->chain) { + GST_DEBUG ("Found the target chain"); + drainpad->drained = TRUE; + handled = TRUE; + } + + *drained = chain->endpad->drained; + goto beach; + } + + /* We known there are groups to switch to */ + if (chain->next_groups) + *last_group = FALSE; + + /* Check the active group */ + if (chain->active_group) { + gboolean subdrained = FALSE; + handled = drain_and_switch_group (chain->active_group, drainpad, + last_group, &subdrained, switched); + + /* The group is drained, see if we can switch to another */ + if (handled && subdrained && !*switched) { + if (chain->next_groups) { + /* Switch to next group */ + GST_DEBUG_OBJECT (dbin, "Hiding current group %p", chain->active_group); + gst_decode_group_hide (chain->active_group); + chain->old_groups = + g_list_prepend (chain->old_groups, chain->active_group); + GST_DEBUG_OBJECT (dbin, "Switching to next group %p", + chain->next_groups->data); + chain->active_group = chain->next_groups->data; + chain->next_groups = + g_list_delete_link (chain->next_groups, chain->next_groups); + *switched = TRUE; + *drained = FALSE; + } else { + GST_DEBUG ("Group %p was the last in chain %p", chain->active_group, + chain); + *drained = TRUE; + /* We're drained ! */ + } + } + } + +beach: + CHAIN_MUTEX_UNLOCK (chain); + + GST_DEBUG ("Chain %p (handled:%d, last_group:%d, drained:%d, switched:%d)", + chain, handled, *last_group, *drained, *switched); + + return handled; +} + /* check if the group is drained, meaning all pads have seen an EOS * event. */ static gboolean gst_decode_pad_handle_eos (GstDecodePad * pad) { + gboolean last_group = TRUE; + gboolean switched = FALSE; + gboolean drained = FALSE; GstDecodeChain *chain = pad->chain; + GstDecodeBin *dbin = chain->dbin; - GST_LOG_OBJECT (pad->dbin, "chain : %p, pad %p", chain, pad); - pad->drained = TRUE; - return gst_decode_chain_handle_eos (chain); -} + GST_LOG_OBJECT (dbin, "pad %p", pad); + drain_and_switch_chains (dbin->decode_chain, pad, &last_group, &drained, + &switched); -/* gst_decode_chain_handle_eos: - * - * Checks if there are next groups in any parent chain - * to which we can switch or if everything is drained. - * - * If there are groups to switch to, hide the current active - * one and expose the new one. - * - * If a group isn't completely drained (i.e. we received EOS - * only on one of the streams) this function will return FALSE - * to indicate the EOS on the given chain should be dropped - * to avoid it from going downstream. - * - * MT-safe, don't call with chain lock! - */ -static gboolean -gst_decode_chain_handle_eos (GstDecodeChain * eos_chain) -{ - GstDecodeBin *dbin = eos_chain->dbin; - GstDecodeGroup *group; - GstDecodeChain *chain = eos_chain; - gboolean drained; - gboolean forward_eos = TRUE; - - g_return_val_if_fail (eos_chain->endpad, TRUE); - - CHAIN_MUTEX_LOCK (chain); - while ((group = chain->parent)) { - CHAIN_MUTEX_UNLOCK (chain); - chain = group->parent; - CHAIN_MUTEX_LOCK (chain); - - if (gst_decode_group_is_drained (group)) { - continue; - } - break; - } - - drained = chain->active_group ? - gst_decode_group_is_drained (chain->active_group) : TRUE; - - /* Now either group == NULL and chain == dbin->decode_chain - * or chain is the lowest chain that has a non-drained group */ - if (chain->active_group && drained && chain->next_groups) { - /* There's an active group which is drained and we have another - * one to switch to. */ - GST_DEBUG_OBJECT (dbin, "Hiding current group %p", chain->active_group); - gst_decode_group_hide (chain->active_group); - chain->old_groups = g_list_prepend (chain->old_groups, chain->active_group); - GST_DEBUG_OBJECT (dbin, "Switching to next group %p", - chain->next_groups->data); - chain->active_group = chain->next_groups->data; - chain->next_groups = - g_list_delete_link (chain->next_groups, chain->next_groups); - CHAIN_MUTEX_UNLOCK (chain); + if (switched) { + /* If we resulted in a group switch, expose what's needed */ EXPOSE_LOCK (dbin); if (gst_decode_chain_is_complete (dbin->decode_chain)) gst_decode_bin_expose (dbin); EXPOSE_UNLOCK (dbin); - } else if (!chain->active_group || drained) { - /* The group is drained and there isn't a future one */ - g_assert (chain == dbin->decode_chain); - CHAIN_MUTEX_UNLOCK (chain); - - GST_LOG_OBJECT (dbin, "all groups drained, fire signal"); - g_signal_emit (G_OBJECT (dbin), gst_decode_bin_signals[SIGNAL_DRAINED], 0, - NULL); - } else { - CHAIN_MUTEX_UNLOCK (chain); - GST_DEBUG_OBJECT (dbin, - "Current active group in chain %p is not drained yet", chain); - /* Instruct caller to drop EOS event if we have future groups */ - if (chain->next_groups) - forward_eos = FALSE; } - return forward_eos; + return last_group; } /* gst_decode_group_is_drained: diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index 16e35eeced..5c599c6c10 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -1272,6 +1272,34 @@ gst_play_bin_finalize (GObject * object) G_OBJECT_CLASS (parent_class)->finalize (object); } +static gboolean +gst_playbin_uri_is_valid (GstPlayBin * playbin, const gchar * uri) +{ + const gchar *c; + + GST_LOG_OBJECT (playbin, "checking uri '%s'", uri); + + /* this just checks the protocol */ + if (!gst_uri_is_valid (uri)) + return FALSE; + + for (c = uri; *c != '\0'; ++c) { + if (!g_ascii_isprint (*c)) + goto invalid; + if (*c == ' ') + goto invalid; + } + + return TRUE; + +invalid: + { + GST_WARNING_OBJECT (playbin, "uri '%s' not valid, character #%u", + uri, (guint) ((guintptr) c - (guintptr) uri)); + return FALSE; + } +} + static void gst_play_bin_set_uri (GstPlayBin * playbin, const gchar * uri) { @@ -1282,6 +1310,17 @@ gst_play_bin_set_uri (GstPlayBin * playbin, const gchar * uri) return; } + if (!gst_playbin_uri_is_valid (playbin, uri)) { + if (g_str_has_prefix (uri, "file:")) { + GST_ERROR_OBJECT (playbin, "malformed file URI '%s' - make sure to " + "escape spaces and non-ASCII characters properly and specify an " + "absolute path. Use gst_filename_to_uri() to convert filenames " + "to URIs", uri); + } else { + GST_ERROR_OBJECT (playbin, "malformed URI '%s'", uri); + } + } + GST_PLAY_BIN_LOCK (playbin); group = playbin->next_group; diff --git a/gst/playback/gstplaysink.c b/gst/playback/gstplaysink.c index b653b760c5..76e6e3adc8 100644 --- a/gst/playback/gstplaysink.c +++ b/gst/playback/gstplaysink.c @@ -2113,8 +2113,7 @@ gst_play_sink_reconfigure (GstPlaySink * playsink) GST_OBJECT_UNLOCK (playsink); /* figure out which components we need */ - if (flags & GST_PLAY_FLAG_TEXT && playsink->video_pad_raw - && playsink->text_pad) { + if (flags & GST_PLAY_FLAG_TEXT && playsink->text_pad) { /* we have subtitles and we are requested to show it */ need_text = TRUE; } diff --git a/gst/playback/gstsubtitleoverlay.c b/gst/playback/gstsubtitleoverlay.c index 78ff733b9c..4d4573ed33 100644 --- a/gst/playback/gstsubtitleoverlay.c +++ b/gst/playback/gstsubtitleoverlay.c @@ -227,6 +227,31 @@ static const gchar *_sub_pad_names[] = { "subpicture", "subpicture_sink", "subtitle_sink", "subtitle" }; +static inline gboolean +_is_raw_video (GstStructure * s) +{ + const gchar *name; + + name = gst_structure_get_name (s); + + if (g_str_has_prefix (name, "video/x-raw")) + return TRUE; + return FALSE; +} + +static gboolean +_is_raw_video_pad (GstPad * pad) +{ + GstCaps *caps = gst_pad_get_current_caps (pad); + gboolean raw; + + raw = _is_raw_video (gst_caps_get_structure (caps, 0)); + + gst_caps_unref (caps); + + return raw; +} + static GstCaps * _get_sub_caps (GstElementFactory * factory) { @@ -1075,6 +1100,7 @@ _pad_blocked_cb (GstPad * pad, GstProbeType type, gpointer type_data, } else { const gchar *name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory)); + gboolean is_raw_video = _is_raw_video_pad (self->video_sinkpad); if (strcmp (name, "textoverlay") == 0) { /* Set some textoverlay specific properties */ @@ -1094,66 +1120,123 @@ _pad_blocked_cb (GstPad * pad, GstProbeType type, gpointer type_data, g_object_set (self->renderer, "font-desc", self->font_desc, NULL); } - /* First link everything internally */ - if (G_UNLIKELY (!_create_element (self, &self->post_colorspace, - COLORSPACE, NULL, "post-colorspace", FALSE))) { - continue; - } + if (is_raw_video) { + /* First check that renderer also supports raw video */ + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); + continue; + } - src = gst_element_get_static_pad (element, "src"); - if (G_UNLIKELY (!src)) { - GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); - continue; - } + if (G_UNLIKELY (!_is_raw_video_pad (sink))) { + GST_DEBUG_OBJECT (self, "Renderer doesn't support raw video"); + gst_object_unref (sink); + continue; + } + gst_object_unref (sink); - sink = gst_element_get_static_pad (self->post_colorspace, "sink"); - if (G_UNLIKELY (!sink)) { - GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); - gst_object_unref (src); - continue; - } + /* First link everything internally */ + if (G_UNLIKELY (!_create_element (self, &self->post_colorspace, + COLORSPACE, NULL, "post-colorspace", FALSE))) { + continue; + } + src = gst_element_get_static_pad (element, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); + continue; + } - if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { - GST_WARNING_OBJECT (self, "Can't link renderer with " COLORSPACE); + sink = gst_element_get_static_pad (self->post_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); + gst_object_unref (src); + continue; + } + + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link renderer with " COLORSPACE); + gst_object_unref (src); + gst_object_unref (sink); + continue; + } gst_object_unref (src); gst_object_unref (sink); - continue; - } - gst_object_unref (src); - gst_object_unref (sink); - if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace, - COLORSPACE, NULL, "pre-colorspace", FALSE))) { - continue; - } + if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace, + COLORSPACE, NULL, "pre-colorspace", FALSE))) { + continue; + } - sink = _get_video_pad (element); - if (G_UNLIKELY (!sink)) { - GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); - continue; - } + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); + continue; + } - src = gst_element_get_static_pad (self->pre_colorspace, "src"); - if (G_UNLIKELY (!src)) { - GST_WARNING_OBJECT (self, "Can't get srcpad from " COLORSPACE); - gst_object_unref (sink); - continue; - } + src = gst_element_get_static_pad (self->pre_colorspace, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get srcpad from " COLORSPACE); + gst_object_unref (sink); + continue; + } - if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { - GST_WARNING_OBJECT (self, "Can't link " COLORSPACE " to renderer"); + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link " COLORSPACE " to renderer"); + gst_object_unref (src); + gst_object_unref (sink); + continue; + } gst_object_unref (src); gst_object_unref (sink); - continue; - } - gst_object_unref (src); - gst_object_unref (sink); - /* Set src ghostpad target */ - src = gst_element_get_static_pad (self->post_colorspace, "src"); - if (G_UNLIKELY (!src)) { - GST_WARNING_OBJECT (self, "Can't get src pad from " COLORSPACE); - continue; + /* Set src ghostpad target */ + src = gst_element_get_static_pad (self->post_colorspace, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from " COLORSPACE); + continue; + } + } else { /* No raw video pad */ + GstCaps *allowed_caps, *video_caps = NULL; + GstPad *video_peer; + gboolean can_intersect = FALSE; + + video_peer = gst_pad_get_peer (self->video_sinkpad); + if (video_peer) { + video_caps = gst_pad_get_current_caps (video_peer); + if (!video_caps) { + video_caps = gst_pad_get_caps (video_peer, NULL); + } + gst_object_unref (video_peer); + } + + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); + continue; + } + allowed_caps = gst_pad_get_caps (sink, NULL); + gst_object_unref (sink); + + if (allowed_caps && video_caps) + can_intersect = gst_caps_can_intersect (allowed_caps, video_caps); + + if (allowed_caps) + gst_caps_unref (allowed_caps); + + if (video_caps) + gst_caps_unref (video_caps); + + if (G_UNLIKELY (!can_intersect)) { + GST_WARNING_OBJECT (self, "Renderer with custom caps is not " + "compatible with video stream"); + continue; + } + + src = gst_element_get_static_pad (element, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); + continue; + } } if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST @@ -1199,10 +1282,19 @@ _pad_blocked_cb (GstPad * pad, GstProbeType type, gpointer type_data, } /* Set the sink ghostpad targets */ - sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); - if (G_UNLIKELY (!sink)) { - GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); - continue; + if (self->pre_colorspace) { + sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); + continue; + } + } else { + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from %" GST_PTR_FORMAT, + element); + continue; + } } if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST @@ -1606,6 +1698,7 @@ static gboolean gst_subtitle_overlay_video_sink_setcaps (GstSubtitleOverlay * self, GstCaps * caps) { + GstPad *target; gboolean ret = TRUE; GstVideoInfo info; @@ -1614,9 +1707,21 @@ gst_subtitle_overlay_video_sink_setcaps (GstSubtitleOverlay * self, if (!gst_video_info_from_caps (&info, caps)) { GST_ERROR_OBJECT (self, "Failed to parse caps"); ret = FALSE; + GST_SUBTITLE_OVERLAY_UNLOCK (self); goto out; } + target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->video_sinkpad)); + + GST_SUBTITLE_OVERLAY_LOCK (self); + + if (!target || !gst_pad_accept_caps (target, caps)) { + GST_DEBUG_OBJECT (target, "Target did not accept caps -- reconfiguring"); + + block_subtitle (self); + block_video (self); + } + GST_SUBTITLE_OVERLAY_LOCK (self); if (self->fps_n != info.fps_n || self->fps_d != info.fps_d) { GST_DEBUG_OBJECT (self, "New video fps: %d/%d", info.fps_n, info.fps_d); @@ -1627,6 +1732,8 @@ gst_subtitle_overlay_video_sink_setcaps (GstSubtitleOverlay * self, GST_SUBTITLE_OVERLAY_UNLOCK (self); out: + if (target) + gst_object_unref (target); return ret; } diff --git a/gst/subparse/gstsubparse.c b/gst/subparse/gstsubparse.c index c910cb2439..2a12d96ccd 100644 --- a/gst/subparse/gstsubparse.c +++ b/gst/subparse/gstsubparse.c @@ -1211,9 +1211,9 @@ gst_sub_parse_data_format_autodetect_regex_once (GstSubParseRegex regtype) } break; case GST_SUB_PARSE_REGEX_SUBRIP: - result = (gpointer) g_regex_new ("^([ 0-9]){0,3}[0-9]\\s*(\x0d)?\x0a" - "[ 0-9][0-9]:[ 0-9][0-9]:[ 0-9][0-9][,.][ 0-9]{0,2}[0-9]" - " +--> +([ 0-9])?[0-9]:[ 0-9][0-9]:[ 0-9][0-9][,.][ 0-9]{0,2}[0-9]", + result = (gpointer) g_regex_new ("^ {0,3}[ 0-9]{1,4}\\s*(\x0d)?\x0a" + " ?[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,3}" + " +--> +[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,2}", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr); if (result == NULL) { g_warning ("Compilation of subrip regex failed: %s", gerr->message); diff --git a/gst/typefind/gsttypefindfunctions.c b/gst/typefind/gsttypefindfunctions.c index b9dd5d9cd1..31f516e606 100644 --- a/gst/typefind/gsttypefindfunctions.c +++ b/gst/typefind/gsttypefindfunctions.c @@ -2887,6 +2887,13 @@ mod_type_find (GstTypeFind * tf, gpointer unused) return; } } + /* AMF */ + if ((data = gst_type_find_peek (tf, 0, 19)) != NULL) { + if (memcmp (data, "ASYLUM Music Format", 19) == 0) { + gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, MOD_CAPS); + return; + } + } } /*** application/x-shockwave-flash ***/ diff --git a/gst/videorate/gstvideorate.c b/gst/videorate/gstvideorate.c index 9d0938fff9..3317af2521 100644 --- a/gst/videorate/gstvideorate.c +++ b/gst/videorate/gstvideorate.c @@ -88,6 +88,7 @@ enum #define DEFAULT_SKIP_TO_FIRST FALSE #define DEFAULT_DROP_ONLY FALSE #define DEFAULT_AVERAGE_PERIOD 0 +#define DEFAULT_MAX_RATE G_MAXINT enum { @@ -100,7 +101,8 @@ enum ARG_NEW_PREF, ARG_SKIP_TO_FIRST, ARG_DROP_ONLY, - ARG_AVERAGE_PERIOD + ARG_AVERAGE_PERIOD, + ARG_MAX_RATE /* FILL ME */ }; @@ -232,6 +234,20 @@ gst_video_rate_class_init (GstVideoRateClass * klass) 0, G_MAXINT64, DEFAULT_AVERAGE_PERIOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstVideoRate:max-rate: + * + * maximum framerate to pass through + * + * Since: 0.10.36 + */ + g_object_class_install_property (object_class, ARG_MAX_RATE, + g_param_spec_int ("max-rate", "maximum framerate", + "Maximum framerate allowed to pass through " + "(in frames per second, implies drop-only)", + 1, G_MAXINT, DEFAULT_MAX_RATE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + gst_element_class_set_details_simple (element_class, "Video rate adjuster", "Filter/Effect/Video", "Drops/duplicates/adjusts timestamps on video frames to make a perfect stream", @@ -241,28 +257,186 @@ gst_video_rate_class_init (GstVideoRateClass * klass) gst_static_pad_template_get (&gst_video_rate_sink_template)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&gst_video_rate_src_template)); +} +static void +gst_value_fraction_get_extremes (const GValue * v, + gint * min_num, gint * min_denom, gint * max_num, gint * max_denom) +{ + if (GST_VALUE_HOLDS_FRACTION (v)) { + *min_num = *max_num = gst_value_get_fraction_numerator (v); + *min_denom = *max_denom = gst_value_get_fraction_denominator (v); + } else if (GST_VALUE_HOLDS_FRACTION_RANGE (v)) { + const GValue *min, *max; + + min = gst_value_get_fraction_range_min (v); + *min_num = gst_value_get_fraction_numerator (min); + *min_denom = gst_value_get_fraction_denominator (min); + + max = gst_value_get_fraction_range_max (v); + *max_num = gst_value_get_fraction_numerator (max); + *max_denom = gst_value_get_fraction_denominator (max); + } else if (GST_VALUE_HOLDS_LIST (v)) { + gint min_n = G_MAXINT, min_d = 1, max_n = 0, max_d = 1; + int i, n; + + *min_num = G_MAXINT; + *min_denom = 1; + *max_num = 0; + *max_denom = 1; + + n = gst_value_list_get_size (v); + + g_assert (n > 0); + + for (i = 0; i < n; i++) { + const GValue *t = gst_value_list_get_value (v, i); + + gst_value_fraction_get_extremes (t, &min_n, &min_d, &max_n, &max_d); + if (gst_util_fraction_compare (min_n, min_d, *min_num, *min_denom) < 0) { + *min_num = min_n; + *min_denom = min_d; + } + + if (gst_util_fraction_compare (max_n, max_d, *max_num, *max_denom) > 0) { + *max_num = max_n; + *max_denom = max_d; + } + } + } else { + g_warning ("Unknown type for framerate"); + *min_num = 0; + *min_denom = 1; + *max_num = G_MAXINT; + *max_denom = 1; + } +} + +/* Clamp the framerate in a caps structure to be a smaller range then + * [1...max_rate], otherwise return false */ +static gboolean +gst_video_max_rate_clamp_structure (GstStructure * s, gint maxrate, + gint * min_num, gint * min_denom, gint * max_num, gint * max_denom) +{ + gboolean ret = FALSE; + + if (!gst_structure_has_field (s, "framerate")) { + /* No framerate field implies any framerate, clamping would result in + * [1..max_rate] so not a real subset */ + goto out; + } else { + const GValue *v; + GValue intersection = { 0, }; + GValue clamp = { 0, }; + gint tmp_num, tmp_denom; + + g_value_init (&clamp, GST_TYPE_FRACTION_RANGE); + gst_value_set_fraction_range_full (&clamp, 0, 1, maxrate, 1); + + v = gst_structure_get_value (s, "framerate"); + ret = gst_value_intersect (&intersection, v, &clamp); + g_value_unset (&clamp); + + if (!ret) + goto out; + + gst_value_fraction_get_extremes (&intersection, + min_num, min_denom, max_num, max_denom); + + gst_value_fraction_get_extremes (v, + &tmp_num, &tmp_denom, max_num, max_denom); + + if (gst_util_fraction_compare (*max_num, *max_denom, maxrate, 1) > 0) { + *max_num = maxrate; + *max_denom = 1; + } + + gst_structure_take_value (s, "framerate", &intersection); + } + +out: + return ret; } static GstCaps * gst_video_rate_transform_caps (GstBaseTransform * trans, GstPadDirection direction, GstCaps * caps, GstCaps * filter) { + GstVideoRate *videorate = GST_VIDEO_RATE (trans); GstCaps *ret; - GstStructure *s; + GstStructure *s, *s2; + GstStructure *s3 = NULL; + int maxrate = g_atomic_int_get (&videorate->max_rate); /* Should always be called with simple caps */ g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), NULL); ret = gst_caps_copy (caps); - s = gst_structure_copy (gst_caps_get_structure (caps, 0)); + s = gst_caps_get_structure (ret, 0); + s2 = gst_structure_copy (s); - /* set the framerate as a range */ - gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, - G_MAXINT, 1, NULL); + if (videorate->drop_only) { + gint min_num = 0, min_denom = 1; + gint max_num = G_MAXINT, max_denom = 1; - gst_caps_append_structure (ret, s); + /* Clamp the caps to our maximum rate as the first caps if possible */ + if (!gst_video_max_rate_clamp_structure (s, maxrate, + &min_num, &min_denom, &max_num, &max_denom)) { + min_num = 0; + min_denom = 1; + max_num = maxrate; + max_denom = 1; + + /* clamp wouldn't be a real subset of 1..maxrate, in this case the sink + * caps should become [1..maxrate], [1..maxint] and the src caps just + * [1..maxrate]. In case there was a caps incompatibility things will + * explode later as appropriate :) + * + * In case [X..maxrate] == [X..maxint], skip as we'll set it later + */ + if (direction == GST_PAD_SRC && maxrate != G_MAXINT) + gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, + min_num, min_denom, maxrate, 1, NULL); + else + gst_caps_remove_structure (ret, 0); + } + + if (direction == GST_PAD_SRC) { + /* We can accept anything as long as it's at least the minimal framerate + * the the sink needs */ + gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, + min_num, min_denom, G_MAXINT, 1, NULL); + + /* Also allow unknown framerate, if it isn't already */ + if (min_num != 0 || min_denom != 1) { + s3 = gst_structure_copy (s); + gst_structure_set (s3, "framerate", GST_TYPE_FRACTION, 0, 1, NULL); + } + } else if (max_num != 0 || max_denom != 1) { + /* We can provide everything upto the maximum framerate at the src */ + gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, + 0, 1, max_num, max_denom, NULL); + } + } else if (direction == GST_PAD_SINK) { + gint min_num = 0, min_denom = 1; + gint max_num = G_MAXINT, max_denom = 1; + + if (!gst_video_max_rate_clamp_structure (s, maxrate, + &min_num, &min_denom, &max_num, &max_denom)) + gst_caps_remove_structure (ret, 0); + + gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, + maxrate, 1, NULL); + } else { + /* set the framerate as a range */ + gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, + G_MAXINT, 1, NULL); + } + + gst_caps_merge_structure (ret, s2); + if (s3 != NULL) + gst_caps_merge_structure (ret, s3); return ret; } @@ -375,6 +549,7 @@ gst_video_rate_init (GstVideoRate * videorate) videorate->drop_only = DEFAULT_DROP_ONLY; videorate->average_period = DEFAULT_AVERAGE_PERIOD; videorate->average_period_set = DEFAULT_AVERAGE_PERIOD; + videorate->max_rate = DEFAULT_MAX_RATE; videorate->from_rate_numerator = 0; videorate->from_rate_denominator = 0; @@ -605,7 +780,6 @@ format_error: { GST_WARNING_OBJECT (videorate, "Got segment but doesn't have GST_FORMAT_TIME value"); - gst_event_unref (event); return FALSE; } } @@ -960,15 +1134,25 @@ gst_video_rate_set_property (GObject * object, break; case ARG_DROP_ONLY: videorate->drop_only = g_value_get_boolean (value); + goto reconfigure; break; case ARG_AVERAGE_PERIOD: videorate->average_period_set = g_value_get_uint64 (value); break; + case ARG_MAX_RATE: + g_atomic_int_set (&videorate->max_rate, g_value_get_int (value)); + goto reconfigure; + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (videorate); + return; + +reconfigure: + GST_OBJECT_UNLOCK (videorate); + gst_base_transform_reconfigure (GST_BASE_TRANSFORM (videorate)); } static void @@ -1006,6 +1190,9 @@ gst_video_rate_get_property (GObject * object, case ARG_AVERAGE_PERIOD: g_value_set_uint64 (value, videorate->average_period_set); break; + case ARG_MAX_RATE: + g_value_set_int (value, g_atomic_int_get (&videorate->max_rate)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/gst/videorate/gstvideorate.h b/gst/videorate/gstvideorate.h index a8e1de1db5..998d6daa9a 100644 --- a/gst/videorate/gstvideorate.h +++ b/gst/videorate/gstvideorate.h @@ -76,6 +76,8 @@ struct _GstVideoRate gboolean skip_to_first; gboolean drop_only; guint64 average_period_set; + + volatile int max_rate; }; struct _GstVideoRateClass diff --git a/tests/check/elements/decodebin2.c b/tests/check/elements/decodebin2.c index f015ef2cb4..784c0e554b 100644 --- a/tests/check/elements/decodebin2.c +++ b/tests/check/elements/decodebin2.c @@ -27,7 +27,6 @@ #include #include -#include #include static const gchar dummytext[] = @@ -370,15 +369,15 @@ static GType gst_fake_h264_decoder_get_type (void); #undef parent_class #define parent_class fake_h264_parser_parent_class typedef struct _GstFakeH264Parser GstFakeH264Parser; -typedef GstBaseTransformClass GstFakeH264ParserClass; +typedef GstElementClass GstFakeH264ParserClass; struct _GstFakeH264Parser { - GstBaseTransform parent; + GstElement parent; }; -GST_BOILERPLATE (GstFakeH264Parser, gst_fake_h264_parser, GstBaseTransform, - GST_TYPE_BASE_TRANSFORM); +GST_BOILERPLATE (GstFakeH264Parser, gst_fake_h264_parser, GstElement, + GST_TYPE_ELEMENT); static void gst_fake_h264_parser_base_init (gpointer klass) @@ -400,89 +399,87 @@ gst_fake_h264_parser_base_init (gpointer klass) "FakeH264Parser", "Codec/Parser/Converter/Video", "yep", "me"); } -static GstFlowReturn -gst_fake_h264_parser_transform (GstBaseTransform * trans, GstBuffer * inbuf, - GstBuffer * outbuf) -{ - return GST_FLOW_OK; -} - -static GstCaps * -gst_fake_h264_parser_transform_caps (GstBaseTransform * trans, - GstPadDirection direction, GstCaps * caps) -{ - if (direction == GST_PAD_SRC) - return gst_caps_from_string ("video/x-h264"); - else - return gst_caps_from_string ("video/x-h264, " - "stream-format=(string) { avc, byte-stream }"); -} - -static gboolean -gst_fake_h264_parser_get_unit_size (GstBaseTransform * trans, GstCaps * caps, - guint * size) -{ - *size = 1; - return TRUE; -} - -static gboolean -gst_fake_h264_parser_set_caps (GstBaseTransform * trans, GstCaps * incaps, - GstCaps * outcaps) -{ - GstStructure *s; - const gchar *stream_format; - - s = gst_caps_get_structure (incaps, 0); - fail_unless (gst_structure_has_name (s, "video/x-h264")); - - s = gst_caps_get_structure (outcaps, 0); - fail_unless (gst_structure_has_name (s, "video/x-h264")); - stream_format = gst_structure_get_string (s, "stream-format"); - fail_unless_equals_string ("byte-stream", stream_format); - - return TRUE; -} - -static GstFlowReturn -gst_fake_h264_parser_prepare_output_buffer (GstBaseTransform * trans, - GstBuffer * inbuf, gint size, GstCaps * caps, GstBuffer ** outbuf) -{ - *outbuf = gst_buffer_ref (inbuf); - return GST_FLOW_OK; -} - static void gst_fake_h264_parser_class_init (GstFakeH264ParserClass * klass) { - GstBaseTransformClass *basetrans_class = (GstBaseTransformClass *) klass; +} - basetrans_class->transform = gst_fake_h264_parser_transform; - basetrans_class->transform_caps = gst_fake_h264_parser_transform_caps; - basetrans_class->get_unit_size = gst_fake_h264_parser_get_unit_size; - basetrans_class->set_caps = gst_fake_h264_parser_set_caps; - basetrans_class->prepare_output_buffer = - gst_fake_h264_parser_prepare_output_buffer; +static gboolean +gst_fake_h264_parser_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstElement *self = GST_ELEMENT (gst_pad_get_parent (pad)); + GstPad *otherpad = gst_element_get_static_pad (self, "src"); + GstCaps *accepted_caps; + GstStructure *s; + const gchar *stream_format; + + accepted_caps = gst_pad_get_allowed_caps (otherpad); + accepted_caps = gst_caps_make_writable (accepted_caps); + gst_caps_truncate (accepted_caps); + + s = gst_caps_get_structure (accepted_caps, 0); + stream_format = gst_structure_get_string (s, "stream-format"); + if (!stream_format) + gst_structure_set (s, "stream-format", G_TYPE_STRING, "avc", NULL); + + gst_pad_set_caps (otherpad, accepted_caps); + gst_caps_unref (accepted_caps); + + gst_object_unref (otherpad); + gst_object_unref (self); + + return TRUE; +} + +static GstFlowReturn +gst_fake_h264_parser_sink_chain (GstPad * pad, GstBuffer * buf) +{ + GstElement *self = GST_ELEMENT (gst_pad_get_parent (pad)); + GstPad *otherpad = gst_element_get_static_pad (self, "src"); + GstFlowReturn ret = GST_FLOW_OK; + + buf = gst_buffer_make_metadata_writable (buf); + gst_buffer_set_caps (buf, GST_PAD_CAPS (otherpad)); + + ret = gst_pad_push (otherpad, buf); + + gst_object_unref (otherpad); + gst_object_unref (self); + + return ret; } static void gst_fake_h264_parser_init (GstFakeH264Parser * self, GstFakeH264ParserClass * klass) { + GstPad *pad; + + pad = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (self), "sink"), "sink"); + gst_pad_set_setcaps_function (pad, gst_fake_h264_parser_sink_setcaps); + gst_pad_set_chain_function (pad, gst_fake_h264_parser_sink_chain); + gst_element_add_pad (GST_ELEMENT (self), pad); + + pad = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (self), "src"), "src"); + gst_element_add_pad (GST_ELEMENT (self), pad); } #undef parent_class #define parent_class fake_h264_decoder_parent_class typedef struct _GstFakeH264Decoder GstFakeH264Decoder; -typedef GstBaseTransformClass GstFakeH264DecoderClass; +typedef GstElementClass GstFakeH264DecoderClass; struct _GstFakeH264Decoder { - GstBaseTransform parent; + GstElement parent; }; -GST_BOILERPLATE (GstFakeH264Decoder, gst_fake_h264_decoder, GstBaseTransform, - GST_TYPE_BASE_TRANSFORM); +GST_BOILERPLATE (GstFakeH264Decoder, gst_fake_h264_decoder, GstElement, + GST_TYPE_ELEMENT); static void gst_fake_h264_decoder_base_init (gpointer klass) @@ -503,75 +500,62 @@ gst_fake_h264_decoder_base_init (gpointer klass) "FakeH264Decoder", "Codec/Decoder/Video", "yep", "me"); } -static GstFlowReturn -gst_fake_h264_decoder_transform (GstBaseTransform * trans, GstBuffer * inbuf, - GstBuffer * outbuf) -{ - return GST_FLOW_OK; -} - -static GstCaps * -gst_fake_h264_decoder_transform_caps (GstBaseTransform * trans, - GstPadDirection direction, GstCaps * caps) -{ - if (direction == GST_PAD_SRC) - return gst_caps_from_string ("video/x-h264, " - "stream-format=(string) byte-stream"); - else - return gst_caps_from_string ("video/x-raw-yuv"); -} - -static gboolean -gst_fake_h264_decoder_get_unit_size (GstBaseTransform * trans, GstCaps * caps, - guint * size) -{ - *size = 1; - return TRUE; -} - -static gboolean -gst_fake_h264_decoder_set_caps (GstBaseTransform * trans, GstCaps * incaps, - GstCaps * outcaps) -{ - GstStructure *s; - const gchar *stream_format; - - s = gst_caps_get_structure (incaps, 0); - fail_unless (gst_structure_has_name (s, "video/x-h264")); - stream_format = gst_structure_get_string (s, "stream-format"); - fail_unless_equals_string ("byte-stream", stream_format); - - s = gst_caps_get_structure (outcaps, 0); - fail_unless (gst_structure_has_name (s, "video/x-raw-yuv")); - - return TRUE; -} - -static GstFlowReturn -gst_fake_h264_decoder_prepare_output_buffer (GstBaseTransform * trans, - GstBuffer * inbuf, gint size, GstCaps * caps, GstBuffer ** outbuf) -{ - *outbuf = gst_buffer_ref (inbuf); - return GST_FLOW_OK; -} - static void gst_fake_h264_decoder_class_init (GstFakeH264DecoderClass * klass) { - GstBaseTransformClass *basetrans_class = (GstBaseTransformClass *) klass; +} - basetrans_class->transform = gst_fake_h264_decoder_transform; - basetrans_class->transform_caps = gst_fake_h264_decoder_transform_caps; - basetrans_class->get_unit_size = gst_fake_h264_decoder_get_unit_size; - basetrans_class->set_caps = gst_fake_h264_decoder_set_caps; - basetrans_class->prepare_output_buffer = - gst_fake_h264_decoder_prepare_output_buffer; +static gboolean +gst_fake_h264_decoder_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstElement *self = GST_ELEMENT (gst_pad_get_parent (pad)); + GstPad *otherpad = gst_element_get_static_pad (self, "src"); + + caps = gst_caps_new_simple ("video/x-raw-yuv", NULL); + gst_pad_set_caps (otherpad, caps); + gst_caps_unref (caps); + + gst_object_unref (otherpad); + gst_object_unref (self); + + return TRUE; +} + +static GstFlowReturn +gst_fake_h264_decoder_sink_chain (GstPad * pad, GstBuffer * buf) +{ + GstElement *self = GST_ELEMENT (gst_pad_get_parent (pad)); + GstPad *otherpad = gst_element_get_static_pad (self, "src"); + GstFlowReturn ret = GST_FLOW_OK; + + buf = gst_buffer_make_metadata_writable (buf); + gst_buffer_set_caps (buf, GST_PAD_CAPS (otherpad)); + + ret = gst_pad_push (otherpad, buf); + + gst_object_unref (otherpad); + gst_object_unref (self); + + return ret; } static void gst_fake_h264_decoder_init (GstFakeH264Decoder * self, GstFakeH264DecoderClass * klass) { + GstPad *pad; + + pad = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (self), "sink"), "sink"); + gst_pad_set_setcaps_function (pad, gst_fake_h264_decoder_sink_setcaps); + gst_pad_set_chain_function (pad, gst_fake_h264_decoder_sink_chain); + gst_element_add_pad (GST_ELEMENT (self), pad); + + pad = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (self), "src"), "src"); + gst_element_add_pad (GST_ELEMENT (self), pad); } static void diff --git a/tests/check/elements/videorate.c b/tests/check/elements/videorate.c index 47c4d1cbc6..58a8ebccd8 100644 --- a/tests/check/elements/videorate.c +++ b/tests/check/elements/videorate.c @@ -794,6 +794,276 @@ GST_START_TEST (test_selected_caps) GST_END_TEST; + +/* Caps negotiation tests */ +typedef struct +{ + const gchar *caps; + gboolean drop_only; + int max_rate; + /* Result of the videomaxrate caps after transforming */ + const gchar *expected_sink_caps; + const gchar *expected_src_caps; +} TestInfo; + +static TestInfo caps_negotiation_tests[] = { + { + .caps = "video/x-raw-yuv", + .drop_only = FALSE, + .expected_sink_caps = "video/x-raw-yuv", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv", + .drop_only = FALSE, + .max_rate = 15, + .expected_sink_caps = "video/x-raw-yuv", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, 15]"}, + { + .caps = "video/x-raw-yuv", + .drop_only = TRUE, + .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv", + .drop_only = TRUE, + .max_rate = 15, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[0/1, 15];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, 15]"}, + + + { + .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .drop_only = FALSE, + .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .drop_only = FALSE, + .max_rate = 15, + .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, 15]"}, + { + .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .drop_only = TRUE, + .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .drop_only = TRUE, + .max_rate = 15, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[0/1, 15];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, 15]"}, + { + .caps = "video/x-raw-yuv, framerate=15/1", + .drop_only = FALSE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=15/1", + .drop_only = FALSE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1]"}, + { + .caps = "video/x-raw-yuv, framerate=15/1", + .drop_only = TRUE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 15/1]"}, + { + .caps = "video/x-raw-yuv, framerate=15/1", + .drop_only = TRUE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 15/1];"}, + { + .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", + .drop_only = FALSE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];"}, + { + .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", + .drop_only = FALSE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 20/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1];"}, + { + .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", + .drop_only = TRUE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, 30/1]"}, + { + .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", + .drop_only = TRUE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 20/1];" + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 20/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1]"}, + { + .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", + .drop_only = FALSE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", + .drop_only = FALSE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1];"}, + { + .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", + .drop_only = TRUE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[0/1, 30/1];"}, + { + .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", + .drop_only = TRUE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1]"}, +}; + +static GstCaps * +_getcaps_function (GstPad * pad) +{ + GstCaps *caps = g_object_get_data (G_OBJECT (pad), "caps"); + + fail_unless (caps != NULL); + + return gst_caps_copy (caps); +} + +static void +check_caps_identical (GstCaps * a, GstCaps * b, const char *name) +{ + int i; + + if (gst_caps_get_size (a) != gst_caps_get_size (b)) + goto fail; + + for (i = 0; i < gst_caps_get_size (a); i++) { + GstStructure *sa, *sb; + + sa = gst_caps_get_structure (a, i); + sb = gst_caps_get_structure (b, i); + + if (!gst_structure_is_equal (sa, sb)) + goto fail; + } + + return; + +fail: + fail ("%s caps (%s) is not equal to caps (%s)", + name, gst_caps_to_string (a), gst_caps_to_string (b)); +} + +static void +check_peer_caps (GstPad * pad, const char *expected, const char *name) +{ + GstCaps *caps; + GstCaps *expected_caps; + + caps = gst_pad_peer_get_caps (pad); + fail_unless (caps != NULL); + + expected_caps = gst_caps_from_string (expected); + fail_unless (expected_caps != NULL); + + check_caps_identical (caps, expected_caps, name); + + gst_caps_unref (caps); + gst_caps_unref (expected_caps); +} + +GST_START_TEST (test_caps_negotiation) +{ + GstElement *videorate; + GstCaps *caps; + TestInfo *test = &caps_negotiation_tests[__i__]; + + videorate = setup_videorate_full (&srctemplate, &sinktemplate); + + caps = gst_caps_from_string (test->caps); + g_object_set_data_full (G_OBJECT (mysrcpad), "caps", + gst_caps_ref (caps), (GDestroyNotify) gst_caps_unref); + g_object_set_data_full (G_OBJECT (mysinkpad), "caps", + gst_caps_ref (caps), (GDestroyNotify) gst_caps_unref); + gst_caps_unref (caps); + + g_object_set (videorate, "drop-only", test->drop_only, NULL); + if (test->max_rate != 0) + g_object_set (videorate, "max-rate", test->max_rate, NULL); + + gst_pad_set_getcaps_function (mysrcpad, _getcaps_function); + gst_pad_set_getcaps_function (mysinkpad, _getcaps_function); + + check_peer_caps (mysrcpad, test->expected_sink_caps, "sink"); + check_peer_caps (mysinkpad, test->expected_src_caps, "src"); + + gst_object_unref (videorate); +} + +GST_END_TEST; + static Suite * videorate_suite (void) { @@ -810,6 +1080,8 @@ videorate_suite (void) tcase_add_test (tc_chain, test_non_ok_flow); tcase_add_test (tc_chain, test_upstream_caps_nego); tcase_add_test (tc_chain, test_selected_caps); + tcase_add_loop_test (tc_chain, test_caps_negotiation, + 0, G_N_ELEMENTS (caps_negotiation_tests)); return s; }