mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 10:40:34 +00:00
20a9403fbf
Original commit message from CVS: + alsasrc compiles and runs in "alsasrc ! fakesink" and "alsasrc ! osssink" pipelines. seems to have a 100% cpu issue at the moment.
1326 lines
43 KiB
C
1326 lines
43 KiB
C
/*
|
|
* Copyright (C) 2001 CodeFactory AB
|
|
* Copyright (C) 2001 Thomas Nyberg <thomas@codefactory.se>
|
|
* Copyright (C) 2001-2002 Andy Wingo <apwingo@eos.ncsu.edu>
|
|
* Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this library; if not, write to the Free
|
|
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <sys/time.h>
|
|
#include "gstalsa.h"
|
|
|
|
/* error checking for standard alsa functions */
|
|
#ifdef G_HAVE_ISO_VARARGS
|
|
#define ERROR_CHECK(value, ...) G_STMT_START{ \
|
|
int err = (value); \
|
|
if (err < 0) { \
|
|
g_warning ( __VA_ARGS__, snd_strerror (err)); \
|
|
return FALSE; \
|
|
} \
|
|
}G_STMT_END
|
|
#elif defined(G_HAVE_GNUC_VARARGS)
|
|
#define ERROR_CHECK(value, args...) G_STMT_START{ \
|
|
int err = (value); \
|
|
if (err < 0) { \
|
|
g_warning ( ## args, snd_strerror (err)); \
|
|
return FALSE; \
|
|
} \
|
|
}G_STMT_END
|
|
#else
|
|
#define ERROR_CHECK(value, args...) G_STMT_START{ \
|
|
int err = (value); \
|
|
if (err < 0) { \
|
|
g_warning (snd_strerror (err)); \
|
|
return FALSE; \
|
|
} \
|
|
}G_STMT_END
|
|
#endif
|
|
|
|
/* elementfactory information */
|
|
static GstElementDetails gst_alsa_sink_details = {
|
|
"Alsa Sink",
|
|
"Sink/Audio",
|
|
"LGPL",
|
|
"Output to a sound card via ALSA",
|
|
VERSION,
|
|
"Thomas Nyberg <thomas@codefactory.se>, "
|
|
"Andy Wingo <apwingo@eos.ncsu.edu>"
|
|
"Benjamin Otte <in7y118@public.uni-hamburg.de>",
|
|
"(C) 2001-2003"
|
|
};
|
|
|
|
/* elementfactory information */
|
|
static GstElementDetails gst_alsa_src_details = {
|
|
"Alsa Src",
|
|
"Source/Audio",
|
|
"LGPL",
|
|
"Read from a sound card via ALSA",
|
|
VERSION,
|
|
"Thomas Nyberg <thomas@codefactory.se>, "
|
|
"Andy Wingo <apwingo@eos.ncsu.edu>"
|
|
"Benjamin Otte <in7y118@public.uni-hamburg.de>",
|
|
"(C) 2001-2003"
|
|
};
|
|
|
|
/* GObject functions */
|
|
static void gst_alsa_class_init (GstAlsaClass *klass);
|
|
static void gst_alsa_init (GstAlsa *this);
|
|
static void gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
|
static void gst_alsa_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
|
|
|
|
/* GStreamer functions for pads and state changing */
|
|
static GstPadTemplate *gst_alsa_src_pad_factory ();
|
|
static GstPadTemplate *gst_alsa_src_request_pad_factory ();
|
|
static GstPadTemplate *gst_alsa_sink_pad_factory ();
|
|
static GstPadTemplate *gst_alsa_sink_request_pad_factory ();
|
|
|
|
static GstPad *gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name);
|
|
static GstPadLinkReturn gst_alsa_link (GstPad *pad, GstCaps *caps);
|
|
static GstCaps *gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels);
|
|
|
|
static GstBufferPool *gst_alsa_src_get_buffer_pool (GstPad *pad);
|
|
|
|
static GstElementStateReturn gst_alsa_change_state (GstElement *element);
|
|
|
|
/* audio processing functions */
|
|
static int gst_alsa_do_mmap (GstAlsa *this, guint numpads, snd_pcm_sframes_t *avail);
|
|
|
|
static void gst_alsa_sink_loop (GstElement *element);
|
|
static void gst_alsa_src_loop (GstElement *element);
|
|
static void gst_alsa_xrun_recovery (GstAlsa *this);
|
|
|
|
static gboolean gst_alsa_sink_check_event (GstAlsa *this, gint pad_nr);
|
|
|
|
/* alsa setup / start / stop functions */
|
|
static gboolean gst_alsa_set_hw_params (GstAlsa *this);
|
|
static gboolean gst_alsa_set_sw_params (GstAlsa *this);
|
|
|
|
static gboolean gst_alsa_open_audio (GstAlsa *this);
|
|
static gboolean gst_alsa_start_audio (GstAlsa *this);
|
|
static gboolean gst_alsa_drain_audio (GstAlsa *this);
|
|
static gboolean gst_alsa_stop_audio (GstAlsa *this);
|
|
static gboolean gst_alsa_close_audio (GstAlsa *this);
|
|
|
|
/*** TYPE FUNCTIONS ***********************************************************/
|
|
|
|
#define GST_TYPE_ALSA_FORMAT (gst_alsa_format_get_type())
|
|
static GType
|
|
gst_alsa_format_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
static GEnumValue *values = NULL;
|
|
gint i;
|
|
|
|
if (values == NULL) {
|
|
/* the three: for -1, 0, and the terminating NULL */
|
|
values = g_new0 (GEnumValue, SND_PCM_FORMAT_LAST + 1);
|
|
|
|
for (i = -1; i <= SND_PCM_FORMAT_LAST; i++) {
|
|
values[i + 1].value = i; /* UNKNOWN is -1 */
|
|
values[i + 1].value_name = g_strdup_printf ("%d", i);
|
|
values[i + 1].value_nick = g_strdup (snd_pcm_format_name ((snd_pcm_format_t) i));
|
|
}
|
|
}
|
|
|
|
if (!type)
|
|
type = g_enum_register_static ("GstAlsaFormat", values);
|
|
|
|
return type;
|
|
}
|
|
GType
|
|
gst_alsa_get_type (void)
|
|
{
|
|
static GType alsa_type = 0;
|
|
|
|
if (!alsa_type) {
|
|
static const GTypeInfo alsa_info = {
|
|
sizeof (GstAlsaClass),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstAlsa),
|
|
0,
|
|
NULL,
|
|
};
|
|
|
|
alsa_type = g_type_register_static (GST_TYPE_ELEMENT, "GstAlsa", &alsa_info, 0);
|
|
}
|
|
return alsa_type;
|
|
}
|
|
|
|
GType
|
|
gst_alsa_sink_get_type (void)
|
|
{
|
|
static GType alsa_type = 0;
|
|
|
|
if (!alsa_type) {
|
|
static const GTypeInfo alsa_info = {
|
|
sizeof (GstAlsaClass),
|
|
NULL,
|
|
NULL,
|
|
(GClassInitFunc) gst_alsa_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstAlsa),
|
|
0,
|
|
(GInstanceInitFunc) gst_alsa_init,
|
|
};
|
|
|
|
alsa_type = g_type_register_static (GST_TYPE_ALSA, "GstAlsaSink", &alsa_info, 0);
|
|
}
|
|
return alsa_type;
|
|
}
|
|
|
|
GType
|
|
gst_alsa_src_get_type (void)
|
|
{
|
|
static GType alsa_type = 0;
|
|
|
|
if (!alsa_type) {
|
|
static const GTypeInfo alsa_info = {
|
|
sizeof (GstAlsaClass),
|
|
NULL,
|
|
NULL,
|
|
(GClassInitFunc) gst_alsa_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstAlsa),
|
|
0,
|
|
(GInstanceInitFunc) gst_alsa_init,
|
|
};
|
|
|
|
alsa_type = g_type_register_static (GST_TYPE_ALSA, "GstAlsaSrc", &alsa_info, 0);
|
|
}
|
|
return alsa_type;
|
|
}
|
|
|
|
/*** GOBJECT FUNCTIONS ********************************************************/
|
|
|
|
enum
|
|
{
|
|
ARG_0,
|
|
ARG_DEVICE,
|
|
ARG_FORMAT,
|
|
ARG_CHANNELS,
|
|
ARG_RATE,
|
|
ARG_PERIODCOUNT,
|
|
ARG_PERIODSIZE,
|
|
ARG_BUFFERSIZE,
|
|
ARG_DEBUG,
|
|
ARG_AUTORECOVER
|
|
};
|
|
|
|
static GstElement *parent_class = NULL;
|
|
|
|
static void
|
|
gst_alsa_class_init (GstAlsaClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
GstElementClass *element_class;
|
|
|
|
object_class = (GObjectClass *) klass;
|
|
element_class = (GstElementClass *) klass;
|
|
|
|
if (parent_class == NULL)
|
|
parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
|
|
|
|
object_class->get_property = gst_alsa_get_property;
|
|
object_class->set_property = gst_alsa_set_property;
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE,
|
|
g_param_spec_string ("device", "Device", "Alsa device, as defined in an asoundrc",
|
|
"default", G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
/* the next 3 are only settable on srcs */
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FORMAT,
|
|
g_param_spec_enum ("format", "Format", "PCM audio format",
|
|
GST_TYPE_ALSA_FORMAT, -1, G_PARAM_READWRITE));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_CHANNELS,
|
|
g_param_spec_int ("channels", "Channels", "Number of channels",
|
|
1, GST_ALSA_MAX_CHANNELS, 2, G_PARAM_READWRITE));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_RATE,
|
|
g_param_spec_int ("rate", "Rate", "Sample rate, in Hz",
|
|
8000, 192000, 44100, G_PARAM_READWRITE));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PERIODCOUNT,
|
|
g_param_spec_int ("period-count", "Period count", "Number of hardware buffers to use",
|
|
2, 64, 2, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PERIODSIZE,
|
|
g_param_spec_int ("period-size", "Period size", "Number of frames (samples on each channel) in one hardware period",
|
|
64, 8192, 8192, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BUFFERSIZE,
|
|
g_param_spec_int ("buffer-size", "Buffer size", "Number of frames the hardware buffer can hold",
|
|
128, 65536, 16384, G_PARAM_READWRITE));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_AUTORECOVER,
|
|
g_param_spec_boolean ("autorecover", "Automatic xrun recovery", "When TRUE tries to reduce processor load on xruns",
|
|
TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
|
|
|
|
element_class->change_state = gst_alsa_change_state;
|
|
element_class->request_new_pad = gst_alsa_request_new_pad;
|
|
}
|
|
|
|
static void
|
|
gst_alsa_init (GstAlsa *this)
|
|
{
|
|
gint i;
|
|
/* init values */
|
|
this->handle = NULL;
|
|
this->channels = 1;
|
|
|
|
GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED);
|
|
|
|
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) {
|
|
this->stream = SND_PCM_STREAM_CAPTURE;
|
|
this->format = SND_PCM_FORMAT_S16; /* native endian */
|
|
gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_src_loop);
|
|
this->pads[0].pad = gst_pad_new_from_template (gst_alsa_src_pad_factory (), "src");
|
|
gst_pad_set_bufferpool_function(this->pads[0].pad, gst_alsa_src_get_buffer_pool);
|
|
|
|
/* set the rate to a sensible value. we can't have gobject construct this
|
|
manually since it only really makes sense on src elements. the rate can
|
|
be changed later through the gobject set property function. */
|
|
this->rate = 44100;
|
|
} else if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK) {
|
|
this->stream = SND_PCM_STREAM_PLAYBACK;
|
|
this->format = SND_PCM_FORMAT_UNKNOWN; /* we don't know until caps are set */
|
|
gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_sink_loop);
|
|
this->pads[0].pad = gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "sink");
|
|
this->pads[0].bs = gst_bytestream_new (this->pads[0].pad);
|
|
}
|
|
|
|
gst_element_add_pad (GST_ELEMENT (this), this->pads[0].pad);
|
|
for (i = 1; i < GST_ALSA_MAX_CHANNELS; i++) {
|
|
this->pads[i].pad = NULL;
|
|
}
|
|
|
|
gst_pad_set_link_function (this->pads[0].pad, gst_alsa_link);
|
|
}
|
|
|
|
static void
|
|
gst_alsa_set_property (GObject *object, guint prop_id, const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GstAlsa *this;
|
|
gint buffer_size;
|
|
|
|
this = (GstAlsa *) object;
|
|
switch (prop_id) {
|
|
case ARG_DEVICE:
|
|
if (this->device)
|
|
g_free (this->device);
|
|
this->device = g_strdup (g_value_get_string (value));
|
|
break;
|
|
case ARG_FORMAT:
|
|
/* setting this property only makes sense on sources */
|
|
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC)
|
|
this->format = g_value_get_enum (value);
|
|
break;
|
|
case ARG_CHANNELS:
|
|
/* setting this property only makes sense on sources */
|
|
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC)
|
|
this->channels = g_value_get_int (value);
|
|
break;
|
|
case ARG_RATE:
|
|
/* setting this property only makes sense on sources */
|
|
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC)
|
|
this->rate = g_value_get_int (value);
|
|
break;
|
|
case ARG_PERIODCOUNT:
|
|
g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
|
|
this->period_count = g_value_get_int (value);
|
|
break;
|
|
case ARG_PERIODSIZE:
|
|
g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
|
|
this->period_size = g_value_get_int (value);
|
|
break;
|
|
case ARG_BUFFERSIZE:
|
|
g_return_if_fail (!GST_FLAG_IS_SET (this, GST_ALSA_RUNNING));
|
|
buffer_size = g_value_get_int (value);
|
|
this->period_count = buffer_size / this->period_size;
|
|
break;
|
|
case ARG_AUTORECOVER:
|
|
this->autorecover = g_value_get_boolean (value);
|
|
return;
|
|
default:
|
|
GST_DEBUG (0, "Unknown arg");
|
|
return;
|
|
}
|
|
|
|
if (GST_STATE (this) == GST_STATE_NULL)
|
|
return;
|
|
|
|
if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) {
|
|
gst_alsa_stop_audio (this);
|
|
gst_alsa_start_audio (this);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_alsa_get_property (GObject *object, guint prop_id, GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GstAlsa *this;
|
|
|
|
this = (GstAlsa *) object;
|
|
|
|
switch (prop_id) {
|
|
case ARG_DEVICE:
|
|
g_value_set_string (value, this->device);
|
|
break;
|
|
case ARG_FORMAT:
|
|
g_value_set_enum (value, this->format);
|
|
break;
|
|
case ARG_CHANNELS:
|
|
g_value_set_int (value, this->channels);
|
|
break;
|
|
case ARG_RATE:
|
|
g_value_set_int (value, this->rate);
|
|
break;
|
|
case ARG_PERIODCOUNT:
|
|
g_value_set_int (value, this->period_count);
|
|
break;
|
|
case ARG_PERIODSIZE:
|
|
g_value_set_int (value, this->period_size);
|
|
break;
|
|
case ARG_BUFFERSIZE:
|
|
g_value_set_int (value, this->period_size * this->period_count);
|
|
break;
|
|
case ARG_AUTORECOVER:
|
|
g_value_set_boolean (value, this->autorecover);
|
|
break;
|
|
default:
|
|
GST_DEBUG (0, "Unknown arg");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*** GSTREAMER PAD / STATE FUNCTIONS*******************************************/
|
|
|
|
static GstPadTemplate *
|
|
gst_alsa_src_pad_factory (void)
|
|
{
|
|
static GstPadTemplate *template = NULL;
|
|
|
|
if (!template)
|
|
template = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
|
|
gst_alsa_caps (SND_PCM_FORMAT_UNKNOWN, -1, -1),
|
|
NULL);
|
|
|
|
return template;
|
|
}
|
|
|
|
static GstPadTemplate *
|
|
gst_alsa_src_request_pad_factory (void)
|
|
{
|
|
static GstPadTemplate *template = NULL;
|
|
|
|
if (!template)
|
|
template = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
|
|
gst_alsa_caps (SND_PCM_FORMAT_UNKNOWN, -1, 1),
|
|
NULL);
|
|
|
|
return template;
|
|
}
|
|
|
|
static GstPadTemplate *
|
|
gst_alsa_sink_pad_factory (void)
|
|
{
|
|
static GstPadTemplate *template = NULL;
|
|
|
|
if (!template)
|
|
template = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
|
|
gst_alsa_caps (SND_PCM_FORMAT_UNKNOWN, -1, -1),
|
|
NULL);
|
|
|
|
return template;
|
|
}
|
|
|
|
static GstPadTemplate *
|
|
gst_alsa_sink_request_pad_factory (void)
|
|
{
|
|
static GstPadTemplate *template = NULL;
|
|
|
|
if (!template)
|
|
template =
|
|
gst_pad_template_new ("sink%d", GST_PAD_SINK, GST_PAD_REQUEST,
|
|
gst_alsa_caps (SND_PCM_FORMAT_UNKNOWN, -1, 1),
|
|
NULL);
|
|
|
|
return template;
|
|
}
|
|
|
|
static GstPad *
|
|
gst_alsa_request_new_pad (GstElement *element, GstPadTemplate *templ,
|
|
const gchar *name)
|
|
{
|
|
GstAlsa *this;
|
|
gint channel = 0;
|
|
|
|
g_return_val_if_fail ((this = GST_ALSA (element)), NULL);
|
|
g_return_val_if_fail (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING), NULL);
|
|
|
|
/* you can't request a pad if the non-request pad already has more than 1 channel */
|
|
g_return_val_if_fail (this->channels <= element->numpads, NULL);
|
|
|
|
if (name) {
|
|
/* locate the channel number in the requested pad name. to do so look at
|
|
where the % (which begins the %d) is in the template name. */
|
|
channel = (gint) strtol (name + (strchr (templ->name_template, '%') -
|
|
templ->name_template), NULL, 0);
|
|
if (channel < 1 || channel >= GST_ALSA_MAX_CHANNELS) {
|
|
g_warning ("invalid channel requested. (%d)", channel);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* make sure the requested channel is free. */
|
|
if (channel > 0 || this->pads[channel].pad != NULL) {
|
|
g_warning ("requested channel %d already in use.", channel);
|
|
return NULL;
|
|
}
|
|
|
|
/* if the user doesn't care which channel, find the lowest channel number
|
|
that's free. */
|
|
if (channel == 0) {
|
|
for (channel = 1; channel < GST_ALSA_MAX_CHANNELS; channel++) {
|
|
if (this->pads[channel].pad != NULL)
|
|
goto found_channel;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
found_channel:
|
|
this->pads[channel].pad = gst_pad_new_from_template (templ, name);
|
|
gst_pad_set_link_function (this->pads[channel].pad, gst_alsa_link);
|
|
gst_element_add_pad (GST_ELEMENT (this), this->pads[channel].pad);
|
|
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SINK)
|
|
this->pads[channel].bs = gst_bytestream_new (this->pads[channel].pad);
|
|
else
|
|
gst_pad_set_bufferpool_function(this->pads[channel].pad, gst_alsa_src_get_buffer_pool);
|
|
|
|
return this->pads[channel].pad;
|
|
}
|
|
|
|
/* gets the matching alsa format or SND_PCM_FORMAT_UNKNOWN if none matches */
|
|
static snd_pcm_format_t
|
|
gst_alsa_get_format (GstCaps *caps)
|
|
{
|
|
const gchar *format_name;
|
|
|
|
/* we have to differentiate between int and float formats */
|
|
if (!gst_caps_get_string (caps, "format", &format_name))
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
|
|
if (strncmp (format_name, "int", 3) == 0) {
|
|
gboolean sign;
|
|
gint width, depth, endianness, law;
|
|
|
|
/* extract the needed information from the caps */
|
|
if (!gst_caps_get (caps,
|
|
"width", &width,
|
|
"depth", &depth,
|
|
"law", &law,
|
|
"signed", &sign,
|
|
NULL))
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
|
|
/* extract endianness if needed */
|
|
if (width > 8) {
|
|
if (!gst_caps_get (caps,
|
|
"endianness", &endianness,
|
|
NULL))
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
} else {
|
|
endianness = G_BYTE_ORDER;
|
|
}
|
|
|
|
/* find corresponding alsa format */
|
|
switch (law) {
|
|
case 0: return snd_pcm_build_linear_format (depth, width, sign ? 0 : 1, endianness == G_LITTLE_ENDIAN ? 0 : 1);
|
|
case 1:
|
|
if (width == 8 && depth == 8 && sign == FALSE) {
|
|
return SND_PCM_FORMAT_MU_LAW;
|
|
} else {
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
}
|
|
case 2:
|
|
if (width == 8 && depth == 8 && sign == FALSE) {
|
|
return SND_PCM_FORMAT_A_LAW;
|
|
} else {
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
}
|
|
default: return SND_PCM_FORMAT_UNKNOWN;
|
|
}
|
|
|
|
} else if (strncmp (format_name, "float", 5) == 0) {
|
|
gchar *layout;
|
|
gfloat intercept, slope;
|
|
|
|
/* get layout */
|
|
if (!gst_caps_get (caps, "layout", &layout,
|
|
"intercept", &intercept,
|
|
"slope", &slope,
|
|
NULL))
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
if (intercept != 0.0f || slope != 1.0f) {
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
}
|
|
/* match layout to format wrt to endianness */
|
|
if (strncmp (layout, "gfloat", 6) == 0) {
|
|
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) return SND_PCM_FORMAT_FLOAT_LE;
|
|
if (G_BYTE_ORDER == G_BIG_ENDIAN) return SND_PCM_FORMAT_FLOAT_BE;
|
|
return SND_PCM_FORMAT_FLOAT;
|
|
} else if (strncmp (layout, "gdouble", 7) == 0) {
|
|
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) return SND_PCM_FORMAT_FLOAT64_LE;
|
|
if (G_BYTE_ORDER == G_BIG_ENDIAN) return SND_PCM_FORMAT_FLOAT64_BE;
|
|
return SND_PCM_FORMAT_FLOAT64;
|
|
} else {
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
}
|
|
|
|
/* get props for a spec */
|
|
static GstProps *
|
|
gst_alsa_get_props (snd_pcm_format_t format)
|
|
{
|
|
if (format == SND_PCM_FORMAT_A_LAW) {
|
|
return gst_props_new ("format", GST_PROPS_STRING ("int"),
|
|
"law", GST_PROPS_INT(2),
|
|
"width", GST_PROPS_INT(8),
|
|
"depth", GST_PROPS_INT(8),
|
|
"signed", GST_PROPS_BOOLEAN (FALSE),
|
|
NULL);
|
|
} else if (format == SND_PCM_FORMAT_MU_LAW) {
|
|
return gst_props_new ("format", GST_PROPS_STRING ("int"),
|
|
"law", GST_PROPS_INT(1),
|
|
"width", GST_PROPS_INT(8),
|
|
"depth", GST_PROPS_INT(8),
|
|
"signed", GST_PROPS_BOOLEAN (FALSE),
|
|
NULL);
|
|
} else if (snd_pcm_format_linear (format)) {
|
|
/* int */
|
|
GstProps *props = gst_props_new ("format", GST_PROPS_STRING ("int"),
|
|
"width", GST_PROPS_INT(snd_pcm_format_physical_width (format)),
|
|
"depth", GST_PROPS_INT(snd_pcm_format_width (format)),
|
|
"law", GST_PROPS_INT(0),
|
|
"signed", GST_PROPS_BOOLEAN (snd_pcm_format_signed (format) == 1 ? TRUE : FALSE),
|
|
NULL);
|
|
/* endianness */
|
|
if (snd_pcm_format_physical_width (format) > 8) {
|
|
switch (snd_pcm_format_little_endian (format)) {
|
|
case 0:
|
|
gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_BIG_ENDIAN)));
|
|
break;
|
|
case 1:
|
|
gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_LITTLE_ENDIAN)));
|
|
break;
|
|
default:
|
|
g_warning("ALSA: Unknown byte order in sound driver. Continuing by assuming system byte order.");
|
|
gst_props_add_entry (props, gst_props_entry_new ("endianness", GST_PROPS_INT (G_BYTE_ORDER)));
|
|
break;
|
|
}
|
|
}
|
|
return props;
|
|
} else if (snd_pcm_format_float (format)) {
|
|
/* no float with non-platform endianness */
|
|
if (!snd_pcm_format_cpu_endian (format))
|
|
return NULL;
|
|
|
|
return gst_props_new ("format", GST_PROPS_STRING ("float"),
|
|
"layout", GST_PROPS_STRING (snd_pcm_format_width (format) == 64 ? "gdouble" : "gfloat"),
|
|
"intercept", GST_PROPS_FLOAT (0),
|
|
"slope", GST_PROPS_FLOAT (1),
|
|
NULL);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static inline void add_channels (GstProps *props, gint rate, gint channels) {
|
|
if (rate < 0) {
|
|
gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT_RANGE (8000, 192000)));
|
|
} else {
|
|
gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT (rate)));
|
|
}
|
|
if (channels < 0) {
|
|
gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (1, GST_ALSA_MAX_CHANNELS)));
|
|
} else {
|
|
gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT (channels)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all available caps.
|
|
* @format: SND_PCM_FORMAT_UNKNOWN for all formats, desired format else
|
|
* @rate: allowed rates if < 0, else desired rate
|
|
* @channels: all allowed values for channels if < 0, else desired channels
|
|
*/
|
|
static GstCaps *
|
|
gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels)
|
|
{
|
|
GstCaps *ret_caps = NULL;
|
|
|
|
if (format != SND_PCM_FORMAT_UNKNOWN) {
|
|
/* there are some caps set already */
|
|
GstProps *props = gst_alsa_get_props (format);
|
|
/* we can never use a format we can't set caps for */
|
|
g_assert (props != NULL);
|
|
|
|
add_channels (props, rate, channels);
|
|
ret_caps = gst_caps_new ("alsacaps", "audio/raw", props);
|
|
} else {
|
|
int i;
|
|
GstProps *props;
|
|
|
|
for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) {
|
|
props = gst_alsa_get_props (i);
|
|
/* can be NULL, because not all alsa formats can be specified as caps */
|
|
if (props != NULL) {
|
|
add_channels (props, rate, channels);
|
|
ret_caps = gst_caps_append (ret_caps, gst_caps_new (g_strdup (snd_pcm_format_name (i)),
|
|
"audio/raw", props));
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret_caps;
|
|
}
|
|
|
|
/* Negotiates the caps */
|
|
GstPadLinkReturn
|
|
gst_alsa_link (GstPad *pad, GstCaps *caps)
|
|
{
|
|
GstAlsa *this;
|
|
snd_pcm_format_t format;
|
|
gint rate, channels;
|
|
|
|
g_return_val_if_fail (caps != NULL, GST_PAD_LINK_REFUSED);
|
|
g_return_val_if_fail (pad != NULL, GST_PAD_LINK_REFUSED);
|
|
|
|
this = GST_ALSA (gst_pad_get_parent (pad));
|
|
|
|
if (GST_CAPS_IS_FIXED (caps)) {
|
|
if (this->handle == NULL)
|
|
if (!gst_alsa_open_audio (this))
|
|
return GST_PAD_LINK_REFUSED;
|
|
|
|
format = gst_alsa_get_format (caps);
|
|
GST_DEBUG (GST_CAT_CAPS, "found format %s\n", snd_pcm_format_name (format));
|
|
/* FIXME: allow changing the format here, even by retrying caps on other pads */
|
|
if (format == SND_PCM_FORMAT_UNKNOWN || (this->format != SND_PCM_FORMAT_UNKNOWN && this->format != format))
|
|
return GST_PAD_LINK_REFUSED;
|
|
if (!gst_caps_get (caps, "rate", &rate,
|
|
"channels", &channels,
|
|
NULL))
|
|
return GST_PAD_LINK_REFUSED;
|
|
if (this->format != SND_PCM_FORMAT_UNKNOWN && this->rate != rate)
|
|
return GST_PAD_LINK_REFUSED;
|
|
if (this->format != SND_PCM_FORMAT_UNKNOWN && ((this->channels > 1 || channels > 1)))
|
|
return GST_PAD_LINK_REFUSED;
|
|
|
|
if (this->format == SND_PCM_FORMAT_UNKNOWN) {
|
|
this->channels = channels;
|
|
} else {
|
|
this->channels++;
|
|
}
|
|
this->format = format;
|
|
this->rate = rate;
|
|
|
|
/* sync the params */
|
|
if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this);
|
|
if (GST_FLAG_IS_SET (this, GST_ALSA_OPEN)) gst_alsa_close_audio (this);
|
|
|
|
if (!gst_alsa_open_audio (this)) return GST_PAD_LINK_REFUSED;
|
|
if (!gst_alsa_start_audio (this)) return GST_PAD_LINK_REFUSED;
|
|
|
|
return GST_PAD_LINK_OK;
|
|
}
|
|
|
|
return GST_PAD_LINK_DELAYED;
|
|
}
|
|
|
|
static GstBufferPool *
|
|
gst_alsa_src_get_buffer_pool (GstPad *pad)
|
|
{
|
|
int width, bytes_per_frame;
|
|
|
|
GstAlsa *this = GST_ALSA (gst_pad_get_parent (pad));
|
|
|
|
width = snd_pcm_format_physical_width (this->format);
|
|
bytes_per_frame = ( width / 8 ) * (GST_ELEMENT (this)->numpads == 1 ? this->channels : 1);
|
|
|
|
/* FIXME : is this right ? constant size buffers are probably a good thing,
|
|
but what if the size changes (e.g. during xrun autorecovery) ? */
|
|
return gst_buffer_pool_get_default (this->period_size * bytes_per_frame,
|
|
this->period_count);
|
|
}
|
|
|
|
static GstElementStateReturn
|
|
gst_alsa_change_state (GstElement *element)
|
|
{
|
|
GstAlsa *this;
|
|
|
|
g_return_val_if_fail (element != NULL, FALSE);
|
|
this = GST_ALSA (element);
|
|
|
|
switch (GST_STATE_TRANSITION (element)) {
|
|
case GST_STATE_NULL_TO_READY:
|
|
if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN) == FALSE)
|
|
if (!gst_alsa_open_audio (this))
|
|
return GST_STATE_FAILURE;
|
|
break;
|
|
case GST_STATE_READY_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_PAUSED_TO_PLAYING:
|
|
if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING) == FALSE)
|
|
if (!gst_alsa_start_audio (this))
|
|
return GST_STATE_FAILURE;
|
|
|
|
case GST_STATE_PLAYING_TO_PAUSED:
|
|
if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING))
|
|
gst_alsa_drain_audio (this);
|
|
break;
|
|
case GST_STATE_PAUSED_TO_READY:
|
|
break;
|
|
case GST_STATE_READY_TO_NULL:
|
|
if (GST_FLAG_IS_SET (element, GST_ALSA_OPEN))
|
|
gst_alsa_close_audio (this);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
if (GST_ELEMENT_CLASS (parent_class)->change_state)
|
|
return GST_ELEMENT_CLASS (parent_class)->change_state (element);
|
|
|
|
return GST_STATE_SUCCESS;
|
|
}
|
|
|
|
/*** AUDIO PROCESSING *********************************************************/
|
|
|
|
static int
|
|
gst_alsa_do_mmap (GstAlsa *this, guint numpads, snd_pcm_sframes_t *avail)
|
|
{
|
|
snd_pcm_uframes_t offset;
|
|
snd_pcm_channel_area_t *dst, *src, *areas;
|
|
int i, err, width = snd_pcm_format_physical_width (this->format);
|
|
|
|
/* areas points to the memory areas that belong to gstreamer. */
|
|
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) {
|
|
areas = dst = calloc(this->channels, sizeof(snd_pcm_channel_area_t));
|
|
} else {
|
|
areas = src = calloc(this->channels, sizeof(snd_pcm_channel_area_t));
|
|
}
|
|
|
|
if (numpads == 1) {
|
|
/* interleaved */
|
|
for (i = 0; i < this->channels; i++) {
|
|
areas[i].addr = this->pads[0].data;
|
|
areas[i].first = i * width;
|
|
areas[i].step = this->channels * width;
|
|
}
|
|
} else {
|
|
/* noninterleaved */
|
|
for (i = 0; i < this->channels; i++) {
|
|
areas[i].addr = this->pads[i].data;
|
|
areas[i].first = 0;
|
|
areas[i].step = width;
|
|
}
|
|
}
|
|
|
|
/* might want to try to do some pointer sneakery in the first if block to get
|
|
rid of this second if block ... but right now i'm too sleepy */
|
|
if (G_OBJECT_TYPE (this) == GST_TYPE_ALSA_SRC) {
|
|
if ((err = snd_pcm_mmap_begin (this->handle, (const snd_pcm_channel_area_t **) &src, &offset, avail)) < 0) {
|
|
g_warning ("gstalsa: mmap failed: %s", snd_strerror (err));
|
|
return -1;
|
|
}
|
|
} else {
|
|
if ((err = snd_pcm_mmap_begin (this->handle, (const snd_pcm_channel_area_t **) &dst, &offset, avail)) < 0) {
|
|
g_warning ("gstalsa: mmap failed: %s", snd_strerror (err));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((err = snd_pcm_areas_copy (dst, offset, src, 0, this->channels, *avail, this->format)) < 0) {
|
|
snd_pcm_mmap_commit (this->handle, offset, 0);
|
|
g_warning ("gstalsa: data copy failed: %s", snd_strerror (err));
|
|
return -1;
|
|
}
|
|
if ((err = snd_pcm_mmap_commit (this->handle, offset, *avail)) < 0) {
|
|
g_warning ("gstalsa: mmap commit failed: %s", snd_strerror (err));
|
|
return -1;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
inline static snd_pcm_sframes_t
|
|
gst_alsa_update_avail (GstAlsa *this)
|
|
{
|
|
snd_pcm_sframes_t avail = snd_pcm_avail_update (this->handle);
|
|
if (avail < 0) {
|
|
if (avail == -EPIPE) {
|
|
gst_alsa_xrun_recovery (this);
|
|
return -EPIPE;
|
|
} else {
|
|
g_warning ("unknown ALSA avail_update return value (%d)", (int) avail);
|
|
return -1;
|
|
}
|
|
}
|
|
return avail;
|
|
}
|
|
|
|
inline static gint
|
|
gst_alsa_pcm_wait (GstAlsa *this)
|
|
{
|
|
if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) {
|
|
if (snd_pcm_wait (this->handle, 1000) < 0) {
|
|
if (errno == EINTR) {
|
|
/* happens mostly when run under gdb, or when exiting due to a signal */
|
|
GST_DEBUG (GST_CAT_PLUGIN_INFO, "got interrupted while waiting");
|
|
if (gst_element_interrupt (GST_ELEMENT (this)))
|
|
return -1;
|
|
}
|
|
g_warning ("error waiting for alsa pcm: (%d: %s)", errno, strerror (errno));
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
gst_alsa_sink_loop (GstElement *element)
|
|
{
|
|
snd_pcm_sframes_t avail, copied;
|
|
gint i;
|
|
gint bytes, num_bytes; /* per channel */
|
|
GstAlsa *this = GST_ALSA (element);
|
|
|
|
g_return_if_fail (this != NULL);
|
|
|
|
/* caps nego: fetch 1 byte from every pad */
|
|
if (this->format == SND_PCM_FORMAT_UNKNOWN) {
|
|
GST_DEBUG (GST_CAT_NEGOTIATION, "starting caps negotiation");
|
|
for (i = 0; i < element->numpads; i++) {
|
|
g_assert (this->pads[i].pad != NULL);
|
|
do {
|
|
num_bytes = gst_bytestream_peek_bytes (this->pads[i].bs, &this->pads[i].data, 1);
|
|
} while (num_bytes == 0 && gst_alsa_sink_check_event (this, i));
|
|
if (num_bytes == 0)
|
|
return;
|
|
}
|
|
if (this->format == SND_PCM_FORMAT_UNKNOWN) {
|
|
gst_element_error (GST_ELEMENT (this), "alsasink: No caps available");
|
|
}
|
|
}
|
|
|
|
sink_restart:
|
|
|
|
while (1) {
|
|
avail = gst_alsa_update_avail (this);
|
|
if (avail == -EPIPE) goto sink_restart;
|
|
if (avail == -1) break;
|
|
if (avail > 0) {
|
|
int width = snd_pcm_format_physical_width (this->format);
|
|
|
|
/* check how many bytes we still have in all our bytestreams */
|
|
bytes = avail * ( width / 8 ) * (element->numpads == 1 ? this->channels : 1);
|
|
for (i = 0; i < element->numpads; i++) {
|
|
g_assert (this->pads[i].pad != NULL);
|
|
do {
|
|
num_bytes = gst_bytestream_peek_bytes (this->pads[i].bs, &this->pads[i].data, bytes);
|
|
} while (num_bytes == 0 && gst_alsa_sink_check_event (this, i));
|
|
if (num_bytes == 0)
|
|
break;
|
|
bytes = MIN (bytes, num_bytes);
|
|
}
|
|
|
|
/* FIXME: lotsa stuff can have happened while fetching data. Do we need to check something? */
|
|
|
|
/* put this data into alsa */
|
|
avail = bytes / (width / 8 ) / (element->numpads == 1 ? this->channels : 1);
|
|
if ((copied = gst_alsa_do_mmap (this, element->numpads, &avail)) < 0)
|
|
break;
|
|
|
|
/* flush the data */
|
|
bytes = copied * ( width / 8 ) * (element->numpads == 1 ? this->channels : 1);
|
|
for (i = 0; i < element->numpads; i++)
|
|
gst_bytestream_flush (this->pads[i].bs, bytes);
|
|
|
|
/* BUG: we start the stream explicitly, autostart doesn't work correctly (alsa 0.9.0rc7) */
|
|
if (snd_pcm_state(this->handle) == SND_PCM_STATE_PREPARED && snd_pcm_avail_update (this->handle) == 0) {
|
|
GST_DEBUG (GST_CAT_PLUGIN_INFO, "Explicitly starting playback");
|
|
snd_pcm_start(this->handle);
|
|
}
|
|
}
|
|
|
|
/* wait */
|
|
if (gst_alsa_pcm_wait (this) < 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_alsa_src_loop (GstElement *element)
|
|
{
|
|
snd_pcm_sframes_t avail, copied;
|
|
GstBufferPool *pool = NULL;
|
|
GstBuffer *buf;
|
|
GstCaps *caps;
|
|
gint i;
|
|
GstAlsaPad *pad;
|
|
GstAlsa *this = GST_ALSA (element);
|
|
|
|
g_return_if_fail (this != NULL);
|
|
|
|
static gboolean caps_set = FALSE;
|
|
|
|
/* set the caps on all pads */
|
|
if (!caps_set) {
|
|
GST_DEBUG (GST_CAT_NEGOTIATION, "starting caps negotiation");
|
|
caps = gst_alsa_caps (this->format, this->rate, this->channels);
|
|
for (i = 0; i < element->numpads; i++) {
|
|
if (gst_pad_try_set_caps (this->pads[i].pad, caps) <= 0) {
|
|
GST_DEBUG (GST_CAT_NEGOTIATION, "setting caps (%p) in alsasrc (%p) on pad %d failed", caps, this, i);
|
|
return;
|
|
}
|
|
}
|
|
caps_set = TRUE;
|
|
}
|
|
|
|
src_restart:
|
|
|
|
while (1) {
|
|
avail = gst_alsa_update_avail (this);
|
|
if (avail == -EPIPE) goto src_restart;
|
|
if (avail == -1) break;
|
|
if (avail > 0) {
|
|
int width = snd_pcm_format_physical_width (this->format);
|
|
int bytes_per_frame = ( width / 8 ) * (element->numpads == 1 ? this->channels : 1);
|
|
|
|
if ((copied = gst_alsa_do_mmap (this, element->numpads, &avail)) < 0)
|
|
break;
|
|
|
|
/* we get the buffer pool once per go round */
|
|
if (! pool) pool = gst_alsa_src_get_buffer_pool (this->pads[0].pad);
|
|
|
|
/* push the data to gstreamer if it's big enough to fill up a buffer. */
|
|
for (i = 0; i < element->numpads; i++) {
|
|
pad = &this->pads[i];
|
|
pad->offset += MIN (copied, this->period_size - pad->offset);
|
|
|
|
if (pad->offset >= this->period_size) {
|
|
g_assert (pad->offset <= this->period_size);
|
|
|
|
buf = gst_buffer_new_from_pool (pool, 0, 0);
|
|
|
|
GST_BUFFER_DATA (buf) = pad->data;
|
|
GST_BUFFER_SIZE (buf) = this->period_size * bytes_per_frame;
|
|
GST_BUFFER_MAXSIZE (buf) = this->period_size * bytes_per_frame;
|
|
|
|
gst_pad_push (pad->pad, buf);
|
|
|
|
pad->data = NULL;
|
|
pad->offset = 0;
|
|
}
|
|
}
|
|
|
|
pool = NULL;
|
|
|
|
/* BUG: we start the stream explicitly, autostart doesn't work correctly (alsa 0.9.0rc7) */
|
|
if (snd_pcm_state(this->handle) == SND_PCM_STATE_PREPARED && snd_pcm_avail_update (this->handle) == 0) {
|
|
GST_DEBUG (GST_CAT_PLUGIN_INFO, "Explicitly starting capture");
|
|
snd_pcm_start(this->handle);
|
|
}
|
|
}
|
|
|
|
/* wait */
|
|
if (gst_alsa_pcm_wait (this) < 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_alsa_xrun_recovery (GstAlsa *this)
|
|
{
|
|
snd_pcm_status_t *status;
|
|
gint err;
|
|
|
|
snd_pcm_status_alloca (&status);
|
|
|
|
if ((err = snd_pcm_status (this->handle, status)) < 0)
|
|
g_warning ("status error: %s", snd_strerror (err));
|
|
|
|
if (snd_pcm_status_get_state (status) == SND_PCM_STATE_XRUN) {
|
|
struct timeval now, diff, tstamp;
|
|
|
|
gettimeofday (&now, 0);
|
|
snd_pcm_status_get_trigger_tstamp (status, &tstamp);
|
|
timersub (&now, &tstamp, &diff);
|
|
g_warning ("alsa: xrun of at least %.3f msecs", diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
|
|
|
|
/* if we're allowed to recover, ... */
|
|
if (this->autorecover) {
|
|
/* ... then increase the period size or buffer size / period count to
|
|
prevent further xruns (at the cost of increased latency and memory
|
|
usage). */
|
|
if (this->period_count >= 4) {
|
|
this->period_size *= 2;
|
|
this->period_count /= 2;
|
|
} else {
|
|
this->period_count *= 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(gst_alsa_stop_audio (this) && gst_alsa_start_audio (this))) {
|
|
gst_element_error (GST_ELEMENT (this), "alsasink: Error restarting audio after xrun");
|
|
}
|
|
}
|
|
|
|
/* TRUE, if everything should continue */
|
|
static gboolean
|
|
gst_alsa_sink_check_event (GstAlsa *this, gint pad_nr)
|
|
{
|
|
GstEvent *event = NULL;
|
|
guint32 avail;
|
|
gboolean cont = TRUE;
|
|
|
|
gst_bytestream_get_status (this->pads[pad_nr].bs, &avail, &event);
|
|
|
|
if (event) {
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
|
|
gst_element_set_eos (GST_ELEMENT (this));
|
|
cont = FALSE;
|
|
} else {
|
|
g_warning ("GstAlsaSink: got an unknown event (Type: %d)", GST_EVENT_TYPE (event));
|
|
}
|
|
gst_event_unref (event);
|
|
} else {
|
|
/* the element at the top of the chain did not emit an event. */
|
|
g_assert_not_reached ();
|
|
}
|
|
return cont;
|
|
}
|
|
|
|
/*** AUDIO SETUP / START / STOP ***********************************************/
|
|
|
|
static gboolean
|
|
gst_alsa_open_audio (GstAlsa *this)
|
|
{
|
|
g_assert (this != NULL);
|
|
g_assert (this->handle == NULL);
|
|
|
|
GST_INFO (GST_CAT_PLUGIN_INFO, "Opening alsa device \"%s\" for %s...\n", this->device,
|
|
this->stream == SND_PCM_STREAM_PLAYBACK ? "playback" : "capture");
|
|
|
|
ERROR_CHECK (snd_output_stdio_attach (&this->out, stdout, 0),
|
|
"error opening log output: %s");
|
|
/* blocking i/o */
|
|
ERROR_CHECK (snd_pcm_open (&this->handle, this->device, this->stream, 0),
|
|
"error opening pcm device %s: %s\n", this->device);
|
|
|
|
GST_FLAG_SET (this, GST_ALSA_OPEN);
|
|
return TRUE;
|
|
}
|
|
/* you must set all hw parameters at once - thx ALSA for not documenting this */
|
|
static gboolean
|
|
gst_alsa_set_hw_params (GstAlsa *this)
|
|
{
|
|
snd_pcm_hw_params_t *hw_params;
|
|
snd_pcm_access_mask_t *mask;
|
|
snd_pcm_uframes_t size_min, size_max;
|
|
unsigned int count_min, count_max;
|
|
|
|
/* whether to use default values when setting params */
|
|
gboolean def = (this->format == SND_PCM_FORMAT_UNKNOWN);
|
|
|
|
g_return_val_if_fail (this != NULL, FALSE);
|
|
g_return_val_if_fail (this->handle != NULL, FALSE);
|
|
|
|
GST_INFO (GST_CAT_PLUGIN_INFO, "Preparing channel: %s %dHz, %d channels\n",
|
|
snd_pcm_format_name (this->format), this->rate, this->channels);
|
|
|
|
snd_pcm_hw_params_alloca (&hw_params);
|
|
ERROR_CHECK (snd_pcm_hw_params_any (this->handle, hw_params),
|
|
"Broken configuration for this PCM: %s");
|
|
ERROR_CHECK (snd_pcm_hw_params_set_periods_integer (this->handle, hw_params),
|
|
"cannot restrict period size to integral value: %s");
|
|
|
|
mask = alloca (snd_pcm_access_mask_sizeof ());
|
|
snd_pcm_access_mask_none (mask);
|
|
if (GST_ELEMENT (this)->numpads == 1) {
|
|
snd_pcm_access_mask_set (mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
|
|
} else {
|
|
snd_pcm_access_mask_set (mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
|
|
}
|
|
ERROR_CHECK (snd_pcm_hw_params_set_access_mask (this->handle, hw_params, mask),
|
|
"The Gstreamer ALSA plugin does not support your hardware. Error: %s");
|
|
|
|
ERROR_CHECK (snd_pcm_hw_params_set_format (this->handle, hw_params, def ? SND_PCM_FORMAT_S16 : this->format),
|
|
"Sample format (%s) not available: %s", snd_pcm_format_name (def ? SND_PCM_FORMAT_S16 : this->format));
|
|
ERROR_CHECK (snd_pcm_hw_params_set_channels (this->handle, hw_params, this->channels),
|
|
"Channels count (%d) not available: %s", this->channels);
|
|
ERROR_CHECK (snd_pcm_hw_params_set_rate (this->handle, hw_params, def ? 44100 : this->rate, 0),
|
|
"error setting rate (%d): %s", def ? 44100 : this->rate);
|
|
|
|
if (snd_pcm_hw_params_get_period_size_min (hw_params, &size_min, 0) < 0) size_min = this->period_size;
|
|
if (snd_pcm_hw_params_get_period_size_max (hw_params, &size_max, 0) < 0) size_max = this->period_size;
|
|
g_assert (size_max >= size_min);
|
|
if (size_min > this->period_size) this->period_size = size_min;
|
|
if (size_max < this->period_size) this->period_size = size_max;
|
|
ERROR_CHECK (snd_pcm_hw_params_set_period_size (this->handle, hw_params, this->period_size, 0),
|
|
"error setting period size to %u frames: %s", (guint) this->period_size);
|
|
if (snd_pcm_hw_params_get_periods_min (hw_params, &count_min, 0) < 0) count_min = this->period_count;
|
|
if (snd_pcm_hw_params_get_periods_max (hw_params, &count_max, 0) < 0) count_max = this->period_count;
|
|
g_assert (count_max >= count_min);
|
|
if (count_min > this->period_count) this->period_count = count_min;
|
|
if (count_max < this->period_count) this->period_count = count_max;
|
|
ERROR_CHECK (snd_pcm_hw_params_set_buffer_size (this->handle, hw_params, this->period_size * this->period_count),
|
|
"error setting buffer size to %u: %s", (guint) (this->period_size * this->period_count));
|
|
|
|
ERROR_CHECK (snd_pcm_hw_params (this->handle, hw_params),
|
|
"Could not set hardware parameters: %s");
|
|
|
|
return TRUE;
|
|
}
|
|
static gboolean
|
|
gst_alsa_set_sw_params (GstAlsa *this)
|
|
{
|
|
snd_pcm_sw_params_t *sw_params;
|
|
|
|
snd_pcm_sw_params_alloca (&sw_params);
|
|
ERROR_CHECK (snd_pcm_sw_params_current (this->handle, sw_params),
|
|
"Could not get current software parameters: %s");
|
|
|
|
ERROR_CHECK (snd_pcm_sw_params_set_silence_size (this->handle, sw_params, 0),
|
|
"could not set silence size: %s");
|
|
ERROR_CHECK (snd_pcm_sw_params_set_silence_threshold (this->handle, sw_params, 0),
|
|
"could not set silence threshold: %s");
|
|
ERROR_CHECK (snd_pcm_sw_params_set_avail_min (this->handle, sw_params, this->period_size),
|
|
"could not set avail min: %s");
|
|
ERROR_CHECK (snd_pcm_sw_params_set_start_threshold (this->handle, sw_params, 1),
|
|
"could not set start mode: %s");
|
|
ERROR_CHECK (snd_pcm_sw_params_set_stop_threshold (this->handle, sw_params, this->period_size * this->period_count),
|
|
"could not set stop mode: %s");
|
|
ERROR_CHECK (snd_pcm_sw_params_set_xfer_align(this->handle, sw_params, 1),
|
|
"Unable to set transfer align for playback: %s");
|
|
ERROR_CHECK (snd_pcm_sw_params (this->handle, sw_params),
|
|
"could not set sw_params: %s");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_alsa_start_audio (GstAlsa *this)
|
|
{
|
|
if (!gst_alsa_set_hw_params (this))
|
|
return FALSE;
|
|
if (!gst_alsa_set_sw_params (this))
|
|
return FALSE;
|
|
|
|
GST_FLAG_SET (this, GST_ALSA_RUNNING);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_alsa_drain_audio (GstAlsa *this) {
|
|
g_assert (this != NULL);
|
|
g_return_val_if_fail (this != NULL, FALSE);
|
|
g_return_val_if_fail (this->handle != NULL, FALSE);
|
|
|
|
GST_DEBUG (GST_CAT_PLUGIN_INFO, "stopping alsa");
|
|
|
|
if (this->stream == SND_PCM_STREAM_PLAYBACK) {
|
|
ERROR_CHECK (snd_pcm_drain (this->handle),
|
|
"couldn't stop and drain buffer: %s");
|
|
}
|
|
|
|
GST_FLAG_UNSET (this, GST_ALSA_RUNNING);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_alsa_stop_audio (GstAlsa *this)
|
|
{
|
|
g_assert (this != NULL);
|
|
g_return_val_if_fail (this != NULL, FALSE);
|
|
g_return_val_if_fail (this->handle != NULL, FALSE);
|
|
|
|
GST_DEBUG (GST_CAT_PLUGIN_INFO, "stopping alsa, skipping pending frames");
|
|
|
|
if (this->stream == SND_PCM_STREAM_PLAYBACK) {
|
|
ERROR_CHECK (snd_pcm_drop (this->handle),
|
|
"couldn't stop (dropping frames): %s");
|
|
}
|
|
|
|
GST_FLAG_UNSET (this, GST_ALSA_RUNNING);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_alsa_close_audio (GstAlsa *this)
|
|
{
|
|
g_return_val_if_fail (this != NULL, FALSE);
|
|
g_return_val_if_fail (this->handle != NULL, FALSE);
|
|
|
|
ERROR_CHECK (snd_pcm_close (this->handle), "Error closing device: %s");
|
|
|
|
this->handle = NULL;
|
|
GST_FLAG_UNSET (this, GST_ALSA_OPEN);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*** GSTREAMER PLUGIN *********************************************************/
|
|
|
|
static gboolean
|
|
plugin_init (GModule * module, GstPlugin * plugin)
|
|
{
|
|
GstElementFactory *factory;
|
|
|
|
if (!gst_library_load ("gstbytestream"))
|
|
return FALSE;
|
|
|
|
factory = gst_element_factory_new ("alsasrc", GST_TYPE_ALSA_SRC, &gst_alsa_src_details);
|
|
g_return_val_if_fail (factory != NULL, FALSE);
|
|
gst_element_factory_add_pad_template (factory, gst_alsa_src_pad_factory ());
|
|
gst_element_factory_add_pad_template (factory, gst_alsa_src_request_pad_factory ());
|
|
gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
|
|
|
|
factory = gst_element_factory_new ("alsasink", GST_TYPE_ALSA_SINK, &gst_alsa_sink_details);
|
|
g_return_val_if_fail (factory != NULL, FALSE);
|
|
gst_element_factory_add_pad_template (factory, gst_alsa_sink_pad_factory ());
|
|
gst_element_factory_add_pad_template (factory, gst_alsa_sink_request_pad_factory ());
|
|
gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
|
|
|
|
gst_plugin_set_longname (plugin, "ALSA plugin library");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GstPluginDesc plugin_desc = {
|
|
GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
"alsa",
|
|
plugin_init
|
|
};
|