From 0a6cc4d112b73b22c24babad0f2c2153fa35a038 Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sun, 23 Dec 2001 00:25:30 +0000 Subject: [PATCH] added alsa Original commit message from CVS: added alsa --- TODO | 1 + configure.ac | 1 + ext/Makefile.am | 10 +- ext/alsa/Makefile.am | 13 + ext/alsa/README | 64 +++ ext/alsa/gstalsa.c | 1433 ++++++++++++++++++++++++++++++++++++++++++++++++++ ext/alsa/gstalsa.h | 126 +++++ 7 files changed, 1646 insertions(+), 2 deletions(-) create mode 100644 ext/alsa/Makefile.am create mode 100644 ext/alsa/README create mode 100644 ext/alsa/gstalsa.c create mode 100644 ext/alsa/gstalsa.h diff --git a/TODO b/TODO index f229dca..bfcbdf4 100644 --- a/TODO +++ b/TODO @@ -17,3 +17,4 @@ * check options in a52, it has some arch stuff and some opti stuff that looks dodgy + diff --git a/configure.ac b/configure.ac index 89fe6d4..1e4660c 100644 --- a/configure.ac +++ b/configure.ac @@ -1152,6 +1152,7 @@ sys/xvideo/Makefile ext/Makefile ext/a52/Makefile ext/aalib/Makefile +ext/alsa/Makefile ext/audiofile/Makefile ext/esd/Makefile ext/lame/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index 2b2aca0..ff91846 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -10,6 +10,12 @@ else AALIB_DIR= endif +if USE_ALSA +ALSA_DIR=alsa +else +ALSA_DIR= +endif + if USE_AUDIOFILE AUDIOFILE_DIR=audiofile else @@ -47,8 +53,8 @@ VORBIS_DIR= endif -SUBDIRS=$(A52_DIR) $(AALIB_DIR) $(AUDIOFILE_DIR) $(ESD_DIR) \ +SUBDIRS=$(A52_DIR) $(AALIB_DIR) $(ALSA_DIR) $(AUDIOFILE_DIR) $(ESD_DIR) \ $(LAME_DIR) $(MAD_DIR) \ $(SDL_DIR) $(VORBIS_DIR) -DIST_SUBDIRS=a52 aalib audiofile esd lame mad sdl vorbis +DIST_SUBDIRS=a52 aalib alsa audiofile esd lame mad sdl vorbis diff --git a/ext/alsa/Makefile.am b/ext/alsa/Makefile.am new file mode 100644 index 0000000..75ff111 --- /dev/null +++ b/ext/alsa/Makefile.am @@ -0,0 +1,13 @@ +plugindir = $(libdir)/gst + +plugin_LTLIBRARIES = libgstalsa.la + +libgstalsa_la_SOURCES = gstalsa.c + +noinst_HEADERS = gstalsa.h + +libgstalsa_la_CFLAGS = $(ALSA_CFLAGS) $(GST_CFLAGS) +libgstalsa_la_LIBADD = $(ALSA_LIBS) + +EXTRA_DIST = README + diff --git a/ext/alsa/README b/ext/alsa/README new file mode 100644 index 0000000..406b83a --- /dev/null +++ b/ext/alsa/README @@ -0,0 +1,64 @@ +0.0 History +----------- + +This plugin was originally written by Thomas Nyberg for +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. + +1.0 Introduction +---------------- + +This plugin was designed for use with ALSA 0.9.x, using other versions most +assuredly will not work. + +The plugin will probe which cards and pcm-devices that are availible, and then +it tries to determine their capabilities. This will allow the use of multiple +cards with multiple pcm-devices - at the same time! Since I only have one card +on my system, as of yet, I haven't had the chance to try this out. + +2.0 How to use it +----------------- + +Since we can have multiple soundcards, each having the same name - and also +multiple pcm-devices on each card, a problem with naming occurs. When +the alsa-plugins are initialized, a factory is created for each card and +pcm-device on the system. + +On my desktop, I have the following: + +wingo@cebreiro:~/src/gstreamer$ gstreamer-inspect alsa +INFO ( 339:-1) Initializing GStreamer Core Library +INFO ( 339:-1) CPU features: (808009bf) MMX 3DNOW +Plugin Details: + Name: alsa + Long Name: Alsa plugin library + Filename: /usr/lib/gst/libalsa.so + + ES1370/1_sink: ES1370 DAC2/ADC + ES1370/2_sink: ES1370 DAC1 + ES1370/1_src: ES1370 DAC2/ADC + alsasink: default alsa sink + alsasrc: default alsa src + +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 +default alsa devices. + +3.0 License +----------- + +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. + + + + + + diff --git a/ext/alsa/gstalsa.c b/ext/alsa/gstalsa.c new file mode 100644 index 0000000..8cd8afc --- /dev/null +++ b/ext/alsa/gstalsa.c @@ -0,0 +1,1433 @@ +/* + Copyright (C) 2001 CodeFactory AB + Copyright (C) 2001 Thomas Nyberg + Copyright (C) 2001 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 "alsa.h" + +static GstElementDetails gst_alsa_sink_details = { + "Alsa Sink", + "Sink/Audio", + "Output to a sound card via ALSA: see plugins/alsa/README for more info", + VERSION, + "Thomas Nyberg , " + "Andy Wingo ", + "(C) 2001 " +}; + +static GstElementDetails gst_alsa_src_details = { + "Alsa Src", + "Source/Audio", + "Read from a sound card via ALSA: see plugins/alsa/README for more info", + VERSION, + "Thomas Nyberg , " + "Andy Wingo " + "(C) 2001", +}; + +static GstElement *parent_class = NULL; + +static void gst_alsa_init(GstAlsa *this); +static void gst_alsa_class_init(GstAlsaClass *klass); + +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 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 GstPadNegotiateReturn gst_alsa_negotiate(GstPad *pad, GstCaps **caps, gpointer *user_data); + +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 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_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); + + +enum { + ARG_0, + ARG_DEVICE, + ARG_FORMAT, + ARG_CHANNELS, + ARG_RATE, + ARG_PERIODCOUNT, + ARG_PERIODFRAMES +}; + +GType +gst_alsa_get_type (void) +{ + static GType alsa_type = 0; + + if (!alsa_type) { + static const GTypeInfo alsa_info = { + sizeof(GstAlsaClass), + NULL, + NULL, + NULL, + NULL, + NULL, + sizeof(GstAlsa), + 0, + NULL, + }; + alsa_type = g_type_register_static (GST_TYPE_ELEMENT, "GstAlsa", &alsa_info, 0); + } + return alsa_type; +} + +GType +gst_alsa_sink_get_type (void) +{ + static GType alsa_type = 0; + + if (!alsa_type) { + static const GTypeInfo alsa_info = { + sizeof(GstAlsaClass), + NULL, + NULL, + (GClassInitFunc)gst_alsa_class_init, + NULL, + NULL, + sizeof(GstAlsa), + 0, + (GInstanceInitFunc)gst_alsa_init, + }; + alsa_type = g_type_register_static (GST_TYPE_ALSA, "GstAlsaSink", &alsa_info, 0); + } + return alsa_type; +} + +GType +gst_alsa_src_get_type (void) +{ + static GType alsa_type = 0; + + if (!alsa_type) { + static const GTypeInfo alsa_info = { + sizeof(GstAlsaClass), + NULL, + NULL, + (GClassInitFunc)gst_alsa_class_init, + NULL, + NULL, + sizeof(GstAlsa), + 0, + (GInstanceInitFunc)gst_alsa_init, + }; + alsa_type = g_type_register_static (GST_TYPE_ALSA, "GstAlsaSrc", &alsa_info, 0); + } + return alsa_type; +} + +static GstPadTemplate* +gst_alsa_src_pad_factory(void) +{ + static GstPadTemplate *template = NULL; + + if (!template) + template = gst_padtemplate_new("src", GST_PAD_SRC, GST_PAD_SOMETIMES, + gst_caps_new("src", "audio/raw", NULL), + NULL); + + return template; +} + +static GstPadTemplate* +gst_alsa_src_request_pad_factory(void) +{ + static GstPadTemplate *template = NULL; + + if (!template) + template = gst_padtemplate_new("src%d", GST_PAD_SRC, GST_PAD_REQUEST, + gst_caps_new("src", "audio/raw", + gst_props_new("channels", GST_PROPS_INT(1), NULL)), + NULL); + + return template; +} + +static GstPadTemplate* +gst_alsa_sink_pad_factory(void) +{ + static GstPadTemplate *template = NULL; + + if (!template) + template = gst_padtemplate_new("sink", GST_PAD_SINK, GST_PAD_SOMETIMES, + gst_caps_new("sink", "audio/raw", NULL), + NULL); + + return template; +} + +static GstPadTemplate* +gst_alsa_sink_request_pad_factory(void) +{ + static GstPadTemplate *template = NULL; + + if (!template) + template = gst_padtemplate_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 void +gst_alsa_class_init(GstAlsaClass *klass) +{ + GObjectClass *object_class; + GstElementClass *element_class; + + object_class = (GObjectClass *)klass; + element_class = (GstElementClass *)klass; + + if (parent_class == NULL) + parent_class = g_type_class_ref(GST_TYPE_ELEMENT); + + object_class->get_property = gst_alsa_get_property; + object_class->set_property = gst_alsa_set_property; + + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_DEVICE, + g_param_spec_string("device","device","alsa device: please see README for details", + "default", + G_PARAM_READWRITE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_FORMAT, + g_param_spec_int("format","format","format", + 0, SND_PCM_FORMAT_LAST, SND_PCM_FORMAT_S16, + G_PARAM_READWRITE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_CHANNELS, + g_param_spec_int("channels","channels","channels", + 1, 64, 1, + G_PARAM_READWRITE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_RATE, + g_param_spec_int("rate","rate","rate", + 8000, 192000, 44100, + G_PARAM_READWRITE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_PERIODCOUNT, + g_param_spec_int("period_count","period count","period count", + 2, 64, 2, + G_PARAM_READWRITE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_PERIODFRAMES, + g_param_spec_int("period_frames","period frames","period frames", + 64, 4096, 256, + G_PARAM_READWRITE)); + + element_class->change_state = gst_alsa_change_state; + + element_class->request_new_pad = gst_alsa_request_new_pad; +} + +static void +gst_alsa_init(GstAlsa *this) +{ + /* init values */ + this->handle = NULL; + + /* i tried to trim this down to only the essentials needed for proper + * functioning; we'll be setting this values 'in earnest' when caps come */ + + this->rate = 44100; + this->channels = 2; + + /* i wish i could leave these to defaults, but when caps come and typically + * the number of channels doubles, you can't have the default count and size + * that alsa gives you from the pre-caps setup apply to the post-caps setup + * due to internal hw buffer size restrictions. these values seem to be + * pretty common, though, although not pushing latency limits */ + this->period_count = 2; + this->period_frames = 4096; + + this->device = g_strdup("default"); + + /* is this right? */ + GST_FLAG_SET(this, GST_ELEMENT_THREAD_SUGGESTED); + + 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 */ + } else { + g_print("you cannot instantiate an object of type gstalsa. use alsasink or alsasrc instead.\n"); + G_BREAKPOINT(); + 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_negotiate_function(GST_ALSA_PAD(this->pads)->pad, gst_alsa_negotiate); + 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) { + if (sscanf(name, templ->name_template, &channel) != 1) { + g_warning("invalid pad name %s (trying to fit with %s)", name, + templ->name_template); + return NULL; + } + 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; + } + } else { + l = this->pads; + channel = 0; + while (l) { + if (GST_ALSA_PAD(l)->channel > channel) + channel = GST_ALSA_PAD(l)->channel; + 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_negotiate_function(pad->pad, gst_alsa_negotiate); + + 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; +} + +static void +gst_alsa_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + 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_int (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; + default: + GST_DEBUG(0, "Unknown arg"); + return; + } + + 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); + } +} + +static void +gst_alsa_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstAlsa *this; + + this = (GstAlsa *)object; + + switch (prop_id) { + case ARG_DEVICE: + g_value_set_string (value, this->device); + break; + case ARG_FORMAT: + g_value_set_int (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; + default: + GST_DEBUG(0, "Unknown arg"); + break; + } +} + +static GstElementStateReturn +gst_alsa_change_state(GstElement *element) +{ + GstAlsa *this; + guint chn; + + 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((GstAlsa *)element); + if (GST_FLAG_IS_SET(element, GST_ALSA_OPEN)) + gst_alsa_close_audio((GstAlsa *)element); + /* FIXME: clean up bytestreams, etc */ + 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((GstAlsa *)element) == 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; + + case GST_STATE_PLAYING: + if (GST_FLAG_IS_SET(element, GST_ALSA_RUNNING) == FALSE) + if (gst_alsa_start_audio((GstAlsa *)element) == FALSE) + 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; +} + +static gboolean +gst_alsa_parse_caps (GstAlsa *this, GstCaps *caps) +{ + gint law, endianness, width, depth; + gboolean sign; + gint format = -1; + const gchar* format_name; + + format_name = gst_caps_get_string(caps, "format"); + + if (format_name == NULL) { + return FALSE; + } else if (strcmp(format_name, "int")==0) { + width = gst_caps_get_int (caps, "width"); + depth = gst_caps_get_int (caps, "depth"); + + law = gst_caps_get_int (caps, "law"); + endianness = gst_caps_get_int (caps, "endianness"); + sign = gst_caps_get_boolean (caps, "signed"); + + 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) { + if (strcmp(gst_caps_get_string(caps, "layout"), "gfloat")==0) { + format = SND_PCM_FORMAT_FLOAT; + } else { + return FALSE; + /* if you need float64, chat w/ me on irc.gstreamer.net and i'll tell you what needs to + * be done. i'm 'wingo'. */ + } + } else { + return FALSE; + } + + this->format = format; + this->rate = gst_caps_get_int(caps, "rate"); + if (this->data_interleaved) + this->channels = gst_caps_get_int(caps, "channels"); + else if (gst_caps_get_int(caps, "channels") != 1) + return FALSE; + + return TRUE; +} + +/* caps are so painful sometimes. */ +static GstCaps* +gst_alsa_caps (GstAlsa *this) +{ + gint law, endianness, width, depth; + gboolean sign; + GstProps *props; + + 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); + } 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); + } + + return gst_caps_new ("alsasrc", "audio/raw", props); +} + +/* + * Negotiates the caps, "borrowed" from gstosssink.c + */ +GstPadNegotiateReturn +gst_alsa_negotiate(GstPad *pad, GstCaps **caps, gpointer *user_data) +{ + GstAlsa *this; + gboolean need_mmap; + + g_return_val_if_fail (pad != NULL, GST_PAD_NEGOTIATE_FAIL); + g_return_val_if_fail (GST_IS_PAD (pad), GST_PAD_NEGOTIATE_FAIL); + + this = GST_ALSA(gst_pad_get_parent(pad)); + + // we decide + if (user_data == NULL) { + *caps = NULL; + return GST_PAD_NEGOTIATE_TRY; + } + // have we got caps? + else if (*caps) { + if (this->handle == NULL) + return GST_PAD_NEGOTIATE_FAIL; + + 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_NEGOTIATE_FAIL; + + if (!gst_alsa_start_audio(this)) + return GST_PAD_NEGOTIATE_FAIL; + + if (need_mmap && !gst_alsa_get_channel_addresses(this)) + return GST_PAD_NEGOTIATE_FAIL; + + return GST_PAD_NEGOTIATE_AGREE; + } + + return GST_PAD_NEGOTIATE_FAIL; + } + + return GST_PAD_NEGOTIATE_FAIL; +} + +/* shamelessly stolen from pbd's audioengine. thanks, paul! */ +static void +gst_alsa_loop (GstElement *element) +{ + struct pollfd pfd; + gboolean xrun_detected; + guint32 i; + GstAlsa *this = GST_ALSA(element); + + g_return_if_fail(this != NULL); + + snd_pcm_poll_descriptors (this->handle, &pfd, 1); + + if (this->stream == SND_PCM_STREAM_PLAYBACK) { + pfd.events = POLLOUT | POLLERR; + } else { + pfd.events = POLLIN | POLLERR; + } + + do { + if (poll (&pfd, 1, 1000) < 0) { + if (errno == EINTR) { + /* this happens mostly when run + * under gdb, or when exiting due to a signal */ + g_print ("EINTR\n"); + continue; + } + + g_warning("poll call failed (%s)", strerror(errno)); + return; + } + + if (pfd.revents & POLLERR) { + g_warning("alsa: poll reports error."); + return; + } + + if (pfd.revents == 0) { + g_print ("poll on alsa %s device \"%s\" timed out\n", + this->stream==SND_PCM_STREAM_CAPTURE ? "capture" : "playback", + this->device); + /* timed out, such as when the device is paused */ + continue; + } + + xrun_detected = FALSE; + + this->avail = snd_pcm_avail_update (this->handle); +// g_print ("snd_pcm_avail_update() = %d\n", this->avail); + + if (this->avail < 0) { + if (this->avail == -EPIPE) { + xrun_detected = TRUE; + } else { + g_warning("unknown ALSA avail_update return value (%d)", + (int)this->avail); + return; + } + } + + /* pretty sophisticated eh? */ + if (xrun_detected) + g_warning ("xrun detected"); + + /* 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); + } + gst_element_yield (element); + } 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; +// gint i=0; + + 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_set_caps (GST_ALSA_PAD(l)->pad, caps)) { + g_print ("DANGER WILL ROBINSON!\n"); + sleep(1); + return FALSE; + } + + l = l->next; + } + caps_set = TRUE; + } + + unit = this->sample_bytes * (this->data_interleaved ? this->channels : 1); + + while (frames) { +// g_print ("(%d) frames to process: %d\n", i++, 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) { + if (pad->offset > this->period_frames) + G_BREAKPOINT(); + + 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) + } + + return TRUE; +} + +static gboolean +gst_alsa_sink_process (GstAlsa *this, snd_pcm_uframes_t frames) +{ + guint8 *peeked; + guint32 len, avail; + GstEvent *event = NULL; + GList *l; + + /* 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 'frames' only. + * */ + + if (!this->sample_bytes) { + if (!GST_ALSA_PAD(this->pads)->bs) + GST_ALSA_PAD(this->pads)->bs = gst_bytestream_new(GST_ALSA_PAD(this->pads)->pad); + + if (!(peeked = gst_bytestream_peek_bytes(GST_ALSA_PAD(this->pads)->bs, frames))) { + g_warning("initial pull on pad %s returned NULL", GST_OBJECT_NAME(GST_ALSA_PAD(this->pads)->pad)); + gst_element_set_state(GST_ELEMENT(this), GST_STATE_PAUSED); + 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) { + if (!GST_ALSA_PAD(this->pads)->bs) + GST_ALSA_PAD(this->pads)->bs = gst_bytestream_new(GST_ALSA_PAD(this->pads)->pad); + + if (!(peeked = gst_bytestream_peek_bytes(GST_ALSA_PAD(this->pads)->bs, len))) { + gst_bytestream_get_status(GST_ALSA_PAD(this->pads)->bs, &avail, &event); + if (event) { + g_warning("got an event on alsasink"); + 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 ;) + * also, for sample accuracy etc, we should play avail + * bytes, but hey. */ + gst_element_set_state(GST_ELEMENT(this), GST_STATE_PAUSED); + gst_event_free(event); + return TRUE; + } + } else { + /* the element at the top of the chain did not emit an eos + * event. this is a Bug(tm) */ + g_assert_not_reached(); + } + } + + memcpy(GST_ALSA_PAD(this->pads)->access_addr, peeked, len); + gst_bytestream_flush(GST_ALSA_PAD(this->pads)->bs, len); + + l=l->next; + } + + return TRUE; +} + +/* 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; + + g_return_val_if_fail(this != NULL, FALSE); + g_return_val_if_fail(this->handle != NULL, FALSE); + + 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); + + 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("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 (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); + + 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 (ret < 0) { + g_warning("could not set hw params: %s", snd_strerror(ret)); + snd_pcm_hw_params_dump(hw_param, this->out); + 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 (ret < 0) { + g_warning("could not set start mode: %s", snd_strerror(ret)); + return FALSE; + } + + ret = snd_pcm_sw_params_set_stop_threshold (this->handle, sw_param, ~0U); + if (ret < 0) { + g_warning("could not set stop mode: %s", snd_strerror(ret)); + 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; + } + + 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; + } + + 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 (this->handle, sw_param); + if (ret < 0) { + g_warning("could not set sw_params: %s", snd_strerror(ret)); + return FALSE; + } + + 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) +{ + gint ret; + g_assert(this != NULL); + + if (this->handle) + gst_alsa_close_audio(this); + + g_print("Opening alsa device \"%s\" for %s...\n", this->device, + this->stream==SND_PCM_STREAM_PLAYBACK ? "playback" : "capture"); + + ret = snd_output_stdio_attach(&this->out, stdout, 0); + if (ret < 0) { + g_print("error opening log output: %s", snd_strerror(ret)); + return FALSE; + } + + /* blocking i/o */ + if ((ret = snd_pcm_open(&this->handle, this->device, this->stream, 0))) { + g_print("error opening pcm device: %s", snd_strerror(ret)); + return FALSE; + } + + 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) +{ + gint err; + guint32 chn; + + 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; + } + + 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 (!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 (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) +{ +// gint err; + g_return_if_fail(this != NULL); + g_return_if_fail(this->handle != NULL); + +/* if ((err = snd_pcm_drop (this->handle)) < 0) { + g_warning("channel flush for failed: %s", snd_strerror (err)); + return; + } */ + + snd_pcm_close(this->handle); + + this->handle = NULL; + + 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; + + 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; + } + + GST_DEBUG(0, "got %d mmap'd frames\n", (int)this->avail); + +// g_print ("snd_pcm_mmap_begin() sets avail = %d\n", 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; 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; + + return TRUE; +} + +static void +gst_alsa_release_channel_addresses (GstAlsa *this) +{ + guint32 err, i; + GList *l; + + g_return_if_fail (this->mmap_open == TRUE); + + GST_DEBUG(0, "releasing mmap'd data region: %d frames\n", (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; + } + + l = this->pads; + while (l) { + GST_ALSA_PAD(l)->access_addr = NULL; + l = l->next; + } + + for (i=0; ichannels; i++) { + this->access_addr[i] = NULL; + } + + 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); + } +// mark_channel_done (chn); +} + +/* 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; + } +} + +static gboolean +plugin_init (GModule *module, GstPlugin *plugin) +{ + GstElementFactory *factory; + + if (!gst_library_load ("gstbytestream")) { + gst_info("alsa: could not load support library: 'gstbytestream'\n"); + return FALSE; + } + + factory = gst_elementfactory_new ("alsasrc", GST_TYPE_ALSA_SRC, &gst_alsa_src_details); + g_return_val_if_fail (factory != NULL, FALSE); + gst_elementfactory_add_padtemplate (factory, gst_alsa_src_pad_factory()); + gst_elementfactory_add_padtemplate (factory, gst_alsa_src_request_pad_factory()); + gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); + + factory = gst_elementfactory_new ("alsasink", GST_TYPE_ALSA_SINK, &gst_alsa_sink_details); + g_return_val_if_fail (factory != NULL, FALSE); + gst_elementfactory_add_padtemplate (factory, gst_alsa_sink_pad_factory()); + gst_elementfactory_add_padtemplate (factory, gst_alsa_sink_request_pad_factory()); + gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); + + gst_plugin_set_longname(plugin, "ALSA plugin library"); + + return TRUE; +} + +GstPluginDesc plugin_desc = { + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "alsa", + plugin_init +}; + + diff --git a/ext/alsa/gstalsa.h b/ext/alsa/gstalsa.h new file mode 100644 index 0000000..4fdaaa6 --- /dev/null +++ b/ext/alsa/gstalsa.h @@ -0,0 +1,126 @@ +/* + Copyright (C) 2001 CodeFactory AB + Copyright (C) 2001 Thomas Nyberg + Copyright (C) 2001 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__ + +#include +#include +#include +#include + +#define GST_ALSA(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, GST_TYPE_ALSA, GstAlsa) +#define GST_ALSA_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, GST_TYPE_ALSA, GstAlsaClass) +#define GST_IS_ALSA(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, GST_TYPE_ALSA) +#define GST_IS_ALSA_CLASS(klass) G_TYPE_CHECK_CLASS_TYPE(klass, GST_TYPE_ALSA) +#define GST_TYPE_ALSA gst_alsa_get_type() + +#define GST_ALSA_SINK(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, GST_TYPE_ALSA_SINK, GstAlsa) +#define GST_ALSA_SINK_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, GST_TYPE_ALSA_SINK, GstAlsaClass) +#define GST_IS_ALSA_SINK(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, GST_TYPE_ALSA_SINK) +#define GST_IS_ALSA_SINK_CLASS(klass) G_TYPE_CHECK_CLASS_TYPE(klass, GST_TYPE_ALSA_SINK) +#define GST_TYPE_ALSA_SINK gst_alsa_sink_get_type() + +#define GST_ALSA_SRC(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, GST_TYPE_ALSA_SRC, GstAlsa) +#define GST_ALSA_SRC_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, GST_TYPE_ALSA_SRC, GstAlsaClass) +#define GST_IS_ALSA_SRC(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, GST_TYPE_ALSA_SRC) +#define GST_IS_ALSA_SRC_CLASS(klass) G_TYPE_CHECK_CLASS_TYPE(klass, GST_TYPE_ALSA_SRC) +#define GST_TYPE_ALSA_SRC gst_alsa_src_get_type() + +#define GST_ALSA_PAD(obj) ((GstAlsaPad*)obj->data) /* obj is a GList */ + +/* I would have preferred to avoid this variety of trickery, but without it i + * can't tell whether I'm a source or a sink upon creation. */ + +typedef struct _GstAlsa GstAlsa; +typedef struct _GstAlsaClass GstAlsaClass; +typedef GstAlsa GstAlsaSink; +typedef GstAlsaClass GstAlsaSinkClass; +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, +}; + +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; +} GstAlsaPad; + +struct _GstAlsa { + 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; +}; + +struct _GstAlsaClass { + GstElementClass parent_class; +}; + +GType gst_alsa_get_type (void); +GType gst_alsa_sink_get_type (void); +GType gst_alsa_src_get_type (void); + +#endif /* __ALSA_H__ */ -- 2.7.4