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