Skip to content
Snippets Groups Projects
Commit 972ed193 authored by Andreas Traczyk's avatar Andreas Traczyk
Browse files

portaudio: refactor to support split stream design

Allows the start/stop of individual streams as well as both input
and output simultaneously. When both streams are to be started,
full duplex mode is attempted first.

Resets device indices to -1 in the case that the host api has
changed, or if the dring audio config is scrapped somehow.

Change-Id: I1655a34d2111222b6add19f1b36b53bc4a2838ec
parent e787de4e
No related branches found
No related tags found
No related merge requests found
...@@ -98,7 +98,7 @@ public: ...@@ -98,7 +98,7 @@ public:
virtual int getIndexRingtone() const = 0; virtual int getIndexRingtone() const = 0;
/** /**
* Determine wether or not the audio layer is active (i.e. stream opened) * Determine whether or not the audio layer is active (i.e. playback opened)
*/ */
inline bool isStarted() const { return status_ == Status::Started; } inline bool isStarted() const { return status_ == Status::Started; }
...@@ -106,7 +106,7 @@ public: ...@@ -106,7 +106,7 @@ public:
bool waitForStart(const std::chrono::duration<Rep, Period>& rel_time) const bool waitForStart(const std::chrono::duration<Rep, Period>& rel_time) const
{ {
std::unique_lock<std::mutex> lk(mutex_); std::unique_lock<std::mutex> lk(mutex_);
startedCv_.wait_for(lk, rel_time, [&] { return isStarted(); }); startedCv_.wait_for(lk, rel_time, [this] { return isStarted(); });
return isStarted(); return isStarted();
} }
...@@ -252,7 +252,7 @@ protected: ...@@ -252,7 +252,7 @@ protected:
std::unique_ptr<AudioFrameResizer> playbackQueue_; std::unique_ptr<AudioFrameResizer> playbackQueue_;
/** /**
* Whether or not the audio layer stream is started * Whether or not the audio layer's playback stream is started
*/ */
std::atomic<Status> status_ {Status::Idle}; std::atomic<Status> status_ {Status::Idle};
mutable std::condition_variable startedCv_; mutable std::condition_variable startedCv_;
......
...@@ -42,16 +42,22 @@ struct PortAudioLayer::PortAudioLayerImpl ...@@ -42,16 +42,22 @@ struct PortAudioLayer::PortAudioLayerImpl
~PortAudioLayerImpl(); ~PortAudioLayerImpl();
void init(PortAudioLayer&); void init(PortAudioLayer&);
void initInput(PortAudioLayer&);
void initOutput(PortAudioLayer&);
void terminate() const; void terminate() const;
void initStream(PortAudioLayer&); bool initInputStream(PortAudioLayer&);
bool initOutputStream(PortAudioLayer&);
bool initFullDuplexStream(PortAudioLayer&);
std::vector<std::string> getDeviceByType(AudioDeviceType type) const; std::vector<std::string> getDeviceByType(AudioDeviceType type) const;
int getIndexByType(AudioDeviceType type); int getIndexByType(AudioDeviceType type);
int getInternalIndexByType(const int index, AudioDeviceType type); int getInternalIndexByType(const int index, AudioDeviceType type);
PaDeviceIndex indexIn_; PaDeviceIndex indexIn_;
bool inputInitialized_ {false};
PaDeviceIndex indexOut_; PaDeviceIndex indexOut_;
PaDeviceIndex indexRing_; PaDeviceIndex indexRing_;
bool outputInitialized_ {false};
AudioBuffer playbackBuff_; AudioBuffer playbackBuff_;
...@@ -84,7 +90,18 @@ struct PortAudioLayer::PortAudioLayerImpl ...@@ -84,7 +90,18 @@ struct PortAudioLayer::PortAudioLayerImpl
PortAudioLayer::PortAudioLayer(const AudioPreference& pref) PortAudioLayer::PortAudioLayer(const AudioPreference& pref)
: AudioLayer {pref} : AudioLayer {pref}
, pimpl_ {new PortAudioLayerImpl(*this, pref)} , pimpl_ {new PortAudioLayerImpl(*this, pref)}
{} {
auto numDevices = Pa_GetDeviceCount();
if (numDevices < 0) {
JAMI_ERR("Pa_CountDevices returned 0x%x", numDevices);
return;
}
const PaDeviceInfo* deviceInfo;
for (auto i = 0; i < numDevices; i++) {
deviceInfo = Pa_GetDeviceInfo(i);
JAMI_DBG("PortAudio device: %d, %s", i, deviceInfo->name);
}
}
PortAudioLayer::~PortAudioLayer() PortAudioLayer::~PortAudioLayer()
{ {
...@@ -154,43 +171,104 @@ PortAudioLayer::getIndexRingtone() const ...@@ -154,43 +171,104 @@ PortAudioLayer::getIndexRingtone() const
void void
PortAudioLayer::startStream(AudioDeviceType stream) PortAudioLayer::startStream(AudioDeviceType stream)
{ {
{ auto startPlayback = [this](bool fullDuplexMode = false) -> bool {
std::lock_guard<std::mutex> lock(mutex_); std::unique_lock<std::mutex> lock(mutex_);
if (status_ != Status::Idle) if (status_.load() != Status::Idle)
return; return false;
status_ = Status::Started; bool ret {false};
} if (fullDuplexMode)
pimpl_->initStream(*this); ret = pimpl_->initFullDuplexStream(*this);
else
ret = pimpl_->initOutputStream(*this);
if (ret) {
status_.store(Status::Started);
lock.unlock();
flushUrgent(); flushUrgent();
flushMain(); flushMain();
} }
return ret;
};
switch (stream) {
case AudioDeviceType::ALL:
if (!startPlayback(true)) {
pimpl_->initInputStream(*this);
startPlayback();
}
break;
case AudioDeviceType::CAPTURE:
pimpl_->initInputStream(*this);
break;
case AudioDeviceType::PLAYBACK:
case AudioDeviceType::RINGTONE:
startPlayback();
break;
}
}
void void
PortAudioLayer::stopStream(AudioDeviceType stream) PortAudioLayer::stopStream(AudioDeviceType stream)
{ {
{ auto stopPaStream = [](PaStream* stream) -> bool {
std::lock_guard<std::mutex> lock {mutex_}; if (!stream)
return false;
if (status_ != Status::Started) auto err = Pa_StopStream(stream);
return; if (err != paNoError) {
JAMI_DBG("Stop PortAudio Streams");
for (auto& st_ptr : pimpl_->streams_) {
if (!st_ptr)
continue;
auto err = Pa_StopStream(st_ptr);
if (err != paNoError)
JAMI_ERR("Pa_StopStream error : %s", Pa_GetErrorText(err));
err = Pa_CloseStream(st_ptr);
if (err != paNoError)
JAMI_ERR("Pa_StopStream error : %s", Pa_GetErrorText(err)); JAMI_ERR("Pa_StopStream error : %s", Pa_GetErrorText(err));
return false;
}
err = Pa_CloseStream(stream);
if (err != paNoError) {
JAMI_ERR("Pa_CloseStream error : %s", Pa_GetErrorText(err));
return false;
} }
return true;
};
status_ = Status::Idle; auto stopPlayback = [this, &stopPaStream](bool fullDuplexMode = false) -> bool {
std::lock_guard<std::mutex> lock(mutex_);
if (status_.load() != Status::Started)
return false;
bool stopped = false;
if (fullDuplexMode)
stopped = stopPaStream(pimpl_->streams_[Direction::IO]);
else
stopped = stopPaStream(pimpl_->streams_[Direction::Output]);
if (stopped)
status_.store(Status::Idle);
return stopped;
};
bool stopped = false;
switch (stream) {
case AudioDeviceType::ALL:
if (pimpl_->streams_[Direction::IO]) {
stopped = stopPlayback(true);
} else {
stopped = stopPaStream(pimpl_->streams_[Direction::Input]) && stopPlayback();
}
if (stopped) {
recordChanged(false);
playbackChanged(false);
JAMI_DBG("PortAudioLayer I/O streams stopped");
} else
return;
break;
case AudioDeviceType::CAPTURE:
if (stopPaStream(pimpl_->streams_[Direction::Input])) {
recordChanged(false);
JAMI_DBG("PortAudioLayer input stream stopped");
} else
return;
break;
case AudioDeviceType::PLAYBACK:
case AudioDeviceType::RINGTONE:
if (stopPlayback()) {
playbackChanged(false);
JAMI_DBG("PortAudioLayer output stream stopped");
} else
return;
break;
} }
// Flush the ring buffers // Flush the ring buffers
...@@ -234,6 +312,72 @@ PortAudioLayer::PortAudioLayerImpl::~PortAudioLayerImpl() ...@@ -234,6 +312,72 @@ PortAudioLayer::PortAudioLayerImpl::~PortAudioLayerImpl()
terminate(); terminate();
} }
void
PortAudioLayer::PortAudioLayerImpl::initInput(PortAudioLayer& parent)
{
auto numDevices = Pa_GetDeviceCount();
if (indexIn_ <= paNoDevice || indexIn_ >= numDevices) {
indexIn_ = Pa_GetDefaultInputDevice();
}
// Pa_GetDefaultInputDevice returned paNoDevice or we already initialized the device
if (indexIn_ == paNoDevice || inputInitialized_)
return;
if (const auto inputDeviceInfo = Pa_GetDeviceInfo(indexIn_)) {
if (inputDeviceInfo->maxInputChannels <= 0) {
indexIn_ = paNoDevice;
return initInput(parent);
}
parent.audioInputFormat_.sample_rate = inputDeviceInfo->defaultSampleRate;
parent.audioInputFormat_.nb_channels = inputDeviceInfo->maxInputChannels;
parent.hardwareInputFormatAvailable(parent.audioInputFormat_);
JAMI_DBG("PortAudioLayer initialized input: %s {%d Hz, %d channels}",
inputDeviceInfo->name,
parent.audioInputFormat_.sample_rate,
parent.audioInputFormat_.nb_channels);
inputInitialized_ = true;
} else {
JAMI_WARN("PortAudioLayer could not initialize input");
indexIn_ = paNoDevice;
inputInitialized_ = true;
}
}
void
PortAudioLayer::PortAudioLayerImpl::initOutput(PortAudioLayer& parent)
{
auto numDevices = Pa_GetDeviceCount();
if (indexOut_ <= paNoDevice || indexOut_ >= numDevices) {
indexRing_ = indexOut_ = Pa_GetDefaultOutputDevice();
} else {
indexRing_ = indexOut_;
}
// Pa_GetDefaultOutputDevice returned paNoDevice or we already initialized the device
if (indexOut_ == paNoDevice || outputInitialized_)
return;
if (const auto outputDeviceInfo = Pa_GetDeviceInfo(indexOut_)) {
if (outputDeviceInfo->maxOutputChannels <= 0) {
indexOut_ = paNoDevice;
return initOutput(parent);
}
parent.audioFormat_.sample_rate = outputDeviceInfo->defaultSampleRate;
parent.audioFormat_.nb_channels = outputDeviceInfo->maxOutputChannels;
parent.hardwareFormatAvailable(parent.audioFormat_);
JAMI_DBG("PortAudioLayer initialized output: %s {%d Hz, %d channels}",
outputDeviceInfo->name,
parent.audioFormat_.sample_rate,
parent.audioFormat_.nb_channels);
outputInitialized_ = true;
} else {
JAMI_WARN("PortAudioLayer could not initialize output");
indexOut_ = paNoDevice;
outputInitialized_ = true;
}
}
std::vector<std::string> std::vector<std::string>
PortAudioLayer::PortAudioLayerImpl::getDeviceByType(AudioDeviceType type) const PortAudioLayer::PortAudioLayerImpl::getDeviceByType(AudioDeviceType type) const
{ {
...@@ -268,40 +412,14 @@ PortAudioLayer::PortAudioLayerImpl::init(PortAudioLayer& parent) ...@@ -268,40 +412,14 @@ PortAudioLayer::PortAudioLayerImpl::init(PortAudioLayer& parent)
terminate(); terminate();
} }
auto numDevices = Pa_GetDeviceCount(); initInput(parent);
if (indexOut_ <= paNoDevice || indexOut_ >= numDevices) { initOutput(parent);
indexRing_ = indexOut_ = Pa_GetDefaultOutputDevice();
} else {
indexRing_ = indexOut_;
}
if (indexIn_ <= paNoDevice || indexIn_ >= numDevices) {
indexIn_ = Pa_GetDefaultInputDevice();
}
if (indexOut_ != paNoDevice) {
if (const auto outputDeviceInfo = Pa_GetDeviceInfo(indexOut_)) {
parent.audioFormat_.nb_channels = outputDeviceInfo->maxOutputChannels;
parent.audioFormat_.sample_rate = outputDeviceInfo->defaultSampleRate;
parent.hardwareFormatAvailable(parent.audioFormat_);
JAMI_DBG() << "PortAudioLayer initialized output using: " << outputDeviceInfo->name;
} else {
indexOut_ = paNoDevice;
}
}
if (indexIn_ != paNoDevice) {
if (const auto inputDeviceInfo = Pa_GetDeviceInfo(indexIn_)) {
parent.audioInputFormat_.nb_channels = inputDeviceInfo->maxInputChannels;
parent.audioInputFormat_.sample_rate = inputDeviceInfo->defaultSampleRate;
parent.hardwareInputFormatAvailable(parent.audioInputFormat_);
JAMI_DBG() << "PortAudioLayer initialized input using: " << inputDeviceInfo->name;
} else {
indexIn_ = paNoDevice;
}
}
std::fill(std::begin(streams_), std::end(streams_), nullptr); std::fill(std::begin(streams_), std::end(streams_), nullptr);
auto apiIndex = Pa_GetDefaultHostApi();
auto apiInfo = Pa_GetHostApiInfo(apiIndex);
JAMI_DBG() << "Portaudio initialized using: " << apiInfo->name;
} }
int int
...@@ -430,21 +548,16 @@ openFullDuplexStream(PaStream** stream, ...@@ -430,21 +548,16 @@ openFullDuplexStream(PaStream** stream,
JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err));
} }
void bool
PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) PortAudioLayer::PortAudioLayerImpl::initInputStream(PortAudioLayer& parent)
{ {
parent.dcblocker_.reset(); JAMI_DBG("Open PortAudio Input Stream");
auto& stream = streams_[Direction::Input];
auto apiIndex = Pa_GetDefaultHostApi(); if (indexIn_ != paNoDevice) {
auto apiInfo = Pa_GetHostApiInfo(apiIndex); openStreamDevice(
JAMI_DBG() << "Initializing Portaudio streams using: " << apiInfo->name; &streams_[Direction::Input],
JAMI_DBG("Open PortAudio Full-duplex input/output stream");
if (indexOut_ != paNoDevice && indexIn_ != paNoDevice) {
openFullDuplexStream(
&streams_[Direction::IO],
indexIn_, indexIn_,
indexOut_, Direction::Input,
[](const void* inputBuffer, [](const void* inputBuffer,
void* outputBuffer, void* outputBuffer,
unsigned long framesPerBuffer, unsigned long framesPerBuffer,
...@@ -452,7 +565,7 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) ...@@ -452,7 +565,7 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent)
PaStreamCallbackFlags statusFlags, PaStreamCallbackFlags statusFlags,
void* userData) -> int { void* userData) -> int {
auto layer = static_cast<PortAudioLayer*>(userData); auto layer = static_cast<PortAudioLayer*>(userData);
return layer->pimpl_->paIOCallback(*layer, return layer->pimpl_->paInputCallback(*layer,
static_cast<const AudioSample*>(inputBuffer), static_cast<const AudioSample*>(inputBuffer),
static_cast<AudioSample*>(outputBuffer), static_cast<AudioSample*>(outputBuffer),
framesPerBuffer, framesPerBuffer,
...@@ -461,10 +574,29 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) ...@@ -461,10 +574,29 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent)
}, },
&parent); &parent);
} else { } else {
JAMI_ERR("Error: No valid input device. There will be no mic.");
return false;
}
JAMI_DBG("Starting PortAudio Input Stream");
auto err = Pa_StartStream(stream);
if (err != paNoError) {
JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err));
return false;
}
parent.recordChanged(true);
return true;
}
bool
PortAudioLayer::PortAudioLayerImpl::initOutputStream(PortAudioLayer& parent)
{
JAMI_DBG("Open PortAudio Output Stream"); JAMI_DBG("Open PortAudio Output Stream");
auto& stream = streams_[Direction::Output];
if (indexOut_ != paNoDevice) { if (indexOut_ != paNoDevice) {
openStreamDevice( openStreamDevice(
&streams_[Direction::Output], &stream,
indexOut_, indexOut_,
Direction::Output, Direction::Output,
[](const void* inputBuffer, [](const void* inputBuffer,
...@@ -475,8 +607,7 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) ...@@ -475,8 +607,7 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent)
void* userData) -> int { void* userData) -> int {
auto layer = static_cast<PortAudioLayer*>(userData); auto layer = static_cast<PortAudioLayer*>(userData);
return layer->pimpl_->paOutputCallback(*layer, return layer->pimpl_->paOutputCallback(*layer,
static_cast<const AudioSample*>( static_cast<const AudioSample*>(inputBuffer),
inputBuffer),
static_cast<AudioSample*>(outputBuffer), static_cast<AudioSample*>(outputBuffer),
framesPerBuffer, framesPerBuffer,
timeInfo, timeInfo,
...@@ -485,14 +616,36 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) ...@@ -485,14 +616,36 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent)
&parent); &parent);
} else { } else {
JAMI_ERR("Error: No valid output device. There will be no sound."); JAMI_ERR("Error: No valid output device. There will be no sound.");
return false;
} }
JAMI_DBG("Open PortAudio Input Stream"); JAMI_DBG("Starting PortAudio Output Stream");
if (indexIn_ != paNoDevice) { auto err = Pa_StartStream(stream);
openStreamDevice( if (err != paNoError) {
&streams_[Direction::Input], JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err));
return false;
}
parent.playbackChanged(true);
return true;
}
bool
PortAudioLayer::PortAudioLayerImpl::initFullDuplexStream(PortAudioLayer& parent)
{
if (indexOut_ == paNoDevice || indexIn_ == paNoDevice) {
JAMI_ERR("Error: Invalid input/output devices. There will be no audio.");
return false;
}
parent.dcblocker_.reset();
JAMI_DBG("Open PortAudio Full-duplex input/output stream");
auto& stream = streams_[Direction::IO];
openFullDuplexStream(
&stream,
indexIn_, indexIn_,
Direction::Input, indexOut_,
[](const void* inputBuffer, [](const void* inputBuffer,
void* outputBuffer, void* outputBuffer,
unsigned long framesPerBuffer, unsigned long framesPerBuffer,
...@@ -500,28 +653,25 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) ...@@ -500,28 +653,25 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent)
PaStreamCallbackFlags statusFlags, PaStreamCallbackFlags statusFlags,
void* userData) -> int { void* userData) -> int {
auto layer = static_cast<PortAudioLayer*>(userData); auto layer = static_cast<PortAudioLayer*>(userData);
return layer->pimpl_->paInputCallback(*layer, return layer->pimpl_->paIOCallback(*layer,
static_cast<const AudioSample*>( static_cast<const AudioSample*>(inputBuffer),
inputBuffer),
static_cast<AudioSample*>(outputBuffer), static_cast<AudioSample*>(outputBuffer),
framesPerBuffer, framesPerBuffer,
timeInfo, timeInfo,
statusFlags); statusFlags);
}, },
&parent); &parent);
} else {
JAMI_ERR("Error: No valid input device. There will be no mic.");
}
}
JAMI_DBG("Start PortAudio Streams"); JAMI_DBG("Start PortAudio I/O Streams");
for (auto& st_ptr : streams_) { auto err = Pa_StartStream(stream);
if (st_ptr) { if (err != paNoError) {
auto err = Pa_StartStream(st_ptr);
if (err != paNoError)
JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err));
return false;
} }
}
parent.recordChanged(true);
parent.playbackChanged(true);
return true;
} }
int int
...@@ -545,7 +695,6 @@ PortAudioLayer::PortAudioLayerImpl::paOutputCallback(PortAudioLayer& parent, ...@@ -545,7 +695,6 @@ PortAudioLayer::PortAudioLayerImpl::paOutputCallback(PortAudioLayer& parent,
auto nFrames = toPlay->pointer()->nb_samples * toPlay->pointer()->channels; auto nFrames = toPlay->pointer()->nb_samples * toPlay->pointer()->channels;
std::copy_n((AudioSample*) toPlay->pointer()->extended_data[0], nFrames, outputBuffer); std::copy_n((AudioSample*) toPlay->pointer()->extended_data[0], nFrames, outputBuffer);
return paContinue; return paContinue;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment