Skip to content

Commit

Permalink
Unblock ASIOStart() before calling bufferSwitch().
Browse files Browse the repository at this point in the history
Fixes #60.
  • Loading branch information
dechamps committed Sep 2, 2020
1 parent 6be63c8 commit 1262460
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 16 deletions.
10 changes: 9 additions & 1 deletion src/flexasio/FlexASIO/flexasio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ namespace flexasio {
return result;
}()),
hostSupportsOutputReady(preparedState.flexASIO.hostSupportsOutputReady),
activeStream(StartStream(preparedState.stream.get())) {}
activeStream(preparedState.stream.get()) {}

void FlexASIO::Stop() {
if (!preparedState.has_value()) throw ASIOException(ASE_InvalidMode, "stop() called before createBuffers()");
Expand Down Expand Up @@ -844,6 +844,14 @@ namespace flexasio {
memset(output_samples[output_channel_index], 0, frameCount * outputSampleSizeInBytes);
}

// Some backends (e.g. WASAPI) issue the first stream callback from within Pa_StartStream().
// This is problematic because some host applications (e.g. foo_out_asio) wait for Start() to finish
// before returning from bufferSwitch(), resulting in a deadlock. See: https://github.com/dechamps/FlexASIO/issues/60
// To work around this problem, unblock Start() if it is still running by the time we reach this point.
// One possible downside of this approach is that we might not report Start() errors properly if they occur
// after the first stream callback fires.
if (state == InitialState()) activeStream.EndWaitForStartOutcome();

// See dechamps_ASIOUtil/BUFFERS.md for the gory details of how ASIO buffer management works.

if (state != State::PRIMING) {
Expand Down
5 changes: 3 additions & 2 deletions src/flexasio/FlexASIO/flexasio.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ namespace flexasio {
PreparedState& preparedState;
const bool host_supports_timeinfo;
const bool hostSupportsOutputReady;
State state = hostSupportsOutputReady ? State::PRIMING : State::PRIMED;
State InitialState() const { return hostSupportsOutputReady ? State::PRIMING : State::PRIMED; }
State state = InitialState();
// The index of the "unlocked" buffer (or "half-buffer", i.e. 0 or 1) that contains data not currently being processed by the ASIO host.
long driverBufferIndex = state == State::PRIMING ? 1 : 0;
std::atomic<SamplePosition> samplePosition;
Expand All @@ -162,7 +163,7 @@ namespace flexasio {

Win32HighResolutionTimer win32HighResolutionTimer;
Registration registration{ preparedState.runningState, *this };
const ActiveStream activeStream;
ActiveStream activeStream;
};

static int StreamCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) throw();
Expand Down
81 changes: 72 additions & 9 deletions src/flexasio/FlexASIO/portaudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,25 @@
#include "log.h"
#include "../FlexASIOUtil/portaudio.h"

#include <Objbase.h>
#include <comdef.h>

namespace flexasio {

namespace {

class COMInitializer {
public:
COMInitializer() {
::_com_util::CheckError(CoInitializeEx(NULL, COINIT_MULTITHREADED));
}
~COMInitializer() {
CoUninitialize();
}
};

}

void StreamDeleter::operator()(PaStream* stream) throw() {
Log() << "Closing PortAudio stream " << stream;
const auto error = Pa_CloseStream(stream);
Expand All @@ -28,19 +45,65 @@ namespace flexasio {
return Stream(stream);
}

void StreamStopper::operator()(PaStream* stream) throw() {
ActiveStream::ActiveStream(PaStream* stream) : stream(stream), startThread([this] {
std::exception_ptr exception;
try {
Log() << "Starting PortAudio stream " << this->stream;
{
COMInitializer comInitializer; // Required because WASAPI Pa_StartStream() calls into the COM library
const auto error = Pa_StartStream(this->stream);
if (error != paNoError) throw std::runtime_error(std::string("unable to start PortAudio stream: ") + Pa_GetErrorText(error));
}
Log() << "PortAudio stream started";
}
catch (...) {
exception = std::current_exception();
}
Log() << "Setting start outcome";
try {
if (exception)
promisedOutcome.set_exception(exception);
else
promisedOutcome.set_value();
}
catch (const std::future_error& future_error) {
if (future_error.code() != std::future_errc::promise_already_satisfied) throw;
Log() << "Start outcome already set";
return;
}
Log() << "Start outcome set";
}) {
Log() << "Waiting for start outcome";
promisedOutcome.get_future().get();
Log() << "Start outcome is OK";
}

ActiveStream::StartThread::~StartThread() {
if (thread.joinable()) {
Log() << "Waiting for start thread to finish";
thread.join();
Log() << "Start thread finished";
}
}

void ActiveStream::EndWaitForStartOutcome() {
Log() << "Ending wait for outcome";
try {
promisedOutcome.set_value();
}
catch (const std::future_error& future_error) {
if (future_error.code() != std::future_errc::promise_already_satisfied) throw;
Log() << "Start outcome already set";
return;
}
Log() << "Outcome wait ended";
}

ActiveStream::~ActiveStream() {
Log() << "Stopping PortAudio stream " << stream;
const auto error = Pa_StopStream(stream);
if (error != paNoError)
Log() << "Unable to stop PortAudio stream: " << Pa_GetErrorText(error);
}

ActiveStream StartStream(PaStream* const stream) {
Log() << "Starting PortAudio stream " << stream;
const auto error = Pa_StartStream(stream);
if (error != paNoError) throw std::runtime_error(std::string("unable to start PortAudio stream: ") + Pa_GetErrorText(error));
Log() << "PortAudio stream started";
return ActiveStream(stream);
}

}
27 changes: 23 additions & 4 deletions src/flexasio/FlexASIO/portaudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <portaudio.h>

#include <memory>
#include <future>
#include <thread>

namespace flexasio {

Expand All @@ -12,10 +14,27 @@ namespace flexasio {
using Stream = std::unique_ptr<PaStream, StreamDeleter>;
Stream OpenStream(const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate, unsigned long framesPerBuffer, PaStreamFlags streamFlags, PaStreamCallback *streamCallback, void *userData);

struct StreamStopper {
void operator()(PaStream*) throw();
class ActiveStream {
public:
ActiveStream(PaStream*);
~ActiveStream();

void EndWaitForStartOutcome();

private:
class StartThread {
public:
template <typename... Args> StartThread(Args&&... args) : thread(std::forward<Args>(args)...) {}
~StartThread();

private:
std::thread thread;
};

PaStream* const stream;

std::promise<void> promisedOutcome;
StartThread startThread;
};
using ActiveStream = std::unique_ptr<PaStream, StreamStopper>;
ActiveStream StartStream(PaStream*);

}

0 comments on commit 1262460

Please sign in to comment.