Commit 730ff798 authored by Benjamin Otte's avatar Benjamin Otte
Browse files

fixing alsa step 2: complete rewrite of data transfer. The whole stuff is...

fixing alsa step 2: complete rewrite of data transfer. The whole stuff is clean enough to go from there now.

Original commit message from CVS:
fixing alsa step 2: complete rewrite of data transfer. The whole stuff is clean enough to go from there now.
License change to LGPL, since no copied code is left now.
Missing:
- alsasrc
- resetting format
- corner cases
- testsuite
parent 2534ded8
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
This plugin was originally written by Thomas Nyberg <thomas@codefactory.se> for This plugin was originally written by Thomas Nyberg <thomas@codefactory.se> for
ALSA 0.5.x. It was updated and changed quite a bit in September 2001 by Andy ALSA 0.5.x. It was updated and changed quite a bit in September 2001 by Andy
Wingo for use with ALSA 0.9.x. Wingo for use with ALSA 0.9.x. It was rewritten in great part again by Benjamin
Otte in January/February 2003.
1.0 Introduction 1.0 Introduction
---------------- ----------------
...@@ -44,21 +45,18 @@ My particular card has 4 channel out and 2 channel in. As you can see, each of ...@@ -44,21 +45,18 @@ My particular card has 4 channel out and 2 channel in. As you can see, each of
these devices are stereo. The alsasink and alsasrc elements correspond to the these devices are stereo. The alsasink and alsasrc elements correspond to the
default alsa devices. default alsa devices.
3.0 License 3.0 Where to look for other ALSA drivers
----------- ----------------------------------------
ALSA 0.9.x isn't very well documented, so I had to rely on a number of different
sources for guidance. One of the main sources of inspiration was Paul Davis'
audioengine (available in quasimodo's cvs tree,
http://quasimodo.sourceforge.net/), from which I took a large amount of code.
Since that project is licensed under the GPL, it was only right that I license
the updated version of this plugin under the GPL as well. What, in my
understanding, this means for the end user is that you cannot use the ALSA
plugin with a non-GPL program. If in doubt, please refer to the COPYING file
located in this directory.
ALSA 0.9 isn't very well documented, so we had to rely on a number of different
sources for guidance. Sources of inspiration include JACK's alsa driver
(available in jack's cvs tree, http://jack.sourceforge.net/), the alsa2 driver
of mplayer (http://www.mplayerhq.hu) and the pcm.c example from the ALSA library
itself.
(http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html)
4.0 License
-----------
This plugin is licensed under the Lesser General Public License (LGPL). See the
file COPYING in the top source directory.
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* Copyright (C) 2001 CodeFactory AB * Copyright (C) 2001 CodeFactory AB
* Copyright (C) 2001 Thomas Nyberg <thomas@codefactory.se> * Copyright (C) 2001 Thomas Nyberg <thomas@codefactory.se>
* Copyright (C) 2001-2002 Andy Wingo <apwingo@eos.ncsu.edu> * Copyright (C) 2001-2002 Andy Wingo <apwingo@eos.ncsu.edu>
* Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public * modify it under the terms of the GNU General Public
...@@ -22,6 +23,33 @@ ...@@ -22,6 +23,33 @@
#include <sys/time.h> #include <sys/time.h>
#include "gstalsa.h" #include "gstalsa.h"
/* error checking for standard alsa functions */
#ifdef G_HAVE_ISO_VARARGS
#define ERROR_CHECK(value, ...) G_STMT_START{ \
int err = (value); \
if (err < 0) { \
g_warning ( __VA_ARGS__, snd_strerror (err)); \
return FALSE; \
} \
}G_STMT_END
#elif defined(G_HAVE_GNUC_VARARGS)
#define ERROR_CHECK(value, args...) G_STMT_START{ \
int err = (value); \
if (err < 0) { \
g_warning ( ## args, snd_strerror (err)); \
return FALSE; \
} \
}G_STMT_END
#else
#define ERROR_CHECK(value, args...) G_STMT_START{ \
int err = (value); \
if (err < 0) { \
g_warning (snd_strerror (err)); \
return FALSE; \
} \
}G_STMT_END
#endif
/* elementfactory information */ /* elementfactory information */
static GstElementDetails gst_alsa_sink_details = { static GstElementDetails gst_alsa_sink_details = {
"Alsa Sink", "Alsa Sink",
...@@ -30,8 +58,9 @@ static GstElementDetails gst_alsa_sink_details = { ...@@ -30,8 +58,9 @@ static GstElementDetails gst_alsa_sink_details = {
"Output to a sound card via ALSA", "Output to a sound card via ALSA",
VERSION, VERSION,
"Thomas Nyberg <thomas@codefactory.se>, " "Thomas Nyberg <thomas@codefactory.se>, "
"Andy Wingo <apwingo@eos.ncsu.edu>", "Andy Wingo <apwingo@eos.ncsu.edu>"
"(C) 2001 " "Benjamin Otte <in7y118@public.uni-hamburg.de>",
"(C) 2001-2003"
}; };
/* elementfactory information */ /* elementfactory information */
...@@ -42,8 +71,9 @@ static GstElementDetails gst_alsa_src_details = { ...@@ -42,8 +71,9 @@ static GstElementDetails gst_alsa_src_details = {
"Read from a sound card via ALSA", "Read from a sound card via ALSA",
VERSION, VERSION,
"Thomas Nyberg <thomas@codefactory.se>, " "Thomas Nyberg <thomas@codefactory.se>, "
"Andy Wingo <apwingo@eos.ncsu.edu>", "Andy Wingo <apwingo@eos.ncsu.edu>"
"(C) 2001" "Benjamin Otte <in7y118@public.uni-hamburg.de>",
"(C) 2001-2003"
}; };
/* GObject functions */ /* GObject functions */
...@@ -65,30 +95,20 @@ static GstCaps *gst_alsa_caps (snd_pcm_format_t format, gint rate, gint ...@@ -65,30 +95,20 @@ static GstCaps *gst_alsa_caps (snd_pcm_format_t format, gint rate, gint
static GstElementStateReturn gst_alsa_change_state (GstElement *element); static GstElementStateReturn gst_alsa_change_state (GstElement *element);
/* audio processing functions */ /* audio processing functions */
static void gst_alsa_loop (GstElement *element); static void gst_alsa_sink_loop (GstElement *element);
static void gst_alsa_xrun_recovery (GstAlsa *this); static void gst_alsa_xrun_recovery (GstAlsa *this);
static gboolean gst_alsa_src_process (GstAlsa *this, snd_pcm_uframes_t frames); static gboolean gst_alsa_sink_check_event (GstAlsa *this, gint pad_nr);
static gboolean gst_alsa_sink_process (GstAlsa *this, snd_pcm_uframes_t frames);
static void gst_alsa_sink_check_event (GstAlsa *this, GstAlsaPad *pad);
/* alsa setup / start / stop functions */ /* alsa setup / start / stop functions */
static gboolean gst_alsa_set_params (GstAlsa *this); 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_open_audio (GstAlsa *this);
static gboolean gst_alsa_start_audio (GstAlsa *this); static gboolean gst_alsa_start_audio (GstAlsa *this);
static void gst_alsa_stop_audio (GstAlsa *this); static gboolean gst_alsa_drain_audio (GstAlsa *this);
static void gst_alsa_close_audio (GstAlsa *this); static gboolean gst_alsa_stop_audio (GstAlsa *this);
static gboolean gst_alsa_close_audio (GstAlsa *this);
static gboolean gst_alsa_get_channel_addresses (GstAlsa *this);
static void gst_alsa_release_channel_addresses (GstAlsa *this);
/* #define _DEBUG */
#ifdef _DEBUG
#define DEBUG(text, args...) g_message(text, ##args)
#else
#define DEBUG(text, args...)
#endif
/*** TYPE FUNCTIONS ***********************************************************/ /*** TYPE FUNCTIONS ***********************************************************/
...@@ -195,7 +215,8 @@ enum ...@@ -195,7 +215,8 @@ enum
ARG_CHANNELS, ARG_CHANNELS,
ARG_RATE, ARG_RATE,
ARG_PERIODCOUNT, ARG_PERIODCOUNT,
ARG_PERIODFRAMES, ARG_PERIODSIZE,
ARG_BUFFERSIZE,
ARG_DEBUG, ARG_DEBUG,
ARG_AUTORECOVER ARG_AUTORECOVER
}; };
...@@ -220,26 +241,27 @@ gst_alsa_class_init (GstAlsaClass *klass) ...@@ -220,26 +241,27 @@ gst_alsa_class_init (GstAlsaClass *klass)
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE,
g_param_spec_string ("device", "Device", "Alsa device, as defined in an asoundrc", g_param_spec_string ("device", "Device", "Alsa device, as defined in an asoundrc",
"default", G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); "default", G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
/* The next 3 should only be settable on srcs, should it? */
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FORMAT, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FORMAT,
g_param_spec_enum ("format", "Format", "PCM audio format", g_param_spec_enum ("format", "Format", "PCM audio format",
GST_TYPE_ALSA_FORMAT, -1, G_PARAM_READWRITE)); GST_TYPE_ALSA_FORMAT, -1, G_PARAM_READABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_CHANNELS, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_CHANNELS,
g_param_spec_int ("channels", "Channels", "Number of channels", g_param_spec_int ("channels", "Channels", "Number of channels",
1, 64, 2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); 1, GST_ALSA_MAX_CHANNELS, 2, G_PARAM_READABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_RATE, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_RATE,
g_param_spec_int ("rate", "Rate", "Sample rate, in Hz", g_param_spec_int ("rate", "Rate", "Sample rate, in Hz",
8000, 192000, 44100, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); 8000, 192000, 44100, G_PARAM_READABLE));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PERIODCOUNT, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PERIODCOUNT,
g_param_spec_int ("period-count", "Period count", "Number of hardware buffers to use", g_param_spec_int ("period-count", "Period count", "Number of hardware buffers to use",
2, 64, 2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); 2, 64, 2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PERIODFRAMES, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PERIODSIZE,
g_param_spec_int ("period-frames", "Period frames", "Number of frames (samples on each channel) in one hardware period", g_param_spec_int ("period-size", "Period size", "Number of frames (samples on each channel) in one hardware period",
64, 8192, 8192, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); 64, 8192, 8192, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEBUG, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BUFFERSIZE,
g_param_spec_boolean ("debug", "Debug", "Set to TRUE to output PCM state info", g_param_spec_int ("buffer-size", "Buffer size", "Number of frames the hardware buffer can hold",
FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); 128, 65536, 16384, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_AUTORECOVER, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_AUTORECOVER,
g_param_spec_boolean ("autorecover", "Automatic xrun recovery", "Set to TRUE to increase the period count on xruns", g_param_spec_boolean ("autorecover", "Automatic xrun recovery", "When TRUE tries to reduce processor load on xruns",
TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
element_class->change_state = gst_alsa_change_state; element_class->change_state = gst_alsa_change_state;
...@@ -249,40 +271,31 @@ gst_alsa_class_init (GstAlsaClass *klass) ...@@ -249,40 +271,31 @@ gst_alsa_class_init (GstAlsaClass *klass)
static void static void
gst_alsa_init (GstAlsa *this) gst_alsa_init (GstAlsa *this)
{ {
gint i;
/* init values */ /* init values */
this->handle = NULL; this->handle = NULL;
this->channels = 1;
GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED); GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED);
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) { if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) {
this->stream = SND_PCM_STREAM_CAPTURE; this->stream = SND_PCM_STREAM_CAPTURE;
this->format = SND_PCM_FORMAT_S16; /* native endian */ this->format = SND_PCM_FORMAT_S16; /* native endian */
this->process = gst_alsa_src_process; this->pads[0].pad = gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "src");
this->pads = g_list_append (NULL, g_new0 (GstAlsaPad, 1));
GST_ALSA_PAD (this->pads)->pad =
gst_pad_new_from_template (gst_alsa_src_pad_factory (), "src");
} else if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK) { } else if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK) {
this->stream = SND_PCM_STREAM_PLAYBACK; this->stream = SND_PCM_STREAM_PLAYBACK;
this->format = SND_PCM_FORMAT_UNKNOWN; /* we don't know until caps are set */ this->format = SND_PCM_FORMAT_UNKNOWN; /* we don't know until caps are set */
this->process = gst_alsa_sink_process; gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_sink_loop);
this->pads = g_list_append (NULL, g_new0 (GstAlsaPad, 1)); this->pads[0].pad = gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "sink");
GST_ALSA_PAD (this->pads)->pad = this->pads[0].bs = gst_bytestream_new (this->pads[0].pad);
gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "sink");
} }
GST_ALSA_PAD (this->pads)->channel = -1; gst_element_add_pad (GST_ELEMENT (this), this->pads[0].pad);
for (i = 1; i < GST_ALSA_MAX_CHANNELS; i++) {
/* data is interleaved by default, because there's only one default pad */ this->pads[i].pad = NULL;
this->data_interleaved = TRUE; }
this->rate = 0;
this->channels = 0; gst_pad_set_link_function (this->pads[0].pad, gst_alsa_link);
gst_element_add_pad (GST_ELEMENT (this), GST_ALSA_PAD (this->pads)->pad);
gst_pad_set_link_function (GST_ALSA_PAD (this->pads)->pad, gst_alsa_link);
gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_loop);
} }
static void static void
...@@ -290,6 +303,7 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value, ...@@ -290,6 +303,7 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value,
GParamSpec *pspec) GParamSpec *pspec)
{ {
GstAlsa *this; GstAlsa *this;
gint buffer_size;
this = (GstAlsa *) object; this = (GstAlsa *) object;
switch (prop_id) { switch (prop_id) {
...@@ -298,7 +312,7 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value, ...@@ -298,7 +312,7 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value,
g_free (this->device); g_free (this->device);
this->device = g_strdup (g_value_get_string (value)); this->device = g_strdup (g_value_get_string (value));
break; break;
case ARG_FORMAT: /* case ARG_FORMAT:
this->format = g_value_get_enum (value); this->format = g_value_get_enum (value);
break; break;
case ARG_CHANNELS: case ARG_CHANNELS:
...@@ -306,18 +320,20 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value, ...@@ -306,18 +320,20 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value,
break; break;
case ARG_RATE: case ARG_RATE:
this->rate = g_value_get_int (value); this->rate = g_value_get_int (value);
break; break;*/
case ARG_PERIODCOUNT: case ARG_PERIODCOUNT:
g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
this->period_count = g_value_get_int (value); this->period_count = g_value_get_int (value);
this->buffer_frames = this->period_count * this->period_frames;
break; break;
case ARG_PERIODFRAMES: case ARG_PERIODSIZE:
this->period_frames = g_value_get_int (value); g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
this->buffer_frames = this->period_count * this->period_frames; 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;
break; break;
case ARG_DEBUG:
this->debug = g_value_get_boolean (value);
return;
case ARG_AUTORECOVER: case ARG_AUTORECOVER:
this->autorecover = g_value_get_boolean (value); this->autorecover = g_value_get_boolean (value);
return; return;
...@@ -331,10 +347,7 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value, ...@@ -331,10 +347,7 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value,
if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) { if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) {
gst_alsa_stop_audio (this); gst_alsa_stop_audio (this);
gst_alsa_set_params (this);
gst_alsa_start_audio (this); gst_alsa_start_audio (this);
} else {
gst_alsa_set_params (this);
} }
} }
...@@ -350,7 +363,7 @@ gst_alsa_get_property (GObject *object, guint prop_id, GValue *value, ...@@ -350,7 +363,7 @@ gst_alsa_get_property (GObject *object, guint prop_id, GValue *value,
case ARG_DEVICE: case ARG_DEVICE:
g_value_set_string (value, this->device); g_value_set_string (value, this->device);
break; break;
case ARG_FORMAT: /* case ARG_FORMAT:
g_value_set_enum (value, this->format); g_value_set_enum (value, this->format);
break; break;
case ARG_CHANNELS: case ARG_CHANNELS:
...@@ -359,14 +372,14 @@ gst_alsa_get_property (GObject *object, guint prop_id, GValue *value, ...@@ -359,14 +372,14 @@ gst_alsa_get_property (GObject *object, guint prop_id, GValue *value,
case ARG_RATE: case ARG_RATE:
g_value_set_int (value, this->rate); g_value_set_int (value, this->rate);
break; break;
case ARG_PERIODCOUNT: */ case ARG_PERIODCOUNT:
g_value_set_int (value, this->period_count); g_value_set_int (value, this->period_count);
break; break;
case ARG_PERIODFRAMES: case ARG_PERIODSIZE:
g_value_set_int (value, this->period_frames); g_value_set_int (value, this->period_size);
break; break;
case ARG_DEBUG: case ARG_BUFFERSIZE:
g_value_set_boolean (value, this->debug); g_value_set_int (value, this->period_size * this->period_count);
break; break;
case ARG_AUTORECOVER: case ARG_AUTORECOVER:
g_value_set_boolean (value, this->autorecover); g_value_set_boolean (value, this->autorecover);
...@@ -437,80 +450,49 @@ gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, ...@@ -437,80 +450,49 @@ gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ,
const gchar *name) const gchar *name)
{ {
GstAlsa *this; GstAlsa *this;
GstAlsaPad *pad; gint channel = 0;
gint channel;
gchar *newname;
GList *l;
g_return_val_if_fail ((this = GST_ALSA (element)), NULL); g_return_val_if_fail ((this = GST_ALSA (element)), NULL);
g_return_val_if_fail (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING), NULL);
/* you can't request a pad if the non-request pad is connected */ /* you can't request a pad if the non-request pad already has more than 1 channel */
g_return_val_if_fail (this->data_interleaved == FALSE || g_return_val_if_fail (this->channels > GST_ELEMENT(this)->numpads, NULL);
this->pads == NULL ||
GST_ALSA_PAD (this->pads) == NULL ||
GST_ALSA_PAD (this->pads)->pad == NULL ||
GST_PAD_PEER (GST_ALSA_PAD (this->pads)->pad) == NULL,
NULL);
if (name) { if (name) {
/* locate the channel number in the requested pad name. to do so look at /* locate the channel number in the requested pad name. to do so look at
where the % (which begins the %d) is in the template name. */ where the % (which begins the %d) is in the template name. */
channel = atoi (name + (strchr (templ->name_template, '%') - channel = (gint) strtol (name + (strchr (templ->name_template, '%') -
templ->name_template)); templ->name_template), NULL, 0);
if (channel < 1 || channel >= GST_ALSA_MAX_CHANNELS) {
/* make sure the requested channel is free. */ g_warning ("invalid channel requested. (%d)", channel);
l = this->pads; return NULL;
while (l) {
if (GST_ALSA_PAD (l)->channel == channel) {
g_warning ("requested channel %d already in use.", channel);
return NULL;
}
l = l->next;
}
newname = g_strdup (name);
} else {
channel = 0;
/* if the user doesn't care which channel, find the lowest channel number
that's free. */
l = this->pads;
while (l) {
if (GST_ALSA_PAD (l)->channel >= channel)
channel = GST_ALSA_PAD (l)->channel + 1;
l = l->next;
} }
newname = g_strdup_printf (templ->name_template, channel);
} }
/* set up a new GstAlsaPad struct to hold this channel's info. */ /* make sure the requested channel is free. */
pad = g_new0 (GstAlsaPad, 1); if (channel > 0 || this->pads[channel].pad != NULL) {
pad->channel = channel; g_warning ("requested channel %d already in use.", channel);
pad->pad = gst_pad_new_from_template (templ, newname); return NULL;
gst_element_add_pad (GST_ELEMENT (this), pad->pad);
gst_pad_set_link_function (pad->pad, gst_alsa_link);
/* if the only pad is the default (nonrequest) pad, then remove the current
pad (we know it's free from the above g_return_val_if_fail) */
if (this->data_interleaved && this->pads) {
gst_element_remove_pad (GST_ELEMENT (this), GST_ALSA_PAD (this->pads)->pad);
g_free (GST_ALSA_PAD (this->pads));
g_list_free (this->pads);
this->pads = NULL;
} }
this->pads = g_list_append (this->pads, pad); /* if the user doesn't care which channel, find the lowest channel number
that's free. */
/* all request pads are mono (non-interleaved). FIXME: allow interleaved if (channel == 0) {
access (for hw:N,M access on consumer hardware) */ for (channel = 1; channel < GST_ALSA_MAX_CHANNELS; channel++) {
if (this->data_interleaved) { if (this->pads[channel].pad != NULL)
this->channels = pad->channel + 1; goto found_channel;
this->data_interleaved = FALSE; }
} else { return NULL;
this->channels = MAX (this->channels, pad->channel + 1);
} }
return pad->pad; found_channel:
this->pads[channel].pad = gst_pad_new_from_template (templ, name);
gst_pad_set_link_function (this->pads[channel].pad, gst_alsa_link);
gst_element_add_pad (GST_ELEMENT (this), this->pads[channel].pad);
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK)
this->pads[channel].bs = gst_bytestream_new (this->pads[channel].pad);
return this->pads[channel].pad;
} }
/* gets the matching alsa format or SND_PCM_FORMAT_UNKNOWN if none matches */ /* gets the matching alsa format or SND_PCM_FORMAT_UNKNOWN if none matches */
...@@ -615,7 +597,7 @@ static inline void add_channels (GstProps *props, gint rate, gint channels) { ...@@ -615,7 +597,7 @@ static inline void add_channels (GstProps *props, gint rate, gint channels) {
gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT (rate))); gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT (rate)));
} }
if (channels < 0) { if (channels < 0) {
gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (1, 64))); gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (1, GST_ALSA_MAX_CHANNELS)));
} else { } else {
gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT (channels))); gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT (channels)));
} }
...@@ -624,7 +606,7 @@ static inline void add_channels (GstProps *props, gint rate, gint channels) { ...@@ -624,7 +606,7 @@ static inline void add_channels (GstProps *props, gint rate, gint channels) {
* Get all available caps. * Get all available caps.
* @format: SND_PCM_FORMAT_UNKNOWN for all formats, desired format else * @format: SND_PCM_FORMAT_UNKNOWN for all formats, desired format else
* @rate: allowed rates if < 0, else desired rate * @rate: allowed rates if < 0, else desired rate
* @channels: all allowed values for channels if < 0, else desired channel number * @channels: all allowed values for channels if < 0, else desired channels
*/ */
static GstCaps * static GstCaps *
gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels) gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels)
...@@ -657,14 +639,13 @@ gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels) ...@@ -657,14 +639,13 @@ gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels)
return ret_caps; return ret_caps;
} }
/* Negotiates the caps, "borrowed" from gstosssink.c */ /* Negotiates the caps */
GstPadLinkReturn GstPadLinkReturn
gst_alsa_link (GstPad *pad, GstCaps *caps) gst_alsa_link (GstPad *pad, GstCaps *caps)
{ {
GstAlsa *this; GstAlsa *this;
snd_pcm_format_t format; snd_pcm_format_t format;
gint rate, channels; gint rate, channels;
gboolean need_mmap;
g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED); g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED);
g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED); g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED);
...@@ -676,9 +657,10 @@ gst_alsa_link (GstPad *pad, GstCaps *caps) ...@@ -676,9 +657,10 @@ gst_alsa_link (GstPad *pad, GstCaps *caps)
if (!gst_alsa_open_audio (this)) if (!gst_alsa_open_audio (this))
return GST_PAD_LINK_REFUSED; return GST_PAD_LINK_REFUSED;
/* FIXME: allow changing the format here */ /* FIXME: allow changing the format here, even by retrying caps on other pads */
format = gst_alsa_get_format (caps); format = gst_alsa_get_format (caps);
DEBUG ("found format %s\n", snd_pcm_format_name (format)); GST_DEBUG (GST_CAT_CAPS, "found format %s\n", snd_pcm_format_name (format));
if (this->format != SND_PCM_FORMAT_UNKNOWN && this->format != format) if (this->format != SND_PCM_FORMAT_UNKNOWN && this->format != format)
return GST_PAD_LINK_REFUSED; return GST_PAD_LINK_REFUSED;
if (!gst_caps_get (caps, "rate", &rate, if (!gst_caps_get (caps, "rate", &rate,
...@@ -698,20 +680,13 @@ gst_alsa_link (GstPad *pad, GstCaps *caps) ...@@ -698,20 +680,13 @@ gst_alsa_link (GstPad *pad, GstCaps *caps)
this->format = format; this->format = format;
this->rate = rate; this->rate = rate;
need_mmap = this->mmap_open;
/* sync the params */ /* sync the params */
if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this); if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this);
if (GST_FLAG_IS_SET (this, GST_ALSA_OPEN)) gst_alsa_close_audio (this);