From f0328983265a94930e96c5af51452f0c05fcbfd9 Mon Sep 17 00:00:00 2001 From: Leif Johnson Date: Mon, 27 Jan 2003 12:59:24 +0000 Subject: [PATCH] + removed the access_addr crap from GstAlsaPad ... just use this->access_addr[channel] instead + completely reorganiz... Original commit message from CVS: + removed the access_addr crap from GstAlsaPad ... just use this->access_addr[channel] instead + completely reorganized and reindented code + removed the gst_alsa_sink_silence_on_channel function, needs to be completely redone anyway + got alsasink to work on my machine finally ! yay ! --- ext/alsa/gstalsa.c | 2360 +++++++++++++++++++++----------------------- ext/alsa/gstalsa.h | 135 ++- 2 files changed, 1191 insertions(+), 1304 deletions(-) diff --git a/ext/alsa/gstalsa.c b/ext/alsa/gstalsa.c index 780d5cf60e..40982c178e 100644 --- a/ext/alsa/gstalsa.c +++ b/ext/alsa/gstalsa.c @@ -1,23 +1,22 @@ -/* -*- c-basic-offset: 4 -*- */ /* - Copyright (C) 2001 CodeFactory AB - Copyright (C) 2001 Thomas Nyberg - Copyright (C) 2001-2002 Andy Wingo - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public - 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 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ + * Copyright (C) 2001 CodeFactory AB + * Copyright (C) 2001 Thomas Nyberg + * Copyright (C) 2001-2002 Andy Wingo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ #include #include @@ -25,64 +24,64 @@ /* elementfactory information */ static GstElementDetails gst_alsa_sink_details = { - "Alsa Sink", - "Sink/Audio", - "LGPL", - "Output to a sound card via ALSA", - VERSION, - "Thomas Nyberg , " - "Andy Wingo ", - "(C) 2001 " + "Alsa Sink", + "Sink/Audio", + "LGPL", + "Output to a sound card via ALSA", + VERSION, + "Thomas Nyberg , " + "Andy Wingo ", + "(C) 2001 " }; /* elementfactory information */ static GstElementDetails gst_alsa_src_details = { - "Alsa Src", - "Source/Audio", - "LGPL", - "Read from a sound card via ALSA", - VERSION, - "Thomas Nyberg , " - "Andy Wingo ", - "(C) 2001" + "Alsa Src", + "Source/Audio", + "LGPL", + "Read from a sound card via ALSA", + VERSION, + "Thomas Nyberg , " + "Andy Wingo ", + "(C) 2001" }; -static GstElement *parent_class = NULL; +/* GObject functions */ +static void gst_alsa_class_init (GstAlsaClass *klass); +static void gst_alsa_init (GstAlsa *this); +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); -static void gst_alsa_init(GstAlsa *this); -static void gst_alsa_class_init(GstAlsaClass *klass); +/* GStreamer functions for pads and state changing */ +static GstPadTemplate *gst_alsa_src_pad_factory (); +static GstPadTemplate *gst_alsa_src_request_pad_factory (); +static GstPadTemplate *gst_alsa_sink_pad_factory (); +static GstPadTemplate *gst_alsa_sink_request_pad_factory (); -static GstPadTemplate *gst_alsa_src_pad_factory(); -static GstPadTemplate *gst_alsa_src_request_pad_factory(); -static GstPadTemplate *gst_alsa_sink_pad_factory(); -static GstPadTemplate *gst_alsa_sink_request_pad_factory(); +static GstPad *gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name); +static GstPadLinkReturn gst_alsa_link (GstPad *pad, GstCaps *caps); +static GstCaps *gst_alsa_caps (GstAlsa *this); -static GstPad* gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name); +static GstElementStateReturn gst_alsa_change_state (GstElement *element); -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); -static GstElementStateReturn gst_alsa_change_state(GstElement *element); -static GstPadLinkReturn gst_alsa_link(GstPad *pad, GstCaps *caps); +/* audio processing functions */ +static void gst_alsa_loop (GstElement *element); +static void gst_alsa_xrun_recovery (GstAlsa *this); -static GstCaps* gst_alsa_caps (GstAlsa *this); - -static gboolean gst_alsa_open_audio(GstAlsa *this); -static gboolean gst_alsa_start_audio(GstAlsa *this); -static void gst_alsa_stop_audio(GstAlsa *this); -static void gst_alsa_close_audio(GstAlsa *this); - -static gboolean gst_alsa_set_params(GstAlsa *this); -static void gst_alsa_loop (GstElement *element); -static void gst_alsa_xrun_recovery (GstAlsa *this); -static gboolean gst_alsa_sink_process (GstAlsa *this, snd_pcm_uframes_t frames); static gboolean gst_alsa_src_process (GstAlsa *this, snd_pcm_uframes_t frames); +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 */ +static gboolean gst_alsa_set_params (GstAlsa *this); + +static gboolean gst_alsa_open_audio (GstAlsa *this); +static gboolean gst_alsa_start_audio (GstAlsa *this); +static void gst_alsa_stop_audio (GstAlsa *this); +static void gst_alsa_close_audio (GstAlsa *this); static gboolean gst_alsa_get_channel_addresses (GstAlsa *this); -static void gst_alsa_release_channel_addresses (GstAlsa *this); - -static void gst_alsa_sink_silence_on_channel (GstAlsa *this, guint32 chn, guint32 nframes); - -static void memset_interleave (char *dst, char val, unsigned int bytes, unsigned int unit_bytes, unsigned int skip_bytes); +static void gst_alsa_release_channel_addresses (GstAlsa *this); /* #define _DEBUG */ #ifdef _DEBUG @@ -91,1418 +90,1307 @@ static void memset_interleave (char *dst, char val, unsigned int bytes, unsigned #define DEBUG(text, args...) #endif - -enum { - ARG_0, - ARG_DEVICE, - ARG_FORMAT, - ARG_CHANNELS, - ARG_RATE, - ARG_PERIODCOUNT, - ARG_PERIODFRAMES, - ARG_DEBUG -}; +/*** TYPE FUNCTIONS ***********************************************************/ #define GST_TYPE_ALSA_FORMAT (gst_alsa_format_get_type()) static GType gst_alsa_format_get_type (void) { - static GType type = 0; - static GEnumValue *values = NULL; - gint i, len = SND_PCM_FORMAT_GSM + 3; + static GType type = 0; + static GEnumValue *values = NULL; + gint i, len = SND_PCM_FORMAT_GSM + 3; - if (values == NULL) { - /* the three: for -1, 0, and the terminating NULL */ - values = g_new0 (GEnumValue, len); + if (values == NULL) { + /* the three: for -1, 0, and the terminating NULL */ + values = g_new0 (GEnumValue, len); - for (i=0; iget_property = gst_alsa_get_property; + object_class->set_property = gst_alsa_set_property; - if (!template) - template = gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_SOMETIMES, - gst_caps_new("sink", "audio/raw", NULL), - NULL); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE, + g_param_spec_string ("device", "Device", "Alsa device, as defined in an asoundrc", + "default", G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FORMAT, + g_param_spec_enum ("format", "Format", "PCM audio format", + GST_TYPE_ALSA_FORMAT, -1, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_CHANNELS, + g_param_spec_int ("channels", "Channels", "Number of channels", + 1, 64, 2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_RATE, + g_param_spec_int ("rate", "Rate", "Sample rate, in Hz", + 8000, 192000, 44100, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + 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", + 2, 64, 2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PERIODFRAMES, + g_param_spec_int ("period-frames", "Period frames", "Number of frames (samples on each channel) in one hardware period", + 64, 8192, 8192, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEBUG, + g_param_spec_boolean ("debug", "Debug", "Set to TRUE to output PCM state info", + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + 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", + TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - return template; -} - -static GstPadTemplate* -gst_alsa_sink_request_pad_factory(void) -{ - static GstPadTemplate *template = NULL; - - if (!template) - template = - gst_pad_template_new("sink%d", GST_PAD_SINK, GST_PAD_REQUEST, - gst_caps_new("sink-request", "audio/raw", - gst_props_new("channels", - GST_PROPS_INT(1), - NULL)), - NULL); - - return template; + element_class->change_state = gst_alsa_change_state; + element_class->request_new_pad = gst_alsa_request_new_pad; } static void -gst_alsa_class_init(GstAlsaClass *klass) +gst_alsa_init (GstAlsa *this) { - GObjectClass *object_class; - GstElementClass *element_class; + /* init values */ + this->handle = NULL; - object_class = (GObjectClass *)klass; - element_class = (GstElementClass *)klass; + GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED); - if (parent_class == NULL) - parent_class = g_type_class_ref(GST_TYPE_ELEMENT); + if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) { + this->stream = SND_PCM_STREAM_CAPTURE; + this->format = SND_PCM_FORMAT_S16; /* native endian */ + this->process = gst_alsa_src_process; + 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"); - object_class->get_property = gst_alsa_get_property; - object_class->set_property = gst_alsa_set_property; + } else if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK) { + this->stream = SND_PCM_STREAM_PLAYBACK; + this->format = SND_PCM_FORMAT_UNKNOWN; /* we don't know until caps are set */ + this->process = gst_alsa_sink_process; + this->pads = g_list_append (NULL, g_new0 (GstAlsaPad, 1)); + GST_ALSA_PAD (this->pads)->pad = + gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "sink"); - g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_DEVICE, - g_param_spec_string("device","Device","Alsa device, as defined in an asoundrc", - "default", - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_FORMAT, - g_param_spec_enum("format","Format","PCM audio format", - GST_TYPE_ALSA_FORMAT, -1, - G_PARAM_READWRITE)); - g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_CHANNELS, - g_param_spec_int("channels","Channels","Number of channels", - 1, 64, 2, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_RATE, - g_param_spec_int("rate","Rate","Sample rate, in Hz", - 8000, 192000, 44100, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - 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", - 2, 64, 2, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_PERIODFRAMES, - g_param_spec_int("period-frames","Period frames","Number of frames (samples on each channel) in one hardware period", - 64, 8192, 8192, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_DEBUG, - g_param_spec_boolean("debug","Debug","Set to TRUE to output PCM state info", - FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + } - element_class->change_state = gst_alsa_change_state; + GST_ALSA_PAD (this->pads)->channel = -1; - element_class->request_new_pad = gst_alsa_request_new_pad; + /* data is interleaved by default, because there's only one default pad */ + this->data_interleaved = TRUE; + + 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 -gst_alsa_init(GstAlsa *this) +gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value, + GParamSpec *pspec) { - /* init values */ - this->handle = NULL; + GstAlsa *this; - GST_FLAG_SET(this, GST_ELEMENT_THREAD_SUGGESTED); + 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_FORMAT: + this->format = g_value_get_enum (value); + break; + case ARG_CHANNELS: + this->channels = g_value_get_int (value); + break; + case ARG_RATE: + this->rate = g_value_get_int (value); + break; + case ARG_PERIODCOUNT: + this->period_count = g_value_get_int (value); + this->buffer_frames = this->period_count * this->period_frames; + break; + case ARG_PERIODFRAMES: + this->period_frames = g_value_get_int (value); + this->buffer_frames = this->period_count * this->period_frames; + break; + case ARG_DEBUG: + this->debug = g_value_get_boolean (value); + return; + case ARG_AUTORECOVER: + this->autorecover = g_value_get_boolean (value); + return; + default: + GST_DEBUG (0, "Unknown arg"); + return; + } - if (G_OBJECT_TYPE(this) == GST_TYPE_ALSA_SRC) { - this->stream = SND_PCM_STREAM_CAPTURE; - 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"); - this->process = gst_alsa_src_process; - this->format = SND_PCM_FORMAT_S16; /* native endian */ - } else if (G_OBJECT_TYPE(this) == GST_TYPE_ALSA_SINK) { - this->stream = SND_PCM_STREAM_PLAYBACK; - this->pads = g_list_append(NULL, g_new0(GstAlsaPad, 1)); - GST_ALSA_PAD(this->pads)->pad = gst_pad_new_from_template(gst_alsa_sink_pad_factory(), "sink"); - this->process = gst_alsa_sink_process; - this->format = SND_PCM_FORMAT_UNKNOWN; /* we don't know until caps are - * set */ - } + if (GST_STATE (this) == GST_STATE_NULL) + return; - GST_ALSA_PAD(this->pads)->channel = -1; - - this->data_interleaved = TRUE; - - 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 GstPad* -gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name) -{ - GstAlsa *this; - gint channel; - gchar *newname; - GList *l; - GstAlsaPad *pad; - - g_return_val_if_fail ((this = GST_ALSA(element)), NULL); - - /* you can't request a pad if the non-request pad is connected */ - g_return_val_if_fail (this->data_interleaved == FALSE || - 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) { - channel = atoi (name + (strchr (templ->name_template, '%') - templ->name_template)); - - l = this->pads; - 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 { - l = this->pads; - channel = 0; - 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); - } - - pad = g_new0(GstAlsaPad, 1); - pad->channel = channel; - pad->pad = gst_pad_new_from_template (templ, newname); - gst_element_add_pad (GST_ELEMENT (this), pad->pad); - gst_pad_set_link_function(pad->pad, gst_alsa_link); - - 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); - - /* FIXME: allow interleaved access (for hw:N,M access on consumer hardware) */ - - if (this->data_interleaved) { - this->channels = pad->channel + 1; - this->data_interleaved = FALSE; - } else { - this->channels = MAX(this->channels, pad->channel + 1); - } - - return pad->pad; + if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) { + gst_alsa_stop_audio (this); + gst_alsa_set_params (this); + gst_alsa_start_audio (this); + } else { + gst_alsa_set_params (this); + } } static void -gst_alsa_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +gst_alsa_get_property (GObject *object, guint prop_id, GValue *value, + GParamSpec *pspec) { - GstAlsa *this; + GstAlsa *this; - 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_FORMAT: - this->format = g_value_get_enum (value); - break; - case ARG_CHANNELS: - this->channels = g_value_get_int (value); - break; - case ARG_RATE: - this->rate = g_value_get_int (value); - break; - case ARG_PERIODCOUNT: - this->period_count = g_value_get_int (value); - this->buffer_frames = this->period_count * this->period_frames; - break; - case ARG_PERIODFRAMES: - this->period_frames = g_value_get_int (value); - this->buffer_frames = this->period_count * this->period_frames; - break; - case ARG_DEBUG: - this->debug = g_value_get_boolean (value); - return; - default: - GST_DEBUG(0, "Unknown arg"); - return; - } + this = (GstAlsa *) object; - if (GST_STATE(this) == GST_STATE_NULL) - return; - - if (GST_FLAG_IS_SET(this, GST_ALSA_RUNNING)) { - gst_alsa_stop_audio(this); - gst_alsa_set_params(this); - gst_alsa_start_audio(this); - } else { - gst_alsa_set_params(this); - } + switch (prop_id) { + case ARG_DEVICE: + g_value_set_string (value, this->device); + break; + case ARG_FORMAT: + g_value_set_enum (value, this->format); + break; + case ARG_CHANNELS: + g_value_set_int (value, this->channels); + break; + case ARG_RATE: + g_value_set_int (value, this->rate); + break; + case ARG_PERIODCOUNT: + g_value_set_int (value, this->period_count); + break; + case ARG_PERIODFRAMES: + g_value_set_int (value, this->period_frames); + break; + case ARG_DEBUG: + g_value_set_boolean (value, this->debug); + break; + case ARG_AUTORECOVER: + g_value_set_boolean (value, this->autorecover); + break; + default: + GST_DEBUG (0, "Unknown arg"); + break; + } } -static void -gst_alsa_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +/*** GSTREAMER PAD / STATE FUNCTIONS*******************************************/ + +static GstPadTemplate * +gst_alsa_src_pad_factory (void) { - GstAlsa *this; + static GstPadTemplate *template = NULL; - this = (GstAlsa *)object; + if (!template) + template = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_SOMETIMES, + gst_caps_new ("src", "audio/raw", NULL), + NULL); - switch (prop_id) { - case ARG_DEVICE: - g_value_set_string (value, this->device); - break; - case ARG_FORMAT: - g_value_set_enum (value, this->format); - break; - case ARG_CHANNELS: - g_value_set_int (value, this->channels); - break; - case ARG_RATE: - g_value_set_int (value, this->rate); - break; - case ARG_PERIODCOUNT: - g_value_set_int (value, this->period_count); - break; - case ARG_PERIODFRAMES: - g_value_set_int (value, this->period_frames); - break; - case ARG_DEBUG: - g_value_set_boolean (value, this->debug); - break; - default: - GST_DEBUG(0, "Unknown arg"); - break; - } + return template; } -static GstElementStateReturn -gst_alsa_change_state(GstElement *element) +static GstPadTemplate * +gst_alsa_src_request_pad_factory (void) { - GstAlsa *this; - guint chn; - GList *l; + static GstPadTemplate *template = NULL; - g_return_val_if_fail(element != NULL, FALSE); - this = GST_ALSA (element); + if (!template) + template = + gst_pad_template_new ("src%d", GST_PAD_SRC, GST_PAD_REQUEST, + gst_caps_new ("src-request", "audio/raw", + gst_props_new ("channels", GST_PROPS_INT (1), NULL)), + NULL); - switch (GST_STATE_PENDING(element)) { - case GST_STATE_NULL: - if (GST_FLAG_IS_SET(element, GST_ALSA_RUNNING)) - gst_alsa_stop_audio(this); - if (GST_FLAG_IS_SET(element, GST_ALSA_OPEN)) - gst_alsa_close_audio(this); + return template; +} - l = this->pads; - while(l) { - if (GST_ALSA_PAD(l)->bs) - gst_bytestream_destroy(GST_ALSA_PAD(l)->bs); - l = l->next; - } +static GstPadTemplate * +gst_alsa_sink_pad_factory (void) +{ + static GstPadTemplate *template = NULL; - break; + if (!template) + template = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_SOMETIMES, + gst_caps_new ("sink", "audio/raw", NULL), + NULL); - case GST_STATE_READY: - break; + return template; +} - case GST_STATE_PAUSED: - if (GST_FLAG_IS_SET(element, GST_ALSA_OPEN) == FALSE) - if (gst_alsa_open_audio(this) == FALSE) - return GST_STATE_FAILURE; - if (GST_FLAG_IS_SET(element, GST_ALSA_RUNNING)) { - if (this->stream == SND_PCM_STREAM_PLAYBACK) { - for (chn = 0; chn < this->channels; chn++) { - gst_alsa_sink_silence_on_channel (this, chn, this->avail); - } - } - gst_alsa_stop_audio((GstAlsa *)element); - } - break; +static GstPadTemplate * +gst_alsa_sink_request_pad_factory (void) +{ + static GstPadTemplate *template = NULL; - case GST_STATE_PLAYING: - if (GST_FLAG_IS_SET(element, GST_ALSA_RUNNING) == FALSE) - if (gst_alsa_start_audio(this) == FALSE) - return GST_STATE_FAILURE; - break; + if (!template) + template = + gst_pad_template_new ("sink%d", GST_PAD_SINK, GST_PAD_REQUEST, + gst_caps_new ("sink-request", "audio/raw", + gst_props_new ("channels", GST_PROPS_INT (1), NULL)), + NULL); + + return template; +} + +static GstPad * +gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, + const gchar *name) +{ + GstAlsa *this; + GstAlsaPad *pad; + gint channel; + gchar *newname; + GList *l; + + g_return_val_if_fail ((this = GST_ALSA (element)), NULL); + + /* you can't request a pad if the non-request pad is connected */ + g_return_val_if_fail (this->data_interleaved == FALSE || + 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) { + /* 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. */ + channel = atoi (name + (strchr (templ->name_template, '%') - + templ->name_template)); + + /* make sure the requested channel is free. */ + l = this->pads; + 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 (GST_ELEMENT_CLASS(parent_class)->change_state) - return GST_ELEMENT_CLASS(parent_class)->change_state(element); + /* 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); + } - return GST_STATE_SUCCESS; + /* set up a new GstAlsaPad struct to hold this channel's info. */ + pad = g_new0 (GstAlsaPad, 1); + pad->channel = channel; + pad->pad = gst_pad_new_from_template (templ, newname); + + 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); + + /* all request pads are mono (non-interleaved). FIXME: allow interleaved + access (for hw:N,M access on consumer hardware) */ + if (this->data_interleaved) { + this->channels = pad->channel + 1; + this->data_interleaved = FALSE; + } else { + this->channels = MAX (this->channels, pad->channel + 1); + } + + return pad->pad; } static gboolean gst_alsa_parse_caps (GstAlsa *this, GstCaps *caps) { - gint law, endianness, width, depth, channels; - gboolean sign; - gint format = -1; - const gchar* format_name; + gint law, endianness, width, depth, channels; + gboolean sign; + gint format = -1; + const gchar *format_name; - if (!gst_caps_get_string (caps, "format", &format_name)) - return FALSE; + if (!gst_caps_get_string (caps, "format", &format_name)) + return FALSE; - if (format_name == NULL) { - return FALSE; - } else if (strcmp(format_name, "int")==0) { - if (!gst_caps_get (caps, - "width", &width, - "depth", &depth, - "law", &law, - "endianness", &endianness, - "signed", &sign, - NULL)) - return FALSE; - - if (law == 0) { - if (width == 8) { - if (sign == TRUE) { - format = SND_PCM_FORMAT_S8; - } else { - format = SND_PCM_FORMAT_U8; - } - } else if (width == 16) { - if (sign == TRUE) { - if (endianness == G_LITTLE_ENDIAN) - format = SND_PCM_FORMAT_S16_LE; - else if (endianness == G_BIG_ENDIAN) - format = SND_PCM_FORMAT_S16_BE; - } else { - if (endianness == G_LITTLE_ENDIAN) - format = SND_PCM_FORMAT_U16_LE; - else if (endianness == G_BIG_ENDIAN) - format = SND_PCM_FORMAT_U16_BE; - } - } else if (width == 24) { - if (sign == TRUE) { - if (endianness == G_LITTLE_ENDIAN) - format = SND_PCM_FORMAT_S24_LE; - else if (endianness == G_BIG_ENDIAN) - format = SND_PCM_FORMAT_S24_BE; - } else { - if (endianness == G_LITTLE_ENDIAN) - format = SND_PCM_FORMAT_U24_LE; - else if (endianness == G_BIG_ENDIAN) - format = SND_PCM_FORMAT_U24_BE; - } - } else if (width == 32) { - if (sign == TRUE) { - if (endianness == G_LITTLE_ENDIAN) - format = SND_PCM_FORMAT_S32_LE; - else if (endianness == G_BIG_ENDIAN) - format = SND_PCM_FORMAT_S32_BE; - } else { - if (endianness == G_LITTLE_ENDIAN) - format = SND_PCM_FORMAT_U32_LE; - else if (endianness == G_BIG_ENDIAN) - format = SND_PCM_FORMAT_U32_BE; - } - } - } else if (law == 1) { /* mu law */ - if (width == depth && width == 8 && sign == FALSE) { - format = SND_PCM_FORMAT_MU_LAW; - } else { - return FALSE; - } - } else if (law == 2) { /* a law, ug. */ - if (width == depth && width == 8 && sign == FALSE) { - format = SND_PCM_FORMAT_A_LAW; - } - } else { - return FALSE; - } - } else if (strcmp(format_name, "float")==0) { - const gchar *layout; - - if (!gst_caps_get_string (caps, "layout", &layout)) - return FALSE; - - if (strcmp(layout, "gfloat")==0) { - format = SND_PCM_FORMAT_FLOAT; - } else { - return FALSE; - /* you need doubles? jeez... */ - } - } else { - return FALSE; - } - - this->format = format; + if (format_name == NULL) { + return FALSE; + } else if (strcmp (format_name, "int") == 0) { if (!gst_caps_get (caps, - "rate", &this->rate, - "channels", &channels, - NULL)) - return FALSE; + "width", &width, "depth", &depth, "law", &law, + "endianness", &endianness, "signed", &sign, NULL)) + return FALSE; - if (this->data_interleaved) - this->channels = channels; - else if (channels != 1) + if (law == 0) { + if (width == 8) { + if (sign == TRUE) { + format = SND_PCM_FORMAT_S8; + } else { + format = SND_PCM_FORMAT_U8; + } + } else if (width == 16) { + if (sign == TRUE) { + if (endianness == G_LITTLE_ENDIAN) format = SND_PCM_FORMAT_S16_LE; + else if (endianness == G_BIG_ENDIAN) format = SND_PCM_FORMAT_S16_BE; + } else { + if (endianness == G_LITTLE_ENDIAN) format = SND_PCM_FORMAT_U16_LE; + else if (endianness == G_BIG_ENDIAN) format = SND_PCM_FORMAT_U16_BE; + } + } else if (width == 24) { + if (sign == TRUE) { + if (endianness == G_LITTLE_ENDIAN) format = SND_PCM_FORMAT_S24_LE; + else if (endianness == G_BIG_ENDIAN) format = SND_PCM_FORMAT_S24_BE; + } else { + if (endianness == G_LITTLE_ENDIAN) format = SND_PCM_FORMAT_U24_LE; + else if (endianness == G_BIG_ENDIAN) format = SND_PCM_FORMAT_U24_BE; + } + } else if (width == 32) { + if (sign == TRUE) { + if (endianness == G_LITTLE_ENDIAN) format = SND_PCM_FORMAT_S32_LE; + else if (endianness == G_BIG_ENDIAN) format = SND_PCM_FORMAT_S32_BE; + } else { + if (endianness == G_LITTLE_ENDIAN) format = SND_PCM_FORMAT_U32_LE; + else if (endianness == G_BIG_ENDIAN) format = SND_PCM_FORMAT_U32_BE; + } + } + } else if (law == 1) { /* mu law */ + if (width == depth && width == 8 && sign == FALSE) { + format = SND_PCM_FORMAT_MU_LAW; + } else { return FALSE; + } + } else if (law == 2) { /* a law, ug. */ + if (width == depth && width == 8 && sign == FALSE) { + format = SND_PCM_FORMAT_A_LAW; + } + } else { + return FALSE; + } + } else if (strcmp (format_name, "float") == 0) { + const gchar *layout; - return TRUE; + if (!gst_caps_get_string (caps, "layout", &layout)) + return FALSE; + + if (strcmp (layout, "gfloat") == 0) { + format = SND_PCM_FORMAT_FLOAT; + } else { + return FALSE; + /* you need doubles? jeez... */ + } + } else { + return FALSE; + } + + this->format = format; + if (!gst_caps_get (caps, "rate", &this->rate, "channels", &channels, NULL)) + return FALSE; + + if (this->data_interleaved) + this->channels = channels; + else if (channels != 1) + return FALSE; + + return TRUE; } /* caps are so painful sometimes. */ -static GstCaps* +static GstCaps * gst_alsa_caps (GstAlsa *this) { - gint law, endianness, width, depth; - gboolean sign; - GstProps *props; + gint law, endianness, width, depth; + gboolean sign; + GstProps *props; - g_return_val_if_fail (this != NULL && this->handle != NULL, NULL); + g_return_val_if_fail (this != NULL && this->handle != NULL, NULL); - if (this->format == SND_PCM_FORMAT_FLOAT) { - props = gst_props_new ("format", GST_PROPS_STRING ("float"), - "layout", GST_PROPS_STRING ("gfloat"), - "rate", GST_PROPS_INT (this->rate), - "channels", GST_PROPS_INT ((this->data_interleaved ? this->channels : 1)), - NULL); + if (this->format == SND_PCM_FORMAT_FLOAT) { + props = gst_props_new ("format", GST_PROPS_STRING ("float"), + "layout", GST_PROPS_STRING ("gfloat"), + "rate", GST_PROPS_INT (this->rate), + "channels", GST_PROPS_INT ((this->data_interleaved ? this->channels : 1)), + NULL); + } else { + /* we'll just have to assume int, i don't feel like checking */ + if (this->format == SND_PCM_FORMAT_MU_LAW) { + law = 1; + width = 8; sign = FALSE; endianness = 0; + } else if (this->format == SND_PCM_FORMAT_A_LAW) { + law = 2; + width = 8; sign = FALSE; endianness = 0; } else { - /* we'll just have to assume int, i don't feel like checking */ - if (this->format == SND_PCM_FORMAT_MU_LAW) { - law = 1; - width = 8; - sign = FALSE; - endianness = 0; - } else if (this->format == SND_PCM_FORMAT_A_LAW) { - law = 2; - width = 8; - sign = FALSE; - endianness = 0; - } else { - law = 0; - if (this->format == SND_PCM_FORMAT_S8) { - width = 8; - sign = TRUE; - endianness = 0; - } else if (this->format == SND_PCM_FORMAT_U8) { - width = 8; - sign = FALSE; - endianness = 0; - } else if (this->format == SND_PCM_FORMAT_S16_LE) { - width = 16; - sign = TRUE; - endianness = G_LITTLE_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_S16_BE) { - width = 16; - sign = TRUE; - endianness = G_BIG_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_U16_LE) { - width = 16; - sign = FALSE; - endianness = G_LITTLE_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_U16_BE) { - width = 16; - sign = FALSE; - endianness = G_BIG_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_S24_LE) { - width = 24; - sign = TRUE; - endianness = G_LITTLE_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_S24_BE) { - width = 24; - sign = TRUE; - endianness = G_BIG_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_U24_LE) { - width = 24; - sign = FALSE; - endianness = G_LITTLE_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_U24_BE) { - width = 24; - sign = FALSE; - endianness = G_BIG_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_S32_LE) { - width = 32; - sign = TRUE; - endianness = G_LITTLE_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_S32_BE) { - width = 32; - sign = TRUE; - endianness = G_BIG_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_U32_LE) { - width = 32; - sign = FALSE; - endianness = G_LITTLE_ENDIAN; - } else if (this->format == SND_PCM_FORMAT_U32_BE) { - width = 32; - sign = FALSE; - endianness = G_BIG_ENDIAN; - } else { - g_error ("what is going on here?"); - return NULL; - } - } - depth = width; - props = gst_props_new ("format", GST_PROPS_STRING ("int"), - "rate", GST_PROPS_INT (this->rate), - "channels", GST_PROPS_INT ((this->data_interleaved ? this->channels : 1)), - "law", GST_PROPS_INT (law), - "endianness", GST_PROPS_INT (endianness), - "signed", GST_PROPS_BOOLEAN (sign), - "width", GST_PROPS_INT (width), - "depth", GST_PROPS_INT (depth), - NULL); + law = 0; + if (this->format == SND_PCM_FORMAT_S8) { + width = 8; sign = TRUE; endianness = 0; + } else if (this->format == SND_PCM_FORMAT_U8) { + width = 8; sign = FALSE; endianness = 0; + } else if (this->format == SND_PCM_FORMAT_S16_LE) { + width = 16; sign = TRUE; endianness = G_LITTLE_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_S16_BE) { + width = 16; sign = TRUE; endianness = G_BIG_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_U16_LE) { + width = 16; sign = FALSE; endianness = G_LITTLE_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_U16_BE) { + width = 16; sign = FALSE; endianness = G_BIG_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_S24_LE) { + width = 24; sign = TRUE; endianness = G_LITTLE_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_S24_BE) { + width = 24; sign = TRUE; endianness = G_BIG_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_U24_LE) { + width = 24; sign = FALSE; endianness = G_LITTLE_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_U24_BE) { + width = 24; sign = FALSE; endianness = G_BIG_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_S32_LE) { + width = 32; sign = TRUE; endianness = G_LITTLE_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_S32_BE) { + width = 32; sign = TRUE; endianness = G_BIG_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_U32_LE) { + width = 32; sign = FALSE; endianness = G_LITTLE_ENDIAN; + } else if (this->format == SND_PCM_FORMAT_U32_BE) { + width = 32; sign = FALSE; endianness = G_BIG_ENDIAN; + } else { + g_error ("Unknown audio format %u", this->format); + return NULL; + } } + depth = width; + props = gst_props_new ("format", GST_PROPS_STRING ("int"), + "rate", GST_PROPS_INT (this->rate), + "channels", GST_PROPS_INT ((this->data_interleaved ? this->channels : 1)), + "law", GST_PROPS_INT (law), + "endianness", GST_PROPS_INT (endianness), + "signed", GST_PROPS_BOOLEAN (sign), + "width", GST_PROPS_INT (width), + "depth", GST_PROPS_INT (depth), + NULL); + } - return gst_caps_new ("alsasrc", "audio/raw", props); + return gst_caps_new ("alsasrc", "audio/raw", props); } -/* - * Negotiates the caps, "borrowed" from gstosssink.c - */ +/* Negotiates the caps, "borrowed" from gstosssink.c */ GstPadLinkReturn -gst_alsa_link(GstPad *pad, GstCaps *caps) +gst_alsa_link (GstPad *pad, GstCaps *caps) { - GstAlsa *this; - gboolean need_mmap; + GstAlsa *this; + gboolean need_mmap; - 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 (caps != NULL, GST_PAD_LINK_REFUSED); + g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED); - this = GST_ALSA(gst_pad_get_parent(pad)); - - if (GST_CAPS_IS_FIXED (caps)) { - if (this->handle == NULL) - if (!gst_alsa_open_audio(this)) - return GST_PAD_LINK_REFUSED; - - if (gst_alsa_parse_caps(this, caps)) { - need_mmap = this->mmap_open; - - /* 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_OPEN)) - gst_alsa_close_audio(this); - - /* FIXME send out another caps if nego fails */ - - if (!gst_alsa_open_audio(this)) - return GST_PAD_LINK_REFUSED; - - if (!gst_alsa_start_audio(this)) - return GST_PAD_LINK_REFUSED; - - if (need_mmap && !gst_alsa_get_channel_addresses(this)) - return GST_PAD_LINK_REFUSED; - - return GST_PAD_LINK_OK; - } + this = GST_ALSA (gst_pad_get_parent (pad)); + if (GST_CAPS_IS_FIXED (caps)) { + if (this->handle == NULL) + if (!gst_alsa_open_audio (this)) return GST_PAD_LINK_REFUSED; + + if (gst_alsa_parse_caps (this, caps)) { + need_mmap = this->mmap_open; + + /* 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_OPEN)) gst_alsa_close_audio (this); + + /* FIXME send out another caps if nego fails */ + + if (!gst_alsa_open_audio (this)) return GST_PAD_LINK_REFUSED; + if (!gst_alsa_start_audio (this)) return GST_PAD_LINK_REFUSED; + + if (need_mmap && !gst_alsa_get_channel_addresses (this)) + return GST_PAD_LINK_REFUSED; + + return GST_PAD_LINK_OK; } - return GST_PAD_LINK_DELAYED; + return GST_PAD_LINK_REFUSED; + } + + return GST_PAD_LINK_DELAYED; } +static GstElementStateReturn +gst_alsa_change_state (GstElement *element) +{ + GstAlsa *this; + GList *l; + + g_return_val_if_fail (element != NULL, FALSE); + this = GST_ALSA (element); + + switch (GST_STATE_PENDING (element)) { + case GST_STATE_NULL: + if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this); + if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN)) gst_alsa_close_audio (this); + + /* clear out bytestreams as well. */ + l = this->pads; + while (l) { + if (GST_ALSA_PAD (l)->bs) + gst_bytestream_destroy (GST_ALSA_PAD (l)->bs); + l = l->next; + } + + break; + + case GST_STATE_READY: + break; + + case GST_STATE_PAUSED: + if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN) == FALSE) + if (!gst_alsa_open_audio (this)) + return GST_STATE_FAILURE; + + if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) + gst_alsa_stop_audio (this); + + break; + + case GST_STATE_PLAYING: + if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) == FALSE) + if (!gst_alsa_start_audio (this)) + return GST_STATE_FAILURE; + + break; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); + + return GST_STATE_SUCCESS; +} + +/*** AUDIO PROCESSING *********************************************************/ + /* shamelessly stolen from pbd's audioengine and jack alsa_driver. thanks, paul! */ static void gst_alsa_loop (GstElement *element) { - guint32 i; - GstAlsa *this = GST_ALSA(element); + GstAlsa *this = GST_ALSA (element); - g_return_if_fail(this != NULL); + g_return_if_fail (this != NULL); - do { - if (snd_pcm_wait (this->handle, 1000) < 0) { - if (errno == EINTR) { - /* this happens mostly when run - * under gdb, or when exiting due to a signal */ - g_print ("EINTR\n"); - if (gst_element_interrupt (element)) - break; - else - continue; - } + while (1) { + if (snd_pcm_wait (this->handle, 1000) < 0) { + if (errno == EINTR) { + /* this happens mostly when run + * under gdb, or when exiting due to a signal */ + g_print ("EINTR\n"); + if (gst_element_interrupt (element)) + break; + else + continue; + } - g_warning("error waiting for alsa pcm: (%d: %s)", errno, strerror(errno)); - return; - } - - this->avail = snd_pcm_avail_update (this->handle); - DEBUG ("snd_pcm_avail_update() = %d", (int)this->avail); - - if (this->avail < 0) { - if (this->avail == -EPIPE) { - gst_alsa_xrun_recovery (this); - this->avail = 0; - } else { - g_warning("unknown ALSA avail_update return value (%d)", - (int)this->avail); - return; - } - } - - /* round down to nearest period_frames avail */ - this->avail -= this->avail % this->period_frames; - - DEBUG ("snd_pcm_avail_update(), rounded down = %d", (int)this->avail); - - /* we need to loop here because the available bytes might not be - * contiguous */ - while (this->avail) { - /* changes this->avail, as a side effect */ - if (!gst_alsa_get_channel_addresses (this) < 0) { - g_error("could not get channels"); - return; - } - - if (this->mute && this->stream == SND_PCM_STREAM_PLAYBACK) { - for (i = 0; i < this->channels; i++) { - if (this->mute & (1<buffer_frames); - } - } - } - - if (!this->process(this, this->avail)) { - g_warning("alsa: something happened while processing audio"); - return; - } - - /* we could have released the mmap regions on a state change */ - if (this->mmap_open) - gst_alsa_release_channel_addresses(this); - } - - if (gst_element_interrupt (element)) - break; - - } while (TRUE); -} - -static gboolean -gst_alsa_src_process (GstAlsa *this, snd_pcm_uframes_t frames) -{ - GstBuffer *buf; - GList *l; - GstAlsaPad *pad = NULL; - GstCaps *caps; - gint unit; - - static gboolean caps_set = FALSE; - - if (!caps_set) { - /* let's get on the caps-setting merry-go-round! */ - caps = gst_alsa_caps(this); - l = this->pads; - while (l) { - if (gst_pad_try_set_caps (GST_ALSA_PAD(l)->pad, caps) <= 0) { - g_print ("setting caps (%p) in source (%p) failed\n", caps, this); - sleep(1); - return FALSE; - } - - l = l->next; - } - caps_set = TRUE; + g_warning ("error waiting for alsa pcm: (%d: %s)", errno, strerror (errno)); + return; } - unit = this->sample_bytes * (this->data_interleaved ? this->channels : 1); + this->avail = snd_pcm_avail_update (this->handle); + DEBUG ("snd_pcm_avail_update() = %d", (int) this->avail); - while (frames) { - l = this->pads; - while (l) { - pad = GST_ALSA_PAD(l); - - if (!pad->buf) { - pad->buf = g_malloc(this->period_frames * unit); - /* g_print ("created buffer %p of size %d\n", pad->buf, this->period_frames * unit); */ - } - /* - g_print ("pad->buf = %p, offset = %d\n", pad->buf, pad->offset); - g_print ("about to memcpy(%p, %p, %d)\n", - pad->buf + pad->offset * unit, - pad->access_addr, - MIN(frames, this->period_frames - pad->offset) * unit); - */ - memcpy(pad->buf + pad->offset * unit, pad->access_addr, - MIN(frames, this->period_frames - pad->offset) * unit); - - pad->offset += MIN(frames, this->period_frames - pad->offset); - - if (pad->offset >= this->period_frames) { - g_assert(pad->offset <= this->period_frames); - buf = gst_buffer_new(); - GST_BUFFER_DATA(buf) = pad->buf; - GST_BUFFER_SIZE(buf) = this->period_frames * unit; - GST_BUFFER_MAXSIZE(buf) = this->period_frames * unit; - gst_pad_push(pad->pad, buf); - pad->buf = NULL; - pad->offset = 0; - } - l = l->next; - } - frames -= MIN(frames, this->period_frames - pad->offset); /* shouldn't */ - /* matter which pad, in theory (tm) */ + if (this->avail < 0) { + if (this->avail == -EPIPE) { + gst_alsa_xrun_recovery (this); + this->avail = 0; + } else { + g_warning ("unknown ALSA avail_update return value (%d)", (int) this->avail); + return; + } } - return TRUE; -} + /* round down to nearest period_frames avail */ + this->avail -= this->avail % this->period_frames; -static gboolean -gst_alsa_sink_process (GstAlsa *this, snd_pcm_uframes_t frames) -{ - guint8 *peeked; - guint32 len, avail, num_peeked = 0; - GstEvent *event = NULL; - GstAlsaPad *alsa_pad; - GList *l; + DEBUG ("snd_pcm_avail_update(), rounded down = %d", (int) this->avail); - /* this is necessary because the sample_bytes will change, probably, when - caps are set, which will occur after the first bytestream_peek. we - underestimate the amount of data we will need by peeking 1 byte only. */ + /* the available bytes might not be contiguous */ + while (this->avail > 0) { + if (!gst_alsa_get_channel_addresses (this)) { + g_error ("could not get channels"); + return; + } - if (!this->sample_bytes) { - alsa_pad = GST_ALSA_PAD(this->pads); - - if (! alsa_pad->bs) - alsa_pad->bs = gst_bytestream_new(alsa_pad->pad); - - while (num_peeked == 0) { - num_peeked = gst_bytestream_peek_bytes (alsa_pad->bs, &peeked, frames); - if (num_peeked == 0) { - gst_bytestream_get_status(alsa_pad->bs, &avail, &event); - if (event) { - if (GST_EVENT_TYPE(event) == GST_EVENT_EOS) { - /* really, we should just cut this pad out of the graph. let - me know when this is needed ;) */ - - gst_element_set_eos(GST_ELEMENT(this)); - gst_event_unref(event); - return TRUE; - } else { - g_warning("GstAlsaSink: got an unknown event (Type: %d)", GST_EVENT_TYPE(event)); - } - } else { - /* the element at the top of the chain did not emit an eos - * event. this is a Bug(tm) */ - g_assert_not_reached(); - } - } else if (num_peeked < frames) { - g_warning("could not make initial pull of %d bytes on pad %s:%s", - (int)frames, - GST_DEBUG_PAD_NAME(alsa_pad->pad)); - gst_element_set_eos (GST_ELEMENT(this)); - return FALSE; - } - } - - if (!this->sample_bytes) { - g_critical ("alsa plugin requires a pipeline that can adequately set caps."); - return FALSE; - } - } - - len = frames * this->channels * this->sample_bytes; - - l = this->pads; - while (l) { - alsa_pad = GST_ALSA_PAD(l); - - if (! alsa_pad->bs) - alsa_pad->bs = gst_bytestream_new(alsa_pad->pad); - - num_peeked = gst_bytestream_peek_bytes(alsa_pad->bs, &peeked, len); - if (num_peeked == 0) { - gst_bytestream_get_status(alsa_pad->bs, &avail, &event); - if (event) { - if (GST_EVENT_TYPE(event) == GST_EVENT_EOS) { - /* really, we should just cut this pad out of the graph. let - me know when this is needed ;) */ - - gst_element_set_eos(GST_ELEMENT(this)); - gst_event_unref(event); - return TRUE; - } else { - g_warning("GstAlsaSink: got an unknown event (Type: %d)", GST_EVENT_TYPE(event)); - } - } else { - /* the element at the top of the chain did not emit an eos - * event. this is a Bug(tm) */ - g_assert_not_reached(); - } - } else if (peeked && alsa_pad->access_addr) { - memcpy(alsa_pad->access_addr, peeked, num_peeked); - gst_bytestream_flush(alsa_pad->bs, num_peeked); - } else { - g_warning ("error while writing %u bytes (peeked into %p) to %p", - num_peeked, peeked, alsa_pad->access_addr); + if (! (this->mute && this->stream == SND_PCM_STREAM_PLAYBACK)) + if (!this->process (this, this->avail)) { + g_warning ("alsa: something happened while processing audio"); + return; } - l = l->next; + /* releasing channel addresses sets this->avail to 0. */ + if (this->mmap_open) + gst_alsa_release_channel_addresses (this); } - return TRUE; + if (gst_element_interrupt (element)) + break; + } } static void gst_alsa_xrun_recovery (GstAlsa *this) { - snd_pcm_status_t *status; - int res; + snd_pcm_status_t *status; + gint err; - snd_pcm_status_alloca(&status); + snd_pcm_status_alloca (&status); - if ((res = snd_pcm_status(this->handle, status)) < 0) { - g_warning ("status error: %s", snd_strerror(res)); - } + if ((err = snd_pcm_status (this->handle, status)) < 0) + g_warning ("status error: %s", snd_strerror (err)); - if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) { - struct timeval now, diff, tstamp; - gettimeofday(&now, 0); - snd_pcm_status_get_trigger_tstamp(status, &tstamp); - timersub(&now, &tstamp, &diff); - g_warning("alsa: xrun of at least %.3f msecs", diff.tv_sec * 1000 + diff.tv_usec / 1000.0); - } + if (snd_pcm_status_get_state (status) == SND_PCM_STATE_XRUN) { + struct timeval now, diff, tstamp; - gst_alsa_stop_audio (this); - gst_alsa_start_audio (this); + gettimeofday (&now, 0); + snd_pcm_status_get_trigger_tstamp (status, &tstamp); + timersub (&now, &tstamp, &diff); + g_warning ("alsa: xrun of at least %.3f msecs", diff.tv_sec * 1000 + diff.tv_usec / 1000.0); + } + + gst_alsa_stop_audio (this); + + /* increase the period count to prevent further xruns (at the cost of + increased latency and memory usage). only do this if it's allowed. */ + if ((this->period_count <= 32) && this->autorecover) { + this->period_count *= 2; + this->buffer_frames = this->period_count * this->period_frames; + gst_alsa_set_params(this); + } + + gst_alsa_start_audio (this); } +static gboolean +gst_alsa_src_process (GstAlsa *this, snd_pcm_uframes_t frames) +{ + GstAlsaPad *pad = NULL; + GstBuffer *buf; + GstCaps *caps; + GList *l; + guint32 channel; + gint unit; + + static gboolean caps_set = FALSE; + + /* let's get on the caps-setting merry-go-round! */ + if (!caps_set) { + caps = gst_alsa_caps (this); + l = this->pads; + while (l) { + if (gst_pad_try_set_caps (GST_ALSA_PAD (l)->pad, caps) <= 0) { + g_print ("setting caps (%p) in source (%p) failed\n", caps, this); + sleep (1); + return FALSE; + } + + l = l->next; + } + caps_set = TRUE; + } + + unit = this->sample_bytes * (this->data_interleaved ? this->channels : 1); + + /* pull the same amount of data into each pad. create an outgoing buffer with + the data, and push the buffer. lather, rinse, repeat. */ + while (frames) { + l = this->pads; + while (l) { + pad = GST_ALSA_PAD (l); + + if (!pad->buf) + pad->buf = g_malloc (this->period_frames * unit); + + channel = (pad->channel > 0) ? pad->channel - 1 : 0; + memcpy (pad->buf + pad->offset * unit, this->access_addr[channel], + MIN (frames, this->period_frames - pad->offset) * unit); + + pad->offset += MIN (frames, this->period_frames - pad->offset); + + if (pad->offset >= this->period_frames) { + g_assert (pad->offset <= this->period_frames); + buf = gst_buffer_new (); + GST_BUFFER_DATA (buf) = pad->buf; + GST_BUFFER_SIZE (buf) = this->period_frames * unit; + GST_BUFFER_MAXSIZE (buf) = this->period_frames * unit; + gst_pad_push (pad->pad, buf); + pad->buf = NULL; + pad->offset = 0; + } + l = l->next; + } + /* all pads should have the same amount of data, shouldn't matter which one + we use for updating output frames ... */ + frames -= MIN (frames, this->period_frames - pad->offset); + } + + return TRUE; +} + +static gboolean +gst_alsa_sink_process (GstAlsa *this, snd_pcm_uframes_t frames) +{ + guint8 *peeked; + guint32 len, channel, num_peeked = 0; + gboolean muted; + GstAlsaPad *pad; + GList *l; + + /* we need to check sample bytes here because the sample_bytes will change, + probably, when caps are set, which will occur after the first + bytestream_peek. we underestimate the amount of data we will need by + peeking "frames" bytes only. */ + + if (!this->sample_bytes) { + pad = GST_ALSA_PAD (this->pads); + + if (!pad->bs) + pad->bs = gst_bytestream_new (pad->pad); + + while (num_peeked == 0) { + num_peeked = gst_bytestream_peek_bytes (pad->bs, &peeked, frames); + if (num_peeked == 0) { + gst_alsa_sink_check_event (this, pad); + } else if (num_peeked < frames) { + g_warning ("could not make initial pull of %d bytes on pad %s:%s", + (int) frames, GST_DEBUG_PAD_NAME (pad->pad)); + gst_element_set_eos (GST_ELEMENT (this)); + return FALSE; + } + } + + if (!this->sample_bytes) { + g_critical ("alsa plugin requires a pipeline that can adequately set caps."); + return FALSE; + } + } + + len = frames * this->channels * this->sample_bytes; + + /* write len bytes out to each of our pads' mmap_area pointers. */ + l = this->pads; + while (l) { + pad = GST_ALSA_PAD (l); + + channel = (pad->channel > 0) ? pad->channel - 1 : 0; + muted = (1 << channel) && this->mute; + + if (!pad->bs) + pad->bs = gst_bytestream_new (pad->pad); + + num_peeked = gst_bytestream_peek_bytes (pad->bs, &peeked, len); + if (num_peeked == 0) { + gst_alsa_sink_check_event (this, pad); + } else if (peeked && this->access_addr[channel] && (! muted)) { + memcpy (this->access_addr[channel], peeked, num_peeked); + gst_bytestream_flush (pad->bs, num_peeked); + } else { + g_warning ("error while writing %u bytes (peeked into %p) to %p", + num_peeked, peeked, this->access_addr[channel]); + } + + l = l->next; + } + + return TRUE; +} + +static void +gst_alsa_sink_check_event (GstAlsa *this, GstAlsaPad *pad) +{ + GstEvent *event = NULL; + guint8 *peeked; + guint32 avail, channel, num_peeked = 0; + + gst_bytestream_get_status (pad->bs, &avail, &event); + + if (event) { + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + channel = (pad->channel > 0) ? pad->channel - 1 : 0; + num_peeked = gst_bytestream_peek_bytes (pad->bs, &peeked, avail); + if (num_peeked && peeked) + memcpy (this->access_addr[channel], peeked, num_peeked); + + gst_element_set_eos (GST_ELEMENT (this)); + gst_event_unref (event); + + /* FIXME: remove this alsa pad from the element */ + } else { + g_warning ("GstAlsaSink: got an unknown event (Type: %d)", GST_EVENT_TYPE (event)); + } + } else { + /* the element at the top of the chain did not emit an eos event. */ + g_assert_not_reached (); + } +} + +/*** AUDIO SETUP / START / STOP ***********************************************/ + /* taken more or less from pbd's audioengine code */ static gboolean gst_alsa_set_params (GstAlsa *this) { - snd_pcm_sw_params_t *sw_param; - snd_pcm_hw_params_t *hw_param; - snd_pcm_access_mask_t *mask; - gint ret; + snd_pcm_sw_params_t *sw_param; + snd_pcm_hw_params_t *hw_param; + snd_pcm_access_mask_t *mask; + gint ret; - g_return_val_if_fail(this != NULL, FALSE); - g_return_val_if_fail(this->handle != NULL, FALSE); + g_return_val_if_fail (this != NULL, FALSE); + g_return_val_if_fail (this->handle != NULL, FALSE); - DEBUG("Preparing channel: %s %dHz, %d channels\n", - snd_pcm_format_name(this->format), - this->rate, this->channels); + g_print ("Preparing channel: %s %dHz, %d channels\n", + snd_pcm_format_name (this->format), this->rate, this->channels); - snd_pcm_hw_params_alloca(&hw_param); - snd_pcm_sw_params_alloca(&sw_param); + snd_pcm_hw_params_alloca (&hw_param); + snd_pcm_sw_params_alloca (&sw_param); - ret = snd_pcm_hw_params_any(this->handle, hw_param); + ret = snd_pcm_hw_params_any (this->handle, hw_param); + if (ret < 0) { + g_warning ("Broken configuration for this PCM: no configurations available"); + return FALSE; + } + + if ((ret = snd_pcm_hw_params_set_periods_integer (this->handle, hw_param)) < 0) { + g_warning ("cannot restrict period size to integral value."); + return FALSE; + } + + mask = alloca (snd_pcm_access_mask_sizeof ()); + snd_pcm_access_mask_none (mask); + + if (this->data_interleaved) + snd_pcm_access_mask_set (mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); + + snd_pcm_access_mask_set (mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + ret = snd_pcm_hw_params_set_access_mask (this->handle, hw_param, mask); + if (ret < 0) { + g_warning ("the gstreamer alsa plugin does not support your hardware."); + return FALSE; + } + + if (this->format != SND_PCM_FORMAT_UNKNOWN) { + ret = snd_pcm_hw_params_set_format (this->handle, hw_param, this->format); if (ret < 0) { - g_warning("Broken configuration for this PCM: no configurations available"); - return FALSE; + g_warning ("Sample format (%s) not available: %s", + snd_pcm_format_name (this->format), snd_strerror (ret)); + return FALSE; } + this->sample_bytes = snd_pcm_format_physical_width (this->format) / 8; + } - if ((ret = snd_pcm_hw_params_set_periods_integer (this->handle, hw_param)) < 0) { - g_warning("cannot restrict period size to integral value."); - return FALSE; - } + ret = snd_pcm_hw_params_set_channels (this->handle, hw_param, this->channels); + if (ret < 0) { + g_warning ("Channels count (%d) not available: %s", this->channels, snd_strerror (ret)); + return FALSE; + } + this->channels = snd_pcm_hw_params_get_channels (hw_param); - mask = alloca(snd_pcm_access_mask_sizeof()); - snd_pcm_access_mask_none(mask); - - if (this->data_interleaved) { - snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); - } else { - snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); - } - ret = snd_pcm_hw_params_set_access_mask(this->handle, hw_param, mask); + if (this->rate) { + ret = snd_pcm_hw_params_set_rate (this->handle, hw_param, this->rate, 0); if (ret < 0) { - g_warning("the gstreamer alsa plugin does not support your hardware."); - return FALSE; + g_warning ("error setting rate (%d): %s", this->rate, snd_strerror (ret)); + return FALSE; } + } - if (this->format != SND_PCM_FORMAT_UNKNOWN) { - ret = snd_pcm_hw_params_set_format(this->handle, hw_param, this->format); - if (ret < 0) { - g_warning("Sample format (%s) not available: %s", snd_pcm_format_name(this->format), snd_strerror(ret)); - return FALSE; - } - this->sample_bytes = snd_pcm_format_physical_width(this->format) / 8; - } - - ret = snd_pcm_hw_params_set_channels(this->handle, hw_param, this->channels); + if (this->period_count) { + ret = snd_pcm_hw_params_set_periods (this->handle, hw_param, this->period_count, 0); if (ret < 0) { - g_warning("Channels count (%d) not available: %s", this->channels, snd_strerror(ret)); - return FALSE; + g_warning ("error setting period count minimum (%d): %s", + this->period_count, snd_strerror (ret)); + return FALSE; } - this->channels = snd_pcm_hw_params_get_channels(hw_param); + } - if (this->rate) { - ret = snd_pcm_hw_params_set_rate(this->handle, hw_param, this->rate, 0); - if (ret < 0) { - g_warning("error setting rate (%d): %s", this->rate, snd_strerror(ret)); - return FALSE; - } - } - - if (this->period_count) { - ret = snd_pcm_hw_params_set_periods (this->handle, hw_param, this->period_count, 0); - if (ret < 0) { - g_warning("error setting period count minimum (%d): %s", this->period_count, snd_strerror(ret)); - return FALSE; - } - } - - if (this->period_frames) { - ret = snd_pcm_hw_params_set_period_size (this->handle, hw_param, this->period_frames, 0); - if (ret < 0) { - g_warning("error setting period in frames (%d): %s", this->period_frames, snd_strerror(ret)); - return FALSE; - } - } - - if (this->buffer_frames) { - ret = snd_pcm_hw_params_set_buffer_size (this->handle, hw_param, this->buffer_frames); - if (ret < 0) { - g_warning("error setting buffer size (%d): %s", this->buffer_frames, snd_strerror(ret)); - return FALSE; - } - } - - ret = snd_pcm_hw_params(this->handle, hw_param); + if (this->period_frames) { + ret = snd_pcm_hw_params_set_period_size (this->handle, hw_param, this->period_frames, 0); if (ret < 0) { - g_warning("could not set hw params: %s", snd_strerror(ret)); - snd_pcm_hw_params_dump(hw_param, this->out); - return FALSE; + g_warning ("error setting period in frames (%d): %s", + this->period_frames, snd_strerror (ret)); + return FALSE; } + } - if (!this->rate) - this->rate = snd_pcm_hw_params_get_rate(hw_param, 0); - if (!this->format) - this->format = snd_pcm_hw_params_get_format(hw_param); - if (!this->period_count) - this->period_count = snd_pcm_hw_params_get_periods(hw_param, 0); - if (!this->period_frames) - this->period_frames = snd_pcm_hw_params_get_period_size(hw_param, 0); - if (!this->buffer_frames) - this->buffer_frames = snd_pcm_hw_params_get_buffer_size(hw_param); - if (this->buffer_frames != this->period_count * this->period_frames) - g_critical ("buffer size != period size * number of periods, unexpected things may happen!"); - - snd_pcm_sw_params_current (this->handle, sw_param); - - ret = snd_pcm_sw_params_set_start_threshold (this->handle, sw_param, ~0U); + if (this->buffer_frames) { + ret = snd_pcm_hw_params_set_buffer_size (this->handle, hw_param, this->buffer_frames); if (ret < 0) { - g_warning("could not set start mode: %s", snd_strerror(ret)); - return FALSE; + g_warning ("error setting buffer size (%d): %s", this->buffer_frames, snd_strerror (ret)); + return FALSE; } + } - ret = snd_pcm_sw_params_set_stop_threshold (this->handle, sw_param, this->buffer_frames); - if (ret < 0) { - g_warning("could not set stop mode: %s", snd_strerror(ret)); - return FALSE; - } + ret = snd_pcm_hw_params (this->handle, hw_param); + if (ret < 0) { + g_warning ("could not set hw params: %s", snd_strerror (ret)); + snd_pcm_hw_params_dump (hw_param, this->out); + return FALSE; + } - ret = snd_pcm_sw_params_set_silence_threshold (this->handle, sw_param, 0); - if (ret < 0) { - g_warning("could not set silence threshold: %s", snd_strerror(ret)); - return FALSE; - } + if (!this->rate) + this->rate = snd_pcm_hw_params_get_rate (hw_param, 0); + if (!this->format) + this->format = snd_pcm_hw_params_get_format (hw_param); + if (!this->period_count) + this->period_count = snd_pcm_hw_params_get_periods (hw_param, 0); + if (!this->period_frames) + this->period_frames = snd_pcm_hw_params_get_period_size (hw_param, 0); + if (!this->buffer_frames) + this->buffer_frames = snd_pcm_hw_params_get_buffer_size (hw_param); + if (this->buffer_frames != this->period_count * this->period_frames) + g_critical ("buffer size != period size * number of periods, unexpected things may happen!"); - ret = snd_pcm_sw_params_set_silence_size (this->handle, sw_param, this->buffer_frames); - if (ret < 0) { - g_warning("could not set silence size: %s", snd_strerror(ret)); - return FALSE; - } + snd_pcm_sw_params_current (this->handle, sw_param); - ret = snd_pcm_sw_params_set_avail_min (this->handle, sw_param, this->period_frames); - if (ret < 0) { - g_warning("could not set avail min: %s", snd_strerror(ret)); - return FALSE; - } + ret = snd_pcm_sw_params_set_start_threshold (this->handle, sw_param, ~0U); + if (ret < 0) { + g_warning ("could not set start mode: %s", snd_strerror (ret)); + return FALSE; + } - ret = snd_pcm_sw_params (this->handle, sw_param); - if (ret < 0) { - g_warning("could not set sw_params: %s", snd_strerror(ret)); - return FALSE; - } + ret = snd_pcm_sw_params_set_stop_threshold (this->handle, sw_param, this->buffer_frames); + if (ret < 0) { + g_warning ("could not set stop mode: %s", snd_strerror (ret)); + return FALSE; + } - if (this->debug) - snd_pcm_dump(this->handle, this->out); + ret = snd_pcm_sw_params_set_silence_threshold (this->handle, sw_param, 0); + if (ret < 0) { + g_warning ("could not set silence threshold: %s", snd_strerror (ret)); + return FALSE; + } - this->access_interleaved = !(snd_pcm_hw_params_get_access (hw_param) == - SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + ret = snd_pcm_sw_params_set_silence_size (this->handle, sw_param, this->buffer_frames); + if (ret < 0) { + g_warning ("could not set silence size: %s", snd_strerror (ret)); + return FALSE; + } - if (this->access_interleaved) { - this->interleave_unit = this->sample_bytes; - this->interleave_skip = this->interleave_unit * this->channels; - } else { - this->interleave_unit = 0; /* not used */ - this->interleave_skip = this->sample_bytes; - } + ret = snd_pcm_sw_params_set_avail_min (this->handle, sw_param, this->period_frames); + if (ret < 0) { + g_warning ("could not set avail min: %s", snd_strerror (ret)); + return FALSE; + } - if (this->access_addr) - g_free (this->access_addr); - this->access_addr = g_new0 (char*, this->channels); + ret = snd_pcm_sw_params (this->handle, sw_param); + if (ret < 0) { + g_warning ("could not set sw_params: %s", snd_strerror (ret)); + return FALSE; + } - return TRUE; + if (this->debug) + snd_pcm_dump (this->handle, this->out); + + this->access_interleaved = !(snd_pcm_hw_params_get_access (hw_param) == + SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + + if (this->access_interleaved) { + this->interleave_unit = this->sample_bytes; + this->interleave_skip = this->interleave_unit * this->channels; + } else { + this->interleave_unit = 0; /* not used */ + this->interleave_skip = this->sample_bytes; + } + + if (this->access_addr) + g_free (this->access_addr); + this->access_addr = g_new0 (char *, this->channels); + + return TRUE; } static gboolean -gst_alsa_open_audio(GstAlsa *this) +gst_alsa_open_audio (GstAlsa *this) { - gint ret; - g_assert(this != NULL); + gint ret; - if (this->handle) - gst_alsa_close_audio(this); + g_assert (this != NULL); - DEBUG("Opening alsa device \"%s\" for %s...\n", this->device, - this->stream==SND_PCM_STREAM_PLAYBACK ? "playback" : "capture"); + if (this->handle) + gst_alsa_close_audio (this); - ret = snd_output_stdio_attach(&this->out, stdout, 0); - if (ret < 0) { - g_print("error opening log output: %s\n", snd_strerror(ret)); - return FALSE; - } + g_print ("Opening alsa device \"%s\" for %s...\n", this->device, + this->stream == SND_PCM_STREAM_PLAYBACK ? "playback" : "capture"); - /* blocking i/o */ - if ((ret = snd_pcm_open(&this->handle, this->device, this->stream, 0))) { - g_print("error opening pcm device %s: %s\n", - this->device, snd_strerror(ret)); - return FALSE; - } + ret = snd_output_stdio_attach (&this->out, stdout, 0); + if (ret < 0) { + g_print ("error opening log output: %s\n", snd_strerror (ret)); + return FALSE; + } - if (gst_alsa_set_params(this) == FALSE) { - gst_alsa_close_audio(this); - return FALSE; - } + /* blocking i/o */ + if ((ret = snd_pcm_open (&this->handle, this->device, this->stream, 0))) { + g_print ("error opening pcm device %s: %s\n", this->device, snd_strerror (ret)); + return FALSE; + } - GST_FLAG_SET(this, GST_ALSA_OPEN); - return TRUE; + if (gst_alsa_set_params (this) == FALSE) { + gst_alsa_close_audio (this); + return FALSE; + } + + GST_FLAG_SET (this, GST_ALSA_OPEN); + return TRUE; } static gboolean -gst_alsa_start_audio(GstAlsa *this) +gst_alsa_start_audio (GstAlsa *this) { - gint err; - guint32 chn; + gint err; - g_return_val_if_fail(this != NULL, FALSE); - g_return_val_if_fail(this->handle != NULL, FALSE); + g_return_val_if_fail (this != NULL, FALSE); + g_return_val_if_fail (this->handle != NULL, FALSE); - if ((err = snd_pcm_prepare (this->handle)) < 0) { - g_warning("channel prepare failed: %s", snd_strerror (err)); - return FALSE; - } + if ((err = snd_pcm_prepare (this->handle)) < 0) { + g_warning ("channel prepare failed: %s", snd_strerror (err)); + return FALSE; + } - this->avail = snd_pcm_avail_update (this->handle); + this->avail = snd_pcm_avail_update (this->handle); - if (this->stream == SND_PCM_STREAM_PLAYBACK && - this->avail != this->buffer_frames) { - g_warning ("full buffer not available at start"); - return FALSE; - } + if (this->stream == SND_PCM_STREAM_PLAYBACK && this->avail != this->buffer_frames) { + g_warning ("full buffer not available at start"); + return FALSE; + } - if (!gst_alsa_get_channel_addresses (this)) { - return FALSE; - } + if (!gst_alsa_get_channel_addresses (this)) + return FALSE; - if (this->stream == SND_PCM_STREAM_PLAYBACK) { - for (chn = 0; chn < this->channels; chn++) { - gst_alsa_sink_silence_on_channel (this, chn, this->buffer_frames); - } - } + gst_alsa_release_channel_addresses (this); + if ((err = snd_pcm_start (this->handle)) < 0) { + g_warning ("could not start audio: %s", snd_strerror (err)); + return FALSE; + } + + GST_FLAG_SET (this, GST_ALSA_RUNNING); + return TRUE; +} + +static void +gst_alsa_stop_audio (GstAlsa *this) +{ + gint err; + + g_assert (this != NULL); + + g_return_if_fail (this != NULL); + g_return_if_fail (this->handle != NULL); + + if (this->mmap_open) gst_alsa_release_channel_addresses (this); - if ((err = snd_pcm_start (this->handle)) < 0) { - g_warning("could not start audio: %s", snd_strerror (err)); - return FALSE; - } + if (this->stream == SND_PCM_STREAM_PLAYBACK && (err = snd_pcm_drop (this->handle)) < 0) { + g_warning ("channel flush failed: %s", snd_strerror (err)); + return; + } - GST_FLAG_SET(this, GST_ALSA_RUNNING); - return TRUE; + GST_FLAG_UNSET (this, GST_ALSA_RUNNING); } static void -gst_alsa_stop_audio(GstAlsa *this) +gst_alsa_close_audio (GstAlsa *this) { - gint err; - g_assert(this != NULL); + g_return_if_fail (this != NULL); + g_return_if_fail (this->handle != NULL); - g_return_if_fail(this != NULL); - g_return_if_fail(this->handle != NULL); + snd_pcm_close (this->handle); - if (this->mmap_open) - gst_alsa_release_channel_addresses (this); + this->handle = NULL; - if (this->stream == SND_PCM_STREAM_PLAYBACK && - (err = snd_pcm_drop (this->handle)) < 0) { - g_warning("channel flush failed: %s", snd_strerror (err)); - return; - } - - GST_FLAG_UNSET(this, GST_ALSA_RUNNING); -} - -static void -gst_alsa_close_audio(GstAlsa *this) -{ - g_return_if_fail(this != NULL); - g_return_if_fail(this->handle != NULL); - - snd_pcm_close(this->handle); - - this->handle = NULL; - - GST_FLAG_UNSET(this, GST_ALSA_OPEN); + GST_FLAG_UNSET (this, GST_ALSA_OPEN); } static gboolean gst_alsa_get_channel_addresses (GstAlsa *this) { - guint32 err, i; - const snd_pcm_channel_area_t *a; - GList *l; + guint32 err, i; + const snd_pcm_channel_area_t *a; - g_return_val_if_fail (this->mmap_open == FALSE, FALSE); + g_return_val_if_fail (this->mmap_open == FALSE, FALSE); - if ((err = snd_pcm_mmap_begin (this->handle, &this->mmap_areas, &this->offset, &this->avail)) < 0) { - g_warning("gstalsa: mmap failed: %s", snd_strerror(err)); - return FALSE; - } + if ((err = snd_pcm_mmap_begin (this->handle, &this->mmap_areas, &this->offset, &this->avail)) < 0) { + g_warning ("gstalsa: mmap failed: %s", snd_strerror (err)); + return FALSE; + } - GST_DEBUG(0, "got %d mmap'd frames", (int)this->avail); + GST_DEBUG (0, "got %d mmap'd frames", (int) this->avail); - l = this->pads; - while (l) { - a = &this->mmap_areas[GST_ALSA_PAD(l)->channel > 0 ? - GST_ALSA_PAD(l)->channel-1 : 0]; - GST_ALSA_PAD(l)->access_addr = (char *) a->addr + ((a->first + a->step * - this->offset) / 8); - l = l->next; - } + for (i = 0; i < this->channels; i++) { + a = &this->mmap_areas[i]; + this->access_addr[i] = + (char *) a->addr + ((a->first + a->step * this->offset) / 8); + } - for (i=0; ichannels; i++) { - a = &this->mmap_areas[i]; - this->access_addr[i] = (char *) a->addr + ((a->first + a->step * - this->offset) / 8); - } + this->mmap_open = TRUE; - this->mmap_open = TRUE; - - return TRUE; + return TRUE; } static void -gst_alsa_release_channel_addresses (GstAlsa *this) +gst_alsa_release_channel_addresses (GstAlsa * this) { - guint32 err, i; - GList *l; + guint32 err, i; - g_return_if_fail (this->mmap_open == TRUE); + g_return_if_fail (this->mmap_open == TRUE); - GST_DEBUG(0, "releasing mmap'd data region: %d frames", (int)this->avail); + GST_DEBUG (0, "releasing mmap'd data region: %d frames", (int) this->avail); - if ((err = snd_pcm_mmap_commit (this->handle, this->offset, this->avail)) < 0) { - g_warning("gstalsa: mmap commit failed: %s", snd_strerror(err)); - return; - } + if ((err = snd_pcm_mmap_commit (this->handle, this->offset, this->avail)) < 0) { + g_warning ("gstalsa: mmap commit failed: %s", snd_strerror (err)); + return; + } - l = this->pads; - while (l) { - GST_ALSA_PAD(l)->access_addr = NULL; - l = l->next; - } + for (i = 0; i < this->channels; i++) + this->access_addr[i] = NULL; - for (i=0; ichannels; i++) { - this->access_addr[i] = NULL; - } - - this->mmap_open = FALSE; - this->avail=0; + this->mmap_open = FALSE; + this->avail = 0; } -static void -gst_alsa_sink_silence_on_channel (GstAlsa *this, guint32 chn, guint32 nframes) -{ - if (this->access_interleaved) { - memset_interleave - (this->access_addr[chn], - 0, nframes * this->sample_bytes, - this->interleave_unit, - this->interleave_skip); - } else { - memset (this->access_addr[chn], 0, nframes * this->sample_bytes); - } -} - -/* taken directly from paul davis' memops.cc */ -static void -memset_interleave (char *dst, char val, unsigned int bytes, - unsigned int unit_bytes, - unsigned int skip_bytes) -{ - switch (unit_bytes) { - case 1: - while (bytes--) { - *dst = val; - dst += skip_bytes; - } - break; - case 2: - while (bytes) { - *((short *) dst) = (short) val; - dst += skip_bytes; - bytes -= 2; - } - break; - case 4: - while (bytes) { - *((int *) dst) = (int) val; - dst += skip_bytes; - bytes -= 4; - } - break; - } -} +/*** GSTREAMER PLUGIN *********************************************************/ static gboolean -plugin_init (GModule *module, GstPlugin *plugin) +plugin_init (GModule * module, GstPlugin * plugin) { - GstElementFactory *factory; + GstElementFactory *factory; - if (!gst_library_load ("gstbytestream")) - return FALSE; + if (!gst_library_load ("gstbytestream")) + return FALSE; - factory = gst_element_factory_new ("alsasrc", GST_TYPE_ALSA_SRC, - &gst_alsa_src_details); - g_return_val_if_fail (factory != NULL, FALSE); - gst_element_factory_add_pad_template (factory, gst_alsa_src_pad_factory ()); - gst_element_factory_add_pad_template (factory, - gst_alsa_src_request_pad_factory ()); - gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); + factory = gst_element_factory_new ("alsasrc", GST_TYPE_ALSA_SRC, &gst_alsa_src_details); + g_return_val_if_fail (factory != NULL, FALSE); + gst_element_factory_add_pad_template (factory, gst_alsa_src_pad_factory ()); + gst_element_factory_add_pad_template (factory, gst_alsa_src_request_pad_factory ()); + gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); - factory = gst_element_factory_new ("alsasink", GST_TYPE_ALSA_SINK, - &gst_alsa_sink_details); - g_return_val_if_fail (factory != NULL, FALSE); - gst_element_factory_add_pad_template (factory, gst_alsa_sink_pad_factory()); - gst_element_factory_add_pad_template (factory, - gst_alsa_sink_request_pad_factory()); - gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); + factory = gst_element_factory_new ("alsasink", GST_TYPE_ALSA_SINK, &gst_alsa_sink_details); + g_return_val_if_fail (factory != NULL, FALSE); + gst_element_factory_add_pad_template (factory, gst_alsa_sink_pad_factory ()); + gst_element_factory_add_pad_template (factory, gst_alsa_sink_request_pad_factory ()); + gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); - gst_plugin_set_longname(plugin, "ALSA plugin library"); + gst_plugin_set_longname (plugin, "ALSA plugin library"); - return TRUE; + return TRUE; } GstPluginDesc plugin_desc = { - GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "alsa", - plugin_init + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "alsa", + plugin_init }; - - diff --git a/ext/alsa/gstalsa.h b/ext/alsa/gstalsa.h index 56de5e125c..d8ef531804 100644 --- a/ext/alsa/gstalsa.h +++ b/ext/alsa/gstalsa.h @@ -1,23 +1,22 @@ -/* -*- c-basic-offset: 4 -*- */ /* - Copyright (C) 2001 CodeFactory AB - Copyright (C) 2001 Thomas Nyberg - Copyright (C) 2001-2002 Andy Wingo - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - 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 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ + * Copyright (C) 2001 CodeFactory AB + * Copyright (C) 2001 Thomas Nyberg + * Copyright (C) 2001-2002 Andy Wingo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ #ifndef __GST_ALSA_H__ #define __GST_ALSA_H__ @@ -58,68 +57,68 @@ typedef GstAlsa GstAlsaSrc; typedef GstAlsaClass GstAlsaSrcClass; enum { - GST_ALSA_OPEN = GST_ELEMENT_FLAG_LAST, - GST_ALSA_RUNNING, - GST_ALSA_FLAG_LAST = GST_ELEMENT_FLAG_LAST + 3, + GST_ALSA_OPEN = GST_ELEMENT_FLAG_LAST, + GST_ALSA_RUNNING, + GST_ALSA_FLAG_LAST = GST_ELEMENT_FLAG_LAST + 3, }; typedef gboolean (*GstAlsaProcessFunc) (GstAlsa *, snd_pcm_uframes_t frames); typedef struct { - gint channel; - GstPad *pad; - GstByteStream *bs; - /* pointer to where we can access mmap_areas */ - char *access_addr; - char *buf; - /* how much of the buffer we have used */ - snd_pcm_uframes_t offset; + gint channel; + GstPad *pad; + GstByteStream *bs; + + /* buf and offset are only used for src elements to hold data from the sound + card, while we're waiting for an entire period_frames data. */ + char *buf; + snd_pcm_uframes_t offset; } GstAlsaPad; struct _GstAlsa { - GstElement parent; + GstElement parent; - /* list of GstAlsaPads */ - GList *pads; - - gchar *device; - snd_pcm_stream_t stream; - - snd_pcm_t *handle; - snd_output_t *out; - - /* our mmap'd data areas */ - gboolean mmap_open; - const snd_pcm_channel_area_t *mmap_areas; - char **access_addr; - snd_pcm_uframes_t offset; - snd_pcm_sframes_t avail; - - GstAlsaProcessFunc process; - - snd_pcm_format_t format; - guint rate; - gint channels; - guint32 mute; /* bitmask. */ - - /* the gstreamer data */ - gboolean data_interleaved; - - /* access to the hardware */ - gboolean access_interleaved; - guint sample_bytes; - guint interleave_unit; - guint interleave_skip; - - guint buffer_frames; - guint period_count; /* 'number of fragments' in oss-speak */ - guint period_frames; + /* list of GstAlsaPads */ + GList *pads; - gboolean debug; + gchar *device; + snd_pcm_stream_t stream; + snd_pcm_t *handle; + snd_output_t *out; + + /* our mmap'd data areas */ + gboolean mmap_open; + const snd_pcm_channel_area_t *mmap_areas; + char **access_addr; + snd_pcm_uframes_t offset; + snd_pcm_sframes_t avail; + + GstAlsaProcessFunc process; + + snd_pcm_format_t format; + guint rate; + gint channels; + guint32 mute; /* bitmask. */ + + /* the gstreamer data */ + gboolean data_interleaved; + gboolean autorecover; + + /* access to the hardware */ + gboolean access_interleaved; + guint sample_bytes; + guint interleave_unit; + guint interleave_skip; + + guint buffer_frames; + guint period_count; /* 'number of fragments' in oss-speak */ + guint period_frames; + + gboolean debug; }; struct _GstAlsaClass { - GstElementClass parent_class; + GstElementClass parent_class; }; GType gst_alsa_get_type (void);