/* flush the oldest buffer */
static GstFlowReturn
-gst_video_rate_flush_prev (GstVideoRate * videorate, gboolean duplicate)
+gst_video_rate_flush_prev (GstVideoRate * videorate, gboolean duplicate,
+ GstClockTime next_intime)
{
GstFlowReturn res;
GstBuffer *outbuf;
videorate->out++;
videorate->out_frame_count++;
- if (videorate->to_rate_numerator) {
- /* interpolate next expected timestamp in the segment */
- videorate->next_ts =
- videorate->segment.base + videorate->segment.start +
- videorate->base_ts + gst_util_uint64_scale (videorate->out_frame_count,
- videorate->to_rate_denominator * GST_SECOND,
- videorate->to_rate_numerator);
- GST_BUFFER_DURATION (outbuf) = videorate->next_ts - push_ts;
- } else if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (outbuf))) {
- videorate->next_ts = GST_BUFFER_PTS (outbuf) + GST_BUFFER_DURATION (outbuf);
+ if (videorate->segment.rate < 0.0) {
+ if (videorate->to_rate_numerator) {
+ /* interpolate next expected timestamp in the segment */
+
+ videorate->next_ts =
+ videorate->segment.base + videorate->segment.stop -
+ videorate->base_ts -
+ gst_util_uint64_scale (videorate->out_frame_count,
+ videorate->to_rate_denominator * GST_SECOND,
+ videorate->to_rate_numerator);
+ GST_BUFFER_DURATION (outbuf) = push_ts - videorate->next_ts;
+ } else if (next_intime != GST_CLOCK_TIME_NONE) {
+ videorate->next_ts = next_intime;
+ } else {
+ GST_FIXME_OBJECT (videorate, "No next intime for reverse playback");
+ }
+ } else {
+ if (videorate->to_rate_numerator) {
+ /* interpolate next expected timestamp in the segment */
+ videorate->next_ts =
+ videorate->segment.base + videorate->segment.start +
+ videorate->base_ts +
+ gst_util_uint64_scale (videorate->out_frame_count,
+ videorate->to_rate_denominator * GST_SECOND,
+ videorate->to_rate_numerator);
+ GST_BUFFER_DURATION (outbuf) = videorate->next_ts - push_ts;
+ } else if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (outbuf))) {
+ videorate->next_ts =
+ GST_BUFFER_PTS (outbuf) + GST_BUFFER_DURATION (outbuf);
+ } else {
+ /* There must always be a valid duration on prevbuf if rate > 0,
+ * it is ensured in the transform_ip function */
+ GST_FIXME_OBJECT (videorate, "No buffer duration known");
+ }
}
/* We do not need to update time in VFR (variable frame rate) mode */
* regardless, prevent going loopy in strange cases */
while (res == GST_FLOW_OK && count <= MAGIC_LIMIT
&& !videorate->drop_only
- && ((GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)
+ && ((videorate->segment.rate > 0.0
+ && GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)
+ && GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
&& videorate->next_ts - videorate->segment.base <
- videorate->segment.stop)
+ videorate->segment.stop) || (videorate->segment.rate < 0.0
+ && GST_CLOCK_TIME_IS_VALID (videorate->segment.start)
+ && GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
+ && videorate->next_ts - videorate->segment.base >=
+ videorate->segment.start)
|| count < 1)) {
- res = gst_video_rate_flush_prev (videorate, count > 0);
+ res =
+ gst_video_rate_flush_prev (videorate, count > 0,
+ GST_CLOCK_TIME_NONE);
count++;
}
if (count > 1) {
* regardless, prevent going loopy in strange cases */
while (res == GST_FLOW_OK && count <= MAGIC_LIMIT
&& !videorate->drop_only
- && ((videorate->next_ts - videorate->segment.base <
- videorate->segment.stop)
+ && ((videorate->segment.rate > 0.0
+ && GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)
+ && GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
+ && videorate->next_ts - videorate->segment.base <
+ videorate->segment.stop) || (videorate->segment.rate < 0.0
+ && GST_CLOCK_TIME_IS_VALID (videorate->segment.start)
+ && GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
+ && videorate->next_ts - videorate->segment.base >=
+ videorate->segment.start)
|| count < 1)) {
- res = gst_video_rate_flush_prev (videorate, count > 0);
+ res =
+ gst_video_rate_flush_prev (videorate, count > 0,
+ GST_CLOCK_TIME_NONE);
count++;
}
} else if (!videorate->drop_only && videorate->prevbuf) {
videorate->next_ts + GST_BUFFER_DURATION (videorate->prevbuf);
while (res == GST_FLOW_OK && count <= MAGIC_LIMIT &&
- ((videorate->next_ts - videorate->segment.base < end_ts)
+ ((videorate->segment.rate > 0.0
+ && GST_CLOCK_TIME_IS_VALID (videorate->segment.stop)
+ && GST_CLOCK_TIME_IS_VALID (videorate->next_ts)
+ && videorate->next_ts - videorate->segment.base < end_ts)
|| count < 1)) {
- res = gst_video_rate_flush_prev (videorate, count > 0);
+ res =
+ gst_video_rate_flush_prev (videorate, count > 0,
+ GST_CLOCK_TIME_NONE);
count++;
}
} else {
- res = gst_video_rate_flush_prev (videorate, FALSE);
+ res =
+ gst_video_rate_flush_prev (videorate, FALSE, GST_CLOCK_TIME_NONE);
count = 1;
}
}
/* drop frames if they exceed our output rate */
if (GST_CLOCK_TIME_IS_VALID (videorate->last_ts)) {
- GstClockTimeDiff diff = ts - videorate->last_ts;
+ GstClockTimeDiff diff =
+ videorate->segment.rate <
+ 0 ? videorate->last_ts - ts : ts - videorate->last_ts;
/* Drop buffer if its early compared to the desired frame rate and
* the current average is higher than the desired average
{
GstVideoRate *videorate;
GstFlowReturn res = GST_BASE_TRANSFORM_FLOW_DROPPED;
- GstClockTime intime, in_ts, in_dur;
+ GstClockTime intime, in_ts, in_dur, last_ts;
GstClockTime avg_period;
gboolean skip = FALSE;
in_dur = GST_BUFFER_DURATION (buffer);
if (G_UNLIKELY (in_ts == GST_CLOCK_TIME_NONE)) {
+ /* For reverse playback, we need all input timestamps as we can't
+ * guess from the previous buffers timestamp and duration */
+ if (G_UNLIKELY (in_ts == GST_CLOCK_TIME_NONE
+ && videorate->segment.rate < 0.0))
+ goto invalid_buffer;
in_ts = videorate->last_ts;
if (G_UNLIKELY (in_ts == GST_CLOCK_TIME_NONE))
goto invalid_buffer;
/* get the time of the next expected buffer timestamp, we use this when the
* next buffer has -1 as a timestamp */
+ last_ts = videorate->last_ts;
videorate->last_ts = in_ts;
- if (in_dur != GST_CLOCK_TIME_NONE)
+ if (in_dur != GST_CLOCK_TIME_NONE && videorate->segment.rate > 0.0)
videorate->last_ts += in_dur;
GST_DEBUG_OBJECT (videorate, "got buffer with timestamp %" GST_TIME_FORMAT,
/* we need to have two buffers to compare */
if (videorate->prevbuf == NULL || videorate->drop_only) {
+ /* We can calculate the duration of the buffer here if not given for
+ * reverse playback. We need this later */
+ if (videorate->segment.rate < 0.0 && !GST_BUFFER_DURATION_IS_VALID (buffer)) {
+ /* As we require valid timestamps all the time for reverse playback, we either
+ * have a valid last_ts or we're at the very first buffer. */
+ if (!GST_CLOCK_TIME_IS_VALID (last_ts))
+ GST_BUFFER_DURATION (buffer) = videorate->segment.stop - in_ts;
+ else
+ GST_BUFFER_DURATION (buffer) = last_ts - in_ts;
+ }
+
gst_video_rate_swap_prev (videorate, buffer, intime);
videorate->in++;
if (!GST_CLOCK_TIME_IS_VALID (videorate->next_ts)) {
* timestamp in the segment */
if (videorate->skip_to_first || skip) {
videorate->next_ts = intime;
- videorate->base_ts = in_ts - videorate->segment.start;
+ if (videorate->segment.rate < 0.0)
+ videorate->base_ts = videorate->segment.stop - in_ts;
+ else
+ videorate->base_ts = in_ts - videorate->segment.start;
videorate->out_frame_count = 0;
} else {
- videorate->next_ts = videorate->segment.start + videorate->segment.base;
+ if (videorate->segment.rate < 0.0)
+ videorate->next_ts =
+ videorate->segment.stop + videorate->segment.base;
+ else
+ videorate->next_ts =
+ videorate->segment.start + videorate->segment.base;
}
}
* allowed frame period. This also keeps latency down to 0 frames
*/
if (videorate->drop_only) {
- if (intime >= videorate->next_ts) {
+ if ((videorate->segment.rate > 0.0 && intime >= videorate->next_ts) ||
+ (videorate->segment.rate < 0.0 && intime <= videorate->next_ts)) {
GstFlowReturn r;
/* on error the _flush function posted a warning already */
- if ((r = gst_video_rate_flush_prev (videorate, FALSE)) != GST_FLOW_OK) {
+ if ((r = gst_video_rate_flush_prev (videorate, FALSE,
+ GST_CLOCK_TIME_NONE)) != GST_FLOW_OK) {
res = r;
goto done;
}
videorate->in++;
/* drop new buffer if it's before previous one */
- if (intime < prevtime) {
+ if ((videorate->segment.rate > 0.0 && intime < prevtime) ||
+ (videorate->segment.rate < 0.0 && intime > prevtime)) {
GST_DEBUG_OBJECT (videorate,
"The new buffer (%" GST_TIME_FORMAT
") is before the previous buffer (%"
/* got 2 buffers, see which one is the best */
do {
- diff1 = prevtime - videorate->next_ts;
- diff2 = intime - videorate->next_ts;
-
/* take absolute values, beware: abs and ABS don't work for gint64 */
- if (diff1 < 0)
- diff1 = -diff1;
- if (diff2 < 0)
- diff2 = -diff2;
+ if (prevtime > videorate->next_ts)
+ diff1 = prevtime - videorate->next_ts;
+ else
+ diff1 = videorate->next_ts - prevtime;
+
+ if (intime > videorate->next_ts)
+ diff2 = intime - videorate->next_ts;
+ else
+ diff2 = videorate->next_ts - intime;
GST_LOG_OBJECT (videorate,
"diff with prev %" GST_TIME_FORMAT " diff with new %"
GST_TIME_ARGS (diff1), GST_TIME_ARGS (diff2),
GST_TIME_ARGS (videorate->next_ts));
- if (!GST_BUFFER_DURATION_IS_VALID (videorate->prevbuf) &&
- intime > prevtime) {
+ if (videorate->segment.rate < 0.0) {
+ /* Make sure that we have a duration for this buffer. The previous
+ * buffer already has a duration given by either exactly this code,
+ * or the code above for the very first buffer */
+ g_assert (GST_BUFFER_DURATION_IS_VALID (videorate->prevbuf));
+ if (!GST_BUFFER_DURATION_IS_VALID (buffer) && prevtime > intime)
+ GST_BUFFER_DURATION (buffer) = prevtime - intime;
+ } else {
/* Make sure that we have a duration for previous buffer */
- GST_BUFFER_DURATION (videorate->prevbuf) = intime - prevtime;
+ if (!GST_BUFFER_DURATION_IS_VALID (videorate->prevbuf)
+ && intime > prevtime)
+ GST_BUFFER_DURATION (videorate->prevbuf) = intime - prevtime;
}
/* output first one when its the best */
/* on error the _flush function posted a warning already */
if ((r = gst_video_rate_flush_prev (videorate,
- count > 1)) != GST_FLOW_OK) {
+ count > 1, intime)) != GST_FLOW_OK) {
res = r;
goto done;
}