1 /* GStreamer
2  * Copyright (C) 2016 Centricular Ltd.
3  * Author: Arun Raghavan <arun@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-tinyalsasink
23  * @title: tinyalsasink
24  * @see_also: alsasink
25  *
26  * This element renders raw audio samples using the ALSA audio API via the
27  * tinyalsa library.
28  *
29  * ## Example pipelines
30  * |[
31  * gst-launch-1.0 -v uridecodebin uri=file:///path/to/audio.ogg ! audioconvert ! audioresample ! tinyalsasink
32  * ]| Play an Ogg/Vorbis file and output audio via ALSA using the tinyalsa
33  * library.
34  *
35  */
36 
37 #include <gst/audio/gstaudiobasesink.h>
38 
39 #include <tinyalsa/asoundlib.h>
40 
41 #include "tinyalsasink.h"
42 
43 /* Hardcoding these bitmask values rather than including a kernel header */
44 #define SNDRV_PCM_FORMAT_S8 0
45 #define SNDRV_PCM_FORMAT_S16_LE 2
46 #define SNDRV_PCM_FORMAT_S24_LE 6
47 #define SNDRV_PCM_FORMAT_S32_LE 10
48 #define SNDRV_PCM_FORMAT_ANY            \
49   ((1 << SNDRV_PCM_FORMAT_S8)     |     \
50    (1 << SNDRV_PCM_FORMAT_S16_LE) |     \
51    (1 << SNDRV_PCM_FORMAT_S24_LE) |     \
52    (1 << SNDRV_PCM_FORMAT_S32_LE))
53 
54 GST_DEBUG_CATEGORY_STATIC (tinyalsa_sink_debug);
55 #define GST_CAT_DEFAULT tinyalsa_sink_debug
56 
57 #define parent_class gst_tinyalsa_sink_parent_class
58 G_DEFINE_TYPE (GstTinyalsaSink, gst_tinyalsa_sink, GST_TYPE_AUDIO_SINK);
59 
60 enum
61 {
62   PROP_0,
63   PROP_CARD,
64   PROP_DEVICE,
65   PROP_LAST
66 };
67 
68 #define DEFAULT_CARD 0
69 #define DEFAULT_DEVICE 0
70 
71 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
72     GST_PAD_SINK,
73     GST_PAD_ALWAYS,
74     GST_STATIC_CAPS ("audio/x-raw, "
75         "format = (string) { S16LE, S32LE, S24_32LE, S8 }, "
76         "channels = (int) [ 1, MAX ], "
77         "rate = (int) [ 1, MAX ], " "layout = (string) interleaved"));
78 
79 static void
gst_tinyalsa_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)80 gst_tinyalsa_sink_get_property (GObject * object, guint prop_id,
81     GValue * value, GParamSpec * pspec)
82 {
83   GstTinyalsaSink *sink = GST_TINYALSA_SINK (object);
84 
85   switch (prop_id) {
86     case PROP_CARD:
87       g_value_set_uint (value, sink->card);
88       break;
89 
90     case PROP_DEVICE:
91       g_value_set_uint (value, sink->device);
92       break;
93 
94     default:
95       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96       break;
97   }
98 }
99 
100 static void
gst_tinyalsa_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)101 gst_tinyalsa_sink_set_property (GObject * object, guint prop_id,
102     const GValue * value, GParamSpec * pspec)
103 {
104   GstTinyalsaSink *sink = GST_TINYALSA_SINK (object);
105 
106   switch (prop_id) {
107     case PROP_CARD:
108       sink->card = g_value_get_uint (value);
109       break;
110 
111     case PROP_DEVICE:
112       sink->device = g_value_get_uint (value);
113       break;
114 
115     default:
116       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
117       break;
118   }
119 }
120 
121 static GstCaps *
gst_tinyalsa_sink_getcaps(GstBaseSink * bsink,GstCaps * filter)122 gst_tinyalsa_sink_getcaps (GstBaseSink * bsink, GstCaps * filter)
123 {
124   GstTinyalsaSink *sink = GST_TINYALSA_SINK (bsink);
125   GstCaps *caps = NULL;
126   GValue formats = { 0, };
127   GValue format = { 0, };
128   struct pcm_params *params = NULL;
129   struct pcm_mask *mask;
130   int rate_min, rate_max, channels_min, channels_max;
131   guint16 m;
132 
133   GST_DEBUG_OBJECT (sink, "Querying caps");
134 
135   GST_OBJECT_LOCK (sink);
136 
137   if (sink->cached_caps) {
138     GST_DEBUG_OBJECT (sink, "Returning cached caps");
139     caps = gst_caps_ref (sink->cached_caps);
140     goto done;
141   }
142 
143   if (sink->pcm) {
144     /* We can't query the device while it's open, so return current caps */
145     caps = gst_pad_get_current_caps (GST_BASE_SINK_PAD (bsink));
146     goto done;
147   }
148 
149   params = pcm_params_get (sink->card, sink->device, PCM_OUT);
150   if (!params) {
151     GST_ERROR_OBJECT (sink, "Could not get PCM params");
152     goto done;
153   }
154 
155   mask = pcm_params_get_mask (params, PCM_PARAM_FORMAT);
156   m = (mask->bits[1] << 8) | mask->bits[0];
157 
158   if (!(m & SNDRV_PCM_FORMAT_ANY)) {
159     GST_ERROR_OBJECT (sink, "Could not find any supported format");
160     goto done;
161   }
162 
163   caps = gst_caps_new_empty_simple ("audio/x-raw");
164 
165   g_value_init (&formats, GST_TYPE_LIST);
166   g_value_init (&format, G_TYPE_STRING);
167 
168   if (m & (1 << SNDRV_PCM_FORMAT_S8)) {
169     g_value_set_static_string (&format, "S8");
170     gst_value_list_prepend_value (&formats, &format);
171   }
172   if (m & (1 << SNDRV_PCM_FORMAT_S16_LE)) {
173     g_value_set_static_string (&format, "S16LE");
174     gst_value_list_prepend_value (&formats, &format);
175   }
176   if (m & (1 << SNDRV_PCM_FORMAT_S24_LE)) {
177     g_value_set_static_string (&format, "S24_32LE");
178     gst_value_list_prepend_value (&formats, &format);
179   }
180   if (m & (1 << SNDRV_PCM_FORMAT_S32_LE)) {
181     g_value_set_static_string (&format, "S32LE");
182     gst_value_list_prepend_value (&formats, &format);
183   }
184 
185   gst_caps_set_value (caps, "format", &formats);
186 
187   g_value_unset (&format);
188   g_value_unset (&formats);
189 
190   /* This is a bit of a lie, since the device likely only supports some
191    * standard rates in this range. We should probably filter the range to
192    * those, standard audio rates but even that isn't guaranteed to be accurate.
193    */
194   rate_min = pcm_params_get_min (params, PCM_PARAM_RATE);
195   rate_max = pcm_params_get_max (params, PCM_PARAM_RATE);
196 
197   if (rate_min == rate_max)
198     gst_caps_set_simple (caps, "rate", G_TYPE_INT, rate_min, NULL);
199   else
200     gst_caps_set_simple (caps, "rate", GST_TYPE_INT_RANGE, rate_min, rate_max,
201         NULL);
202 
203   channels_min = pcm_params_get_min (params, PCM_PARAM_CHANNELS);
204   channels_max = pcm_params_get_max (params, PCM_PARAM_CHANNELS);
205 
206   if (channels_min == channels_max)
207     gst_caps_set_simple (caps, "channels", G_TYPE_INT, channels_min, NULL);
208   else
209     gst_caps_set_simple (caps, "channels", GST_TYPE_INT_RANGE, channels_min,
210         channels_max, NULL);
211 
212   gst_caps_replace (&sink->cached_caps, caps);
213 
214 done:
215   GST_OBJECT_UNLOCK (sink);
216 
217   GST_DEBUG_OBJECT (sink, "Got caps %" GST_PTR_FORMAT, caps);
218 
219   if (caps && filter) {
220     GstCaps *intersection =
221         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
222 
223     gst_caps_unref (caps);
224     caps = intersection;
225   }
226 
227   if (params)
228     pcm_params_free (params);
229 
230   return caps;
231 }
232 
233 static gboolean
gst_tinyalsa_sink_open(GstAudioSink * asink)234 gst_tinyalsa_sink_open (GstAudioSink * asink)
235 {
236   /* Nothing to do here, we can't call pcm_open() till we have stream
237    * parameters available */
238   return TRUE;
239 }
240 
241 static enum pcm_format
pcm_format_from_gst(GstAudioFormat format)242 pcm_format_from_gst (GstAudioFormat format)
243 {
244   switch (format) {
245     case GST_AUDIO_FORMAT_S8:
246       return PCM_FORMAT_S8;
247 
248     case GST_AUDIO_FORMAT_S16LE:
249       return PCM_FORMAT_S16_LE;
250 
251     case GST_AUDIO_FORMAT_S24_32LE:
252       return PCM_FORMAT_S24_LE;
253 
254     case GST_AUDIO_FORMAT_S32LE:
255       return PCM_FORMAT_S32_LE;
256 
257     default:
258       g_assert_not_reached ();
259   }
260 }
261 
262 static void
pcm_config_from_spec(struct pcm_config * config,const GstAudioRingBufferSpec * spec)263 pcm_config_from_spec (struct pcm_config *config,
264     const GstAudioRingBufferSpec * spec)
265 {
266   gint64 frames;
267 
268   config->format = pcm_format_from_gst (GST_AUDIO_INFO_FORMAT (&spec->info));
269   config->channels = GST_AUDIO_INFO_CHANNELS (&spec->info);
270   config->rate = GST_AUDIO_INFO_RATE (&spec->info);
271 
272   gst_audio_info_convert (&spec->info,
273       GST_FORMAT_TIME, spec->latency_time * GST_USECOND,
274       GST_FORMAT_DEFAULT /* frames */ , &frames);
275 
276   config->period_size = frames;
277   config->period_count = spec->buffer_time / spec->latency_time;
278 }
279 
280 static gboolean
gst_tinyalsa_sink_prepare(GstAudioSink * asink,GstAudioRingBufferSpec * spec)281 gst_tinyalsa_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
282 {
283   GstTinyalsaSink *sink = GST_TINYALSA_SINK (asink);
284   struct pcm_config config = { 0, };
285   struct pcm_params *params = NULL;
286   int period_size_min, period_size_max;
287   int periods_min, periods_max;
288 
289   pcm_config_from_spec (&config, spec);
290 
291   GST_DEBUG_OBJECT (sink, "Requesting %u periods of %u frames",
292       config.period_count, config.period_size);
293 
294   params = pcm_params_get (sink->card, sink->device, PCM_OUT);
295   if (!params)
296     GST_ERROR_OBJECT (sink, "Could not get PCM params");
297 
298   period_size_min = pcm_params_get_min (params, PCM_PARAM_PERIOD_SIZE);
299   period_size_max = pcm_params_get_max (params, PCM_PARAM_PERIOD_SIZE);
300   periods_min = pcm_params_get_min (params, PCM_PARAM_PERIODS);
301   periods_max = pcm_params_get_max (params, PCM_PARAM_PERIODS);
302 
303   pcm_params_free (params);
304 
305   /* Snap period size/count to the permitted range */
306   config.period_size =
307       CLAMP (config.period_size, period_size_min, period_size_max);
308   config.period_count = CLAMP (config.period_count, periods_min, periods_max);
309 
310   /* mutex with getcaps */
311   GST_OBJECT_LOCK (sink);
312 
313   sink->pcm = pcm_open (sink->card, sink->device, PCM_OUT | PCM_NORESTART,
314       &config);
315 
316   GST_OBJECT_UNLOCK (sink);
317 
318   if (!sink->pcm || !pcm_is_ready (sink->pcm)) {
319     GST_ERROR_OBJECT (sink, "Could not open device: %s",
320         pcm_get_error (sink->pcm));
321     goto fail;
322   }
323 
324   if (pcm_prepare (sink->pcm) < 0) {
325     GST_ERROR_OBJECT (sink, "Could not prepare device: %s",
326         pcm_get_error (sink->pcm));
327     goto fail;
328   }
329 
330   spec->segsize = pcm_frames_to_bytes (sink->pcm, config.period_size);
331   spec->segtotal = config.period_count;
332 
333   GST_DEBUG_OBJECT (sink, "Configured for %u periods of %u frames",
334       config.period_count, config.period_size);
335 
336   return TRUE;
337 
338 fail:
339   if (sink->pcm)
340     pcm_close (sink->pcm);
341 
342   return FALSE;
343 }
344 
345 static gboolean
gst_tinyalsa_sink_unprepare(GstAudioSink * asink)346 gst_tinyalsa_sink_unprepare (GstAudioSink * asink)
347 {
348   GstTinyalsaSink *sink = GST_TINYALSA_SINK (asink);
349 
350   if (pcm_stop (sink->pcm) < 0) {
351     GST_ERROR_OBJECT (sink, "Could not stop device: %s",
352         pcm_get_error (sink->pcm));
353   }
354 
355   /* mutex with getcaps */
356   GST_OBJECT_LOCK (sink);
357 
358   if (pcm_close (sink->pcm)) {
359     GST_ERROR_OBJECT (sink, "Could not close device: %s",
360         pcm_get_error (sink->pcm));
361     return FALSE;
362   }
363 
364   sink->pcm = NULL;
365 
366   gst_caps_replace (&sink->cached_caps, NULL);
367 
368   GST_OBJECT_UNLOCK (sink);
369 
370   GST_DEBUG_OBJECT (sink, "Device unprepared");
371 
372   return TRUE;
373 }
374 
375 static gboolean
gst_tinyalsa_sink_close(GstAudioSink * asink)376 gst_tinyalsa_sink_close (GstAudioSink * asink)
377 {
378   /* Nothing to do here, see gst_tinyalsa_sink_open() */
379   return TRUE;
380 }
381 
382 static gint
gst_tinyalsa_sink_write(GstAudioSink * asink,gpointer data,guint length)383 gst_tinyalsa_sink_write (GstAudioSink * asink, gpointer data, guint length)
384 {
385   GstTinyalsaSink *sink = GST_TINYALSA_SINK (asink);
386   int ret;
387 
388 again:
389   GST_DEBUG_OBJECT (sink, "Starting write");
390 
391   ret = pcm_write (sink->pcm, data, length);
392   if (ret == -EPIPE) {
393     GST_WARNING_OBJECT (sink, "Got an underrun");
394 
395     if (pcm_prepare (sink->pcm) < 0) {
396       GST_ERROR_OBJECT (sink, "Could not prepare device: %s",
397           pcm_get_error (sink->pcm));
398       return -1;
399     }
400 
401     goto again;
402 
403   } else if (ret < 0) {
404     GST_ERROR_OBJECT (sink, "Could not write data to device: %s",
405         pcm_get_error (sink->pcm));
406     return -1;
407   }
408 
409   GST_DEBUG_OBJECT (sink, "Wrote %u bytes", length);
410 
411   return length;
412 }
413 
414 static void
gst_tinyalsa_sink_reset(GstAudioSink * asink)415 gst_tinyalsa_sink_reset (GstAudioSink * asink)
416 {
417   GstTinyalsaSink *sink = GST_TINYALSA_SINK (asink);
418 
419   if (pcm_stop (sink->pcm) < 0) {
420     GST_ERROR_OBJECT (sink, "Could not stop device: %s",
421         pcm_get_error (sink->pcm));
422   }
423 
424   if (pcm_prepare (sink->pcm) < 0) {
425     GST_ERROR_OBJECT (sink, "Could not prepare device: %s",
426         pcm_get_error (sink->pcm));
427   }
428 }
429 
430 static guint
gst_tinyalsa_sink_delay(GstAudioSink * asink)431 gst_tinyalsa_sink_delay (GstAudioSink * asink)
432 {
433   GstTinyalsaSink *sink = GST_TINYALSA_SINK (asink);
434   int delay;
435 
436   delay = pcm_get_delay (sink->pcm);
437 
438   if (delay < 0) {
439     /* This might happen before the stream has started */
440     GST_DEBUG_OBJECT (sink, "Got negative delay");
441     delay = 0;
442   } else
443     GST_DEBUG_OBJECT (sink, "Got delay of %u", delay);
444 
445   return delay;
446 }
447 
448 static void
gst_tinyalsa_sink_class_init(GstTinyalsaSinkClass * klass)449 gst_tinyalsa_sink_class_init (GstTinyalsaSinkClass * klass)
450 {
451   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
452   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
453   GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass);
454   GstAudioSinkClass *audiosink_class = GST_AUDIO_SINK_CLASS (klass);
455 
456   gobject_class->get_property =
457       GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_get_property);
458   gobject_class->set_property =
459       GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_set_property);
460 
461   basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_getcaps);
462 
463   audiosink_class->open = GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_open);
464   audiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_prepare);
465   audiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_unprepare);
466   audiosink_class->close = GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_close);
467   audiosink_class->write = GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_write);
468   audiosink_class->reset = GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_reset);
469   audiosink_class->delay = GST_DEBUG_FUNCPTR (gst_tinyalsa_sink_delay);
470 
471   gst_element_class_set_static_metadata (element_class,
472       "tinyalsa Audio Sink",
473       "Sink/Audio", "Plays audio to an ALSA device",
474       "Arun Raghavan <arun@centricular.com>");
475 
476   gst_element_class_add_static_pad_template (element_class, &sink_template);
477 
478   g_object_class_install_property (gobject_class,
479       PROP_CARD,
480       g_param_spec_uint ("card", "Card", "The ALSA card to use",
481           0, G_MAXUINT, DEFAULT_CARD,
482           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
483 
484   g_object_class_install_property (gobject_class,
485       PROP_DEVICE,
486       g_param_spec_uint ("device", "Device", "The ALSA device to use",
487           0, G_MAXUINT, DEFAULT_CARD,
488           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
489 
490   GST_DEBUG_CATEGORY_INIT (tinyalsa_sink_debug, "tinyalsasink", 0,
491       "tinyalsa Sink");
492 }
493 
494 static void
gst_tinyalsa_sink_init(GstTinyalsaSink * sink)495 gst_tinyalsa_sink_init (GstTinyalsaSink * sink)
496 {
497   sink->card = DEFAULT_CARD;
498   sink->device = DEFAULT_DEVICE;
499 
500   sink->cached_caps = NULL;
501 }
502