ext/alsa/gstalsa.c: Probe for IEC958 pcm to detect if we can do SPDIF output.
authorJulien Moutte <julien@moutte.net>
Fri, 29 Feb 2008 18:44:36 +0000 (18:44 +0000)
committerJulien Moutte <julien@moutte.net>
Fri, 29 Feb 2008 18:44:36 +0000 (18:44 +0000)
Original commit message from CVS:
2008-02-29  Julien Moutte  <julien@fluendo.com>

* ext/alsa/gstalsa.c: (gst_alsa_open_iec958_pcm),
(gst_alsa_probe_supported_formats): Probe for IEC958 pcm to
detect
if we can do SPDIF output.
* ext/alsa/gstalsa.h:
* ext/alsa/gstalsasink.c: (set_hwparams), (alsasink_parse_spec),
(gst_alsasink_prepare), (gst_alsasink_close),
(gst_alsasink_write):
* ext/alsa/gstalsasink.h: Initial support for SPDIF.
* gst-libs/gst/audio/gstringbuffer.c:
(gst_ring_buffer_parse_caps):
* gst-libs/gst/audio/gstringbuffer.h: Add non linear buffer
types
to support AC3, EC3 and IEC958 buffers.

ChangeLog
ext/alsa/gstalsa.c
ext/alsa/gstalsa.h
ext/alsa/gstalsasink.c
ext/alsa/gstalsasink.h
gst-libs/gst/audio/gstringbuffer.c
gst-libs/gst/audio/gstringbuffer.h

index 3567a67..4ba771a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2008-02-29  Julien Moutte  <julien@fluendo.com>
+
+        * ext/alsa/gstalsa.c: (gst_alsa_open_iec958_pcm),
+        (gst_alsa_probe_supported_formats): Probe for IEC958 pcm to detect
+        if we can do SPDIF output.
+        * ext/alsa/gstalsa.h:
+        * ext/alsa/gstalsasink.c: (set_hwparams), (alsasink_parse_spec),
+        (gst_alsasink_prepare), (gst_alsasink_close), (gst_alsasink_write):
+        * ext/alsa/gstalsasink.h: Initial support for SPDIF.
+        * gst-libs/gst/audio/gstringbuffer.c: (gst_ring_buffer_parse_caps):
+        * gst-libs/gst/audio/gstringbuffer.h: Add non linear buffer types
+        to support AC3, EC3 and IEC958 buffers.
+
 2008-02-29  Tim-Philipp Müller  <tim at centricular dot net>
 
        * gst-libs/gst/interfaces/mixer.c: (GST_MIXER_MESSAGE_HAS_TYPE),
index 194e64d..9fb16a4 100644 (file)
@@ -360,6 +360,45 @@ max_chan_error:
   }
 }
 
+snd_pcm_t *
+gst_alsa_open_iec958_pcm (GstObject * obj)
+{
+  char *iec958_pcm_name = NULL;
+  snd_pcm_t *pcm = NULL;
+  int res;
+  char devstr[256];             /* Storage for local 'default' device string */
+
+  /*
+   * Try and open our default iec958 device. Fall back to searching on card x
+   * if this fails, which should only happen on older alsa setups
+   */
+
+  /* The string will be one of these:
+   * SPDIF_CON: Non-audio flag not set:
+   *    spdif:{AES0 0x0 AES1 0x82 AES2 0x0 AES3 0x2}
+   * SPDIF_CON: Non-audio flag set:
+   *    spdif:{AES0 0x2 AES1 0x82 AES2 0x0 AES3 0x2}
+   */
+  sprintf (devstr,
+      "iec958:{AES0 0x%02x AES1 0x%02x AES2 0x%02x AES3 0x%02x}",
+      IEC958_AES0_CON_EMPHASIS_NONE | IEC958_AES0_NONAUDIO,
+      IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
+      0, IEC958_AES3_CON_FS_48000);
+
+  GST_DEBUG_OBJECT (obj, "Generated device string \"%s\"", devstr);
+  iec958_pcm_name = devstr;
+
+  res = snd_pcm_open (&pcm, iec958_pcm_name, SND_PCM_STREAM_PLAYBACK, 0);
+  if (G_UNLIKELY (res < 0)) {
+    GST_DEBUG_OBJECT (obj, "failed opening IEC958 device: %s",
+        snd_strerror (res));
+    pcm = NULL;
+  }
+
+  return pcm;
+}
+
+
 /*
  * gst_alsa_probe_supported_formats:
  *
@@ -373,6 +412,7 @@ gst_alsa_probe_supported_formats (GstObject * obj, snd_pcm_t * handle,
     const GstCaps * template_caps)
 {
   snd_pcm_hw_params_t *hw_params;
+  snd_pcm_stream_t stream_type;
   GstCaps *caps;
   gint err;
 
@@ -380,6 +420,8 @@ gst_alsa_probe_supported_formats (GstObject * obj, snd_pcm_t * handle,
   if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0)
     goto error;
 
+  stream_type = snd_pcm_stream (handle);
+
   caps = gst_caps_copy (template_caps);
 
   if (!(caps = gst_alsa_detect_formats (obj, hw_params, caps)))
@@ -391,6 +433,17 @@ gst_alsa_probe_supported_formats (GstObject * obj, snd_pcm_t * handle,
   if (!(caps = gst_alsa_detect_channels (obj, hw_params, caps)))
     goto subroutine_error;
 
+  /* Try opening IEC958 device to see if we can support that format (playback
+   * only for now but we could add SPDIF capture later) */
+  if (stream_type == SND_PCM_STREAM_PLAYBACK) {
+    snd_pcm_t *pcm = gst_alsa_open_iec958_pcm (obj);
+
+    if (G_LIKELY (pcm)) {
+      gst_caps_append (caps, gst_caps_new_simple ("audio/x-iec958", NULL));
+      snd_pcm_close (pcm);
+    }
+  }
+
   snd_pcm_hw_params_free (hw_params);
   return caps;
 
index b2e1e98..e7af889 100644 (file)
@@ -41,6 +41,8 @@
 GST_DEBUG_CATEGORY_EXTERN (alsa_debug);
 #define GST_CAT_DEFAULT alsa_debug
 
+snd_pcm_t * gst_alsa_open_iec958_pcm (GstObject * obj);
+
 GstCaps * gst_alsa_probe_supported_formats (GstObject      * obj,
                                             snd_pcm_t      * handle,
                                             const GstCaps  * template_caps);
index b4e3413..1e90108 100644 (file)
@@ -67,6 +67,8 @@ GST_ELEMENT_DETAILS ("Audio sink (ALSA)",
 
 #define DEFAULT_DEVICE         "default"
 #define DEFAULT_DEVICE_NAME    ""
+#define SPDIF_PERIOD_SIZE 1536
+#define SPDIF_BUFFER_SIZE 15360
 
 enum
 {
@@ -141,7 +143,8 @@ static GstStaticPadTemplate alsasink_sink_factory =
         "signed = (boolean) { TRUE, FALSE }, "
         "width = (int) 8, "
         "depth = (int) 8, "
-        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]")
+        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ];"
+        "audio/x-iec958")
     );
 
 static void
@@ -335,8 +338,9 @@ set_hwparams (GstAlsaSink * alsa)
 
   snd_pcm_hw_params_malloc (&params);
 
-  GST_DEBUG_OBJECT (alsa, "Negotiating to %d channels @ %d Hz (format = %s)",
-      alsa->channels, alsa->rate, snd_pcm_format_name (alsa->format));
+  GST_DEBUG_OBJECT (alsa, "Negotiating to %d channels @ %d Hz (format = %s) "
+      "SPDIF (%d)", alsa->channels, alsa->rate,
+      snd_pcm_format_name (alsa->format), alsa->iec958);
 
   /* start with requested values, if we cannot configure alsa for those values,
    * we set these values to -1, which will leave the default alsa values */
@@ -350,6 +354,16 @@ retry:
   CHECK (snd_pcm_hw_params_set_access (alsa->handle, params, alsa->access),
       wrong_access);
   /* set the sample format */
+  if (alsa->iec958) {
+    /* Try to use big endian first else fallback to le and swap bytes */
+    if (snd_pcm_hw_params_set_format (alsa->handle, params, alsa->format) < 0) {
+      alsa->format = SND_PCM_FORMAT_S16_LE;
+      alsa->need_swap = TRUE;
+      GST_DEBUG_OBJECT (alsa, "falling back to little endian with swapping");
+    } else {
+      alsa->need_swap = FALSE;
+    }
+  }
   CHECK (snd_pcm_hw_params_set_format (alsa->handle, params, alsa->format),
       no_sample_format);
   /* set the count of channels */
@@ -386,7 +400,7 @@ retry:
 
   /* now try to configure the buffer time and period time, if one
    * of those fail, we fall back to the defaults and emit a warning. */
-  if (buffer_time != -1) {
+  if (buffer_time != -1 && !alsa->iec958) {
     /* set the buffer time */
     if ((err = snd_pcm_hw_params_set_buffer_time_near (alsa->handle, params,
                 &buffer_time, &dir)) < 0) {
@@ -399,7 +413,7 @@ retry:
     }
     GST_DEBUG_OBJECT (alsa, "buffer time %u", buffer_time);
   }
-  if (period_time != -1) {
+  if (period_time != -1 && !alsa->iec958) {
     /* set the period time */
     if ((err = snd_pcm_hw_params_set_period_time_near (alsa->handle, params,
                 &period_time, &dir)) < 0) {
@@ -413,6 +427,17 @@ retry:
     GST_DEBUG_OBJECT (alsa, "period time %u", period_time);
   }
 
+  /* Set buffer size and period size manually for SPDIF */
+  if (G_UNLIKELY (alsa->iec958)) {
+    snd_pcm_uframes_t buffer_size = SPDIF_BUFFER_SIZE;
+    snd_pcm_uframes_t period_size = SPDIF_PERIOD_SIZE;
+
+    CHECK (snd_pcm_hw_params_set_buffer_size_near (alsa->handle, params,
+            &buffer_size), buffer_size);
+    CHECK (snd_pcm_hw_params_set_period_size_near (alsa->handle, params,
+            &period_size, NULL), period_size);
+  }
+
   /* write the parameters to device */
   CHECK (snd_pcm_hw_params (alsa->handle, params), set_hw_params);
 
@@ -584,6 +609,9 @@ set_sw_params:
 static gboolean
 alsasink_parse_spec (GstAlsaSink * alsa, GstRingBufferSpec * spec)
 {
+  /* Initialize our boolean */
+  alsa->iec958 = FALSE;
+
   switch (spec->type) {
     case GST_BUFTYPE_LINEAR:
       GST_DEBUG_OBJECT (alsa,
@@ -617,6 +645,13 @@ alsasink_parse_spec (GstAlsaSink * alsa, GstRingBufferSpec * spec)
     case GST_BUFTYPE_MU_LAW:
       alsa->format = SND_PCM_FORMAT_MU_LAW;
       break;
+    case GST_BUFTYPE_NONLINEAR:
+      alsa->format = SND_PCM_FORMAT_S16_BE;
+      if (G_LIKELY (spec->format == GST_IEC958))
+        alsa->iec958 = TRUE;
+      else
+        goto error;
+      break;
     default:
       goto error;
 
@@ -676,6 +711,14 @@ gst_alsasink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
 
   alsa = GST_ALSA_SINK (asink);
 
+  if (spec->format == GST_IEC958) {
+    snd_pcm_close (alsa->handle);
+    alsa->handle = gst_alsa_open_iec958_pcm (GST_OBJECT (alsa));
+    if (G_UNLIKELY (!alsa->handle)) {
+      goto no_iec958;
+    }
+  }
+
   if (!alsasink_parse_spec (alsa, spec))
     goto spec_parse;
 
@@ -688,9 +731,31 @@ gst_alsasink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
   spec->segsize = alsa->period_size * spec->bytes_per_sample;
   spec->segtotal = alsa->buffer_size / alsa->period_size;
 
+  {
+    snd_output_t *out_buf = NULL;
+    char *msg = NULL;
+
+    snd_output_buffer_open (&out_buf);
+    snd_pcm_dump_hw_setup (alsa->handle, out_buf);
+    snd_output_buffer_string (out_buf, &msg);
+    GST_DEBUG_OBJECT (alsa, "Hardware setup: \n%s", msg);
+    snd_output_close (out_buf);
+    snd_output_buffer_open (&out_buf);
+    snd_pcm_dump_sw_setup (alsa->handle, out_buf);
+    snd_output_buffer_string (out_buf, &msg);
+    GST_DEBUG_OBJECT (alsa, "Software setup: \n%s", msg);
+    snd_output_close (out_buf);
+  }
+
   return TRUE;
 
   /* ERRORS */
+no_iec958:
+  {
+    GST_ELEMENT_ERROR (alsa, RESOURCE, OPEN_WRITE, (NULL),
+        ("Could not open IEC958 (SPDIF) device for playback"));
+    return FALSE;
+  }
 spec_parse:
   {
     GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL),
@@ -760,8 +825,10 @@ gst_alsasink_close (GstAudioSink * asink)
   GstAlsaSink *alsa = GST_ALSA_SINK (asink);
   gint err;
 
-  CHECK (snd_pcm_close (alsa->handle), close_error);
-  alsa->handle = NULL;
+  if (alsa->handle) {
+    CHECK (snd_pcm_close (alsa->handle), close_error);
+    alsa->handle = NULL;
+  }
   gst_caps_replace (&alsa->cached_caps, NULL);
 
   return TRUE;
@@ -813,18 +880,28 @@ gst_alsasink_write (GstAudioSink * asink, gpointer data, guint length)
   GstAlsaSink *alsa;
   gint err;
   gint cptr;
-  gint16 *ptr;
+  gint16 *ptr = data;
 
   alsa = GST_ALSA_SINK (asink);
 
+  if (alsa->iec958 && alsa->need_swap) {
+    guint i;
+
+    GST_DEBUG_OBJECT (asink, "swapping bytes");
+    for (i = 0; i < length; i += 2) {
+      ptr[i / 2] = GUINT16_SWAP_LE_BE (ptr[i / 2]);
+    }
+  }
+
+  GST_LOG_OBJECT (asink, "received audio samples buffer of %u bytes", length);
+
   cptr = length / alsa->bytes_per_sample;
-  ptr = data;
 
   GST_ALSA_SINK_LOCK (asink);
   while (cptr > 0) {
     err = snd_pcm_writei (alsa->handle, ptr, cptr);
 
-    GST_DEBUG_OBJECT (asink, "written %d result %d", cptr, err);
+    GST_DEBUG_OBJECT (asink, "written %d frames out of %d", err, cptr);
     if (err < 0) {
       GST_DEBUG_OBJECT (asink, "Write error: %s", snd_strerror (err));
       if (err == -EAGAIN) {
@@ -835,12 +912,12 @@ gst_alsasink_write (GstAudioSink * asink, gpointer data, guint length)
       continue;
     }
 
-    ptr += err * alsa->channels;
+    ptr += snd_pcm_frames_to_bytes (alsa->handle, err);
     cptr -= err;
   }
   GST_ALSA_SINK_UNLOCK (asink);
 
-  return length - cptr;
+  return length - (cptr * alsa->bytes_per_sample);
 
 write_error:
   {
index 4218386..902dbf7 100644 (file)
@@ -62,6 +62,8 @@ struct _GstAlsaSink {
   guint rate;
   guint channels;
   gint bytes_per_sample;
+  gboolean iec958;
+  gboolean need_swap;
 
   guint buffer_time;
   guint period_time;
index 7effd7f..da0479b 100644 (file)
@@ -286,11 +286,6 @@ gst_ring_buffer_parse_caps (GstRingBufferSpec * spec, GstCaps * caps)
   /* we have to differentiate between int and float formats */
   mimetype = gst_structure_get_name (structure);
 
-  /* get rate and channels */
-  if (!(gst_structure_get_int (structure, "rate", &spec->rate) &&
-          gst_structure_get_int (structure, "channels", &spec->channels)))
-    goto parse_error;
-
   if (!strncmp (mimetype, "audio/x-raw-int", 15)) {
     gint endianness;
     const FormatDef *def;
@@ -299,7 +294,9 @@ gst_ring_buffer_parse_caps (GstRingBufferSpec * spec, GstCaps * caps)
     spec->type = GST_BUFTYPE_LINEAR;
 
     /* extract the needed information from the cap */
-    if (!(gst_structure_get_int (structure, "width", &spec->width) &&
+    if (!(gst_structure_get_int (structure, "rate", &spec->rate) &&
+            gst_structure_get_int (structure, "channels", &spec->channels) &&
+            gst_structure_get_int (structure, "width", &spec->width) &&
             gst_structure_get_int (structure, "depth", &spec->depth) &&
             gst_structure_get_boolean (structure, "signed", &spec->sign)))
       goto parse_error;
@@ -333,8 +330,10 @@ gst_ring_buffer_parse_caps (GstRingBufferSpec * spec, GstCaps * caps)
 
     spec->type = GST_BUFTYPE_FLOAT;
 
-    /* get layout */
-    if (!gst_structure_get_int (structure, "width", &spec->width))
+    /* extract the needed information from the cap */
+    if (!(gst_structure_get_int (structure, "rate", &spec->rate) &&
+            gst_structure_get_int (structure, "channels", &spec->channels) &&
+            gst_structure_get_int (structure, "width", &spec->width)))
       goto parse_error;
 
     /* match layout to format wrt to endianness */
@@ -353,6 +352,11 @@ gst_ring_buffer_parse_caps (GstRingBufferSpec * spec, GstCaps * caps)
     /* float silence is all zeros.. */
     memset (spec->silence_sample, 0, 32);
   } else if (!strncmp (mimetype, "audio/x-alaw", 12)) {
+    /* extract the needed information from the cap */
+    if (!(gst_structure_get_int (structure, "rate", &spec->rate) &&
+            gst_structure_get_int (structure, "channels", &spec->channels)))
+      goto parse_error;
+
     spec->type = GST_BUFTYPE_A_LAW;
     spec->format = GST_A_LAW;
     spec->width = 8;
@@ -360,12 +364,37 @@ gst_ring_buffer_parse_caps (GstRingBufferSpec * spec, GstCaps * caps)
     for (i = 0; i < spec->channels; i++)
       spec->silence_sample[i] = 0xd5;
   } else if (!strncmp (mimetype, "audio/x-mulaw", 13)) {
+    /* extract the needed information from the cap */
+    if (!(gst_structure_get_int (structure, "rate", &spec->rate) &&
+            gst_structure_get_int (structure, "channels", &spec->channels)))
+      goto parse_error;
+
     spec->type = GST_BUFTYPE_MU_LAW;
     spec->format = GST_MU_LAW;
     spec->width = 8;
     spec->depth = 8;
     for (i = 0; i < spec->channels; i++)
       spec->silence_sample[i] = 0xff;
+  } else if (!strncmp (mimetype, "audio/x-iec958", 14)) {
+    /* extract the needed information from the cap */
+    if (!(gst_structure_get_int (structure, "rate", &spec->rate)))
+      goto parse_error;
+
+    spec->type = GST_BUFTYPE_NONLINEAR;
+    spec->format = GST_IEC958;
+    spec->width = 16;
+    spec->depth = 16;
+    spec->channels = 2;
+  } else if (!strncmp (mimetype, "audio/x-ac3", 11)) {
+    /* extract the needed information from the cap */
+    if (!(gst_structure_get_int (structure, "rate", &spec->rate)))
+      goto parse_error;
+
+    spec->type = GST_BUFTYPE_NONLINEAR;
+    spec->format = GST_AC3;
+    spec->width = 16;
+    spec->depth = 16;
+    spec->channels = 2;
   } else {
     goto parse_error;
   }
index 6729cdb..a998de0 100644 (file)
@@ -90,6 +90,7 @@ typedef enum {
  * @GST_BUFTYPE_IMA_ADPCM: samples in ima adpcm
  * @GST_BUFTYPE_MPEG: samples in mpeg audio format
  * @GST_BUFTYPE_GSM: samples in gsm format
+ * @GST_BUFTYPE_NONLINEAR: samples in non linear format (ac3, ec3, dts, ...)
  *
  * The format of the samples in the ringbuffer.
  */
@@ -101,7 +102,8 @@ typedef enum
   GST_BUFTYPE_A_LAW,
   GST_BUFTYPE_IMA_ADPCM,
   GST_BUFTYPE_MPEG,
-  GST_BUFTYPE_GSM
+  GST_BUFTYPE_GSM,
+  GST_BUFTYPE_NONLINEAR
 } GstBufferFormatType;
 
 typedef enum
@@ -149,7 +151,11 @@ typedef enum
   GST_A_LAW,
   GST_IMA_ADPCM,
   GST_MPEG,
-  GST_GSM
+  GST_GSM,
+  GST_IEC958,
+  GST_AC3,
+  GST_EC3,
+  GST_DTS
 } GstBufferFormat;
 
 /**