Ejemplo n.º 1
0
static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
    SoundIoInStream *instream = &is->pub;
    SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
    SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
    pa_threaded_mainloop_lock(sipa->main_loop);

    pa_stream_flags_t flags = PA_STREAM_AUTO_TIMING_UPDATE;
    if (instream->software_latency > 0.0)
        flags = (pa_stream_flags_t) (flags|PA_STREAM_ADJUST_LATENCY);

    int err = pa_stream_connect_record(ispa->stream,
            instream->device->id,
            &ispa->buffer_attr, flags);
    if (err) {
        pa_threaded_mainloop_unlock(sipa->main_loop);
        return SoundIoErrorOpeningDevice;
    }

    while (!ispa->stream_ready)
        pa_threaded_mainloop_wait(sipa->main_loop);

    pa_operation *update_timing_info_op = pa_stream_update_timing_info(ispa->stream, timing_update_callback, si);
    if ((err = perform_operation(si, update_timing_info_op))) {
        pa_threaded_mainloop_unlock(sipa->main_loop);
        return err;
    }


    pa_threaded_mainloop_unlock(sipa->main_loop);
    return 0;
}
Ejemplo n.º 2
0
/* Someone requested that the latency is shown */
static void sigusr1_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {

    if (!stream)
        return;

    pa_operation_unref(pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL));
}
Ejemplo n.º 3
0
static int instream_start_pa(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
    struct SoundIoInStream *instream = &is->pub;
    struct SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
    struct SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
    pa_threaded_mainloop_lock(sipa->main_loop);

    pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING);

    int err = pa_stream_connect_record(ispa->stream,
            instream->device->id,
            &ispa->buffer_attr, flags);
    if (err) {
        pa_threaded_mainloop_unlock(sipa->main_loop);
        return SoundIoErrorOpeningDevice;
    }

    while (!SOUNDIO_ATOMIC_LOAD(ispa->stream_ready))
        pa_threaded_mainloop_wait(sipa->main_loop);

    pa_operation *update_timing_info_op = pa_stream_update_timing_info(ispa->stream, timing_update_callback, si);
    if ((err = perform_operation(si, update_timing_info_op))) {
        pa_threaded_mainloop_unlock(sipa->main_loop);
        return err;
    }


    pa_threaded_mainloop_unlock(sipa->main_loop);
    return 0;
}
Ejemplo n.º 4
0
void CAESinkPULSE::GetDelay(AEDelayStatus& status)
{
  if (!m_IsAllocated)
  {
    status.SetDelay(0);
    return;
  }
  int error = 0;
  pa_usec_t latency = (pa_usec_t) -1;
  pa_threaded_mainloop_lock(m_MainLoop);
  if ((error = pa_stream_get_latency(m_Stream, &latency, NULL)) < 0)
  {
    if (error == -PA_ERR_NODATA)
    {
      WaitForOperation(pa_stream_update_timing_info(m_Stream, NULL,NULL), m_MainLoop, "Update Timing Information");
      if ((error = pa_stream_get_latency(m_Stream, &latency, NULL)) < 0)
      {
        CLog::Log(LOGDEBUG, "GetDelay - Failed to get Latency %d", error); 
      }
    }
  }
  if (error < 0 )
    latency = (pa_usec_t) 0;

  pa_threaded_mainloop_unlock(m_MainLoop);
  status.SetDelay(latency / 1000000.0);
}
Ejemplo n.º 5
0
static void stream_latency_cb(pa_stream *p, void *userdata) {
#ifndef INTERPOLATE
    pa_operation *o;

    o = pa_stream_update_timing_info(p, NULL, NULL);
    pa_operation_unref(o);
#endif
}
Ejemplo n.º 6
0
static void time_event_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) {
    if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) {
        pa_operation *o;
        if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL)))
            pa_log(_("pa_stream_update_timing_info() failed: %s"), pa_strerror(pa_context_errno(context)));
        else
            pa_operation_unref(o);
    }

    pa_context_rttime_restart(context, e, pa_rtclock_now() + TIME_EVENT_USEC);
}
Ejemplo n.º 7
0
// Return the current latency in seconds
static float get_delay(struct ao *ao)
{
    /* This code basically does what pa_stream_get_latency() _should_
     * do, but doesn't due to multiple known bugs in PulseAudio (at
     * PulseAudio version 2.1). In particular, the timing interpolation
     * mode (PA_STREAM_INTERPOLATE_TIMING) can return completely bogus
     * values, and the non-interpolating code has a bug causing too
     * large results at end of stream (so a stream never seems to finish).
     * This code can still return wrong values in some cases due to known
     * PulseAudio bugs that can not be worked around on the client side.
     *
     * We always query the server for latest timing info. This may take
     * too long to work well with remote audio servers, but at least
     * this should be enough to fix the normal local playback case.
     */
    struct priv *priv = ao->priv;
    pa_threaded_mainloop_lock(priv->mainloop);
    if (!waitop(priv, pa_stream_update_timing_info(priv->stream, NULL, NULL))) {
        GENERIC_ERR_MSG(priv->context, "pa_stream_update_timing_info() failed");
        return 0;
    }
    pa_threaded_mainloop_lock(priv->mainloop);
    const pa_timing_info *ti = pa_stream_get_timing_info(priv->stream);
    if (!ti) {
        pa_threaded_mainloop_unlock(priv->mainloop);
        GENERIC_ERR_MSG(priv->context, "pa_stream_get_timing_info() failed");
        return 0;
    }
    const struct pa_sample_spec *ss = pa_stream_get_sample_spec(priv->stream);
    if (!ss) {
        pa_threaded_mainloop_unlock(priv->mainloop);
        GENERIC_ERR_MSG(priv->context, "pa_stream_get_sample_spec() failed");
        return 0;
    }
    // data left in PulseAudio's main buffers (not written to sink yet)
    int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
    // since this info may be from a while ago, playback has progressed since
    latency -= ti->transport_usec;
    // data already moved from buffers to sink, but not played yet
    int64_t sink_latency = ti->sink_usec;
    if (!ti->playing)
        /* At the end of a stream, part of the data "left" in the sink may
         * be padding silence after the end; that should be subtracted to
         * get the amount of real audio from our stream. This adjustment
         * is missing from Pulseaudio's own get_latency calculations
         * (as of PulseAudio 2.1). */
        sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
    if (sink_latency > 0)
        latency += sink_latency;
    if (latency < 0)
        latency = 0;
    pa_threaded_mainloop_unlock(priv->mainloop);
    return latency / 1e6;
}
Ejemplo n.º 8
0
void QPulseAudioThread::time_event_callback ( pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata )
{
    struct timeval next;

    if ( stream && pa_stream_get_state ( stream ) == PA_STREAM_READY ) {
        pa_operation *o;
        if ( ! ( o = pa_stream_update_timing_info ( stream, stream_update_timing_callback, NULL ) ) )
            fprintf ( stderr, "pa_stream_update_timing_info() failed: %s\n", pa_strerror ( pa_context_errno ( context ) ) );
        else
            pa_operation_unref ( o );
    }

    pa_gettimeofday ( &next );
    pa_timeval_add ( &next, TIME_EVENT_USEC );

    m->time_restart ( e, &next );
}
Ejemplo n.º 9
0
static int init(struct ao *ao, char *params)
{
    struct pa_sample_spec ss;
    struct pa_channel_map map;
    char *devarg = NULL;
    char *host = NULL;
    char *sink = NULL;
    const char *version = pa_get_library_version();

    struct priv *priv = talloc_zero(ao, struct priv);
    ao->priv = priv;

    if (params) {
        devarg = strdup(params);
        sink = strchr(devarg, ':');
        if (sink)
            *sink++ = 0;
        if (devarg[0])
            host = devarg;
    }

    priv->broken_pause = false;
    /* not sure which versions are affected, assume 0.9.11* to 0.9.14*
     * known bad: 0.9.14, 0.9.13
     * known good: 0.9.9, 0.9.10, 0.9.15
     * To test: pause, wait ca. 5 seconds, framestep and see if MPlayer
     * hangs somewhen. */
    if (strncmp(version, "0.9.1", 5) == 0 && version[5] >= '1'
        && version[5] <= '4') {
        mp_msg(MSGT_AO, MSGL_WARN,
               "[pulse] working around probably broken pause functionality,\n"
               "        see http://www.pulseaudio.org/ticket/440\n");
        priv->broken_pause = true;
    }

    ss.channels = ao->channels;
    ss.rate = ao->samplerate;

    const struct format_map *fmt_map = format_maps;
    while (fmt_map->mp_format != ao->format) {
        if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) {
            mp_msg(MSGT_AO, MSGL_V,
                   "AO: [pulse] Unsupported format, using default\n");
            fmt_map = format_maps;
            break;
        }
        fmt_map++;
    }
    ao->format = fmt_map->mp_format;
    ss.format = fmt_map->pa_format;

    if (!pa_sample_spec_valid(&ss)) {
        mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Invalid sample spec\n");
        goto fail;
    }

    pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA);
    ao->bps = pa_bytes_per_second(&ss);

    if (!(priv->mainloop = pa_threaded_mainloop_new())) {
        mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate main loop\n");
        goto fail;
    }

    if (!(priv->context = pa_context_new(pa_threaded_mainloop_get_api(
                                 priv->mainloop), PULSE_CLIENT_NAME))) {
        mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate context\n");
        goto fail;
    }

    pa_context_set_state_callback(priv->context, context_state_cb, ao);

    if (pa_context_connect(priv->context, host, 0, NULL) < 0)
        goto fail;

    pa_threaded_mainloop_lock(priv->mainloop);

    if (pa_threaded_mainloop_start(priv->mainloop) < 0)
        goto unlock_and_fail;

    /* Wait until the context is ready */
    pa_threaded_mainloop_wait(priv->mainloop);

    if (pa_context_get_state(priv->context) != PA_CONTEXT_READY)
        goto unlock_and_fail;

    if (!(priv->stream = pa_stream_new(priv->context, "audio stream", &ss,
                                       &map)))
        goto unlock_and_fail;

    pa_stream_set_state_callback(priv->stream, stream_state_cb, ao);
    pa_stream_set_write_callback(priv->stream, stream_request_cb, ao);
    pa_stream_set_latency_update_callback(priv->stream,
                                          stream_latency_update_cb, ao);
    pa_buffer_attr bufattr = {
        .maxlength = -1,
        .tlength = pa_usec_to_bytes(1000000, &ss),
        .prebuf = -1,
        .minreq = -1,
        .fragsize = -1,
    };
    if (pa_stream_connect_playback(priv->stream, sink, &bufattr,
                                   PA_STREAM_INTERPOLATE_TIMING
                                   | PA_STREAM_AUTO_TIMING_UPDATE, NULL,
                                   NULL) < 0)
        goto unlock_and_fail;

    /* Wait until the stream is ready */
    pa_threaded_mainloop_wait(priv->mainloop);

    if (pa_stream_get_state(priv->stream) != PA_STREAM_READY)
        goto unlock_and_fail;

    pa_threaded_mainloop_unlock(priv->mainloop);

    free(devarg);
    return 0;

unlock_and_fail:

    if (priv->mainloop)
        pa_threaded_mainloop_unlock(priv->mainloop);

fail:
    if (priv->context)
        GENERIC_ERR_MSG(priv->context, "Init failed");
    free(devarg);
    uninit(ao, true);
    return -1;
}

static void cork(struct ao *ao, bool pause)
{
    struct priv *priv = ao->priv;
    pa_threaded_mainloop_lock(priv->mainloop);
    priv->retval = 0;
    if (!waitop(priv, pa_stream_cork(priv->stream, pause, success_cb, ao)) ||
        !priv->retval)
        GENERIC_ERR_MSG(priv->context, "pa_stream_cork() failed");
}

// Play the specified data to the pulseaudio server
static int play(struct ao *ao, void *data, int len, int flags)
{
    struct priv *priv = ao->priv;
    /* For some reason Pulseaudio behaves worse if this is done after
     * the write - rapidly repeated seeks result in bogus increasing
     * reported latency. */
    if (priv->did_reset)
        cork(ao, false);
    pa_threaded_mainloop_lock(priv->mainloop);
    if (pa_stream_write(priv->stream, data, len, NULL, 0,
                        PA_SEEK_RELATIVE) < 0) {
        GENERIC_ERR_MSG(priv->context, "pa_stream_write() failed");
        len = -1;
    }
    if (priv->did_reset) {
        priv->did_reset = false;
        if (!waitop(priv, pa_stream_update_timing_info(priv->stream,
                                                       success_cb, ao))
            || !priv->retval)
            GENERIC_ERR_MSG(priv->context, "pa_stream_UPP() failed");
    } else
        pa_threaded_mainloop_unlock(priv->mainloop);
    return len;
}

// Reset the audio stream, i.e. flush the playback buffer on the server side
static void reset(struct ao *ao)
{
    // pa_stream_flush() works badly if not corked
    cork(ao, true);
    struct priv *priv = ao->priv;
    pa_threaded_mainloop_lock(priv->mainloop);
    priv->retval = 0;
    if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) ||
        !priv->retval)
        GENERIC_ERR_MSG(priv->context, "pa_stream_flush() failed");
    priv->did_reset = true;
}
static void audio_callback(void* data)
{
  sa_stream_t* s = (sa_stream_t*)data;
  unsigned int bytes_per_frame = s->sample_spec.channels * pa_sample_size(&s->sample_spec);
  size_t buffer_size = s->sample_spec.rate * bytes_per_frame;
  char* buffer = malloc(buffer_size);

  while(1) {
    char* dst = buffer;
    size_t bytes_to_copy, bytes;

    pa_threaded_mainloop_lock(s->m);
    while(1) {
      if (s == NULL || s->stream == NULL) {
        if (s != NULL && s->m != NULL) 
          pa_threaded_mainloop_unlock(s->m);
        goto free_buffer;
      }
      if ((bytes_to_copy = pa_stream_writable_size(s->stream)) == (size_t) -1) {
        fprintf(stderr, "pa_stream_writable_size() failed: %s", pa_strerror(pa_context_errno(s->context)));
        pa_threaded_mainloop_unlock(s->m);
        goto free_buffer;
      }
      if(bytes_to_copy > 0)
        break;
      pa_threaded_mainloop_wait(s->m);
    }
    pa_threaded_mainloop_unlock(s->m);
    if (bytes_to_copy > buffer_size)
      bytes_to_copy = buffer_size;
    bytes = bytes_to_copy;

    pthread_mutex_lock(&s->mutex);
    if (!s->thread_id) {
      pthread_mutex_unlock(&s->mutex);
      break;
    }
    /*
     * Consume data from the start of the buffer list.
     */
    while (1) {
      unsigned int avail = s->bl_head->end - s->bl_head->start;
      assert(s->bl_head->start <= s->bl_head->end);

      if (avail >= bytes_to_copy) {
        /*
         * We have all we need in the head buffer, so just grab it and go.
         */
        memcpy(dst, s->bl_head->data + s->bl_head->start, bytes_to_copy);
        s->bl_head->start += bytes_to_copy;
        break;
    
      } else {
        sa_buf* next = 0;
        /*
         * Copy what we can from the head and move on to the next buffer.
         */
        memcpy(dst, s->bl_head->data + s->bl_head->start, avail);
        s->bl_head->start += avail;
        dst += avail;
        bytes_to_copy -= avail;
        /*
         * We want to free the now-empty buffer, but not if it's also the
         * current tail. If it is the tail, we don't have enough data to fill
         * the destination buffer, so we write less and give up.
         */
        next = s->bl_head->next;
        if (next == NULL) {
          bytes = bytes-bytes_to_copy;
          break;
        }
        free(s->bl_head);
        s->bl_head = next;
        s->n_bufs--;
      } /* if (avail >= bytes_to_copy), else */
    } /* while (1) */

    if(bytes > 0) {
      pa_threaded_mainloop_lock(s->m);
      if (pa_stream_write(s->stream, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE) < 0) {
        fprintf(stderr, "pa_stream_write() failed: %s", pa_strerror(pa_context_errno(s->context)));
        pa_threaded_mainloop_unlock(s->m);
        return;
      }
      pa_stream_update_timing_info(s->stream, NULL, NULL);
      s->bytes_written += bytes;
      pa_threaded_mainloop_unlock(s->m);
    }
    pthread_mutex_unlock(&s->mutex);
  }
free_buffer:
  free(buffer);
}
Ejemplo n.º 11
0
static int outstream_open_pa(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
    struct SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
    struct SoundIoOutStream *outstream = &os->pub;

    if ((unsigned)outstream->layout.channel_count > PA_CHANNELS_MAX)
        return SoundIoErrorIncompatibleBackend;

    if (!outstream->name)
        outstream->name = "SoundIoOutStream";

    struct SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
    SOUNDIO_ATOMIC_STORE(ospa->stream_ready, false);
    SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(ospa->clear_buffer_flag);

    assert(sipa->pulse_context);

    pa_threaded_mainloop_lock(sipa->main_loop);

    pa_sample_spec sample_spec;
    sample_spec.format = to_pulseaudio_format(outstream->format);
    sample_spec.rate = outstream->sample_rate;

    sample_spec.channels = outstream->layout.channel_count;
    pa_channel_map channel_map = to_pulseaudio_channel_map(&outstream->layout);

    ospa->stream = pa_stream_new(sipa->pulse_context, outstream->name, &sample_spec, &channel_map);
    if (!ospa->stream) {
        pa_threaded_mainloop_unlock(sipa->main_loop);
        outstream_destroy_pa(si, os);
        return SoundIoErrorNoMem;
    }
    pa_stream_set_state_callback(ospa->stream, playback_stream_state_callback, os);

    ospa->buffer_attr.maxlength = UINT32_MAX;
    ospa->buffer_attr.tlength = UINT32_MAX;
    ospa->buffer_attr.prebuf = 0;
    ospa->buffer_attr.minreq = UINT32_MAX;
    ospa->buffer_attr.fragsize = UINT32_MAX;

    int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate;
    if (outstream->software_latency > 0.0) {
        int buffer_length = outstream->bytes_per_frame *
            ceil_dbl_to_int(outstream->software_latency * bytes_per_second / (double)outstream->bytes_per_frame);

        ospa->buffer_attr.maxlength = buffer_length;
        ospa->buffer_attr.tlength = buffer_length;
    }

    pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_START_CORKED | PA_STREAM_AUTO_TIMING_UPDATE |
            PA_STREAM_INTERPOLATE_TIMING);

    int err = pa_stream_connect_playback(ospa->stream,
            outstream->device->id, &ospa->buffer_attr,
            flags, NULL, NULL);
    if (err) {
        pa_threaded_mainloop_unlock(sipa->main_loop);
        return SoundIoErrorOpeningDevice;
    }

    while (!SOUNDIO_ATOMIC_LOAD(ospa->stream_ready))
        pa_threaded_mainloop_wait(sipa->main_loop);

    pa_operation *update_timing_info_op = pa_stream_update_timing_info(ospa->stream, timing_update_callback, si);
    if ((err = perform_operation(si, update_timing_info_op))) {
        pa_threaded_mainloop_unlock(sipa->main_loop);
        return err;
    }

    size_t writable_size = pa_stream_writable_size(ospa->stream);
    outstream->software_latency = ((double)writable_size) / (double)bytes_per_second;

    pa_threaded_mainloop_unlock(sipa->main_loop);

    return 0;
}
Ejemplo n.º 12
0
int
cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
                  cubeb_stream_params stream_params, unsigned int latency,
                  cubeb_data_callback data_callback, cubeb_state_callback state_callback,
                  void * user_ptr)
{
  pa_sample_spec ss;
  cubeb_stream * stm;
  pa_operation * o;
  pa_buffer_attr battr;
  pa_channel_map map;

  if (stream_params.rate < 1 || stream_params.rate > 192000 ||
      stream_params.channels < 1 || stream_params.channels > 32 ||
      latency < 1 || latency > 2000) {
    return CUBEB_ERROR_INVALID_FORMAT;
  }

  switch (stream_params.format) {
  case CUBEB_SAMPLE_S16LE:
    ss.format = PA_SAMPLE_S16LE;
    break;
  case CUBEB_SAMPLE_S16BE:
    ss.format = PA_SAMPLE_S16BE;
    break;
  case CUBEB_SAMPLE_FLOAT32LE:
    ss.format = PA_SAMPLE_FLOAT32LE;
    break;
  case CUBEB_SAMPLE_FLOAT32BE:
    ss.format = PA_SAMPLE_FLOAT32BE;
    break;
  default:
    return CUBEB_ERROR_INVALID_FORMAT;
  }

  ss.rate = stream_params.rate;
  ss.channels = stream_params.channels;

  /* XXX check that this does the right thing for Vorbis and WaveEx */
  pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT);

  stm = calloc(1, sizeof(*stm));
  assert(stm);

  stm->context = context;
  assert(stm->context);

  stm->data_callback = data_callback;
  stm->state_callback = state_callback;
  stm->user_ptr = user_ptr;

  stm->sample_spec = ss;

  battr.maxlength = -1;
  battr.tlength = pa_usec_to_bytes(latency * 1000, &stm->sample_spec);
  battr.prebuf = -1;
  battr.minreq = battr.tlength / 2;
  battr.fragsize = -1;

  pa_threaded_mainloop_lock(stm->context->mainloop);
  stm->stream = pa_stream_new(stm->context->context, stream_name, &ss, &map);
  pa_stream_set_state_callback(stm->stream, stream_state_callback, stm->context->mainloop);
  pa_stream_set_write_callback(stm->stream, stream_request_callback, stm);
  pa_stream_connect_playback(stm->stream, NULL, &battr,
                             PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
                             PA_STREAM_START_CORKED,
                             NULL, NULL);
  pa_threaded_mainloop_unlock(stm->context->mainloop);

  stream_state_wait(stm, PA_STREAM_READY);

  /* force a timing update now, otherwise timing info does not become valid
     until some point after initialization has completed. */
  pa_threaded_mainloop_lock(stm->context->mainloop);
  o = pa_stream_update_timing_info(stm->stream, stream_success_callback, stm->context->mainloop);
  operation_wait(stm->context, o);
  pa_operation_unref(o);
  pa_threaded_mainloop_unlock(stm->context->mainloop);

  *stream = stm;

  return CUBEB_OK;
}
Ejemplo n.º 13
0
/*!
 * \Write outgoing samples directly to pulseaudio server.
 * \param playdev Input. Device to which to play the samples.
 * \param nSamples Input. Number of samples to play.
 * \param cSamples Input. Sample buffer to play from.
 * \param report_latency Input. 1 to update \c quisk_sound_state.latencyPlay, 0 otherwise.
 * \param volume Input. Ratio in [0,1] by which to scale the played samples.
 */
void quisk_play_pulseaudio(struct sound_dev *dev, int nSamples, complex double *cSamples, 
    int report_latency, double volume) {
    pa_stream *s = dev->handle;
    int i=0, n=0;
    void *fbuffer;
    int fbuffer_bytes = 0;
    
    if( !dev || nSamples <= 0)
        return;
    
    if (dev->cork_status)
        return;
    
    if (report_latency) {     // Report the latency, if requested.
        pa_operation *o;
        
        pa_threaded_mainloop_lock(pa_ml);
        
        if (!(o = pa_stream_update_timing_info(s, stream_timing_callback, dev))) {
            printf("pa_stream_update_timing(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        }
        else {
            while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
                pa_threaded_mainloop_wait(pa_ml);
            pa_operation_unref(o);
        }
        
        pa_threaded_mainloop_unlock(pa_ml);
        
    }
    

    fbuffer = pa_xmalloc(nSamples * dev->num_channels * dev->sample_bytes);
   
    // Convert from complex data to framebuffer

    if (dev->sample_bytes == 4) {
        float fi=0.f, fq=0.f;
        for(i = 0, n = 0; n < nSamples; i += (dev->num_channels * 4), ++n) {
            fi = (volume * creal(cSamples[n])) / CLIP32;
            fq = (volume * cimag(cSamples[n])) / CLIP32;
            memcpy(fbuffer + i + (dev->channel_I * 4), &fi, 4);
            memcpy(fbuffer + i + (dev->channel_Q * 4), &fq, 4);
        }
    }

    else if (dev->sample_bytes == 2) {
        int ii, qq;
        for(i = 0, n = 0; n < nSamples; i += (dev->num_channels * 2), ++n) {
            ii = (int)(volume * creal(cSamples[n]) / 65536);
            qq = (int)(volume * cimag(cSamples[n]) / 65536);
            memcpy(fbuffer + i + (dev->channel_I * 2), &ii, 2);
            memcpy(fbuffer + i + (dev->channel_Q * 2), &qq, 2);
        }
    }

    else {
       printf("Unknown sample size for %s", dev->name);
       exit(1);
    }



    fbuffer_bytes = nSamples * dev->num_channels * dev->sample_bytes;
    pa_threaded_mainloop_lock(pa_ml);
    size_t writable = pa_stream_writable_size(s);


    if (writable > 0) {
        if ( writable > 1024*1000 ) //sanity check to prevent pa_xmalloc from crashing on monitor streams
            writable = 1024*1000;
        if (fbuffer_bytes > writable) {
            if (quisk_sound_state.verbose_pulse)
                printf("Truncating write by %u bytes\n", fbuffer_bytes - (int)writable);
            fbuffer_bytes = writable;
        }
        pa_stream_write(dev->handle, fbuffer, (size_t)fbuffer_bytes, NULL, 0, PA_SEEK_RELATIVE);
        //printf("wrote %d to %s\n", writable, dev->name);
    }
    else {
        if (quisk_sound_state.verbose_pulse)
            printf("Can't write to stream %s. Dropping %d bytes\n", dev->name, fbuffer_bytes);
    }
    
    pa_threaded_mainloop_unlock(pa_ml);
    pa_xfree(fbuffer);
    fbuffer=NULL;
}