From 855a2f314b13c3cf87d625e9ef327c1639492fc5 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 18 May 2006 12:51:01 +0000 Subject: 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 --- ext/alsaspdif/Makefile.am | 14 + ext/alsaspdif/alsaspdifsink.c | 824 ++++++++++++++++++++++++++++++++++++++++++ ext/alsaspdif/alsaspdifsink.h | 84 +++++ 3 files changed, 922 insertions(+) create mode 100644 ext/alsaspdif/Makefile.am create mode 100644 ext/alsaspdif/alsaspdifsink.c create mode 100644 ext/alsaspdif/alsaspdifsink.h (limited to 'ext/alsaspdif') 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 + * 2005-6 Michael Smith + * + * 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 +#include + +#include +#include +#include + +#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 \n" + "Michael Smith " +}; + +/* 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 (¶ms); + 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 + * 2005-6 Michael Smith + * + * 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 + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API +#include + +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__ */ -- cgit v1.2.1