1 /* GStreamer interactive test for accurate seeking
2 * Copyright (C) 2014 Tim-Philipp Müller <tim centricular com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 * Based on python script by Kibeom Kim <kkb110@gmail.com>
20 */
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <string.h>
26
27 #include <gst/gst.h>
28 #include <gst/base/base.h>
29 #include <gst/audio/audio.h>
30 #include <gst/app/app.h>
31
32 #define SAMPLE_FREQ 44100
33
34 static const guint8 *
_memmem(const guint8 * haystack,gsize hlen,const guint8 * needle,gsize nlen)35 _memmem (const guint8 * haystack, gsize hlen, const guint8 * needle, gsize nlen)
36 {
37 const guint8 *p = haystack;
38 int needle_first;
39 gsize plen = hlen;
40
41 if (!nlen)
42 return NULL;
43
44 needle_first = *(unsigned char *) needle;
45
46 while (plen >= nlen && (p = memchr (p, needle_first, plen - nlen + 1))) {
47 if (!memcmp (p, needle, nlen))
48 return (guint8 *) p;
49
50 p++;
51 plen = hlen - (p - haystack);
52 }
53
54 return NULL;
55 }
56
57 static GstClockTime
sample_to_nanotime(guint sample)58 sample_to_nanotime (guint sample)
59 {
60 return (guint64) ((1.0 * sample * GST_SECOND / SAMPLE_FREQ) + 0.5);
61 }
62
63 static guint
nanotime_to_sample(GstClockTime nanotime)64 nanotime_to_sample (GstClockTime nanotime)
65 {
66 return gst_util_uint64_scale_round (nanotime, SAMPLE_FREQ, GST_SECOND);
67 }
68
69 static GstBuffer *
generate_test_data(guint N)70 generate_test_data (guint N)
71 {
72 gint16 *left, *right, *stereo;
73 guint largeN, i, j;
74
75 /* 32767 = (2 ** 15) - 1 */
76 /* 32768 = (2 ** 15) */
77 largeN = ((N + 32767) / 32768) * 32768;
78 left = g_new0 (gint16, largeN);
79 right = g_new0 (gint16, largeN);
80 stereo = g_new0 (gint16, 2 * largeN);
81
82 for (i = 0; i < (largeN / 32768); ++i) {
83 gint c = 0;
84
85 for (j = i * 32768; j < ((i + 1) * 32768); ++j) {
86 left[j] = i;
87
88 if (i % 2 == 0) {
89 right[j] = c;
90 } else {
91 right[j] = 32767 - c;
92 }
93 ++c;
94 }
95 }
96
97 /* could just fill stereo directly from the start, but keeping original code for now */
98 for (i = 0; i < largeN; ++i) {
99 stereo[(2 * i) + 0] = left[i];
100 stereo[(2 * i) + 1] = right[i];
101 }
102 g_free (left);
103 g_free (right);
104
105 return gst_buffer_new_wrapped (stereo, 2 * largeN * sizeof (gint16));
106 }
107
108 static void
generate_test_sound(const gchar * fn,const gchar * launch_string,guint num_samples)109 generate_test_sound (const gchar * fn, const gchar * launch_string,
110 guint num_samples)
111 {
112 GstElement *pipeline, *src, *parse, *enc_bin, *sink;
113 GstFlowReturn flow;
114 GstMessage *msg;
115 GstBuffer *buf;
116 GstCaps *caps;
117
118 pipeline = gst_pipeline_new (NULL);
119
120 src = gst_element_factory_make ("appsrc", NULL);
121
122 caps = gst_caps_new_simple ("audio/x-raw",
123 "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
124 "rate", G_TYPE_INT, SAMPLE_FREQ, "channels", G_TYPE_INT, 2,
125 "layout", G_TYPE_STRING, "interleaved",
126 "channel-mask", GST_TYPE_BITMASK, (guint64) 3, NULL);
127 g_object_set (src, "caps", caps, "format", GST_FORMAT_TIME, NULL);
128 gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
129 gst_caps_unref (caps);
130
131 /* audioparse to put proper timestamps on buffers for us, without which
132 * vorbisenc in particular is unhappy (or oggmux, rather) */
133 parse = gst_element_factory_make ("audioparse", NULL);
134 if (parse != NULL) {
135 g_object_set (parse, "use-sink-caps", TRUE, NULL);
136 } else {
137 parse = gst_element_factory_make ("identity", NULL);
138 g_warning ("audioparse element not available, vorbis/ogg might not work\n");
139 }
140
141 enc_bin = gst_parse_bin_from_description (launch_string, TRUE, NULL);
142
143 sink = gst_element_factory_make ("filesink", NULL);
144 g_object_set (sink, "location", fn, NULL);
145
146 gst_bin_add_many (GST_BIN (pipeline), src, parse, enc_bin, sink, NULL);
147
148 gst_element_link_many (src, parse, enc_bin, sink, NULL);
149
150 gst_element_set_state (pipeline, GST_STATE_PLAYING);
151
152 buf = generate_test_data (num_samples);
153 flow = gst_app_src_push_buffer (GST_APP_SRC (src), buf);
154 g_assert (flow == GST_FLOW_OK);
155
156 gst_app_src_end_of_stream (GST_APP_SRC (src));
157
158 /*g_print ("generating test sound %s, waiting for EOS..\n", fn); */
159
160 msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipeline),
161 GST_CLOCK_TIME_NONE, GST_MESSAGE_EOS | GST_MESSAGE_ERROR);
162
163 g_assert (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS);
164 gst_message_unref (msg);
165
166 gst_element_set_state (pipeline, GST_STATE_NULL);
167 gst_object_unref (pipeline);
168
169 /* g_print ("Done %s\n", fn); */
170 }
171
172 static void
test_seek_FORMAT_TIME_by_sample(const gchar * fn,GList * seek_positions)173 test_seek_FORMAT_TIME_by_sample (const gchar * fn, GList * seek_positions)
174 {
175 GstElement *pipeline, *src, *sink;
176 GstAdapter *adapter;
177 GstSample *sample;
178 GstCaps *caps;
179 gconstpointer answer;
180 guint answer_size;
181
182 pipeline = gst_parse_launch ("filesrc name=src ! decodebin ! "
183 "audioconvert dithering=0 ! appsink name=sink", NULL);
184
185 src = gst_bin_get_by_name (GST_BIN (pipeline), "src");
186 g_object_set (src, "location", fn, NULL);
187 gst_object_unref (src);
188
189 sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
190 caps = gst_caps_new_simple ("audio/x-raw",
191 "format", G_TYPE_STRING, GST_AUDIO_NE (S16),
192 "rate", G_TYPE_INT, SAMPLE_FREQ, "channels", G_TYPE_INT, 2, NULL);
193 g_object_set (sink, "caps", caps, "sync", FALSE, NULL);
194 gst_caps_unref (caps);
195
196 gst_element_set_state (pipeline, GST_STATE_PLAYING);
197
198 /* wait for preroll, so we can seek */
199 gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipeline), GST_CLOCK_TIME_NONE,
200 GST_MESSAGE_ASYNC_DONE);
201
202 /* first, read entire file to end */
203 adapter = gst_adapter_new ();
204 while ((sample = gst_app_sink_pull_sample (GST_APP_SINK (sink)))) {
205 gst_adapter_push (adapter, gst_buffer_ref (gst_sample_get_buffer (sample)));
206 gst_sample_unref (sample);
207 }
208 answer_size = gst_adapter_available (adapter);
209 answer = gst_adapter_map (adapter, answer_size);
210 /* g_print ("%s: read %u bytes\n", fn, answer_size); */
211
212 g_print ("%10s\t%10s\t%10s\n", "requested", "sample per ts", "actual(data)");
213
214 while (seek_positions != NULL) {
215 gconstpointer found;
216 GstMapInfo map;
217 GstBuffer *buf;
218 gboolean ret;
219 guint actual_position, buffer_timestamp_position;
220 guint seek_sample;
221
222 seek_sample = GPOINTER_TO_UINT (seek_positions->data);
223
224 ret = gst_element_seek_simple (pipeline, GST_FORMAT_TIME,
225 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
226 sample_to_nanotime (seek_sample));
227
228 g_assert (ret);
229
230 sample = gst_app_sink_pull_sample (GST_APP_SINK (sink));
231
232 buf = gst_sample_get_buffer (sample);
233 gst_buffer_map (buf, &map, GST_MAP_READ);
234 GST_MEMDUMP ("answer", answer, answer_size);
235 GST_MEMDUMP ("buffer", map.data, map.size);
236 found = _memmem (answer, answer_size, map.data, map.size);
237 gst_buffer_unmap (buf, &map);
238
239 g_assert (found != NULL);
240 actual_position = ((goffset) ((guint8 *) found - (guint8 *) answer)) / 4;
241 buffer_timestamp_position = nanotime_to_sample (GST_BUFFER_PTS (buf));
242 g_print ("%10u\t%10u\t%10u\n", seek_sample, buffer_timestamp_position,
243 actual_position);
244 gst_sample_unref (sample);
245
246 seek_positions = seek_positions->next;
247 }
248
249 gst_element_set_state (pipeline, GST_STATE_NULL);
250 gst_object_unref (sink);
251 gst_object_unref (pipeline);
252 g_object_unref (adapter);
253 }
254
255 static GList *
create_test_samples(guint from,guint to,guint step)256 create_test_samples (guint from, guint to, guint step)
257 {
258 GQueue q = G_QUEUE_INIT;
259 guint i;
260
261 for (i = from; i < to; i += step)
262 g_queue_push_tail (&q, GUINT_TO_POINTER (i));
263
264 return q.head;
265 }
266
267 #define SECS 10
268
269 int
main(int argc,char ** argv)270 main (int argc, char **argv)
271 {
272 GList *test_samples;
273
274 gst_init (&argc, &argv);
275
276 test_samples = create_test_samples (SAMPLE_FREQ, SAMPLE_FREQ * 2, 5000);
277
278 g_print ("\nwav:\n");
279 generate_test_sound ("test.wav", "wavenc", SAMPLE_FREQ * SECS);
280 test_seek_FORMAT_TIME_by_sample ("test.wav", test_samples);
281
282 g_print ("\nflac:\n");
283 generate_test_sound ("test.flac", "flacenc", SAMPLE_FREQ * SECS);
284 test_seek_FORMAT_TIME_by_sample ("test.flac", test_samples);
285
286 g_print ("\nogg:\n");
287 generate_test_sound ("test.ogg",
288 "audioconvert dithering=0 ! vorbisenc quality=1 ! oggmux",
289 SAMPLE_FREQ * SECS);
290 test_seek_FORMAT_TIME_by_sample ("test.ogg", test_samples);
291
292 g_print ("\nmp3:\n");
293 generate_test_sound ("test.mp3", "lamemp3enc bitrate=320",
294 SAMPLE_FREQ * SECS);
295 test_seek_FORMAT_TIME_by_sample ("test.mp3", test_samples);
296
297 g_list_free (test_samples);
298 return 0;
299 }
300