mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-12 18:35:35 +00:00
a48818210d
Original commit message from CVS: * ext/alsa/gstalsa.c: (gst_alsa_init), (gst_alsa_dispose), (gst_alsa_get_time), (gst_alsa_xrun_recovery): * ext/alsa/gstalsa.h: * ext/alsa/gstalsaclock.c: (gst_alsa_clock_get_type): * ext/alsa/gstalsasrc.c: (gst_alsa_src_init), (gst_alsa_src_loop), (gst_alsa_src_change_state): * ext/alsa/gstalsasrc.h: Make the xrun code timestamp and offset the buffers correctly. moved the clock to the base class, use alsa methods to get time. Do correct timestamping on outgoing buffers.
441 lines
13 KiB
C
441 lines
13 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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstalsasrc.h"
|
|
#include "gstalsaclock.h"
|
|
|
|
/* elementfactory information */
|
|
static GstElementDetails gst_alsa_src_details = GST_ELEMENT_DETAILS ("Alsa Src",
|
|
"Source/Audio",
|
|
"Read from a sound card via ALSA",
|
|
"Thomas Nyberg <thomas@codefactory.se>, "
|
|
"Andy Wingo <apwingo@eos.ncsu.edu>, "
|
|
"Benjamin Otte <in7y118@public.uni-hamburg.de>");
|
|
|
|
static GstPadTemplate *gst_alsa_src_pad_factory (void);
|
|
static void gst_alsa_src_base_init (gpointer g_class);
|
|
static void gst_alsa_src_class_init (gpointer g_class, gpointer class_data);
|
|
static void gst_alsa_src_init (GstAlsaSrc * this);
|
|
static int gst_alsa_src_mmap (GstAlsa * this, snd_pcm_sframes_t * avail);
|
|
static int gst_alsa_src_read (GstAlsa * this, snd_pcm_sframes_t * avail);
|
|
static void gst_alsa_src_loop (GstElement * element);
|
|
static void gst_alsa_src_flush (GstAlsaSrc * src);
|
|
static GstElementStateReturn gst_alsa_src_change_state (GstElement * element);
|
|
|
|
static GstAlsa *src_parent_class = NULL;
|
|
|
|
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));
|
|
|
|
return template;
|
|
}
|
|
|
|
GType
|
|
gst_alsa_src_get_type (void)
|
|
{
|
|
static GType alsa_src_type = 0;
|
|
|
|
if (!alsa_src_type) {
|
|
static const GTypeInfo alsa_src_info = {
|
|
sizeof (GstAlsaSrcClass),
|
|
gst_alsa_src_base_init,
|
|
NULL,
|
|
gst_alsa_src_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstAlsaSrc),
|
|
0,
|
|
(GInstanceInitFunc) gst_alsa_src_init,
|
|
};
|
|
|
|
alsa_src_type =
|
|
g_type_register_static (GST_TYPE_ALSA_MIXER, "GstAlsaSrc",
|
|
&alsa_src_info, 0);
|
|
}
|
|
return alsa_src_type;
|
|
}
|
|
|
|
static void
|
|
gst_alsa_src_base_init (gpointer g_class)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_alsa_src_pad_factory ());
|
|
|
|
gst_element_class_set_details (element_class, &gst_alsa_src_details);
|
|
}
|
|
|
|
static void
|
|
gst_alsa_src_class_init (gpointer g_class, gpointer class_data)
|
|
{
|
|
GObjectClass *object_class;
|
|
GstElementClass *element_class;
|
|
GstAlsaClass *alsa_class;
|
|
GstAlsaSrcClass *klass;
|
|
|
|
klass = (GstAlsaSrcClass *) g_class;
|
|
object_class = (GObjectClass *) klass;
|
|
element_class = (GstElementClass *) klass;
|
|
alsa_class = (GstAlsaClass *) klass;
|
|
|
|
if (src_parent_class == NULL)
|
|
src_parent_class = g_type_class_ref (GST_TYPE_ALSA_MIXER);
|
|
|
|
alsa_class->stream = SND_PCM_STREAM_CAPTURE;
|
|
alsa_class->transmit_mmap = gst_alsa_src_mmap;
|
|
alsa_class->transmit_rw = gst_alsa_src_read;
|
|
|
|
element_class->change_state = gst_alsa_src_change_state;
|
|
}
|
|
|
|
static void
|
|
gst_alsa_src_init (GstAlsaSrc * src)
|
|
{
|
|
GstAlsa *this = GST_ALSA (src);
|
|
|
|
this->pad[0] = gst_pad_new_from_template (gst_alsa_src_pad_factory (), "src");
|
|
gst_pad_set_link_function (this->pad[0], gst_alsa_link);
|
|
gst_pad_set_getcaps_function (this->pad[0], gst_alsa_get_caps);
|
|
gst_element_add_pad (GST_ELEMENT (this), this->pad[0]);
|
|
|
|
gst_element_set_loop_function (GST_ELEMENT (this), gst_alsa_src_loop);
|
|
}
|
|
|
|
static int
|
|
gst_alsa_src_mmap (GstAlsa * this, snd_pcm_sframes_t * avail)
|
|
{
|
|
snd_pcm_uframes_t offset;
|
|
snd_pcm_channel_area_t *dst;
|
|
const snd_pcm_channel_area_t *src;
|
|
int i, err, width = snd_pcm_format_physical_width (this->format->format);
|
|
GstAlsaSrc *alsa_src = GST_ALSA_SRC (this);
|
|
|
|
/* areas points to the memory areas that belong to gstreamer. */
|
|
dst = g_malloc0 (this->format->channels * sizeof (snd_pcm_channel_area_t));
|
|
|
|
if (((GstElement *) this)->numpads == 1) {
|
|
/* interleaved */
|
|
for (i = 0; i < this->format->channels; i++) {
|
|
dst[i].addr = alsa_src->buf[0]->data;
|
|
dst[i].first = i * width;
|
|
dst[i].step = this->format->channels * width;
|
|
}
|
|
} else {
|
|
/* noninterleaved */
|
|
for (i = 0; i < this->format->channels; i++) {
|
|
dst[i].addr = alsa_src->buf[i]->data;
|
|
dst[i].first = 0;
|
|
dst[i].step = width;
|
|
}
|
|
}
|
|
|
|
if ((err = snd_pcm_mmap_begin (this->handle, &src, &offset, avail)) < 0) {
|
|
GST_ERROR_OBJECT (this, "mmap failed: %s", snd_strerror (err));
|
|
return -1;
|
|
}
|
|
if (*avail > 0
|
|
&& (err =
|
|
snd_pcm_areas_copy (dst, 0, src, offset, this->format->channels,
|
|
*avail, this->format->format)) < 0) {
|
|
snd_pcm_mmap_commit (this->handle, offset, 0);
|
|
GST_ERROR_OBJECT (this, "data copy failed: %s", snd_strerror (err));
|
|
return -1;
|
|
}
|
|
if ((err = snd_pcm_mmap_commit (this->handle, offset, *avail)) < 0) {
|
|
GST_ERROR_OBJECT (this, "mmap commit failed: %s", snd_strerror (err));
|
|
return -1;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
static int
|
|
gst_alsa_src_read (GstAlsa * this, snd_pcm_sframes_t * avail)
|
|
{
|
|
void *channels[this->format->channels];
|
|
int err, i;
|
|
GstAlsaSrc *src = GST_ALSA_SRC (this);
|
|
|
|
if (((GstElement *) this)->numpads == 1) {
|
|
/* interleaved */
|
|
err = snd_pcm_readi (this->handle, src->buf[0]->data, *avail);
|
|
} else {
|
|
/* noninterleaved */
|
|
for (i = 0; i < this->format->channels; i++) {
|
|
channels[i] = src->buf[i]->data;
|
|
}
|
|
err = snd_pcm_readn (this->handle, channels, *avail);
|
|
}
|
|
/* error handling */
|
|
if (err < 0) {
|
|
if (err == -EPIPE) {
|
|
gst_alsa_xrun_recovery (this);
|
|
return 0;
|
|
}
|
|
GST_ERROR_OBJECT (this, "error on data access: %s", snd_strerror (err));
|
|
}
|
|
return err;
|
|
}
|
|
static inline gint
|
|
gst_alsa_src_adjust_rate (gint rate, gboolean aggressive)
|
|
{
|
|
static gint rates[] = { 96000, 48000, 44100, 22050, 8000 };
|
|
gint i;
|
|
|
|
if (aggressive)
|
|
return rate;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (rates); i++) {
|
|
if (rate >= rates[i])
|
|
return rates[i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean
|
|
gst_alsa_src_set_caps (GstAlsaSrc * src, gboolean aggressive)
|
|
{
|
|
GstCaps *all_caps, *caps;
|
|
GstStructure *structure, *walk;
|
|
gint channels, min_channels, max_channels;
|
|
gint rate, min_rate, max_rate;
|
|
gint i, endian, width, depth;
|
|
gboolean sign;
|
|
GstAlsa *this = GST_ALSA (src);
|
|
|
|
all_caps = gst_alsa_get_caps (this->pad[0]);
|
|
if (all_caps == NULL)
|
|
return FALSE;
|
|
/* now intersect this with all caps of the peers... */
|
|
for (i = 0; i < GST_ELEMENT (src)->numpads; i++) {
|
|
all_caps =
|
|
gst_caps_intersect (all_caps, gst_pad_get_allowed_caps (this->pad[i]));
|
|
if (all_caps == NULL) {
|
|
GST_DEBUG ("No compatible caps found in alsasrc (%s)",
|
|
GST_ELEMENT_NAME (this));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* construct caps */
|
|
caps = gst_caps_new_simple ("audio/x-raw-int", NULL);
|
|
g_assert (gst_caps_get_size (caps) == 1);
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
|
|
/* now try to find the best match */
|
|
for (i = 0; i < gst_caps_get_size (all_caps); i++) {
|
|
walk = gst_caps_get_structure (all_caps, i);
|
|
if (!(gst_structure_get_int (walk, "signed", &sign) &&
|
|
gst_structure_get_int (walk, "width", &width) &&
|
|
gst_structure_get_int (walk, "depth", &depth))) {
|
|
GST_ERROR_OBJECT (src, "couldn't parse my own format. Huh?");
|
|
continue;
|
|
}
|
|
if (!gst_structure_get_int (walk, "endianness", &endian)) {
|
|
endian = G_BYTE_ORDER;
|
|
}
|
|
gst_structure_set (structure,
|
|
"endianness", G_TYPE_INT, endian,
|
|
"width", G_TYPE_INT, width,
|
|
"depth", G_TYPE_INT, depth, "signed", G_TYPE_BOOLEAN, sign, NULL);
|
|
|
|
min_rate =
|
|
gst_value_get_int_range_min (gst_structure_get_value (walk, "rate"));
|
|
max_rate =
|
|
gst_value_get_int_range_max (gst_structure_get_value (walk, "rate"));
|
|
min_channels =
|
|
gst_value_get_int_range_min (gst_structure_get_value (walk,
|
|
"channels"));
|
|
max_channels =
|
|
gst_value_get_int_range_max (gst_structure_get_value (walk,
|
|
"channels"));
|
|
for (rate = max_rate;; rate--) {
|
|
if ((rate = gst_alsa_src_adjust_rate (rate, aggressive)) < min_rate)
|
|
break;
|
|
gst_structure_set (structure, "rate", G_TYPE_INT, rate, NULL);
|
|
for (channels = aggressive ? max_channels : MIN (max_channels, 2);
|
|
channels >= min_channels; channels--) {
|
|
gst_structure_set (structure, "channels", G_TYPE_INT, channels, NULL);
|
|
GST_DEBUG
|
|
("trying new caps: %ssigned, endianness: %d, width %d, depth %d, channels %d, rate %d",
|
|
sign ? "" : "un", endian, width, depth, channels, rate);
|
|
if (gst_pad_try_set_caps (this->pad[0], caps) != GST_PAD_LINK_REFUSED)
|
|
gst_alsa_link (this->pad[0], caps);
|
|
|
|
if (this->format) {
|
|
/* try to set caps here */
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!aggressive)
|
|
return gst_alsa_src_set_caps (src, TRUE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* we transmit buffers of period_size frames */
|
|
static void
|
|
gst_alsa_src_loop (GstElement * element)
|
|
{
|
|
snd_pcm_sframes_t avail, copied;
|
|
gint i;
|
|
GstAlsa *this = GST_ALSA (element);
|
|
GstAlsaSrc *src = GST_ALSA_SRC (element);
|
|
|
|
/* set the caps on all pads */
|
|
if (!this->format) {
|
|
if (!gst_alsa_src_set_caps (src, FALSE)) {
|
|
GST_ELEMENT_ERROR (element, CORE, NEGOTIATION, (NULL),
|
|
("ALSA format not negotiated"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* the cast to long is explicitly needed;
|
|
* with avail = -32 and period_size = 100, avail < period_size is false */
|
|
while ((avail = gst_alsa_update_avail (this)) < (long) this->period_size) {
|
|
if (avail == -EPIPE) {
|
|
GST_DEBUG_OBJECT (this, "got EPIPE when checking for available bytes");
|
|
continue;
|
|
}
|
|
if (avail < 0) {
|
|
GST_DEBUG_OBJECT (this,
|
|
"got error %s (%d) when checking for available bytes",
|
|
snd_strerror (avail));
|
|
return;
|
|
}
|
|
if (snd_pcm_state (this->handle) != SND_PCM_STATE_RUNNING) {
|
|
if (!gst_alsa_start (this))
|
|
return;
|
|
continue;
|
|
};
|
|
/* wait */
|
|
if (gst_alsa_pcm_wait (this) == FALSE)
|
|
return;
|
|
}
|
|
g_assert (avail >= this->period_size);
|
|
/* make sure every pad has a buffer */
|
|
for (i = 0; i < element->numpads; i++) {
|
|
if (src->buf[i])
|
|
gst_data_unref (GST_DATA (src->buf[i]));
|
|
src->buf[i] =
|
|
gst_buffer_new_and_alloc (gst_alsa_samples_to_bytes (this, avail));
|
|
}
|
|
|
|
/* fill buffer with data */
|
|
if ((copied = this->transmit (this, &avail)) <= 0)
|
|
return;
|
|
|
|
{
|
|
gint outsize;
|
|
GstClockTime outtime, outdur;
|
|
|
|
outsize = gst_alsa_samples_to_bytes (this, copied);
|
|
outdur = gst_alsa_samples_to_timestamp (this, copied);
|
|
outtime = GST_CLOCK_TIME_NONE;
|
|
|
|
if (GST_ELEMENT_CLOCK (this)) {
|
|
if (GST_CLOCK (GST_ALSA (this)->clock) == GST_ELEMENT_CLOCK (this)) {
|
|
outtime = gst_alsa_samples_to_timestamp (this, this->transmitted);
|
|
} else {
|
|
outtime = gst_element_get_time (GST_ELEMENT (this));
|
|
}
|
|
}
|
|
|
|
/* push the buffers out and let them have fun */
|
|
for (i = 0; i < element->numpads; i++) {
|
|
GstBuffer *buf;
|
|
|
|
if (!src->buf[i])
|
|
return;
|
|
if (copied != this->period_size)
|
|
GST_BUFFER_SIZE (src->buf[i]) = outsize;
|
|
|
|
GST_BUFFER_TIMESTAMP (src->buf[i]) = outtime;
|
|
GST_BUFFER_DURATION (src->buf[i]) = outdur;
|
|
GST_BUFFER_OFFSET (src->buf[i]) = this->transmitted;
|
|
GST_BUFFER_OFFSET_END (src->buf[i]) = this->transmitted + copied;
|
|
|
|
buf = src->buf[i];
|
|
src->buf[i] = NULL;
|
|
gst_pad_push (this->pad[i], GST_DATA (buf));
|
|
}
|
|
this->transmitted += copied;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_alsa_src_flush (GstAlsaSrc * src)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < GST_ELEMENT (src)->numpads; i++) {
|
|
if (src->buf[i]) {
|
|
gst_buffer_unref (src->buf[i]);
|
|
src->buf[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static GstElementStateReturn
|
|
gst_alsa_src_change_state (GstElement * element)
|
|
{
|
|
GstAlsaSrc *src;
|
|
|
|
g_return_val_if_fail (element != NULL, FALSE);
|
|
src = GST_ALSA_SRC (element);
|
|
|
|
switch (GST_STATE_TRANSITION (element)) {
|
|
case GST_STATE_NULL_TO_READY:
|
|
case GST_STATE_READY_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_PAUSED_TO_PLAYING:
|
|
break;
|
|
case GST_STATE_PLAYING_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_PAUSED_TO_READY:
|
|
gst_alsa_src_flush (src);
|
|
break;
|
|
case GST_STATE_READY_TO_NULL:
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
if (GST_ELEMENT_CLASS (src_parent_class)->change_state)
|
|
return GST_ELEMENT_CLASS (src_parent_class)->change_state (element);
|
|
|
|
return GST_STATE_SUCCESS;
|
|
}
|