tests/check/Makefile.am (check_vorbis): Add pipelines/vorbisenc.

Original commit message from CVS:
2006-01-30  Andy Wingo  <wingo@pobox.com>

* tests/check/Makefile.am (check_vorbis): Add pipelines/vorbisenc.

* ext/vorbis/vorbisenc.c (gst_vorbisenc_buffer_from_packet): Logic
updated to timestamp from the first sample, not the last.
(gst_vorbisenc_buffer_from_header_packet): New function, takes
special care of granulepos and timestamp for header packets.
(gst_vorbisenc_chain): Reflow, fix some leaks, and handle the case
when the first buffer has a nonzero timestamp.

* ext/vorbis/vorbisenc.h (GstVorbisEnc.granulepos_offset)
(GstVorbisEnc.subgranule_offset): New members. Take care of the
case when the first audio buffer we get has a nonzero timestamp.
(GstVorbisEnc.next_ts): Renamed from prev_ts, because now we
properly timestamp vorbis buffers with the time of the first
sample, not the last.

* ext/vorbis/vorbisenc.c (granulepos_to_clocktime): Renamed from
vorbis_granule_time_copy -- now it takes the granule/subgranule
offset into account.

* tests/check/pipelines/vorbisenc.c: New test for correctness of
timestamps, durations, and granulepos on buffers produced by
vorbisenc.
This commit is contained in:
Andy Wingo 2006-01-30 15:01:28 +00:00
parent 6757e87c91
commit 0ad84fae5d
5 changed files with 511 additions and 89 deletions

View file

@ -1,3 +1,29 @@
2006-01-30 Andy Wingo <wingo@pobox.com>
* tests/check/Makefile.am (check_vorbis): Add pipelines/vorbisenc.
* ext/vorbis/vorbisenc.c (gst_vorbisenc_buffer_from_packet): Logic
updated to timestamp from the first sample, not the last.
(gst_vorbisenc_buffer_from_header_packet): New function, takes
special care of granulepos and timestamp for header packets.
(gst_vorbisenc_chain): Reflow, fix some leaks, and handle the case
when the first buffer has a nonzero timestamp.
* ext/vorbis/vorbisenc.h (GstVorbisEnc.granulepos_offset)
(GstVorbisEnc.subgranule_offset): New members. Take care of the
case when the first audio buffer we get has a nonzero timestamp.
(GstVorbisEnc.next_ts): Renamed from prev_ts, because now we
properly timestamp vorbis buffers with the time of the first
sample, not the last.
* ext/vorbis/vorbisenc.c (granulepos_to_clocktime): Renamed from
vorbis_granule_time_copy -- now it takes the granule/subgranule
offset into account.
* tests/check/pipelines/vorbisenc.c: New test for correctness of
timestamps, durations, and granulepos on buffers produced by
vorbisenc.
2006-01-30 Jan Schmidt <thaytan@mad.scientist.com>
* gst/ffmpegcolorspace/gstffmpegcodecmap.c:

View file

@ -98,17 +98,14 @@ enum
static GstFlowReturn gst_vorbisenc_output_buffers (GstVorbisEnc * vorbisenc);
/* FIXME:
* vorbis_granule_time was added between 1.0 and 1.0.1; it's too silly
* to require a new version for such a simple function, but once we move
* beyond 1.0 for other reasons we can remove this copy */
static double
vorbis_granule_time_copy (vorbis_dsp_state * v, ogg_int64_t granulepos)
static GstClockTime
granulepos_to_clocktime (GstVorbisEnc * vorbisenc, ogg_int64_t granulepos)
{
if (granulepos >= 0)
return ((double) granulepos / v->vi->rate);
return (-1);
return gst_util_uint64_scale ((guint64) granulepos
+ vorbisenc->granulepos_offset, GST_SECOND, vorbisenc->frequency)
+ vorbisenc->subgranule_offset;
return GST_CLOCK_TIME_NONE;
}
#if 0
@ -549,7 +546,6 @@ gst_vorbisenc_init (GstVorbisEnc * vorbisenc)
vorbisenc->quality = QUALITY_DEFAULT;
vorbisenc->quality_set = FALSE;
vorbisenc->last_message = NULL;
}
@ -770,7 +766,7 @@ gst_vorbisenc_setup (GstVorbisEnc * vorbisenc)
vorbis_analysis_init (&vorbisenc->vd, &vorbisenc->vi);
vorbis_block_init (&vorbisenc->vd, &vorbisenc->vb);
vorbisenc->prev_ts = 0;
vorbisenc->next_ts = 0;
vorbisenc->setup = TRUE;
@ -808,18 +804,40 @@ gst_vorbisenc_buffer_from_packet (GstVorbisEnc * vorbisenc, ogg_packet * packet)
outbuf = gst_buffer_new_and_alloc (packet->bytes);
memcpy (GST_BUFFER_DATA (outbuf), packet->packet, packet->bytes);
GST_BUFFER_OFFSET (outbuf) = vorbisenc->bytes_out;
GST_BUFFER_OFFSET_END (outbuf) = packet->granulepos;
GST_BUFFER_TIMESTAMP (outbuf) =
vorbis_granule_time_copy (&vorbisenc->vd,
packet->granulepos) * GST_SECOND;
GST_BUFFER_OFFSET_END (outbuf) = packet->granulepos +
vorbisenc->granulepos_offset;
GST_BUFFER_TIMESTAMP (outbuf) = vorbisenc->next_ts;
/* need to pass in an unadjusted granulepos here */
vorbisenc->next_ts = granulepos_to_clocktime (vorbisenc, packet->granulepos);
GST_BUFFER_DURATION (outbuf) =
GST_BUFFER_TIMESTAMP (outbuf) - vorbisenc->prev_ts;
vorbisenc->prev_ts = GST_BUFFER_TIMESTAMP (outbuf);
vorbisenc->next_ts - GST_BUFFER_TIMESTAMP (outbuf);
GST_DEBUG ("encoded buffer of %d bytes", GST_BUFFER_SIZE (outbuf));
return outbuf;
}
/* the same as above, but different logic for setting timestamp and granulepos
* */
static GstBuffer *
gst_vorbisenc_buffer_from_header_packet (GstVorbisEnc * vorbisenc,
ogg_packet * packet)
{
GstBuffer *outbuf;
outbuf = gst_buffer_new_and_alloc (packet->bytes);
memcpy (GST_BUFFER_DATA (outbuf), packet->packet, packet->bytes);
GST_BUFFER_OFFSET (outbuf) = vorbisenc->bytes_out;
GST_BUFFER_OFFSET_END (outbuf) = 0;
GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE;
GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
GST_DEBUG ("created header packet buffer, %d bytes",
GST_BUFFER_SIZE (outbuf));
return outbuf;
}
/* push out the buffer and do internal bookkeeping */
static GstFlowReturn
gst_vorbisenc_push_buffer (GstVorbisEnc * vorbisenc, GstBuffer * buffer)
@ -913,24 +931,17 @@ gst_vorbisenc_sink_event (GstPad * pad, GstEvent * event)
static GstFlowReturn
gst_vorbisenc_chain (GstPad * pad, GstBuffer * buffer)
{
GstBuffer *buf = GST_BUFFER (buffer);
GstVorbisEnc *vorbisenc;
GstFlowReturn ret = GST_FLOW_OK;
vorbisenc = GST_VORBISENC (GST_PAD_PARENT (pad));
{
gfloat *data;
gulong size;
gulong i, j;
float **buffer;
float **vorbis_buffer;
if (!vorbisenc->setup) {
gst_buffer_unref (buf);
GST_ELEMENT_ERROR (vorbisenc, CORE, NEGOTIATION, (NULL),
("encoder not initialized (input is not audio?)"));
return GST_FLOW_UNEXPECTED;
}
vorbisenc = GST_VORBISENC (GST_PAD_PARENT (pad));
if (!vorbisenc->setup)
goto not_setup;
if (!vorbisenc->header_sent) {
/* Vorbis streams begin with three headers; the initial header (with
@ -945,15 +956,20 @@ gst_vorbisenc_chain (GstPad * pad, GstBuffer * buffer)
GstBuffer *buf1, *buf2, *buf3;
GstCaps *caps;
/* first, make sure header buffers get timestamp == 0 */
vorbisenc->next_ts = 0;
vorbisenc->granulepos_offset = 0;
vorbisenc->subgranule_offset = 0;
GST_DEBUG_OBJECT (vorbisenc, "creating and sending header packets");
gst_vorbisenc_set_metadata (vorbisenc);
vorbis_analysis_headerout (&vorbisenc->vd, &vorbisenc->vc, &header,
&header_comm, &header_code);
/* create header buffers */
buf1 = gst_vorbisenc_buffer_from_packet (vorbisenc, &header);
buf2 = gst_vorbisenc_buffer_from_packet (vorbisenc, &header_comm);
buf3 = gst_vorbisenc_buffer_from_packet (vorbisenc, &header_code);
buf1 = gst_vorbisenc_buffer_from_header_packet (vorbisenc, &header);
buf2 = gst_vorbisenc_buffer_from_header_packet (vorbisenc, &header_comm);
buf3 = gst_vorbisenc_buffer_from_header_packet (vorbisenc, &header_code);
/* mark and put on caps */
caps = gst_pad_get_caps (vorbisenc->srcpad);
@ -969,26 +985,36 @@ gst_vorbisenc_chain (GstPad * pad, GstBuffer * buffer)
/* push out buffers */
if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf1)) != GST_FLOW_OK)
goto done;
goto failed_header_push;
if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf2)) != GST_FLOW_OK)
goto done;
goto failed_header_push;
if ((ret = gst_vorbisenc_push_buffer (vorbisenc, buf3)) != GST_FLOW_OK)
goto done;
goto failed_header_push;
/* now adjust starting granulepos accordingly if the buffer's timestamp is
nonzero */
vorbisenc->next_ts = GST_BUFFER_TIMESTAMP (buffer);
vorbisenc->granulepos_offset = gst_util_uint64_scale
(GST_BUFFER_TIMESTAMP (buffer), vorbisenc->frequency, GST_SECOND);
vorbisenc->subgranule_offset = 0;
vorbisenc->subgranule_offset =
vorbisenc->next_ts - granulepos_to_clocktime (vorbisenc, 0);
vorbisenc->header_sent = TRUE;
}
/* data to encode */
data = (gfloat *) GST_BUFFER_DATA (buf);
size = GST_BUFFER_SIZE (buf) / (vorbisenc->channels * sizeof (float));
data = (gfloat *) GST_BUFFER_DATA (buffer);
size = GST_BUFFER_SIZE (buffer) / (vorbisenc->channels * sizeof (float));
/* expose the buffer to submit data */
buffer = vorbis_analysis_buffer (&vorbisenc->vd, size);
vorbis_buffer = vorbis_analysis_buffer (&vorbisenc->vd, size);
/* uninterleave samples */
/* deinterleave samples, write the buffer data */
for (i = 0; i < size; i++) {
for (j = 0; j < vorbisenc->channels; j++) {
buffer[j][i] = *data++;
vorbis_buffer[j][i] = *data++;
}
}
@ -997,13 +1023,25 @@ gst_vorbisenc_chain (GstPad * pad, GstBuffer * buffer)
vorbisenc->samples_in += size;
gst_buffer_unref (buf);
}
gst_buffer_unref (buffer);
ret = gst_vorbisenc_output_buffers (vorbisenc);
done:
return ret;
/* error cases */
not_setup:
{
gst_buffer_unref (buffer);
GST_ELEMENT_ERROR (vorbisenc, CORE, NEGOTIATION, (NULL),
("encoder not initialized (input is not audio?)"));
return GST_FLOW_UNEXPECTED;
}
failed_header_push:
{
gst_buffer_unref (buffer);
return ret;
}
}
static GstFlowReturn

View file

@ -69,7 +69,9 @@ struct _GstVorbisEnc {
guint64 samples_in;
guint64 bytes_out;
GstClockTime prev_ts;
GstClockTime next_ts;
guint64 granulepos_offset;
gint64 subgranule_offset;
GstTagList * tags;

View file

@ -22,7 +22,7 @@ $(CHECK_REGISTRY):
TESTS = $(check_PROGRAMS)
if USE_VORBIS
check_vorbis = elements/vorbisdec
check_vorbis = elements/vorbisdec pipelines/vorbisenc
else
check_vorbis =
endif

View file

@ -0,0 +1,356 @@
/* GStreamer
*
* unit test for vorbisenc
*
* Copyright (C) <2005> Thomas Vander Stichele <thomas at apestaart dot org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <gst/check/gstcheck.h>
#define TIMESTAMP_OFFSET G_GUINT64_CONSTANT(3249870963)
static GCond *cond = NULL;
static GMutex *lock = NULL;
static GstBuffer *buf = NULL;
static gulong id;
static gboolean
buffer_probe (GstPad * pad, GstBuffer * buffer, gpointer unused)
{
g_mutex_lock (lock);
while (buf != NULL)
g_cond_wait (cond, lock);
buf = gst_buffer_ref (buffer);
g_cond_signal (cond);
g_mutex_unlock (lock);
return TRUE;
}
static void
start_pipeline (GstElement * bin, GstPad * pad)
{
id = gst_pad_add_buffer_probe (pad, G_CALLBACK (buffer_probe), NULL);
cond = g_cond_new ();
lock = g_mutex_new ();
gst_element_set_state (bin, GST_STATE_PLAYING);
}
static GstBuffer *
get_buffer (GstElement * bin, GstPad * pad)
{
GstBuffer *ret;
g_mutex_lock (lock);
while (buf == NULL)
g_cond_wait (cond, lock);
ret = buf;
buf = NULL;
g_cond_signal (cond);
g_mutex_unlock (lock);
return ret;
}
static void
stop_pipeline (GstElement * bin, GstPad * pad)
{
g_mutex_lock (lock);
if (buf)
gst_buffer_unref (buf);
buf = NULL;
gst_pad_remove_buffer_probe (pad, (guint) id);
id = 0;
g_cond_signal (cond);
g_mutex_unlock (lock);
gst_element_set_state (bin, GST_STATE_NULL);
g_mutex_lock (lock);
if (buf)
gst_buffer_unref (buf);
buf = NULL;
g_mutex_unlock (lock);
g_mutex_free (lock);
g_cond_free (cond);
lock = NULL;
cond = NULL;
}
static void
check_buffer_timestamp (GstBuffer * buffer, GstClockTime timestamp)
{
fail_unless (GST_BUFFER_TIMESTAMP (buffer) == timestamp,
"expected timestamp %" GST_TIME_FORMAT
", but got timestamp %" GST_TIME_FORMAT,
GST_TIME_ARGS (timestamp), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
}
static void
check_buffer_duration (GstBuffer * buffer, GstClockTime duration)
{
fail_unless (GST_BUFFER_DURATION (buffer) == duration,
"expected duration %" GST_TIME_FORMAT
", but got duration %" GST_TIME_FORMAT,
GST_TIME_ARGS (duration), GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)));
}
static void
check_buffer_granulepos (GstBuffer * buffer, GstClockTime granulepos)
{
fail_unless (GST_BUFFER_OFFSET_END (buffer) == granulepos,
"expected granulepos %" G_GUINT64_FORMAT
", but got granulepos %" G_GUINT64_FORMAT,
granulepos, GST_BUFFER_OFFSET_END (buffer));
}
/* this check is here to check that the granulepos we derive from the timestamp
is about correct. This is "about correct" because you can't precisely go from
timestamp to granulepos due to the downward-rounding characteristics of
gst_util_uint64_scale, so you check if granulepos is equal to the number, or
the number plus one. */
static void
check_buffer_granulepos_from_endtime (GstBuffer * buffer, GstClockTime endtime)
{
GstClockTime granulepos, expected;
granulepos = GST_BUFFER_OFFSET_END (buffer);
expected = gst_util_uint64_scale (endtime, 44100, GST_SECOND);
fail_unless (granulepos == expected || granulepos == expected + 1,
"expected granulepos %" G_GUINT64_FORMAT
" or %" G_GUINT64_FORMAT
", but got granulepos %" G_GUINT64_FORMAT,
expected, expected + 1, granulepos);
}
GST_START_TEST (test_granulepos_offset)
{
GstElement *bin;
GstPad *pad;
gchar *pipe_str;
GstBuffer *buffer;
GError *error = NULL;
GstClockTime timestamp;
pipe_str = g_strdup_printf ("audiotestsrc timestamp-offset=%" G_GUINT64_FORMAT
" ! audio/x-raw-int,rate=44100"
" ! audioconvert ! vorbisenc ! fakesink", TIMESTAMP_OFFSET);
bin = gst_parse_launch (pipe_str, &error);
fail_unless (bin != NULL, "Error parsing pipeline: %s",
error ? error->message : "(invalid error)");
g_free (pipe_str);
/* get the pad */
{
GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fakesink0");
fail_unless (sink != NULL, "Could not get fakesink out of bin");
pad = gst_element_get_pad (sink, "sink");
fail_unless (pad != NULL, "Could not get pad out of fakesink");
gst_object_unref (sink);
}
start_pipeline (bin, pad);
/* header packets should have timestamp == NONE, granulepos 0 */
buffer = get_buffer (bin, pad);
check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
check_buffer_granulepos (buffer, 0);
gst_buffer_unref (buffer);
buffer = get_buffer (bin, pad);
check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
check_buffer_granulepos (buffer, 0);
gst_buffer_unref (buffer);
buffer = get_buffer (bin, pad);
check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
check_buffer_granulepos (buffer, 0);
gst_buffer_unref (buffer);
{
GstClockTime next_timestamp, last_granulepos;
/* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to
* match the timestamp of the end of the last sample in the output buffer.
* Note that one cannot go timestamp->granulepos->timestamp and get the same
* value due to loss of precision with granulepos. vorbisenc does take care
* to timestamp correctly based on the offset of the input data however, so
* it does do sub-granulepos timestamping. */
buffer = get_buffer (bin, pad);
last_granulepos = GST_BUFFER_OFFSET_END (buffer);
check_buffer_timestamp (buffer, TIMESTAMP_OFFSET);
/* don't really have a good way of checking duration... */
check_buffer_granulepos_from_endtime (buffer,
TIMESTAMP_OFFSET + GST_BUFFER_DURATION (buffer));
next_timestamp = TIMESTAMP_OFFSET + GST_BUFFER_DURATION (buffer);
gst_buffer_unref (buffer);
/* check continuity with the next buffer */
buffer = get_buffer (bin, pad);
check_buffer_timestamp (buffer, next_timestamp);
check_buffer_duration (buffer,
gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND,
44100)
- gst_util_uint64_scale (last_granulepos, GST_SECOND, 44100));
check_buffer_granulepos_from_endtime (buffer,
next_timestamp + GST_BUFFER_DURATION (buffer));
gst_buffer_unref (buffer);
}
stop_pipeline (bin, pad);
gst_object_unref (pad);
gst_object_unref (bin);
}
GST_END_TEST;
GST_START_TEST (test_timestamps)
{
GstElement *bin;
GstPad *pad;
gchar *pipe_str;
GstBuffer *buffer;
GError *error = NULL;
GstClockTime timestamp;
pipe_str = g_strdup_printf ("audiotestsrc"
" ! audio/x-raw-int,rate=44100" " ! audioconvert ! vorbisenc ! fakesink");
bin = gst_parse_launch (pipe_str, &error);
fail_unless (bin != NULL, "Error parsing pipeline: %s",
error ? error->message : "(invalid error)");
g_free (pipe_str);
/* get the pad */
{
GstElement *sink = gst_bin_get_by_name (GST_BIN (bin), "fakesink0");
fail_unless (sink != NULL, "Could not get fakesink out of bin");
pad = gst_element_get_pad (sink, "sink");
fail_unless (pad != NULL, "Could not get pad out of fakesink");
gst_object_unref (sink);
}
start_pipeline (bin, pad);
/* check header packets */
buffer = get_buffer (bin, pad);
check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
check_buffer_granulepos (buffer, 0);
gst_buffer_unref (buffer);
buffer = get_buffer (bin, pad);
check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
check_buffer_granulepos (buffer, 0);
gst_buffer_unref (buffer);
buffer = get_buffer (bin, pad);
check_buffer_timestamp (buffer, GST_CLOCK_TIME_NONE);
check_buffer_duration (buffer, GST_CLOCK_TIME_NONE);
check_buffer_granulepos (buffer, 0);
gst_buffer_unref (buffer);
{
GstClockTime next_timestamp, last_granulepos;
/* first buffer has timestamp 0 */
buffer = get_buffer (bin, pad);
last_granulepos = GST_BUFFER_OFFSET_END (buffer);
check_buffer_timestamp (buffer, 0);
/* don't really have a good way of checking duration... */
check_buffer_granulepos_from_endtime (buffer, GST_BUFFER_DURATION (buffer));
next_timestamp = GST_BUFFER_DURATION (buffer);
gst_buffer_unref (buffer);
/* check continuity with the next buffer */
buffer = get_buffer (bin, pad);
check_buffer_timestamp (buffer, next_timestamp);
check_buffer_duration (buffer,
gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND,
44100)
- gst_util_uint64_scale (last_granulepos, GST_SECOND, 44100));
check_buffer_granulepos_from_endtime (buffer,
next_timestamp + GST_BUFFER_DURATION (buffer));
gst_buffer_unref (buffer);
}
stop_pipeline (bin, pad);
gst_object_unref (pad);
gst_object_unref (bin);
}
GST_END_TEST;
Suite *
vorbisenc_suite (void)
{
Suite *s = suite_create ("vorbisenc");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_granulepos_offset);
tcase_add_test (tc_chain, test_timestamps);
return s;
}
int
main (int argc, char **argv)
{
int nf;
Suite *s = vorbisenc_suite ();
SRunner *sr = srunner_create (s);
gst_check_init (&argc, &argv);
srunner_run_all (sr, CK_NORMAL);
nf = srunner_ntests_failed (sr);
srunner_free (sr);
return nf;
}