diff --git a/ChangeLog b/ChangeLog index 875c6c1ae2..16515547ed 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,25 @@ +2006-10-18 Wim Taymans + + * gst-libs/gst/audio/gstbaseaudiosink.c: + (gst_base_audio_sink_event), (gst_base_audio_sink_render): + * gst-libs/gst/audio/gstbaseaudiosink.h: + Extract rate from the NEWSEGMENT event. + Use commit_full to also take rate adjustment into account when writing + samples to the ringbuffer. + + * gst-libs/gst/audio/gstringbuffer.c: + (gst_ring_buffer_commit_full), (gst_ring_buffer_commit), + (gst_ring_buffer_read): + * gst-libs/gst/audio/gstringbuffer.h: + Added _commit_full() to also take rate into account. + Use simple interpolation algorithm to resample audio. + API: gst_ring_buffer_commit_full() + + * tests/examples/seek/scrubby.c: (speed_cb), (do_seek): + * tests/examples/seek/seek.c: (segment_done): + Don't try to seek with 0.0 rate, just pause instead. + Remove bogus debug line. + 2006-10-18 Tim-Philipp Müller * gst/playback/gstplaybasebin.c: (subbin_startup_sync_msg), diff --git a/gst-libs/gst/audio/gstbaseaudiosink.c b/gst-libs/gst/audio/gstbaseaudiosink.c index 720fd4613e..c067d10c65 100644 --- a/gst-libs/gst/audio/gstbaseaudiosink.c +++ b/gst-libs/gst/audio/gstbaseaudiosink.c @@ -441,6 +441,32 @@ gst_base_audio_sink_event (GstBaseSink * bsink, GstEvent * event) /* now wait till we played everything */ gst_base_audio_sink_drain (sink); break; + case GST_EVENT_NEWSEGMENT: + { + gdouble rate; + GValue src = { 0 }; + GValue dst = { 0 }; + + /* we only need the rate */ + gst_event_parse_new_segment_full (event, NULL, &rate, NULL, NULL, + NULL, NULL, NULL); + + g_value_init (&src, G_TYPE_DOUBLE); + g_value_set_double (&src, rate); + g_value_init (&dst, GST_TYPE_FRACTION); + g_value_transform (&src, &dst); + g_value_unset (&src); + + sink->abidata.ABI.rate_num = gst_value_get_fraction_numerator (&dst); + sink->abidata.ABI.rate_denom = gst_value_get_fraction_denominator (&dst); + sink->abidata.ABI.rate_accum = 0; + + GST_DEBUG_OBJECT (sink, "set rate to %f = %d / %d", rate, + sink->abidata.ABI.rate_num, sink->abidata.ABI.rate_denom); + + g_value_unset (&dst); + break; + } default: break; } @@ -513,9 +539,10 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) guint size; guint samples, written; gint bps; - gdouble crate = 1.0; GstClockTime crate_num; GstClockTime crate_denom; + gint rate_num; + gint rate_denom; GstClockTime cinternal, cexternal; GstClock *clock; gboolean sync; @@ -545,6 +572,9 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) "time %" GST_TIME_FORMAT ", offset %llu, start %" GST_TIME_FORMAT, GST_TIME_ARGS (time), in_offset, GST_TIME_ARGS (bsink->segment.start)); + rate_num = sink->abidata.ABI.rate_num; + rate_denom = sink->abidata.ABI.rate_denom; + /* if not valid timestamp or we can't clip or sync, try to play * sample ASAP */ if (!GST_CLOCK_TIME_IS_VALID (time)) { @@ -606,12 +636,17 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) goto no_sync; } - gst_clock_get_calibration (sink->provided_clock, &cinternal, &cexternal, - &crate_num, &crate_denom); - /* bring buffer timestamp to running time */ render_time = gst_segment_to_running_time (&bsink->segment, GST_FORMAT_TIME, time); + GST_DEBUG_OBJECT (sink, "running time %" GST_TIME_FORMAT, + GST_TIME_ARGS (render_time)); + + /* get calibration parameters to compensate for speed and offset differences + * when we are slaved */ + gst_clock_get_calibration (sink->provided_clock, &cinternal, &cexternal, + &crate_num, &crate_denom); + /* add base time to get absolute clock time */ render_time += (gst_element_get_base_time (GST_ELEMENT_CAST (bsink)) - cexternal) + @@ -621,7 +656,7 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) gst_util_uint64_scale_int (render_time, ringbuf->spec.rate, GST_SECOND); GST_DEBUG_OBJECT (sink, "render time %" GST_TIME_FORMAT - ", render offset %llu, samples %u", + ", render offset %" G_GUINT64_FORMAT ", samples %u", GST_TIME_ARGS (render_time), render_offset, samples); /* never try to align samples when we are slaved to another clock, just @@ -646,7 +681,10 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) } /* now try to align the sample to the previous one */ - diff = ABS ((gint64) render_offset - (gint64) sink->next_sample); + if (render_offset >= sink->next_sample) + diff = render_offset - sink->next_sample; + else + diff = sink->next_sample - render_offset; /* we tollerate half a second diff before we start resyncing. This * should be enough to compensate for various rounding errors in the timestamp @@ -659,6 +697,8 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) /* just align with previous sample then */ render_offset = sink->next_sample; } else { + /* bring sample diff to seconds for error message */ + diff = gst_util_uint64_scale_int (diff, GST_SECOND, ringbuf->spec.rate); /* timestamps drifted apart from previous samples too much, we need to * resync. We log this as an element warning. */ GST_ELEMENT_WARNING (sink, CORE, CLOCK, @@ -669,23 +709,47 @@ gst_base_audio_sink_render (GstBaseSink * bsink, GstBuffer * buf) } no_align: - /* crate contains diff against the clock we are using in the pipeline. */ - crate = - gst_guint64_to_gdouble (crate_num) / gst_guint64_to_gdouble (crate_denom); - GST_DEBUG_OBJECT (sink, - "internal %" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT ", rate %g", - cinternal, cexternal, crate); + /* check if we have a clock rate adjustment to do */ + if (crate_num != 1 || crate_num != 1) { + gint64 num, denom; + + /* make clock rate fit in int */ + while ((crate_num | crate_denom) > G_MAXINT) { + crate_num /= 2; + crate_denom /= 2; + } + /* full 64bit multiplication */ + num = ((gint64) crate_denom) * rate_num; + denom = ((gint64) crate_num) * rate_denom; + + /* make result fit in int again */ + while ((num | denom) > G_MAXINT) { + num /= 2; + denom /= 2; + } + rate_num = num; + rate_denom = denom; + + GST_DEBUG_OBJECT (sink, + "clock rate: internal %" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT + " %d/%d", cinternal, cexternal, rate_num, rate_denom); + } no_sync: - /* clip length based on rate */ - samples = MIN (samples, samples / (crate * bsink->segment.abs_rate)); - /* the next sample should be current sample and its length */ - sink->next_sample = render_offset + samples; + /* the next sample should be current sample and its length, this will be + * updated as we write samples to the ringbuffer. */ + sink->next_sample = render_offset; + + GST_DEBUG_OBJECT (sink, "rendering at %" G_GUINT64_FORMAT, sink->next_sample); do { - written = gst_ring_buffer_commit (ringbuf, render_offset, data, samples); - GST_DEBUG_OBJECT (sink, "wrote %u of %u", written, samples); + written = + gst_ring_buffer_commit_full (ringbuf, &sink->next_sample, data, samples, + rate_num, rate_denom, &sink->abidata.ABI.rate_accum); + + GST_DEBUG_OBJECT (sink, "wrote %u of %u, resampled %" G_GUINT64_FORMAT, + written, samples, sink->next_sample - render_offset); /* if we wrote all, we're done */ if (written == samples) break; @@ -694,11 +758,13 @@ no_sync: if (gst_base_sink_wait_preroll (bsink) != GST_FLOW_OK) goto stopping; - render_offset += written; samples -= written; data += written * bps; } while (TRUE); + GST_DEBUG_OBJECT (sink, "next sample expected at %" G_GUINT64_FORMAT, + sink->next_sample); + if (GST_CLOCK_TIME_IS_VALID (stop) && stop >= bsink->segment.stop) { GST_DEBUG_OBJECT (sink, "start playback because we are at the end of segment"); diff --git a/gst-libs/gst/audio/gstbaseaudiosink.h b/gst-libs/gst/audio/gstbaseaudiosink.h index 5cd2068ac0..a9a33c57a7 100644 --- a/gst-libs/gst/audio/gstbaseaudiosink.h +++ b/gst-libs/gst/audio/gstbaseaudiosink.h @@ -105,7 +105,15 @@ struct _GstBaseAudioSink { GstClock *provided_clock; /*< private >*/ - gpointer _gst_reserved[GST_PADDING]; + union { + struct { + gint rate_num; + gint rate_denom; + gint rate_accum; + } ABI; + /* adding + 0 to mark ABI change to be undone later */ + gpointer _gst_reserved[GST_PADDING + 0]; + } abidata; }; /** diff --git a/gst-libs/gst/audio/gstringbuffer.c b/gst-libs/gst/audio/gstringbuffer.c index fb2c799595..9e090ff212 100644 --- a/gst-libs/gst/audio/gstringbuffer.c +++ b/gst-libs/gst/audio/gstringbuffer.c @@ -1174,54 +1174,155 @@ no_start: } } +#define FWD_SAMPLES(s,se,d,de) \ +G_STMT_START { \ + /* no rate conversion */ \ + guint towrite = MIN (se + bps - s, de - d); \ + /* simple copy */ \ + if (!skip) \ + memcpy (d, s, towrite); \ + *sample += towrite / bps; \ + s += towrite; \ + GST_DEBUG ("copy %u bytes", towrite); \ +} G_STMT_END + +#define FWD_UP_SAMPLES(s,se,d,de) \ +G_STMT_START { \ + guint8 *ds = d; \ + while (s <= se && d < de) { \ + if (!skip) \ + memcpy (d, s, bps); \ + s += bps; \ + *accum += denom; \ + while (*accum > 0) { \ + *accum -= num; \ + d += bps; \ + } \ + } \ + *sample += (d - ds) / bps; \ + GST_DEBUG ("fwd_up %u bytes", d - ds); \ +} G_STMT_END + +#define FWD_DOWN_SAMPLES(s,se,d,de) \ +G_STMT_START { \ + guint8 *ds = d; \ + while (s <= se && d < de) { \ + if (!skip) \ + memcpy (d, s, bps); \ + d += bps; \ + *accum -= num; \ + while (*accum < 0) { \ + *accum += denom; \ + s += bps; \ + } \ + } \ + *sample += (d - ds) / bps; \ + GST_DEBUG ("fwd_down %u bytes", d - ds); \ +} G_STMT_END + +#define REV_UP_SAMPLES(s,se,d,de) \ +G_STMT_START { \ + guint8 *ds = d; \ + while (s <= se && d < de) { \ + if (!skip) \ + memcpy (d, se, bps); \ + se -= bps; \ + *accum += denom; \ + while (*accum > 0) { \ + *accum += num; \ + d += bps; \ + } \ + } \ + *sample += (d - ds) / bps; \ + GST_DEBUG ("rev_up %u bytes", d - ds); \ +} G_STMT_END + +#define REV_DOWN_SAMPLES(s,se,d,de) \ +G_STMT_START { \ + guint8 *ds = d; \ + while (s <= se && d < de) { \ + if (!skip) \ + memcpy (d, se, bps); \ + d += bps; \ + *accum += num; \ + while (*accum < 0) { \ + *accum += denom; \ + se -= bps; \ + } \ + } \ + *sample += (d - ds) / bps; \ + GST_DEBUG ("rev_down %u bytes", d - ds); \ +} G_STMT_END + /** - * gst_ring_buffer_commit: + * gst_ring_buffer_commit_full: * @buf: the #GstRingBuffer to commit * @sample: the sample position of the data * @data: the data to commit * @len: the number of samples in the data to commit + * @num: the numerator of the speed + * @denom: the denominator of the speed + * @accum: accumulator for rate conversion. * - * Commit @len samples pointed to by @data to the ringbuffer - * @buf. The first sample should be written at position @sample in - * the ringbuffer. + * Commit @len samples pointed to by @data to the ringbuffer @buf. * - * @len does not need to be a multiple of the segment size of the ringbuffer - * although it is recommended for optimal performance. + * @num and @denom define the rate conversion to perform on the the samples in + * @data. For negative rates, @num must be negative and @denom positive. * - * Returns: The number of samples written to the ringbuffer or -1 on - * error. + * When @num is positive, the first sample will be written at position @sample + * in the ringbuffer. When @num is negative, the last sample will be written to + * @sample in reverse order. + * + * @len does not need to be a multiple of the segment size of the ringbuffer (if + * @num and @denom are both 1) although it is recommended for optimal performance. + * + * @sample will be updated with the next position in the ringbuffer. This + * position will take into account any resampling done when writing the samples. + * + * Returns: The number of samples written to the ringbuffer or -1 on error. The + * number of samples written can be less than @len when @buf was interrupted + * with a flush or stop. + * + * Since: 0.10.11. * * MT safe. */ guint -gst_ring_buffer_commit (GstRingBuffer * buf, guint64 sample, guchar * data, - guint len) +gst_ring_buffer_commit_full (GstRingBuffer * buf, guint64 * sample, + guchar * data, guint len, gint num, gint denom, gint * accum) { gint segdone; gint segsize, segtotal, bps, sps; - guint8 *dest; - guint to_write; + guint8 *dest, *data_end; + gint writeseg, sampleoff; + + if (G_UNLIKELY (len == 0)) + return 0; g_return_val_if_fail (GST_IS_RING_BUFFER (buf), -1); g_return_val_if_fail (buf->data != NULL, -1); g_return_val_if_fail (data != NULL, -1); + g_return_val_if_fail (denom != 0, -1); dest = GST_BUFFER_DATA (buf->data); segsize = buf->spec.segsize; segtotal = buf->spec.segtotal; bps = buf->spec.bytes_per_sample; sps = buf->samples_per_seg; + /* data_end points to the last sample we have to write, not past it. */ + data_end = data + (bps * (len - 1)); + + /* figure out the segment and the offset inside the segment where + * the first sample should be written. */ + writeseg = *sample / sps; + sampleoff = (*sample % sps) * bps; - to_write = len; /* write out all samples */ - while (to_write > 0) { - gint sampleslen; - gint writeseg, sampleoff; - - /* figure out the segment and the offset inside the segment where - * the sample should be written. */ - writeseg = sample / sps; - sampleoff = (sample % sps); + while (data <= data_end) { + gint avail; + guint8 *d, *d_end; + gint ws; + gboolean skip; while (TRUE) { gint diff; @@ -1233,22 +1334,23 @@ gst_ring_buffer_commit (GstRingBuffer * buf, guint64 sample, guchar * data, diff = writeseg - segdone; GST_DEBUG - ("pointer at %d, sample %llu, write to %d-%d, to_write %d, diff %d, segtotal %d, segsize %d", - segdone, sample, writeseg, sampleoff, to_write, diff, segtotal, - segsize); + ("pointer at %d, write to %d-%d, diff %d, segtotal %d, segsize %d", + segdone, writeseg, sampleoff, diff, segtotal, segsize); /* segment too far ahead, writer too slow, we need to drop, hopefully UNLIKELY */ if (G_UNLIKELY (diff < 0)) { /* we need to drop one segment at a time, pretend we wrote a * segment. */ - sampleslen = MIN (sps, to_write); - goto next; + skip = TRUE; + break; } /* write segment is within writable range, we can break the loop and * start writing the data. */ - if (diff < segtotal) + if (diff < segtotal) { + skip = FALSE; break; + } /* else we need to wait for the segment to become writable. */ if (!wait_segment (buf)) @@ -1256,31 +1358,77 @@ gst_ring_buffer_commit (GstRingBuffer * buf, guint64 sample, guchar * data, } /* we can write now */ - writeseg = writeseg % segtotal; - sampleslen = MIN (sps - sampleoff, to_write); + ws = writeseg % segtotal; + avail = segsize - sampleoff; - GST_DEBUG_OBJECT (buf, "write @%p seg %d, off %d, sampleslen %d", - dest + writeseg * segsize, writeseg, sampleoff, sampleslen); + d = dest + (ws * segsize) + sampleoff; + d_end = d + avail; - memcpy (dest + (writeseg * segsize) + (sampleoff * bps), data, - (sampleslen * bps)); + GST_DEBUG_OBJECT (buf, "write @%p seg %d, sps %d, off %d, avail %d", + dest + ws * segsize, ws, sps, sampleoff, avail); - next: - to_write -= sampleslen; - sample += sampleslen; - data += sampleslen * bps; + if (G_LIKELY (num == 1 && denom == 1)) { + /* no rate conversion, simply copy samples */ + FWD_SAMPLES (data, data_end, d, d_end); + } else if (num >= 0) { + if (num >= denom) + /* forward speed up */ + FWD_UP_SAMPLES (data, data_end, d, d_end); + else + /* forward slow down */ + FWD_DOWN_SAMPLES (data, data_end, d, d_end); + } else { + if (-num >= denom) + /* reverse speed up */ + REV_UP_SAMPLES (data, data_end, d, d_end); + else + /* reverse slow down */ + REV_DOWN_SAMPLES (data, data_end, d, d_end); + } + + /* for the next iteration we write to the next segment at the beginning. */ + writeseg++; + sampleoff = 0; } - return len - to_write; +done: + return (len - 1) - ((data_end - data) / bps); /* ERRORS */ not_started: { GST_DEBUG_OBJECT (buf, "stopped processing"); - return len - to_write; + goto done; } } +/** + * gst_ring_buffer_commit: + * @buf: the #GstRingBuffer to commit + * @sample: the sample position of the data + * @data: the data to commit + * @len: the number of samples in the data to commit + * + * Same as gst_ring_buffer_commit_full() but with a num, denom of 1, ignoring + * accum. + * + * Returns: The number of samples written to the ringbuffer or -1 on + * error. + * + * MT safe. + */ +guint +gst_ring_buffer_commit (GstRingBuffer * buf, guint64 sample, guchar * data, + guint len) +{ + guint res; + guint64 spos = sample; + + res = gst_ring_buffer_commit_full (buf, &spos, data, len, 1, 1, NULL); + + return res; +} + /** * gst_ring_buffer_read: * @buf: the #GstRingBuffer to read from diff --git a/gst-libs/gst/audio/gstringbuffer.h b/gst-libs/gst/audio/gstringbuffer.h index e4cbe1a488..e1598d1246 100644 --- a/gst-libs/gst/audio/gstringbuffer.h +++ b/gst-libs/gst/audio/gstringbuffer.h @@ -337,6 +337,9 @@ void gst_ring_buffer_clear_all (GstRingBuffer *buf); /* commit samples */ guint gst_ring_buffer_commit (GstRingBuffer *buf, guint64 sample, guchar *data, guint len); +guint gst_ring_buffer_commit_full (GstRingBuffer *buf, guint64 *sample, + guchar *data, guint len, + gint num, gint denom, gint *accum); /* read samples */ guint gst_ring_buffer_read (GstRingBuffer *buf, guint64 sample, guchar *data, guint len); diff --git a/tests/examples/seek/scrubby.c b/tests/examples/seek/scrubby.c index 43da052c17..134e955bae 100644 --- a/tests/examples/seek/scrubby.c +++ b/tests/examples/seek/scrubby.c @@ -177,6 +177,9 @@ speed_cb (GtkWidget * widget) GST_DEBUG ("speed change"); cur_speed = gtk_range_get_value (GTK_RANGE (widget)); + if (cur_speed == 0.0) + return; + s_event = gst_event_new_seek (cur_speed, GST_FORMAT_TIME, 0, GST_SEEK_TYPE_NONE, -1, GST_SEEK_TYPE_NONE, -1); @@ -265,6 +268,8 @@ do_seek (GtkWidget * widget, gboolean flush, gboolean segment) stop = tmp; } + if (rate == 0.0) + return TRUE; GST_DEBUG ("seek to %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT ", rate %lf" " on element %s", diff --git a/tests/examples/seek/seek.c b/tests/examples/seek/seek.c index 5b5c1557b3..f1242d9bbc 100644 --- a/tests/examples/seek/seek.c +++ b/tests/examples/seek/seek.c @@ -1330,8 +1330,6 @@ segment_done (GstBus * bus, GstMessage * message, GstPipeline * pipeline) event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, -1); - GST_DEBUG ("segmeent seek to start"); - res = send_event (event); if (!res) { g_print ("segment seek failed\n");