ALSA cleanup step 3:
authorBenjamin Otte <otte@gnome.org>
Tue, 18 Mar 2003 22:07:56 +0000 (22:07 +0000)
committerBenjamin Otte <otte@gnome.org>
Tue, 18 Mar 2003 22:07:56 +0000 (22:07 +0000)
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)

ext/alsa/gstalsa.c
ext/alsa/gstalsa.h

index fe88c03b6fd3a071143e21d70f0c9c52f9f671c7..3cfa2b0f0a3894adc76f08ee1c4573cd43f8662d 100644 (file)
@@ -58,7 +58,7 @@ static GstElementDetails gst_alsa_sink_details = {
   "Output to a sound card via ALSA",
   VERSION,
   "Thomas Nyberg <thomas@codefactory.se>, "
-  "Andy Wingo <apwingo@eos.ncsu.edu>"
+  "Andy Wingo <apwingo@eos.ncsu.edu>"
   "Benjamin Otte <in7y118@public.uni-hamburg.de>",
   "(C) 2001-2003"
 };
@@ -71,7 +71,7 @@ static GstElementDetails gst_alsa_src_details = {
   "Read from a sound card via ALSA",
   VERSION,
   "Thomas Nyberg <thomas@codefactory.se>, "
-  "Andy Wingo <apwingo@eos.ncsu.edu>"
+  "Andy Wingo <apwingo@eos.ncsu.edu>"
   "Benjamin Otte <in7y118@public.uni-hamburg.de>",
   "(C) 2001-2003"
 };
@@ -279,14 +279,11 @@ gst_alsa_init (GstAlsa *this)
   gint i;
   /* init values */
   this->handle = NULL;
-  this->channels = 1;
-  this->caps_set = FALSE;
 
   GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED);
 
   if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) {
     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);
     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);
@@ -294,10 +291,8 @@ gst_alsa_init (GstAlsa *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
        be changed later through the gobject set property function. */
-    this->rate = 44100;
   } else if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK) {
     this->stream = SND_PCM_STREAM_PLAYBACK;
-    this->format = SND_PCM_FORMAT_UNKNOWN; /* we don't know until caps are set */
     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].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:
     /* setting this property only makes sense on sources */
     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;
   case ARG_CHANNELS:
     /* setting this property only makes sense on sources */
     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;
   case ARG_RATE:
     /* setting this property only makes sense on sources */
     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;
   case ARG_PERIODCOUNT:
     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);
     break;
   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;
   case ARG_CHANNELS:
-    g_value_set_int (value, this->channels);
+    g_value_set_int (value, this->format ? this->format->channels : 2);
     break;
   case ARG_RATE:
-    g_value_set_int (value, this->rate);
+    g_value_set_int (value, this->format ? this->format->rate : 44100);
     break;
   case ARG_PERIODCOUNT:
     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 (!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) {
     /* 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. */
@@ -516,15 +508,19 @@ found_channel:
   return this->pads[channel].pad;
 }
 
-/* gets the matching alsa format or SND_PCM_FORMAT_UNKNOWN if none matches */
-static snd_pcm_format_t
+/* gets the matching alsa format or NULL if none matches */
+static GstAlsaFormat *
 gst_alsa_get_format (GstCaps *caps)
 {
   const gchar *format_name;
+  GstAlsaFormat *ret;
+
+  if (!(ret = g_new (GstAlsaFormat, 1)))
+    return NULL;
 
   /* we have to differentiate between int and float formats */
   if (!gst_caps_get_string (caps, "format", &format_name))
-    return SND_PCM_FORMAT_UNKNOWN;
+    goto error;
 
   if (strncmp (format_name, "int", 3) == 0) {
     gboolean sign;
@@ -537,36 +533,40 @@ gst_alsa_get_format (GstCaps *caps)
                        "law", &law,
                        "signed", &sign,
                        NULL))
-        return SND_PCM_FORMAT_UNKNOWN;
+        goto error;
        
     /* extract endianness if needed */
     if (width > 8) {
       if (!gst_caps_get (caps,
                          "endianness", &endianness,
                          NULL))
-        return SND_PCM_FORMAT_UNKNOWN;      
-    } else {
+        goto error;
+      } else {
       endianness = G_BYTE_ORDER;
     }
     
     /* find corresponding alsa format */
     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: 
         if (width == 8 && depth == 8 && sign == FALSE) {
-          return SND_PCM_FORMAT_MU_LAW;
+          ret->format = SND_PCM_FORMAT_MU_LAW;
+         break;
         } else {
-          return SND_PCM_FORMAT_UNKNOWN;
+          goto error;
         }
       case 2: 
         if (width == 8 && depth == 8 && sign == FALSE) {
-          return SND_PCM_FORMAT_A_LAW;
+          ret->format = SND_PCM_FORMAT_A_LAW;
+         break;
         } 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) {
     gchar *layout;
     gfloat intercept, slope;
@@ -576,27 +576,54 @@ gst_alsa_get_format (GstCaps *caps)
                              "intercept", &intercept,
                              "slope", &slope,
                              NULL))
-      return SND_PCM_FORMAT_UNKNOWN;
+      goto error;
     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 (G_BYTE_ORDER == G_LITTLE_ENDIAN) return SND_PCM_FORMAT_FLOAT_LE;
-      if (G_BYTE_ORDER == G_BIG_ENDIAN) return SND_PCM_FORMAT_FLOAT_BE;
-      return SND_PCM_FORMAT_FLOAT;
+      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) return SND_PCM_FORMAT_FLOAT64_LE;
-      if (G_BYTE_ORDER == G_BIG_ENDIAN) return SND_PCM_FORMAT_FLOAT64_BE;
-      return SND_PCM_FORMAT_FLOAT64;
+      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 {
-      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 */
 static GstProps *
 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) {
         add_channels (props, rate, channels);
         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)
 {
   GstAlsa *this;
-  snd_pcm_format_t format;
-  gint rate, channels;
+  GstAlsaFormat *format;
 
   g_return_val_if_fail (caps != 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;
 
     format = gst_alsa_get_format (caps);
-    GST_DEBUG (GST_CAT_CAPS, "found format %s\n", snd_pcm_format_name (format));
-    /* FIXME: allow changing the format here, even by retrying caps on other pads */
-    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) {
-      this->channels = channels;
-    } else {
-      this->channels++;
+    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_alsa_formats_match (this->format, format)) {
+      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_RUNNING)) gst_alsa_stop_audio (this);
-    if (GST_FLAG_IS_SET (this, GST_ALSA_OPEN))    gst_alsa_close_audio (this);
+    if (!GST_FLAG_IS_SET (this, GST_ALSA_CAPS_NEGO)) {
+      gint i;
 
-    if (!gst_alsa_open_audio (this))  return GST_PAD_LINK_REFUSED;
-    if (!gst_alsa_start_audio (this)) return GST_PAD_LINK_REFUSED;
+      GST_FLAG_SET (this, GST_ALSA_CAPS_NEGO);
+
+      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;
   }
@@ -763,8 +798,8 @@ gst_alsa_src_get_buffer_pool (GstPad *pad)
 
   GstAlsa *this = GST_ALSA (gst_pad_get_parent (pad));
 
-  width = snd_pcm_format_physical_width (this->format);
-  bytes_per_frame = ( width / 8 ) * (GST_ELEMENT (this)->numpads == 1 ? this->channels : 1);
+  width = snd_pcm_format_physical_width (this->format->format);
+  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,
      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)) {
   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))
         return GST_STATE_FAILURE;
     break;
   case GST_STATE_READY_TO_PAUSED:
-    break;
-  case GST_STATE_PAUSED_TO_PLAYING:
-    if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) == FALSE)
+    if (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING))
       if (!gst_alsa_start_audio (this))
         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:
+    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))
       gst_alsa_drain_audio (this);
     break;
-  case GST_STATE_PAUSED_TO_READY:
-    break;
   case GST_STATE_READY_TO_NULL:
     if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN))
       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_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 = 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) {
     /* 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].first = i * width;
-      areas[i].step = this->channels * width;
+      areas[i].step = this->format->channels * width;
     }
   } else {
     /* 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].first = 0;
       areas[i].step = width;
@@ -849,7 +891,7 @@ gst_alsa_do_mmap (GstAlsa *this, snd_pcm_sframes_t *avail)
     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);
     g_warning ("gstalsa: data copy failed: %s", snd_strerror (err));
     return -1;
@@ -875,7 +917,6 @@ gst_alsa_update_avail (GstAlsa *this)
   }
   return avail;
 }
-
 /* returns TRUE, if the loop should go on */
 inline static gboolean
 gst_alsa_pcm_wait (GstAlsa *this)
@@ -898,6 +939,34 @@ gst_alsa_pcm_wait (GstAlsa *this)
   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
 gst_alsa_sink_loop (GstElement *element)
 {
@@ -909,7 +978,7 @@ gst_alsa_sink_loop (GstElement *element)
   g_return_if_fail (this != NULL);
 
   /* 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");
     for (i = 0; i < element->numpads; i++) {
       g_assert (this->pads[i].pad != NULL);
@@ -919,7 +988,7 @@ gst_alsa_sink_loop (GstElement *element)
       if (num_bytes == 0)
         return;
     }
-    if (this->format == SND_PCM_FORMAT_UNKNOWN) {
+    if (this->format == NULL) {
       gst_element_error (GST_ELEMENT (this), "alsasink: No caps available");
     }
   }
@@ -930,7 +999,7 @@ sink_restart:
   if (avail == -EPIPE) goto sink_restart;
   if (avail < 0) return;
   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 */
     if (avail < this->period_size) {
@@ -938,7 +1007,7 @@ sink_restart:
     }
     
     /* 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++) {
       g_assert (this->pads[i].pad != NULL);
       do {
@@ -948,31 +1017,35 @@ sink_restart:
         return;
       bytes = MIN (bytes, num_bytes);
     }
-    avail = bytes / (width / 8 ) / (element->numpads == 1 ? this->channels : 1);
-
-    /* FIXME: lotsa stuff can have happened while fetching data. Do we need to check something? */
+    avail = bytes / (width / 8 ) / (element->numpads == 1 ? this->format->channels : 1);
 
     /* wait until the hw buffer has enough space */
-    while ((avail2 = gst_alsa_update_avail (this)) < avail) {
-      if (avail2 == -EPIPE) goto sink_restart;
-      if (avail < 0) return;
+    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;
     }
+
+    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 */
     if ((copied = gst_alsa_do_mmap (this, &avail)) < 0)
       return;
 
     /* 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++)
       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_PREPARED && snd_pcm_avail_update (this->handle) == 0) {
-      GST_DEBUG (GST_CAT_PLUGIN_INFO, "Explicitly starting playback");
-      snd_pcm_start(this->handle);
-    }
+  if (snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING && snd_pcm_avail_update (this->handle) == 0) {
+    gst_alsa_start (this);
   }
 
 }
@@ -991,16 +1064,22 @@ gst_alsa_src_loop (GstElement *element)
   g_return_if_fail (this != NULL);
 
   /* 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");
-    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++) {
       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;
       }
     }
-    this->caps_set = TRUE;
   }
 
 src_restart:
@@ -1008,13 +1087,14 @@ 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);
-    int bytes_per_frame = ( width / 8 ) * (element->numpads == 1 ? this->channels : 1);
+    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 = gst_alsa_do_mmap (this, &avail)) < 0)
       return;
       
@@ -1043,12 +1123,12 @@ src_restart:
     }
 
     pool = NULL;
+  }
 
-    /* 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);
-    }
+  /* 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);
   }
 }
 
@@ -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);
 
   if (event) {
-    if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
-      gst_element_set_eos (GST_ELEMENT (this));
-      cont = FALSE;
-    } else {
-      g_warning ("GstAlsaSink: got an unknown event (Type: %d)", GST_EVENT_TYPE (event));
+    switch (GST_EVENT_TYPE (event)) {
+      case GST_EVENT_EOS:
+        gst_element_set_eos (GST_ELEMENT (this));
+        cont = FALSE;
+        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);
   } else {
@@ -1135,7 +1221,11 @@ gst_alsa_open_audio (GstAlsa *this)
   GST_FLAG_SET (this, GST_ALSA_OPEN);
   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
 gst_alsa_set_hw_params (GstAlsa *this)
 {
@@ -1145,13 +1235,13 @@ gst_alsa_set_hw_params (GstAlsa *this)
   unsigned int count_min, count_max;
   
   /* 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->handle != NULL, FALSE);
 
   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);
   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),
                "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),
-               "Sample format (%s) not available: %s", snd_pcm_format_name (def ? SND_PCM_FORMAT_S16 : this->format));
-  ERROR_CHECK (snd_pcm_hw_params_set_channels (this->handle, hw_params, this->channels),
-               "Channels count (%d) not available: %s", this->channels);
-  ERROR_CHECK (snd_pcm_hw_params_set_rate (this->handle, hw_params, def ? 44100 : this->rate, 0),
-                 "error setting rate (%d): %s", def ? 44100 : this->rate);
+  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->format));
+  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", def ? ((GstElement *) this)->numpads : this->format->channels);
+  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->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_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),
                "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;
 }
 static gboolean
@@ -1211,7 +1306,8 @@ gst_alsa_set_sw_params (GstAlsa *this)
                "could not set silence threshold: %s");
   ERROR_CHECK (snd_pcm_sw_params_set_avail_min (this->handle, sw_params, this->period_size),
                "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");
   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");
index d9c5bbe8054348432dae57ee01efc1aa7a2b3283..46e7c2346a8806a7e07a233cccfe3f3f5e4c3d2a 100644 (file)
@@ -63,8 +63,23 @@ typedef GstAlsaClass GstAlsaSrcClass;
 enum {
   GST_ALSA_OPEN = GST_ELEMENT_FLAG_LAST,
   GST_ALSA_RUNNING,
+  GST_ALSA_CAPS_NEGO,
   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 {
   GstPad *pad;
@@ -72,7 +87,11 @@ typedef struct {
   guint8 *data;
   guint8 offset;
 } GstAlsaPad;
-
+typedef struct {
+  snd_pcm_format_t format;
+  guint rate;
+  gint channels;  
+} GstAlsaFormat;
 struct _GstAlsa {
   GstElement parent;
 
@@ -82,12 +101,10 @@ struct _GstAlsa {
   gchar *device;
   snd_pcm_stream_t stream;
   snd_pcm_t *handle;
+  guint pcm_caps;
   snd_output_t *out;
 
-  snd_pcm_format_t format;
-  guint rate;
-  gint channels;
-  gboolean caps_set;
+  GstAlsaFormat *format; /* NULL if undefined */
 
   /* latency / performance parameters */
   snd_pcm_uframes_t period_size;