/**
 * Pulseaudio stream state callback
 */
static void
stream_state_callback (pa_stream * s, void *userdata)
{
  GNUNET_assert (NULL != s);

  switch (pa_stream_get_state (s))
  {
  case PA_STREAM_CREATING:
  case PA_STREAM_TERMINATED:
    break;
  case PA_STREAM_READY:
    {
      const pa_buffer_attr *a;
      char cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
      char sst[PA_SAMPLE_SPEC_SNPRINT_MAX];

      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
		  _("Stream successfully created.\n"));

      if (!(a = pa_stream_get_buffer_attr (s)))
      {
	GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
		    _("pa_stream_get_buffer_attr() failed: %s\n"),
		    pa_strerror (pa_context_errno
				 (pa_stream_get_context (s))));

      }
      else
      {
	GNUNET_log (GNUNET_ERROR_TYPE_INFO,
		    _("Buffer metrics: maxlength=%u, fragsize=%u\n"),
		    a->maxlength, a->fragsize);
      }
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
		  _("Using sample spec '%s', channel map '%s'.\n"),
		  pa_sample_spec_snprint (sst, sizeof (sst),
					  pa_stream_get_sample_spec (s)),
		  pa_channel_map_snprint (cmt, sizeof (cmt),
					  pa_stream_get_channel_map (s)));

      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
		  _("Connected to device %s (%u, %ssuspended).\n"),
		  pa_stream_get_device_name (s),
		  pa_stream_get_device_index (s),
		  pa_stream_is_suspended (s) ? "" : "not ");
    }
    break;
  case PA_STREAM_FAILED:
  default:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
		_("Stream error: %s\n"),
		pa_strerror (pa_context_errno (pa_stream_get_context (s))));
    quit (1);
  }
}
Beispiel #2
0
void
AudioStream::stream_state_callback(pa_stream* s, void* /*user_data*/)
{
    char str[PA_SAMPLE_SPEC_SNPRINT_MAX];

    switch (pa_stream_get_state(s)) {
        case PA_STREAM_CREATING:
            DEBUG("Stream is creating...");
            break;

        case PA_STREAM_TERMINATED:
            DEBUG("Stream is terminating...");
            break;

        case PA_STREAM_READY:
            DEBUG("Stream successfully created, connected to %s", pa_stream_get_device_name(s));
            DEBUG("maxlength %u", pa_stream_get_buffer_attr(s)->maxlength);
            DEBUG("tlength %u", pa_stream_get_buffer_attr(s)->tlength);
            DEBUG("prebuf %u", pa_stream_get_buffer_attr(s)->prebuf);
            DEBUG("minreq %u", pa_stream_get_buffer_attr(s)->minreq);
            DEBUG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize);
            DEBUG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s)));
            break;

        case PA_STREAM_UNCONNECTED:
            DEBUG("Stream unconnected");
            break;

        case PA_STREAM_FAILED:
        default:
            ERROR("Sink/Source doesn't exists: %s" , pa_strerror(pa_context_errno(pa_stream_get_context(s))));
            break;
    }
}
void
AudioStream::stateChanged(pa_stream* s)
{
    UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX];

    switch (pa_stream_get_state(s)) {
        case PA_STREAM_CREATING:
            RING_DBG("Stream is creating...");
            break;

        case PA_STREAM_TERMINATED:
            RING_DBG("Stream is terminating...");
            break;

        case PA_STREAM_READY:
            RING_DBG("Stream successfully created, connected to %s", pa_stream_get_device_name(s));
            //RING_DBG("maxlength %u", pa_stream_get_buffer_attr(s)->maxlength);
            //RING_DBG("tlength %u", pa_stream_get_buffer_attr(s)->tlength);
            //RING_DBG("prebuf %u", pa_stream_get_buffer_attr(s)->prebuf);
            //RING_DBG("minreq %u", pa_stream_get_buffer_attr(s)->minreq);
            //RING_DBG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize);
            //RING_DBG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s)));
            break;

        case PA_STREAM_UNCONNECTED:
            RING_DBG("Stream unconnected");
            break;

        case PA_STREAM_FAILED:
        default:
            RING_ERR("Stream failure: %s" , pa_strerror(pa_context_errno(pa_stream_get_context(s))));
            break;
    }
}
/* This routine is called whenever the stream state changes */
void QPulseAudioThread::stream_state_callback ( pa_stream *s, void *userdata )
{
    assert ( s );
    QPulseAudioThread * thread = (QPulseAudioThread *)userdata;

    switch ( pa_stream_get_state ( s ) ) {
    case PA_STREAM_UNCONNECTED:
        qDebug() << "UNCONNECTED";
        break;
    case PA_STREAM_CREATING:
        qDebug() << "CREATED";
        break;
    case PA_STREAM_TERMINATED:
        qDebug() << "TERMINATED";
        break;
    case PA_STREAM_READY:
        qDebug() << "READY";
        if ( verbose ) {
            const pa_buffer_attr *a;
            fprintf ( stderr, "Stream successfully created.\n" );

            if ( ! ( a = pa_stream_get_buffer_attr ( s ) ) )
                fprintf ( stderr, "pa_stream_get_buffer_attr() failed: %s\n", pa_strerror ( pa_context_errno ( pa_stream_get_context ( s ) ) ) );
            else {
                fprintf(stderr, "Buffer metrics: maxlength=%u, fragsize=%u\n", a->maxlength, a->fragsize);
            }
        }
        break;
    case PA_STREAM_FAILED:
        qDebug() << "FAILED";
    default:
        fprintf ( stderr, "Stream error: %s\n", pa_strerror ( pa_context_errno ( pa_stream_get_context ( s ) ) ) );
        pulseQuit ( 1 );
    }
}
void stream_state_callback(pa_stream *s, void *userdata) {
  assert(s);

  switch (pa_stream_get_state(s)) {
  case PA_STREAM_CREATING:
    break;
  case PA_STREAM_TERMINATED:
    break;

  case PA_STREAM_READY:

    if (verbose) {
      const pa_buffer_attr *a;
      char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];

      printf("Stream successfully created.\n");

      if (!(a = pa_stream_get_buffer_attr(s)))
	printf("pa_stream_get_buffer_attr() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
      else {
	printf("Buffer metrics: maxlength=%u, fragsize=%u\n", a->maxlength, a->fragsize);
                    
      }
      /*
	printf("Using sample spec '%s', channel map '%s'.",
	pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)),
	pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s)));
      */

      printf("Connected to device %s (%u, %ssuspended).\n",
	     pa_stream_get_device_name(s),
	     pa_stream_get_device_index(s),
	     pa_stream_is_suspended(s) ? "" : "not ");
    }

    break;

  case PA_STREAM_FAILED:
  default:
    printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
    exit(1); //quit(1);
  }
}
Beispiel #6
0
void PulseAudioSystem::stream_callback(pa_stream *s, void *userdata) {
	PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata);
	switch (pa_stream_get_state(s)) {
		case PA_STREAM_FAILED:
			qWarning("PulseAudio: Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
			break;
		default:
			break;
	}
	pas->wakeup();
}
/*drain stream function*/
void quisk_drain_cork_stream(struct sound_dev *dev) {
    pa_stream *s = dev->handle;
    pa_operation *o;
   
    if (!(o = pa_stream_drain(s, stream_drain_complete, NULL))) {
        printf("pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        exit(1);
    }
    
    pa_operation_unref(o);
}
/* Stream draining complete */
static void stream_drain_complete(pa_stream*s, int success, void *userdata) {
    struct sound_dev *dev = userdata;

    if (!success) {
        printf("Failed to drain stream: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        quit(1);
    }

    if (quisk_sound_state.verbose_pulse)
        printf("Playback stream %s drained.\n", dev->name);
}
// This is called by the play function when the timing structure is updated.
static void stream_timing_callback(pa_stream *s, int success, void *userdata) {
    struct sound_dev *dev = userdata;
    pa_usec_t l;
    int negative = 0;
    assert(s);

    if (!success || pa_stream_get_latency(s, &l, &negative) < 0) {
        printf("pa_stream_get_latency() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        return;
    }

    dev->dev_latency = (int)l;
    if (negative)
        dev->dev_latency *= -1;
    pa_threaded_mainloop_signal(pa_ml, 0);
}
Beispiel #10
0
/* This routine is called whenever the stream state changes */
static void stream_state_callback(pa_stream *s, void *userdata) {
    pa_assert(s);

    switch (pa_stream_get_state(s)) {
        case PA_STREAM_CREATING:
        case PA_STREAM_TERMINATED:
            break;

        case PA_STREAM_READY:

            if (verbose) {
                const pa_buffer_attr *a;
                char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];

                pa_log(_("Stream successfully created."));

                if (!(a = pa_stream_get_buffer_attr(s)))
                    pa_log(_("pa_stream_get_buffer_attr() failed: %s"), pa_strerror(pa_context_errno(pa_stream_get_context(s))));
                else {

                    if (mode == PLAYBACK)
                        pa_log(_("Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u"), a->maxlength, a->tlength, a->prebuf, a->minreq);
                    else {
                        pa_assert(mode == RECORD);
                        pa_log(_("Buffer metrics: maxlength=%u, fragsize=%u"), a->maxlength, a->fragsize);
                    }
                }

                pa_log(_("Using sample spec '%s', channel map '%s'."),
                        pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)),
                        pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s)));

                pa_log(_("Connected to device %s (%u, %ssuspended)."),
                        pa_stream_get_device_name(s),
                        pa_stream_get_device_index(s),
                        pa_stream_is_suspended(s) ? "" : "not ");
            }

            break;

        case PA_STREAM_FAILED:
        default:
            pa_log(_("Stream error: %s"), pa_strerror(pa_context_errno(pa_stream_get_context(s))));
            quit(1);
    }
}
Beispiel #11
0
static void
stream_state_callback (pa_stream *pa, void *data)
{
  switch (pa_stream_get_state(pa)) {
  case PA_STREAM_CREATING:
  case PA_STREAM_TERMINATED:
  case PA_STREAM_READY:
  case PA_STREAM_UNCONNECTED:
    break;

  case PA_STREAM_FAILED:
    g_printerr("PA stream failed: %s\n",
	       pa_strerror(pa_context_errno(pa_stream_get_context(pa))));
  default:
    break;
  }
}
//Flush function. Holds mainloop lock until operation is completed.
void quisk_flush_pulseaudio(struct sound_dev *dev) {
    pa_stream *s = dev->handle;
    pa_operation *o;
    
    pa_threaded_mainloop_lock(pa_ml);
    
    if (!(o = pa_stream_flush(s, stream_flushed_callback, dev))) {
        printf("pa_stream_flush(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        exit(1);
    }
    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);
}
static void outputStreamStateCallback(pa_stream *stream, void *userdata)
{
    Q_UNUSED(userdata)
    pa_stream_state_t state = pa_stream_get_state(stream);
#ifdef DEBUG_PULSE
    qDebug() << "Stream state: " << QPulseAudioInternal::stateToQString(state);
#endif
    switch (state) {
    case PA_STREAM_CREATING:
    case PA_STREAM_READY:
    case PA_STREAM_TERMINATED:
        break;

    case PA_STREAM_FAILED:
    default:
        qWarning() << QString("Stream error: %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(stream))));
        QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
        pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
        break;
    }
}
/* This routine is called whenever the stream state changes */
static void stream_state_callback(pa_stream *s, void *userdata) {
    fail_unless(s != NULL);

    switch (pa_stream_get_state(s)) {
        case PA_STREAM_UNCONNECTED:
        case PA_STREAM_CREATING:
        case PA_STREAM_TERMINATED:
            break;

        case PA_STREAM_READY: {

            int r, i = (int) (long) userdata;

            fprintf(stderr, "Writing data to stream %i.\n", i);

            r = pa_stream_write(s, data, sizeof(data), nop_free_cb, (int64_t) sizeof(data) * (int64_t) i, PA_SEEK_ABSOLUTE);
            fail_unless(r == 0);

            /* Be notified when this stream is drained */
            pa_stream_set_underflow_callback(s, underflow_cb, userdata);

            /* All streams have been set up, let's go! */
            if (++n_streams_ready >= NSTREAMS) {
                fprintf(stderr, "Uncorking\n");
                pa_operation_unref(pa_stream_cork(s, 0, NULL, NULL));
            }

            break;
        }

        default:
        case PA_STREAM_FAILED:
            fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
            fail();
    }
}
static void inputStreamStateCallback(pa_stream *stream, void *userdata)
{
    Q_UNUSED(userdata);
    pa_stream_state_t state = pa_stream_get_state(stream);
#ifdef DEBUG_PULSE
    qDebug() << "Stream state: " << QPulseAudioInternal::stateToQString(state);
#endif
    switch (state) {
        case PA_STREAM_CREATING:
        break;
        case PA_STREAM_READY: {
#ifdef DEBUG_PULSE
            QPulseAudioInput *audioInput = static_cast<QPulseAudioInput*>(userdata);
            const pa_buffer_attr *buffer_attr = pa_stream_get_buffer_attr(stream);
            qDebug() << "*** maxlength: " << buffer_attr->maxlength;
            qDebug() << "*** prebuf: " << buffer_attr->prebuf;
            qDebug() << "*** fragsize: " << buffer_attr->fragsize;
            qDebug() << "*** minreq: " << buffer_attr->minreq;
            qDebug() << "*** tlength: " << buffer_attr->tlength;

            pa_sample_spec spec = QPulseAudioInternal::audioFormatToSampleSpec(audioInput->format());
            qDebug() << "*** bytes_to_usec: " << pa_bytes_to_usec(buffer_attr->fragsize, &spec);
#endif
            }
            break;
        case PA_STREAM_TERMINATED:
            break;
        case PA_STREAM_FAILED:
        default:
            qWarning() << QString("Stream error: %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(stream))));
            QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
            pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
            break;
    }
}
qint64 QPulseAudioInput::read(char *data, qint64 len)
{
    m_bytesAvailable = checkBytesReady();

    setError(QAudio::NoError);
    setState(QAudio::ActiveState);

    int readBytes = 0;

    if (!m_pullMode && !m_tempBuffer.isEmpty()) {
        readBytes = qMin(static_cast<int>(len), m_tempBuffer.size());
        memcpy(data, m_tempBuffer.constData(), readBytes);
        m_totalTimeValue += readBytes;

        if (readBytes < m_tempBuffer.size()) {
            m_tempBuffer.remove(0, readBytes);
            return readBytes;
        }

        m_tempBuffer.clear();
    }

    while (pa_stream_readable_size(m_stream) > 0) {
        size_t readLength = 0;

#ifdef DEBUG_PULSE
        qDebug() << "QPulseAudioInput::read -- " << pa_stream_readable_size(m_stream) << " bytes available from pulse audio";
#endif

        QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
        pulseEngine->lock();

        const void *audioBuffer;

        // Second and third parameters (audioBuffer and length) to pa_stream_peek are output parameters,
        // the audioBuffer pointer is set to point to the actual pulse audio data,
        // and the length is set to the length of this data.
        if (pa_stream_peek(m_stream, &audioBuffer, &readLength) < 0) {
            qWarning() << QString("pa_stream_peek() failed: %1").arg(pa_strerror(pa_context_errno(pa_stream_get_context(m_stream))));
            pulseEngine->unlock();
            return 0;
        }

        qint64 actualLength = 0;
        if (m_pullMode) {
            QByteArray adjusted(readLength, Qt::Uninitialized);
            applyVolume(audioBuffer, adjusted.data(), readLength);
            actualLength = m_audioSource->write(adjusted);

            if (actualLength < qint64(readLength)) {
                pulseEngine->unlock();

                setError(QAudio::UnderrunError);
                setState(QAudio::IdleState);

                return actualLength;
            }
        } else {
            actualLength = qMin(static_cast<int>(len - readBytes), static_cast<int>(readLength));
            applyVolume(audioBuffer, data + readBytes, actualLength);
        }

#ifdef DEBUG_PULSE
        qDebug() << "QPulseAudioInput::read -- wrote " << actualLength << " to client";
#endif

        if (actualLength < qint64(readLength)) {
#ifdef DEBUG_PULSE
            qDebug() << "QPulseAudioInput::read -- appending " << readLength - actualLength << " bytes of data to temp buffer";
#endif
            int diff = readLength - actualLength;
            int oldSize = m_tempBuffer.size();
            m_tempBuffer.resize(m_tempBuffer.size() + diff);
            applyVolume(static_cast<const char *>(audioBuffer) + actualLength, m_tempBuffer.data() + oldSize, diff);
            QMetaObject::invokeMethod(this, "userFeed", Qt::QueuedConnection);
        }

        m_totalTimeValue += actualLength;
        readBytes += actualLength;

        pa_stream_drop(m_stream);
        pulseEngine->unlock();

        if (!m_pullMode && readBytes >= len)
            break;

        if (m_intervalTime && (m_timeStamp.elapsed() + m_elapsedTimeOffset) > m_intervalTime) {
            emit notify();
            m_elapsedTimeOffset = m_timeStamp.elapsed() + m_elapsedTimeOffset - m_intervalTime;
            m_timeStamp.restart();
        }
    }

#ifdef DEBUG_PULSE
    qDebug() << "QPulseAudioInput::read -- returning after reading " << readBytes << " bytes";
#endif

    return readBytes;
}
void stream_state_callback(pa_stream *s, void *userdata) {
    struct sound_dev *dev = userdata;
    assert(s);
    assert(dev);
    
    switch (pa_stream_get_state(s)) {
        case PA_STREAM_CREATING:
            break;
            
        case PA_STREAM_TERMINATED:
            if (quisk_sound_state.verbose_pulse)
                printf("stream %s terminated\n", dev->name);
            streams_ready--;
            break;
            
        case PA_STREAM_READY:
            streams_ready++; //increment counter to tell other thread that this stream is ready
            if (quisk_sound_state.verbose_pulse) {
                const pa_buffer_attr *a;
                printf("Connected to device %s (%u, %ssuspended). ",
                       pa_stream_get_device_name(s), pa_stream_get_device_index(s),
                       pa_stream_is_suspended(s) ? "" : "not ");

                if (!(a = pa_stream_get_buffer_attr(s)))
                    printf("pa_stream_get_buffer_attr() failed: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
                else if (!(a->prebuf)) {
                    printf("Buffer metrics %s: maxlength=%u, fragsize=%u\n", dev->name, a->maxlength, a->fragsize);
                }
                else {
                    printf("Buffer metrics %s: maxlength=%u, prebuf=%u, tlength=%u  minreq=%u\n",
                           dev->name, a->maxlength, a->prebuf, a->tlength, a->minreq);
                }
            }
            break;
        
        case PA_STREAM_FAILED:
        default:
            printf("Stream error: %s - %s\n", dev->name, pa_strerror(pa_context_errno(pa_stream_get_context(s))));
            exit(1);
    }
}
/*!
 * \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;
}