예제 #1
0
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;
    }
}
예제 #2
0
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;
    }
}
예제 #3
0
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;
}
예제 #4
0
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;
  }
}
예제 #5
0
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 */
    }
}
예제 #6
0
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;
}
예제 #7
0
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;
    }
}
예제 #8
0
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);
}
예제 #9
0
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;
  }
}
예제 #10
0
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;
}
예제 #11
0
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;
}
예제 #12
0
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;
}
예제 #13
0
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);
}
예제 #14
0
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;
  }
}
예제 #15
0
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;
    }
}
예제 #16
0
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);
}
예제 #17
0
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;
}
예제 #18
0
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;
  }
}
예제 #19
0
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;
    }
}
예제 #20
0
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;
  }
}