From f0154849b0bfafa7bb8cd690389afc824be9e90e Mon Sep 17 00:00:00 2001 From: Julien Moutte Date: Fri, 29 Feb 2008 18:44:36 +0000 Subject: [PATCH] ext/alsa/gstalsa.c: Probe for IEC958 pcm to detect if we can do SPDIF output. Original commit message from CVS: 2008-02-29 Julien Moutte * 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 | 13 ++++ ext/alsa/gstalsa.c | 53 +++++++++++++++ ext/alsa/gstalsa.h | 2 + ext/alsa/gstalsasink.c | 101 +++++++++++++++++++++++++---- ext/alsa/gstalsasink.h | 2 + gst-libs/gst/audio/gstringbuffer.c | 45 ++++++++++--- gst-libs/gst/audio/gstringbuffer.h | 10 ++- 7 files changed, 204 insertions(+), 22 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3567a6725b..4ba771a337 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2008-02-29 Julien Moutte + + * 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 * gst-libs/gst/interfaces/mixer.c: (GST_MIXER_MESSAGE_HAS_TYPE), diff --git a/ext/alsa/gstalsa.c b/ext/alsa/gstalsa.c index 194e64d46b..9fb16a498e 100644 --- a/ext/alsa/gstalsa.c +++ b/ext/alsa/gstalsa.c @@ -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; diff --git a/ext/alsa/gstalsa.h b/ext/alsa/gstalsa.h index b2e1e9862d..e7af889f8f 100644 --- a/ext/alsa/gstalsa.h +++ b/ext/alsa/gstalsa.h @@ -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); diff --git a/ext/alsa/gstalsasink.c b/ext/alsa/gstalsasink.c index b4e3413be1..1e90108cc7 100644 --- a/ext/alsa/gstalsasink.c +++ b/ext/alsa/gstalsasink.c @@ -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 (¶ms); - 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: { diff --git a/ext/alsa/gstalsasink.h b/ext/alsa/gstalsasink.h index 4218386d4a..902dbf77e5 100644 --- a/ext/alsa/gstalsasink.h +++ b/ext/alsa/gstalsasink.h @@ -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; diff --git a/gst-libs/gst/audio/gstringbuffer.c b/gst-libs/gst/audio/gstringbuffer.c index 7effd7fb6b..da0479b06e 100644 --- a/gst-libs/gst/audio/gstringbuffer.c +++ b/gst-libs/gst/audio/gstringbuffer.c @@ -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; } diff --git a/gst-libs/gst/audio/gstringbuffer.h b/gst-libs/gst/audio/gstringbuffer.h index 6729cdb784..a998de0341 100644 --- a/gst-libs/gst/audio/gstringbuffer.h +++ b/gst-libs/gst/audio/gstringbuffer.h @@ -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; /**