/* * 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 GstClockTime gst_alsa_src_get_time (GstAlsa * this); 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, "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); 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]); this->clock = gst_alsa_clock_new ("alsasrcclock", gst_alsa_src_get_time, this); /* we hold a ref to our clock until we're disposed */ gst_object_ref (GST_OBJECT (this->clock)); gst_object_sink (GST_OBJECT (this->clock)); 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; } } while ((avail = gst_alsa_update_avail (this)) < this->period_size) { if (avail == -EPIPE) continue; if (avail < 0) 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]) { src->buf[i] = gst_buffer_new_and_alloc (4096); } } /* fill buffer with data */ if ((copied = this->transmit (this, &avail)) <= 0) return; /* push the buffers out and let them have fun */ for (i = 0; i < element->numpads; i++) { if (!src->buf[i]) return; if (copied != this->period_size) GST_BUFFER_SIZE (src->buf[i]) = gst_alsa_samples_to_bytes (this, copied); GST_BUFFER_TIMESTAMP (src->buf[i]) = gst_alsa_samples_to_timestamp (this, this->transmitted); GST_BUFFER_DURATION (src->buf[i]) = gst_alsa_samples_to_timestamp (this, copied); gst_pad_push (this->pad[i], GST_DATA (src->buf[i])); src->buf[i] = NULL; } 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: case GST_STATE_PAUSED_TO_PLAYING: 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; } static GstClockTime gst_alsa_src_get_time (GstAlsa *this) { snd_pcm_sframes_t delay; if (snd_pcm_delay (this->handle, &delay) == 0) { return GST_SECOND * (this->transmitted + delay) / this->format->rate; } else { return 0; } }