audiolatency: Fix wave detection in buffers
[platform/upstream/gstreamer.git] / gst / audiolatency / gstaudiolatency.c
1 /* Audio latency measurement plugin
2  * Copyright (C) 2018 Centricular Ltd.
3  *   Author: Nirbheek Chauhan <nirbheek@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 /**
22  * SECTION:element-audiolatency
23  * @title: audiolatency
24  *
25  * Measures the audio latency between the source pad and the sink pad by
26  * outputting period ticks on the source pad and measuring how long they take to
27  * arrive on the sink pad.
28  *
29  * The ticks have a period of 1 second, so this element can only measure
30  * latencies smaller than that.
31  *
32  * ## Example pipeline
33  * |[
34  * gst-launch-1.0 -v autoaudiosrc ! audiolatency print-latency=true ! autoaudiosink
35  * ]| Continuously print the latency of the audio output and the audio capture
36  *
37  * In this case, you must ensure that the audio output is captured by the audio
38  * source. The simplest way is to use a standard 3.5mm male-to-male audio cable
39  * to connect line-out to line-in, or speaker-out to mic-in, etc.
40  *
41  * Capturing speaker output with a microphone should also work, as long as the
42  * ambient noise level is low enough. You may have to adjust the microphone gain
43  * to ensure that the volume is loud enough to be detected by the element, and
44  * at the same time that it's not so loud that it picks up ambient noise.
45  *
46  * For programmatic use, instead of using 'print-stats', you should read the
47  * 'last-latency' and 'average-latency' properties at most once a second, or
48  * parse the "latency" element message, which contains the "last-latency" and
49  * "average-latency" fields in the GstStructure.
50  *
51  * The average latency is a running average of the last 5 measurements.
52  */
53
54 #ifdef HAVE_CONFIG_H
55 #include "config.h"
56 #endif
57
58 #include "gstaudiolatency.h"
59
60 #define AUDIOLATENCY_CAPS "audio/x-raw, " \
61     "format = (string) F32LE, " \
62     "layout = (string) interleaved, " \
63     "rate = (int) [ 1, MAX ], " \
64     "channels = (int) [ 1, MAX ]"
65
66 GST_DEBUG_CATEGORY_STATIC (gst_audiolatency_debug);
67 #define GST_CAT_DEFAULT gst_audiolatency_debug
68
69 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
70     GST_PAD_SRC,
71     GST_PAD_ALWAYS,
72     GST_STATIC_CAPS (AUDIOLATENCY_CAPS)
73     );
74
75 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
76     GST_PAD_SINK,
77     GST_PAD_ALWAYS,
78     GST_STATIC_CAPS (AUDIOLATENCY_CAPS)
79     );
80
81 #define gst_audiolatency_parent_class parent_class
82 G_DEFINE_TYPE (GstAudioLatency, gst_audiolatency, GST_TYPE_BIN);
83
84 #define DEFAULT_PRINT_LATENCY   FALSE
85 enum
86 {
87   PROP_0,
88   PROP_PRINT_LATENCY,
89   PROP_LAST_LATENCY,
90   PROP_AVERAGE_LATENCY
91 };
92
93 static gint64 gst_audiolatency_get_latency (GstAudioLatency * self);
94 static gint64 gst_audiolatency_get_average_latency (GstAudioLatency * self);
95 static GstFlowReturn gst_audiolatency_sink_chain (GstPad * pad,
96     GstObject * parent, GstBuffer * buffer);
97 static GstPadProbeReturn gst_audiolatency_src_probe (GstPad * pad,
98     GstPadProbeInfo * info, gpointer user_data);
99
100 static void
101 gst_audiolatency_get_property (GObject * object,
102     guint prop_id, GValue * value, GParamSpec * pspec)
103 {
104   GstAudioLatency *self = GST_AUDIOLATENCY (object);
105
106   switch (prop_id) {
107     case PROP_PRINT_LATENCY:
108       g_value_set_boolean (value, self->print_latency);
109       break;
110     case PROP_LAST_LATENCY:
111       g_value_set_int64 (value, gst_audiolatency_get_latency (self));
112       break;
113     case PROP_AVERAGE_LATENCY:
114       g_value_set_int64 (value, gst_audiolatency_get_average_latency (self));
115       break;
116     default:
117       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
118       break;
119   }
120 }
121
122 static void
123 gst_audiolatency_set_property (GObject * object,
124     guint prop_id, const GValue * value, GParamSpec * pspec)
125 {
126   GstAudioLatency *self = GST_AUDIOLATENCY (object);
127
128   switch (prop_id) {
129     case PROP_PRINT_LATENCY:
130       self->print_latency = g_value_get_boolean (value);
131       break;
132     default:
133       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
134       break;
135   }
136 }
137
138 static void
139 gst_audiolatency_class_init (GstAudioLatencyClass * klass)
140 {
141   GObjectClass *gobject_class = (GObjectClass *) klass;
142   GstElementClass *gstelement_class = (GstElementClass *) klass;
143
144   gobject_class->get_property = gst_audiolatency_get_property;
145   gobject_class->set_property = gst_audiolatency_set_property;
146
147   g_object_class_install_property (gobject_class, PROP_PRINT_LATENCY,
148       g_param_spec_boolean ("print-latency", "Print latencies",
149           "Print the measured latencies on stdout",
150           DEFAULT_PRINT_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
151
152   g_object_class_install_property (gobject_class, PROP_LAST_LATENCY,
153       g_param_spec_int64 ("last-latency", "Last measured latency",
154           "The last latency that was measured, in microseconds", 0,
155           G_USEC_PER_SEC, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
156
157   g_object_class_install_property (gobject_class, PROP_AVERAGE_LATENCY,
158       g_param_spec_int64 ("average-latency", "Running average latency",
159           "The running average latency, in microseconds", 0,
160           G_USEC_PER_SEC, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
161
162   gst_element_class_add_static_pad_template (gstelement_class, &src_template);
163   gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
164
165   gst_element_class_set_static_metadata (gstelement_class, "AudioLatency",
166       "Audio/Util",
167       "Measures the audio latency between the source and the sink",
168       "Nirbheek Chauhan <nirbheek@centricular.com>");
169 }
170
171 static void
172 gst_audiolatency_init (GstAudioLatency * self)
173 {
174   GstPad *srcpad;
175   GstPadTemplate *templ;
176
177   self->send_pts = 0;
178   self->recv_pts = 0;
179   self->print_latency = DEFAULT_PRINT_LATENCY;
180
181   /* Setup sinkpad */
182   self->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
183   gst_pad_set_chain_function (self->sinkpad,
184       GST_DEBUG_FUNCPTR (gst_audiolatency_sink_chain));
185   gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
186
187   /* Setup srcpad */
188   self->audiosrc = gst_element_factory_make ("audiotestsrc", NULL);
189   g_object_set (self->audiosrc, "wave", 8, "samplesperbuffer", 240, NULL);
190   gst_bin_add (GST_BIN (self), self->audiosrc);
191
192   templ = gst_static_pad_template_get (&src_template);
193   srcpad = gst_element_get_static_pad (self->audiosrc, "src");
194   gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BUFFER,
195       (GstPadProbeCallback) gst_audiolatency_src_probe, self, NULL);
196
197   self->srcpad = gst_ghost_pad_new_from_template ("src", srcpad, templ);
198   gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
199   gst_object_unref (srcpad);
200   gst_object_unref (templ);
201
202   GST_LOG_OBJECT (self, "Initialized audiolatency");
203 }
204
205 static gint64
206 gst_audiolatency_get_latency (GstAudioLatency * self)
207 {
208   gint64 last_latency;
209   gint last_latency_idx;
210
211   GST_OBJECT_LOCK (self);
212   /* Decrement index, with wrap-around */
213   last_latency_idx = self->next_latency_idx - 1;
214   if (last_latency_idx < 0)
215     last_latency_idx = GST_AUDIOLATENCY_NUM_LATENCIES - 1;
216
217   last_latency = self->latencies[last_latency_idx];
218   GST_OBJECT_UNLOCK (self);
219
220   return last_latency;
221 }
222
223 static gint64
224 gst_audiolatency_get_average_latency_unlocked (GstAudioLatency * self)
225 {
226   int ii, n = 0;
227   gint64 average = 0;
228
229   for (ii = 0; ii < GST_AUDIOLATENCY_NUM_LATENCIES; ii++) {
230     if (G_LIKELY (self->latencies[ii] > 0))
231       n += 1;
232     average += self->latencies[ii];
233   }
234
235   return average / MAX (n, 1);
236 }
237
238 static gint64
239 gst_audiolatency_get_average_latency (GstAudioLatency * self)
240 {
241   gint64 average;
242
243   GST_OBJECT_LOCK (self);
244   average = gst_audiolatency_get_average_latency_unlocked (self);
245   GST_OBJECT_UNLOCK (self);
246
247   return average;
248 }
249
250 static void
251 gst_audiolatency_set_latency (GstAudioLatency * self, gint64 latency)
252 {
253   gint64 avg_latency;
254
255   GST_OBJECT_LOCK (self);
256   self->latencies[self->next_latency_idx] = latency;
257
258   /* Increment index, with wrap-around */
259   self->next_latency_idx += 1;
260   if (self->next_latency_idx > GST_AUDIOLATENCY_NUM_LATENCIES - 1)
261     self->next_latency_idx = 0;
262
263   avg_latency = gst_audiolatency_get_average_latency_unlocked (self);
264
265   if (self->print_latency)
266     g_print ("last latency: %" G_GINT64_FORMAT "ms, running average: %"
267         G_GINT64_FORMAT "ms\n", latency / 1000, avg_latency / 1000);
268   GST_OBJECT_UNLOCK (self);
269
270   /* Post an element message about it */
271   gst_element_post_message (GST_ELEMENT (self),
272       gst_message_new_element (GST_OBJECT (self),
273           gst_structure_new ("latency", "last-latency", G_TYPE_INT64, latency,
274               "average-latency", G_TYPE_INT64, avg_latency, NULL)));
275 }
276
277 static gint64
278 buffer_has_wave (GstBuffer * buffer, GstPad * pad)
279 {
280   const GstStructure *s;
281   GstCaps *caps;
282   GstMapInfo minfo;
283   guint64 duration;
284   gint64 offset;
285   gint ii, channels, fsize;
286   gfloat *fdata;
287   gboolean ret;
288   GstMemory *memory = NULL;
289
290   switch (gst_buffer_n_memory (buffer)) {
291     case 0:
292       GST_WARNING_OBJECT (pad, "buffer %" GST_PTR_FORMAT "has no memory?",
293           buffer);
294       return -1;
295     case 1:
296       memory = gst_buffer_peek_memory (buffer, 0);
297       ret = gst_memory_map (memory, &minfo, GST_MAP_READ);
298       break;
299     default:
300       ret = gst_buffer_map (buffer, &minfo, GST_MAP_READ);
301   }
302
303   if (!ret) {
304     GST_WARNING_OBJECT (pad, "failed to map buffer %" GST_PTR_FORMAT, buffer);
305     return -1;
306   }
307
308   caps = gst_pad_get_current_caps (pad);
309   s = gst_caps_get_structure (caps, 0);
310   ret = gst_structure_get_int (s, "channels", &channels);
311   gst_caps_unref (caps);
312   if (!ret) {
313     GST_WARNING_OBJECT (pad, "unknown number of channels, can't detect wave");
314     return -1;
315   }
316
317   fdata = (gfloat *) minfo.data;
318   fsize = minfo.size / sizeof (gfloat);
319
320   offset = -1;
321   duration = GST_BUFFER_DURATION (buffer);
322   /* Read only one channel */
323   for (ii = 1; ii < fsize; ii += channels) {
324     if (ABS (fdata[ii]) > 0.7) {
325       /* The waveform probably starts somewhere inside the buffer,
326        * so get the offset in nanoseconds from the buffer pts */
327       offset = gst_util_uint64_scale_int_round (duration, ii, fsize);
328       break;
329     }
330   }
331
332   if (memory)
333     gst_memory_unmap (memory, &minfo);
334   else
335     gst_buffer_unmap (buffer, &minfo);
336
337   /* Return offset in microseconds */
338   return (offset > 0) ? offset / 1000 : -1;
339 }
340
341 static GstPadProbeReturn
342 gst_audiolatency_src_probe (GstPad * pad, GstPadProbeInfo * info,
343     gpointer user_data)
344 {
345   GstAudioLatency *self = user_data;
346   GstBuffer *buffer;
347   gint64 pts, offset;
348
349   if (!(info->type & GST_PAD_PROBE_TYPE_BUFFER))
350     goto out;
351
352   if (GST_STATE (self) != GST_STATE_PLAYING)
353     goto out;
354
355   GST_TRACE ("audiotestsrc pushed out a buffer");
356
357   pts = g_get_monotonic_time ();
358   /* Ticks are once a second, so once we send something, we can skip
359    * checking ~1sec of buffers till the next one. */
360   if (self->send_pts > 0 && pts - self->send_pts <= 950 * 1000)
361     goto out;
362
363   /* Check if buffer contains a waveform */
364   buffer = gst_pad_probe_info_get_buffer (info);
365   offset = buffer_has_wave (buffer, pad);
366   if (offset < 0)
367     goto out;
368
369   pts -= offset;
370   {
371     gint64 after = 0;
372     if (self->send_pts > 0)
373       after = (pts - self->send_pts) / 1000;
374     GST_INFO ("send pts: %" G_GINT64_FORMAT "us (after %" G_GINT64_FORMAT
375         "ms, offset %" G_GINT64_FORMAT "ms)", pts, after, offset / 1000);
376   }
377
378   self->send_pts = pts + offset;
379
380 out:
381   return GST_PAD_PROBE_OK;
382 }
383
384 static GstFlowReturn
385 gst_audiolatency_sink_chain (GstPad * pad, GstObject * parent,
386     GstBuffer * buffer)
387 {
388   GstAudioLatency *self = GST_AUDIOLATENCY (parent);
389   gint64 latency, offset, pts;
390
391   /* Ignore buffers till something gets sent out by us. Fixes a bug where we'd
392    * start out by printing one garbage latency value on Windows. */
393   if (self->send_pts == 0)
394     goto out;
395
396   GST_TRACE_OBJECT (pad, "Got buffer %p", buffer);
397
398   pts = g_get_monotonic_time ();
399   /* Ticks are once a second, so once we receive something, we can skip
400    * checking ~1sec of buffers till the next one. This way we also don't count
401    * the same tick twice for latency measurement. */
402   if (self->recv_pts > 0 && pts - self->recv_pts <= 950 * 1000)
403     goto out;
404
405   offset = buffer_has_wave (buffer, pad);
406   if (offset < 0)
407     goto out;
408
409   self->recv_pts = pts + offset;
410   latency = (self->recv_pts - self->send_pts);
411   gst_audiolatency_set_latency (self, latency);
412
413   GST_INFO ("recv pts: %" G_GINT64_FORMAT "us, latency: %" G_GINT64_FORMAT "ms",
414       self->recv_pts, latency / 1000);
415
416 out:
417   gst_buffer_unref (buffer);
418   return GST_FLOW_OK;
419 }
420
421 /* Element registration */
422 static gboolean
423 plugin_init (GstPlugin * plugin)
424 {
425   GST_DEBUG_CATEGORY_INIT (gst_audiolatency_debug, "audiolatency", 0,
426       "audiolatency");
427
428   return gst_element_register (plugin, "audiolatency", GST_RANK_PRIMARY,
429       GST_TYPE_AUDIOLATENCY);
430 }
431
432 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
433     GST_VERSION_MINOR,
434     audiolatency,
435     "A plugin to measure audio latency",
436     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)