gstalsa.c 46.4 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, 0))) {
        klass->devices = g_list_append (klass->devices, dev);
Ronald S. Bultje's avatar
Ronald S. Bultje committed
353

354
355
356
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
        snd_pcm_close (pcm);
      } 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
454
/*** GSTREAMER PAD / QUERY / CONVERSION / STATE FUNCTIONS *********************/
455
456

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

  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) {
466
467
468
469
470
    /* 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);
471
472
473
474
      return NULL;
    }
  }

475
476
477
  /* 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);
478
479
    return NULL;
  }
480

481
482
483
484
  /* 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;
485
486
487
    }
    return NULL;
  }
488

489
490
491
492
493
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);
494
  gst_pad_set_fixate_function (this->pad[track], gst_alsa_fixate);
495
496
497
498
499
500
501
502
503

  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];
504
505
}

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

513
  if (! (ret = g_new (GstAlsaFormat, 1)))
514
    return NULL;
515

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

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

David Schleef's avatar
David Schleef committed
523
524
525
526
527
    /* 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;
528

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

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

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

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

546
    /* match layout to format wrt to endianness */
547
    if (width == 32) {
548
      if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
549
        ret->format = SND_PCM_FORMAT_FLOAT_LE;
550
551
552
553
      } 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
554
      }
555
    } else if (width == 64) {
556
      if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
557
        ret->format = SND_PCM_FORMAT_FLOAT64_LE;
558
559
560
561
562
563
564
      } 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
565
    }
566
  } else if (! strncmp (mimetype, "audio/x-alaw", 12)) {
567
    ret->format = SND_PCM_FORMAT_A_LAW;
568
  } else if (! strncmp (mimetype, "audio/x-mulaw", 13)) {
569
    ret->format = SND_PCM_FORMAT_MU_LAW;
570
  }
571
572

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

577
  return ret;
578

579
580
581
582
583
584
585
586
587
588
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;
589
590
  return (one->format == two->format) &&
         (one->rate == two->rate) &&
591
592
         (one->channels == two->channels);
}
593

594
/* get props for a spec */
595
596
static GstCaps *
gst_alsa_get_caps_internal (snd_pcm_format_t format)
597
598
{
  if (format == SND_PCM_FORMAT_A_LAW) {
David Schleef's avatar
David Schleef committed
599
    return gst_caps_new_simple ("audio/x-alaw", NULL);
600
  } else if (format == SND_PCM_FORMAT_MU_LAW) {
David Schleef's avatar
David Schleef committed
601
    return gst_caps_new_simple ("audio/x-mulaw", NULL);
602
603
  } else if (snd_pcm_format_linear (format)) {
    /* int */
David Schleef's avatar
David Schleef committed
604
605
606
607
608
    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);
609
610
611
612
    /* 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
613
        gst_structure_set (structure, "endianness", G_TYPE_INT, G_BIG_ENDIAN, NULL);
614
615
        break;
      case 1:
David Schleef's avatar
David Schleef committed
616
        gst_structure_set (structure, "endianness", G_TYPE_INT, G_LITTLE_ENDIAN, NULL);
617
618
        break;
      default:
619
        GST_WARNING ("Unknown byte order in sound driver. Continuing by assuming system byte order.");
David Schleef's avatar
David Schleef committed
620
        gst_structure_set (structure, "endianness", G_TYPE_INT, G_BYTE_ORDER, NULL);
621
        break;
Benjamin Otte's avatar
Benjamin Otte committed
622
      }
623
    }
David Schleef's avatar
David Schleef committed
624
    return gst_caps_new_full (structure, NULL);
625
626
627
628
  } else if (snd_pcm_format_float (format)) {
    /* no float with non-platform endianness */
    if (!snd_pcm_format_cpu_endian (format))
      return NULL;
629

David Schleef's avatar
David Schleef committed
630
631
632
633
    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);
634
  }
635
  return NULL;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
636
637
}

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

/**
 * 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
 */
670
GstCaps *
671
gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels)
Benjamin Otte's avatar
Benjamin Otte committed
672
{
David Schleef's avatar
David Schleef committed
673
  GstCaps *ret_caps;
674

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

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

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

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

692
      /* can be NULL, because not all alsa formats can be specified as caps */
David Schleef's avatar
David Schleef committed
693
694
695
696
      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);
697
698
699
      }
    }
  }
700
701

  return ret_caps;
702
703
}

704
/* Return better caps when device is open */
705
GstCaps *
David Schleef's avatar
David Schleef committed
706
gst_alsa_get_caps (GstPad *pad)
Benjamin Otte's avatar
Benjamin Otte committed
707
{
708
709
710
711
712
713
714
  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
715

716
  g_return_val_if_fail (pad != NULL, NULL);
Benjamin Otte's avatar
Benjamin Otte committed
717

718
719
720
  this = GST_ALSA (gst_pad_get_parent (pad));

  if (!GST_FLAG_IS_SET (this, GST_ALSA_OPEN))
721
    return gst_caps_copy (GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (pad)));
David Schleef's avatar
David Schleef committed
722
  
723
724
725
726
727
728
729
730
731
  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),
732
                 "Couldn't get minimum channel count for device %s: %s", this->device);
733
    ERROR_CHECK (snd_pcm_hw_params_get_channels_max (hw_params, &max_rate),
734
                 "Couldn't get maximum channel count for device %s: %s", this->device);
735
736
737
738
739
    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),
740
               "Couldn't get minimum rate for device %s: %s", this->device);
741
742
  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),
743
               "Couldn't get maximum rate for device %s: %s", this->device);
744
  max_rate = max_rate > GST_ALSA_MAX_RATE ? GST_ALSA_MAX_RATE : max_rate + i;
745

746
747
748
749
  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)) {
750
      GstCaps *caps = gst_alsa_get_caps_internal (i);
751
      /* we can never use a format we can't set caps for */
David Schleef's avatar
David Schleef committed
752
753
754
      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);
755
756
757
758
759
	if (ret) {
	  gst_caps_append (ret, caps);
	} else {
	  ret = caps;
	}
760
761
      }
    }
Benjamin Otte's avatar
Benjamin Otte committed
762
  }
763

764
765
766
767
768
769
770
771
772
  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
773
}
774

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
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;
}

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

810
811
  g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED);
  g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED);
812

813
  this = GST_ALSA (gst_pad_get_parent (pad));
814

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

David Schleef's avatar
David Schleef committed
819
820
821
822
823
824
825
826
  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;
827

David Schleef's avatar
David Schleef committed
828
    GST_FLAG_SET (this, GST_ALSA_CAPS_NEGO);
829

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

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

David Schleef's avatar
David Schleef committed
840
841
842
843
844
845
846
847
848
    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) {
849
              GST_ELEMENT_ERROR (this, CORE, NEGOTIATION, (NULL),
850
                                   ("could not reset caps to a sane value"));
David Schleef's avatar
David Schleef committed
851
852
853
854
	      gst_caps_free (old);
	      break;
	    } else {
	      /* FIXME: unset caps on pads somehow */
855
856
	    }
	  }
David Schleef's avatar
David Schleef committed
857
          gst_caps_free (old);
858
859
860
861
          ret = GST_PAD_LINK_REFUSED;
	  goto out;
        }
      }
862
    }
863

864
    GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO);
865

866
867
868
869
870
    /* 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)) {
871
      GST_ELEMENT_ERROR (this, RESOURCE, SETTINGS, (NULL), (NULL));
872
      return GST_PAD_LINK_REFUSED;
Benjamin Otte's avatar
Benjamin Otte committed
873
874
    }

875
876
    return GST_PAD_LINK_OK;
  }
877

878
  return GST_PAD_LINK_DELAYED;
Benjamin Otte's avatar
Benjamin Otte committed
879

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

static GstElementStateReturn
gst_alsa_change_state (GstElement *element)
{
889
  int err;
890
891
892
893
894
895
896
  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:
897
898
    if (! (GST_FLAG_IS_SET (element, GST_ALSA_OPEN) ||
           gst_alsa_open_audio (this))) return GST_STATE_FAILURE;
899
900
    break;
  case GST_STATE_READY_TO_PAUSED:
901
902
    if (! (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) ||
           gst_alsa_start_audio (this))) return GST_STATE_FAILURE;
903
904
905
906
    this->transmitted = 0;
    break;
  case GST_STATE_PAUSED_TO_PLAYING:
    if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) {
907
      if ((err = snd_pcm_pause (this->handle, 0)) < 0) {
908
        GST_ERROR_OBJECT (this, "Error unpausing sound: %s", snd_strerror (err));
909
        return GST_STATE_FAILURE;
Benjamin Otte's avatar
Benjamin Otte committed
910
      }
911
912
913
    } 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
914
    }
915
    gst_alsa_clock_start (this->clock);
916
917
    break;
  case GST_STATE_PLAYING_TO_PAUSED:
918
    if (GST_ALSA_CAPS_IS_SET (this, GST_ALSA_CAPS_PAUSE)) {
919
      if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) {
920
        if ((err = snd_pcm_pause (this->handle, 1)) < 0) {
921
          GST_ERROR_OBJECT (this, "Error pausing sound: %s", snd_strerror (err));
922
923
924
          return GST_STATE_FAILURE;
        }
      }
925
926
927
    } 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);
928
    }
929
930
    gst_alsa_clock_stop (this->clock);
    break;
931
  case GST_STATE_PAUSED_TO_READY:
932
    if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this);
933
934
935
936
    g_free (this->format);
    this->format = NULL;
    break;
  case GST_STATE_READY_TO_NULL:
937
    if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN)) gst_alsa_close_audio (this);
938
    break;
Benjamin Otte's avatar
Benjamin Otte committed
939

940
941
  default:
    g_assert_not_reached();
942
  }
Benjamin Otte's avatar
Benjamin Otte committed
943

944
945
946
947
  if (GST_ELEMENT_CLASS (parent_class)->change_state)
    return GST_ELEMENT_CLASS (parent_class)->change_state (element);

  return GST_STATE_SUCCESS;
948
}
949
950
951

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

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

958
/*** AUDIO PROCESSING *********************************************************/
959

960
inline snd_pcm_sframes_t
961
962
963
964
965
966
967
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 {
968
      GST_WARNING_OBJECT (this, "unknown ALSA avail_update return value (%d)", (int) avail);
969
970
971
972
    }
  }
  return avail;
}
973

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

980
981
982
983
  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 */
984
        GST_DEBUG ("got interrupted while waiting");
985
986
987
988
989
        if (gst_element_interrupt (GST_ELEMENT (this))) {
          return TRUE;
        } else {
          return FALSE;
        }
990
      }
991
992
993
994
      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
995
    }
Benjamin Otte's avatar
Benjamin Otte committed
996
  }
997
998
  return TRUE;
}
999

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

  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;
1033
  }
1034
  return TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1035
}
1036
1037

gboolean
1038
gst_alsa_xrun_recovery (GstAlsa *this)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
1039
{