Commit 3dcfb098 authored by Andreas Traczyk's avatar Andreas Traczyk Committed by Kateryna Kostiuk

portaudio: use directsound and a full-duplex stream

- forces dsound as the only host API
- patches portaudio dsound impl to use AEC and NS if possible

Change-Id: I3f648a41ae8991318c839de22ca322f350a2b2b2
parent b59eddda
......@@ -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
--- 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
--- 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
......@@ -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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment