diff --git a/contrib/src/portaudio/0001-add-get-default-comm-devices-api.patch b/contrib/src/portaudio/0001-add-get-default-comm-devices-api.patch new file mode 100644 index 0000000000000000000000000000000000000000..193606f29fd700330dc562833be3dd2c3ba908ef --- /dev/null +++ b/contrib/src/portaudio/0001-add-get-default-comm-devices-api.patch @@ -0,0 +1,223 @@ +From 70ea89479fcff70982bb95ea82426320b8fe0845 Mon Sep 17 00:00:00 2001 +From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> +Date: Thu, 11 Mar 2021 19:19:54 -0500 +Subject: [PATCH] add get default comm devices api + +--- + include/portaudio.h | 11 +++++ + src/common/pa_front.c | 54 +++++++++++++++++++++++++ + src/hostapi/wasapi/pa_win_wasapi.c | 65 ++++++++++++++++++++++++++++++ + 3 files changed, 130 insertions(+) + +diff --git a/include/portaudio.h b/include/portaudio.h +index 8a94aaf..0466a54 100644 +--- a/include/portaudio.h ++++ b/include/portaudio.h +@@ -320,6 +320,13 @@ typedef struct PaHostApiInfo + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; ++ ++ /** The default input/output devices for this host API(if supported). ++ The value will be a device index ranging from 0 to (Pa_GetDeviceCount()-1), ++ or paNoDevice if no default output device is available. ++ */ ++ PaDeviceIndex defaultCommInputDevice; ++ PaDeviceIndex defaultCommOutputDevice; + + } PaHostApiInfo; + +@@ -449,6 +456,10 @@ PaDeviceIndex Pa_GetDefaultInputDevice( void ); + PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + ++PaDeviceIndex Pa_GetDefaultCommOutputDevice( void ); ++PaDeviceIndex Pa_GetDefaultCommInputDevice( void ); ++ ++ + /** The type used to represent monotonic time in seconds. PaTime is + used for the fields of the PaStreamCallbackTimeInfo argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). +diff --git a/src/common/pa_front.c b/src/common/pa_front.c +index 188cee9..ea0c3da 100644 +--- a/src/common/pa_front.c ++++ b/src/common/pa_front.c +@@ -234,6 +234,8 @@ static PaError InitializeHostApis( void ) + PaUtilHostApiRepresentation* hostApi = hostApis_[hostApisCount_]; + assert( hostApi->info.defaultInputDevice < hostApi->info.deviceCount ); + assert( hostApi->info.defaultOutputDevice < hostApi->info.deviceCount ); ++ assert( hostApi->info.defaultCommInputDevice < hostApi->info.deviceCount ); ++ assert( hostApi->info.defaultCommOutputDevice < hostApi->info.deviceCount ); + + /* the first successfully initialized host API with a default input *or* + output device is used as the default host API. +@@ -253,6 +255,12 @@ static PaError InitializeHostApis( void ) + if( hostApi->info.defaultOutputDevice != paNoDevice ) + hostApi->info.defaultOutputDevice += baseDeviceIndex; + ++ if( hostApi->info.defaultCommInputDevice != paNoDevice ) ++ hostApi->info.defaultCommInputDevice += baseDeviceIndex; ++ ++ if( hostApi->info.defaultCommOutputDevice != paNoDevice ) ++ hostApi->info.defaultCommOutputDevice += baseDeviceIndex; ++ + baseDeviceIndex += hostApi->info.deviceCount; + deviceCount_ += hostApi->info.deviceCount; + +@@ -746,6 +754,52 @@ PaDeviceIndex Pa_GetDefaultOutputDevice( void ) + } + + ++PaDeviceIndex Pa_GetDefaultCommInputDevice( void ) ++{ ++ PaHostApiIndex hostApi; ++ PaDeviceIndex result; ++ ++ PA_LOGAPI_ENTER( "Pa_GetDefaultCommInputDevice" ); ++ ++ hostApi = Pa_GetDefaultHostApi(); ++ if( hostApi < 0 ) ++ { ++ result = paNoDevice; ++ } ++ else ++ { ++ result = hostApis_[hostApi]->info.defaultCommInputDevice; ++ } ++ ++ PA_LOGAPI_EXIT_T( "Pa_GetDefaultCommInputDevice", "PaDeviceIndex: %d", result ); ++ ++ return result; ++} ++ ++ ++PaDeviceIndex Pa_GetDefaultCommOutputDevice( void ) ++{ ++ PaHostApiIndex hostApi; ++ PaDeviceIndex result; ++ ++ PA_LOGAPI_ENTER( "Pa_GetDefaultCommOutputDevice" ); ++ ++ hostApi = Pa_GetDefaultHostApi(); ++ if( hostApi < 0 ) ++ { ++ result = paNoDevice; ++ } ++ else ++ { ++ result = hostApis_[hostApi]->info.defaultCommOutputDevice; ++ } ++ ++ PA_LOGAPI_EXIT_T( "Pa_GetDefaultCommOutputDevice", "PaDeviceIndex: %d", result ); ++ ++ return result; ++} ++ ++ + const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ) + { + int hostSpecificDeviceIndex; +diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c +index b12b91f..fb70eff 100644 +--- a/src/hostapi/wasapi/pa_win_wasapi.c ++++ b/src/hostapi/wasapi/pa_win_wasapi.c +@@ -441,6 +441,9 @@ typedef struct + WCHAR defaultRenderer [MAX_STR_LEN]; + WCHAR defaultCapturer [MAX_STR_LEN]; + ++ WCHAR defaultCommRenderer [MAX_STR_LEN]; ++ WCHAR defaultCommCapturer [MAX_STR_LEN]; ++ + PaWasapiDeviceInfo *devInfo; + + // Is true when WOW64 Vista/7 Workaround is needed +@@ -1463,6 +1466,8 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; ++ (*hostApi)->info.defaultCommInputDevice = paNoDevice; ++ (*hostApi)->info.defaultCommOutputDevice = paNoDevice; + + #ifndef PA_WINRT + paWasapi->enumerator = NULL; +@@ -1524,6 +1529,57 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd + } + } + ++ // getting default device ids in the eCommunications "role" ++ { ++ { ++ IMMDevice *defaultCommRenderer = NULL; ++ hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(paWasapi->enumerator, eRender, eCommunications, &defaultCommRenderer); ++ if (hr != S_OK) ++ { ++ if (hr != E_NOTFOUND) { ++ // We need to set the result to a value otherwise we will return paNoError ++ // [IF_FAILED_JUMP(hResult, error);] ++ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); ++ } ++ } ++ else ++ { ++ WCHAR *pszDeviceId = NULL; ++ hr = IMMDevice_GetId(defaultCommRenderer, &pszDeviceId); ++ // We need to set the result to a value otherwise we will return paNoError ++ // [IF_FAILED_JUMP(hResult, error);] ++ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); ++ wcsncpy(paWasapi->defaultCommRenderer, pszDeviceId, MAX_STR_LEN-1); ++ CoTaskMemFree(pszDeviceId); ++ IMMDevice_Release(defaultCommRenderer); ++ } ++ } ++ ++ { ++ IMMDevice *defaultCommCapturer = NULL; ++ hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(paWasapi->enumerator, eCapture, eCommunications, &defaultCommCapturer); ++ if (hr != S_OK) ++ { ++ if (hr != E_NOTFOUND) { ++ // We need to set the result to a value otherwise we will return paNoError ++ // [IF_FAILED_JUMP(hResult, error);] ++ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); ++ } ++ } ++ else ++ { ++ WCHAR *pszDeviceId = NULL; ++ hr = IMMDevice_GetId(defaultCommCapturer, &pszDeviceId); ++ // We need to set the result to a value otherwise we will return paNoError ++ // [IF_FAILED_JUMP(hResult, error);] ++ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); ++ wcsncpy(paWasapi->defaultCommCapturer, pszDeviceId, MAX_STR_LEN-1); ++ CoTaskMemFree(pszDeviceId); ++ IMMDevice_Release(defaultCommCapturer); ++ } ++ } ++ } ++ + hr = IMMDeviceEnumerator_EnumAudioEndpoints(paWasapi->enumerator, eAll, DEVICE_STATE_ACTIVE, &pEndPoints); + // We need to set the result to a value otherwise we will return paNoError + // [IF_FAILED_JUMP(hResult, error);] +@@ -1599,6 +1655,14 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd + {// we found the default output! + (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount; + } ++ if (lstrcmpW(paWasapi->devInfo[i].szDeviceID, paWasapi->defaultCommCapturer) == 0) ++ {// we found the default input! ++ (*hostApi)->info.defaultCommInputDevice = (*hostApi)->info.deviceCount; ++ } ++ if (lstrcmpW(paWasapi->devInfo[i].szDeviceID, paWasapi->defaultCommRenderer) == 0) ++ {// we found the default output! ++ (*hostApi)->info.defaultCommOutputDevice = (*hostApi)->info.deviceCount; ++ } + } + + hr = IMMDevice_GetState(paWasapi->devInfo[i].device, &paWasapi->devInfo[i].state); +@@ -5736,3 +5800,4 @@ void PaWasapi_FreeMemory(void *ptr) + bFirst = FALSE; + } + #endif ++ +-- +2.17.1 + diff --git a/contrib/src/portaudio/package.json b/contrib/src/portaudio/package.json index 254b89fa1badc9efa1106b96296215de7d7da287..ff5fdf84ef84b864fc0bdcaacd166e99512892a5 100644 --- a/contrib/src/portaudio/package.json +++ b/contrib/src/portaudio/package.json @@ -9,5 +9,6 @@ "PA_USE_DS=0", "PA_USE_WMME=0", "PA_USE_WDMKS=0" - ] -} \ No newline at end of file + ], + "patches": ["0001-add-get-default-comm-devices-api.patch"] +} diff --git a/src/media/audio/portaudio/portaudiolayer.cpp b/src/media/audio/portaudio/portaudiolayer.cpp index 7a284ab6c2b7825b1c6d0522de546d73db2eb0db..d3fbdd5ad32afeb55a0b0e95013a58bc1a46415c 100644 --- a/src/media/audio/portaudio/portaudiolayer.cpp +++ b/src/media/audio/portaudio/portaudiolayer.cpp @@ -50,18 +50,21 @@ struct PortAudioLayer::PortAudioLayerImpl bool initFullDuplexStream(PortAudioLayer&); bool apiInitialised_ {false}; - std::vector<std::string> getDeviceByType(AudioDeviceType type) const; + std::vector<std::string> getDevicesByType(AudioDeviceType type) const; int getIndexByType(AudioDeviceType type); - int getInternalIndexByType(const int index, AudioDeviceType type); + std::string getDeviceNameByType(const int index, AudioDeviceType type); + PaDeviceIndex getApiIndexByType(AudioDeviceType type); + std::string getApiDefaultDeviceName(AudioDeviceType type, bool commDevice) const; + + std::string deviceRecord_ {}; + std::string devicePlayback_ {}; + std::string deviceRingtone_ {}; + + static constexpr const int defaultIndex_ {0}; - PaDeviceIndex indexIn_; bool inputInitialized_ {false}; - PaDeviceIndex indexOut_; - PaDeviceIndex indexRing_; bool outputInitialized_ {false}; - AudioBuffer playbackBuff_; - std::array<PaStream*, static_cast<int>(Direction::End)> streams_; int paOutputCallback(PortAudioLayer& parent, @@ -114,42 +117,31 @@ PortAudioLayer::~PortAudioLayer() std::vector<std::string> PortAudioLayer::getCaptureDeviceList() const { - return pimpl_->getDeviceByType(AudioDeviceType::CAPTURE); + return pimpl_->getDevicesByType(AudioDeviceType::CAPTURE); } std::vector<std::string> PortAudioLayer::getPlaybackDeviceList() const { - return pimpl_->getDeviceByType(AudioDeviceType::PLAYBACK); + return pimpl_->getDevicesByType(AudioDeviceType::PLAYBACK); } int PortAudioLayer::getAudioDeviceIndex(const std::string& name, AudioDeviceType type) const { - auto deviceList = pimpl_->getDeviceByType(type); - - int numDevices = 0; - numDevices = deviceList.size(); - if (numDevices < 0) { - JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(numDevices)); - } else { - int i = 0; - for (auto d = deviceList.cbegin(); d != deviceList.cend(); ++d, ++i) { - if (*d == name) { - return i; - } - } - } - return paNoDevice; + auto devices = pimpl_->getDevicesByType(type); + auto it = std::find_if(devices.cbegin(), devices.cend(), [&name](const auto& deviceName) { + return deviceName == name; + }); + return it != devices.end() ? std::distance(devices.cbegin(), it) : -1; } std::string PortAudioLayer::getAudioDeviceName(int index, AudioDeviceType type) const { + (void) index; (void) type; - const PaDeviceInfo* deviceInfo; - deviceInfo = Pa_GetDeviceInfo(index); - return deviceInfo->name; + return {}; } int @@ -218,7 +210,7 @@ void PortAudioLayer::stopStream(AudioDeviceType stream) { auto stopPaStream = [](PaStream* stream) -> bool { - if (!stream) + if (!stream || Pa_IsStreamStopped(stream) != paNoError) return false; auto err = Pa_StopStream(stream); if (err != paNoError) { @@ -287,16 +279,16 @@ PortAudioLayer::stopStream(AudioDeviceType stream) void PortAudioLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type) { - auto internalIndex = pimpl_->getInternalIndexByType(index, type); + auto deviceName = pimpl_->getDeviceNameByType(index, type); switch (type) { case AudioDeviceType::PLAYBACK: - preference.setAlsaCardout(internalIndex); + preference.setPortAudioDevicePlayback(deviceName); break; case AudioDeviceType::CAPTURE: - preference.setAlsaCardin(internalIndex); + preference.setPortAudioDeviceRecord(deviceName); break; case AudioDeviceType::RINGTONE: - preference.setAlsaCardring(internalIndex); + preference.setPortAudioDeviceRingtone(deviceName); break; default: break; @@ -307,10 +299,9 @@ PortAudioLayer::updatePreference(AudioPreference& preference, int index, AudioDe PortAudioLayer::PortAudioLayerImpl::PortAudioLayerImpl(PortAudioLayer& parent, const AudioPreference& pref) - : indexIn_ {pref.getAlsaCardin()} - , indexOut_ {pref.getAlsaCardout()} - , indexRing_ {pref.getAlsaCardring()} - , playbackBuff_ {0, parent.audioFormat_} + : deviceRecord_ {pref.getPortAudioDeviceRecord()} + , devicePlayback_ {pref.getPortAudioDevicePlayback()} + , deviceRingtone_ {pref.getPortAudioDeviceRingtone()} { init(parent); } @@ -323,91 +314,79 @@ PortAudioLayer::PortAudioLayerImpl::~PortAudioLayerImpl() void PortAudioLayer::PortAudioLayerImpl::initInput(PortAudioLayer& parent) { - auto numDevices = Pa_GetDeviceCount(); - if (indexIn_ <= paNoDevice || indexIn_ >= numDevices) { - indexIn_ = Pa_GetDefaultInputDevice(); - } + // convert out preference to an api index + auto apiIndex = getApiIndexByType(AudioDeviceType::CAPTURE); - // Pa_GetDefaultInputDevice returned paNoDevice or we already initialized the device - if (indexIn_ == paNoDevice || inputInitialized_) + // Pa_GetDefault[Comm]InputDevice returned paNoDevice or we already initialized the device + if (apiIndex == 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 { + const auto inputDeviceInfo = Pa_GetDeviceInfo(apiIndex); + if (!inputDeviceInfo) { + // this represents complete failure after attempting a fallback to default JAMI_WARN("PortAudioLayer could not initialize input"); - indexIn_ = paNoDevice; + deviceRecord_.clear(); inputInitialized_ = true; + return; + } + + // if the device index is somehow no longer a device of the correct type, reset the + // internal index to paNoDevice and reenter in an attempt to set the default + // communications device + if (inputDeviceInfo->maxInputChannels <= 0) { + JAMI_WARN("PortAudioLayer could not initialize input, falling back to default device"); + deviceRecord_.clear(); + return initInput(parent); } + + // at this point, the device is of the correct type and can be opened + 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; } void PortAudioLayer::PortAudioLayerImpl::initOutput(PortAudioLayer& parent) { - auto numDevices = Pa_GetDeviceCount(); - if (indexOut_ <= paNoDevice || indexOut_ >= numDevices) { - indexRing_ = indexOut_ = Pa_GetDefaultOutputDevice(); - } else { - indexRing_ = indexOut_; - } + // convert out preference to an api index + auto apiIndex = getApiIndexByType(AudioDeviceType::PLAYBACK); - // Pa_GetDefaultOutputDevice returned paNoDevice or we already initialized the device - if (indexOut_ == paNoDevice || outputInitialized_) + // Pa_GetDefault[Comm]OutputDevice returned paNoDevice or we already initialized the device + if (apiIndex == 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 { + const auto outputDeviceInfo = Pa_GetDeviceInfo(apiIndex); + if (!outputDeviceInfo) { + // this represents complete failure after attempting a fallback to default JAMI_WARN("PortAudioLayer could not initialize output"); - indexOut_ = paNoDevice; + devicePlayback_.clear(); outputInitialized_ = true; + return; } -} -std::vector<std::string> -PortAudioLayer::PortAudioLayerImpl::getDeviceByType(AudioDeviceType type) const -{ - std::vector<std::string> ret; - int numDevices = 0; - - numDevices = Pa_GetDeviceCount(); - if (numDevices < 0) - JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(numDevices)); - else { - for (int i = 0; i < numDevices; i++) { - const auto deviceInfo = Pa_GetDeviceInfo(i); - if (type == AudioDeviceType::PLAYBACK) { - if (deviceInfo->maxOutputChannels > 0) - ret.push_back(deviceInfo->name); - } else { - if (deviceInfo->maxInputChannels > 0) - ret.push_back(deviceInfo->name); - } - } + // if the device index is somehow no longer a device of the correct type, reset the + // internal index to paNoDevice and reenter in an attempt to set the default + // communications device + if (outputDeviceInfo->maxOutputChannels <= 0) { + JAMI_WARN("PortAudioLayer could not initialize output, falling back to default device"); + devicePlayback_.clear(); + return initOutput(parent); } - return ret; + + // at this point, the device is of the correct type and can be opened + 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; } void @@ -432,52 +411,101 @@ PortAudioLayer::PortAudioLayerImpl::init(PortAudioLayer& parent) std::fill(std::begin(streams_), std::end(streams_), nullptr); } +std::vector<std::string> +PortAudioLayer::PortAudioLayerImpl::getDevicesByType(AudioDeviceType type) const +{ + std::vector<std::string> devices; + auto numDevices = Pa_GetDeviceCount(); + if (numDevices < 0) + JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(numDevices)); + else { + for (int i = 0; i < numDevices; i++) { + const auto deviceInfo = Pa_GetDeviceInfo(i); + if (type == AudioDeviceType::CAPTURE) { + if (deviceInfo->maxInputChannels > 0) + devices.push_back(deviceInfo->name); + } else if (deviceInfo->maxOutputChannels > 0) + devices.push_back(deviceInfo->name); + } + // add the default device aliases if requested and if there are any devices of this type + if (!devices.empty()) { + // default comm (index:0) + auto defaultDeviceName = getApiDefaultDeviceName(type, true); + devices.insert(devices.begin(), "{{Default}} - " + defaultDeviceName); + } + } + return devices; +} + int PortAudioLayer::PortAudioLayerImpl::getIndexByType(AudioDeviceType type) { - int index = indexRing_; - if (type == AudioDeviceType::PLAYBACK) { - index = indexOut_; - } else if (type == AudioDeviceType::CAPTURE) { - index = indexIn_; + auto devices = getDevicesByType(type); + if (!devices.size()) { + return 0; } + std::string_view toMatch = (type == AudioDeviceType::CAPTURE + ? deviceRecord_ + : (type == AudioDeviceType::PLAYBACK ? devicePlayback_ + : deviceRingtone_)); + auto it = std::find_if(devices.cbegin(), devices.cend(), [&toMatch](const auto& deviceName) { + return deviceName == toMatch; + }); + return it != devices.end() ? std::distance(devices.cbegin(), it) : 0; +} - auto deviceList = getDeviceByType(type); - if (!deviceList.size()) { - return paNoDevice; - } +std::string +PortAudioLayer::PortAudioLayerImpl::getDeviceNameByType(const int index, AudioDeviceType type) +{ + if (index == defaultIndex_) + return {}; - const PaDeviceInfo* indexedDeviceInfo; - indexedDeviceInfo = Pa_GetDeviceInfo(index); - if (!indexedDeviceInfo) { - return paNoDevice; - } + auto devices = getDevicesByType(type); + if (!devices.size() || index >= devices.size()) + return {}; + + return devices.at(index); +} - for (int i = 0; i < deviceList.size(); ++i) { - if (deviceList.at(i) == indexedDeviceInfo->name) { - return i; +PaDeviceIndex +PortAudioLayer::PortAudioLayerImpl::getApiIndexByType(AudioDeviceType type) +{ + auto numDevices = Pa_GetDeviceCount(); + if (numDevices < 0) + JAMI_ERR("PortAudioLayer error : %s", Pa_GetErrorText(numDevices)); + else { + std::string_view toMatch = (type == AudioDeviceType::CAPTURE + ? deviceRecord_ + : (type == AudioDeviceType::PLAYBACK ? devicePlayback_ + : deviceRingtone_)); + if (toMatch.empty()) + return type == AudioDeviceType::CAPTURE ? Pa_GetDefaultCommInputDevice() + : Pa_GetDefaultCommOutputDevice(); + for (int i = 0; i < numDevices; ++i) { + if (const auto deviceInfo = Pa_GetDeviceInfo(i)) { + if (deviceInfo->name == toMatch) + return i; + } } } - return paNoDevice; } -int -PortAudioLayer::PortAudioLayerImpl::getInternalIndexByType(const int index, AudioDeviceType type) +std::string +PortAudioLayer::PortAudioLayerImpl::getApiDefaultDeviceName(AudioDeviceType type, + bool commDevice) const { - auto deviceList = getDeviceByType(type); - if (!deviceList.size() || index >= deviceList.size()) { - return paNoDevice; + std::string deviceName {}; + PaDeviceIndex deviceIndex {paNoDevice}; + if (type == AudioDeviceType::CAPTURE) { + deviceIndex = commDevice ? Pa_GetDefaultCommInputDevice() : Pa_GetDefaultInputDevice(); + } else { + deviceIndex = commDevice ? Pa_GetDefaultCommOutputDevice() : Pa_GetDefaultOutputDevice(); } - - for (int i = 0; i < Pa_GetDeviceCount(); i++) { - const auto deviceInfo = Pa_GetDeviceInfo(i); - if (deviceList.at(index) == deviceInfo->name) { - return i; - } + if (const auto deviceInfo = Pa_GetDeviceInfo(deviceIndex)) { + deviceName = deviceInfo->name; } - - return paNoDevice; + return deviceName; } void @@ -563,10 +591,11 @@ PortAudioLayer::PortAudioLayerImpl::initInputStream(PortAudioLayer& parent) { JAMI_DBG("Open PortAudio Input Stream"); auto& stream = streams_[Direction::Input]; - if (indexIn_ != paNoDevice) { + auto apiIndex = getApiIndexByType(AudioDeviceType::CAPTURE); + if (apiIndex != paNoDevice) { openStreamDevice( &streams_[Direction::Input], - indexIn_, + apiIndex, Direction::Input, [](const void* inputBuffer, void* outputBuffer, @@ -604,10 +633,11 @@ PortAudioLayer::PortAudioLayerImpl::initOutputStream(PortAudioLayer& parent) { JAMI_DBG("Open PortAudio Output Stream"); auto& stream = streams_[Direction::Output]; - if (indexOut_ != paNoDevice) { + auto apiIndex = getApiIndexByType(AudioDeviceType::PLAYBACK); + if (apiIndex != paNoDevice) { openStreamDevice( &stream, - indexOut_, + apiIndex, Direction::Output, [](const void* inputBuffer, void* outputBuffer, @@ -643,7 +673,9 @@ PortAudioLayer::PortAudioLayerImpl::initOutputStream(PortAudioLayer& parent) bool PortAudioLayer::PortAudioLayerImpl::initFullDuplexStream(PortAudioLayer& parent) { - if (indexOut_ == paNoDevice || indexIn_ == paNoDevice) { + auto apiIndexRecord = getApiIndexByType(AudioDeviceType::CAPTURE); + auto apiIndexPlayback = getApiIndexByType(AudioDeviceType::PLAYBACK); + if (apiIndexRecord == paNoDevice || apiIndexPlayback == paNoDevice) { JAMI_ERR("Error: Invalid input/output devices. There will be no audio."); return false; } @@ -654,8 +686,8 @@ PortAudioLayer::PortAudioLayerImpl::initFullDuplexStream(PortAudioLayer& parent) auto& stream = streams_[Direction::IO]; openFullDuplexStream( &stream, - indexIn_, - indexOut_, + apiIndexRecord, + apiIndexPlayback, [](const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, diff --git a/src/preferences.cpp b/src/preferences.cpp index 975cd418d61d24225e16dade5493b6b905f42865..d122b1563a58bae7d2cedf75adb1a1a481a0fad0 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -104,6 +104,7 @@ static constexpr const char* ZID_FILE_KEY {"zidFile"}; constexpr const char* const AudioPreference::CONFIG_LABEL; static constexpr const char* ALSAMAP_KEY {"alsa"}; static constexpr const char* PULSEMAP_KEY {"pulse"}; +static constexpr const char* PORTAUDIO_KEY {"portaudio"}; static constexpr const char* CARDIN_KEY {"cardIn"}; static constexpr const char* CARDOUT_KEY {"cardOut"}; static constexpr const char* CARDRING_KEY {"cardRing"}; @@ -446,6 +447,13 @@ AudioPreference::serialize(YAML::Emitter& out) const out << YAML::Key << DEVICE_RINGTONE_KEY << YAML::Value << pulseDeviceRingtone_; out << YAML::EndMap; + // portaudio submap + out << YAML::Key << PORTAUDIO_KEY << YAML::Value << YAML::BeginMap; + out << YAML::Key << DEVICE_PLAYBACK_KEY << YAML::Value << portaudioDevicePlayback_; + out << YAML::Key << DEVICE_RECORD_KEY << YAML::Value << portaudioDeviceRecord_; + out << YAML::Key << DEVICE_RINGTONE_KEY << YAML::Value << portaudioDeviceRingtone_; + out << YAML::EndMap; + // more common options! out << YAML::Key << RECORDPATH_KEY << YAML::Value << recordpath_; out << YAML::Key << VOLUMEMIC_KEY << YAML::Value << volumemic_; @@ -495,6 +503,12 @@ AudioPreference::unserialize(const YAML::Node& in) parseValue(pulse, DEVICE_RECORD_KEY, pulseDeviceRecord_); parseValue(pulse, DEVICE_RINGTONE_KEY, pulseDeviceRingtone_); + // portaudio submap + const auto& portaudio = node[PORTAUDIO_KEY]; + parseValue(portaudio, DEVICE_PLAYBACK_KEY, portaudioDevicePlayback_); + parseValue(portaudio, DEVICE_RECORD_KEY, portaudioDeviceRecord_); + parseValue(portaudio, DEVICE_RINGTONE_KEY, portaudioDeviceRingtone_); + // more common options! parseValue(node, RECORDPATH_KEY, recordpath_); parseValue(node, VOLUMEMIC_KEY, volumemic_); diff --git a/src/preferences.h b/src/preferences.h index afec033475eccf5da2e2551fec72c53eac6ba4d6..97da4e697b19e21e572f4d43b8eb1332683e1cad 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -152,7 +152,6 @@ class AudioPreference : public Serializable public: AudioPreference(); AudioLayer* createAudioLayer(); - AudioLayer* switchAndCreateAudioLayer(); static std::vector<std::string> getSupportedAudioManagers(); @@ -161,10 +160,12 @@ public: void setAudioApi(const std::string& api) { audioApi_ = api; } void serialize(YAML::Emitter& out) const override; + void unserialize(const YAML::Node& in) override; // alsa preference int getAlsaCardin() const { return alsaCardin_; } + void setAlsaCardin(int c) { alsaCardin_ = c; } int getAlsaCardout() const { return alsaCardout_; } @@ -180,6 +181,7 @@ public: void setAlsaPlugin(const std::string& p) { alsaPlugin_ = p; } int getAlsaSmplrate() const { return alsaSmplrate_; } + void setAlsaSmplrate(int r) { alsaSmplrate_ = r; } // pulseaudio preference @@ -194,6 +196,19 @@ public: void setPulseDeviceRingtone(const std::string& r) { pulseDeviceRingtone_ = r; } + // portaudio preference + const std::string& getPortAudioDevicePlayback() const { return portaudioDevicePlayback_; } + + void setPortAudioDevicePlayback(const std::string& p) { portaudioDevicePlayback_ = p; } + + const std::string& getPortAudioDeviceRecord() const { return portaudioDeviceRecord_; } + + void setPortAudioDeviceRecord(const std::string& r) { portaudioDeviceRecord_ = r; } + + const std::string& getPortAudioDeviceRingtone() const { return portaudioDeviceRingtone_; } + + void setPortAudioDeviceRingtone(const std::string& r) { portaudioDeviceRingtone_ = r; } + // general preference const std::string& getRecordPath() const { return recordpath_; } @@ -245,6 +260,11 @@ private: std::string pulseDeviceRecord_; std::string pulseDeviceRingtone_; + // portaudio preference + std::string portaudioDevicePlayback_; + std::string portaudioDeviceRecord_; + std::string portaudioDeviceRingtone_; + // general preference std::string recordpath_; //: /home/msavard/Bureau bool alwaysRecording_;