ALSA cleanup step 3:

Original commit message from CVS:
ALSA cleanup step 3:
- make caps nego work, when caps are already set
- rewriting lots of caps nego while doing so
- start stream explicitly now (will probably stay that way because of sync)
- random bugfixes
alsasrc is probably broken again.
alsasink should now be stable enough to be used with gst-player or rhythmbox (seeking works)
This commit is contained in:
Benjamin Otte 2003-03-18 22:07:56 +00:00
parent 8144288b14
commit 4b1649ad34
2 changed files with 242 additions and 129 deletions

View file

@ -58,7 +58,7 @@ static GstElementDetails gst_alsa_sink_details = {
"Output to a sound card via ALSA", "Output to a sound card via ALSA",
VERSION, VERSION,
"Thomas Nyberg <thomas@codefactory.se>, " "Thomas Nyberg <thomas@codefactory.se>, "
"Andy Wingo <apwingo@eos.ncsu.edu>" "Andy Wingo <apwingo@eos.ncsu.edu>, "
"Benjamin Otte <in7y118@public.uni-hamburg.de>", "Benjamin Otte <in7y118@public.uni-hamburg.de>",
"(C) 2001-2003" "(C) 2001-2003"
}; };
@ -71,7 +71,7 @@ static GstElementDetails gst_alsa_src_details = {
"Read from a sound card via ALSA", "Read from a sound card via ALSA",
VERSION, VERSION,
"Thomas Nyberg <thomas@codefactory.se>, " "Thomas Nyberg <thomas@codefactory.se>, "
"Andy Wingo <apwingo@eos.ncsu.edu>" "Andy Wingo <apwingo@eos.ncsu.edu>, "
"Benjamin Otte <in7y118@public.uni-hamburg.de>", "Benjamin Otte <in7y118@public.uni-hamburg.de>",
"(C) 2001-2003" "(C) 2001-2003"
}; };
@ -279,14 +279,11 @@ gst_alsa_init (GstAlsa *this)
gint i; gint i;
/* init values */ /* init values */
this->handle = NULL; this->handle = NULL;
this->channels = 1;
this->caps_set = FALSE;
GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED); GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED);
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) { if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) {
this->stream = SND_PCM_STREAM_CAPTURE; this->stream = SND_PCM_STREAM_CAPTURE;
this->format = SND_PCM_FORMAT_S16; /* native endian */
gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_src_loop); 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"); 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); gst_pad_set_bufferpool_function(this->pads[0].pad, gst_alsa_src_get_buffer_pool);
@ -294,10 +291,8 @@ gst_alsa_init (GstAlsa *this)
/* set the rate to a sensible value. we can't have gobject construct this /* set the rate to a sensible value. we can't have gobject construct this
manually since it only really makes sense on src elements. the rate can manually since it only really makes sense on src elements. the rate can
be changed later through the gobject set property function. */ be changed later through the gobject set property function. */
this->rate = 44100;
} else if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK) { } else if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK) {
this->stream = SND_PCM_STREAM_PLAYBACK; this->stream = SND_PCM_STREAM_PLAYBACK;
this->format = SND_PCM_FORMAT_UNKNOWN; /* we don't know until caps are set */
gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_sink_loop); 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->pads[0].pad = gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "sink");
this->pads[0].bs = gst_bytestream_new (this->pads[0].pad); this->pads[0].bs = gst_bytestream_new (this->pads[0].pad);
@ -328,17 +323,17 @@ gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value,
case ARG_FORMAT: case ARG_FORMAT:
/* setting this property only makes sense on sources */ /* setting this property only makes sense on sources */
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC)
this->format = g_value_get_enum (value); this->format->format = g_value_get_enum (value);
break; break;
case ARG_CHANNELS: case ARG_CHANNELS:
/* setting this property only makes sense on sources */ /* setting this property only makes sense on sources */
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC)
this->channels = g_value_get_int (value); this->format->channels = g_value_get_int (value);
break; break;
case ARG_RATE: case ARG_RATE:
/* setting this property only makes sense on sources */ /* setting this property only makes sense on sources */
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC)
this->rate = g_value_get_int (value); this->format->rate = g_value_get_int (value);
break; break;
case ARG_PERIODCOUNT: case ARG_PERIODCOUNT:
g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)); g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
@ -383,13 +378,13 @@ gst_alsa_get_property (GObject *object, guint prop_id, GValue *value,
g_value_set_string (value, this->device); g_value_set_string (value, this->device);
break; break;
case ARG_FORMAT: case ARG_FORMAT:
g_value_set_enum (value, this->format); g_value_set_enum (value, this->format ? this->format->format : SND_PCM_FORMAT_UNKNOWN);
break; break;
case ARG_CHANNELS: case ARG_CHANNELS:
g_value_set_int (value, this->channels); g_value_set_int (value, this->format ? this->format->channels : 2);
break; break;
case ARG_RATE: case ARG_RATE:
g_value_set_int (value, this->rate); g_value_set_int (value, this->format ? this->format->rate : 44100);
break; break;
case ARG_PERIODCOUNT: case ARG_PERIODCOUNT:
g_value_set_int (value, this->period_count); g_value_set_int (value, this->period_count);
@ -474,9 +469,6 @@ gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ,
g_return_val_if_fail ((this = GST_ALSA (element)), NULL); g_return_val_if_fail ((this = GST_ALSA (element)), NULL);
g_return_val_if_fail (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING), NULL); g_return_val_if_fail (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING), NULL);
/* you can't request a pad if the non-request pad already has more than 1 channel */
g_return_val_if_fail (this->channels <= element->numpads, NULL);
if (name) { if (name) {
/* locate the channel number in the requested pad name. to do so look at /* locate the channel number in the requested pad name. to do so look at
where the % (which begins the %d) is in the template name. */ where the % (which begins the %d) is in the template name. */
@ -516,15 +508,19 @@ found_channel:
return this->pads[channel].pad; return this->pads[channel].pad;
} }
/* gets the matching alsa format or SND_PCM_FORMAT_UNKNOWN if none matches */ /* gets the matching alsa format or NULL if none matches */
static snd_pcm_format_t static GstAlsaFormat *
gst_alsa_get_format (GstCaps *caps) gst_alsa_get_format (GstCaps *caps)
{ {
const gchar *format_name; const gchar *format_name;
GstAlsaFormat *ret;
if (!(ret = g_new (GstAlsaFormat, 1)))
return NULL;
/* we have to differentiate between int and float formats */ /* we have to differentiate between int and float formats */
if (!gst_caps_get_string (caps, "format", &format_name)) if (!gst_caps_get_string (caps, "format", &format_name))
return SND_PCM_FORMAT_UNKNOWN; goto error;
if (strncmp (format_name, "int", 3) == 0) { if (strncmp (format_name, "int", 3) == 0) {
gboolean sign; gboolean sign;
@ -537,36 +533,40 @@ gst_alsa_get_format (GstCaps *caps)
"law", &law, "law", &law,
"signed", &sign, "signed", &sign,
NULL)) NULL))
return SND_PCM_FORMAT_UNKNOWN; goto error;
/* extract endianness if needed */ /* extract endianness if needed */
if (width > 8) { if (width > 8) {
if (!gst_caps_get (caps, if (!gst_caps_get (caps,
"endianness", &endianness, "endianness", &endianness,
NULL)) NULL))
return SND_PCM_FORMAT_UNKNOWN; goto error;
} else { } else {
endianness = G_BYTE_ORDER; endianness = G_BYTE_ORDER;
} }
/* find corresponding alsa format */ /* find corresponding alsa format */
switch (law) { switch (law) {
case 0: return snd_pcm_build_linear_format (depth, width, sign ? 0 : 1, endianness == G_LITTLE_ENDIAN ? 0 : 1); case 0:
ret->format = snd_pcm_build_linear_format (depth, width, sign ? 0 : 1, endianness == G_LITTLE_ENDIAN ? 0 : 1);
break;
case 1: case 1:
if (width == 8 && depth == 8 && sign == FALSE) { if (width == 8 && depth == 8 && sign == FALSE) {
return SND_PCM_FORMAT_MU_LAW; ret->format = SND_PCM_FORMAT_MU_LAW;
break;
} else { } else {
return SND_PCM_FORMAT_UNKNOWN; goto error;
} }
case 2: case 2:
if (width == 8 && depth == 8 && sign == FALSE) { if (width == 8 && depth == 8 && sign == FALSE) {
return SND_PCM_FORMAT_A_LAW; ret->format = SND_PCM_FORMAT_A_LAW;
break;
} else { } else {
return SND_PCM_FORMAT_UNKNOWN; goto error;
} }
default: return SND_PCM_FORMAT_UNKNOWN; default:
goto error;
} }
} else if (strncmp (format_name, "float", 5) == 0) { } else if (strncmp (format_name, "float", 5) == 0) {
gchar *layout; gchar *layout;
gfloat intercept, slope; gfloat intercept, slope;
@ -576,27 +576,54 @@ gst_alsa_get_format (GstCaps *caps)
"intercept", &intercept, "intercept", &intercept,
"slope", &slope, "slope", &slope,
NULL)) NULL))
return SND_PCM_FORMAT_UNKNOWN; goto error;
if (intercept != 0.0f || slope != 1.0f) { if (intercept != 0.0f || slope != 1.0f) {
return SND_PCM_FORMAT_UNKNOWN; goto error;
} }
/* match layout to format wrt to endianness */ /* match layout to format wrt to endianness */
if (strncmp (layout, "gfloat", 6) == 0) { if (strncmp (layout, "gfloat", 6) == 0) {
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) return SND_PCM_FORMAT_FLOAT_LE; if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
if (G_BYTE_ORDER == G_BIG_ENDIAN) return SND_PCM_FORMAT_FLOAT_BE; ret->format = SND_PCM_FORMAT_FLOAT_LE;
return SND_PCM_FORMAT_FLOAT; } 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) { } else if (strncmp (layout, "gdouble", 7) == 0) {
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) return SND_PCM_FORMAT_FLOAT64_LE; if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
if (G_BYTE_ORDER == G_BIG_ENDIAN) return SND_PCM_FORMAT_FLOAT64_BE; ret->format = SND_PCM_FORMAT_FLOAT64_LE;
return SND_PCM_FORMAT_FLOAT64; } else if (G_BYTE_ORDER == G_BIG_ENDIAN) {
ret->format = SND_PCM_FORMAT_FLOAT64_BE;
} else {
ret->format = SND_PCM_FORMAT_FLOAT64;
}
} else { } else {
return SND_PCM_FORMAT_UNKNOWN; goto error;
} }
} }
return SND_PCM_FORMAT_UNKNOWN; /* 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 */ /* get props for a spec */
static GstProps * static GstProps *
gst_alsa_get_props (snd_pcm_format_t format) gst_alsa_get_props (snd_pcm_format_t format)
@ -695,7 +722,7 @@ gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels)
if (props != NULL) { if (props != NULL) {
add_channels (props, rate, channels); add_channels (props, rate, channels);
ret_caps = gst_caps_append (ret_caps, gst_caps_new (g_strdup (snd_pcm_format_name (i)), ret_caps = gst_caps_append (ret_caps, gst_caps_new (g_strdup (snd_pcm_format_name (i)),
"audio/raw", props)); "audio/raw", props));
} }
} }
} }
@ -708,8 +735,7 @@ GstPadLinkReturn
gst_alsa_link (GstPad *pad, GstCaps *caps) gst_alsa_link (GstPad *pad, GstCaps *caps)
{ {
GstAlsa *this; GstAlsa *this;
snd_pcm_format_t format; GstAlsaFormat *format;
gint rate, channels;
g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED); g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED);
g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED); g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED);
@ -722,33 +748,42 @@ gst_alsa_link (GstPad *pad, GstCaps *caps)
return GST_PAD_LINK_REFUSED; return GST_PAD_LINK_REFUSED;
format = gst_alsa_get_format (caps); format = gst_alsa_get_format (caps);
GST_DEBUG (GST_CAT_CAPS, "found format %s\n", snd_pcm_format_name (format)); if (format == NULL)
/* FIXME: allow changing the format here, even by retrying caps on other pads */ return GST_PAD_LINK_DELAYED;
if (format == SND_PCM_FORMAT_UNKNOWN || (this->format != SND_PCM_FORMAT_UNKNOWN && this->format != format))
return GST_PAD_LINK_REFUSED;
if (!gst_caps_get (caps, "rate", &rate,
"channels", &channels,
NULL))
return GST_PAD_LINK_REFUSED;
if (this->format != SND_PCM_FORMAT_UNKNOWN && this->rate != rate)
return GST_PAD_LINK_REFUSED;
if (this->format != SND_PCM_FORMAT_UNKNOWN && ((this->channels > 1 || channels > 1)))
return GST_PAD_LINK_REFUSED;
if (this->format == SND_PCM_FORMAT_UNKNOWN) { GST_DEBUG (GST_CAT_CAPS, "found format %s\n", snd_pcm_format_name (format->format));
this->channels = channels;
} else { if (gst_alsa_formats_match (this->format, format)) {
this->channels++; g_free (format);
return GST_PAD_LINK_OK;
} }
this->format = format;
this->rate = rate;
/* sync the params */ if (!GST_FLAG_IS_SET (this, GST_ALSA_CAPS_NEGO)) {
if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this); gint i;
if (GST_FLAG_IS_SET (this, GST_ALSA_OPEN)) gst_alsa_close_audio (this);
if (!gst_alsa_open_audio (this)) return GST_PAD_LINK_REFUSED; GST_FLAG_SET (this, GST_ALSA_CAPS_NEGO);
if (!gst_alsa_start_audio (this)) return GST_PAD_LINK_REFUSED;
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) {
g_free (format);
return GST_PAD_LINK_REFUSED;
}
}
GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO);
g_free (this->format);
this->format = format;
/* 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);
if (!gst_alsa_open_audio (this)) return GST_PAD_LINK_REFUSED;
if (!gst_alsa_start_audio (this)) return GST_PAD_LINK_REFUSED;
}
return GST_PAD_LINK_OK; return GST_PAD_LINK_OK;
} }
@ -763,8 +798,8 @@ gst_alsa_src_get_buffer_pool (GstPad *pad)
GstAlsa *this = GST_ALSA (gst_pad_get_parent (pad)); GstAlsa *this = GST_ALSA (gst_pad_get_parent (pad));
width = snd_pcm_format_physical_width (this->format); width = snd_pcm_format_physical_width (this->format->format);
bytes_per_frame = ( width / 8 ) * (GST_ELEMENT (this)->numpads == 1 ? this->channels : 1); bytes_per_frame = ( width / 8 ) * (GST_ELEMENT (this)->numpads == 1 ? this->format->channels : 1);
/* FIXME : is this right ? constant size buffers are probably a good thing, /* FIXME : is this right ? constant size buffers are probably a good thing,
but what if the size changes (e.g. during xrun autorecovery) ? */ but what if the size changes (e.g. during xrun autorecovery) ? */
@ -782,23 +817,30 @@ gst_alsa_change_state (GstElement *element)
switch (GST_STATE_TRANSITION (element)) { switch (GST_STATE_TRANSITION (element)) {
case GST_STATE_NULL_TO_READY: case GST_STATE_NULL_TO_READY:
if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN) == FALSE) if (!GST_FLAG_IS_SET (element, GST_ALSA_OPEN))
if (!gst_alsa_open_audio (this)) if (!gst_alsa_open_audio (this))
return GST_STATE_FAILURE; return GST_STATE_FAILURE;
break; break;
case GST_STATE_READY_TO_PAUSED: case GST_STATE_READY_TO_PAUSED:
break; if (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING))
case GST_STATE_PAUSED_TO_PLAYING:
if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) == FALSE)
if (!gst_alsa_start_audio (this)) if (!gst_alsa_start_audio (this))
return GST_STATE_FAILURE; return GST_STATE_FAILURE;
break;
case GST_STATE_PAUSED_TO_PLAYING:
if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED)
snd_pcm_pause (this->handle, 0);
break;
case GST_STATE_PLAYING_TO_PAUSED: 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)
snd_pcm_pause (this->handle, 1);
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)) if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING))
gst_alsa_drain_audio (this); gst_alsa_drain_audio (this);
break; break;
case GST_STATE_PAUSED_TO_READY:
break;
case GST_STATE_READY_TO_NULL: case GST_STATE_READY_TO_NULL:
if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN)) if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN))
gst_alsa_close_audio (this); gst_alsa_close_audio (this);
@ -821,21 +863,21 @@ gst_alsa_do_mmap (GstAlsa *this, snd_pcm_sframes_t *avail)
{ {
snd_pcm_uframes_t offset; snd_pcm_uframes_t offset;
snd_pcm_channel_area_t *dst, *src, *areas; snd_pcm_channel_area_t *dst, *src, *areas;
int i, err, width = snd_pcm_format_physical_width (this->format); int i, err, width = snd_pcm_format_physical_width (this->format->format);
/* areas points to the memory areas that belong to gstreamer. */ /* areas points to the memory areas that belong to gstreamer. */
areas = src = dst = calloc(this->channels, sizeof(snd_pcm_channel_area_t)); areas = src = dst = calloc(this->format->channels, sizeof(snd_pcm_channel_area_t));
if (((GstElement *) this)->numpads == 1) { if (((GstElement *) this)->numpads == 1) {
/* interleaved */ /* interleaved */
for (i = 0; i < this->channels; i++) { for (i = 0; i < this->format->channels; i++) {
areas[i].addr = this->pads[0].data; areas[i].addr = this->pads[0].data;
areas[i].first = i * width; areas[i].first = i * width;
areas[i].step = this->channels * width; areas[i].step = this->format->channels * width;
} }
} else { } else {
/* noninterleaved */ /* noninterleaved */
for (i = 0; i < this->channels; i++) { for (i = 0; i < this->format->channels; i++) {
areas[i].addr = this->pads[i].data; areas[i].addr = this->pads[i].data;
areas[i].first = 0; areas[i].first = 0;
areas[i].step = width; areas[i].step = width;
@ -849,7 +891,7 @@ gst_alsa_do_mmap (GstAlsa *this, snd_pcm_sframes_t *avail)
return -1; return -1;
} }
if ((err = snd_pcm_areas_copy (dst, offset, src, 0, this->channels, *avail, this->format)) < 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); snd_pcm_mmap_commit (this->handle, offset, 0);
g_warning ("gstalsa: data copy failed: %s", snd_strerror (err)); g_warning ("gstalsa: data copy failed: %s", snd_strerror (err));
return -1; return -1;
@ -875,7 +917,6 @@ gst_alsa_update_avail (GstAlsa *this)
} }
return avail; return avail;
} }
/* returns TRUE, if the loop should go on */ /* returns TRUE, if the loop should go on */
inline static gboolean inline static gboolean
gst_alsa_pcm_wait (GstAlsa *this) gst_alsa_pcm_wait (GstAlsa *this)
@ -898,6 +939,34 @@ gst_alsa_pcm_wait (GstAlsa *this)
return TRUE; return TRUE;
} }
/**
* 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)
{
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_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;
default:
/* it's a bug when we get here */
g_assert_not_reached ();
break;
}
return TRUE;
}
static void static void
gst_alsa_sink_loop (GstElement *element) gst_alsa_sink_loop (GstElement *element)
{ {
@ -909,7 +978,7 @@ gst_alsa_sink_loop (GstElement *element)
g_return_if_fail (this != NULL); g_return_if_fail (this != NULL);
/* caps nego: fetch 1 byte from every pad */ /* caps nego: fetch 1 byte from every pad */
if (this->format == SND_PCM_FORMAT_UNKNOWN) { if (this->format == NULL) {
GST_DEBUG (GST_CAT_NEGOTIATION, "starting caps negotiation"); GST_DEBUG (GST_CAT_NEGOTIATION, "starting caps negotiation");
for (i = 0; i < element->numpads; i++) { for (i = 0; i < element->numpads; i++) {
g_assert (this->pads[i].pad != NULL); g_assert (this->pads[i].pad != NULL);
@ -919,7 +988,7 @@ gst_alsa_sink_loop (GstElement *element)
if (num_bytes == 0) if (num_bytes == 0)
return; return;
} }
if (this->format == SND_PCM_FORMAT_UNKNOWN) { if (this->format == NULL) {
gst_element_error (GST_ELEMENT (this), "alsasink: No caps available"); gst_element_error (GST_ELEMENT (this), "alsasink: No caps available");
} }
} }
@ -930,7 +999,7 @@ sink_restart:
if (avail == -EPIPE) goto sink_restart; if (avail == -EPIPE) goto sink_restart;
if (avail < 0) return; if (avail < 0) return;
if (avail > 0) { if (avail > 0) {
int width = snd_pcm_format_physical_width (this->format); int width = snd_pcm_format_physical_width (this->format->format);
/* Not enough space. We grab data nonetheless and sleep afterwards */ /* Not enough space. We grab data nonetheless and sleep afterwards */
if (avail < this->period_size) { if (avail < this->period_size) {
@ -938,7 +1007,7 @@ sink_restart:
} }
/* check how many bytes we still have in all our bytestreams */ /* check how many bytes we still have in all our bytestreams */
bytes = avail * ( width / 8 ) * (element->numpads == 1 ? this->channels : 1); bytes = avail * ( width / 8 ) * (element->numpads == 1 ? this->format->channels : 1);
for (i = 0; i < element->numpads; i++) { for (i = 0; i < element->numpads; i++) {
g_assert (this->pads[i].pad != NULL); g_assert (this->pads[i].pad != NULL);
do { do {
@ -948,31 +1017,35 @@ sink_restart:
return; return;
bytes = MIN (bytes, num_bytes); bytes = MIN (bytes, num_bytes);
} }
avail = bytes / (width / 8 ) / (element->numpads == 1 ? this->channels : 1); avail = bytes / (width / 8 ) / (element->numpads == 1 ? this->format->channels : 1);
/* FIXME: lotsa stuff can have happened while fetching data. Do we need to check something? */
/* wait until the hw buffer has enough space */ /* wait until the hw buffer has enough space */
while ((avail2 = gst_alsa_update_avail (this)) < avail) { while (gst_element_get_state (element) == GST_STATE_PLAYING && (avail2 = gst_alsa_update_avail (this)) < avail) {
if (avail2 == -EPIPE) goto sink_restart; if (avail2 <= -EPIPE) goto sink_restart;
if (avail < 0) return; 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) if (gst_alsa_pcm_wait (this) == FALSE)
return; return;
} }
if (gst_element_get_state (element) != GST_STATE_PLAYING)
return;
/* FIXME: lotsa stuff can have happened while fetching data. Do we need to check something? */
/* put this data into alsa */ /* put this data into alsa */
if ((copied = gst_alsa_do_mmap (this, &avail)) < 0) if ((copied = gst_alsa_do_mmap (this, &avail)) < 0)
return; return;
/* flush the data */ /* flush the data */
bytes = copied * ( width / 8 ) * (element->numpads == 1 ? this->channels : 1); bytes = copied * ( width / 8 ) * (element->numpads == 1 ? this->format->channels : 1);
for (i = 0; i < element->numpads; i++) for (i = 0; i < element->numpads; i++)
gst_bytestream_flush (this->pads[i].bs, bytes); gst_bytestream_flush (this->pads[i].bs, bytes);
}
/* BUG: we start the stream explicitly, autostart doesn't work correctly (alsa 0.9.0rc7) */ if (snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING && snd_pcm_avail_update (this->handle) == 0) {
if (snd_pcm_state(this->handle) == SND_PCM_STATE_PREPARED && snd_pcm_avail_update (this->handle) == 0) { gst_alsa_start (this);
GST_DEBUG (GST_CAT_PLUGIN_INFO, "Explicitly starting playback");
snd_pcm_start(this->handle);
}
} }
} }
@ -991,16 +1064,22 @@ gst_alsa_src_loop (GstElement *element)
g_return_if_fail (this != NULL); g_return_if_fail (this != NULL);
/* set the caps on all pads */ /* set the caps on all pads */
if (!this->caps_set) { 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"); GST_DEBUG (GST_CAT_NEGOTIATION, "starting caps negotiationgst_alsa_pcm_wait");
caps = gst_alsa_caps (this->format, this->rate, this->channels); caps = gst_alsa_caps (this->format->format, this->format->rate, this->format->channels);
for (i = 0; i < element->numpads; i++) { for (i = 0; i < element->numpads; i++) {
if (gst_pad_try_set_caps (this->pads[i].pad, caps) <= 0) { 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); GST_DEBUG (GST_CAT_NEGOTIATION, "setting caps (%p) in alsasrc (%p) on pad %d failed", caps, this, i);
return; return;
} }
} }
this->caps_set = TRUE;
} }
src_restart: src_restart:
@ -1008,13 +1087,14 @@ src_restart:
while ((avail = gst_alsa_update_avail (this)) < this->period_size) { while ((avail = gst_alsa_update_avail (this)) < this->period_size) {
if (avail == -EPIPE) goto src_restart; if (avail == -EPIPE) goto src_restart;
if (avail < 0) return; if (avail < 0) return;
if (snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING) break;
/* wait */ /* wait */
if (gst_alsa_pcm_wait (this) == FALSE) if (gst_alsa_pcm_wait (this) == FALSE)
return; return;
} }
if (avail > 0) { if (avail > 0) {
int width = snd_pcm_format_physical_width (this->format); int width = snd_pcm_format_physical_width (this->format->format);
int bytes_per_frame = ( width / 8 ) * (element->numpads == 1 ? this->channels : 1); int bytes_per_frame = ( width / 8 ) * (element->numpads == 1 ? this->format->channels : 1);
if ((copied = gst_alsa_do_mmap (this, &avail)) < 0) if ((copied = gst_alsa_do_mmap (this, &avail)) < 0)
return; return;
@ -1043,12 +1123,12 @@ src_restart:
} }
pool = NULL; pool = NULL;
}
/* BUG: we start the stream explicitly, autostart doesn't work correctly (alsa 0.9.0rc7) */ /* 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) { 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"); GST_DEBUG (GST_CAT_PLUGIN_INFO, "Explicitly starting capture");
snd_pcm_start(this->handle); snd_pcm_start(this->handle);
}
} }
} }
@ -1101,11 +1181,17 @@ gst_alsa_sink_check_event (GstAlsa *this, gint pad_nr)
gst_bytestream_get_status (this->pads[pad_nr].bs, &avail, &event); gst_bytestream_get_status (this->pads[pad_nr].bs, &avail, &event);
if (event) { if (event) {
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { switch (GST_EVENT_TYPE (event)) {
gst_element_set_eos (GST_ELEMENT (this)); case GST_EVENT_EOS:
cont = FALSE; gst_element_set_eos (GST_ELEMENT (this));
} else { cont = FALSE;
g_warning ("GstAlsaSink: got an unknown event (Type: %d)", GST_EVENT_TYPE (event)); break;
case GST_EVENT_INTERRUPT:
cont = FALSE;
break;
default:
g_warning ("GstAlsaSink: got an unknown event (Type: %d)", GST_EVENT_TYPE (event));
break;
} }
gst_event_unref (event); gst_event_unref (event);
} else { } else {
@ -1135,7 +1221,11 @@ gst_alsa_open_audio (GstAlsa *this)
GST_FLAG_SET (this, GST_ALSA_OPEN); GST_FLAG_SET (this, GST_ALSA_OPEN);
return TRUE; return TRUE;
} }
/* you must set all hw parameters at once - thx ALSA for not documenting this */ /**
* You must set all hw parameters at once and can't use already set params and
* change them.
* Thx ALSA for not documenting this
*/
static gboolean static gboolean
gst_alsa_set_hw_params (GstAlsa *this) gst_alsa_set_hw_params (GstAlsa *this)
{ {
@ -1145,13 +1235,13 @@ gst_alsa_set_hw_params (GstAlsa *this)
unsigned int count_min, count_max; unsigned int count_min, count_max;
/* whether to use default values when setting params */ /* whether to use default values when setting params */
gboolean def = (this->format == SND_PCM_FORMAT_UNKNOWN); gboolean def = (this->format == NULL);
g_return_val_if_fail (this != NULL, FALSE); g_return_val_if_fail (this != NULL, FALSE);
g_return_val_if_fail (this->handle != NULL, FALSE); g_return_val_if_fail (this->handle != NULL, FALSE);
GST_INFO (GST_CAT_PLUGIN_INFO, "Preparing channel: %s %dHz, %d channels\n", GST_INFO (GST_CAT_PLUGIN_INFO, "Preparing channel: %s %dHz, %d channels\n",
snd_pcm_format_name (this->format), this->rate, this->channels); snd_pcm_format_name (this->format->format), this->format->rate, this->format->channels);
snd_pcm_hw_params_alloca (&hw_params); snd_pcm_hw_params_alloca (&hw_params);
ERROR_CHECK (snd_pcm_hw_params_any (this->handle, hw_params), ERROR_CHECK (snd_pcm_hw_params_any (this->handle, hw_params),
@ -1169,12 +1259,12 @@ gst_alsa_set_hw_params (GstAlsa *this)
ERROR_CHECK (snd_pcm_hw_params_set_access_mask (this->handle, hw_params, mask), ERROR_CHECK (snd_pcm_hw_params_set_access_mask (this->handle, hw_params, mask),
"The Gstreamer ALSA plugin does not support your hardware. Error: %s"); "The Gstreamer ALSA plugin does not support your hardware. Error: %s");
ERROR_CHECK (snd_pcm_hw_params_set_format (this->handle, hw_params, def ? SND_PCM_FORMAT_S16 : this->format), ERROR_CHECK (snd_pcm_hw_params_set_format (this->handle, hw_params, def ? SND_PCM_FORMAT_S16 : this->format->format),
"Sample format (%s) not available: %s", snd_pcm_format_name (def ? SND_PCM_FORMAT_S16 : this->format)); "Sample format (%s) not available: %s", snd_pcm_format_name (def ? SND_PCM_FORMAT_S16 : this->format->format));
ERROR_CHECK (snd_pcm_hw_params_set_channels (this->handle, hw_params, this->channels), ERROR_CHECK (snd_pcm_hw_params_set_channels (this->handle, hw_params, def ? ((GstElement *) this)->numpads : this->format->channels),
"Channels count (%d) not available: %s", this->channels); "Channels count (%d) not available: %s", def ? ((GstElement *) this)->numpads : this->format->channels);
ERROR_CHECK (snd_pcm_hw_params_set_rate (this->handle, hw_params, def ? 44100 : this->rate, 0), ERROR_CHECK (snd_pcm_hw_params_set_rate (this->handle, hw_params, def ? 44100 : this->format->rate, 0),
"error setting rate (%d): %s", def ? 44100 : this->rate); "error setting rate (%d): %s", def ? 44100 : this->format->rate);
if (snd_pcm_hw_params_get_period_size_min (hw_params, &size_min, 0) < 0) size_min = this->period_size; if (snd_pcm_hw_params_get_period_size_min (hw_params, &size_min, 0) < 0) size_min = this->period_size;
if (snd_pcm_hw_params_get_period_size_max (hw_params, &size_max, 0) < 0) size_max = this->period_size; if (snd_pcm_hw_params_get_period_size_max (hw_params, &size_max, 0) < 0) size_max = this->period_size;
@ -1194,6 +1284,11 @@ gst_alsa_set_hw_params (GstAlsa *this)
ERROR_CHECK (snd_pcm_hw_params (this->handle, hw_params), ERROR_CHECK (snd_pcm_hw_params (this->handle, hw_params),
"Could not set hardware parameters: %s"); "Could not set hardware parameters: %s");
/* now get the pcm caps */
GST_ALSA_CAPS_SET (this, GST_ALSA_CAPS_PAUSE, snd_pcm_hw_params_can_pause (hw_params));
GST_ALSA_CAPS_SET (this, GST_ALSA_CAPS_RESUME, snd_pcm_hw_params_can_resume (hw_params));
GST_ALSA_CAPS_SET (this, GST_ALSA_CAPS_SYNC_START, snd_pcm_hw_params_can_sync_start (hw_params));
return TRUE; return TRUE;
} }
static gboolean static gboolean
@ -1211,7 +1306,8 @@ gst_alsa_set_sw_params (GstAlsa *this)
"could not set silence threshold: %s"); "could not set silence threshold: %s");
ERROR_CHECK (snd_pcm_sw_params_set_avail_min (this->handle, sw_params, this->period_size), ERROR_CHECK (snd_pcm_sw_params_set_avail_min (this->handle, sw_params, this->period_size),
"could not set avail min: %s"); "could not set avail min: %s");
ERROR_CHECK (snd_pcm_sw_params_set_start_threshold (this->handle, sw_params, 1), /* we start explicitly */
ERROR_CHECK (snd_pcm_sw_params_set_start_threshold (this->handle, sw_params, this->period_size * this->period_count + 1),
"could not set start mode: %s"); "could not set start mode: %s");
ERROR_CHECK (snd_pcm_sw_params_set_stop_threshold (this->handle, sw_params, this->period_size * this->period_count), ERROR_CHECK (snd_pcm_sw_params_set_stop_threshold (this->handle, sw_params, this->period_size * this->period_count),
"could not set stop mode: %s"); "could not set stop mode: %s");

View file

@ -63,8 +63,23 @@ typedef GstAlsaClass GstAlsaSrcClass;
enum { enum {
GST_ALSA_OPEN = GST_ELEMENT_FLAG_LAST, GST_ALSA_OPEN = GST_ELEMENT_FLAG_LAST,
GST_ALSA_RUNNING, GST_ALSA_RUNNING,
GST_ALSA_CAPS_NEGO,
GST_ALSA_FLAG_LAST = GST_ELEMENT_FLAG_LAST + 3, GST_ALSA_FLAG_LAST = GST_ELEMENT_FLAG_LAST + 3,
}; };
typedef enum {
GST_ALSA_CAPS_PAUSE = 0,
GST_ALSA_CAPS_RESUME,
GST_ALSA_CAPS_SYNC_START
/* add more */
} GstAlsaPcmCaps;
#define GST_ALSA_CAPS_IS_SET(obj, flag) (GST_ALSA (obj)->pcm_caps & (1<<(flag)))
#define GST_ALSA_CAPS_SET(obj, flag, set) G_STMT_START{ \
if (set) { \
(GST_ALSA (obj)->pcm_caps |= (1<<(flag))); \
} else { \
(GST_ALSA (obj)->pcm_caps &= ~(1<<(flag))); \
} \
}G_STMT_END
typedef struct { typedef struct {
GstPad *pad; GstPad *pad;
@ -72,7 +87,11 @@ typedef struct {
guint8 *data; guint8 *data;
guint8 offset; guint8 offset;
} GstAlsaPad; } GstAlsaPad;
typedef struct {
snd_pcm_format_t format;
guint rate;
gint channels;
} GstAlsaFormat;
struct _GstAlsa { struct _GstAlsa {
GstElement parent; GstElement parent;
@ -82,12 +101,10 @@ struct _GstAlsa {
gchar *device; gchar *device;
snd_pcm_stream_t stream; snd_pcm_stream_t stream;
snd_pcm_t *handle; snd_pcm_t *handle;
guint pcm_caps;
snd_output_t *out; snd_output_t *out;
snd_pcm_format_t format; GstAlsaFormat *format; /* NULL if undefined */
guint rate;
gint channels;
gboolean caps_set;
/* latency / performance parameters */ /* latency / performance parameters */
snd_pcm_uframes_t period_size; snd_pcm_uframes_t period_size;