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
This commit is contained in:
Michael Smith 2006-05-18 12:51:01 +00:00
parent 9b3c826eb9
commit 855a2f314b
6 changed files with 943 additions and 1 deletions

2
common

@ -1 +1 @@
Subproject commit 3062df90281144cbdb55bd58ee9f0714ab346c23 Subproject commit a5b66304e7abe1440a0f8b0ed232ffbc56e8f3de

View file

@ -266,6 +266,18 @@ dnl keep this list sorted alphabetically !
if test "x$BUILD_EXTERNAL" = "xyes"; then if test "x$BUILD_EXTERNAL" = "xyes"; then
dnl *** alsa ***
translit(dnm, m, l) AM_CONDITIONAL(USE_ALSA, true)
GST_CHECK_FEATURE(ALSA, [alsa plug-ins], gstalsa, [
PKG_CHECK_MODULES(ALSA, alsa >= 0.9.1, [
HAVE_ALSA="yes"
AC_SUBST(ALSA_CFLAGS)
AC_SUBST(ALSA_LIBS)
], [
AM_PATH_ALSA(0.9.1, HAVE_ALSA="yes", HAVE_ALSA="no")
])
])
dnl *** amrwb *** dnl *** amrwb ***
translit(dnm, m, l) AM_CONDITIONAL(USE_AMRWB, true) translit(dnm, m, l) AM_CONDITIONAL(USE_AMRWB, true)
GST_CHECK_FEATURE(AMRWB, [amrwb code], amrwb, [ GST_CHECK_FEATURE(AMRWB, [amrwb code], amrwb, [
@ -650,6 +662,7 @@ examples/Makefile
examples/directfb/Makefile examples/directfb/Makefile
ext/amrwb/amrwb-code/Makefile ext/amrwb/amrwb-code/Makefile
ext/amrwb/Makefile ext/amrwb/Makefile
ext/alsaspdif/Makefile
ext/bz2/Makefile ext/bz2/Makefile
ext/directfb/Makefile ext/directfb/Makefile
ext/divx/Makefile ext/divx/Makefile

View file

@ -1,3 +1,9 @@
if USE_ALSA
ALSASPDIF_DIR = alsaspdif
else
ALSASPDIF_DIR =
endif
if USE_AMRWB if USE_AMRWB
AMRWB_DIR = amrwb AMRWB_DIR = amrwb
else else
@ -228,6 +234,7 @@ XVID_DIR=
endif endif
SUBDIRS=\ SUBDIRS=\
$(ALSASPDIF_DIR) \
$(AMRWB_DIR) \ $(AMRWB_DIR) \
$(ARTS_DIR) \ $(ARTS_DIR) \
$(ARTSC_DIR) \ $(ARTSC_DIR) \

14
ext/alsaspdif/Makefile.am Normal file
View file

@ -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

View file

@ -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");

View file

@ -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__ */