gstalsa.c 46.5 KB
Newer Older
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1
/*
2
3
4
 * Copyright (C) 2001 CodeFactory AB
 * Copyright (C) 2001 Thomas Nyberg <thomas@codefactory.se>
 * Copyright (C) 2001-2002 Andy Wingo <apwingo@eos.ncsu.edu>
5
 * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
6
7
 *
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Library General Public
9
10
11
12
13
 * 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
14
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Library General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Library General Public
18
19
20
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
21

22
23
24
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
25

26
#include <sys/time.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
27

28
#include "gst/gst-i18n-plugin.h"
29
#include "gst/propertyprobe/propertyprobe.h"
30
31
#include "gstalsa.h"
#include "gstalsaclock.h"
32
#include "gstalsamixer.h"
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
33

34
/* GObject functions */
35
36
static void			gst_alsa_class_init		(gpointer               g_class,
                                                                 gpointer               class_data);
37
38
39
40
41
42
43
44
45
46
static void			gst_alsa_init			(GstAlsa *		this);
static void			gst_alsa_dispose		(GObject *		object);
static void			gst_alsa_set_property		(GObject *		object,
								 guint			prop_id,
								 const GValue *		value,
								 GParamSpec *		pspec);
static void			gst_alsa_get_property		(GObject *		object,
								 guint			prop_id,
								 GValue *		value,
								 GParamSpec *		pspec);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
47

48
49
50
/* interface */
static void			gst_alsa_probe_interface_init	(GstPropertyProbeInterface *iface);

51
52
/* GStreamer functions for pads and state changing */
static GstPad *			gst_alsa_request_new_pad	(GstElement *		element,
53
54
55
								 GstPadTemplate *	templ,
								 const gchar *		name);
static GstElementStateReturn	gst_alsa_change_state		(GstElement *		element);
56
static GstClock *		gst_alsa_get_clock		(GstElement *		element);
57
static void			gst_alsa_set_clock		(GstElement *		element,
58
								 GstClock *		clock);
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* ALSA setup / start / stop functions */
static gboolean			gst_alsa_probe_hw_params	(GstAlsa *		this,
								 GstAlsaFormat *	format);
static gboolean			gst_alsa_set_hw_params		(GstAlsa *		this);
static gboolean			gst_alsa_set_sw_params		(GstAlsa *		this);

static gboolean			gst_alsa_open_audio		(GstAlsa *		this);
static gboolean			gst_alsa_start_audio		(GstAlsa *		this);
static gboolean			gst_alsa_drain_audio		(GstAlsa *		this);
static gboolean 		gst_alsa_stop_audio		(GstAlsa *		this);
static gboolean 		gst_alsa_close_audio		(GstAlsa *		this);

/* GStreamer querying, conversion, and format functions */
static const GstFormat * 	gst_alsa_get_formats	 	(GstPad *		pad);
Benjamin Otte's avatar
Benjamin Otte committed
73
static gboolean 		gst_alsa_convert 		(GstAlsa *		this,
74
								 GstFormat		src_format,
Benjamin Otte's avatar
Benjamin Otte committed
75
76
77
78
								 gint64			src_value,
	            						 GstFormat *		dest_format,
								 gint64 *		dest_value);
static gboolean 		gst_alsa_pad_convert 		(GstPad *		pad,
79
								 GstFormat		src_format,
Benjamin Otte's avatar
Benjamin Otte committed
80
81
82
83
84
								 gint64			src_value,
	            						 GstFormat *		dest_format,
								 gint64 *		dest_value);
static const GstQueryType * 	gst_alsa_get_query_types 	(GstPad *		pad);
static gboolean 		gst_alsa_query_func 		(GstElement *		element,
85
								 GstQueryType		type,
Benjamin Otte's avatar
Benjamin Otte committed
86
87
88
								 GstFormat *		format,
								 gint64 *		value);
static gboolean 		gst_alsa_query	 		(GstElement *		element,
89
								 GstQueryType		type,
Benjamin Otte's avatar
Benjamin Otte committed
90
91
92
93
94
95
								 GstFormat *		format,
								 gint64 *		value);
static gboolean 		gst_alsa_pad_query 		(GstPad *		pad,
								 GstQueryType		type,
								 GstFormat *		format,
								 gint64 *		value);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
96

97
/*** TYPE FUNCTIONS ***********************************************************/
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
98
99

GType
100
gst_alsa_get_type (void)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
101
{
102
103
104
105
106
107
108
  static GType alsa_type = 0;

  if (!alsa_type) {
    static const GTypeInfo alsa_info = {
      sizeof (GstAlsaClass),
      NULL,
      NULL,
109
      gst_alsa_class_init,
110
111
112
113
114
115
      NULL,
      NULL,
      sizeof (GstAlsa),
      0,
      (GInstanceInitFunc) gst_alsa_init,
    };
116
117
118
119
120
    static const GInterfaceInfo alsa_probe_info = {
      (GInterfaceInitFunc) gst_alsa_probe_interface_init,
      NULL,
      NULL
    };
121

122
    alsa_type = g_type_register_static (GST_TYPE_ELEMENT, "GstAlsa", &alsa_info, 0);
123
124
125
126

    g_type_add_interface_static (alsa_type,
				 GST_TYPE_PROPERTY_PROBE,
				 &alsa_probe_info);
127
  }
128

129
  return alsa_type;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
130
131
}

132
/*** GOBJECT FUNCTIONS ********************************************************/
133

134
135
136
137
enum
{
  ARG_0,
  ARG_DEVICE,
138
  ARG_DEVICE_NAME,
139
  ARG_PERIODCOUNT,
140
141
  ARG_PERIODSIZE,
  ARG_BUFFERSIZE,
Benjamin Otte's avatar
Benjamin Otte committed
142
  ARG_AUTORECOVER,
143
144
  ARG_MMAP,
  ARG_MAXDISCONT
145
};
146

147
static GstElement *parent_class = NULL;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
148

149
static void
150
gst_alsa_class_init (gpointer g_class, gpointer class_data)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
151
{
152
153
  GObjectClass *object_class;
  GstElementClass *element_class;
154
  GstAlsaClass *klass;
155

156
157
158
  klass = (GstAlsaClass *)g_class;
  object_class = (GObjectClass *) g_class;
  element_class = (GstElementClass *) g_class;
159
160
161
162

  if (parent_class == NULL)
    parent_class = g_type_class_ref (GST_TYPE_ELEMENT);

163
  object_class->dispose = gst_alsa_dispose;
164
165
166
  object_class->get_property = gst_alsa_get_property;
  object_class->set_property = gst_alsa_set_property;

167
  g_object_class_install_property (object_class, ARG_DEVICE,
168
169
170
    g_param_spec_string ("device", "Device",
                         "ALSA device, as defined in an asoundrc",
                         "default", G_PARAM_READWRITE));
171
172
173
174
  g_object_class_install_property (object_class, ARG_DEVICE_NAME,
    g_param_spec_string ("device_name", "Device name",
			 "Name of the device",
			 NULL, G_PARAM_READABLE));
175
  g_object_class_install_property (object_class, ARG_PERIODCOUNT,
176
177
    g_param_spec_int ("period-count", "Period count",
                      "Number of hardware buffers to use",
178
                      2, 64, 2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
179
  g_object_class_install_property (object_class, ARG_PERIODSIZE,
180
181
    g_param_spec_int ("period-size", "Period size",
                      "Number of frames (samples on each channel) in one hardware period",
Benjamin Otte's avatar
Benjamin Otte committed
182
                      2, 8192, 8192, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
183
  g_object_class_install_property (object_class, ARG_BUFFERSIZE,
184
185
    g_param_spec_int ("buffer-size", "Buffer size",
                      "Number of frames the hardware buffer can hold",
Benjamin Otte's avatar
Benjamin Otte committed
186
                      4, 65536, 16384, G_PARAM_READWRITE));
187
  g_object_class_install_property (object_class, ARG_AUTORECOVER,
188
189
    g_param_spec_boolean ("autorecover", "Automatic xrun recovery",
                          "When TRUE tries to reduce processor load on xruns",
190
                          TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
191
  g_object_class_install_property (object_class, ARG_MMAP,
192
193
    g_param_spec_boolean ("mmap", "Use mmap'ed access",
                          "Wether to use mmap (faster) or standard read/write (more compatible)",
Benjamin Otte's avatar
Benjamin Otte committed
194
                          TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
195
  g_object_class_install_property (object_class, ARG_MAXDISCONT,
196
197
    g_param_spec_uint64 ("max-discont", "Maximum Discontinuity",
                         "GStreamer timeunits before the timestamp syncing starts dropping/inserting samples",
198
   /* rounding errors */ 1000, GST_SECOND, GST_ALSA_DEFAULT_DISCONT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
199

Benjamin Otte's avatar
Benjamin Otte committed
200
201
202
203
204
  element_class->change_state    = GST_DEBUG_FUNCPTR (gst_alsa_change_state);
  element_class->query	 	 = GST_DEBUG_FUNCPTR (gst_alsa_query);
  element_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_alsa_request_new_pad);
  element_class->set_clock 	 = GST_DEBUG_FUNCPTR (gst_alsa_set_clock);
  element_class->get_clock 	 = GST_DEBUG_FUNCPTR (gst_alsa_get_clock);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
205
206
}

207
208
static void
gst_alsa_init (GstAlsa *this)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
209
{
210
211
  this->device = g_strdup("default");

Benjamin Otte's avatar
Benjamin Otte committed
212
  GST_FLAG_SET (this, GST_ELEMENT_EVENT_AWARE);
213
  GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
214
}
215

216
217
218
219
220
static void
gst_alsa_dispose (GObject *object)
{
  GstAlsa *this = GST_ALSA (object);

221
222
  g_free (this->device);

223
  if (this->clock)
Benjamin Otte's avatar
Benjamin Otte committed
224
    gst_object_unparent (GST_OBJECT (this->clock));
Benjamin Otte's avatar
Benjamin Otte committed
225
226

  G_OBJECT_CLASS (parent_class)->dispose (object);
227
}
228

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
229
static void
230
gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
231
{
232
  GstAlsa *this;
233
  gint buffer_size;
234
235
236
237
238
239
240
241
242

  this = (GstAlsa *) object;
  switch (prop_id) {
  case ARG_DEVICE:
    if (this->device)
      g_free (this->device);
    this->device = g_strdup (g_value_get_string (value));
    break;
  case ARG_PERIODCOUNT:
243
    g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
244
245
    this->period_count = g_value_get_int (value);
    break;
246
247
248
249
250
251
252
253
  case ARG_PERIODSIZE:
    g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
    this->period_size = g_value_get_int (value);
    break;
  case ARG_BUFFERSIZE:
    g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
    buffer_size = g_value_get_int (value);
    this->period_count = buffer_size / this->period_size;
254
255
256
257
    break;
  case ARG_AUTORECOVER:
    this->autorecover = g_value_get_boolean (value);
    return;
Benjamin Otte's avatar
Benjamin Otte committed
258
259
260
  case ARG_MMAP:
    this->mmap = g_value_get_boolean (value);
    return;
261
262
263
  case ARG_MAXDISCONT:
    this->max_discont = (GstClockTime) g_value_get_uint64 (value);
    return;
264
  default:
Benjamin Otte's avatar
Benjamin Otte committed
265
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
266
267
268
269
270
271
272
273
    return;
  }

  if (GST_STATE (this) == GST_STATE_NULL)
    return;

  if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) {
    gst_alsa_stop_audio (this);
274
    gst_alsa_start_audio (this);
275
  }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
276
277
278
}

static void
279
gst_alsa_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
280
{
281
282
283
284
285
286
287
288
  GstAlsa *this;

  this = (GstAlsa *) object;

  switch (prop_id) {
  case ARG_DEVICE:
    g_value_set_string (value, this->device);
    break;
289
290
291
292
293
294
295
  case ARG_DEVICE_NAME:
    if (GST_STATE (this) != GST_STATE_NULL) {
      g_value_set_string (value, snd_pcm_info_get_name (this->info));
    } else {
      g_value_set_string (value, NULL);
    }
    break;
296
  case ARG_PERIODCOUNT:
297
298
    g_value_set_int (value, this->period_count);
    break;
299
300
  case ARG_PERIODSIZE:
    g_value_set_int (value, this->period_size);
301
    break;
302
303
  case ARG_BUFFERSIZE:
    g_value_set_int (value, this->period_size * this->period_count);
304
305
306
307
    break;
  case ARG_AUTORECOVER:
    g_value_set_boolean (value, this->autorecover);
    break;
Benjamin Otte's avatar
Benjamin Otte committed
308
309
310
  case ARG_MMAP:
    g_value_set_boolean (value, this->mmap);
    break;
311
312
313
  case ARG_MAXDISCONT:
    g_value_set_uint64 (value, (guint64) this->max_discont);
    return;
314
  default:
Benjamin Otte's avatar
Benjamin Otte committed
315
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
316
317
318
    break;
  }
}
319

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
static const GList *
gst_alsa_probe_get_properties (GstPropertyProbe *probe)
{
  GObjectClass *klass = G_OBJECT_GET_CLASS (probe);
  static GList *list = NULL;

  if (!list) {
    list = g_list_append (NULL, g_object_class_find_property (klass, "device"));  }

  return list;
}

static gboolean
gst_alsa_class_probe_devices (GstAlsaClass *klass,
			      gboolean      check)
{
  static gboolean init = FALSE;

  /* I'm pretty sure ALSA has a good way to do this. However, their cool
   * auto-generated documentation is pretty much useless if you try to
   * do function-wise look-ups. */

  if (!init && !check) {
#define MAX_DEVICES 16 /* random number */
    gint num, res;
    gchar *dev;
    snd_pcm_t *pcm;

    for (num = 0; num < MAX_DEVICES; num++) {
      dev = g_strdup_printf ("hw:%d", num);
Ronald S. Bultje's avatar
Ronald S. Bultje committed
350

351
352
      if (!(res = snd_pcm_open (&pcm, dev, 0, SND_PCM_NONBLOCK)) ||
          res == -EBUSY) {
353
        klass->devices = g_list_append (klass->devices, dev);
Ronald S. Bultje's avatar
Ronald S. Bultje committed
354

355
356
	if (res != -EBUSY)
          snd_pcm_close (pcm);
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
      } else {
        g_free (dev);
      }
    }

    init = TRUE;
  }

  return init;
}

static GValueArray *
gst_alsa_class_list_devices (GstAlsaClass *klass)
{
  GValueArray *array;
  GValue value = { 0 };
  GList *item;

  if (!klass->devices)
    return NULL;

  array = g_value_array_new (g_list_length (klass->devices));
  g_value_init (&value, G_TYPE_STRING);
  for (item = klass->devices; item != NULL; item = item->next) {
    g_value_set_string (&value, item->data);
    g_value_array_append (array, &value);
  }
  g_value_unset (&value);
                                                                                
  return array;

}

static void
gst_alsa_probe_probe_property (GstPropertyProbe *probe,
			       guint             prop_id,
			       const GParamSpec *pspec)
{
  GstAlsaClass *klass = GST_ALSA_GET_CLASS (probe);

  switch (prop_id) {
    case ARG_DEVICE:
      gst_alsa_class_probe_devices (klass, FALSE);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
      break;
  }
}

static gboolean
gst_alsa_probe_needs_probe (GstPropertyProbe *probe,
			    guint             prop_id,
			    const GParamSpec *pspec)
{
  GstAlsaClass *klass = GST_ALSA_GET_CLASS (probe);
  gboolean ret = FALSE;

  switch (prop_id) {
    case ARG_DEVICE:
      ret = !gst_alsa_class_probe_devices (klass, TRUE);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
      break;
  }

  return ret;
}

static GValueArray *
gst_alsa_probe_get_values (GstPropertyProbe *probe,
			   guint             prop_id,
			   const GParamSpec *pspec)
{
  GstAlsaClass *klass = GST_ALSA_GET_CLASS (probe);
  GValueArray *array = NULL;

  switch (prop_id) {
    case ARG_DEVICE:
      array = gst_alsa_class_list_devices (klass);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
      break;
  }

  return array;
}

static void
gst_alsa_probe_interface_init (GstPropertyProbeInterface *iface)
{
  iface->get_properties = gst_alsa_probe_get_properties;
  iface->probe_property = gst_alsa_probe_probe_property;
  iface->needs_probe    = gst_alsa_probe_needs_probe;
  iface->get_values     = gst_alsa_probe_get_values;
}

Benjamin Otte's avatar
Benjamin Otte committed
456
/*** GSTREAMER PAD / QUERY / CONVERSION / STATE FUNCTIONS *********************/
457
458

static GstPad *
459
gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name)
460
{
461
  GstAlsa *this;
462
  gint track = 0;
463
464
465
466
467

  g_return_val_if_fail ((this = GST_ALSA (element)), NULL);
  g_return_val_if_fail (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING), NULL);

  if (name) {
468
469
470
471
472
    /* locate the track number in the requested pad name. */
    track = (gint) strtol (name + (strchr(templ->name_template, '%') -
                                   templ->name_template), NULL, 0);
    if (track < 1 || track >= GST_ALSA_MAX_TRACKS) {
      GST_INFO_OBJECT (this, "invalid track requested. (%d)", track);
473
474
475
476
      return NULL;
    }
  }

477
478
479
  /* make sure the requested track is free. */
  if (track > 0 || this->pad[track] != NULL) {
    GST_INFO_OBJECT (this, "requested track %d already in use.", track);
480
481
    return NULL;
  }
482

483
484
485
486
  /* if the user doesn't care, use the lowest available track number */
  if (track == 0) {
    for (track = 1; track < GST_ALSA_MAX_TRACKS; track++) {
      if (this->pad[track] != NULL) goto found_track;
487
488
489
    }
    return NULL;
  }
490

491
492
493
494
495
found_track:
  this->pad[track] = gst_pad_new_from_template (templ, name);

  gst_pad_set_link_function (this->pad[track], gst_alsa_link);
  gst_pad_set_getcaps_function (this->pad[track], gst_alsa_get_caps);
496
  gst_pad_set_fixate_function (this->pad[track], gst_alsa_fixate);
497
498
499
500
501
502
503
504
505

  gst_element_add_pad (GST_ELEMENT (this), this->pad[track]);

  gst_pad_set_convert_function (this->pad[track], gst_alsa_pad_convert);
  gst_pad_set_query_function (this->pad[track], gst_alsa_pad_query);
  gst_pad_set_query_type_function (this->pad[track], gst_alsa_get_query_types);
  gst_pad_set_formats_function (this->pad[track], gst_alsa_get_formats);

  return this->pad[track];
506
507
}

508
509
/* gets the matching alsa format or NULL if none matches */
static GstAlsaFormat *
David Schleef's avatar
David Schleef committed
510
gst_alsa_get_format (const GstStructure *structure)
511
{
512
  const gchar *mimetype;
513
  GstAlsaFormat *ret;
514

515
  if (! (ret = g_new (GstAlsaFormat, 1)))
516
    return NULL;
517

518
  /* we have to differentiate between int and float formats */
David Schleef's avatar
David Schleef committed
519
  mimetype = gst_structure_get_name (structure);
520

521
  if (! strncmp (mimetype, "audio/x-raw-int", 15)) {
522
    gboolean sign;
523
    gint width, depth, endianness;
524

David Schleef's avatar
David Schleef committed
525
526
527
528
529
    /* extract the needed information from the cap */
    if (!(gst_structure_get_int (structure, "width", &width) &&
	  gst_structure_get_int (structure, "depth", &depth) &&
	  gst_structure_get_boolean (structure, "signed", &sign)))
        goto error;
530

531
532
    /* extract endianness if needed */
    if (width > 8) {
David Schleef's avatar
David Schleef committed
533
      if (!gst_structure_get_int (structure, "endianness", &endianness))
534
        goto error;
535
    } else {
536
      endianness = G_BYTE_ORDER;
Benjamin Otte's avatar
Benjamin Otte committed
537
    }
538

539
    ret->format = snd_pcm_build_linear_format (depth, width, sign ? 0 : 1, endianness == G_LITTLE_ENDIAN ? 0 : 1);
540
541
542

  } else if (! strncmp (mimetype, "audio/x-raw-float", 17)) {
    gint width;
543
544

    /* get layout */
David Schleef's avatar
David Schleef committed
545
    if (!gst_structure_get_int (structure, "width", &width))
546
      goto error;
547

548
    /* match layout to format wrt to endianness */
549
    if (width == 32) {
550
      if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
551
        ret->format = SND_PCM_FORMAT_FLOAT_LE;
552
553
554
555
      } else if (G_BYTE_ORDER == G_BIG_ENDIAN) {
        ret->format = SND_PCM_FORMAT_FLOAT_BE;
      } else {
        ret->format = SND_PCM_FORMAT_FLOAT;
Benjamin Otte's avatar
Benjamin Otte committed
556
      }
557
    } else if (width == 64) {
558
      if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
559
        ret->format = SND_PCM_FORMAT_FLOAT64_LE;
560
561
562
563
564
565
566
      } else if (G_BYTE_ORDER == G_BIG_ENDIAN) {
        ret->format = SND_PCM_FORMAT_FLOAT64_BE;
      } else {
        ret->format = SND_PCM_FORMAT_FLOAT64;
      }
    } else {
      goto error;
Benjamin Otte's avatar
Benjamin Otte committed
567
    }
568
  } else if (! strncmp (mimetype, "audio/x-alaw", 12)) {
569
    ret->format = SND_PCM_FORMAT_A_LAW;
570
  } else if (! strncmp (mimetype, "audio/x-mulaw", 13)) {
571
    ret->format = SND_PCM_FORMAT_MU_LAW;
572
  }
573
574

  /* get rate and channels */
David Schleef's avatar
David Schleef committed
575
576
  if (!(gst_structure_get_int (structure, "rate", &ret->rate) &&
	gst_structure_get_int (structure, "channels", &ret->channels)))
577
    goto error;
578

579
  return ret;
580

581
582
583
584
585
586
587
588
589
590
error:
  g_free (ret);
  return NULL;
}

static inline gboolean
gst_alsa_formats_match (GstAlsaFormat *one, GstAlsaFormat *two)
{
  if (one == two) return TRUE;
  if (one == NULL || two == NULL) return FALSE;
591
592
  return (one->format == two->format) &&
         (one->rate == two->rate) &&
593
594
         (one->channels == two->channels);
}
595

596
/* get props for a spec */
597
598
static GstCaps *
gst_alsa_get_caps_internal (snd_pcm_format_t format)
599
600
{
  if (format == SND_PCM_FORMAT_A_LAW) {
David Schleef's avatar
David Schleef committed
601
    return gst_caps_new_simple ("audio/x-alaw", NULL);
602
  } else if (format == SND_PCM_FORMAT_MU_LAW) {
David Schleef's avatar
David Schleef committed
603
    return gst_caps_new_simple ("audio/x-mulaw", NULL);
604
605
  } else if (snd_pcm_format_linear (format)) {
    /* int */
David Schleef's avatar
David Schleef committed
606
607
608
609
610
    GstStructure *structure = gst_structure_new ("audio/x-raw-int",
	    "width",  G_TYPE_INT, (gint) snd_pcm_format_physical_width (format),
            "depth",  G_TYPE_INT, (gint) snd_pcm_format_width (format),
            "signed", G_TYPE_BOOLEAN, snd_pcm_format_signed (format) == 1 ? TRUE : FALSE,
            NULL);
611
612
613
614
    /* endianness */
    if (snd_pcm_format_physical_width (format) > 8) {
      switch (snd_pcm_format_little_endian (format)) {
      case 0:
David Schleef's avatar
David Schleef committed
615
        gst_structure_set (structure, "endianness", G_TYPE_INT, G_BIG_ENDIAN, NULL);
616
617
        break;
      case 1:
David Schleef's avatar
David Schleef committed
618
        gst_structure_set (structure, "endianness", G_TYPE_INT, G_LITTLE_ENDIAN, NULL);
619
620
        break;
      default:
621
        GST_WARNING ("Unknown byte order in sound driver. Continuing by assuming system byte order.");
David Schleef's avatar
David Schleef committed
622
        gst_structure_set (structure, "endianness", G_TYPE_INT, G_BYTE_ORDER, NULL);
623
        break;
Benjamin Otte's avatar
Benjamin Otte committed
624
      }
625
    }
David Schleef's avatar
David Schleef committed
626
    return gst_caps_new_full (structure, NULL);
627
628
629
630
  } else if (snd_pcm_format_float (format)) {
    /* no float with non-platform endianness */
    if (!snd_pcm_format_cpu_endian (format))
      return NULL;
631

David Schleef's avatar
David Schleef committed
632
633
634
635
    return gst_caps_new_simple ("audio/x-raw-float",
	    "width",      G_TYPE_INT, (gint) snd_pcm_format_width (format),
	    "endianness", G_TYPE_INT, G_BYTE_ORDER,
            NULL);
636
  }
637
  return NULL;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
638
639
}

640
static inline void
David Schleef's avatar
David Schleef committed
641
642
643
add_channels (GstStructure *structure, gint min_rate, gint max_rate,
    gint min_channels, gint max_channels)
{
644
  if (min_rate < 0) {
David Schleef's avatar
David Schleef committed
645
646
647
648
649
    min_rate = GST_ALSA_MIN_RATE;
    max_rate = GST_ALSA_MAX_RATE;
  }
  if (max_rate < 0) {
    gst_structure_set (structure, "rate", G_TYPE_INT, min_rate, NULL);
650
  } else {
David Schleef's avatar
David Schleef committed
651
652
    gst_structure_set (structure, "rate", GST_TYPE_INT_RANGE, min_rate,
        max_rate, NULL);
653
  }
654
  if (min_channels < 0) {
David Schleef's avatar
David Schleef committed
655
656
657
658
659
    min_channels = 1;
    max_channels = GST_ALSA_MAX_CHANNELS;
  }
  if (max_channels < 0) {
    gst_structure_set (structure, "channels", G_TYPE_INT, min_channels, NULL);
660
  } else {
David Schleef's avatar
David Schleef committed
661
662
    gst_structure_set (structure, "channels", GST_TYPE_INT_RANGE,
        min_channels, max_channels, NULL);
663
664
  }
}
665
666
667
668
669
670
671

/**
 * Get all available caps.
 * @format: SND_PCM_FORMAT_UNKNOWN for all formats, desired format else
 * @rate: allowed rates if < 0, else desired rate
 * @channels: all allowed values for channels if < 0, else desired channels
 */
672
GstCaps *
673
gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels)
Benjamin Otte's avatar
Benjamin Otte committed
674
{
David Schleef's avatar
David Schleef committed
675
  GstCaps *ret_caps;
676

677
678
  if (format != SND_PCM_FORMAT_UNKNOWN) {
    /* there are some caps set already */
679
680
    ret_caps = gst_alsa_get_caps_internal (format);

681
    /* we can never use a format we can't set caps for */
682
    g_assert (ret_caps != NULL);
David Schleef's avatar
David Schleef committed
683
    g_assert (gst_caps_get_size (ret_caps) == 1);
684

David Schleef's avatar
David Schleef committed
685
    add_channels (gst_caps_get_structure (ret_caps, 0), rate, -1, channels, -1);
Benjamin Otte's avatar
Benjamin Otte committed
686
  } else {
687
    int i;
688
    GstCaps *temp;
689

David Schleef's avatar
David Schleef committed
690
    ret_caps = gst_caps_new_empty ();
691
    for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) {
692
693
      temp = gst_alsa_get_caps_internal (i);

694
      /* can be NULL, because not all alsa formats can be specified as caps */
David Schleef's avatar
David Schleef committed
695
696
697
698
      if (temp != NULL) {
	g_assert (gst_caps_get_size (temp) == 1);
        add_channels (gst_caps_get_structure (temp, 0), rate, -1, channels, -1);
        gst_caps_append (ret_caps, temp);
699
700
701
      }
    }
  }
702
703

  return ret_caps;
704
705
}

706
/* Return better caps when device is open */
707
GstCaps *
David Schleef's avatar
David Schleef committed
708
gst_alsa_get_caps (GstPad *pad)
Benjamin Otte's avatar
Benjamin Otte committed
709
{
710
711
712
713
714
715
716
  GstAlsa *this;
  snd_pcm_hw_params_t *hw_params;
  snd_pcm_format_mask_t *mask;
  int i;
  unsigned int min_rate, max_rate;
  gint min_channels, max_channels;
  GstCaps *ret = NULL;
Benjamin Otte's avatar
Benjamin Otte committed
717

718
  g_return_val_if_fail (pad != NULL, NULL);
Benjamin Otte's avatar
Benjamin Otte committed
719

720
721
722
  this = GST_ALSA (gst_pad_get_parent (pad));

  if (!GST_FLAG_IS_SET (this, GST_ALSA_OPEN))
723
    return gst_caps_copy (GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (pad)));
David Schleef's avatar
David Schleef committed
724
  
725
726
727
728
729
730
731
732
733
  snd_pcm_hw_params_alloca (&hw_params);
  ERROR_CHECK (snd_pcm_hw_params_any (this->handle, hw_params),
               "Broken configuration for this PCM: %s");

  if (((GstElement *) this)->numpads > 1) {
    min_channels = 1;
    max_channels = -1;
  } else {
    ERROR_CHECK (snd_pcm_hw_params_get_channels_min (hw_params, &min_rate),
734
                 "Couldn't get minimum channel count for device %s: %s", this->device);
735
    ERROR_CHECK (snd_pcm_hw_params_get_channels_max (hw_params, &max_rate),
736
                 "Couldn't get maximum channel count for device %s: %s", this->device);
737
738
739
740
741
    min_channels = min_rate;
    max_channels = max_rate > GST_ALSA_MAX_CHANNELS ? GST_ALSA_MAX_CHANNELS : max_rate;
  }

  ERROR_CHECK (snd_pcm_hw_params_get_rate_min (hw_params, &min_rate, &i),
742
               "Couldn't get minimum rate for device %s: %s", this->device);
743
744
  min_rate = min_rate < GST_ALSA_MIN_RATE ? GST_ALSA_MIN_RATE : min_rate + i;
  ERROR_CHECK (snd_pcm_hw_params_get_rate_max (hw_params, &max_rate, &i),
745
               "Couldn't get maximum rate for device %s: %s", this->device);
746
  max_rate = max_rate > GST_ALSA_MAX_RATE ? GST_ALSA_MAX_RATE : max_rate + i;
747

748
749
750
751
  snd_pcm_format_mask_alloca (&mask);
  snd_pcm_hw_params_get_format_mask (hw_params, mask);
  for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) {
    if (snd_pcm_format_mask_test (mask, i)) {
752
      GstCaps *caps = gst_alsa_get_caps_internal (i);
753
      /* we can never use a format we can't set caps for */
David Schleef's avatar
David Schleef committed
754
755
756
      if (caps != NULL) {
	g_assert (gst_caps_get_size (caps) == 1);
        add_channels (gst_caps_get_structure (caps, 0), min_rate, max_rate, min_channels, max_channels);
757
758
759
760
761
	if (ret) {
	  gst_caps_append (ret, caps);
	} else {
	  ret = caps;
	}
762
763
      }
    }
Benjamin Otte's avatar
Benjamin Otte committed
764
  }
765

766
767
768
769
770
771
772
773
774
  if (ret == NULL) {
    GST_WARNING_OBJECT (this, "no supported caps found, returning empty caps");
    return gst_caps_new_empty ();
  } else {
    G_GNUC_UNUSED gchar *str = gst_caps_to_string (ret);
    GST_LOG_OBJECT (this, "get_caps returns %s", str);
    g_free (str);
    return ret;
  }
Benjamin Otte's avatar
Benjamin Otte committed
775
}
776

777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
GstCaps *
gst_alsa_fixate (GstPad *pad, const GstCaps *caps)
{
  GstCaps *newcaps;
  GstStructure *structure;

  newcaps = gst_caps_new_full (gst_structure_copy(gst_caps_get_structure (caps, 0)), NULL);
  structure = gst_caps_get_structure (newcaps, 0);

  if (gst_caps_structure_fixate_field_nearest_int (structure, "rate", 44100)) {
    return newcaps;
  }
  if (gst_caps_structure_fixate_field_nearest_int (structure, "depth", 16)) {
    return newcaps;
  }
  if (gst_caps_structure_fixate_field_nearest_int (structure, "width", 16)) {
    return newcaps;
  }
  if (gst_caps_structure_fixate_field_nearest_int (structure, "channels", 2)) {
    return newcaps;
  }

  gst_caps_free (newcaps);

  return NULL;
}

804
805
/* Negotiates the caps */
GstPadLinkReturn
David Schleef's avatar
David Schleef committed
806
gst_alsa_link (GstPad *pad, const GstCaps *caps)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
807
{
808
809
810
  GstAlsa *this;
  GstAlsaFormat *format;
  GstPadLinkReturn ret;
811

812
813
  g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED);
  g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED);
814

815
  this = GST_ALSA (gst_pad_get_parent (pad));
816

David Schleef's avatar
David Schleef committed
817
818
  if (this->handle == NULL)
    if (!gst_alsa_open_audio (this))
819
      return GST_PAD_LINK_REFUSED;
Benjamin Otte's avatar
Benjamin Otte committed
820

David Schleef's avatar
David Schleef committed
821
822
823
824
825
826
827
828
  format = gst_alsa_get_format (gst_caps_get_structure (caps, 0));
  if (format == NULL)
    return GST_PAD_LINK_REFUSED;
    
  GST_DEBUG ("found format %s", snd_pcm_format_name (format->format));
    
  if (!GST_FLAG_IS_SET (this, GST_ALSA_CAPS_NEGO)) {
    gint i;
829

David Schleef's avatar
David Schleef committed
830
    GST_FLAG_SET (this, GST_ALSA_CAPS_NEGO);
831

David Schleef's avatar
David Schleef committed
832
833
834
835
    if (gst_alsa_formats_match (this->format, format)) {
      ret = GST_PAD_LINK_OK;
      goto out;
    }
836

David Schleef's avatar
David Schleef committed
837
838
839
840
    if (!gst_alsa_probe_hw_params (this, format)) {
      ret = GST_PAD_LINK_REFUSED;
      goto out;
    }
841

David Schleef's avatar
David Schleef committed
842
843
844
845
846
847
848
849
850
    for (i = 0; i < ((GstElement *) this)->numpads; i++) {
      g_assert (this->pad[i] != NULL);
      if (this->pad[i] == pad)
	continue;
      if (gst_pad_try_set_caps (this->pad[i], caps) == GST_PAD_LINK_REFUSED) {
	if (this->format) {
	  GstCaps *old = gst_alsa_caps (this->format->format, this->format->rate, this->format->channels);
	  for (--i; i >= 0; i--) {
	    if (gst_pad_try_set_caps (this->pad[i], old) == GST_PAD_LINK_REFUSED) {
851
              GST_ELEMENT_ERROR (this, CORE, NEGOTIATION, (NULL),
852
                                   ("could not reset caps to a sane value"));
David Schleef's avatar
David Schleef committed
853
854
855
856
	      gst_caps_free (old);
	      break;
	    } else {
	      /* FIXME: unset caps on pads somehow */
857
858
	    }
	  }
David Schleef's avatar
David Schleef committed
859
          gst_caps_free (old);
860
861
862
863
          ret = GST_PAD_LINK_REFUSED;
	  goto out;
        }
      }
864
    }
865

866
    GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO);
867

868
869
870
871
872
    /* sync the params */
    if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this);
    g_free (this->format);
    this->format = format;
    if (! gst_alsa_start_audio (this)) {
873
      GST_ELEMENT_ERROR (this, RESOURCE, SETTINGS, (NULL), (NULL));
874
      return GST_PAD_LINK_REFUSED;
Benjamin Otte's avatar
Benjamin Otte committed
875
876
    }

877
878
    return GST_PAD_LINK_OK;
  }
879

880
  return GST_PAD_LINK_DELAYED;
Benjamin Otte's avatar
Benjamin Otte committed
881

882
883
884
885
886
887
888
889
890
out:
  g_free (format);
  GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO);
  return ret;
}

static GstElementStateReturn
gst_alsa_change_state (GstElement *element)
{
891
  int err;
892
893
894
895
896
897
898
  GstAlsa *this;

  g_return_val_if_fail (element != NULL, FALSE);
  this = GST_ALSA (element);

  switch (GST_STATE_TRANSITION (element)) {
  case GST_STATE_NULL_TO_READY:
899
900
    if (! (GST_FLAG_IS_SET (element, GST_ALSA_OPEN) ||
           gst_alsa_open_audio (this))) return GST_STATE_FAILURE;
901
902
    break;
  case GST_STATE_READY_TO_PAUSED:
903
904
    if (! (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) ||
           gst_alsa_start_audio (this))) return GST_STATE_FAILURE;
905
906
907
908
    this->transmitted = 0;
    break;
  case GST_STATE_PAUSED_TO_PLAYING:
    if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) {
909
      if ((err = snd_pcm_pause (this->handle, 0)) < 0) {
910
        GST_ERROR_OBJECT (this, "Error unpausing sound: %s", snd_strerror (err));
911
        return GST_STATE_FAILURE;
Benjamin Otte's avatar
Benjamin Otte committed
912
      }
913
914
915
    } else if (! (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) ||
	gst_alsa_start_audio (this))) {
      return GST_STATE_FAILURE;
Benjamin Otte's avatar
Benjamin Otte committed
916
    }
917
    gst_alsa_clock_start (this->clock);
918
919
    break;
  case GST_STATE_PLAYING_TO_PAUSED:
920
    if (GST_ALSA_CAPS_IS_SET (this, GST_ALSA_CAPS_PAUSE)) {
921
      if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) {
922
        if ((err = snd_pcm_pause (this->handle, 1)) < 0) {
923
          GST_ERROR_OBJECT (this, "Error pausing sound: %s", snd_strerror (err));
924
925
926
          return GST_STATE_FAILURE;
        }
      }
927
928
929
    } else {
      /* if device doesn't know how to pause, we just stop */
      if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this);
930
    }
931
932
    gst_alsa_clock_stop (this->clock);
    break;
933
  case GST_STATE_PAUSED_TO_READY:
934
    if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this);
935
936
937
938
    g_free (this->format);
    this->format = NULL;
    break;
  case GST_STATE_READY_TO_NULL:
939
    if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN)) gst_alsa_close_audio (this);
940
    break;
Benjamin Otte's avatar
Benjamin Otte committed
941

942
943
  default:
    g_assert_not_reached();
944
  }
Benjamin Otte's avatar
Benjamin Otte committed
945

946
947
948
949
  if (GST_ELEMENT_CLASS (parent_class)->change_state)
    return GST_ELEMENT_CLASS (parent_class)->change_state (element);

  return GST_STATE_SUCCESS;
950
}
951
952
953

static GstClock *
gst_alsa_get_clock (GstElement *element)
954
{ return GST_CLOCK (GST_ALSA (element)->clock); }
955
956
957

static void
gst_alsa_set_clock (GstElement *element, GstClock *clock)
958
{ /* we need this function just so everybody knows we use a clock */ }
959

960
/*** AUDIO PROCESSING *********************************************************/
961

962
inline snd_pcm_sframes_t
963
964
965
966
967
968
969
gst_alsa_update_avail (GstAlsa *this)
{
  snd_pcm_sframes_t avail = snd_pcm_avail_update (this->handle);
  if (avail < 0) {
    if (avail == -EPIPE) {
      gst_alsa_xrun_recovery (this);
    } else {
970
      GST_WARNING_OBJECT (this, "unknown ALSA avail_update return value (%d)", (int) avail);
971
972
973
974
    }
  }
  return avail;
}
975

976
/* returns TRUE, if the loop should go on */
977
inline gboolean
978
979
980
gst_alsa_pcm_wait (GstAlsa *this)
{
  int err;
981

982
983
984
985
  if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) {
    if ((err = snd_pcm_wait (this->handle, 1000)) < 0) {
      if (err == EINTR) {
        /* happens mostly when run under gdb, or when exiting due to a signal */
986
        GST_DEBUG ("got interrupted while waiting");
987
988
989
990
991
        if (gst_element_interrupt (GST_ELEMENT (this))) {
          return TRUE;
        } else {
          return FALSE;
        }
992
      }
993
994
995
996
      if (!gst_alsa_xrun_recovery (this)) {
	GST_ERROR_OBJECT (this, "error waiting for alsa pcm: (%d: %s)", err, snd_strerror (err));
	return FALSE;
      }
Benjamin Otte's avatar
Benjamin Otte committed
997
    }
Benjamin Otte's avatar
Benjamin Otte committed
998
  }
999
1000
  return TRUE;
}
1001

1002
1003
1004
1005
/**
 * error out or make sure we're in SND_PCM_STATE_RUNNING afterwards 
 * return FALSE if we're not
 */
1006
inline gboolean
1007
1008
gst_alsa_start (GstAlsa *this)
{
1009
  GST_DEBUG ("Setting state to RUNNING");
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034

  switch (snd_pcm_state(this->handle)) {
    case SND_PCM_STATE_XRUN:
      gst_alsa_xrun_recovery (this);
      return gst_alsa_start (this);
    case SND_PCM_STATE_SETUP:
      ERROR_CHECK (snd_pcm_prepare (this->handle), "error preparing: %s");
    case SND_PCM_STATE_SUSPENDED:
    case SND_PCM_STATE_PREPARED:
      ERROR_CHECK (snd_pcm_start(this->handle), "error starting playback: %s");
      break;
    case SND_PCM_STATE_PAUSED:
      ERROR_CHECK (snd_pcm_pause (this->handle, 0), "error unpausing: %s");
      break;
    case SND_PCM_STATE_RUNNING:
      break;
    case SND_PCM_STATE_DRAINING:
    case SND_PCM_STATE_OPEN:
      /* this probably happens when someone replugged a pipeline and we're in a
         really weird state because our cothread wasn't busted */
      return FALSE;
    default:
      /* it's a bug when we get here */
      g_assert_not_reached ();
      break;
1035
  }
1036
  return TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1037
}
1038
1039