/* * Copyright (C) 2001 CodeFactory AB * Copyright (C) 2001 Thomas Nyberg * Copyright (C) 2001-2002 Andy Wingo * Copyright (C) 2003 Benjamin Otte * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "gstalsa.h" /* error checking for standard alsa functions */ #define SIMPLE_ERROR_CHECK(value) G_STMT_START{ \ int err = (value); \ if (err < 0) { \ return FALSE; \ } \ }G_STMT_END #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 , " "Andy Wingo , " "Benjamin Otte ", "(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 , " "Andy Wingo , " "Benjamin Otte ", "(C) 2001-2003" }; /* GObject functions */ static void gst_alsa_class_init (GstAlsaClass * klass); static void gst_alsa_init (GstAlsa * this); static void gst_alsa_dispose (GObject * object); 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); /* GstAlsaSink functions */ static GstPadTemplate * gst_alsa_sink_pad_factory (void); static GstPadTemplate * gst_alsa_sink_request_pad_factory (void); static void gst_alsa_sink_class_init (GstAlsaSinkClass * klass); static void gst_alsa_sink_init (GstAlsaSink * this); static inline void gst_alsa_sink_flush_one_pad (GstAlsaSink * sink, gint i); static void gst_alsa_sink_flush_pads (GstAlsaSink * sink); static int gst_alsa_sink_mmap (GstAlsa * this, snd_pcm_sframes_t * avail); static int gst_alsa_sink_write (GstAlsa * this, snd_pcm_sframes_t * avail); static void gst_alsa_sink_loop (GstElement * element); static gboolean gst_alsa_sink_check_event (GstAlsaSink * sink, gint pad_nr); static GstElementStateReturn gst_alsa_sink_change_state (GstElement * element); /* GstAlsaSrc functions */ static GstPadTemplate * gst_alsa_src_pad_factory (void); static GstPadTemplate * gst_alsa_src_request_pad_factory (void); static void gst_alsa_src_class_init (GstAlsaSrcClass * klass); 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); /* GStreamer functions for pads, queries, conversions and state changing */ 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_get_caps (GstPad * pad, GstCaps * caps); static GstCaps * gst_alsa_caps (snd_pcm_format_t format, gint rate, gint channels); static GstElementStateReturn gst_alsa_change_state (GstElement * element); static const GstFormat * gst_alsa_get_formats (GstPad * pad); static gboolean gst_alsa_convert (GstAlsa * this, GstFormat src_format, gint64 src_value, GstFormat * dest_format, gint64 * dest_value); static gboolean gst_alsa_pad_convert (GstPad * pad, GstFormat src_format, gint64 src_value, GstFormat * dest_format, gint64 * dest_value); static const GstQueryType * gst_alsa_get_query_types (GstPad * pad); static gboolean gst_alsa_query_func (GstElement * element, GstQueryType type, GstFormat * format, gint64 * value); static gboolean gst_alsa_query (GstElement * element, GstQueryType type, GstFormat * format, gint64 * value); static gboolean gst_alsa_pad_query (GstPad * pad, GstQueryType type, GstFormat * format, gint64 * value); /* audio processing functions */ static void gst_alsa_xrun_recovery (GstAlsa * this); inline static snd_pcm_sframes_t gst_alsa_update_avail (GstAlsa * this); inline static gboolean gst_alsa_pcm_wait (GstAlsa * this); inline static gboolean gst_alsa_start (GstAlsa * this); /* alsa setup / start / stop functions */ static void gst_alsa_set_eos (GstAlsa * this); static gboolean gst_alsa_probe_hw_params (GstAlsa * this, GstAlsaFormat * format); 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); /* clock functions */ static void gst_alsa_clock_class_init (GstAlsaClockClass * klass); static void gst_alsa_clock_init (GstAlsaClock * clock); static GstAlsaClock * gst_alsa_clock_new (gchar *name, GstAlsaClockGetTimeFunc func, GstAlsa* owner); static void gst_alsa_clock_start (GstAlsaClock * clock); static void gst_alsa_clock_stop (GstAlsaClock * clock); static GstClockTime gst_alsa_clock_get_internal_time (GstClock * clock); static guint64 gst_alsa_clock_get_resolution (GstClock * clock); static GstClockEntryStatus gst_alsa_clock_wait (GstClock * clock, GstClockEntry * entry); static void gst_alsa_clock_unlock (GstClock * clock, GstClockEntry * entry); static GstClockTime gst_alsa_sink_get_time (GstAlsa * this); static GstClockTime gst_alsa_src_get_time (GstAlsa * this); static GstClock * gst_alsa_get_clock (GstElement * element); static void gst_alsa_set_clock (GstElement * element, GstClock * clock); static GstClockClass * clock_parent_class = NULL; /* static guint gst_alsa_clock_signals[LAST_SIGNAL] = { 0 }; */ /* format conversions */ static inline snd_pcm_uframes_t gst_alsa_timestamp_to_samples (GstAlsa * this, GstClockTime time); static inline GstClockTime gst_alsa_samples_to_timestamp (GstAlsa * this, snd_pcm_uframes_t samples); static inline snd_pcm_uframes_t gst_alsa_bytes_to_samples (GstAlsa * this, guint bytes); static inline guint gst_alsa_samples_to_bytes (GstAlsa * this, snd_pcm_uframes_t samples); static inline GstClockTime gst_alsa_bytes_to_timestamp (GstAlsa * this, guint bytes); static inline guint gst_alsa_timestamp_to_bytes (GstAlsa * this, GstClockTime time); /*** TYPE FUNCTIONS ***********************************************************/ GType gst_alsa_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_ELEMENT, "GstAlsa", &alsa_info, 0); } return alsa_type; } /*** GOBJECT FUNCTIONS ********************************************************/ enum { ARG_0, ARG_DEVICE, ARG_PERIODCOUNT, ARG_PERIODSIZE, ARG_BUFFERSIZE, ARG_AUTORECOVER, ARG_MMAP, ARG_MAXDISCONT }; 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->dispose = gst_alsa_dispose; 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)); 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", 2, 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", 4, 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)); g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MMAP, g_param_spec_boolean ("mmap", "Use mmap'ed access", "Wether to use mmap (faster) or standard read/write (more compatible)", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MAXDISCONT, g_param_spec_uint64 ("max-discont", "Maximum Discontinuity", "GStreamer timeunits before the timestamp syncing starts dropping/insertting samples", /* rounding errors */ 1000, GST_SECOND, GST_ALSA_DEFAULT_DISCONT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); element_class->change_state = GST_DEBUG_FUNCPTR (gst_alsa_change_state); element_class->query = GST_DEBUG_FUNCPTR (gst_alsa_query); element_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_alsa_request_new_pad); element_class->set_clock = GST_DEBUG_FUNCPTR (gst_alsa_set_clock); element_class->get_clock = GST_DEBUG_FUNCPTR (gst_alsa_get_clock); } static void gst_alsa_init (GstAlsa *this) { GST_FLAG_SET (this, GST_ELEMENT_EVENT_AWARE); GST_FLAG_SET (this, GST_ELEMENT_THREAD_SUGGESTED); } static void gst_alsa_dispose (GObject *object) { GstAlsa *this = GST_ALSA (object); if (this->clock) gst_object_unparent (GST_OBJECT (this->clock)); G_OBJECT_CLASS (parent_class)->dispose (object); } 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_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; case ARG_MMAP: this->mmap = g_value_get_boolean (value); return; case ARG_MAXDISCONT: this->max_discont = (GstClockTime) g_value_get_uint64 (value); return; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 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_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; case ARG_MMAP: g_value_set_boolean (value, this->mmap); break; case ARG_MAXDISCONT: g_value_set_uint64 (value, (guint64) this->max_discont); return; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /*** GSTALSASINK FUNCTIONS ****************************************************/ static GstAlsa *sink_parent_class = NULL; 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; } GType gst_alsa_sink_get_type (void) { static GType alsa_sink_type = 0; if (!alsa_sink_type) { static const GTypeInfo alsa_sink_info = { sizeof (GstAlsaSinkClass), NULL, NULL, (GClassInitFunc) gst_alsa_sink_class_init, NULL, NULL, sizeof (GstAlsaSink), 0, (GInstanceInitFunc) gst_alsa_sink_init, }; alsa_sink_type = g_type_register_static (GST_TYPE_ALSA, "GstAlsaSink", &alsa_sink_info, 0); } return alsa_sink_type; } static void gst_alsa_sink_class_init (GstAlsaSinkClass *klass) { GObjectClass *object_class; GstElementClass *element_class; GstAlsaClass *alsa_class; object_class = (GObjectClass *) klass; element_class = (GstElementClass *) klass; alsa_class = (GstAlsaClass *) klass; if (sink_parent_class == NULL) sink_parent_class = g_type_class_ref (GST_TYPE_ALSA); alsa_class->stream = SND_PCM_STREAM_PLAYBACK; alsa_class->transmit_mmap = gst_alsa_sink_mmap; alsa_class->transmit_rw = gst_alsa_sink_write; element_class->change_state = gst_alsa_sink_change_state; } static void gst_alsa_sink_init (GstAlsaSink *sink) { GstAlsa *this = GST_ALSA (sink); this->pad[0] = gst_pad_new_from_template (gst_alsa_sink_pad_factory (), "sink"); 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 ("alsasinkclock", gst_alsa_sink_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_sink_loop); } static inline void gst_alsa_sink_flush_one_pad (GstAlsaSink *sink, gint i) { switch (sink->behaviour[i]) { case 0: if (sink->buf[i]) gst_data_unref (GST_DATA (sink->buf[i])); sink->buf[i] = NULL; sink->data[i] = NULL; sink->behaviour[i] = 0; sink->size[i] = 0; break; case 1: g_free (sink->data[i]); sink->data[i] = NULL; sink->behaviour[i] = 0; sink->size[i] = 0; break; default: g_assert_not_reached (); } } static void gst_alsa_sink_flush_pads (GstAlsaSink *sink) { gint i; for (i = 0; i < GST_ELEMENT (sink)->numpads; i++) { /* flush twice to unref buffer when behaviour == 1 */ gst_alsa_sink_flush_one_pad (sink, i); gst_alsa_sink_flush_one_pad (sink, i); } } /* TRUE, if everything should continue */ static gboolean gst_alsa_sink_check_event (GstAlsaSink *sink, gint pad_nr) { gboolean cont = TRUE; GstEvent *event = GST_EVENT (sink->buf[pad_nr]); GstAlsa *this = GST_ALSA (sink); if (event) { switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: gst_alsa_set_eos (this); cont = FALSE; break; case GST_EVENT_INTERRUPT: cont = FALSE; break; case GST_EVENT_DISCONTINUOUS: { gint64 value; /* only the first pad my seek */ if (pad_nr != 0) { break; } if (gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) { if (!gst_clock_handle_discont (GST_ELEMENT (this)->clock, value)) g_printerr ("GstAlsa: clock couldn't handle discontinuity\n"); } if (!gst_event_discont_get_value (event, GST_FORMAT_DEFAULT, &value)) { if (!gst_event_discont_get_value (event, GST_FORMAT_BYTES, &value)) { if (!gst_event_discont_get_value (event, GST_FORMAT_TIME, &value)) { g_warning ("GstAlsa: Could not acquire samplecount after seek, the clock might screw your pipeline now"); break; } else { if (this->format) /* discont event before any data (and before any caps...) */ value = gst_alsa_timestamp_to_samples (this, value); } } else { if (this->format) /* discont event before any data (and before any caps...) */ value = gst_alsa_bytes_to_samples (this, value); } } if (GST_CLOCK_TIME_IS_VALID (this->clock->start_time)) { /* if the clock is running */ g_assert (this->format); /* adjust the start time */ this->clock->start_time += gst_alsa_samples_to_timestamp (this, this->transmitted) - gst_alsa_samples_to_timestamp (this, value); } this->transmitted = value; break; } default: g_warning ("GstAlsa: got an unknown event (Type: %d)", GST_EVENT_TYPE (event)); break; } gst_event_unref (event); sink->buf[pad_nr] = NULL; } else { /* the element at the top of the chain did not emit an event. */ g_assert_not_reached (); } return cont; } static int gst_alsa_sink_mmap (GstAlsa *this, snd_pcm_sframes_t *avail) { snd_pcm_uframes_t offset; const snd_pcm_channel_area_t *dst; snd_pcm_channel_area_t *src; GstAlsaSink *sink = GST_ALSA_SINK (this); int i, err, width = snd_pcm_format_physical_width (this->format->format); /* areas points to the memory areas that belong to gstreamer. */ src = calloc(this->format->channels, sizeof(snd_pcm_channel_area_t)); if (((GstElement *) this)->numpads == 1) { /* interleaved */ for (i = 0; i < this->format->channels; i++) { src[i].addr = sink->data[0]; src[i].first = i * width; src[i].step = this->format->channels * width; } } else { /* noninterleaved */ for (i = 0; i < this->format->channels; i++) { src[i].addr = sink->data[i]; src[i].first = 0; src[i].step = width; } } if ((err = snd_pcm_mmap_begin (this->handle, &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->format->channels, *avail, this->format->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; } static int gst_alsa_sink_write (GstAlsa *this, snd_pcm_sframes_t *avail) { GstAlsaSink *sink = GST_ALSA_SINK (this); void *channels[this->format->channels]; int err, i; if (((GstElement *) this)->numpads == 1) { /* interleaved */ err = snd_pcm_writei (this->handle, sink->data[0], *avail); } else { /* noninterleaved */ for (i = 0; i < this->format->channels; i++) { channels[i] = sink->data[i]; } err = snd_pcm_writen (this->handle, channels, *avail); } /* error handling */ if (err < 0) { if (err == -EPIPE) { gst_alsa_xrun_recovery (this); return 0; } g_warning ("error on data access: %s", snd_strerror (err)); } return err; } static void gst_alsa_sink_loop (GstElement *element) { snd_pcm_sframes_t avail, avail2, copied; snd_pcm_uframes_t samplestamp; gint i; guint bytes; /* per channel */ GstAlsa *this = GST_ALSA (element); GstAlsaSink *sink = GST_ALSA_SINK (element); g_return_if_fail (sink != NULL); sink_restart: avail = gst_alsa_update_avail (this); if (avail == -EPIPE) goto sink_restart; if (avail < 0) return; if (avail > 0) { /* Not enough space. We grab data nonetheless and sleep afterwards */ if (avail < this->period_size) { avail = this->period_size; } /* check how many bytes we still have in all our bytestreams */ /* initialize this value to a somewhat sane state, we might alloc this much data below (which would be a bug, but who knows)... */ bytes = this->period_size * this->period_count * element->numpads * 8; /* must be > max sample size in bytes */ for (i = 0; i < element->numpads; i++) { g_assert (this->pad[i] != NULL); while (sink->size[i] == 0) { if (!sink->buf[i]) sink->buf[i] = gst_pad_pull (this->pad[i]); if (GST_IS_EVENT (sink->buf[i])) { if (gst_alsa_sink_check_event (sink, i)) continue; return; } /* caps nego failed somewhere */ if (this->format == NULL) { gst_element_error (GST_ELEMENT (this), "alsasink: No caps available"); return; } samplestamp = gst_alsa_timestamp_to_samples (this, GST_BUFFER_TIMESTAMP (sink->buf[i])); if ((!GST_BUFFER_TIMESTAMP_IS_VALID (sink->buf[i])) || /* difference between expected and current is < GST_ALSA_DEVIATION */ ((this->transmitted + gst_alsa_timestamp_to_samples (this, this->max_discont) >= samplestamp) && (this->transmitted <= gst_alsa_timestamp_to_samples (this, this->max_discont) + samplestamp))) { no_difference: sink->size[i] = sink->buf[i]->size; sink->data[i] = sink->buf[i]->data; sink->behaviour[i] = 0; } else if (samplestamp > this->transmitted) { /* there are empty samples in front of us, fill them with silence */ int samples = MIN (bytes, samplestamp - this->transmitted) * (element->numpads == 1 ? this->format->channels : 1); int size = samples * snd_pcm_format_physical_width (this->format->format) / 8; g_printerr ("Allocating %d bytes (%ld samples) now to resync: sample %ld expected, but got %ld\n", size, MIN (bytes, samplestamp - this->transmitted), this->transmitted, samplestamp); sink->data[i] = g_malloc (size); if (!sink->data[i]) { g_warning ("GstAlsa: error allocating %d bytes, buffers unsynced now.", size); goto no_difference; } sink->size[i] = size; if (0 != snd_pcm_format_set_silence (this->format->format, sink->data[i], samples)) { g_warning ("GstAlsa: error silencing buffer, enjoy the noise."); } sink->behaviour[i] = 1; } else if (gst_alsa_samples_to_bytes (this, this->transmitted - samplestamp) >= sink->buf[i]->size) { g_printerr ("Skipping %lu samples to resync (complete buffer): sample %ld expected, but got %ld\n", gst_alsa_bytes_to_samples (this, sink->buf[i]->size), this->transmitted, samplestamp); /* this buffer is way behind */ gst_buffer_unref (sink->buf[i]); sink->buf[i] = NULL; continue; } else if (this->transmitted > samplestamp) { gint difference = gst_alsa_samples_to_bytes (this, this->transmitted - samplestamp); g_printerr ("Skipping %lu samples to resync: sample %ld expected, but got %ld\n", (gulong) this->transmitted - samplestamp, this->transmitted, samplestamp); /* this buffer is only a bit behind */ sink->size[i] = sink->buf[i]->size - difference; sink->data[i] = sink->buf[i]->data + difference; sink->behaviour[i] = 0; } else { g_assert_not_reached (); } } bytes = MIN (bytes, sink->size[i]); } avail = MIN (avail, gst_alsa_bytes_to_samples (this, bytes)); /* wait until the hw buffer has enough space */ while (gst_element_get_state (element) == GST_STATE_PLAYING && (avail2 = gst_alsa_update_avail (this)) < avail) { if (avail2 <= -EPIPE) goto sink_restart; if (avail2 < 0) return; if (avail2 < avail && snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING) if (!gst_alsa_start (this)) return; if (gst_alsa_pcm_wait (this) == FALSE) return; } /* FIXME: lotsa stuff can have happened while fetching data. Do we need to check something? */ /* put this data into alsa */ if ((copied = this->transmit (this, &avail)) < 0) return; /* update our clock */ this->transmitted += copied; /* flush the data */ bytes = gst_alsa_samples_to_bytes (this, copied); for (i = 0; i < element->numpads; i++) { if ((sink->size[i] -= bytes) == 0) { gst_alsa_sink_flush_one_pad (sink, i); continue; } g_assert (sink->size[i] > 0); if (sink->behaviour[i] != 1) sink->data[i] += bytes; } } if (snd_pcm_state(this->handle) != SND_PCM_STATE_RUNNING && snd_pcm_avail_update (this->handle) == 0) { gst_alsa_start (this); } } static GstElementStateReturn gst_alsa_sink_change_state (GstElement *element) { GstAlsaSink *sink; g_return_val_if_fail (element != NULL, FALSE); sink = GST_ALSA_SINK (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_sink_flush_pads (sink); break; case GST_STATE_READY_TO_NULL: break; default: g_assert_not_reached(); } if (GST_ELEMENT_CLASS (sink_parent_class)->change_state) return GST_ELEMENT_CLASS (sink_parent_class)->change_state (element); return GST_STATE_SUCCESS; } /*** GSTALSASRC FUNCTIONS *****************************************************/ 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), 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; } 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), NULL, NULL, (GClassInitFunc) 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_class_init (GstAlsaSrcClass *klass) { GObjectClass *object_class; GstElementClass *element_class; GstAlsaClass *alsa_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 = calloc(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) { g_warning ("gstalsa: 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); 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; } 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; } g_warning ("error on data access: %s", snd_strerror (err)); } return err; } static inline gint gst_alsa_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, *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], NULL); 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_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 ("alsasrc caps", "audio/x-raw-int", "endianness", GST_PROPS_INT (G_BYTE_ORDER), "signed", GST_PROPS_BOOLEAN (TRUE), "width", GST_PROPS_INT (16), "depth", GST_PROPS_INT (16), "rate", GST_PROPS_INT (44100), "channels", GST_PROPS_INT (1)); gst_caps_ref (caps); gst_caps_sink (caps); /* now try to find the best match */ walk = all_caps; while (walk) { gst_caps_get (walk, "signed", &sign, "width", &width, "depth", &depth, NULL); if (gst_caps_has_property (walk, "endianness")) { gst_caps_get_int (walk, "endianness", &endian); } else { endian = G_BYTE_ORDER; } gst_caps_set (caps, "endianness", GST_PROPS_INT (endian)); gst_caps_set (caps, "width", GST_PROPS_INT (width)); gst_caps_set (caps, "depth", GST_PROPS_INT (depth)); gst_caps_set (caps, "signed", GST_PROPS_BOOLEAN (sign)); gst_props_entry_get_int_range (gst_props_get_entry (GST_CAPS_PROPERTIES (walk), "rate"), &min_rate, &max_rate); gst_props_entry_get_int_range (gst_props_get_entry (GST_CAPS_PROPERTIES (walk), "channels"), &min_channels, &max_channels); for (rate = max_rate;; rate--) { if ((rate = gst_alsa_adjust_rate (rate, aggressive)) < min_rate) break; gst_caps_set (caps, "rate", GST_PROPS_INT (rate)); for (channels = aggressive ? max_channels : MIN (max_channels, 2); channels >= min_channels; channels--) { gst_caps_set (caps, "channels", GST_PROPS_INT (channels)); GST_DEBUG ("trying new caps: %ssigned, endianness: %d, width %d, depth %d, channels %d, rate %d\n", 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; } } } walk = GST_CAPS_NEXT (walk); } 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, "Could not set caps"); return; } /* get the bufferpool going */ if (src->pool) gst_buffer_pool_unref (src->pool); src->pool = gst_buffer_pool_get_default (gst_alsa_samples_to_bytes (this, this->period_size), 2 * element->numpads); } 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_from_pool (src->pool, 0, 0); } } /* 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_pad_push (this->pad[i], 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; } } if (src->pool) { gst_buffer_pool_unref (src->pool); src->pool = 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; } /*** GSTREAMER PAD / QUERY / CONVERSION / STATE FUNCTIONS *********************/ 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); 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->pad[channel] != 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->pad[channel] != NULL) goto found_channel; } return NULL; } found_channel: this->pad[channel] = gst_pad_new_from_template (templ, name); gst_pad_set_link_function (this->pad[channel], gst_alsa_link); gst_pad_set_getcaps_function (this->pad[channel], gst_alsa_get_caps); gst_element_add_pad (GST_ELEMENT (this), this->pad[channel]); gst_pad_set_convert_function (this->pad[channel], gst_alsa_pad_convert); gst_pad_set_query_function (this->pad[channel], gst_alsa_pad_query); gst_pad_set_query_type_function (this->pad[channel], gst_alsa_get_query_types); gst_pad_set_formats_function (this->pad[channel], gst_alsa_get_formats); return this->pad[channel]; } /* gets the matching alsa format or NULL if none matches */ static GstAlsaFormat * gst_alsa_get_format (GstCaps *caps) { const gchar *mimetype; GstAlsaFormat *ret; if (!(ret = g_new (GstAlsaFormat, 1))) return NULL; /* we have to differentiate between int and float formats */ mimetype = gst_caps_get_mime (caps); if (strcmp (mimetype, "audio/x-raw-int") == 0) { gboolean sign; gint width, depth, endianness; /* extract the needed information from the caps */ if (!gst_caps_get (caps, "width", &width, "depth", &depth, "signed", &sign, NULL)) goto error; /* extract endianness if needed */ if (width > 8) { if (!gst_caps_get (caps, "endianness", &endianness, NULL)) goto error; } else { endianness = G_BYTE_ORDER; } /* find corresponding alsa format */ ret->format = snd_pcm_build_linear_format (depth, width, sign ? 0 : 1, endianness == G_LITTLE_ENDIAN ? 0 : 1); } else if (strcmp (mimetype, "audio/x-raw-float") == 0) { gint depth; gfloat intercept, slope; /* get layout */ if (!gst_caps_get (caps, "depth", &depth, "intercept", &intercept, "slope", &slope, NULL)) goto error; if (intercept != 0.0f || slope != 1.0f) { goto error; } /* match layout to format wrt to endianness */ if (depth == 32) { if (G_BYTE_ORDER == G_LITTLE_ENDIAN) { ret->format = SND_PCM_FORMAT_FLOAT_LE; } else if (G_BYTE_ORDER == G_BIG_ENDIAN) { ret->format = SND_PCM_FORMAT_FLOAT_BE; } else { ret->format = SND_PCM_FORMAT_FLOAT; } } else if (depth == 64) { if (G_BYTE_ORDER == G_LITTLE_ENDIAN) { ret->format = SND_PCM_FORMAT_FLOAT64_LE; } else if (G_BYTE_ORDER == G_BIG_ENDIAN) { ret->format = SND_PCM_FORMAT_FLOAT64_BE; } else { ret->format = SND_PCM_FORMAT_FLOAT64; } } else { goto error; } } else if (!strcmp (mimetype, "audio/x-alaw")) { ret->format = SND_PCM_FORMAT_A_LAW; } else if (!strcmp (mimetype, "audio/x-mulaw")) { ret->format = SND_PCM_FORMAT_MU_LAW; } /* get rate and channels */ if (!gst_caps_get (caps, "rate", &ret->rate, "channels", &ret->channels, NULL)) goto error; return ret; error: g_free (ret); return NULL; } static inline gboolean gst_alsa_formats_match (GstAlsaFormat *one, GstAlsaFormat *two) { if (one == two) return TRUE; if (one == NULL || two == NULL) return FALSE; return (one->format == two->format) && (one->rate == two->rate) && (one->channels == two->channels); } /* get props for a spec */ static GstCaps * gst_alsa_get_caps_internal (snd_pcm_format_t format) { const gchar *name = snd_pcm_format_name (format); if (format == SND_PCM_FORMAT_A_LAW) { return GST_CAPS_NEW (name, "audio/x-alaw", "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_CAPS_NEW (name, "audio/x-mulaw", "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 gst_caps_new (name, "audio/x-raw-int", 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_CAPS_NEW (name, "audio/x-raw-float", "depth", GST_PROPS_INT (snd_pcm_format_width (format)), "endianness", GST_PROPS_INT (G_BYTE_ORDER), "intercept", GST_PROPS_FLOAT (0.), "slope", GST_PROPS_FLOAT (1.), NULL); } return NULL; } static inline void add_channels (GstProps *props, gint min_rate, gint max_rate, gint min_channels, gint max_channels) { if (min_rate < 0) { gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT_RANGE (GST_ALSA_MIN_RATE, GST_ALSA_MAX_RATE))); } else if (max_rate < 0) { gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT (min_rate))); } else { gst_props_add_entry (props, gst_props_entry_new ("rate", GST_PROPS_INT_RANGE (min_rate, max_rate))); } if (min_channels < 0) { gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (1, GST_ALSA_MAX_CHANNELS))); } else if (max_channels < 0) { gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT (min_channels))); } else { gst_props_add_entry (props, gst_props_entry_new ("channels", GST_PROPS_INT_RANGE (min_channels, max_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 */ ret_caps = gst_alsa_get_caps_internal (format); /* we can never use a format we can't set caps for */ g_assert (ret_caps != NULL); g_assert (ret_caps->properties != NULL); add_channels (ret_caps->properties, rate, -1, channels, -1); } else { int i; GstCaps *temp; for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) { temp = gst_alsa_get_caps_internal (i); /* can be NULL, because not all alsa formats can be specified as caps */ if (temp != NULL && temp->properties != NULL) { add_channels (temp->properties, rate, -1, channels, -1); ret_caps = gst_caps_append (ret_caps, temp); } } } return ret_caps; } /* Return better caps when device is open */ static GstCaps * gst_alsa_get_caps (GstPad *pad, GstCaps *caps) { GstAlsa *this; snd_pcm_hw_params_t *hw_params; snd_pcm_format_mask_t *mask; int i; unsigned int min_rate, max_rate; gint min_channels, max_channels; GstCaps *ret = NULL; g_return_val_if_fail (pad != NULL, NULL); this = GST_ALSA (gst_pad_get_parent (pad)); if (!GST_FLAG_IS_SET (this, GST_ALSA_OPEN)) return gst_pad_get_pad_template_caps (pad); snd_pcm_hw_params_alloca (&hw_params); ERROR_CHECK (snd_pcm_hw_params_any (this->handle, hw_params), "Broken configuration for this PCM: %s"); if (((GstElement *) this)->numpads > 1) { min_channels = 1; max_channels = -1; } else { ERROR_CHECK (snd_pcm_hw_params_get_channels_min (hw_params, &min_rate), "Coulödn't get minimum channel count for device %s: %s", this->device); ERROR_CHECK (snd_pcm_hw_params_get_channels_max (hw_params, &max_rate), "Coulödn't get maximum channel count for device %s: %s", this->device); min_channels = min_rate; max_channels = max_rate > GST_ALSA_MAX_CHANNELS ? GST_ALSA_MAX_CHANNELS : max_rate; } ERROR_CHECK (snd_pcm_hw_params_get_rate_min (hw_params, &min_rate, &i), "Coulödn't get minimum rate for device %s: %s", this->device); min_rate = min_rate < GST_ALSA_MIN_RATE ? GST_ALSA_MIN_RATE : min_rate + i; ERROR_CHECK (snd_pcm_hw_params_get_rate_max (hw_params, &max_rate, &i), "Coulödn't get maximum rate for device %s: %s", this->device); max_rate = max_rate > GST_ALSA_MAX_RATE ? GST_ALSA_MAX_RATE : max_rate + i; snd_pcm_format_mask_alloca (&mask); snd_pcm_hw_params_get_format_mask (hw_params, mask); for (i = 0; i <= SND_PCM_FORMAT_LAST; i++) { if (snd_pcm_format_mask_test (mask, i)) { GstCaps *caps = gst_alsa_get_caps_internal (i); /* we can never use a format we can't set caps for */ if (caps->properties != NULL) { add_channels (caps->properties, min_rate, max_rate, min_channels, max_channels); ret = gst_caps_append (ret, caps); } } } return ret; } /* Negotiates the caps */ GstPadLinkReturn gst_alsa_link (GstPad *pad, GstCaps *caps) { GstAlsa *this; GstAlsaFormat *format; GstPadLinkReturn ret; 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); if (format == NULL) return GST_PAD_LINK_DELAYED; GST_DEBUG ("found format %s\n", snd_pcm_format_name (format->format)); if (!GST_FLAG_IS_SET (this, GST_ALSA_CAPS_NEGO)) { gint i; GST_FLAG_SET (this, GST_ALSA_CAPS_NEGO); if (gst_alsa_formats_match (this->format, format)) { ret = GST_PAD_LINK_OK; goto out; } if (!gst_alsa_probe_hw_params (this, format)) { ret = GST_PAD_LINK_REFUSED; goto out; } for (i = 0; i < ((GstElement *) this)->numpads; i++) { g_assert (this->pad[i] != NULL); if (this->pad[i] == pad) continue; if (gst_pad_try_set_caps (this->pad[i], gst_caps_ref (caps)) == GST_PAD_LINK_REFUSED) { if (this->format) { GstCaps *old = gst_alsa_caps (this->format->format, this->format->rate, this->format->channels); for (--i; i >= 0; i--) { if (gst_pad_try_set_caps (this->pad[i], gst_caps_ref (old)) == GST_PAD_LINK_REFUSED) { gst_element_error (GST_ELEMENT (this), "error resetting caps to sane value"); gst_caps_unref (old); break; } } gst_caps_unref (old); } else { /* FIXME: unset caps on pads somehow */ } ret = GST_PAD_LINK_REFUSED; goto out; } } GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO); /* sync the params */ if (GST_FLAG_IS_SET (this, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this); g_free (this->format); this->format = format; if (!gst_alsa_start_audio (this)) { gst_element_error (GST_ELEMENT (this), "Probed format doesn't work"); return GST_PAD_LINK_REFUSED; } } return GST_PAD_LINK_OK; } return GST_PAD_LINK_DELAYED; out: g_free (format); GST_FLAG_UNSET (this, GST_ALSA_CAPS_NEGO); return ret; } 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)) if (!gst_alsa_open_audio (this)) return GST_STATE_FAILURE; break; case GST_STATE_READY_TO_PAUSED: if (!GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) if (!gst_alsa_start_audio (this)) return GST_STATE_FAILURE; this->transmitted = 0; break; case GST_STATE_PAUSED_TO_PLAYING: if (snd_pcm_state (this->handle) == SND_PCM_STATE_PAUSED) { int err = snd_pcm_pause (this->handle, 0); if (err < 0) { g_warning ("Error unpausing sound: %s", snd_strerror (err)); return GST_STATE_FAILURE; } gst_alsa_clock_start (this->clock); } break; case GST_STATE_PLAYING_TO_PAUSED: if (GST_ALSA_CAPS_IS_SET(this, GST_ALSA_CAPS_PAUSE)) { if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) { int err = snd_pcm_pause (this->handle, 1); if (err < 0) { g_warning ("Error pausing sound: %s", snd_strerror (err)); return GST_STATE_FAILURE; } gst_alsa_clock_stop (this->clock); } break; } /* if device doesn't know how to pause, we just stop */ case GST_STATE_PAUSED_TO_READY: if (GST_FLAG_IS_SET (element, GST_ALSA_RUNNING)) gst_alsa_stop_audio (this); g_free (this->format); this->format = NULL; 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; } static const GstFormat * gst_alsa_get_formats (GstPad *pad) { static const GstFormat formats[] = { GST_FORMAT_TIME, GST_FORMAT_DEFAULT, GST_FORMAT_BYTES, 0 }; return formats; } static gboolean gst_alsa_pad_convert (GstPad *pad, GstFormat src_format, gint64 src_value, GstFormat *dest_format, gint64 *dest_value) { return gst_alsa_convert (GST_ALSA (GST_PAD_PARENT (pad)), src_format, src_value, dest_format, dest_value); } static gboolean gst_alsa_convert (GstAlsa *this, GstFormat src_format, gint64 src_value, GstFormat *dest_format, gint64 *dest_value) { gboolean res = TRUE; if (src_format == *dest_format) { *dest_value = src_value; return TRUE; } if (this->format == NULL) return FALSE; switch (src_format) { case GST_FORMAT_BYTES: switch (*dest_format) { case GST_FORMAT_DEFAULT: *dest_value = gst_alsa_bytes_to_samples (this, (guint) src_value); break; case GST_FORMAT_TIME: *dest_value = gst_alsa_bytes_to_timestamp (this, (guint) src_value); break; default: res = FALSE; break; } break; case GST_FORMAT_TIME: switch (*dest_format) { case GST_FORMAT_DEFAULT: *dest_value = gst_alsa_timestamp_to_samples (this, (GstClockTime) src_value); break; case GST_FORMAT_BYTES: *dest_value = gst_alsa_timestamp_to_bytes (this, (GstClockTime) src_value); break; default: res = FALSE; break; } break; case GST_FORMAT_DEFAULT: switch (*dest_format) { case GST_FORMAT_TIME: *dest_value = gst_alsa_samples_to_timestamp (this, (guint) src_value); break; case GST_FORMAT_BYTES: *dest_value = gst_alsa_samples_to_bytes (this, (guint) src_value); break; case GST_FORMAT_DEFAULT: g_assert_not_reached (); /* fall through */ default: res = FALSE; break; } break; default: res = FALSE; } return res; } static const GstQueryType * gst_alsa_get_query_types (GstPad *pad) { static const GstQueryType query_types[] = { GST_QUERY_LATENCY, GST_QUERY_POSITION, 0, }; return query_types; } static gboolean gst_alsa_query_func (GstElement *element, GstQueryType type, GstFormat *format, gint64 *value) { gboolean res = FALSE; GstAlsa *this = GST_ALSA (element); switch (type) { case GST_QUERY_LATENCY: { snd_pcm_sframes_t delay; ERROR_CHECK (snd_pcm_delay (this->handle, &delay), "Error getting delay: %s"); res = gst_alsa_convert (this, GST_FORMAT_DEFAULT, (gint64) delay, format, value); break; } case GST_QUERY_POSITION: res = gst_alsa_convert (this, GST_FORMAT_DEFAULT, this->transmitted, format, value); break; default: break; } return res; } static gboolean gst_alsa_query (GstElement *element, GstQueryType type, GstFormat *format, gint64 *value) { return gst_alsa_pad_query (GST_ALSA (element)->pad[0], type, format, value); } static gboolean gst_alsa_pad_query (GstPad *pad, GstQueryType type, GstFormat *format, gint64 *value) { if (gst_alsa_query_func (GST_PAD_PARENT (pad), type, format, value)) return TRUE; if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK && gst_pad_query (gst_pad_get_peer (pad), type, format, value)) return TRUE; return FALSE; } /*** AUDIO PROCESSING *********************************************************/ 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); } else { g_warning ("unknown ALSA avail_update return value (%d)", (int) avail); } } return avail; } /* returns TRUE, if the loop should go on */ inline static gboolean gst_alsa_pcm_wait (GstAlsa *this) { int err; if (snd_pcm_state (this->handle) == SND_PCM_STATE_RUNNING) { if ((err = snd_pcm_wait (this->handle, 1000)) < 0) { if (err == EINTR) { /* happens mostly when run under gdb, or when exiting due to a signal */ GST_DEBUG ("got interrupted while waiting"); if (gst_element_interrupt (GST_ELEMENT (this))) { return TRUE; } else { return FALSE; } } g_warning ("error waiting for alsa pcm: (%d: %s)", err, snd_strerror (err)); return FALSE; } } return TRUE; } /** * error out or make sure we're in SND_PCM_STATE_RUNNING afterwards * return FALSE if we're not */ inline static gboolean gst_alsa_start (GstAlsa *this) { GST_DEBUG ("Setting state to RUNNING"); switch (snd_pcm_state(this->handle)) { case SND_PCM_STATE_XRUN: gst_alsa_xrun_recovery (this); return gst_alsa_start (this); case SND_PCM_STATE_SETUP: ERROR_CHECK (snd_pcm_prepare (this->handle), "error preparing: %s"); case SND_PCM_STATE_SUSPENDED: case SND_PCM_STATE_PREPARED: ERROR_CHECK (snd_pcm_start(this->handle), "error starting playback: %s"); break; case SND_PCM_STATE_PAUSED: ERROR_CHECK (snd_pcm_pause (this->handle, 0), "error unpausing: %s"); break; case SND_PCM_STATE_RUNNING: break; case SND_PCM_STATE_DRAINING: case SND_PCM_STATE_OPEN: /* this probably happens when someone replugged a pipeline and we're in a really weird state because our cothread wasn't busted */ return FALSE; default: /* it's a bug when we get here */ g_assert_not_reached (); break; } gst_alsa_clock_start (this->clock); return TRUE; } 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"); } } /*** AUDIO SETUP / START / STOP ***********************************************/ static void gst_alsa_set_eos (GstAlsa *this) { gst_alsa_drain_audio (this); gst_element_set_eos (GST_ELEMENT (this)); } static gboolean gst_alsa_open_audio (GstAlsa *this) { g_assert (this != NULL); g_assert (this->handle == NULL); GST_INFO ( "Opening alsa device \"%s\"...\n", this->device); ERROR_CHECK (snd_output_stdio_attach (&this->out, stderr, 0), "error opening log output: %s"); /* we use non-blocking i/o */ ERROR_CHECK (snd_pcm_open (&this->handle, this->device, GST_ALSA_GET_CLASS (this)->stream, SND_PCM_NONBLOCK), "error opening pcm device %s: %s\n", this->device); GST_FLAG_SET (this, GST_ALSA_OPEN); return TRUE; } /* if someone finds an easy way to merge this with _set_hw_params, go ahead */ static gboolean gst_alsa_probe_hw_params (GstAlsa *this, GstAlsaFormat *format) { snd_pcm_hw_params_t *hw_params; snd_pcm_access_mask_t *mask; snd_pcm_uframes_t period_size; unsigned int period_count; g_return_val_if_fail (this != NULL, FALSE); g_return_val_if_fail (format != NULL, FALSE); GST_INFO ( "Probing format: %s %dHz, %d channels\n", snd_pcm_format_name (format->format), format->rate, format->channels); snd_pcm_hw_params_alloca (&hw_params); SIMPLE_ERROR_CHECK (snd_pcm_hw_params_any (this->handle, hw_params)); SIMPLE_ERROR_CHECK (snd_pcm_hw_params_set_periods_integer (this->handle, hw_params)); /* enable this for soundcard specific debugging */ /* snd_pcm_hw_params_dump (hw_params, this->out); */ 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, this->mmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED : SND_PCM_ACCESS_RW_INTERLEAVED); } else { snd_pcm_access_mask_set (mask, this->mmap ? SND_PCM_ACCESS_MMAP_NONINTERLEAVED : SND_PCM_ACCESS_RW_NONINTERLEAVED); } SIMPLE_ERROR_CHECK (snd_pcm_hw_params_set_access_mask (this->handle, hw_params, mask)); SIMPLE_ERROR_CHECK (snd_pcm_hw_params_set_format (this->handle, hw_params, format->format)); SIMPLE_ERROR_CHECK (snd_pcm_hw_params_set_channels (this->handle, hw_params, format->channels)); SIMPLE_ERROR_CHECK (snd_pcm_hw_params_set_rate (this->handle, hw_params, format->rate, 0)); period_count = this->period_count; SIMPLE_ERROR_CHECK (snd_pcm_hw_params_set_periods_near (this->handle, hw_params, &period_count, 0)); period_size = this->period_size; SIMPLE_ERROR_CHECK (snd_pcm_hw_params_set_period_size_near (this->handle, hw_params, &period_size, 0)); return TRUE; } /** * You must set all hw parameters at once and can't use already set params and * change them. * 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; g_return_val_if_fail (this != NULL, FALSE); g_return_val_if_fail (this->handle != NULL, FALSE); if (this->format) { GST_INFO ( "Preparing format: %s %dHz, %d channels", snd_pcm_format_name (this->format->format), this->format->rate, this->format->channels); } else { GST_INFO ( "Preparing format: (none)"); } 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"); /* enable this for soundcard specific debugging */ /* snd_pcm_hw_params_dump (hw_params, this->out); */ 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, this->mmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED : SND_PCM_ACCESS_RW_INTERLEAVED); } else { snd_pcm_access_mask_set (mask, this->mmap ? SND_PCM_ACCESS_MMAP_NONINTERLEAVED : SND_PCM_ACCESS_RW_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"); if (this->format) { ERROR_CHECK (snd_pcm_hw_params_set_format (this->handle, hw_params, this->format->format), "Sample format (%s) not available: %s", snd_pcm_format_name (this->format->format)); ERROR_CHECK (snd_pcm_hw_params_set_channels (this->handle, hw_params, this->format->channels), "Channels count (%d) not available: %s", this->format->channels); ERROR_CHECK (snd_pcm_hw_params_set_rate (this->handle, hw_params, this->format->rate, 0), "error setting rate (%d): %s", this->format->rate); } ERROR_CHECK (snd_pcm_hw_params_set_periods_near (this->handle, hw_params, &this->period_count, 0), "error setting buffer size to %u: %s", (guint) this->period_count); ERROR_CHECK (snd_pcm_hw_params_set_period_size_near (this->handle, hw_params, &this->period_size, 0), "error setting period size to %u frames: %s", (guint) this->period_size); ERROR_CHECK (snd_pcm_hw_params (this->handle, hw_params), "Could not set hardware parameters: %s"); /* now get the pcm caps */ GST_ALSA_CAPS_SET (this, GST_ALSA_CAPS_PAUSE, snd_pcm_hw_params_can_pause (hw_params)); GST_ALSA_CAPS_SET (this, GST_ALSA_CAPS_RESUME, snd_pcm_hw_params_can_resume (hw_params)); GST_ALSA_CAPS_SET (this, GST_ALSA_CAPS_SYNC_START, snd_pcm_hw_params_can_sync_start (hw_params)); if (this->mmap) { this->transmit = GST_ALSA_GET_CLASS (this)->transmit_mmap; } else { this->transmit = GST_ALSA_GET_CLASS (this)->transmit_rw; } 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"); /* we start explicitly */ ERROR_CHECK (snd_pcm_sw_params_set_start_threshold (this->handle, sw_params, this->period_size * this->period_count + 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) { g_assert (GST_FLAG_IS_SET (this, GST_ALSA_OPEN)); 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->handle != NULL, FALSE); GST_DEBUG ("stopping alsa"); switch (snd_pcm_state (this->handle)) { case SND_PCM_STATE_XRUN: case SND_PCM_STATE_RUNNING: gst_alsa_clock_stop (this->clock); /* fall through - clock is already stopped when paused */ case SND_PCM_STATE_PAUSED: /* snd_pcm_drain only works in blocking mode */ ERROR_CHECK (snd_pcm_nonblock(this->handle, 0), "couldn't set blocking mode: %s"); ERROR_CHECK (snd_pcm_drain (this->handle), "couldn't stop and drain buffer: %s"); ERROR_CHECK (snd_pcm_nonblock(this->handle, 1), "couldn't set non-blocking mode: %s"); break; default: break; } 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->handle != NULL, FALSE); GST_DEBUG ("stopping alsa, skipping pending frames"); switch (snd_pcm_state (this->handle)) { case SND_PCM_STATE_XRUN: case SND_PCM_STATE_RUNNING: gst_alsa_clock_stop (this->clock); /* fall through - clock is already stopped when paused */ case SND_PCM_STATE_PAUSED: ERROR_CHECK (snd_pcm_drop (this->handle), "couldn't stop (dropping frames): %s"); break; default: break; } 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; } /*** CLOCK FUNCTIONS **********************************************************/ GType gst_alsa_clock_get_type (void) { static GType clock_type = 0; if (!clock_type) { static const GTypeInfo clock_info = { sizeof (GstAlsaClockClass), NULL, NULL, (GClassInitFunc) gst_alsa_clock_class_init, NULL, NULL, sizeof (GstAlsaClock), 4, (GInstanceInitFunc) gst_alsa_clock_init, NULL }; clock_type = g_type_register_static (GST_TYPE_CLOCK, "GstAlsaClock", &clock_info, 0); } return clock_type; } static void gst_alsa_clock_class_init (GstAlsaClockClass *klass) { GObjectClass *gobject_class; GstObjectClass *gstobject_class; GstClockClass *gstclock_class; gobject_class = (GObjectClass*) klass; gstobject_class = (GstObjectClass*) klass; gstclock_class = (GstClockClass*) klass; clock_parent_class = g_type_class_ref (GST_TYPE_CLOCK); gstclock_class->get_internal_time = gst_alsa_clock_get_internal_time; gstclock_class->get_resolution = gst_alsa_clock_get_resolution; gstclock_class->wait = gst_alsa_clock_wait; gstclock_class->unlock = gst_alsa_clock_unlock; } static void gst_alsa_clock_init (GstAlsaClock *clock) { gst_object_set_name (GST_OBJECT (clock), "GstAlsaClock"); clock->start_time = GST_CLOCK_TIME_NONE; } static GstAlsaClock* gst_alsa_clock_new (gchar *name, GstAlsaClockGetTimeFunc get_time, GstAlsa *owner) { GstAlsaClock *alsa_clock = GST_ALSA_CLOCK (g_object_new (GST_TYPE_ALSA_CLOCK, NULL)); g_assert (alsa_clock); alsa_clock->get_time = get_time; alsa_clock->owner = owner; alsa_clock->adjust = 0; gst_object_set_name (GST_OBJECT (alsa_clock), name); gst_object_set_parent (GST_OBJECT (alsa_clock), GST_OBJECT (owner)); return alsa_clock; } void gst_alsa_clock_start (GstAlsaClock *clock) { GTimeVal timeval; g_get_current_time (&timeval); g_assert (!GST_CLOCK_TIME_IS_VALID (clock->start_time)); if (clock->owner->format) { clock->start_time = GST_TIMEVAL_TO_TIME (timeval) + clock->adjust - clock->get_time (clock->owner); } else { clock->start_time = GST_TIMEVAL_TO_TIME (timeval) + clock->adjust; } } void gst_alsa_clock_stop (GstAlsaClock *clock) { GTimeVal timeval; g_get_current_time (&timeval); g_assert (GST_CLOCK_TIME_IS_VALID (clock->start_time)); clock->adjust += GST_TIMEVAL_TO_TIME (timeval) - clock->start_time - clock->get_time (clock->owner); clock->start_time = GST_CLOCK_TIME_NONE; } static GstClockTime gst_alsa_clock_get_internal_time (GstClock *clock) { GstAlsaClock *alsa_clock = GST_ALSA_CLOCK (clock); if (GST_CLOCK_TIME_IS_VALID (alsa_clock->start_time)) { return alsa_clock->get_time (alsa_clock->owner) + alsa_clock->start_time; } else { GTimeVal timeval; g_get_current_time (&timeval); return GST_TIMEVAL_TO_TIME (timeval) + alsa_clock->adjust; } } static guint64 gst_alsa_clock_get_resolution (GstClock *clock) { GstAlsaClock *this = GST_ALSA_CLOCK (clock); if (this->owner->format) { return GST_SECOND / this->owner->format->rate; } else { /* FIXME: is there an "unknown" value? We just return the sysclock's time by default */ return 1 * GST_USECOND; } } static GstClockEntryStatus gst_alsa_clock_wait (GstClock *clock, GstClockEntry *entry) { GstClockTime target, entry_time; GstClockTimeDiff diff; GstAlsaClock *this = GST_ALSA_CLOCK (clock); entry_time = gst_alsa_clock_get_internal_time (clock); diff = GST_CLOCK_ENTRY_TIME (entry) - gst_clock_get_time (clock); if (diff < 0) return GST_CLOCK_ENTRY_EARLY; if (diff > clock->max_diff) { g_warning ("GstAlsaClock: abnormal clock request diff: %" G_GINT64_FORMAT") >" " %"G_GINT64_FORMAT, diff, clock->max_diff); return GST_CLOCK_ENTRY_EARLY; } target = entry_time + diff; GST_DEBUG ("real_target %" G_GUINT64_FORMAT " target %" G_GUINT64_FORMAT " now %" G_GUINT64_FORMAT, target, GST_CLOCK_ENTRY_TIME (entry), entry_time); while (gst_alsa_clock_get_internal_time (clock) < target && this->last_unlock < entry_time) { g_usleep (gst_alsa_clock_get_resolution (clock) * G_USEC_PER_SEC / GST_SECOND); } return entry->status; } static void gst_alsa_clock_unlock (GstClock *clock, GstClockEntry *entry) { GstAlsaClock *this = GST_ALSA_CLOCK (clock); this->last_unlock = this->get_time (this->owner); } static GstClockTime gst_alsa_sink_get_time (GstAlsa *this) { snd_pcm_sframes_t delay; if (snd_pcm_delay (this->handle, &delay) == 0) { return GST_SECOND * (GstClockTime) (this->transmitted > delay ? this->transmitted - delay : 0) / this->format->rate; } else { return 0; } } 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; } } static GstClock * gst_alsa_get_clock (GstElement *element) { return GST_CLOCK (GST_ALSA (element)->clock); } static void gst_alsa_set_clock (GstElement *element, GstClock *clock) { /* we only must have this function so everybody knows we use a clock */ } /*** FORMAT FUNCTIONS *********************************************************/ /* ALL THES FUNCTIONS ASSUME this->format != NULL */ static inline snd_pcm_uframes_t gst_alsa_timestamp_to_samples (GstAlsa *this, GstClockTime time) { return (snd_pcm_uframes_t) ((time * this->format->rate + this->format->rate / 2) / GST_SECOND); } static inline GstClockTime gst_alsa_samples_to_timestamp (GstAlsa *this, snd_pcm_uframes_t samples) { return (GstClockTime) (samples * GST_SECOND / this->format->rate); } static inline snd_pcm_uframes_t gst_alsa_bytes_to_samples (GstAlsa *this, guint bytes) { return bytes / (snd_pcm_format_physical_width (this->format->format) / 8) / (GST_ELEMENT (this)->numpads == 1 ? this->format->channels : 1); } static inline guint gst_alsa_samples_to_bytes (GstAlsa *this, snd_pcm_uframes_t samples) { return samples * snd_pcm_format_physical_width (this->format->format) / 8 * (GST_ELEMENT (this)->numpads == 1 ? this->format->channels : 1); } static inline GstClockTime gst_alsa_bytes_to_timestamp (GstAlsa *this, guint bytes) { return gst_alsa_samples_to_timestamp (this, gst_alsa_bytes_to_samples (this, bytes)); } static inline guint gst_alsa_timestamp_to_bytes (GstAlsa *this, GstClockTime time) { return gst_alsa_samples_to_bytes (this, gst_alsa_timestamp_to_samples (this, time)); } /*** 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 };