summaryrefslogtreecommitdiffstats
path: root/ext
diff options
context:
space:
mode:
authorMichael Smith <msmith@xiph.org>2006-05-18 12:51:01 +0000
committerMichael Smith <msmith@xiph.org>2006-05-18 12:51:01 +0000
commit855a2f314b13c3cf87d625e9ef327c1639492fc5 (patch)
treee4ad6019ad09e54b8994208e90a2ce8216856ee8 /ext
parent9b3c826eb9e3261eacfd92c5fa6195de5e80c1e8 (diff)
downloadgst-plugins-bad-855a2f314b13c3cf87d625e9ef327c1639492fc5.tar.gz
gst-plugins-bad-855a2f314b13c3cf87d625e9ef327c1639492fc5.tar.bz2
gst-plugins-bad-855a2f314b13c3cf87d625e9ef327c1639492fc5.zip
Add an alsa plugin to output IEC958 frames over S/PDIF
Original commit message from CVS: * configure.ac: * ext/Makefile.am: * ext/alsaspdif/Makefile.am: * ext/alsaspdif/alsaspdifsink.c: (alsaspdifsink_base_init), (alsaspdifsink_class_init), (alsaspdifsink_init), (alsaspdifsink_dispose), (alsaspdifsink_set_property), (alsaspdifsink_get_property), (alsaspdifsink_provide_clock), (alsaspdifsink_get_time), (alsaspdifsink_open), (alsaspdifsink_close), (alsaspdifsink_find_pcm_device), (alsaspdifsink_write_frame), (alsaspdifsink_event), (alsaspdifsink_get_times), (alsaspdifsink_current_delay), (generate_iec958_zero_frame), (alsaspdifsink_render), (ignore_alsa_err), (alsaspdifsink_change_state), (plugin_init): * ext/alsaspdif/alsaspdifsink.h: Add an alsa plugin to output IEC958 frames over S/PDIF
Diffstat (limited to 'ext')
-rw-r--r--ext/Makefile.am7
-rw-r--r--ext/alsaspdif/Makefile.am14
-rw-r--r--ext/alsaspdif/alsaspdifsink.c824
-rw-r--r--ext/alsaspdif/alsaspdifsink.h84
4 files changed, 929 insertions, 0 deletions
diff --git a/ext/Makefile.am b/ext/Makefile.am
index fb15e1ae..0071a248 100644
--- a/ext/Makefile.am
+++ b/ext/Makefile.am
@@ -1,3 +1,9 @@
+if USE_ALSA
+ALSASPDIF_DIR = alsaspdif
+else
+ALSASPDIF_DIR =
+endif
+
if USE_AMRWB
AMRWB_DIR = amrwb
else
@@ -228,6 +234,7 @@ XVID_DIR=
endif
SUBDIRS=\
+ $(ALSASPDIF_DIR) \
$(AMRWB_DIR) \
$(ARTS_DIR) \
$(ARTSC_DIR) \
diff --git a/ext/alsaspdif/Makefile.am b/ext/alsaspdif/Makefile.am
new file mode 100644
index 00000000..77bd3c62
--- /dev/null
+++ b/ext/alsaspdif/Makefile.am
@@ -0,0 +1,14 @@
+plugin_LTLIBRARIES = libgstalsaspdif.la
+
+# sources used to compile this plugin
+libgstalsaspdif_la_SOURCES = alsaspdifsink.c
+
+# flags used to compile this plugin
+# we use the GST_LIBS flags because we might be using plug-in libs
+libgstalsaspdif_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(ALSA_CFLAGS)
+libgstalsaspdif_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_MAJORMINOR) $(GST_BASE_LIBS) $(GST_BASE_LIBS) $(GST_LIBS) $(ALSA_LIBS)
+libgstalsaspdif_la_LDFLAGS = $(GST_BASE_LIBS) $(GST_PLUGIN_LDFLAGS)
+
+# headers we need but don't want installed
+noinst_HEADERS = alsaspdifsink.h
+
diff --git a/ext/alsaspdif/alsaspdifsink.c b/ext/alsaspdif/alsaspdifsink.c
new file mode 100644
index 00000000..2b3198ce
--- /dev/null
+++ b/ext/alsaspdif/alsaspdifsink.c
@@ -0,0 +1,824 @@
+/* Based on a plugin from Martin Soto's Seamless DVD Player.
+ * Copyright (C) 2003, 2004 Martin Soto <martinsoto@users.sourceforge.net>
+ * 2005-6 Michael Smith <msmith@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <unistd.h>
+
+#include <gst/gst.h>
+#include <gst/audio/gstaudioclock.h>
+#include <gst/base/gstbasesink.h>
+
+#include "alsaspdifsink.h"
+
+GST_DEBUG_CATEGORY_STATIC (alsaspdifsink_debug);
+#define GST_CAT_DEFAULT (alsaspdifsink_debug)
+
+/* The magic audio-type we pretend to be for AC3 output */
+#define AC3_CHANNELS 2
+#define AC3_RATE 48000
+#define AC3_BITS 16
+
+/* Define AC3 FORMAT as big endian. Fall back to swapping
+ * on sound devices that don't support it */
+#define AC3_FORMAT_BE SND_PCM_FORMAT_S16_BE
+#define AC3_FORMAT_LE SND_PCM_FORMAT_S16_LE
+
+/* The size in bytes of an IEC958 frame. */
+#define IEC958_FRAME_SIZE 6144
+
+/* The duration of a single IEC958 frame. */
+#define IEC958_FRAME_DURATION (32 * GST_MSECOND)
+
+/* Maximal synchronization difference. Measures will be taken if
+ block timestamps differ from actual playing time in more than this
+ value. */
+#define MAX_SYNC_DIFF (IEC958_FRAME_DURATION * 0.8)
+
+/* Size in bytes of an ALSA PCM frame (4, for this case). */
+#define ALSASPDIFSINK_BYTES_PER_FRAME ((AC3_BITS / 8) * AC3_CHANNELS)
+
+/* Playing time for the given number of ALSA PCM frames. */
+#define ALSASPDIFSINK_TIME_PER_FRAMES(sink, frames) \
+ (((GstClockTime) (frames) * GST_SECOND) / AC3_RATE)
+
+/* Number of ALSA PCM frames for the given playing time. */
+#define ALSASPDIFSINK_FRAMES_PER_TIME(sink, time) \
+ (((GstClockTime) AC3_RATE * (time)) / GST_SECOND)
+
+/* ElementFactory information. */
+static GstElementDetails alsaspdifsink_details = {
+ "S/PDIF ALSA audiosink",
+ "audio/x-iec958",
+ "Feeds audio to S/PDIF interfaces through the ALSA sound driver",
+ "Martin Soto <martinsoto@users.sourceforge.net>\n"
+ "Michael Smith <msmith@fluendo.com>"
+};
+
+/* AlsaSPDIFSink signals and args */
+enum
+{
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_CARD,
+ PROP_DEVICE
+};
+
+static GstStaticPadTemplate alsaspdifsink_sink_factory =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-iec958")
+ );
+
+#define _do_init(bla) \
+ GST_DEBUG_CATEGORY_INIT (alsaspdifsink_debug, "alsaspdifsink", 0, \
+ "ALSA S/PDIF audio sink element");
+
+GST_BOILERPLATE_FULL (AlsaSPDIFSink, alsaspdifsink, GstBaseSink,
+ GST_TYPE_BASE_SINK, _do_init);
+
+static void alsaspdifsink_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void alsaspdifsink_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+static gboolean alsaspdifsink_event (GstBaseSink * bsink, GstEvent * event);
+static GstFlowReturn alsaspdifsink_render (GstBaseSink * bsink,
+ GstBuffer * buf);
+static void alsaspdifsink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
+ GstClockTime * start, GstClockTime * end);
+
+static gboolean alsaspdifsink_open (AlsaSPDIFSink * sink);
+static void alsaspdifsink_close (AlsaSPDIFSink * sink);
+
+static GstClock *alsaspdifsink_provide_clock (GstElement * elem);
+static GstClockTime alsaspdifsink_get_time (GstClock * clock,
+ gpointer user_data);
+static void alsaspdifsink_dispose (GObject * object);
+
+static GstStateChangeReturn alsaspdifsink_change_state (GstElement * element,
+ GstStateChange transition);
+static int alsaspdifsink_find_pcm_device (AlsaSPDIFSink * sink);
+
+/* Alsa error handler to suppress messages from within the ALSA library */
+static void ignore_alsa_err (const char *file, int line, const char *function,
+ int err, const char *fmt, ...);
+
+static void
+alsaspdifsink_base_init (gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+ gst_element_class_set_details (element_class, &alsaspdifsink_details);
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&alsaspdifsink_sink_factory));
+}
+
+static void
+alsaspdifsink_class_init (AlsaSPDIFSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+
+ gobject_class->set_property = alsaspdifsink_set_property;
+ gobject_class->get_property = alsaspdifsink_get_property;
+ gobject_class->dispose = alsaspdifsink_dispose;
+
+ gstelement_class->change_state = alsaspdifsink_change_state;
+ gstelement_class->provide_clock = alsaspdifsink_provide_clock;
+
+ gstbasesink_class->event = alsaspdifsink_event;
+ gstbasesink_class->render = alsaspdifsink_render;
+ gstbasesink_class->get_times = alsaspdifsink_get_times;
+
+#if 0
+ /* We ignore the device property anyway, so don't install it
+ * we don't want the user supplying just any device string for us.
+ * At most we might want a card number and an iec958.%d device name
+ * to attempt */
+ g_object_class_install_property (gobject_class, PROP_DEVICE,
+ g_param_spec_string ("device", "Device",
+ "ALSA device, as defined in an asound configuration file",
+ "default", G_PARAM_READWRITE));
+#endif
+ g_object_class_install_property (gobject_class, PROP_CARD,
+ g_param_spec_int ("card", "Card",
+ "ALSA card number for the SPDIF device to use",
+ 0, G_MAXINT, 0, G_PARAM_READWRITE));
+
+ snd_lib_error_set_handler (ignore_alsa_err);
+}
+
+static void
+alsaspdifsink_init (AlsaSPDIFSink * sink, AlsaSPDIFSinkClass * g_class)
+{
+ /* Create the provided clock. */
+ sink->clock = gst_audio_clock_new ("clock", alsaspdifsink_get_time, sink);
+
+ sink->card = 0;
+ sink->device = g_strdup ("default");
+}
+
+static void
+alsaspdifsink_dispose (GObject * object)
+{
+ AlsaSPDIFSink *sink = ALSASPDIFSINK (object);
+
+ if (sink->clock)
+ gst_object_unref (sink->clock);
+ sink->clock = NULL;
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+alsaspdifsink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ AlsaSPDIFSink *sink;
+
+ sink = ALSASPDIFSINK (object);
+
+ switch (prop_id) {
+ /*
+ case PROP_DEVICE:
+ if(sink->device)
+ g_free(sink->device);
+ sink->device = g_strdup(g_value_get_string(value));
+ break;
+ */
+ case PROP_CARD:
+ sink->card = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+alsaspdifsink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ AlsaSPDIFSink *sink;
+
+ sink = ALSASPDIFSINK (object);
+
+ switch (prop_id) {
+ /*
+ case PROP_DEVICE:
+ g_value_set_string(value, sink->device);
+ break;
+ */
+ case PROP_CARD:
+ g_value_set_int (value, sink->card);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstClock *
+alsaspdifsink_provide_clock (GstElement * elem)
+{
+ AlsaSPDIFSink *sink = ALSASPDIFSINK (elem);
+
+ return GST_CLOCK (gst_object_ref (sink->clock));
+}
+
+static GstClockTime
+alsaspdifsink_get_time (GstClock * clock, gpointer user_data)
+{
+ AlsaSPDIFSink *sink = ALSASPDIFSINK (user_data);
+
+ return sink->frames * IEC958_FRAME_DURATION;
+}
+
+static gboolean
+alsaspdifsink_open (AlsaSPDIFSink * sink)
+{
+ char *pcm_name = sink->device;
+ snd_pcm_hw_params_t *params;
+ snd_pcm_sw_params_t *sw_params;
+ unsigned int rate, buffer_time, period_time, tmp;
+ snd_pcm_uframes_t avail_min;
+ int err, step;
+ char devstr[256]; /* Storage for local 'default' device string */
+ GstClockTime time;
+
+ snd_pcm_hw_params_alloca (&params);
+ snd_pcm_sw_params_alloca (&sw_params);
+
+ /*
+ * 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:{CARD %d AES0 0x%02x AES1 0x%02x AES2 0x%02x AES3 0x%02x}",
+ sink->card,
+ IEC958_AES0_NONAUDIO,
+ IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
+ 0, IEC958_AES3_CON_FS_48000);
+
+ GST_DEBUG_OBJECT (sink, "Generated device string \"%s\"", devstr);
+ pcm_name = devstr;
+
+ err = snd_pcm_open (&(sink->pcm), pcm_name, SND_PCM_STREAM_PLAYBACK, 0);
+ if (err < 0) {
+ GST_DEBUG_OBJECT ("Open failed for %s - searching for IEC958 manually\n",
+ pcm_name);
+
+ err = alsaspdifsink_find_pcm_device (sink);
+ if (err == 0 && sink->pcm == NULL) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Could not open IEC958/SPDIF output device"), GST_ERROR_SYSTEM);
+ return FALSE;
+ }
+ }
+
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("snd_pcm_open: %s", snd_strerror (err)), GST_ERROR_SYSTEM);
+ return FALSE;
+ }
+
+ err = snd_pcm_hw_params_any (sink->pcm, params);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Broken configuration for this PCM: "
+ "no configurations available"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ /* Set interleaved access. */
+ err = snd_pcm_hw_params_set_access (sink->pcm, params,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Access type not available"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ err = snd_pcm_hw_params_set_format (sink->pcm, params, AC3_FORMAT_BE);
+ if (err < 0) {
+ /* Try LE output and swap data */
+ err = snd_pcm_hw_params_set_format (sink->pcm, params, AC3_FORMAT_LE);
+ sink->need_swap = TRUE;
+ } else
+ sink->need_swap = FALSE;
+
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Sample format not available"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ err = snd_pcm_hw_params_set_channels (sink->pcm, params, AC3_CHANNELS);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Channels count not available"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ rate = AC3_RATE;
+ err = snd_pcm_hw_params_set_rate_near (sink->pcm, params, &rate, 0);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Rate not available"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ buffer_time = 500000;
+ err = snd_pcm_hw_params_set_buffer_time_near (sink->pcm, params,
+ &buffer_time, 0);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Buffer time not available"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+ time = buffer_time * 1000;
+ GST_DEBUG_OBJECT (sink, "buffer size set to %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (time));
+
+ step = 2;
+ period_time = 10000 * 2;
+ do {
+ period_time /= 2;
+ tmp = period_time;
+
+ err = snd_pcm_hw_params_set_period_time_near (sink->pcm, params, &tmp, 0);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Period time not available"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ if (tmp == period_time) {
+ period_time /= 3;
+ tmp = period_time;
+ err = snd_pcm_hw_params_set_period_time_near (sink->pcm, params, &tmp, 0);
+ if (tmp == period_time) {
+ period_time = 10000 * 2;
+ }
+ }
+ } while (buffer_time == period_time && period_time > 10000);
+
+ if (buffer_time == period_time) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Buffer time and period time match, could not use"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ err = snd_pcm_hw_params (sink->pcm, params);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("PCM hw_params failed: %s", snd_strerror (err)), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ err = snd_pcm_sw_params_current (sink->pcm, sw_params);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Cannot retrieve software params"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+
+ avail_min = 48000 * 0.15;
+ err = snd_pcm_sw_params_set_avail_min (sink->pcm, sw_params, avail_min);
+ if (err < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("Cannot set avail min"), GST_ERROR_SYSTEM);
+ goto __close;
+ }
+ snd_pcm_sw_params_get_avail_min (sw_params, &avail_min);
+ GST_DEBUG_OBJECT (sink, "Avail min set to:%lu frames", avail_min);
+
+ return TRUE;
+
+__close:
+ snd_pcm_close (sink->pcm);
+ sink->pcm = NULL;
+ return FALSE;
+}
+
+
+static void
+alsaspdifsink_close (AlsaSPDIFSink * sink)
+{
+ if (sink->pcm) {
+ snd_pcm_close (sink->pcm);
+ sink->pcm = NULL;
+ }
+}
+
+/* Try and find an IEC958 PCM device and mixer on card 0 and open it
+ * This function is only used on older ALSA installs that don't have the
+ * correct iec958 alias stuff set up, and relies on there being only
+ * one IEC958 PCM device (relies IEC958 in the device name) and one IEC958
+ * mixer control for doing the settings.
+ */
+static int
+alsaspdifsink_find_pcm_device (AlsaSPDIFSink * sink)
+{
+ int err = -1, dev, idx, count;
+ const gchar *ctl_name = "hw:0";
+ const gchar *spdif_name = SND_CTL_NAME_IEC958 ("", PLAYBACK, NONE);
+ int card = sink->card;
+ gchar pcm_name[24];
+ snd_pcm_t *pcm = NULL;
+ snd_ctl_t *ctl;
+ snd_ctl_card_info_t *info;
+ snd_ctl_elem_list_t *clist;
+ snd_ctl_elem_id_t *cid;
+ snd_pcm_info_t *pinfo;
+
+ GST_WARNING ("Opening IEC958 named device failed. Trying to autodetect");
+
+ snd_ctl_card_info_alloca (&info);
+ snd_pcm_info_alloca (&pinfo);
+
+ if ((err = snd_ctl_open (&ctl, ctl_name, card)) < 0)
+ return err;
+
+ /* Find a mixer for IEC958 settings */
+ snd_ctl_elem_list_alloca (&clist);
+ if ((err = snd_ctl_elem_list (ctl, clist)) < 0)
+ goto beach;
+
+ if ((err =
+ snd_ctl_elem_list_alloc_space (clist,
+ snd_ctl_elem_list_get_count (clist))) < 0)
+ goto beach;
+ if ((err = snd_ctl_elem_list (ctl, clist)) < 0)
+ goto beach;
+
+ count = snd_ctl_elem_list_get_used (clist);
+ for (idx = 0; idx < count; idx++) {
+ if (strstr (snd_ctl_elem_list_get_name (clist, idx), spdif_name) != NULL)
+ break;
+ }
+ if (idx == count) {
+ /* No SPDIF mixer availble */
+ err = 0;
+ goto beach;
+ }
+ snd_ctl_elem_id_alloca (&cid);
+ snd_ctl_elem_list_get_id (clist, idx, cid);
+
+ /* Now find a PCM device for IEC 958 */
+ if ((err = snd_ctl_card_info (ctl, info)) < 0)
+ goto beach;
+ dev = -1;
+ do {
+ if (snd_ctl_pcm_next_device (ctl, &dev) < 0)
+ goto beach;
+ if (dev < 0)
+ break; /* No more devices */
+
+ /* Filter for playback devices */
+ snd_pcm_info_set_device (pinfo, dev);
+ snd_pcm_info_set_subdevice (pinfo, 0);
+ snd_pcm_info_set_stream (pinfo, SND_PCM_STREAM_PLAYBACK);
+ if ((err = snd_ctl_pcm_info (ctl, pinfo)) < 0) {
+ if (err != -ENOENT)
+ goto beach; /* Genuine error */
+
+ /* Device has no playback streams */
+ continue;
+ }
+ if (strstr (snd_pcm_info_get_name (pinfo), "IEC958") == NULL)
+ continue; /* Not the device we are looking for */
+
+ count = snd_pcm_info_get_subdevices_count (pinfo);
+ GST_LOG_OBJECT (sink, "Device %d has %d subdevices\n", dev,
+ snd_pcm_info_get_subdevices_count (pinfo));
+ for (idx = 0; idx < count; idx++) {
+ snd_pcm_info_set_subdevice (pinfo, idx);
+
+ if ((err = snd_ctl_pcm_info (ctl, pinfo)) < 0)
+ goto beach;
+
+ g_assert (snd_pcm_info_get_stream (pinfo) == SND_PCM_STREAM_PLAYBACK);
+
+ GST_LOG_OBJECT (sink, "Found playback stream on dev %d sub-d %d\n", dev,
+ idx);
+
+ /* Found a suitable PCM device, let's open it */
+ g_snprintf (pcm_name, 24, "hw:%d,%d", card, dev);
+ if ((err =
+ snd_pcm_open (&(pcm), pcm_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
+ goto beach;
+
+ break;
+ }
+ } while (pcm == NULL);
+
+ if (pcm != NULL) {
+ snd_ctl_elem_value_t *cval;
+ snd_aes_iec958_t iec958;
+
+ /* Have a PCM device and a mixer, set things up */
+ snd_ctl_elem_value_alloca (&cval);
+ snd_ctl_elem_value_set_id (cval, cid);
+ snd_ctl_elem_value_get_iec958 (cval, &iec958);
+ iec958.status[0] = IEC958_AES0_NONAUDIO;
+ iec958.status[1] = IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER;
+ iec958.status[2] = 0;
+ iec958.status[3] = IEC958_AES3_CON_FS_48000;
+ snd_ctl_elem_value_set_iec958 (cval, &iec958);
+
+ sink->pcm = pcm;
+ pcm = NULL;
+ err = 0;
+ }
+
+beach:
+ if (pcm)
+ snd_pcm_close (pcm);
+ snd_ctl_close (ctl);
+ return err;
+}
+
+static void
+alsaspdifsink_write_frame (AlsaSPDIFSink * sink, guchar * buf)
+{
+ snd_pcm_sframes_t res;
+ int num_frames = IEC958_FRAME_SIZE / ALSASPDIFSINK_BYTES_PER_FRAME;
+
+ /* If we couldn't output big endian when we opened the devic, then
+ * we need to swap here */
+ if (sink->need_swap) {
+ int i;
+ guchar tmp;
+
+ for (i = 0; i < IEC958_FRAME_SIZE; i += 2) {
+ tmp = buf[i];
+ buf[i] = buf[i + 1];
+ buf[i + 1] = tmp;
+ }
+ }
+
+ res = 0;
+ do {
+ if (res == -EPIPE) {
+ /* Underrun. */
+ GST_INFO_OBJECT (sink, "buffer underrun");
+ res = snd_pcm_prepare (sink->pcm);
+ } else if (res == -ESTRPIPE) {
+ /* Suspend. */
+ while ((res = snd_pcm_resume (sink->pcm)) == -EAGAIN) {
+ GST_DEBUG_OBJECT (sink, "sleeping for suspend");
+ g_usleep (100000);
+ }
+
+ if (res < 0) {
+ res = snd_pcm_prepare (sink->pcm);
+ }
+ }
+
+ if (res >= 0) {
+ res = snd_pcm_writei (sink->pcm, (void *) buf, num_frames);
+ }
+
+ if (res > 0) {
+ num_frames -= res;
+ }
+
+ } while (res == -EPIPE || num_frames > 0);
+
+ sink->frames++;
+
+ if (res < 0) {
+ GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+ ("writei returned error: %s", snd_strerror (res)), GST_ERROR_SYSTEM);
+ return;
+ }
+}
+
+static gboolean
+alsaspdifsink_event (GstBaseSink * bsink, GstEvent * event)
+{
+ AlsaSPDIFSink *sink = ALSASPDIFSINK (bsink);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_FLUSH_START:
+ snd_pcm_drop (sink->pcm);
+ break;
+ case GST_EVENT_FLUSH_STOP:
+ snd_pcm_start (sink->pcm);
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+alsaspdifsink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
+ GstClockTime * start, GstClockTime * end)
+{
+ /* Like GstBaseAudioSink, we set these to NONE */
+ *start = GST_CLOCK_TIME_NONE;
+ *end = GST_CLOCK_TIME_NONE;
+}
+
+#if 0
+static GstClockTime
+alsaspdifsink_current_delay (AlsaSPDIFSink * sink)
+{
+ snd_pcm_sframes_t delay;
+ int err;
+
+ err = snd_pcm_delay (sink->pcm, &delay);
+ if (err < 0 || delay < 0) {
+ return 0;
+ }
+
+ return ALSASPDIFSINK_TIME_PER_FRAMES (sink, delay);
+}
+
+static void
+generate_iec958_zero_frame (guchar * buffer)
+{
+ /* 2 sync words, 16 bits each */
+ buffer[0] = 0xF8;
+ buffer[1] = 0x72;
+ buffer[2] = 0x4E;
+ buffer[3] = 0x1F;
+
+ /* 16-bit burst-info. Contains data type (zero here, for 'null data'),
+ stream number (we output '0' for this always), and a few other bits.
+ As it happens, all-zero is the correct value.
+ */
+ buffer[4] = 0;
+ buffer[5] = 0;
+
+ /* 16-bit frame size. Also zero */
+ buffer[6] = 0;
+ buffer[7] = 0;
+
+ memset (buffer + 8, 0, IEC958_FRAME_SIZE - 8);
+}
+#endif
+
+static GstFlowReturn
+alsaspdifsink_render (GstBaseSink * bsink, GstBuffer * buf)
+{
+ AlsaSPDIFSink *sink = ALSASPDIFSINK (bsink);
+
+#if 0
+ GstClockTime next_write;
+
+ if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buf)))
+ sink->cur_ts = GST_BUFFER_TIMESTAMP (buf);
+
+ next_write = gst_element_get_time (GST_ELEMENT (sink)) +
+ alsaspdifsink_current_delay (sink);
+
+ /*
+ fprintf (stderr, "Drift: % 0.6fs, delay: % 0.6fs\r",
+ GST_TIME_ARGS (GST_CLOCK_DIFF (sink->cur_ts, next_write)),
+ GST_TIME_ARGS (alsaspdifsink_current_delay (sink)));
+ */
+
+ /* If we're too far behind, send empty IEC958 frames. */
+ if (sink->cur_ts > next_write + MAX_SYNC_DIFF) {
+ int frames = (int) (
+ ((double) (sink->cur_ts - next_write)) /
+ (double) IEC958_FRAME_DURATION + 0.5);
+ int i;
+
+ for (i = 0; i < frames; i++) {
+ static guchar frame[IEC958_FRAME_SIZE];
+
+ generate_iec958_zero_frame (frame);
+
+ alsaspdifsink_write_frame (sink, frame);
+ }
+ }
+ /* If we're too far ahead, just drop this buffer */
+ else if (sink->cur_ts + MAX_SYNC_DIFF < next_write) {
+ goto end;
+ }
+#endif
+
+ GST_LOG_OBJECT (sink, "Writing %d bytes to spdif out", GST_BUFFER_SIZE (buf));
+ if (GST_BUFFER_SIZE (buf) == IEC958_FRAME_SIZE)
+ alsaspdifsink_write_frame (sink, GST_BUFFER_DATA (buf));
+ else
+ GST_WARNING_OBJECT (sink, "Ignoring buffer of incorrect size");
+
+#if 0
+end:
+ if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buf)))
+ sink->cur_ts = GST_BUFFER_DURATION (buf);
+#endif
+
+ return GST_FLOW_OK;
+}
+
+/* Drop error output from within alsalib on the floor */
+static void
+ignore_alsa_err (const char *file, int line, const char *function,
+ int err, const char *fmt, ...)
+{
+}
+
+static GstStateChangeReturn
+alsaspdifsink_change_state (GstElement * element, GstStateChange transition)
+{
+ AlsaSPDIFSink *sink = ALSASPDIFSINK (element);
+ GstStateChangeReturn ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ sink->frames = 0;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ if (!alsaspdifsink_open (sink)) {
+ GST_WARNING_OBJECT (sink, "Failed to open alsa device");
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ GST_INFO_OBJECT (sink, "Parent change_state returned %d", ret);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ alsaspdifsink_close (sink);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ if (!gst_element_register (plugin, "alsaspdifsink", GST_RANK_PRIMARY,
+ GST_TYPE_ALSASPDIFSINK)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "alsaspdif",
+ "Alsa plugin for S/PDIF output",
+ plugin_init,
+ VERSION, GST_LICENSE_UNKNOWN, PACKAGE, "http://www.fluendo.com");
diff --git a/ext/alsaspdif/alsaspdifsink.h b/ext/alsaspdif/alsaspdifsink.h
new file mode 100644
index 00000000..04a8ff76
--- /dev/null
+++ b/ext/alsaspdif/alsaspdifsink.h
@@ -0,0 +1,84 @@
+/* Based on a plugin from Martin Soto's Seamless DVD Player.
+ * Copyright (C) 2003, 2004 Martin Soto <martinsoto@users.sourceforge.net>
+ * 2005-6 Michael Smith <msmith@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __ALSASPDIFSINK_H__
+#define __ALSASPDIFSINK_H__
+
+#include <gst/gst.h>
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+#include <alsa/asoundlib.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_ALSASPDIFSINK \
+ (alsaspdifsink_get_type())
+#define ALSASPDIFSINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ALSASPDIFSINK,AlsaSPDIFSink))
+#define ALSASPDIFSINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ALSASPDIFSINK,AlsaSPDIFSinkClass))
+#define GST_IS_ALSASPDIFSINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ALSASPDIFSINK))
+#define GST_IS_ALSASPDIFSINK_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ALSASPDIFSINK))
+#define GST_TYPE_ALSASPDIFSINK (alsaspdifsink_get_type())
+
+typedef struct _AlsaSPDIFSink AlsaSPDIFSink;
+typedef struct _AlsaSPDIFSinkClass AlsaSPDIFSinkClass;
+
+typedef enum {
+ ALSASPDIFSINK_OPEN = GST_ELEMENT_FLAG_LAST,
+ ALSASPDIFSINK_FLAG_LAST = GST_ELEMENT_FLAG_LAST + 2,
+} AlsaSPDIFSinkFlags;
+
+/* ALSA spdif types. */
+enum {
+ SPDIF_NONE = 0,
+ SPDIF_CON,
+ SPDIF_PRO,
+ SPDIF_PCM
+};
+
+struct _AlsaSPDIFSink {
+ GstBaseSink basesink;
+
+ GstClockTime cur_ts; /* Current time stamp. */
+
+ snd_pcm_t *pcm; /* ALSA output device. */
+
+ gint card; /* ALSA card number to use */
+ char *device; /* ALSA device name */
+
+ GstClock *clock; /* The clock for this element. */
+
+ guint64 frames; /* Number of complete frames written */
+ gboolean need_swap; /* Whether to byte swap outgoing data */
+};
+
+struct _AlsaSPDIFSinkClass {
+ GstBaseSinkClass parent_class;
+};
+
+extern GType alsaspdifsink_get_type (void);
+
+G_END_DECLS
+
+#endif /* __DXR3AUDIOINK_H__ */