From: Benjamin Otte Date: Thu, 24 Apr 2003 00:03:06 +0000 (+0000) Subject: total code reorganization as a start to get alsasrc working - sink and src are now... X-Git-Tag: 1.19.3~511^2~15440 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=dcdb4244243450321ac6f39e3acf183592979ca2;p=platform%2Fupstream%2Fgstreamer.git total code reorganization as a start to get alsasrc working - sink and src are now really different classes, not just... Original commit message from CVS: total code reorganization as a start to get alsasrc working - sink and src are now really different classes, not just on paper - includes a fix that makes the testsuite work that might be an older bug --- diff --git a/ext/alsa/gstalsa.c b/ext/alsa/gstalsa.c index 2207bad773..21079f0acd 100644 --- a/ext/alsa/gstalsa.c +++ b/ext/alsa/gstalsa.c @@ -93,12 +93,31 @@ static void gst_alsa_get_property (GObject * object, GValue * value, GParamSpec * pspec); -/* GStreamer functions for pads and state changing */ -static GstPadTemplate * gst_alsa_src_pad_factory (void); -static GstPadTemplate * gst_alsa_src_request_pad_factory (void); +/* GstAlsaSink functions */ static GstPadTemplate * gst_alsa_sink_pad_factory (void); static GstPadTemplate * gst_alsa_sink_request_pad_factory (void); +static void gst_alsa_sink_class_init (GstAlsaSinkClass * klass); +static void gst_alsa_sink_init (GstAlsaSink * this); +static inline void gst_alsa_sink_flush_one_pad (GstAlsaSink * sink, + gint i); +static void gst_alsa_sink_flush_pads (GstAlsaSink * sink); +static int gst_alsa_sink_mmap (GstAlsa * this, + snd_pcm_sframes_t * avail); +static int gst_alsa_sink_write (GstAlsa * this, + snd_pcm_sframes_t * avail); +static void gst_alsa_sink_loop (GstElement * element); +static gboolean gst_alsa_sink_check_event (GstAlsaSink * sink, + gint pad_nr); +static GstElementStateReturn gst_alsa_sink_change_state (GstElement * element); + +/* GstAlsaSrc functions */ +static GstPadTemplate * gst_alsa_src_pad_factory (void); +static GstPadTemplate * gst_alsa_src_request_pad_factory (void); +static void gst_alsa_src_class_init (GstAlsaSrcClass * klass); +static void gst_alsa_src_init (GstAlsaSrc * this); +static void gst_alsa_src_loop (GstElement * element); +/* GStreamer functions for pads and state changing */ static GstPad * gst_alsa_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * name); @@ -109,22 +128,14 @@ static GstCaps * gst_alsa_get_caps (GstPad * pad, static GstCaps * gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels); - -static GstBufferPool * gst_alsa_src_get_buffer_pool (GstPad * pad); - static GstElementStateReturn gst_alsa_change_state (GstElement * element); /* audio processing functions */ -static int gst_alsa_do_mmap (GstAlsa * this, - snd_pcm_sframes_t * avail); - -static void gst_alsa_sink_loop (GstElement * element); static void gst_alsa_src_loop (GstElement * element); static void gst_alsa_xrun_recovery (GstAlsa * this); - -static gboolean gst_alsa_sink_check_event (GstAlsa * this, - gint pad_nr, - GstEvent * event); +inline static snd_pcm_sframes_t gst_alsa_update_avail (GstAlsa * this); +inline static gboolean gst_alsa_pcm_wait (GstAlsa * this); +inline static gboolean gst_alsa_start (GstAlsa * this); /* alsa setup / start / stop functions */ static void gst_alsa_set_eos (GstAlsa * this); @@ -186,52 +197,6 @@ 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), @@ -245,7 +210,7 @@ gst_alsa_src_get_type (void) (GInstanceInitFunc) gst_alsa_init, }; - alsa_type = g_type_register_static (GST_TYPE_ALSA, "GstAlsaSrc", &alsa_info, 0); + alsa_type = g_type_register_static (GST_TYPE_ELEMENT, "GstAlsa", &alsa_info, 0); } return alsa_type; } @@ -313,55 +278,15 @@ gst_alsa_class_init (GstAlsaClass *klass) static void gst_alsa_init (GstAlsa *this) { - gint i; - /* init values */ - this->handle = NULL; - this->transmit = NULL; - for (i = 0; i < GST_ALSA_MAX_CHANNELS; i++) { - this->pads[i].pad = NULL; - this->pads[i].data = NULL; - this->pads[i].size = 0; - this->pads[i].buf = NULL; - } - GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED); - - if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) { - this->stream = SND_PCM_STREAM_CAPTURE; - gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_src_loop); - this->pads[0].pad = gst_pad_new_from_template (gst_alsa_src_pad_factory (), "src"); - gst_pad_set_bufferpool_function(this->pads[0].pad, gst_alsa_src_get_buffer_pool); - this->clock = gst_alsa_clock_new ("alsasrcclock", gst_alsa_src_get_time, this); - } else if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK) { - this->stream = SND_PCM_STREAM_PLAYBACK; - gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_sink_loop); - this->pads[0].pad = gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "sink"); - this->clock = gst_alsa_clock_new ("alsasinkclock", gst_alsa_sink_get_time, this); - } else { - g_assert_not_reached (); - } - /* we hold a ref to our clock until we're disposed */ - gst_object_ref (GST_OBJECT (this->clock)); - gst_object_sink (GST_OBJECT (this->clock)); - gst_element_add_pad (GST_ELEMENT (this), this->pads[0].pad); - - gst_pad_set_link_function (this->pads[0].pad, gst_alsa_link); - gst_pad_set_getcaps_function (this->pads[0].pad, gst_alsa_get_caps); } - static void gst_alsa_dispose (GObject *object) { - gint i; GstAlsa *this = GST_ALSA (object); - for (i = 0; i < ((GstElement *) this)->numpads; i++) { - if (this->pads[i].buf) { - gst_data_unref (GST_DATA (this->pads[i].buf)); - } - } - gst_object_unref (GST_OBJECT (this->clock)); - + if (this->clock) + gst_object_unref (GST_OBJECT (this->clock)); G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -451,33 +376,10 @@ gst_alsa_get_property (GObject *object, guint prop_id, GValue *value, } } -/*** GSTREAMER PAD / STATE FUNCTIONS*******************************************/ - -static GstPadTemplate * -gst_alsa_src_pad_factory (void) -{ - static GstPadTemplate *template = NULL; - - if (!template) - template = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, - gst_alsa_caps (SND_PCM_FORMAT_UNKNOWN, -1, -1), - NULL); - - return template; -} - -static GstPadTemplate * -gst_alsa_src_request_pad_factory (void) -{ - static GstPadTemplate *template = NULL; - if (!template) - template = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, - gst_alsa_caps (SND_PCM_FORMAT_UNKNOWN, -1, 1), - NULL); +/*** GSTALSASINK FUNCTIONS ****************************************************/ - return template; -} +static GstAlsa *sink_parent_class = NULL; static GstPadTemplate * gst_alsa_sink_pad_factory (void) @@ -491,7 +393,6 @@ gst_alsa_sink_pad_factory (void) return template; } - static GstPadTemplate * gst_alsa_sink_request_pad_factory (void) { @@ -505,899 +406,1215 @@ gst_alsa_sink_request_pad_factory (void) return template; } - -static GstPad * -gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, - const gchar *name) +GType +gst_alsa_sink_get_type (void) { - GstAlsa *this; - gint channel = 0; - - g_return_val_if_fail ((this = GST_ALSA (element)), NULL); - g_return_val_if_fail (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING), NULL); + static GType alsa_sink_type = 0; - 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 = (gint) strtol (name + (strchr (templ->name_template, '%') - - templ->name_template), NULL, 0); - if (channel < 1 || channel >= GST_ALSA_MAX_CHANNELS) { - g_warning ("invalid channel requested. (%d)", channel); - return NULL; - } - } + if (!alsa_sink_type) { + static const GTypeInfo alsa_sink_info = { + sizeof (GstAlsaSinkClass), + NULL, + NULL, + (GClassInitFunc) gst_alsa_sink_class_init, + NULL, + NULL, + sizeof (GstAlsaSink), + 0, + (GInstanceInitFunc) gst_alsa_sink_init, + }; - /* make sure the requested channel is free. */ - if (channel > 0 || this->pads[channel].pad != NULL) { - g_warning ("requested channel %d already in use.", channel); - return NULL; + alsa_sink_type = g_type_register_static (GST_TYPE_ALSA, "GstAlsaSink", &alsa_sink_info, 0); } + return alsa_sink_type; +} +static void +gst_alsa_sink_class_init (GstAlsaSinkClass *klass) +{ + GObjectClass *object_class; + GstElementClass *element_class; + GstAlsaClass *alsa_class; - /* if the user doesn't care which channel, find the lowest channel number - that's free. */ - if (channel == 0) { - for (channel = 1; channel < GST_ALSA_MAX_CHANNELS; channel++) { - if (this->pads[channel].pad != NULL) - goto found_channel; - } - return NULL; - } + object_class = (GObjectClass *) klass; + element_class = (GstElementClass *) klass; + alsa_class = (GstAlsaClass *) klass; -found_channel: - this->pads[channel].pad = gst_pad_new_from_template (templ, name); - gst_pad_set_link_function (this->pads[channel].pad, gst_alsa_link); - gst_pad_set_getcaps_function (this->pads[channel].pad, gst_alsa_get_caps); - gst_element_add_pad (GST_ELEMENT (this), this->pads[channel].pad); - if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) - gst_pad_set_bufferpool_function(this->pads[channel].pad, gst_alsa_src_get_buffer_pool); + if (sink_parent_class == NULL) + sink_parent_class = g_type_class_ref (GST_TYPE_ALSA); + + alsa_class->stream = SND_PCM_STREAM_PLAYBACK; + alsa_class->transmit_mmap = gst_alsa_sink_mmap; + alsa_class->transmit_rw = gst_alsa_sink_write; - return this->pads[channel].pad; + element_class->change_state = gst_alsa_sink_change_state; } - -/* gets the matching alsa format or NULL if none matches */ -static GstAlsaFormat * -gst_alsa_get_format (GstCaps *caps) +static void +gst_alsa_sink_init (GstAlsaSink *sink) { - const gchar *format_name; - GstAlsaFormat *ret; + GstAlsa *this = GST_ALSA (sink); - if (!(ret = g_new (GstAlsaFormat, 1))) - return NULL; + this->pad[0] = gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "sink"); + gst_pad_set_link_function (this->pad[0], gst_alsa_link); + gst_pad_set_getcaps_function (this->pad[0], gst_alsa_get_caps); + gst_element_add_pad (GST_ELEMENT (this), this->pad[0]); + + this->clock = gst_alsa_clock_new ("alsasinkclock", gst_alsa_sink_get_time, this); + /* we hold a ref to our clock until we're disposed */ + gst_object_ref (GST_OBJECT (this->clock)); + gst_object_sink (GST_OBJECT (this->clock)); - /* we have to differentiate between int and float formats */ - if (!gst_caps_get_string (caps, "format", &format_name)) - goto error; + gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_sink_loop); +} - if (strncmp (format_name, "int", 3) == 0) { - gboolean sign; - gint width, depth, endianness, law; +static inline void +gst_alsa_sink_flush_one_pad (GstAlsaSink *sink, gint i) +{ + switch (sink->behaviour[i]) { + case 0: + if (sink->buf[i]) + gst_data_unref (GST_DATA (sink->buf[i])); + sink->buf[i] = NULL; + sink->data[i] = NULL; + sink->behaviour[i] = 0; + sink->size[i] = 0; + break; + case 1: + g_free (sink->data[i]); + sink->data[i] = NULL; + sink->behaviour[i] = 0; + sink->size[i] = 0; + break; + default: + g_assert_not_reached (); + } +} +static void +gst_alsa_sink_flush_pads (GstAlsaSink *sink) +{ + gint i; - /* extract the needed information from the caps */ - if (!gst_caps_get (caps, - "width", &width, - "depth", &depth, - "law", &law, - "signed", &sign, - NULL)) - goto error; - - /* extract endianness if needed */ - if (width > 8) { - if (!gst_caps_get (caps, - "endianness", &endianness, - NULL)) - goto error; - } else { - endianness = G_BYTE_ORDER; - } - - /* find corresponding alsa format */ - switch (law) { - case 0: - ret->format = snd_pcm_build_linear_format (depth, width, sign ? 0 : 1, endianness == G_LITTLE_ENDIAN ? 0 : 1); - break; - case 1: - if (width == 8 && depth == 8 && sign == FALSE) { - ret->format = SND_PCM_FORMAT_MU_LAW; - break; - } else { - goto error; - } - case 2: - if (width == 8 && depth == 8 && sign == FALSE) { - ret->format = SND_PCM_FORMAT_A_LAW; - break; - } else { - goto error; - } - default: - goto error; - } - } else if (strncmp (format_name, "float", 5) == 0) { - gchar *layout; - gfloat intercept, slope; + for (i = 0; i < GST_ELEMENT (sink)->numpads; i++) { + /* flush twice to unref buffer when behaviour == 1 */ + gst_alsa_sink_flush_one_pad (sink, i); + gst_alsa_sink_flush_one_pad (sink, i); + } +} +/* TRUE, if everything should continue */ +static gboolean +gst_alsa_sink_check_event (GstAlsaSink *sink, gint pad_nr) +{ + gboolean cont = TRUE; + GstEvent *event = GST_EVENT (sink->buf[pad_nr]); + GstAlsa *this = GST_ALSA (sink); - /* get layout */ - if (!gst_caps_get (caps, "layout", &layout, - "intercept", &intercept, - "slope", &slope, - NULL)) - goto error; - if (intercept != 0.0f || slope != 1.0f) { - goto error; - } - /* match layout to format wrt to endianness */ - if (strncmp (layout, "gfloat", 6) == 0) { - if (G_BYTE_ORDER == G_LITTLE_ENDIAN) { - ret->format = SND_PCM_FORMAT_FLOAT_LE; - } else if (G_BYTE_ORDER == G_BIG_ENDIAN) { - ret->format = SND_PCM_FORMAT_FLOAT_BE; - } else { - ret->format = SND_PCM_FORMAT_FLOAT; - } - } else if (strncmp (layout, "gdouble", 7) == 0) { - if (G_BYTE_ORDER == G_LITTLE_ENDIAN) { - ret->format = SND_PCM_FORMAT_FLOAT64_LE; - } else if (G_BYTE_ORDER == G_BIG_ENDIAN) { - ret->format = SND_PCM_FORMAT_FLOAT64_BE; - } else { - ret->format = SND_PCM_FORMAT_FLOAT64; - } - } else { - goto error; + if (event) { + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + gst_alsa_set_eos (this); + cont = FALSE; + break; + case GST_EVENT_INTERRUPT: + cont = FALSE; + break; + case GST_EVENT_NEW_MEDIA: + /* only the first pad my seek */ + if (pad_nr != 0) + break; + + if (GST_CLOCK_TIME_IS_VALID (this->clock->start_time)) { /* if the clock is running */ + g_assert (this->format); + /* adjust the start time */ + this->clock->start_time += gst_alsa_samples_to_timestamp (this, this->transmitted); + } + this->transmitted = 0; + /* FIXME: Notify the clock that we're at offset 0 again */ + break; + case GST_EVENT_DISCONTINUOUS: + { + gint64 value; + + /* only the first pad my seek */ + if (pad_nr != 0) { + break; + } + if (gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) { + if (!gst_clock_handle_discont (GST_ELEMENT (this)->clock, value)) + g_printerr ("GstAlsa: clock couldn't handle discontinuity\n"); + } + if (!gst_event_discont_get_value (event, GST_FORMAT_UNITS, &value)) { + if (!gst_event_discont_get_value (event, GST_FORMAT_BYTES, &value)) { + if (!gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) { + g_warning ("GstAlsa: Could not acquire samplecount after seek, the clock might screw your pipeline now"); + break; + } else { + if (this->format) /* discont event before any data (and before any caps...) */ + value = gst_alsa_timestamp_to_samples (this, value); + } + } else { + if (this->format) /* discont event before any data (and before any caps...) */ + value = gst_alsa_bytes_to_samples (this, value); + } + } + if (GST_CLOCK_TIME_IS_VALID (this->clock->start_time)) { /* if the clock is running */ + g_assert (this->format); + /* adjust the start time */ + this->clock->start_time += gst_alsa_samples_to_timestamp (this, this->transmitted) - + gst_alsa_samples_to_timestamp (this, value); + } + this->transmitted = value; + break; + } + default: + g_warning ("GstAlsa: got an unknown event (Type: %d)", GST_EVENT_TYPE (event)); + break; + } + gst_event_unref (event); + sink->buf[pad_nr] = NULL; + } else { + /* the element at the top of the chain did not emit an event. */ + g_assert_not_reached (); + } + return cont; +} +static int +gst_alsa_sink_mmap (GstAlsa *this, snd_pcm_sframes_t *avail) +{ + snd_pcm_uframes_t offset; + const snd_pcm_channel_area_t *dst; + snd_pcm_channel_area_t *src; + GstAlsaSink *sink = GST_ALSA_SINK (this); + int i, err, width = snd_pcm_format_physical_width (this->format->format); + + /* areas points to the memory areas that belong to gstreamer. */ + src = calloc(this->format->channels, sizeof(snd_pcm_channel_area_t)); + + if (((GstElement *) this)->numpads == 1) { + /* interleaved */ + for (i = 0; i < this->format->channels; i++) { + src[i].addr = sink->data[0]; + src[i].first = i * width; + src[i].step = this->format->channels * width; + } + } else { + /* noninterleaved */ + for (i = 0; i < this->format->channels; i++) { + src[i].addr = sink->data[i]; + src[i].first = 0; + src[i].step = width; } } - /* get rate and channels */ - if (!gst_caps_get (caps, "rate", &ret->rate, - "channels", &ret->channels, - NULL)) - goto error; - - return ret; - -error: - g_free (ret); - return NULL; -} + if ((err = snd_pcm_mmap_begin (this->handle, &dst, &offset, avail)) < 0) { + g_warning ("gstalsa: mmap failed: %s", snd_strerror (err)); + return -1; + } -static inline gboolean -gst_alsa_formats_match (GstAlsaFormat *one, GstAlsaFormat *two) + if ((err = snd_pcm_areas_copy (dst, offset, src, 0, this->format->channels, *avail, this->format->format)) < 0) { + snd_pcm_mmap_commit (this->handle, offset, 0); + g_warning ("gstalsa: data copy failed: %s", snd_strerror (err)); + return -1; + } + if ((err = snd_pcm_mmap_commit (this->handle, offset, *avail)) < 0) { + g_warning ("gstalsa: mmap commit failed: %s", snd_strerror (err)); + return -1; + } + + return err; +} +static int +gst_alsa_sink_write (GstAlsa *this, snd_pcm_sframes_t *avail) { - if (one == two) return TRUE; - if (one == NULL || two == NULL) return FALSE; - return (one->format == two->format) && - (one->rate == two->rate) && - (one->channels == two->channels); + GstAlsaSink *sink = GST_ALSA_SINK (this); + void *channels[this->format->channels]; + int err, i; + + if (((GstElement *) this)->numpads == 1) { + /* interleaved */ + err = snd_pcm_writei (this->handle, sink->data[0], *avail); + } else { + /* noninterleaved */ + for (i = 0; i < this->format->channels; i++) { + channels[i] = sink->data[i]; + } + err = snd_pcm_writen (this->handle, channels, *avail); + } + /* error handling */ + if (err < 0) { + if (err == -EPIPE) { + gst_alsa_xrun_recovery (this); + return 0; + } + g_warning ("error on data access: %s", snd_strerror (err)); + } + return err; } -/* get props for a spec */ -static GstProps * -gst_alsa_get_props (snd_pcm_format_t format) +static void +gst_alsa_sink_loop (GstElement *element) { - if (format == SND_PCM_FORMAT_A_LAW) { - return gst_props_new ("format", GST_PROPS_STRING ("int"), - "law", GST_PROPS_INT(2), - "width", GST_PROPS_INT(8), - "depth", GST_PROPS_INT(8), - "signed", GST_PROPS_BOOLEAN (FALSE), - NULL); - } else if (format == SND_PCM_FORMAT_MU_LAW) { - return gst_props_new ("format", GST_PROPS_STRING ("int"), - "law", GST_PROPS_INT(1), - "width", GST_PROPS_INT(8), - "depth", GST_PROPS_INT(8), - "signed", GST_PROPS_BOOLEAN (FALSE), - NULL); - } else if (snd_pcm_format_linear (format)) { - /* int */ - GstProps *props = gst_props_new ("format", GST_PROPS_STRING ("int"), - "width", GST_PROPS_INT(snd_pcm_format_physical_width (format)), - "depth", GST_PROPS_INT(snd_pcm_format_width (format)), - "law", GST_PROPS_INT(0), - "signed", GST_PROPS_BOOLEAN (snd_pcm_format_signed (format) == 1 ? TRUE : FALSE), - NULL); - /* endianness */ - if (snd_pcm_format_physical_width (format) > 8) { - switch (snd_pcm_format_little_endian (format)) { - case 0: - gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_BIG_ENDIAN))); - break; - case 1: - gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_LITTLE_ENDIAN))); - break; - default: - g_warning("ALSA: Unknown byte order in sound driver. Continuing by assuming system byte order."); - gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_BYTE_ORDER))); - break; + snd_pcm_sframes_t avail, avail2, copied; + snd_pcm_uframes_t samplestamp; + gint i; + guint bytes; /* per channel */ + GstAlsa *this = GST_ALSA (element); + GstAlsaSink *sink = GST_ALSA_SINK (element); + + g_return_if_fail (sink != NULL); + +sink_restart: + + avail = gst_alsa_update_avail (this); + if (avail == -EPIPE) goto sink_restart; + if (avail < 0) return; + if (avail > 0) { + + /* Not enough space. We grab data nonetheless and sleep afterwards */ + if (avail < this->period_size) { + avail = this->period_size; + } + + /* check how many bytes we still have in all our bytestreams */ + /* initialize this value to a somewhat sane state, we might alloc this much data below (which would be a bug, but who knows)... */ + bytes = this->period_size * this->period_count * element->numpads * 8; /* must be > max sample size in bytes */ + for (i = 0; i < element->numpads; i++) { + g_assert (this->pad[i] != NULL); + while (sink->size[i] == 0) { + if (!sink->buf[i]) + sink->buf[i] = gst_pad_pull (this->pad[i]); + if (GST_IS_EVENT (sink->buf[i])) { + if (gst_alsa_sink_check_event (sink, i)) + continue; + return; + } + /* caps nego failed somewhere */ + if (this->format == NULL) { + gst_element_error (GST_ELEMENT (this), "alsasink: No caps available"); + return; + } + samplestamp = gst_alsa_timestamp_to_samples (this, GST_BUFFER_TIMESTAMP (sink->buf[i])); + if (!GST_BUFFER_TIMESTAMP_IS_VALID (sink->buf[i]) || + /* difference between them is < GST_ALSA_DEVIATION */ + ((this->transmitted + gst_alsa_timestamp_to_samples (this, this->max_discont) >= samplestamp) && + (this->transmitted <= gst_alsa_timestamp_to_samples (this, this->max_discont) + samplestamp))) { +no_difference: + sink->size[i] = sink->buf[i]->size; + sink->data[i] = sink->buf[i]->data; + sink->behaviour[i] = 0; + } else if (samplestamp > this->transmitted) { + /* there are empty samples in front of us, fill them with silence */ + int samples = MIN (bytes, samplestamp - this->transmitted) * + (element->numpads == 1 ? this->format->channels : 1); + int size = samples * snd_pcm_format_physical_width (this->format->format) / 8; + g_printerr ("Allocating %d bytes (%ld samples) now to resync: sample %ld expected, but got %ld\n", + size, MIN (bytes, samplestamp - this->transmitted), this->transmitted, samplestamp); + sink->data[i] = g_malloc (size); + if (!sink->data[i]) { + g_warning ("GstAlsa: error allocating %d bytes, buffers unsynced now.", size); + goto no_difference; + } + sink->size[i] = size; + if (0 != snd_pcm_format_set_silence (this->format->format, sink->data[i], samples)) { + g_warning ("GstAlsa: error silencing buffer, enjoy the noise."); + } + sink->behaviour[i] = 1; + } else if (gst_alsa_samples_to_bytes (this, this->transmitted - samplestamp) >= sink->buf[i]->size) { + g_printerr ("Skipping %lu samples to resync (complete buffer)\n", gst_alsa_bytes_to_samples (this, sink->buf[i]->size)); + /* this buffer is way behind */ + gst_buffer_unref (sink->buf[i]); + sink->buf[i] = NULL; + continue; + } else if (this->transmitted > samplestamp) { + gint difference = gst_alsa_samples_to_bytes (this, this->transmitted - samplestamp); + g_printerr ("Skipping %lu samples to resync\n", (gulong) this->transmitted - samplestamp); + /* this buffer is only a bit behind */ + sink->size[i] = sink->buf[i]->size - difference; + sink->data[i] = sink->buf[i]->data + difference; + sink->behaviour[i] = 0; + } else { + g_assert_not_reached (); + } } + bytes = MIN (bytes, sink->size[i]); } - return props; - } else if (snd_pcm_format_float (format)) { - /* no float with non-platform endianness */ - if (!snd_pcm_format_cpu_endian (format)) - return NULL; - return gst_props_new ("format", GST_PROPS_STRING ("float"), - "layout", GST_PROPS_STRING (snd_pcm_format_width (format) == 64 ? "gdouble" : "gfloat"), - "intercept", GST_PROPS_FLOAT (0), - "slope", GST_PROPS_FLOAT (1), - NULL); + avail = MIN (avail, gst_alsa_bytes_to_samples (this, bytes)); + + /* wait until the hw buffer has enough space */ + while (gst_element_get_state (element) == GST_STATE_PLAYING && (avail2 = gst_alsa_update_avail (this)) < avail) { + if (avail2 <= -EPIPE) goto sink_restart; + if (avail2 < 0) return; + if (avail2 < avail && snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING) + if (!gst_alsa_start (this)) return; + if (gst_alsa_pcm_wait (this) == FALSE) + return; + } + + /* FIXME: lotsa stuff can have happened while fetching data. Do we need to check something? */ + + /* put this data into alsa */ + if ((copied = this->transmit (this, &avail)) < 0) + return; + /* update our clock */ + this->transmitted += copied; + /* flush the data */ + bytes = gst_alsa_samples_to_bytes (this, copied); + for (i = 0; i < element->numpads; i++) { + if ((sink->size[i] -= bytes) == 0) { + gst_alsa_sink_flush_one_pad (sink, i); + continue; + } + g_assert (sink->size[i] > 0); + if (sink->behaviour[i] != 1) + sink->data[i] += bytes; + } } - return NULL; -} -static inline void -add_channels (GstProps *props, gint min_rate, gint max_rate, gint min_channels, gint max_channels) { - if (min_rate < 0) { - gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT_RANGE (GST_ALSA_MIN_RATE, GST_ALSA_MAX_RATE))); - } else if (max_rate < 0) { - gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT (min_rate))); - } else { - gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT_RANGE (min_rate, max_rate))); - } - if (min_channels < 0) { - gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (1, GST_ALSA_MAX_CHANNELS))); - } else if (max_channels < 0) { - gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT (min_channels))); - } else { - gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (min_channels, max_channels))); + if (snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING && snd_pcm_avail_update (this->handle) == 0) { + gst_alsa_start (this); } + } -/** - * Get all available caps. - * @format: SND_PCM_FORMAT_UNKNOWN for all formats, desired format else - * @rate: allowed rates if < 0, else desired rate - * @channels: all allowed values for channels if < 0, else desired channels - */ -static GstCaps * -gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels) +static GstElementStateReturn +gst_alsa_sink_change_state (GstElement *element) { - GstCaps *ret_caps = NULL; - - if (format != SND_PCM_FORMAT_UNKNOWN) { - /* there are some caps set already */ - GstProps *props = gst_alsa_get_props (format); - /* we can never use a format we can't set caps for */ - g_assert (props != NULL); + GstAlsaSink *sink; - add_channels (props, rate, -1, channels, -1); - ret_caps = gst_caps_new (g_strdup (snd_pcm_format_name (format)), "audio/raw", props); - } else { - int i; - GstProps *props; + g_return_val_if_fail (element != NULL, FALSE); + sink = GST_ALSA_SINK (element); - for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) { - props = gst_alsa_get_props (i); - /* can be NULL, because not all alsa formats can be specified as caps */ - if (props != NULL) { - add_channels (props, rate, -1, channels, -1); - ret_caps = gst_caps_append (ret_caps, gst_caps_new (g_strdup (snd_pcm_format_name (i)), - "audio/raw", props)); - } - } + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_NULL_TO_READY: + case GST_STATE_READY_TO_PAUSED: + case GST_STATE_PAUSED_TO_PLAYING: + case GST_STATE_PLAYING_TO_PAUSED: + break; + case GST_STATE_PAUSED_TO_READY: + gst_alsa_sink_flush_pads (sink); + break; + case GST_STATE_READY_TO_NULL: + break; + default: + g_assert_not_reached(); } - return ret_caps; + if (GST_ELEMENT_CLASS (sink_parent_class)->change_state) + return GST_ELEMENT_CLASS (sink_parent_class)->change_state (element); + + return GST_STATE_SUCCESS; } -/* Return better caps when device is open */ -static GstCaps * -gst_alsa_get_caps (GstPad *pad, GstCaps *caps) +/*** GSTALSASRC FUNCTIONS *****************************************************/ + +static GstAlsa *src_parent_class = NULL; + +static GstPadTemplate * +gst_alsa_src_pad_factory (void) { - GstAlsa *this; - snd_pcm_hw_params_t *hw_params; - snd_pcm_format_mask_t *mask; - int i; - unsigned int min_rate, max_rate; - gint min_channels, max_channels; - GstCaps *ret = NULL; + static GstPadTemplate *template = NULL; - g_return_val_if_fail (pad != NULL, NULL); + if (!template) + template = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + gst_alsa_caps (SND_PCM_FORMAT_UNKNOWN, -1, -1), + NULL); - this = GST_ALSA (gst_pad_get_parent (pad)); + return template; +} +static GstPadTemplate * +gst_alsa_src_request_pad_factory (void) +{ + static GstPadTemplate *template = NULL; - if (!GST_FLAG_IS_SET (this, GST_ALSA_OPEN)) - return gst_pad_get_pad_template_caps (pad); - - snd_pcm_hw_params_alloca (&hw_params); - ERROR_CHECK (snd_pcm_hw_params_any (this->handle, hw_params), - "Broken configuration for this PCM: %s"); + if (!template) + template = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + gst_alsa_caps (SND_PCM_FORMAT_UNKNOWN, -1, 1), + NULL); - if (((GstElement *) this)->numpads > 1) { - min_channels = 1; - max_channels = -1; - } else { - ERROR_CHECK (snd_pcm_hw_params_get_channels_min (hw_params, &min_rate), - "Coulödn't get minimum channel count for device %s: %s", this->device); - ERROR_CHECK (snd_pcm_hw_params_get_channels_max (hw_params, &max_rate), - "Coulödn't get maximum channel count for device %s: %s", this->device); - min_channels = min_rate; - max_channels = max_rate > GST_ALSA_MAX_CHANNELS ? GST_ALSA_MAX_CHANNELS : max_rate; - } + return template; +} +GType +gst_alsa_src_get_type (void) +{ + static GType alsa_src_type = 0; - ERROR_CHECK (snd_pcm_hw_params_get_rate_min (hw_params, &min_rate, &i), - "Coulödn't get minimum rate for device %s: %s", this->device); - min_rate = min_rate < GST_ALSA_MIN_RATE ? GST_ALSA_MIN_RATE : min_rate + i; - ERROR_CHECK (snd_pcm_hw_params_get_rate_max (hw_params, &max_rate, &i), - "Coulödn't get maximum rate for device %s: %s", this->device); - max_rate = max_rate > GST_ALSA_MAX_RATE ? GST_ALSA_MAX_RATE : max_rate + i; - - snd_pcm_format_mask_alloca (&mask); - snd_pcm_hw_params_get_format_mask (hw_params, mask); - for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) { - if (snd_pcm_format_mask_test (mask, i)) { - GstProps *props = gst_alsa_get_props (i); - /* we can never use a format we can't set caps for */ - if (props != NULL) { - add_channels (props, min_rate, max_rate, min_channels, max_channels); - ret = gst_caps_append (ret, gst_caps_new (g_strdup (snd_pcm_format_name (i)), - "audio/raw", props)); - } - } - } + if (!alsa_src_type) { + static const GTypeInfo alsa_src_info = { + sizeof (GstAlsaSrcClass), + NULL, + NULL, + (GClassInitFunc) gst_alsa_src_class_init, + NULL, + NULL, + sizeof (GstAlsaSrc), + 0, + (GInstanceInitFunc) gst_alsa_src_init, + }; - return ret; + alsa_src_type = g_type_register_static (GST_TYPE_ALSA, "GstAlsaSrc", &alsa_src_info, 0); + } + return alsa_src_type; } -/* Negotiates the caps */ -GstPadLinkReturn -gst_alsa_link (GstPad *pad, GstCaps *caps) +static void +gst_alsa_src_class_init (GstAlsaSrcClass *klass) { - GstAlsa *this; - GstAlsaFormat *format; - GstPadLinkReturn ret; + GObjectClass *object_class; + GstElementClass *element_class; + GstAlsaClass *alsa_class; - g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED); - g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED); + object_class = (GObjectClass *) klass; + element_class = (GstElementClass *) klass; + alsa_class = (GstAlsaClass *) klass; - this = GST_ALSA (gst_pad_get_parent (pad)); + if (src_parent_class == NULL) + src_parent_class = g_type_class_ref (GST_TYPE_ALSA); - if (GST_CAPS_IS_FIXED (caps)) { - if (this->handle == NULL) - if (!gst_alsa_open_audio (this)) - return GST_PAD_LINK_REFUSED; + alsa_class->stream = SND_PCM_STREAM_CAPTURE; - format = gst_alsa_get_format (caps); - if (format == NULL) - return GST_PAD_LINK_DELAYED; - - GST_DEBUG (GST_CAT_CAPS, "found format %s\n", snd_pcm_format_name (format->format)); - - if (!GST_FLAG_IS_SET (this, GST_ALSA_CAPS_NEGO)) { - gint i; +} +static void +gst_alsa_src_init (GstAlsaSrc *src) +{ + GstAlsa *this = GST_ALSA (src); - GST_FLAG_SET (this, GST_ALSA_CAPS_NEGO); + this->pad[0] = gst_pad_new_from_template (gst_alsa_src_pad_factory (), "src"); + gst_pad_set_link_function (this->pad[0], gst_alsa_link); + gst_pad_set_getcaps_function (this->pad[0], gst_alsa_get_caps); + gst_element_add_pad (GST_ELEMENT (this), this->pad[0]); + + this->clock = gst_alsa_clock_new ("alsasrcclock", gst_alsa_src_get_time, this); + /* we hold a ref to our clock until we're disposed */ + gst_object_ref (GST_OBJECT (this->clock)); + gst_object_sink (GST_OBJECT (this->clock)); - if (gst_alsa_formats_match (this->format, format)) { - ret = GST_PAD_LINK_OK; - goto out; - } + gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_src_loop); +} +static void +gst_alsa_src_loop (GstElement *element) +{ +#if 0 + snd_pcm_sframes_t avail, copied; + GstBufferPool *pool = NULL; + GstBuffer *buf; + GstCaps *caps; + gint i; + GstAlsaPad *pad; + GstAlsa *this = GST_ALSA (element); - if (!gst_alsa_probe_hw_params (this, format)) { - ret = GST_PAD_LINK_REFUSED; - goto out; - } + g_return_if_fail (this != NULL); - for (i = 0; i < ((GstElement *) this)->numpads; i++) { - g_assert (this->pads[i].pad != NULL); - if (this->pads[i].pad == pad) - continue; - if (gst_pad_try_set_caps (this->pads[i].pad, gst_caps_ref (caps)) == GST_PAD_LINK_REFUSED) { - if (this->format) { - GstCaps *old = gst_alsa_caps (this->format->format, this->format->rate, this->format->channels); - for (--i; i >= 0; i--) { - if (gst_pad_try_set_caps (this->pads[i].pad, gst_caps_ref (old)) == GST_PAD_LINK_REFUSED) { - gst_element_error (GST_ELEMENT (this), "error resetting caps to sane value"); - gst_caps_unref (old); - break; - } - } - gst_caps_unref (old); - } else { - /* FIXME: unset caps on pads somehow */ - } - ret = GST_PAD_LINK_REFUSED; - goto out; - } + /* set the caps on all pads */ + if (!this->format) { + if (!(this->format = g_new (GstAlsaFormat, 1))) { + gst_element_error (element, "No more memory"); + } + /* FIXME: make this settable */ + this->format->format = SND_PCM_FORMAT_S16; + this->format->rate = 44100; + this->format->channels = (element->numpads == 1) ? 2 : element->numpads; + GST_DEBUG (GST_CAT_NEGOTIATION, "starting caps negotiationgst_alsa_pcm_wait"); + caps = gst_alsa_caps (this->format->format, this->format->rate, this->format->channels); + for (i = 0; i < element->numpads; i++) { + if (gst_pad_try_set_caps (this->pads[i].pad, caps) <= 0) { + GST_DEBUG (GST_CAT_NEGOTIATION, "setting caps (%p) in alsasrc (%p) on pad %d failed", caps, this, i); + return; } - - GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO); + } + } + +src_restart: + + while ((avail = gst_alsa_update_avail (this)) < this->period_size) { + if (avail == -EPIPE) goto src_restart; + if (avail < 0) return; + if (snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING) break; + /* wait */ + if (gst_alsa_pcm_wait (this) == FALSE) + return; + } + if (avail > 0) { + int width = snd_pcm_format_physical_width (this->format->format); + int bytes_per_frame = ( width / 8 ) * (element->numpads == 1 ? this->format->channels : 1); + if ((copied = this->transmit (this, &avail)) < 0) + return; + + /* we get the buffer pool once per go round */ + if (! pool) pool = gst_alsa_src_get_buffer_pool (this->pads[0].pad); - /* sync the params */ - if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this); - g_free (this->format); - this->format = format; - if (!gst_alsa_start_audio (this)) { - gst_element_error (GST_ELEMENT (this), "Probed format doesn't work"); - return GST_PAD_LINK_REFUSED; + /* push the data to gstreamer if it's big enough to fill up a buffer. */ + for (i = 0; i < element->numpads; i++) { + pad = &this->pads[i]; + pad->size += MIN (copied, this->period_size - pad->size); + + if (pad->size >= this->period_size) { + g_assert (pad->size <= this->period_size); + + buf = gst_buffer_new_from_pool (pool, 0, 0); + + GST_BUFFER_DATA (buf) = pad->data; + GST_BUFFER_SIZE (buf) = this->period_size * bytes_per_frame; + GST_BUFFER_MAXSIZE (buf) = this->period_size * bytes_per_frame; + + gst_pad_push (pad->pad, buf); + + pad->data = NULL; + pad->size = 0; } } - return GST_PAD_LINK_OK; + pool = NULL; } - return GST_PAD_LINK_DELAYED; + /* BUG: we start the stream explicitly, autostart doesn't work correctly (alsa 0.9.0rc7) */ + if (snd_pcm_state(this->handle) == SND_PCM_STATE_PREPARED && snd_pcm_avail_update (this->handle) == 0) { + GST_DEBUG (GST_CAT_PLUGIN_INFO, "Explicitly starting capture"); + snd_pcm_start(this->handle); + } -out: - g_free (format); - GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO); - return ret; +#endif } -static GstBufferPool * -gst_alsa_src_get_buffer_pool (GstPad *pad) +/*** GSTREAMER PAD / STATE FUNCTIONS*******************************************/ + +static GstPad * +gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, + const gchar *name) { - int width, bytes_per_frame; + GstAlsa *this; + gint channel = 0; + + g_return_val_if_fail ((this = GST_ALSA (element)), NULL); + g_return_val_if_fail (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING), NULL); + + if (name) { + /* 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 = (gint) strtol (name + (strchr (templ->name_template, '%') - + templ->name_template), NULL, 0); + if (channel < 1 || channel >= GST_ALSA_MAX_CHANNELS) { + g_warning ("invalid channel requested. (%d)", channel); + return NULL; + } + } + + /* make sure the requested channel is free. */ + if (channel > 0 || this->pad[channel] != NULL) { + g_warning ("requested channel %d already in use.", channel); + return NULL; + } - GstAlsa *this = GST_ALSA (gst_pad_get_parent (pad)); + /* if the user doesn't care which channel, find the lowest channel number + that's free. */ + if (channel == 0) { + for (channel = 1; channel < GST_ALSA_MAX_CHANNELS; channel++) { + if (this->pad[channel] != NULL) + goto found_channel; + } + return NULL; + } - width = snd_pcm_format_physical_width (this->format->format); - bytes_per_frame = ( width / 8 ) * (GST_ELEMENT (this)->numpads == 1 ? this->format->channels : 1); +found_channel: + this->pad[channel] = gst_pad_new_from_template (templ, name); + gst_pad_set_link_function (this->pad[channel], gst_alsa_link); + gst_pad_set_getcaps_function (this->pad[channel], gst_alsa_get_caps); + gst_element_add_pad (GST_ELEMENT (this), this->pad[channel]); - /* FIXME : is this right ? constant size buffers are probably a good thing, - but what if the size changes (e.g. during xrun autorecovery) ? */ - return gst_buffer_pool_get_default (this->period_size * bytes_per_frame, - this->period_count); + return this->pad[channel]; } -static GstElementStateReturn -gst_alsa_change_state (GstElement *element) +/* gets the matching alsa format or NULL if none matches */ +static GstAlsaFormat * +gst_alsa_get_format (GstCaps *caps) { - GstAlsa *this; - gint i; + const gchar *format_name; + GstAlsaFormat *ret; - g_return_val_if_fail (element != NULL, FALSE); - this = GST_ALSA (element); + if (!(ret = g_new (GstAlsaFormat, 1))) + return NULL; - switch (GST_STATE_TRANSITION (element)) { - case GST_STATE_NULL_TO_READY: - if (!GST_FLAG_IS_SET (element, GST_ALSA_OPEN)) - if (!gst_alsa_open_audio (this)) - return GST_STATE_FAILURE; - break; - case GST_STATE_READY_TO_PAUSED: - if (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) - if (!gst_alsa_start_audio (this)) - return GST_STATE_FAILURE; - this->transmitted = 0; - break; - case GST_STATE_PAUSED_TO_PLAYING: - if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) { - int err = snd_pcm_pause (this->handle, 0); - if (err < 0) { - g_warning ("Error unpausing sound: %s", snd_strerror (err)); - return GST_STATE_FAILURE; - } - gst_alsa_clock_start (this->clock); + /* we have to differentiate between int and float formats */ + if (!gst_caps_get_string (caps, "format", &format_name)) + goto error; + + if (strncmp (format_name, "int", 3) == 0) { + gboolean sign; + gint width, depth, endianness, law; + + /* extract the needed information from the caps */ + if (!gst_caps_get (caps, + "width", &width, + "depth", &depth, + "law", &law, + "signed", &sign, + NULL)) + goto error; + + /* extract endianness if needed */ + if (width > 8) { + if (!gst_caps_get (caps, + "endianness", &endianness, + NULL)) + goto error; + } else { + endianness = G_BYTE_ORDER; } - break; - case GST_STATE_PLAYING_TO_PAUSED: - if (GST_ALSA_CAPS_IS_SET(this, GST_ALSA_CAPS_PAUSE)) { - if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) { - int err = snd_pcm_pause (this->handle, 1); - if (err < 0) { - g_warning ("Error pausing sound: %s", snd_strerror (err)); - return GST_STATE_FAILURE; + + /* find corresponding alsa format */ + switch (law) { + case 0: + ret->format = snd_pcm_build_linear_format (depth, width, sign ? 0 : 1, endianness == G_LITTLE_ENDIAN ? 0 : 1); + break; + case 1: + if (width == 8 && depth == 8 && sign == FALSE) { + ret->format = SND_PCM_FORMAT_MU_LAW; + break; + } else { + goto error; } - gst_alsa_clock_stop (this->clock); + case 2: + if (width == 8 && depth == 8 && sign == FALSE) { + ret->format = SND_PCM_FORMAT_A_LAW; + break; + } else { + goto error; + } + default: + goto error; + } + } else if (strncmp (format_name, "float", 5) == 0) { + gchar *layout; + gfloat intercept, slope; + + /* get layout */ + if (!gst_caps_get (caps, "layout", &layout, + "intercept", &intercept, + "slope", &slope, + NULL)) + goto error; + if (intercept != 0.0f || slope != 1.0f) { + goto error; + } + /* match layout to format wrt to endianness */ + if (strncmp (layout, "gfloat", 6) == 0) { + if (G_BYTE_ORDER == G_LITTLE_ENDIAN) { + ret->format = SND_PCM_FORMAT_FLOAT_LE; + } else if (G_BYTE_ORDER == G_BIG_ENDIAN) { + ret->format = SND_PCM_FORMAT_FLOAT_BE; + } else { + ret->format = SND_PCM_FORMAT_FLOAT; } - break; + } else if (strncmp (layout, "gdouble", 7) == 0) { + if (G_BYTE_ORDER == G_LITTLE_ENDIAN) { + ret->format = SND_PCM_FORMAT_FLOAT64_LE; + } else if (G_BYTE_ORDER == G_BIG_ENDIAN) { + ret->format = SND_PCM_FORMAT_FLOAT64_BE; + } else { + ret->format = SND_PCM_FORMAT_FLOAT64; + } + } else { + goto error; } - /* if device doesn't know how to pause, we just stop */ - case GST_STATE_PAUSED_TO_READY: - if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) - gst_alsa_close_audio (this); - /* clear format and pads */ - g_free (this->format); - this->format = NULL; - for (i = 0; i < element->numpads; i++) { - if (this->pads[i].buf) { - gst_data_unref (GST_DATA (this->pads[i].buf)); - this->pads[i].buf = NULL; - this->pads[i].data = NULL; - this->pads[i].size = 0; + } + + /* get rate and channels */ + if (!gst_caps_get (caps, "rate", &ret->rate, + "channels", &ret->channels, + NULL)) + goto error; + + return ret; + +error: + g_free (ret); + return NULL; +} + +static inline gboolean +gst_alsa_formats_match (GstAlsaFormat *one, GstAlsaFormat *two) +{ + if (one == two) return TRUE; + if (one == NULL || two == NULL) return FALSE; + return (one->format == two->format) && + (one->rate == two->rate) && + (one->channels == two->channels); +} +/* get props for a spec */ +static GstProps * +gst_alsa_get_props (snd_pcm_format_t format) +{ + if (format == SND_PCM_FORMAT_A_LAW) { + return gst_props_new ("format", GST_PROPS_STRING ("int"), + "law", GST_PROPS_INT(2), + "width", GST_PROPS_INT(8), + "depth", GST_PROPS_INT(8), + "signed", GST_PROPS_BOOLEAN (FALSE), + NULL); + } else if (format == SND_PCM_FORMAT_MU_LAW) { + return gst_props_new ("format", GST_PROPS_STRING ("int"), + "law", GST_PROPS_INT(1), + "width", GST_PROPS_INT(8), + "depth", GST_PROPS_INT(8), + "signed", GST_PROPS_BOOLEAN (FALSE), + NULL); + } else if (snd_pcm_format_linear (format)) { + /* int */ + GstProps *props = gst_props_new ("format", GST_PROPS_STRING ("int"), + "width", GST_PROPS_INT(snd_pcm_format_physical_width (format)), + "depth", GST_PROPS_INT(snd_pcm_format_width (format)), + "law", GST_PROPS_INT(0), + "signed", GST_PROPS_BOOLEAN (snd_pcm_format_signed (format) == 1 ? TRUE : FALSE), + NULL); + /* endianness */ + if (snd_pcm_format_physical_width (format) > 8) { + switch (snd_pcm_format_little_endian (format)) { + case 0: + gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_BIG_ENDIAN))); + break; + case 1: + gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_LITTLE_ENDIAN))); + break; + default: + g_warning("ALSA: Unknown byte order in sound driver. Continuing by assuming system byte order."); + gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_BYTE_ORDER))); + break; } } - break; - case GST_STATE_READY_TO_NULL: - if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN)) - gst_alsa_close_audio (this); - break; + return props; + } else if (snd_pcm_format_float (format)) { + /* no float with non-platform endianness */ + if (!snd_pcm_format_cpu_endian (format)) + return NULL; - default: - g_assert_not_reached(); + return gst_props_new ("format", GST_PROPS_STRING ("float"), + "layout", GST_PROPS_STRING (snd_pcm_format_width (format) == 64 ? "gdouble" : "gfloat"), + "intercept", GST_PROPS_FLOAT (0), + "slope", GST_PROPS_FLOAT (1), + NULL); } - - if (GST_ELEMENT_CLASS (parent_class)->change_state) - return GST_ELEMENT_CLASS (parent_class)->change_state (element); - - return GST_STATE_SUCCESS; + return NULL; } -/*** AUDIO PROCESSING *********************************************************/ - -static int -gst_alsa_do_mmap (GstAlsa *this, snd_pcm_sframes_t *avail) -{ - snd_pcm_uframes_t offset; - snd_pcm_channel_area_t *dst, *src, *areas; - int i, err, width = snd_pcm_format_physical_width (this->format->format); - - /* areas points to the memory areas that belong to gstreamer. */ - areas = src = dst = calloc(this->format->channels, sizeof(snd_pcm_channel_area_t)); - - if (((GstElement *) this)->numpads == 1) { - /* interleaved */ - for (i = 0; i < this->format->channels; i++) { - areas[i].addr = this->pads[0].data; - areas[i].first = i * width; - areas[i].step = this->format->channels * width; - } +static inline void +add_channels (GstProps *props, gint min_rate, gint max_rate, gint min_channels, gint max_channels) { + if (min_rate < 0) { + gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT_RANGE (GST_ALSA_MIN_RATE, GST_ALSA_MAX_RATE))); + } else if (max_rate < 0) { + gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT (min_rate))); } else { - /* noninterleaved */ - for (i = 0; i < this->format->channels; i++) { - areas[i].addr = this->pads[i].data; - areas[i].first = 0; - areas[i].step = width; - } - } - - if ((err = snd_pcm_mmap_begin (this->handle, ( - const snd_pcm_channel_area_t **) (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC ? &src : &dst), - &offset, avail)) < 0) { - g_warning ("gstalsa: mmap failed: %s", snd_strerror (err)); - return -1; - } - - if ((err = snd_pcm_areas_copy (dst, offset, src, 0, this->format->channels, *avail, this->format->format)) < 0) { - snd_pcm_mmap_commit (this->handle, offset, 0); - g_warning ("gstalsa: data copy failed: %s", snd_strerror (err)); - return -1; + gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT_RANGE (min_rate, max_rate))); } - if ((err = snd_pcm_mmap_commit (this->handle, offset, *avail)) < 0) { - g_warning ("gstalsa: mmap commit failed: %s", snd_strerror (err)); - return -1; + if (min_channels < 0) { + gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (1, GST_ALSA_MAX_CHANNELS))); + } else if (max_channels < 0) { + gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT (min_channels))); + } else { + gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (min_channels, max_channels))); } - - return err; } -static int -gst_alsa_do_read_write (GstAlsa *this, snd_pcm_sframes_t *avail) + +/** + * Get all available caps. + * @format: SND_PCM_FORMAT_UNKNOWN for all formats, desired format else + * @rate: allowed rates if < 0, else desired rate + * @channels: all allowed values for channels if < 0, else desired channels + */ +static GstCaps * +gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels) { - void *channels[this->format->channels]; - int err, i; + GstCaps *ret_caps = NULL; - if (((GstElement *) this)->numpads == 1) { - /* interleaved */ - if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) { - err = snd_pcm_readi (this->handle, this->pads[0].data, *avail); - } else { - err = snd_pcm_writei (this->handle, this->pads[0].data, *avail); - } + if (format != SND_PCM_FORMAT_UNKNOWN) { + /* there are some caps set already */ + GstProps *props = gst_alsa_get_props (format); + /* we can never use a format we can't set caps for */ + g_assert (props != NULL); + + add_channels (props, rate, -1, channels, -1); + ret_caps = gst_caps_new (g_strdup (snd_pcm_format_name (format)), "audio/raw", props); } else { - /* noninterleaved */ - for (i = 0; i < this->format->channels; i++) { - channels[i] = this->pads[i].data; - } - if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) { - err = snd_pcm_readn (this->handle, channels, *avail); - } else { - err = snd_pcm_writen (this->handle, channels, *avail); - } - } - /* error handling */ - if (err < 0) { - if (err == -EPIPE) { - gst_alsa_xrun_recovery (this); - return 0; - } - g_warning ("error on data access: %s", snd_strerror (err)); - } - return err; -} -inline static snd_pcm_sframes_t -gst_alsa_update_avail (GstAlsa *this) -{ - snd_pcm_sframes_t avail = snd_pcm_avail_update (this->handle); - if (avail < 0) { - if (avail == -EPIPE) { - gst_alsa_xrun_recovery (this); - } else { - g_warning ("unknown ALSA avail_update return value (%d)", (int) avail); - } - } - return avail; -} -/* returns TRUE, if the loop should go on */ -inline static gboolean -gst_alsa_pcm_wait (GstAlsa *this) -{ - int err; + int i; + GstProps *props; - if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) { - if ((err = snd_pcm_wait (this->handle, 1000)) < 0) { - if (err == EINTR) { - /* happens mostly when run under gdb, or when exiting due to a signal */ - GST_DEBUG (GST_CAT_PLUGIN_INFO, "got interrupted while waiting"); - if (gst_element_interrupt (GST_ELEMENT (this))) { - return TRUE; - } else { - return FALSE; - } + for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) { + props = gst_alsa_get_props (i); + /* can be NULL, because not all alsa formats can be specified as caps */ + if (props != NULL) { + add_channels (props, rate, -1, channels, -1); + ret_caps = gst_caps_append (ret_caps, gst_caps_new (g_strdup (snd_pcm_format_name (i)), + "audio/raw", props)); } - g_warning ("error waiting for alsa pcm: (%d: %s)", err, snd_strerror (err)); - return FALSE; } } - return TRUE; + + return ret_caps; } -/** - * error out or make sure we're in SND_PCM_STATE_RUNNING afterwards - * return FALSE if we're not - */ -inline static gboolean -gst_alsa_start (GstAlsa *this) +/* Return better caps when device is open */ +static GstCaps * +gst_alsa_get_caps (GstPad *pad, GstCaps *caps) { - gint avail; + GstAlsa *this; + snd_pcm_hw_params_t *hw_params; + snd_pcm_format_mask_t *mask; + int i; + unsigned int min_rate, max_rate; + gint min_channels, max_channels; + GstCaps *ret = NULL; - GST_DEBUG (GST_CAT_PLUGIN_INFO, "Starting playback"); + g_return_val_if_fail (pad != NULL, NULL); - switch (snd_pcm_state(this->handle)) { - case SND_PCM_STATE_XRUN: - gst_alsa_xrun_recovery (this); - return gst_alsa_start (this); - case SND_PCM_STATE_SETUP: - ERROR_CHECK (snd_pcm_prepare (this->handle), "error preparing: %s"); - case SND_PCM_STATE_SUSPENDED: - case SND_PCM_STATE_PREPARED: - ERROR_CHECK (snd_pcm_start(this->handle), "error starting playback: %s"); - break; - case SND_PCM_STATE_PAUSED: - ERROR_CHECK (snd_pcm_pause (this->handle, 0), "error unpausing: %s"); - break; - case SND_PCM_STATE_RUNNING: - break; - case SND_PCM_STATE_DRAINING: - case SND_PCM_STATE_OPEN: - /* this probably happens when someone replugged a pipeline and we're in a - really weird state because our cothread wasn't busted */ - return FALSE; - default: - /* it's a bug when we get here */ - g_assert_not_reached (); - break; + this = GST_ALSA (gst_pad_get_parent (pad)); + + if (!GST_FLAG_IS_SET (this, GST_ALSA_OPEN)) + return gst_pad_get_pad_template_caps (pad); + + snd_pcm_hw_params_alloca (&hw_params); + ERROR_CHECK (snd_pcm_hw_params_any (this->handle, hw_params), + "Broken configuration for this PCM: %s"); + + if (((GstElement *) this)->numpads > 1) { + min_channels = 1; + max_channels = -1; + } else { + ERROR_CHECK (snd_pcm_hw_params_get_channels_min (hw_params, &min_rate), + "Coulödn't get minimum channel count for device %s: %s", this->device); + ERROR_CHECK (snd_pcm_hw_params_get_channels_max (hw_params, &max_rate), + "Coulödn't get maximum channel count for device %s: %s", this->device); + min_channels = min_rate; + max_channels = max_rate > GST_ALSA_MAX_CHANNELS ? GST_ALSA_MAX_CHANNELS : max_rate; + } + + ERROR_CHECK (snd_pcm_hw_params_get_rate_min (hw_params, &min_rate, &i), + "Coulödn't get minimum rate for device %s: %s", this->device); + min_rate = min_rate < GST_ALSA_MIN_RATE ? GST_ALSA_MIN_RATE : min_rate + i; + ERROR_CHECK (snd_pcm_hw_params_get_rate_max (hw_params, &max_rate, &i), + "Coulödn't get maximum rate for device %s: %s", this->device); + max_rate = max_rate > GST_ALSA_MAX_RATE ? GST_ALSA_MAX_RATE : max_rate + i; + + snd_pcm_format_mask_alloca (&mask); + snd_pcm_hw_params_get_format_mask (hw_params, mask); + for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) { + if (snd_pcm_format_mask_test (mask, i)) { + GstProps *props = gst_alsa_get_props (i); + /* we can never use a format we can't set caps for */ + if (props != NULL) { + add_channels (props, min_rate, max_rate, min_channels, max_channels); + ret = gst_caps_append (ret, gst_caps_new (g_strdup (snd_pcm_format_name (i)), + "audio/raw", props)); + } + } } - avail = (gint) gst_alsa_update_avail (this); - if (avail < 0) - return FALSE; - gst_alsa_clock_start (this->clock); - return TRUE; + + return ret; } -static void -gst_alsa_sink_loop (GstElement *element) +/* Negotiates the caps */ +GstPadLinkReturn +gst_alsa_link (GstPad *pad, GstCaps *caps) { - snd_pcm_sframes_t avail, avail2, copied; - snd_pcm_uframes_t samplestamp; - gint i; - guint bytes; /* per channel */ - GstAlsa *this = GST_ALSA (element); + GstAlsa *this; + GstAlsaFormat *format; + GstPadLinkReturn ret; - g_return_if_fail (this != NULL); + g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED); + g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED); -sink_restart: + this = GST_ALSA (gst_pad_get_parent (pad)); - avail = gst_alsa_update_avail (this); - if (avail == -EPIPE) goto sink_restart; - if (avail < 0) return; - if (avail > 0) { + if (GST_CAPS_IS_FIXED (caps)) { + if (this->handle == NULL) + if (!gst_alsa_open_audio (this)) + return GST_PAD_LINK_REFUSED; - /* Not enough space. We grab data nonetheless and sleep afterwards */ - if (avail < this->period_size) { - avail = this->period_size; - } + format = gst_alsa_get_format (caps); + if (format == NULL) + return GST_PAD_LINK_DELAYED; - /* check how many bytes we still have in all our bytestreams */ - /* initialize this value to a somewhat sane state, we might alloc this much data below (which would be a bug, but who knows)... */ - bytes = this->period_size * this->period_count * element->numpads * 8; /* must be > max sample size in bytes */ - for (i = 0; i < element->numpads; i++) { - GstAlsaPad *pad = &this->pads[i]; - g_assert (pad->pad != NULL); - while (pad->size == 0) { - if (!pad->buf) - pad->buf = gst_pad_pull (pad->pad); - if (GST_IS_EVENT (pad->buf)) { - gboolean cont = gst_alsa_sink_check_event (this, i, GST_EVENT (pad->buf)); - pad->buf = NULL; - if (cont) - continue; - return; - } - /* caps nego failed somewhere */ - if (this->format == NULL) { - gst_element_error (GST_ELEMENT (this), "alsasink: No caps available"); - return; - } - samplestamp = gst_alsa_timestamp_to_samples (this, GST_BUFFER_TIMESTAMP (pad->buf)); - if (!GST_BUFFER_TIMESTAMP_IS_VALID (pad->buf) || - /* difference between them is < GST_ALSA_DEVIATION */ - ((this->transmitted + gst_alsa_timestamp_to_samples (this, this->max_discont) >= samplestamp) && - (this->transmitted <= gst_alsa_timestamp_to_samples (this, this->max_discont) + samplestamp))) { -no_difference: - pad->size = pad->buf->size; - pad->data = pad->buf->data; - pad->behaviour = 0; - } else if (samplestamp > this->transmitted) { - /* there are empty samples in front of us, fill them with silence */ - int samples = MIN (bytes, samplestamp - this->transmitted) * - (element->numpads == 1 ? this->format->channels : 1); - int size = samples * snd_pcm_format_physical_width (this->format->format) / 8; - g_printerr ("Allocating %d bytes (%ld samples) now to resync: sample %ld expected, but got %ld\n", - size, MIN (bytes, samplestamp - this->transmitted), this->transmitted, samplestamp); - pad->data = g_malloc (size); - if (!pad->data) { - g_warning ("GstAlsa: error allocating %d bytes, buffers unsynced now.", size); - goto no_difference; - } - pad->size = size; - if (0 != snd_pcm_format_set_silence (this->format->format, pad->data, samples)) { - g_warning ("GstAlsa: error silencing buffer, enjoy the noise."); - } - pad->behaviour = 1; - } else if (gst_alsa_samples_to_bytes (this, this->transmitted - samplestamp) >= pad->buf->size) { - g_printerr ("Skipping %lu samples to resync (complete buffer)\n", gst_alsa_bytes_to_samples (this, pad->buf->size)); - /* this buffer is way behind */ - gst_buffer_unref (pad->buf); - pad->buf = NULL; + GST_DEBUG (GST_CAT_CAPS, "found format %s\n", snd_pcm_format_name (format->format)); + + if (!GST_FLAG_IS_SET (this, GST_ALSA_CAPS_NEGO)) { + gint i; + + GST_FLAG_SET (this, GST_ALSA_CAPS_NEGO); + + if (gst_alsa_formats_match (this->format, format)) { + ret = GST_PAD_LINK_OK; + goto out; + } + + if (!gst_alsa_probe_hw_params (this, format)) { + ret = GST_PAD_LINK_REFUSED; + goto out; + } + + for (i = 0; i < ((GstElement *) this)->numpads; i++) { + g_assert (this->pad[i] != NULL); + if (this->pad[i] == pad) continue; - } else if (this->transmitted > samplestamp) { - gint difference = gst_alsa_samples_to_bytes (this, this->transmitted - samplestamp); - g_printerr ("Skipping %lu samples to resync\n", (gulong) this->transmitted - samplestamp); - /* this buffer is only a bit behind */ - pad->size = pad->buf->size - difference; - pad->data = pad->buf->data + difference; - pad->behaviour = 0; - } else { - g_assert_not_reached (); - } + if (gst_pad_try_set_caps (this->pad[i], gst_caps_ref (caps)) == GST_PAD_LINK_REFUSED) { + if (this->format) { + GstCaps *old = gst_alsa_caps (this->format->format, this->format->rate, this->format->channels); + for (--i; i >= 0; i--) { + if (gst_pad_try_set_caps (this->pad[i], gst_caps_ref (old)) == GST_PAD_LINK_REFUSED) { + gst_element_error (GST_ELEMENT (this), "error resetting caps to sane value"); + gst_caps_unref (old); + break; + } + } + gst_caps_unref (old); + } else { + /* FIXME: unset caps on pads somehow */ + } + ret = GST_PAD_LINK_REFUSED; + goto out; + } + } + + GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO); + + /* sync the params */ + if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this); + g_free (this->format); + this->format = format; + if (!gst_alsa_start_audio (this)) { + gst_element_error (GST_ELEMENT (this), "Probed format doesn't work"); + return GST_PAD_LINK_REFUSED; } - bytes = MIN (bytes, pad->size); } - avail = MIN (avail, gst_alsa_bytes_to_samples (this, bytes)); + return GST_PAD_LINK_OK; + } - /* wait until the hw buffer has enough space */ - while (gst_element_get_state (element) == GST_STATE_PLAYING && (avail2 = gst_alsa_update_avail (this)) < avail) { - if (avail2 <= -EPIPE) goto sink_restart; - if (avail2 < 0) return; - if (avail2 < avail && snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING) - if (!gst_alsa_start (this)) return; - if (gst_alsa_pcm_wait (this) == FALSE) - return; - } + return GST_PAD_LINK_DELAYED; - /* FIXME: lotsa stuff can have happened while fetching data. Do we need to check something? */ - - /* put this data into alsa */ - if ((copied = this->transmit (this, &avail)) < 0) - return; - /* update our clock */ - this->transmitted += copied; - /* flush the data */ - bytes = gst_alsa_samples_to_bytes (this, copied); - for (i = 0; i < element->numpads; i++) { - GstAlsaPad *pad = &this->pads[i]; - if ((pad->size -= bytes) == 0) { - switch (pad->behaviour) { - case 0: - gst_data_unref (GST_DATA (pad->buf)); - pad->buf = NULL; /* needed? */ - pad->data = NULL; /* needed? */ - pad->behaviour = 0; /* needed? */ - continue; - case 1: - g_free (pad->data); - pad->data = NULL; /* needed? */ - pad->behaviour = 0; /* needed? */ - continue; - default: - g_assert_not_reached (); - } +out: + g_free (format); + GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO); + return ret; +} + +static GstElementStateReturn +gst_alsa_change_state (GstElement *element) +{ + GstAlsa *this; + + g_return_val_if_fail (element != NULL, FALSE); + this = GST_ALSA (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_NULL_TO_READY: + if (!GST_FLAG_IS_SET (element, GST_ALSA_OPEN)) + if (!gst_alsa_open_audio (this)) + return GST_STATE_FAILURE; + break; + case GST_STATE_READY_TO_PAUSED: + if (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) + if (!gst_alsa_start_audio (this)) + return GST_STATE_FAILURE; + this->transmitted = 0; + break; + case GST_STATE_PAUSED_TO_PLAYING: + if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) { + int err = snd_pcm_pause (this->handle, 0); + if (err < 0) { + g_warning ("Error unpausing sound: %s", snd_strerror (err)); + return GST_STATE_FAILURE; } - g_assert (pad->size > 0); - if (pad->behaviour != 1) - pad->data += bytes; + gst_alsa_clock_start (this->clock); } - } + break; + case GST_STATE_PLAYING_TO_PAUSED: + if (GST_ALSA_CAPS_IS_SET(this, GST_ALSA_CAPS_PAUSE)) { + if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) { + int err = snd_pcm_pause (this->handle, 1); + if (err < 0) { + g_warning ("Error pausing sound: %s", snd_strerror (err)); + return GST_STATE_FAILURE; + } + gst_alsa_clock_stop (this->clock); + } + break; + } + /* if device doesn't know how to pause, we just stop */ + case GST_STATE_PAUSED_TO_READY: + if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) + gst_alsa_stop_audio (this); + g_free (this->format); + this->format = NULL; + break; + case GST_STATE_READY_TO_NULL: + if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN)) + gst_alsa_close_audio (this); + break; - if (snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING && snd_pcm_avail_update (this->handle) == 0) { - gst_alsa_start (this); + default: + g_assert_not_reached(); } + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); + + return GST_STATE_SUCCESS; } -static void -gst_alsa_src_loop (GstElement *element) +/*** AUDIO PROCESSING *********************************************************/ +#if 0 +static int +gst_alsa_do_mmap (GstAlsa *this, snd_pcm_sframes_t *avail) { - snd_pcm_sframes_t avail, copied; - GstBufferPool *pool = NULL; - GstBuffer *buf; - GstCaps *caps; - gint i; - GstAlsaPad *pad; - GstAlsa *this = GST_ALSA (element); + snd_pcm_uframes_t offset; + snd_pcm_channel_area_t *dst, *src, *areas; + int i, err, width = snd_pcm_format_physical_width (this->format->format); - g_return_if_fail (this != NULL); + /* areas points to the memory areas that belong to gstreamer. */ + areas = src = dst = calloc(this->format->channels, sizeof(snd_pcm_channel_area_t)); - /* set the caps on all pads */ - if (!this->format) { - if (!(this->format = g_new (GstAlsaFormat, 1))) { - gst_element_error (element, "No more memory"); + if (((GstElement *) this)->numpads == 1) { + /* interleaved */ + for (i = 0; i < this->format->channels; i++) { + areas[i].addr = this->pads[0].data; + areas[i].first = i * width; + areas[i].step = this->format->channels * width; } - /* FIXME: make this settable */ - this->format->format = SND_PCM_FORMAT_S16; - this->format->rate = 44100; - this->format->channels = (element->numpads == 1) ? 2 : element->numpads; - GST_DEBUG (GST_CAT_NEGOTIATION, "starting caps negotiationgst_alsa_pcm_wait"); - caps = gst_alsa_caps (this->format->format, this->format->rate, this->format->channels); - for (i = 0; i < element->numpads; i++) { - if (gst_pad_try_set_caps (this->pads[i].pad, caps) <= 0) { - GST_DEBUG (GST_CAT_NEGOTIATION, "setting caps (%p) in alsasrc (%p) on pad %d failed", caps, this, i); - return; - } + } else { + /* noninterleaved */ + for (i = 0; i < this->format->channels; i++) { + areas[i].addr = this->pads[i].data; + areas[i].first = 0; + areas[i].step = width; } } -src_restart: - - while ((avail = gst_alsa_update_avail (this)) < this->period_size) { - if (avail == -EPIPE) goto src_restart; - if (avail < 0) return; - if (snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING) break; - /* wait */ - if (gst_alsa_pcm_wait (this) == FALSE) - return; + if ((err = snd_pcm_mmap_begin (this->handle, ( + const snd_pcm_channel_area_t **) (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC ? &src : &dst), + &offset, avail)) < 0) { + g_warning ("gstalsa: mmap failed: %s", snd_strerror (err)); + return -1; } - if (avail > 0) { - int width = snd_pcm_format_physical_width (this->format->format); - int bytes_per_frame = ( width / 8 ) * (element->numpads == 1 ? this->format->channels : 1); - if ((copied = this->transmit (this, &avail)) < 0) - return; - - /* we get the buffer pool once per go round */ - if (! pool) pool = gst_alsa_src_get_buffer_pool (this->pads[0].pad); - - /* push the data to gstreamer if it's big enough to fill up a buffer. */ - for (i = 0; i < element->numpads; i++) { - pad = &this->pads[i]; - pad->size += MIN (copied, this->period_size - pad->size); - if (pad->size >= this->period_size) { - g_assert (pad->size <= this->period_size); - - buf = gst_buffer_new_from_pool (pool, 0, 0); + if ((err = snd_pcm_areas_copy (dst, offset, src, 0, this->format->channels, *avail, this->format->format)) < 0) { + snd_pcm_mmap_commit (this->handle, offset, 0); + g_warning ("gstalsa: data copy failed: %s", snd_strerror (err)); + return -1; + } + if ((err = snd_pcm_mmap_commit (this->handle, offset, *avail)) < 0) { + g_warning ("gstalsa: mmap commit failed: %s", snd_strerror (err)); + return -1; + } - GST_BUFFER_DATA (buf) = pad->data; - GST_BUFFER_SIZE (buf) = this->period_size * bytes_per_frame; - GST_BUFFER_MAXSIZE (buf) = this->period_size * bytes_per_frame; + return err; +} +static int +gst_alsa_do_read_write (GstAlsa *this, snd_pcm_sframes_t *avail) +{ + void *channels[this->format->channels]; + int err, i; - gst_pad_push (pad->pad, buf); + if (((GstElement *) this)->numpads == 1) { + /* interleaved */ + if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) { + err = snd_pcm_readi (this->handle, this->pads[0].data, *avail); + } else { + err = snd_pcm_writei (this->handle, this->pads[0].data, *avail); + } + } else { + /* noninterleaved */ + for (i = 0; i < this->format->channels; i++) { + channels[i] = this->pads[i].data; + } + if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) { + err = snd_pcm_readn (this->handle, channels, *avail); + } else { + err = snd_pcm_writen (this->handle, channels, *avail); + } + } + /* error handling */ + if (err < 0) { + if (err == -EPIPE) { + gst_alsa_xrun_recovery (this); + return 0; + } + g_warning ("error on data access: %s", snd_strerror (err)); + } + return err; +} +#endif +inline static snd_pcm_sframes_t +gst_alsa_update_avail (GstAlsa *this) +{ + snd_pcm_sframes_t avail = snd_pcm_avail_update (this->handle); + if (avail < 0) { + if (avail == -EPIPE) { + gst_alsa_xrun_recovery (this); + } else { + g_warning ("unknown ALSA avail_update return value (%d)", (int) avail); + } + } + return avail; +} +/* returns TRUE, if the loop should go on */ +inline static gboolean +gst_alsa_pcm_wait (GstAlsa *this) +{ + int err; - pad->data = NULL; - pad->size = 0; + if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) { + if ((err = snd_pcm_wait (this->handle, 1000)) < 0) { + if (err == EINTR) { + /* happens mostly when run under gdb, or when exiting due to a signal */ + GST_DEBUG (GST_CAT_PLUGIN_INFO, "got interrupted while waiting"); + if (gst_element_interrupt (GST_ELEMENT (this))) { + return TRUE; + } else { + return FALSE; + } } + g_warning ("error waiting for alsa pcm: (%d: %s)", err, snd_strerror (err)); + return FALSE; } - - pool = NULL; } + return TRUE; +} - /* BUG: we start the stream explicitly, autostart doesn't work correctly (alsa 0.9.0rc7) */ - if (snd_pcm_state(this->handle) == SND_PCM_STATE_PREPARED && snd_pcm_avail_update (this->handle) == 0) { - GST_DEBUG (GST_CAT_PLUGIN_INFO, "Explicitly starting capture"); - snd_pcm_start(this->handle); +/** + * error out or make sure we're in SND_PCM_STATE_RUNNING afterwards + * return FALSE if we're not + */ +inline static gboolean +gst_alsa_start (GstAlsa *this) +{ + gint avail; + + GST_DEBUG (GST_CAT_PLUGIN_INFO, "Starting playback"); + + switch (snd_pcm_state(this->handle)) { + case SND_PCM_STATE_XRUN: + gst_alsa_xrun_recovery (this); + return gst_alsa_start (this); + case SND_PCM_STATE_SETUP: + ERROR_CHECK (snd_pcm_prepare (this->handle), "error preparing: %s"); + case SND_PCM_STATE_SUSPENDED: + case SND_PCM_STATE_PREPARED: + ERROR_CHECK (snd_pcm_start(this->handle), "error starting playback: %s"); + break; + case SND_PCM_STATE_PAUSED: + ERROR_CHECK (snd_pcm_pause (this->handle, 0), "error unpausing: %s"); + break; + case SND_PCM_STATE_RUNNING: + break; + case SND_PCM_STATE_DRAINING: + case SND_PCM_STATE_OPEN: + /* this probably happens when someone replugged a pipeline and we're in a + really weird state because our cothread wasn't busted */ + return FALSE; + default: + /* it's a bug when we get here */ + g_assert_not_reached (); + break; } + avail = (gint) gst_alsa_update_avail (this); + if (avail < 0) + return FALSE; + gst_alsa_clock_start (this->clock); + return TRUE; } - static void gst_alsa_xrun_recovery (GstAlsa *this) { @@ -1436,81 +1653,6 @@ gst_alsa_xrun_recovery (GstAlsa *this) } } -/* TRUE, if everything should continue */ -static gboolean -gst_alsa_sink_check_event (GstAlsa *this, gint pad_nr, GstEvent *event) -{ - gboolean cont = TRUE; - - if (event) { - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_EOS: - gst_alsa_set_eos (this); - cont = FALSE; - break; - case GST_EVENT_INTERRUPT: - cont = FALSE; - break; - case GST_EVENT_NEW_MEDIA: - /* only the first pad my seek */ - if (pad_nr != 0) - break; - - if (GST_CLOCK_TIME_IS_VALID (this->clock->start_time)) { /* if the clock is running */ - g_assert (this->format); - /* adjust the start time */ - this->clock->start_time += gst_alsa_samples_to_timestamp (this, this->transmitted); - } - this->transmitted = 0; - /* FIXME: Notify the clock that we're at offset 0 again */ - break; - case GST_EVENT_DISCONTINUOUS: - { - gint64 value; - - /* only the first pad my seek */ - if (pad_nr != 0) { - break; - } - if (gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) { - if (!gst_clock_handle_discont (GST_ELEMENT (this)->clock, value)) - g_printerr ("GstAlsa: clock couldn't handle discontinuity\n"); - } - if (!gst_event_discont_get_value (event, GST_FORMAT_UNITS, &value)) { - if (!gst_event_discont_get_value (event, GST_FORMAT_BYTES, &value)) { - if (!gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) { - g_warning ("GstAlsa: Could not acquire samplecount after seek, the clock might screw your pipeline now"); - break; - } else { - if (this->format) /* discont event before any data (and before any caps...) */ - value = gst_alsa_timestamp_to_samples (this, value); - } - } else { - if (this->format) /* discont event before any data (and before any caps...) */ - value = gst_alsa_bytes_to_samples (this, value); - } - } - if (GST_CLOCK_TIME_IS_VALID (this->clock->start_time)) { /* if the clock is running */ - g_assert (this->format); - /* adjust the start time */ - this->clock->start_time += gst_alsa_samples_to_timestamp (this, this->transmitted) - - gst_alsa_samples_to_timestamp (this, value); - } - this->transmitted = value; - break; - } - default: - g_warning ("GstAlsa: got an unknown event (Type: %d)", GST_EVENT_TYPE (event)); - break; - } - gst_event_unref (event); - } else { - /* the element at the top of the chain did not emit an event. */ - g_assert_not_reached (); - } - return cont; -} - /*** AUDIO SETUP / START / STOP ***********************************************/ static void @@ -1525,13 +1667,13 @@ gst_alsa_open_audio (GstAlsa *this) g_assert (this != NULL); g_assert (this->handle == NULL); - GST_INFO (GST_CAT_PLUGIN_INFO, "Opening alsa device \"%s\" for %s...\n", this->device, - this->stream == SND_PCM_STREAM_PLAYBACK ? "playback" : "capture"); + GST_INFO (GST_CAT_PLUGIN_INFO, "Opening alsa device \"%s\"...\n", this->device); ERROR_CHECK (snd_output_stdio_attach (&this->out, stderr, 0), "error opening log output: %s"); /* we use non-blocking i/o */ - ERROR_CHECK (snd_pcm_open (&this->handle, this->device, this->stream, SND_PCM_NONBLOCK), + ERROR_CHECK (snd_pcm_open (&this->handle, this->device, + GST_ALSA_GET_CLASS (this)->stream, SND_PCM_NONBLOCK), "error opening pcm device %s: %s\n", this->device); GST_FLAG_SET (this, GST_ALSA_OPEN); @@ -1638,9 +1780,9 @@ gst_alsa_set_hw_params (GstAlsa *this) GST_ALSA_CAPS_SET (this, GST_ALSA_CAPS_SYNC_START, snd_pcm_hw_params_can_sync_start (hw_params)); if (this->mmap) { - this->transmit = gst_alsa_do_mmap; + this->transmit = GST_ALSA_GET_CLASS (this)->transmit_mmap; } else { - this->transmit = gst_alsa_do_read_write; + this->transmit = GST_ALSA_GET_CLASS (this)->transmit_rw; } return TRUE; @@ -1690,26 +1832,24 @@ gst_alsa_start_audio (GstAlsa *this) static gboolean gst_alsa_drain_audio (GstAlsa *this) { - int err; - g_assert (this != NULL); - g_return_val_if_fail (this != NULL, FALSE); g_return_val_if_fail (this->handle != NULL, FALSE); GST_DEBUG (GST_CAT_PLUGIN_INFO, "stopping alsa"); - if (this->stream == SND_PCM_STREAM_PLAYBACK && - (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED || - snd_pcm_state (this->handle) == SND_PCM_STATE_XRUN || - snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING)) { - gst_alsa_clock_stop (this->clock); - /* snd_pcm_drain returns -EAGAIN in non-blocking mode while it outputs sound */ - err = snd_pcm_drain (this->handle); - while (err == -EAGAIN) { - g_usleep (1000); - err = snd_pcm_drain (this->handle); - } - ERROR_CHECK (err, "couldn't stop and drain buffer: %s"); + switch (snd_pcm_state (this->handle)) { + case SND_PCM_STATE_XRUN: + case SND_PCM_STATE_RUNNING: + gst_alsa_clock_stop (this->clock); + /* fall through - clock is already stopped when paused */ + case SND_PCM_STATE_PAUSED: + /* snd_pcm_drain only works in blocking mode */ + ERROR_CHECK (snd_pcm_nonblock(this->handle, 0), "couldn't set blocking mode: %s"); + ERROR_CHECK (snd_pcm_drain (this->handle), "couldn't stop and drain buffer: %s"); + ERROR_CHECK (snd_pcm_nonblock(this->handle, 1), "couldn't set non-blocking mode: %s"); + break; + default: + break; } GST_FLAG_UNSET (this, GST_ALSA_RUNNING); @@ -1724,13 +1864,17 @@ gst_alsa_stop_audio (GstAlsa *this) GST_DEBUG (GST_CAT_PLUGIN_INFO, "stopping alsa, skipping pending frames"); - if (this->stream == SND_PCM_STREAM_PLAYBACK && - (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED || - snd_pcm_state (this->handle) == SND_PCM_STATE_XRUN || - snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING)) { - gst_alsa_clock_stop (this->clock); - ERROR_CHECK (snd_pcm_drop (this->handle), - "couldn't stop (dropping frames): %s"); + switch (snd_pcm_state (this->handle)) { + case SND_PCM_STATE_XRUN: + case SND_PCM_STATE_RUNNING: + gst_alsa_clock_stop (this->clock); + /* fall through - clock is already stopped when paused */ + case SND_PCM_STATE_PAUSED: + ERROR_CHECK (snd_pcm_drop (this->handle), + "couldn't stop (dropping frames): %s"); + break; + default: + break; } GST_FLAG_UNSET (this, GST_ALSA_RUNNING); diff --git a/ext/alsa/gstalsa.h b/ext/alsa/gstalsa.h index 1e66f34f3f..35ec9d3a84 100644 --- a/ext/alsa/gstalsa.h +++ b/ext/alsa/gstalsa.h @@ -38,6 +38,7 @@ #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_ALSA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), 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() @@ -65,10 +66,10 @@ typedef struct _GstAlsa GstAlsa; typedef struct _GstAlsaClass GstAlsaClass; -typedef GstAlsa GstAlsaSink; -typedef GstAlsaClass GstAlsaSinkClass; -typedef GstAlsa GstAlsaSrc; -typedef GstAlsaClass GstAlsaSrcClass; +typedef struct _GstAlsaSink GstAlsaSink; +typedef struct _GstAlsaSinkClass GstAlsaSinkClass; +typedef struct _GstAlsaSrc GstAlsaSrc; +typedef struct _GstAlsaSrcClass GstAlsaSrcClass; typedef struct _GstAlsaClock GstAlsaClock; typedef struct _GstAlsaClockClass GstAlsaClockClass; @@ -98,14 +99,6 @@ typedef enum { typedef int (*GstAlsaTransmitFunction) (GstAlsa *this, snd_pcm_sframes_t *avail); -typedef struct { - GstPad * pad; - guint8 * data; /* pointer into buffer */ - guint size; /* sink: bytes left in buffer */ - GstBuffer * buf; /* current buffer */ - guint behaviour; /* 0 = data points into buffer (so unref when size == 0), - 1 = data should be freed, use buffer after that */ -} GstAlsaPad; typedef struct { snd_pcm_format_t format; guint rate; @@ -116,10 +109,9 @@ struct _GstAlsa { GstElement parent; /* array of GstAlsaPads */ - GstAlsaPad pads[GST_ALSA_MAX_CHANNELS]; + GstPad * pad[GST_ALSA_MAX_CHANNELS]; gchar * device; - snd_pcm_stream_t stream; snd_pcm_t * handle; guint pcm_caps; /* capabilities of the pcm device, see GstAlsaPcmCaps */ snd_output_t * out; @@ -146,9 +138,32 @@ struct _GstAlsa { */ }; struct _GstAlsaClass { - GstElementClass parent_class; -}; + GstElementClass parent_class; + + snd_pcm_stream_t stream; + /* different transmit functions */ + GstAlsaTransmitFunction transmit_mmap; + GstAlsaTransmitFunction transmit_rw; +}; +struct _GstAlsaSink { + GstAlsa parent; + /* array of the data on the channels */ + guint8 * data[GST_ALSA_MAX_CHANNELS]; /* pointer into buffer */ + guint size[GST_ALSA_MAX_CHANNELS]; /* sink: bytes left in buffer */ + GstBuffer * buf[GST_ALSA_MAX_CHANNELS]; /* current buffer */ + guint behaviour[GST_ALSA_MAX_CHANNELS]; /* 0 = data points into buffer (so unref when size == 0), + 1 = data should be freed, use buffer after that */ +}; +struct _GstAlsaSinkClass { + GstAlsaClass parent_class; +}; +struct _GstAlsaSrc { + GstAlsa parent; +}; +struct _GstAlsaSrcClass { + GstAlsaClass parent_class; +}; struct _GstAlsaClock { GstSystemClock parent;