diff --git a/src/media/audio/audiolayer.h b/src/media/audio/audiolayer.h index ab0e5483da6cdff09fde0616a32658500aec6b83..679b35610c5b284ae38440dfaac64bd411582d49 100644 --- a/src/media/audio/audiolayer.h +++ b/src/media/audio/audiolayer.h @@ -98,7 +98,7 @@ public: 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; } @@ -106,7 +106,7 @@ public: bool waitForStart(const std::chrono::duration<Rep, Period>& rel_time) const { 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(); } @@ -252,7 +252,7 @@ protected: 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}; mutable std::condition_variable startedCv_; diff --git a/src/media/audio/portaudio/portaudiolayer.cpp b/src/media/audio/portaudio/portaudiolayer.cpp index 57f19a9d9bec93a48cf62f62473a6a7802334550..3a6aca9fb8c5611f5254e764635abf88108449bb 100644 --- a/src/media/audio/portaudio/portaudiolayer.cpp +++ b/src/media/audio/portaudio/portaudiolayer.cpp @@ -42,16 +42,22 @@ struct PortAudioLayer::PortAudioLayerImpl ~PortAudioLayerImpl(); void init(PortAudioLayer&); + void initInput(PortAudioLayer&); + void initOutput(PortAudioLayer&); void terminate() const; - void initStream(PortAudioLayer&); + bool initInputStream(PortAudioLayer&); + bool initOutputStream(PortAudioLayer&); + bool initFullDuplexStream(PortAudioLayer&); std::vector<std::string> getDeviceByType(AudioDeviceType type) const; int getIndexByType(AudioDeviceType type); int getInternalIndexByType(const int index, AudioDeviceType type); PaDeviceIndex indexIn_; + bool inputInitialized_ {false}; PaDeviceIndex indexOut_; PaDeviceIndex indexRing_; + bool outputInitialized_ {false}; AudioBuffer playbackBuff_; @@ -84,7 +90,18 @@ struct PortAudioLayer::PortAudioLayerImpl PortAudioLayer::PortAudioLayer(const AudioPreference& pref) : AudioLayer {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() { @@ -154,43 +171,104 @@ PortAudioLayer::getIndexRingtone() const void PortAudioLayer::startStream(AudioDeviceType stream) { - { - std::lock_guard<std::mutex> lock(mutex_); - if (status_ != Status::Idle) - return; - status_ = Status::Started; + auto startPlayback = [this](bool fullDuplexMode = false) -> bool { + std::unique_lock<std::mutex> lock(mutex_); + if (status_.load() != Status::Idle) + return false; + bool ret {false}; + if (fullDuplexMode) + ret = pimpl_->initFullDuplexStream(*this); + else + ret = pimpl_->initOutputStream(*this); + if (ret) { + status_.store(Status::Started); + lock.unlock(); + flushUrgent(); + 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; } - pimpl_->initStream(*this); - - flushUrgent(); - flushMain(); } void PortAudioLayer::stopStream(AudioDeviceType stream) { - { - std::lock_guard<std::mutex> lock {mutex_}; - - if (status_ != Status::Started) - return; - - 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)); + auto stopPaStream = [](PaStream* stream) -> bool { + if (!stream) + return false; + auto err = Pa_StopStream(stream); + if (err != paNoError) { + 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 @@ -234,6 +312,72 @@ PortAudioLayer::PortAudioLayerImpl::~PortAudioLayerImpl() 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> PortAudioLayer::PortAudioLayerImpl::getDeviceByType(AudioDeviceType type) const { @@ -268,40 +412,14 @@ PortAudioLayer::PortAudioLayerImpl::init(PortAudioLayer& parent) terminate(); } - auto numDevices = Pa_GetDeviceCount(); - if (indexOut_ <= paNoDevice || indexOut_ >= numDevices) { - 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; - } - } + initInput(parent); + initOutput(parent); 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 @@ -430,21 +548,57 @@ openFullDuplexStream(PaStream** stream, JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); } -void -PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) +bool +PortAudioLayer::PortAudioLayerImpl::initInputStream(PortAudioLayer& parent) { - parent.dcblocker_.reset(); + JAMI_DBG("Open PortAudio Input Stream"); + auto& stream = streams_[Direction::Input]; + if (indexIn_ != paNoDevice) { + openStreamDevice( + &streams_[Direction::Input], + indexIn_, + Direction::Input, + [](const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) -> int { + auto layer = static_cast<PortAudioLayer*>(userData); + return layer->pimpl_->paInputCallback(*layer, + static_cast<const AudioSample*>(inputBuffer), + static_cast<AudioSample*>(outputBuffer), + framesPerBuffer, + timeInfo, + statusFlags); + }, + &parent); + } else { + JAMI_ERR("Error: No valid input device. There will be no mic."); + return false; + } - auto apiIndex = Pa_GetDefaultHostApi(); - auto apiInfo = Pa_GetHostApiInfo(apiIndex); - JAMI_DBG() << "Initializing Portaudio streams using: " << apiInfo->name; + JAMI_DBG("Starting PortAudio Input Stream"); + auto err = Pa_StartStream(stream); + if (err != paNoError) { + JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); + return false; + } - JAMI_DBG("Open PortAudio Full-duplex input/output stream"); - if (indexOut_ != paNoDevice && indexIn_ != paNoDevice) { - openFullDuplexStream( - &streams_[Direction::IO], - indexIn_, + parent.recordChanged(true); + return true; +} + +bool +PortAudioLayer::PortAudioLayerImpl::initOutputStream(PortAudioLayer& parent) +{ + JAMI_DBG("Open PortAudio Output Stream"); + auto& stream = streams_[Direction::Output]; + if (indexOut_ != paNoDevice) { + openStreamDevice( + &stream, indexOut_, + Direction::Output, [](const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, @@ -452,76 +606,72 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) PaStreamCallbackFlags statusFlags, void* userData) -> int { auto layer = static_cast<PortAudioLayer*>(userData); - return layer->pimpl_->paIOCallback(*layer, - static_cast<const AudioSample*>(inputBuffer), - static_cast<AudioSample*>(outputBuffer), - framesPerBuffer, - timeInfo, - statusFlags); + return layer->pimpl_->paOutputCallback(*layer, + static_cast<const AudioSample*>(inputBuffer), + static_cast<AudioSample*>(outputBuffer), + framesPerBuffer, + timeInfo, + statusFlags); }, &parent); } else { - JAMI_DBG("Open PortAudio Output Stream"); - if (indexOut_ != paNoDevice) { - openStreamDevice( - &streams_[Direction::Output], - indexOut_, - Direction::Output, - [](const void* inputBuffer, - void* outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void* userData) -> int { - auto layer = static_cast<PortAudioLayer*>(userData); - return layer->pimpl_->paOutputCallback(*layer, - static_cast<const AudioSample*>( - inputBuffer), - static_cast<AudioSample*>(outputBuffer), - framesPerBuffer, - timeInfo, - statusFlags); - }, - &parent); - } 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"); - if (indexIn_ != paNoDevice) { - openStreamDevice( - &streams_[Direction::Input], - indexIn_, - Direction::Input, - [](const void* inputBuffer, - void* outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void* userData) -> int { - auto layer = static_cast<PortAudioLayer*>(userData); - return layer->pimpl_->paInputCallback(*layer, - static_cast<const AudioSample*>( - inputBuffer), - static_cast<AudioSample*>(outputBuffer), - framesPerBuffer, - timeInfo, - statusFlags); - }, - &parent); - } else { - JAMI_ERR("Error: No valid input device. There will be no mic."); - } + JAMI_DBG("Starting PortAudio Output Stream"); + auto err = Pa_StartStream(stream); + if (err != paNoError) { + JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); + return false; } - JAMI_DBG("Start PortAudio Streams"); - for (auto& st_ptr : streams_) { - if (st_ptr) { - auto err = Pa_StartStream(st_ptr); - if (err != paNoError) - JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); - } + 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_, + indexOut_, + [](const void* inputBuffer, + void* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData) -> int { + auto layer = static_cast<PortAudioLayer*>(userData); + return layer->pimpl_->paIOCallback(*layer, + static_cast<const AudioSample*>(inputBuffer), + static_cast<AudioSample*>(outputBuffer), + framesPerBuffer, + timeInfo, + statusFlags); + }, + &parent); + + JAMI_DBG("Start PortAudio I/O Streams"); + auto err = Pa_StartStream(stream); + if (err != paNoError) { + JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); + return false; + } + + parent.recordChanged(true); + parent.playbackChanged(true); + return true; } int @@ -545,7 +695,6 @@ PortAudioLayer::PortAudioLayerImpl::paOutputCallback(PortAudioLayer& parent, auto nFrames = toPlay->pointer()->nb_samples * toPlay->pointer()->channels; std::copy_n((AudioSample*) toPlay->pointer()->extended_data[0], nFrames, outputBuffer); - return paContinue; }