gst-libs/gst/audio/gstbaseaudiosink.*: Extract rate from the NEWSEGMENT event.

Original commit message from CVS:
* 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.
This commit is contained in:
Wim Taymans 2006-10-18 13:42:49 +00:00
parent 453f06075c
commit 1166abbc99
7 changed files with 311 additions and 61 deletions

View file

@ -1,3 +1,25 @@
2006-10-18 Wim Taymans <wim@fluendo.com>
* 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 <tim at centricular dot net>
* gst/playback/gstplaybasebin.c: (subbin_startup_sync_msg),

View file

@ -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");

View file

@ -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;
};
/**

View file

@ -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

View file

@ -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);

View file

@ -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",

View file

@ -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");