static void gst_alsasink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstAlsaSink *sink; sink = GST_ALSA_SINK (object); switch (prop_id) { case PROP_DEVICE: g_value_set_string (value, sink->device); break; case PROP_DEVICE_NAME: g_value_take_string (value, gst_alsa_find_device_name (GST_OBJECT_CAST (sink), sink->device, sink->handle, SND_PCM_STREAM_PLAYBACK)); break; case PROP_CARD_NAME: g_value_take_string (value, gst_alsa_find_card_name (GST_OBJECT_CAST (sink), sink->device, SND_PCM_STREAM_PLAYBACK)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }
static void gst_alsasink_reset (GstAudioSink * asink) { GstAlsaSink *alsa; gint err; alsa = GST_ALSA_SINK (asink); GST_ALSA_SINK_LOCK (asink); GST_DEBUG_OBJECT (alsa, "drop"); CHECK (snd_pcm_drop (alsa->handle), drop_error); GST_DEBUG_OBJECT (alsa, "prepare"); CHECK (snd_pcm_prepare (alsa->handle), prepare_error); GST_DEBUG_OBJECT (alsa, "reset done"); GST_ALSA_SINK_UNLOCK (asink); return; /* ERRORS */ drop_error: { GST_ERROR_OBJECT (alsa, "alsa-reset: pcm drop error: %s", snd_strerror (err)); GST_ALSA_SINK_UNLOCK (asink); return; } prepare_error: { GST_ERROR_OBJECT (alsa, "alsa-reset: pcm prepare error: %s", snd_strerror (err)); GST_ALSA_SINK_UNLOCK (asink); return; } }
static GstCaps * gst_alsasink_getcaps (GstBaseSink * bsink) { GstElementClass *element_class; GstPadTemplate *pad_template; GstAlsaSink *sink = GST_ALSA_SINK (bsink); GstCaps *caps; if (sink->handle == NULL) { GST_DEBUG_OBJECT (sink, "device not open, using template caps"); return NULL; /* base class will get template caps for us */ } if (sink->cached_caps) { GST_LOG_OBJECT (sink, "Returning cached caps"); return gst_caps_ref (sink->cached_caps); } element_class = GST_ELEMENT_GET_CLASS (sink); pad_template = gst_element_class_get_pad_template (element_class, "sink"); g_return_val_if_fail (pad_template != NULL, NULL); caps = gst_alsa_probe_supported_formats (GST_OBJECT (sink), sink->device, sink->handle, gst_pad_template_get_caps (pad_template)); if (caps) { sink->cached_caps = gst_caps_ref (caps); } GST_INFO_OBJECT (sink, "returning caps %" GST_PTR_FORMAT, caps); return caps; }
static gboolean gst_alsasink_unprepare (GstAudioSink * asink) { GstAlsaSink *alsa; gint err; alsa = GST_ALSA_SINK (asink); CHECK (snd_pcm_drop (alsa->handle), drop); CHECK (snd_pcm_hw_free (alsa->handle), hw_free); return TRUE; /* ERRORS */ drop: { GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), ("Could not drop samples: %s", snd_strerror (err))); return FALSE; } hw_free: { GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), ("Could not free hw params: %s", snd_strerror (err))); return FALSE; } }
static guint gst_alsasink_write (GstAudioSink * asink, gpointer data, guint length) { GstAlsaSink *alsa; gint err; gint cptr; gint16 *ptr = data; alsa = GST_ALSA_SINK (asink); if (alsa->iec958 && alsa->need_swap) { guint i; GST_DEBUG_OBJECT (asink, "swapping bytes"); for (i = 0; i < length / 2; i++) { ptr[i] = GUINT16_SWAP_LE_BE (ptr[i]); } } GST_LOG_OBJECT (asink, "received audio samples buffer of %u bytes", length); cptr = length / alsa->bytes_per_sample; GST_ALSA_SINK_LOCK (asink); while (cptr > 0) { /* start by doing a blocking wait for free space. Set the timeout * to 4 times the period time */ err = snd_pcm_wait (alsa->handle, (4 * alsa->period_time / 1000)); if (err < 0) { GST_DEBUG_OBJECT (asink, "wait error, %d", err); } else { GST_DELAY_SINK_LOCK (asink); err = snd_pcm_writei (alsa->handle, ptr, cptr); GST_DELAY_SINK_UNLOCK (asink); } GST_DEBUG_OBJECT (asink, "written %d frames out of %d", err, cptr); if (err < 0) { GST_DEBUG_OBJECT (asink, "Write error: %s", snd_strerror (err)); if (err == -EAGAIN) { continue; } else if (xrun_recovery (alsa, alsa->handle, err) < 0) { goto write_error; } continue; } ptr += snd_pcm_frames_to_bytes (alsa->handle, err); cptr -= err; } GST_ALSA_SINK_UNLOCK (asink); return length - (cptr * alsa->bytes_per_sample); write_error: { GST_ALSA_SINK_UNLOCK (asink); return length; /* skip one period */ } }
static guint gst_alsasink_delay (GstAudioSink * asink) { GstAlsaSink *alsa; snd_pcm_sframes_t delay; int res; alsa = GST_ALSA_SINK (asink); GST_DELAY_SINK_LOCK (asink); res = snd_pcm_delay (alsa->handle, &delay); GST_DELAY_SINK_UNLOCK (asink); if (G_UNLIKELY (res < 0)) { /* on errors, report 0 delay */ GST_DEBUG_OBJECT (alsa, "snd_pcm_delay returned %d", res); delay = 0; } if (G_UNLIKELY (delay < 0)) { /* make sure we never return a negative delay */ GST_WARNING_OBJECT (alsa, "snd_pcm_delay returned negative delay"); delay = 0; } return delay; }
static gboolean gst_alsasink_open (GstAudioSink * asink) { GstAlsaSink *alsa; gint err; alsa = GST_ALSA_SINK (asink); /* open in non-blocking mode, we'll use snd_pcm_wait() for space to become * available. */ CHECK (snd_pcm_open (&alsa->handle, alsa->device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK), open_error); GST_LOG_OBJECT (alsa, "Opened device %s", alsa->device); return TRUE; /* ERRORS */ open_error: { if (err == -EBUSY) { GST_ELEMENT_ERROR (alsa, RESOURCE, BUSY, (_("Could not open audio device for playback. " "Device is being used by another application.")), ("Device '%s' is busy", alsa->device)); } else { GST_ELEMENT_ERROR (alsa, RESOURCE, OPEN_WRITE, (_("Could not open audio device for playback.")), ("Playback open error on device '%s': %s", alsa->device, snd_strerror (err))); } return FALSE; } }
static GstBuffer * gst_alsasink_payload (GstBaseAudioSink * sink, GstBuffer * buf) { GstAlsaSink *alsa; alsa = GST_ALSA_SINK (sink); if (alsa->iec958) { GstBuffer *out; gint framesize; framesize = gst_audio_iec61937_frame_size (&sink->ringbuffer->spec); if (framesize <= 0) return NULL; out = gst_buffer_new_and_alloc (framesize); if (!gst_audio_iec61937_payload (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf), GST_BUFFER_DATA (out), GST_BUFFER_SIZE (out), &sink->ringbuffer->spec)) { gst_buffer_unref (out); return NULL; } gst_buffer_copy_metadata (out, buf, GST_BUFFER_COPY_ALL); return out; } return gst_buffer_ref (buf); }
static GstCaps * gst_alsasink_getcaps (GstBaseSink * bsink, GstCaps * filter) { GstElementClass *element_class; GstPadTemplate *pad_template; GstAlsaSink *sink = GST_ALSA_SINK (bsink); GstCaps *caps, *templ_caps; if (sink->handle == NULL) { GST_DEBUG_OBJECT (sink, "device not open, using template caps"); return NULL; /* base class will get template caps for us */ } if (sink->cached_caps) { if (filter) { caps = gst_caps_intersect_full (filter, sink->cached_caps, GST_CAPS_INTERSECT_FIRST); GST_LOG_OBJECT (sink, "Returning cached caps %" GST_PTR_FORMAT " with " "filter %" GST_PTR_FORMAT " applied: %" GST_PTR_FORMAT, sink->cached_caps, filter, caps); return caps; } else { GST_LOG_OBJECT (sink, "Returning cached caps %" GST_PTR_FORMAT, sink->cached_caps); return gst_caps_ref (sink->cached_caps); } } element_class = GST_ELEMENT_GET_CLASS (sink); pad_template = gst_element_class_get_pad_template (element_class, "sink"); g_return_val_if_fail (pad_template != NULL, NULL); templ_caps = gst_pad_template_get_caps (pad_template); caps = gst_alsa_probe_supported_formats (GST_OBJECT (sink), sink->device, sink->handle, templ_caps); gst_caps_unref (templ_caps); if (caps) { sink->cached_caps = gst_caps_ref (caps); } GST_INFO_OBJECT (sink, "returning caps %" GST_PTR_FORMAT, caps); if (filter) { GstCaps *intersection; intersection = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (caps); return intersection; } else { return caps; } }
static gboolean gst_alsasink_unprepare (GstAudioSink * asink) { GstAlsaSink *alsa; alsa = GST_ALSA_SINK (asink); snd_pcm_drop (alsa->handle); snd_pcm_hw_free (alsa->handle); return TRUE; }
static gboolean gst_alsasink_acceptcaps (GstPad * pad, GstCaps * caps) { GstAlsaSink *alsa = GST_ALSA_SINK (gst_pad_get_parent_element (pad)); GstCaps *pad_caps; GstStructure *st; gboolean ret = FALSE; GstRingBufferSpec spec = { 0 }; pad_caps = gst_pad_get_caps_reffed (pad); if (pad_caps) { ret = gst_caps_can_intersect (pad_caps, caps); gst_caps_unref (pad_caps); if (!ret) goto done; } /* If we've not got fixed caps, creating a stream might fail, so let's just * return from here with default acceptcaps behaviour */ if (!gst_caps_is_fixed (caps)) goto done; /* parse helper expects this set, so avoid nasty warning * will be set properly later on anyway */ spec.latency_time = GST_SECOND; if (!gst_ring_buffer_parse_caps (&spec, caps)) goto done; /* Make sure input is framed (one frame per buffer) and can be payloaded */ switch (spec.type) { case GST_BUFTYPE_AC3: case GST_BUFTYPE_EAC3: case GST_BUFTYPE_DTS: case GST_BUFTYPE_MPEG: { gboolean framed = FALSE, parsed = FALSE; st = gst_caps_get_structure (caps, 0); gst_structure_get_boolean (st, "framed", &framed); gst_structure_get_boolean (st, "parsed", &parsed); if ((!framed && !parsed) || gst_audio_iec61937_frame_size (&spec) <= 0) goto done; } default: { } } ret = TRUE; done: gst_caps_replace (&spec.caps, NULL); gst_object_unref (alsa); return ret; }
static gboolean gst_alsasink_close (GstAudioSink * asink) { GstAlsaSink *alsa = GST_ALSA_SINK (asink); if (alsa->handle) { snd_pcm_close (alsa->handle); alsa->handle = NULL; } gst_caps_replace (&alsa->cached_caps, NULL); return TRUE; }
static void gst_alsasink_finalise (GObject * object) { GstAlsaSink *sink = GST_ALSA_SINK (object); g_free (sink->device); g_mutex_free (sink->alsa_lock); g_static_mutex_lock (&output_mutex); --output_ref; if (output_ref == 0) { snd_output_close (output); output = NULL; } g_static_mutex_unlock (&output_mutex); G_OBJECT_CLASS (parent_class)->finalize (object); }
static gboolean gst_alsasink_close (GstAudioSink * asink) { GstAlsaSink *alsa = GST_ALSA_SINK (asink); gint err; if (alsa->handle) { CHECK (snd_pcm_close (alsa->handle), close_error); alsa->handle = NULL; } gst_caps_replace (&alsa->cached_caps, NULL); return TRUE; /* ERRORS */ close_error: { GST_ELEMENT_ERROR (alsa, RESOURCE, CLOSE, (NULL), ("Playback close error: %s", snd_strerror (err))); return FALSE; } }
static void gst_alsasink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstAlsaSink *sink; sink = GST_ALSA_SINK (object); switch (prop_id) { case PROP_DEVICE: g_free (sink->device); sink->device = g_value_dup_string (value); /* setting NULL restores the default device */ if (sink->device == NULL) { sink->device = g_strdup (DEFAULT_DEVICE); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }
static GstBuffer * gst_alsasink_payload (GstAudioBaseSink * sink, GstBuffer * buf) { GstAlsaSink *alsa; alsa = GST_ALSA_SINK (sink); if (alsa->iec958) { GstBuffer *out; gint framesize; GstMapInfo iinfo, oinfo; framesize = gst_audio_iec61937_frame_size (&sink->ringbuffer->spec); if (framesize <= 0) return NULL; out = gst_buffer_new_and_alloc (framesize); gst_buffer_map (buf, &iinfo, GST_MAP_READ); gst_buffer_map (out, &oinfo, GST_MAP_WRITE); if (!gst_audio_iec61937_payload (iinfo.data, iinfo.size, oinfo.data, oinfo.size, &sink->ringbuffer->spec, G_BIG_ENDIAN)) { gst_buffer_unmap (buf, &iinfo); gst_buffer_unmap (out, &oinfo); gst_buffer_unref (out); return NULL; } gst_buffer_unmap (buf, &iinfo); gst_buffer_unmap (out, &oinfo); gst_buffer_copy_into (out, buf, GST_BUFFER_COPY_METADATA, 0, -1); return out; } return gst_buffer_ref (buf); }
static gboolean gst_alsasink_query (GstBaseSink * sink, GstQuery * query) { GstAlsaSink *alsa = GST_ALSA_SINK (sink); gboolean ret; switch (GST_QUERY_TYPE (query)) { case GST_QUERY_ACCEPT_CAPS: { GstCaps *caps; gst_query_parse_accept_caps (query, &caps); ret = gst_alsasink_acceptcaps (alsa, caps); gst_query_set_accept_caps_result (query, ret); ret = TRUE; break; } default: ret = GST_BASE_SINK_CLASS (parent_class)->query (sink, query); break; } return ret; }
static gboolean gst_alsasink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec) { GstAlsaSink *alsa; gint err; alsa = GST_ALSA_SINK (asink); if (alsa->iec958) { snd_pcm_close (alsa->handle); alsa->handle = gst_alsa_open_iec958_pcm (GST_OBJECT (alsa), alsa->device); if (G_UNLIKELY (!alsa->handle)) { goto no_iec958; } } if (!alsasink_parse_spec (alsa, spec)) goto spec_parse; CHECK (set_hwparams (alsa), hw_params_failed); CHECK (set_swparams (alsa), sw_params_failed); alsa->bpf = GST_AUDIO_INFO_BPF (&spec->info); spec->segsize = alsa->period_size * alsa->bpf; spec->segtotal = alsa->buffer_size / alsa->period_size; { snd_output_t *out_buf = NULL; char *msg = NULL; snd_output_buffer_open (&out_buf); snd_pcm_dump_hw_setup (alsa->handle, out_buf); snd_output_buffer_string (out_buf, &msg); GST_DEBUG_OBJECT (alsa, "Hardware setup: \n%s", msg); snd_output_close (out_buf); snd_output_buffer_open (&out_buf); snd_pcm_dump_sw_setup (alsa->handle, out_buf); snd_output_buffer_string (out_buf, &msg); GST_DEBUG_OBJECT (alsa, "Software setup: \n%s", msg); snd_output_close (out_buf); } #ifdef SND_CHMAP_API_VERSION if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW && alsa->channels < 9) { snd_pcm_chmap_t *chmap = snd_pcm_get_chmap (alsa->handle); if (chmap && chmap->channels == alsa->channels) { GstAudioChannelPosition pos[8]; if (alsa_chmap_to_channel_positions (chmap, pos)) gst_audio_ring_buffer_set_channel_positions (GST_AUDIO_BASE_SINK (alsa)->ringbuffer, pos); } free (chmap); } #endif /* SND_CHMAP_API_VERSION */ return TRUE; /* ERRORS */ no_iec958: { GST_ELEMENT_ERROR (alsa, RESOURCE, OPEN_WRITE, (NULL), ("Could not open IEC958 (SPDIF) device for playback")); return FALSE; } spec_parse: { GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), ("Error parsing spec")); return FALSE; } hw_params_failed: { GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), ("Setting of hwparams failed: %s", snd_strerror (err))); return FALSE; } sw_params_failed: { GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), ("Setting of swparams failed: %s", snd_strerror (err))); return FALSE; } }
static gboolean gst_alsasink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) { GstAlsaSink *alsa; gint err; alsa = GST_ALSA_SINK (asink); if (alsa->iec958) { snd_pcm_close (alsa->handle); alsa->handle = gst_alsa_open_iec958_pcm (GST_OBJECT (alsa), alsa->device); if (G_UNLIKELY (!alsa->handle)) { goto no_iec958; } } if (!alsasink_parse_spec (alsa, spec)) goto spec_parse; CHECK (set_hwparams (alsa), hw_params_failed); CHECK (set_swparams (alsa), sw_params_failed); alsa->bytes_per_sample = spec->bytes_per_sample; spec->segsize = alsa->period_size * spec->bytes_per_sample; spec->segtotal = alsa->buffer_size / alsa->period_size; { snd_output_t *out_buf = NULL; char *msg = NULL; snd_output_buffer_open (&out_buf); snd_pcm_dump_hw_setup (alsa->handle, out_buf); snd_output_buffer_string (out_buf, &msg); GST_DEBUG_OBJECT (alsa, "Hardware setup: \n%s", msg); snd_output_close (out_buf); snd_output_buffer_open (&out_buf); snd_pcm_dump_sw_setup (alsa->handle, out_buf); snd_output_buffer_string (out_buf, &msg); GST_DEBUG_OBJECT (alsa, "Software setup: \n%s", msg); snd_output_close (out_buf); } return TRUE; /* ERRORS */ no_iec958: { GST_ELEMENT_ERROR (alsa, RESOURCE, OPEN_WRITE, (NULL), ("Could not open IEC958 (SPDIF) device for playback")); return FALSE; } spec_parse: { GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), ("Error parsing spec")); return FALSE; } hw_params_failed: { GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), ("Setting of hwparams failed: %s", snd_strerror (err))); return FALSE; } sw_params_failed: { GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), ("Setting of swparams failed: %s", snd_strerror (err))); return FALSE; } }
static gint gst_alsasink_write (GstAudioSink * asink, gpointer data, guint length) { GstAlsaSink *alsa; gint err; gint cptr; guint8 *ptr = data; alsa = GST_ALSA_SINK (asink); if (alsa->iec958 && alsa->need_swap) { guint i; guint16 *ptr_tmp = (guint16 *) ptr; GST_DEBUG_OBJECT (asink, "swapping bytes"); for (i = 0; i < length / 2; i++) { ptr_tmp[i] = GUINT16_SWAP_LE_BE (ptr_tmp[i]); } } GST_LOG_OBJECT (asink, "received audio samples buffer of %u bytes", length); cptr = length / alsa->bpf; GST_ALSA_SINK_LOCK (asink); while (cptr > 0) { /* start by doing a blocking wait for free space. Set the timeout * to 4 times the period time */ err = snd_pcm_wait (alsa->handle, (4 * alsa->period_time / 1000)); if (err < 0) { GST_DEBUG_OBJECT (asink, "wait error, %d", err); } else { GST_DELAY_SINK_LOCK (asink); err = snd_pcm_writei (alsa->handle, ptr, cptr); GST_DELAY_SINK_UNLOCK (asink); } GST_DEBUG_OBJECT (asink, "written %d frames out of %d", err, cptr); if (err < 0) { GST_DEBUG_OBJECT (asink, "Write error: %s", snd_strerror (err)); if (err == -EAGAIN) { continue; } else if (err == -ENODEV) { goto device_disappeared; } else if (xrun_recovery (alsa, alsa->handle, err) < 0) { goto write_error; } continue; } ptr += snd_pcm_frames_to_bytes (alsa->handle, err); cptr -= err; } GST_ALSA_SINK_UNLOCK (asink); return length - (cptr * alsa->bpf); write_error: { GST_ALSA_SINK_UNLOCK (asink); return length; /* skip one period */ } device_disappeared: { GST_ELEMENT_ERROR (asink, RESOURCE, WRITE, (_("Error outputting to audio device. " "The device has been disconnected.")), (NULL)); goto write_error; } }