diff --git a/contrib/src/portaudio/fetch_and_patch.bat b/contrib/src/portaudio/fetch_and_patch.bat index b0568700006006c76ca74fdf4b21d47268de9007..108e029a7313484e01d56f44a591528246f6713d 100644 --- a/contrib/src/portaudio/fetch_and_patch.bat +++ b/contrib/src/portaudio/fetch_and_patch.bat @@ -20,6 +20,9 @@ cd %BUILD%\portaudio if "%1"=="uwp" ( %APPLY_CMD% %SRC%\portaudio\pa-uwp.patch +) else ( + %APPLY_CMD% %SRC%\portaudio\pa-dsound-aecns.patch + %APPLY_CMD% %SRC%\portaudio\pa-dsound.patch ) cd %SRC% \ No newline at end of file diff --git a/contrib/src/portaudio/pa-dsound-aecns.patch b/contrib/src/portaudio/pa-dsound-aecns.patch new file mode 100644 index 0000000000000000000000000000000000000000..80d0bcd18d595948512d04c517961abde8472788 --- /dev/null +++ b/contrib/src/portaudio/pa-dsound-aecns.patch @@ -0,0 +1,65 @@ +--- a/src/hostapi/dsound/pa_win_ds.c ++++ b/src/hostapi/dsound/pa_win_ds.c +@@ -1520,12 +1520,33 @@ static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream, + hostInputSampleFormat, PaWin_SampleFormatToLinearWaveFormatTag( hostInputSampleFormat ), + nFrameRate, inputChannelMask ); + ++ // aec and noise suppression ++ DSCEFFECTDESC dscfx[2]; ++ ZeroMemory(&dscfx[0], sizeof(DSCEFFECTDESC)); ++ dscfx[0].dwSize = sizeof(DSCEFFECTDESC); ++ dscfx[0].dwFlags = DSCFX_LOCSOFTWARE; ++ dscfx[0].guidDSCFXClass = GUID_DSCFX_CLASS_AEC; ++ dscfx[0].guidDSCFXInstance = GUID_DSCFX_MS_AEC; ++ dscfx[0].dwReserved1 = 0; ++ dscfx[0].dwReserved2 = 0; ++ ++ ZeroMemory(&dscfx[1], sizeof(DSCEFFECTDESC)); ++ dscfx[1].dwSize = sizeof(DSCEFFECTDESC); ++ dscfx[1].dwFlags = DSCFX_LOCSOFTWARE; ++ dscfx[1].guidDSCFXClass = GUID_DSCFX_CLASS_NS; ++ dscfx[1].guidDSCFXInstance = GUID_DSCFX_MS_NS; ++ dscfx[1].dwReserved1 = 0; ++ dscfx[1].dwReserved2 = 0; ++ + ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC)); + captureDesc.dwSize = sizeof(DSCBUFFERDESC); +- captureDesc.dwFlags = 0; + captureDesc.dwBufferBytes = bytesPerInputBuffer; + captureDesc.lpwfxFormat = (WAVEFORMATEX*)&captureWaveFormat; + ++ captureDesc.dwFlags = DSCBCAPS_CTRLFX; ++ captureDesc.dwFXCount = 2; ++ captureDesc.lpDSCFXDesc = dscfx; ++ + // render buffer description + + PaWin_InitializeWaveFormatExtensible( &renderWaveFormat, outputChannelCount, +@@ -1551,6 +1572,24 @@ static HRESULT InitFullDuplexInputOutputBuffers( PaWinDsStream *stream, + NULL /* pUnkOuter must be NULL */ + ); + ++ if (hr != DS_OK) { ++ PA_DEBUG(("DirectSoundFullDuplexCreate(with AEC/NS) failed. hr=%d\n", hr)); ++ // try removing AEC/NS ++ captureDesc.dwFlags = 0; ++ captureDesc.dwFXCount = 0; ++ captureDesc.lpDSCFXDesc = NULL; ++ hr = paWinDsDSoundEntryPoints.DirectSoundFullDuplexCreate8( ++ inputDevice->lpGUID, outputDevice->lpGUID, ++ &captureDesc, &secondaryRenderDesc, ++ GetDesktopWindow(), /* see InitOutputBuffer() for a discussion of whether this is a good idea */ ++ DSSCL_EXCLUSIVE, ++ &stream->pDirectSoundFullDuplex8, ++ &pCaptureBuffer8, ++ &pRenderBuffer8, ++ NULL /* pUnkOuter must be NULL */ ++ ); ++ } ++ + if( hr == DS_OK ) + { + PA_DEBUG(("DirectSoundFullDuplexCreate succeeded!\n")); +-- +2.19.0.windows.1 + diff --git a/contrib/src/portaudio/pa-dsound.patch b/contrib/src/portaudio/pa-dsound.patch new file mode 100644 index 0000000000000000000000000000000000000000..fdb224e464d3f04145e40678d978b524c734a925 --- /dev/null +++ b/contrib/src/portaudio/pa-dsound.patch @@ -0,0 +1,23 @@ +--- a/MSVC/portaudio.vcxproj ++++ b/MSVC/portaudio.vcxproj +@@ -109,7 +109,7 @@ + <IntrinsicFunctions>true</IntrinsicFunctions> + <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\src\common;..\include;.\;..\src\os\win;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> +- <PreprocessorDefinitions>PA_WDMKS_NO_KSGUID_LIB;WIN32;NDEBUG;_USRDLL;PA_ENABLE_DEBUG_OUTPUT;_CRT_SECURE_NO_DEPRECATE;PAWIN_USE_WDMKS_DEVICE_INFO;PA_USE_ASIO=0;PA_USE_DS=0;PA_USE_WMME=1;PA_USE_WASAPI=1;PA_USE_WDMKS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions> ++ <PreprocessorDefinitions>PA_ENABLE_DEBUG_OUTPUT;PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE;PA_WDMKS_NO_KSGUID_LIB;WIN32;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;PAWIN_USE_WDMKS_DEVICE_INFO;PA_USE_ASIO=0;PA_USE_DS=1;PA_USE_WMME=0;PA_USE_WASAPI=0;PA_USE_WDMKS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> +@@ -161,7 +161,7 @@ xcopy /S /Y $(ProjectDir)..\include\*.h "$(OutDir)"include</Command> + <IntrinsicFunctions>true</IntrinsicFunctions> + <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\src\common;..\include;.\;..\src\os\win;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> +- <PreprocessorDefinitions>PA_WDMKS_NO_KSGUID_LIB;_WIN64;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;PAWIN_USE_WDMKS_DEVICE_INFO;PA_USE_ASIO=0;PA_USE_DS=0;PA_USE_WMME=1;PA_USE_WASAPI=1;PA_USE_WDMKS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions> ++ <PreprocessorDefinitions>PA_ENABLE_DEBUG_OUTPUT;PAWIN_USE_DIRECTSOUNDFULLDUPLEXCREATE;PA_WDMKS_NO_KSGUID_LIB;_WIN64;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;PAWIN_USE_WDMKS_DEVICE_INFO;PA_USE_ASIO=0;PA_USE_DS=1;PA_USE_WMME=0;PA_USE_WASAPI=0;PA_USE_WDMKS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> +-- +2.19.0.windows.1 + diff --git a/src/media/audio/portaudio/portaudiolayer.cpp b/src/media/audio/portaudio/portaudiolayer.cpp index 3a1e623fe8e302dde3bb4a188b320c10421601c3..cc5ebc28293bdf707301c66bd9dc29b9953eed1f 100644 --- a/src/media/audio/portaudio/portaudiolayer.cpp +++ b/src/media/audio/portaudio/portaudiolayer.cpp @@ -3,6 +3,7 @@ * * Author: Edric Ladent-Milaret <edric.ladent-milaret@savoirfairelinux.com> * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,7 +34,7 @@ namespace ring { -enum Direction {Input=0, Output=1, End=2}; +enum Direction { Input = 0, Output = 1, IO = 2, End = 3 }; struct PortAudioLayer::PortAudioLayerImpl { @@ -69,6 +70,13 @@ struct PortAudioLayer::PortAudioLayerImpl unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags); + + int paIOCallback(PortAudioLayer& parent, + const AudioSample* inputBuffer, + AudioSample* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags); }; //################################################################################################## @@ -162,6 +170,9 @@ PortAudioLayer::stopStream() RING_DBG("Stop PortAudio Streams"); for (auto& st_ptr : pimpl_->streams_) { + if (!st_ptr) + continue; + auto err = Pa_StopStream(st_ptr); if (err != paNoError) RING_ERR("Pa_StopStream error : %s", Pa_GetErrorText(err)); @@ -255,59 +266,6 @@ PortAudioLayer::PortAudioLayerImpl::getDeviceByType(bool playback) const return ret; } -int -PortAudioLayer::PortAudioLayerImpl::paOutputCallback(PortAudioLayer& parent, - const AudioSample* inputBuffer, - AudioSample* outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags) -{ - // unused arguments - (void) inputBuffer; - (void) timeInfo; - (void) statusFlags; - - auto toPlay = parent.getPlayback(parent.audioFormat_, framesPerBuffer); - if (!toPlay) { - std::fill_n(outputBuffer, framesPerBuffer * parent.audioFormat_.nb_channels, 0); - return paContinue; - } - - auto nFrames = toPlay->pointer()->nb_samples * toPlay->pointer()->channels; - std::copy_n((AudioSample*)toPlay->pointer()->extended_data[0], nFrames, outputBuffer); - - return paContinue; -} - -int -PortAudioLayer::PortAudioLayerImpl::paInputCallback(PortAudioLayer& parent, - const AudioSample* inputBuffer, - AudioSample* outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags) -{ - // unused arguments - (void) outputBuffer; - (void) timeInfo; - (void) statusFlags; - - if (framesPerBuffer == 0) { - RING_WARN("No frames for input."); - return paContinue; - } - - auto inBuff = std::make_unique<AudioFrame>(parent.audioInputFormat_, framesPerBuffer); - auto nFrames = framesPerBuffer * parent.audioInputFormat_.nb_channels; - if (parent.isCaptureMuted_) - libav_utils::fillWithSilence(inBuff->pointer()); - else - std::copy_n(inputBuffer, nFrames, (AudioSample*)inBuff->pointer()->extended_data[0]); - mainRingBuffer_->put(std::move(inBuff)); - return paContinue; -} - void PortAudioLayer::PortAudioLayerImpl::init(PortAudioLayer& parent) { @@ -382,57 +340,119 @@ openStreamDevice(PaStream** stream, RING_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); } +static void +openFullDuplexStream(PaStream** stream, + PaDeviceIndex inputDeviceIndex, PaDeviceIndex ouputDeviceIndex, + PaStreamCallback* callback, void* user_data) +{ + auto input_device_info = Pa_GetDeviceInfo(inputDeviceIndex); + auto output_device_info = Pa_GetDeviceInfo(ouputDeviceIndex); + + PaStreamParameters inputParams; + inputParams.device = inputDeviceIndex; + inputParams.channelCount = input_device_info->maxInputChannels; + inputParams.sampleFormat = paInt16; + inputParams.suggestedLatency = input_device_info->defaultLowInputLatency; + inputParams.hostApiSpecificStreamInfo = nullptr; + + PaStreamParameters outputParams; + outputParams.device = ouputDeviceIndex; + outputParams.channelCount = output_device_info->maxOutputChannels; + outputParams.sampleFormat = paInt16; + outputParams.suggestedLatency = output_device_info->defaultLowOutputLatency; + outputParams.hostApiSpecificStreamInfo = nullptr; + + auto err = Pa_OpenStream( + stream, + &inputParams, + &outputParams, + std::min(input_device_info->defaultSampleRate, input_device_info->defaultSampleRate), + paFramesPerBufferUnspecified, + paNoFlag, + callback, + user_data); + + if (err != paNoError) + RING_ERR("PortAudioLayer error : %s", Pa_GetErrorText(err)); +} + void PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) { parent.dcblocker_.reset(); - RING_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); + auto apiIndex = Pa_GetDefaultHostApi(); + auto apiInfo = Pa_GetHostApiInfo(apiIndex); + RING_DBG() << "Initializing Portaudio streams using: " << apiInfo->name; + + RING_DBG("Open PortAudio Full-duplex input/output stream"); + if (indexOut_ != paNoDevice && indexIn_ != paNoDevice) { + openFullDuplexStream(&streams_[Direction::IO], + 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); } else { - RING_ERR("Error: No valid output device. There will be no sound."); - } + RING_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 { + RING_ERR("Error: No valid output device. There will be no sound."); + } - RING_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 { - RING_ERR("Error: No valid input device. There will be no mic."); + RING_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 { + RING_ERR("Error: No valid input device. There will be no mic."); + } } RING_DBG("Start PortAudio Streams"); @@ -445,4 +465,70 @@ PortAudioLayer::PortAudioLayerImpl::initStream(PortAudioLayer& parent) } } +int +PortAudioLayer::PortAudioLayerImpl::paOutputCallback(PortAudioLayer& parent, + const AudioSample* inputBuffer, + AudioSample* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags) +{ + // unused arguments + (void)inputBuffer; + (void)timeInfo; + (void)statusFlags; + + auto toPlay = parent.getPlayback(parent.audioFormat_, framesPerBuffer); + if (!toPlay) { + std::fill_n(outputBuffer, framesPerBuffer * parent.audioFormat_.nb_channels, 0); + return paContinue; + } + + auto nFrames = toPlay->pointer()->nb_samples * toPlay->pointer()->channels; + std::copy_n((AudioSample*)toPlay->pointer()->extended_data[0], nFrames, outputBuffer); + + return paContinue; +} + +int +PortAudioLayer::PortAudioLayerImpl::paInputCallback(PortAudioLayer& parent, + const AudioSample* inputBuffer, + AudioSample* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags) +{ + // unused arguments + (void)outputBuffer; + (void)timeInfo; + (void)statusFlags; + + if (framesPerBuffer == 0) { + RING_WARN("No frames for input."); + return paContinue; + } + + auto inBuff = std::make_unique<AudioFrame>(parent.audioInputFormat_, framesPerBuffer); + auto nFrames = framesPerBuffer * parent.audioInputFormat_.nb_channels; + if (parent.isCaptureMuted_) + libav_utils::fillWithSilence(inBuff->pointer()); + else + std::copy_n(inputBuffer, nFrames, (AudioSample*)inBuff->pointer()->extended_data[0]); + mainRingBuffer_->put(std::move(inBuff)); + return paContinue; +} + +int +PortAudioLayer::PortAudioLayerImpl::paIOCallback(PortAudioLayer& parent, + const AudioSample* inputBuffer, + AudioSample* outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags) +{ + paInputCallback(parent, inputBuffer, nullptr, framesPerBuffer, timeInfo, statusFlags); + paOutputCallback(parent, nullptr, outputBuffer, framesPerBuffer, timeInfo, statusFlags); + return paContinue; +} + } // namespace ring