From 6248ea084081159a1cea01344cc4744623686e20 Mon Sep 17 00:00:00 2001 From: dmitrykos Date: Mon, 22 Jul 2024 13:05:54 +0300 Subject: [PATCH] wasapi: Fixed Pa_ReadStream()/Pa_WriteStream() for 1-channel (Mono) stream, made some examples configurable for 1-channel operation for easier testing of Mono I/O --- examples/paex_read_write_wire.c | 39 +++- examples/paex_write_sine.c | 18 +- src/hostapi/wasapi/pa_win_wasapi.c | 310 ++++++++++++++++++----------- test/patest_read_record.c | 33 ++- 4 files changed, 262 insertions(+), 138 deletions(-) diff --git a/examples/paex_read_write_wire.c b/examples/paex_read_write_wire.c index 46fc8bc40..79453d291 100644 --- a/examples/paex_read_write_wire.c +++ b/examples/paex_read_write_wire.c @@ -48,12 +48,14 @@ #include #include "portaudio.h" -/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ -#define SAMPLE_RATE (44100) -#define FRAMES_PER_BUFFER (512) -#define NUM_SECONDS (10) -/* #define DITHER_FLAG (paDitherOff) */ -#define DITHER_FLAG (0) +/*#define SAMPLE_RATE (17932) // Test failure to open with this value. */ +#define SAMPLE_RATE (44100) +#define CHANNELS (2) +#define FRAMES_PER_BUFFER (512) +#define NUM_SECONDS (10) +/*#define DITHER_FLAG (paDitherOff) */ +#define DITHER_FLAG (0) +#define USE_LOOPBACK_INPUT (0) /* Select sample format. */ #if 1 @@ -105,6 +107,30 @@ int main(void) if( err != paNoError ) goto error2; inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ + +#if USE_LOOPBACK_INPUT + for (i = Pa_GetDeviceCount() - 1; i >= 0; i--) + { + const PaDeviceInfo* device; + if ((device = Pa_GetDeviceInfo(i)) != NULL) + { + // Note: [Loopback] name postfix is provided by the WASAPI device only + if (strstr(device->name, "[Loopback]") != NULL) + { + inputParameters.device = i; + inputInfo = device; + break; + } + } + } + + if (inputInfo == NULL) + { + fprintf(stderr, "Error: Loopback device not found.\n"); + return -1; + } +#endif + printf( "Input device # %d.\n", inputParameters.device ); inputInfo = Pa_GetDeviceInfo( inputParameters.device ); printf( " Name: %s\n", inputInfo->name ); @@ -120,6 +146,7 @@ int main(void) numChannels = inputInfo->maxInputChannels < outputInfo->maxOutputChannels ? inputInfo->maxInputChannels : outputInfo->maxOutputChannels; + numChannels = (CHANNELS ? CHANNELS : numChannels); printf( "Num channels = %d.\n", numChannels ); inputParameters.channelCount = numChannels; diff --git a/examples/paex_write_sine.c b/examples/paex_write_sine.c index 3035b42ba..a729fd6b5 100644 --- a/examples/paex_write_sine.c +++ b/examples/paex_write_sine.c @@ -48,6 +48,7 @@ #define NUM_SECONDS (5) #define SAMPLE_RATE (44100) +#define CHANNELS (2) #define FRAMES_PER_BUFFER (1024) #ifndef M_PI @@ -63,7 +64,7 @@ int main(void) PaStreamParameters outputParameters; PaStream *stream; PaError err; - float buffer[FRAMES_PER_BUFFER][2]; /* stereo output buffer */ + float buffer[FRAMES_PER_BUFFER][CHANNELS]; /* stereo output buffer */ float sine[TABLE_SIZE]; /* sine wavetable */ int left_phase = 0; int right_phase = 0; @@ -89,7 +90,7 @@ int main(void) fprintf(stderr,"Error: No default output device.\n"); goto error; } - outputParameters.channelCount = 2; /* stereo output */ + outputParameters.channelCount = CHANNELS; outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ outputParameters.suggestedLatency = 0.050; // Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; @@ -122,11 +123,15 @@ int main(void) for( j=0; j < FRAMES_PER_BUFFER; j++ ) { buffer[j][0] = sine[left_phase]; /* left */ - buffer[j][1] = sine[right_phase]; /* right */ left_phase += left_inc; if( left_phase >= TABLE_SIZE ) left_phase -= TABLE_SIZE; - right_phase += right_inc; - if( right_phase >= TABLE_SIZE ) right_phase -= TABLE_SIZE; + + if (CHANNELS == 2) + { + buffer[j][1] = sine[right_phase]; /* right */ + right_phase += right_inc; + if( right_phase >= TABLE_SIZE ) right_phase -= TABLE_SIZE; + } } err = Pa_WriteStream( stream, buffer, FRAMES_PER_BUFFER ); @@ -137,7 +142,8 @@ int main(void) if( err != paNoError ) goto error; ++left_inc; - ++right_inc; + if (CHANNELS == 2) + ++right_inc; Pa_Sleep( 1000 ); } diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index 47b97c5a5..28958d6ea 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -388,6 +388,13 @@ enum { WASAPI_PACKETS_PER_INPUT_BUFFER = 6 }; #define SAFE_RELEASE(punk) if ((punk) != NULL) { (punk)->lpVtbl->Release((punk)); (punk) = NULL; } #define SAFE_ADDREF(punk) if ((punk) != NULL) { (punk)->lpVtbl->AddRef((punk)); } +// System timer +typedef struct SystemTimer +{ + INT32 granularity; + +} SystemTimer; + // Mixer function typedef void (*MixMonoToStereoF) (void *__to, const void *__from, UINT32 count); @@ -648,6 +655,9 @@ typedef struct PaWasapiStream // Thread priority level PaWasapiThreadPriority nThreadPriority; + // System timer + SystemTimer timer; + // State handler PaWasapiStreamStateCallback fnStateHandler; void *pStateHandlerUserData; @@ -851,16 +861,11 @@ static inline UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched) } // ------------------------------------------------------------------------------------------ -typedef struct _SystemTimer -{ - INT32 granularity; - -} SystemTimer; -static LARGE_INTEGER g_SystemTimerFrequency; +static LARGE_INTEGER g_SystemTimerFrequency = {0}; static BOOL g_SystemTimerUseQpc = FALSE; //! Set granularity of the system timer. -static BOOL SystemTimer_SetGranularity(SystemTimer *timer, UINT32 granularity) +static BOOL SystemTimer_SetGranularity(SystemTimer *timer, INT32 granularity) { #ifndef PA_WINRT TIMECAPS caps; @@ -1414,6 +1419,33 @@ static MixMonoToStereoF GetMonoToStereoMixer(const WAVEFORMATEXTENSIBLE *fmtext, return NULL; } +// ------------------------------------------------------------------------------------------ +static HRESULT ReallocateMonoMixerBuffer(PaWasapiSubStream *subStream, UINT32 frames) +{ + assert(subStream->monoMixer != NULL); + + // If not provided then use max buffer size of WASAPI device + if (frames == 0) + frames = subStream->bufferSize; + + assert(frames != 0); + + // Expand buffer if necessary + UINT32 monoBufferSize = frames * subStream->wavexu.ext.Format.nBlockAlign; + if (monoBufferSize > subStream->monoBufferSize) + { + subStream->monoBufferSize = monoBufferSize; + + subStream->monoBuffer = PaWasapi_ReallocateMemory(subStream->monoBuffer, monoBufferSize); + if (subStream->monoBuffer == NULL) + return E_OUTOFMEMORY; + } + + assert(subStream->monoBuffer != NULL); + + return S_OK; +} + // ------------------------------------------------------------------------------------------ #ifdef PA_WINRT typedef struct PaActivateAudioInterfaceCompletionHandler @@ -3687,6 +3719,17 @@ static PaError ActivateAudioClient(const PaDeviceInfo *baseDeviceInfo, PaWasapiS // Correct buffer to max size if it maxed out result of GetBufferSize subStream->bufferSize = maxBufferSize; + // Preallocate mono mixer + if (subStream->monoMixer != NULL) + { + if ((hr = ReallocateMonoMixerBuffer(subStream, maxBufferSize)) != S_OK) + { + LogHostError(hr); + LogPaError(result = paInsufficientMemory); + goto error; + } + } + if (!output) { // Get interface latency (actually unneeded as we calculate latency from the size of maxBufferSize) @@ -3910,7 +3953,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if (stream->in.params.blocking == TRUE) { UINT32 bufferFrames = ALIGN_NEXT_POW2((stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER) * 2); - UINT32 frameSize = stream->in.wavexu.ext.Format.nBlockAlign; + UINT32 frameSize = stream->in.wavexu.ext.Format.nBlockAlign / (stream->in.monoMixer != NULL ? 2 : 1); // buffer if ((stream->in.tailBuffer = PaUtil_AllocateZeroInitializedMemory(sizeof(PaUtilRingBuffer))) == NULL) @@ -3928,7 +3971,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } // initialize - if (PaUtil_InitializeRingBuffer(stream->in.tailBuffer, frameSize, bufferFrames, stream->in.tailBufferMemory) != 0) + if (PaUtil_InitializeRingBuffer(stream->in.tailBuffer, frameSize, bufferFrames, stream->in.tailBufferMemory) != 0) { LogPaError(result = paInternalError); goto error; @@ -4080,6 +4123,11 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, streamCallback, userData); } + // Request fine (1 ms) granularity of the system timer functions for precise operation + // of waitable timers and WaitForSingleObject (existing granularity will be reverted + // by CloseStream) + SystemTimer_SetGranularity(&stream->timer, 1); + // Initialize CPU measurer PaUtil_InitializeCpuLoadMeasurer(&stream->cpuLoadMeasurer, sampleRate); @@ -4204,8 +4252,11 @@ static PaError CloseStream( PaStream* s ) PaUtil_FreeMemory(stream->out.tailBuffer); PaUtil_FreeMemory(stream->out.tailBufferMemory); + SystemTimer_RestoreGranularity(&stream->timer); + PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); + PaUtil_FreeMemory(stream); return result; @@ -4649,12 +4700,13 @@ static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) PaWasapiStream *stream = (PaWasapiStream*)s; HRESULT hr = S_OK; - BYTE *user_buffer = (BYTE *)_buffer; - BYTE *wasapi_buffer = NULL; + BYTE *userBuffer = (BYTE *)_buffer; + BYTE *deviceBuffer, *renderBuffer; DWORD flags = 0; UINT32 i, available, sleep = 0; unsigned long processed; ThreadIdleScheduler sched; + const BOOL isMonoStereoConverter = (stream->in.monoMixer != NULL); // validate if (!stream->isActive) @@ -4673,18 +4725,18 @@ static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) // because PaUtil_CopyOutput() advances these pointers every time it is called if (!stream->bufferProcessor.userInputIsInterleaved) { - user_buffer = (BYTE *)alloca(sizeof(BYTE *) * stream->bufferProcessor.inputChannelCount); - if (user_buffer == NULL) + userBuffer = (BYTE *)alloca(sizeof(BYTE *) * stream->bufferProcessor.inputChannelCount); + if (userBuffer == NULL) return paInsufficientMemory; for (i = 0; i < stream->bufferProcessor.inputChannelCount; ++i) - ((BYTE **)user_buffer)[i] = ((BYTE **)_buffer)[i]; + ((BYTE **)userBuffer)[i] = ((BYTE **)_buffer)[i]; } // Find out if there are tail frames, flush them all before reading hardware if ((available = PaUtil_GetRingBufferReadAvailable(stream->in.tailBuffer)) != 0) { - ring_buffer_size_t buf1_size = 0, buf2_size = 0, read, desired; + ring_buffer_size_t buf1Frames, buf2Frames, read, desired; void *buf1 = NULL, *buf2 = NULL; // Limit desired to amount of requested frames @@ -4693,31 +4745,31 @@ static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) desired = frames; // Get pointers to read regions - read = PaUtil_GetRingBufferReadRegions(stream->in.tailBuffer, desired, &buf1, &buf1_size, &buf2, &buf2_size); + read = PaUtil_GetRingBufferReadRegions(stream->in.tailBuffer, desired, &buf1, &buf1Frames, &buf2, &buf2Frames); if (buf1 != NULL) { // Register available frames to processor - PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf1_size); + PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf1Frames); // Register host buffer pointer to processor PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, buf1, stream->bufferProcessor.inputChannelCount); // Copy user data to host buffer (with conversion if applicable) - processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, buf1_size); + processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&userBuffer, buf1Frames); frames -= processed; } if (buf2 != NULL) { // Register available frames to processor - PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf2_size); + PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf2Frames); // Register host buffer pointer to processor PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, buf2, stream->bufferProcessor.inputChannelCount); // Copy user data to host buffer (with conversion if applicable) - processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, buf2_size); + processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&userBuffer, buf2Frames); frames -= processed; } @@ -4764,18 +4816,14 @@ static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) } else { - if ((sleep = ThreadIdleScheduler_NextSleep(&sched)) != 0) - { - Sleep(sleep); - sleep = 0; - } + sleep = ThreadIdleScheduler_NextSleep(&sched); } continue; } // Get the available data in the shared buffer. - if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &wasapi_buffer, &available, &flags, NULL, NULL)) != S_OK) + if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &deviceBuffer, &available, &flags, NULL, NULL)) != S_OK) { // Buffer size is too small, waiting if (hr != AUDCLNT_S_BUFFER_EMPTY) @@ -4787,23 +4835,44 @@ static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) continue; } + // Get buffer of the Mono to Stereo mixer, otherwise use WASAPI device buffer directly + if (isMonoStereoConverter) + { + // Expand buffer if necessary (normally shall not happen, buffer is preallocated by + // ActivateAudioClient(), this is to circumvent unexpeced buffer size provided by the driver) + if ((hr = ReallocateMonoMixerBuffer(&stream->in, available)) != S_OK) + { + LogHostError(hr); + goto end; + } + + renderBuffer = stream->in.monoBuffer; + + // Mix 2 channels into 1 + stream->in.monoMixer(renderBuffer, deviceBuffer, available); + } + else + { + renderBuffer = deviceBuffer; + } + // Register available frames to processor PaUtil_SetInputFrameCount(&stream->bufferProcessor, available); // Register host buffer pointer to processor - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.inputChannelCount); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, renderBuffer, stream->bufferProcessor.inputChannelCount); - // Copy user data to host buffer (with conversion if applicable) - processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, frames); + // Copy host buffer to user buffer (with conversion if applicable) + processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&userBuffer, frames); frames -= processed; // Save tail into buffer if ((frames == 0) && (available > processed)) { - UINT32 bytes_processed = processed * stream->in.wavexu.ext.Format.nBlockAlign; - UINT32 frames_to_save = available - processed; + UINT32 processedBytes = processed * (stream->in.wavexu.ext.Format.nBlockAlign / (isMonoStereoConverter ? 2 : 1)); + UINT32 tail = available - processed; - PaUtil_WriteRingBuffer(stream->in.tailBuffer, wasapi_buffer + bytes_processed, frames_to_save); + PaUtil_WriteRingBuffer(stream->in.tailBuffer, renderBuffer + processedBytes, tail); } // Release host buffer @@ -4827,12 +4896,13 @@ static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long fram { PaWasapiStream *stream = (PaWasapiStream*)s; - const BYTE *user_buffer = (const BYTE *)_buffer; - BYTE *wasapi_buffer; + const BYTE *userBuffer = (const BYTE *)_buffer; + BYTE *deviceBuffer, *renderBuffer; HRESULT hr = S_OK; UINT32 i, available, sleep = 0; unsigned long processed; ThreadIdleScheduler sched; + const BOOL isMonoStereoConverter = (stream->out.monoMixer != NULL); // validate if (!stream->isActive) @@ -4851,12 +4921,12 @@ static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long fram // because PaUtil_CopyOutput() advances these pointers every time it is called if (!stream->bufferProcessor.userOutputIsInterleaved) { - user_buffer = (const BYTE *)alloca(sizeof(const BYTE *) * stream->bufferProcessor.outputChannelCount); - if (user_buffer == NULL) + userBuffer = (const BYTE *)alloca(sizeof(const BYTE *) * stream->bufferProcessor.outputChannelCount); + if (userBuffer == NULL) return paInsufficientMemory; for (i = 0; i < stream->bufferProcessor.outputChannelCount; ++i) - ((const BYTE **)user_buffer)[i] = ((const BYTE **)_buffer)[i]; + ((const BYTE **)userBuffer)[i] = ((const BYTE **)_buffer)[i]; } // Blocking (potentially, until 'frames' are consumed) loop @@ -4893,7 +4963,7 @@ static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long fram available = frames; // Get pointer to host buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, available, &wasapi_buffer)) != S_OK) + if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, available, &deviceBuffer)) != S_OK) { // Buffer size is too big, waiting if (hr == AUDCLNT_E_BUFFER_TOO_LARGE) @@ -4905,20 +4975,42 @@ static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long fram // Keep waiting again (on Vista it was noticed that WASAPI could SOMETIMES return NULL pointer // to buffer without returning AUDCLNT_E_BUFFER_TOO_LARGE instead) - if (wasapi_buffer == NULL) + if (deviceBuffer == NULL) continue; + // Get buffer of the Mono to Stereo mixer, otherwise use WASAPI device buffer directly + if (isMonoStereoConverter) + { + // Expand buffer if necessary (normally shall not happen, buffer is preallocated by + // ActivateAudioClient(), this is to circumvent unexpeced buffer size provided by the driver) + if ((hr = ReallocateMonoMixerBuffer(&stream->out, available)) != S_OK) + { + LogHostError(hr); + goto end; + } + + renderBuffer = stream->out.monoBuffer; + } + else + { + renderBuffer = deviceBuffer; + } + // Register available frames to processor PaUtil_SetOutputFrameCount(&stream->bufferProcessor, available); // Register host buffer pointer to processor - PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.outputChannelCount); + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, renderBuffer, stream->bufferProcessor.outputChannelCount); // Copy user data to host buffer (with conversion if applicable), this call will advance - // pointer 'user_buffer' to consumed portion of data - processed = PaUtil_CopyOutput(&stream->bufferProcessor, (const void **)&user_buffer, frames); + // pointer 'userBuffer' to consumed portion of data + processed = PaUtil_CopyOutput(&stream->bufferProcessor, (const void **)&userBuffer, frames); frames -= processed; + // Mix 1 into 2 channels + if (isMonoStereoConverter) + stream->out.monoMixer(deviceBuffer, renderBuffer, processed); + // Release host buffer if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, available, 0)) != S_OK) { @@ -5658,17 +5750,13 @@ static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor #endif } - // Process data + // Process data, apply mixer if activated if (stream->out.monoMixer != NULL) { - // Expand buffer - UINT32 monoFrames = frames * (stream->out.wavexu.ext.Format.wBitsPerSample / 8); - if (monoFrames > stream->out.monoBufferSize) - { - stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = monoFrames)); - if (stream->out.monoBuffer == NULL) - return LogHostError(hr = E_OUTOFMEMORY); - } + // Expand buffer if necessary (normally shall not happen, buffer is preallocated by + // ActivateAudioClient(), this is to circumvent unexpeced buffer size provided by the driver) + if ((hr = ReallocateMonoMixerBuffer(&stream->out, frames)) != S_OK) + return LogHostError(hr); // Process processor[S_OUTPUT].processor(NULL, 0, (BYTE *)stream->out.monoBuffer, frames, processor[S_OUTPUT].userData); @@ -5722,29 +5810,23 @@ static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor // if (flags & AUDCLNT_BUFFERFLAGS_SILENT) // data = NULL; - // Process data + // Apply mixer if activated if (stream->in.monoMixer != NULL) { - // expand buffer - UINT32 monoFrames = frames * (stream->in.wavexu.ext.Format.wBitsPerSample / 8); - if (monoFrames > stream->in.monoBufferSize) - { - stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = monoFrames)); - if (stream->in.monoBuffer == NULL) - return LogHostError(hr = E_OUTOFMEMORY); - } + // Expand buffer if necessary (normally shall not happen, buffer is preallocated by + // ActivateAudioClient(), this is to circumvent unexpeced buffer size provided by the driver) + if ((hr = ReallocateMonoMixerBuffer(&stream->in, frames)) != S_OK) + return LogHostError(hr); - // mix 1 to 2 channels + // Mix 2 channels into 1 stream->in.monoMixer(stream->in.monoBuffer, data, frames); - // process - processor[S_INPUT].processor((BYTE *)stream->in.monoBuffer, frames, NULL, 0, processor[S_INPUT].userData); - } - else - { - processor[S_INPUT].processor(data, frames, NULL, 0, processor[S_INPUT].userData); + // Replace buffer with mixer's one + data = (BYTE *)stream->in.monoBuffer; } + processor[S_INPUT].processor(data, frames, NULL, 0, processor[S_INPUT].userData); + // Release buffer if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, frames)) != S_OK) return LogHostError(hr); @@ -5839,7 +5921,6 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) BOOL setEvent[S_COUNT] = { FALSE, FALSE }; BOOL waitAllEvents = FALSE; BOOL threadComInitialized = FALSE; - SystemTimer timer; // Notify: WASAPI-specific stream state NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS); @@ -5849,7 +5930,9 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) return (UINT32)paUnanticipatedHostError; // Request fine (1 ms) granularity of the system timer functions for precise operation of waitable timers - SystemTimer_SetGranularity(&timer, 1); + // Note: while granularity is already set by OpenStream() we still must be sure that user code did not + // change granularity between Pa_OpenStream() and Pa_StartStream() + SystemTimer_SetGranularity(&stream->timer, 1); // Waiting on all events in case of Full-Duplex/Exclusive mode. if ((stream->in.clientProc != NULL) && (stream->out.clientProc != NULL)) @@ -6001,7 +6084,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) FinishComPointers(stream, threadComInitialized); // Restore system timer granularity - SystemTimer_RestoreGranularity(&timer); + SystemTimer_RestoreGranularity(&stream->timer); // Notify: stream inactive stream->isActive = FALSE; @@ -6135,7 +6218,6 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) PaWasapiHostProcessor defaultProcessor; INT32 i; ThreadIdleScheduler scheduler; - SystemTimer timer; LONGLONG startTime; UINT32 sleepTime; INT32 nextSleepTime = 0; //! Do first loop without waiting as time could be spent when calling other APIs before ProcessXXXBuffer. @@ -6152,7 +6234,9 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) return (UINT32)paUnanticipatedHostError; // Request fine (1 ms) granularity of the system timer functions to guarantee correct logic around WaitForSingleObject - SystemTimer_SetGranularity(&timer, 1); + // Note: while granularity is already set by OpenStream() we still must be sure that user code did not + // change granularity between Pa_OpenStream() and Pa_StartStream() + SystemTimer_SetGranularity(&stream->timer, 1); // Calculate sleep time of the processing loop (inside WaitForSingleObject) sleepTime = ConfigureLoopSleepTimeAndScheduler(stream, &scheduler); @@ -6243,7 +6327,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) NotifyStateChanged(stream, paWasapiStreamStateThreadStart, ERROR_SUCCESS); #ifdef PA_WASAPI_LOG_TIME_SLOTS - startWaitTime = SystemTimer_GetTime(&timer); + startWaitTime = SystemTimer_GetTime(&stream->timer); #endif if (!PA_WASAPI__IS_FULLDUPLEX(stream)) @@ -6251,7 +6335,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // Processing Loop while (!CheckForStopOrWait(stream, nextSleepTime)) { - startTime = SystemTimer_GetTime(&timer); + startTime = SystemTimer_GetTime(&stream->timer); #ifdef PA_WASAPI_LOG_TIME_SLOTS printf("[%d|%d],", nextSleepTime, (INT32)(startTime - startWaitTime)); @@ -6333,10 +6417,10 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) } // Get next sleep time - nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime); + nextSleepTime = GetNextSleepTime(&stream->timer, &scheduler, startTime, sleepTime); #ifdef PA_WASAPI_LOG_TIME_SLOTS - startWaitTime = SystemTimer_GetTime(&timer); + startWaitTime = SystemTimer_GetTime(&stream->timer); #endif } } @@ -6349,7 +6433,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) BYTE *i_data = NULL, *o_data = NULL, *o_data_host = NULL; DWORD i_flags = 0; - startTime = SystemTimer_GetTime(&timer); + startTime = SystemTimer_GetTime(&stream->timer); #ifdef PA_WASAPI_LOG_TIME_SLOTS printf("[%d|%d],", nextSleepTime, (INT32)(startTime - startWaitTime)); @@ -6387,62 +6471,52 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) i_processed = i_frames; o_data_host = o_data; - // convert output mono - if (stream->out.monoMixer) + // Use mono-stereo mixer, if active + if (stream->out.monoMixer != NULL) { - UINT32 mono_frames_size = o_processed * (stream->out.wavexu.ext.Format.wBitsPerSample / 8); - // expand buffer - if (mono_frames_size > stream->out.monoBufferSize) + // Expand buffer if necessary (normally shall not happen, buffer is preallocated by + // ActivateAudioClient(), this is to circumvent unexpeced buffer size provided by the driver) + if ((hr = ReallocateMonoMixerBuffer(&stream->out, o_processed)) != S_OK) { - stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); - if (stream->out.monoBuffer == NULL) - { - // release input buffer - IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); - // release output buffer - IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0); - - LogPaError(paInsufficientMemory); - goto thread_error; - } + // Release buffers + IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); + IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0); + + LogHostError(hr); + goto thread_error; } - // replace buffer pointer + // Replace buffer pointer o_data = (BYTE *)stream->out.monoBuffer; } - // convert input mono - if (stream->in.monoMixer) + // Use mono-stereo mixer, if active + if (stream->in.monoMixer != NULL) { - UINT32 mono_frames_size = i_processed * (stream->in.wavexu.ext.Format.wBitsPerSample / 8); - // expand buffer - if (mono_frames_size > stream->in.monoBufferSize) + // Expand buffer if necessary (normally shall not happen, buffer is preallocated by + // ActivateAudioClient(), this is to circumvent unexpeced buffer size provided by the driver) + if ((hr = ReallocateMonoMixerBuffer(&stream->in, i_processed)) != S_OK) { - stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); - if (stream->in.monoBuffer == NULL) - { - // release input buffer - IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); - // release output buffer - IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0); - - LogPaError(paInsufficientMemory); - goto thread_error; - } + // Release buffers + IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); + IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0); + + LogHostError(hr); + goto thread_error; } - // mix 2 to 1 input channels + // Mix 2 channels into 1 stream->in.monoMixer(stream->in.monoBuffer, i_data, i_processed); - // replace buffer pointer + // Replace buffer pointer i_data = (BYTE *)stream->in.monoBuffer; } - // process + // Process processor[S_FULLDUPLEX].processor(i_data, i_processed, o_data, o_processed, processor[S_FULLDUPLEX].userData); - // mix 1 to 2 output channels - if (stream->out.monoBuffer) + // Mix 1 channel into 2 + if (stream->out.monoMixer != NULL) stream->out.monoMixer(o_data_host, stream->out.monoBuffer, o_processed); // release host output buffer @@ -6478,10 +6552,10 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) } // Get next sleep time - nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime); + nextSleepTime = GetNextSleepTime(&stream->timer, &scheduler, startTime, sleepTime); #ifdef PA_WASAPI_LOG_TIME_SLOTS - startWaitTime = SystemTimer_GetTime(&timer); + startWaitTime = SystemTimer_GetTime(&stream->timer); #endif } } @@ -6495,7 +6569,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) FinishComPointers(stream, threadComInitialized); // Restore system timer granularity - SystemTimer_RestoreGranularity(&timer); + SystemTimer_RestoreGranularity(&stream->timer); // Notify: state inactive stream->isActive = FALSE; diff --git a/test/patest_read_record.c b/test/patest_read_record.c index bd9c7feb0..069b1a92f 100644 --- a/test/patest_read_record.c +++ b/test/patest_read_record.c @@ -47,13 +47,14 @@ #include #include "portaudio.h" -/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ -#define SAMPLE_RATE (44100) -#define FRAMES_PER_BUFFER (1024) -#define NUM_SECONDS (5) -#define NUM_CHANNELS (2) -/* #define DITHER_FLAG (paDitherOff) */ -#define DITHER_FLAG (0) /**/ +/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (1024) +#define NUM_SECONDS (5) +#define NUM_CHANNELS (2) +/* #define DITHER_FLAG (paDitherOff) */ +#define DITHER_FLAG (0) /**/ +#define USE_LOOPBACK_INPUT (0) /* Select sample format. */ #if 1 @@ -93,7 +94,6 @@ int main(void) int numBytes; SAMPLE max, average, val; - printf("patest_read_record.c\n"); fflush(stdout); totalFrames = NUM_SECONDS * SAMPLE_RATE; /* Record for a few seconds. */ @@ -111,7 +111,24 @@ int main(void) err = Pa_Initialize(); if( err != paNoError ) goto error; +#if USE_LOOPBACK_INPUT + inputParameters.device = paNoDevice; + for (i = Pa_GetDeviceCount() - 1; i >= 0; i--) + { + const PaDeviceInfo* device; + if ((device = Pa_GetDeviceInfo(i)) != NULL) + { + // Note: [Loopback] name postfix is provided by the WASAPI device only + if (strstr(device->name, "[Loopback]") != NULL) + { + inputParameters.device = i; + break; + } + } + } +#else inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ +#endif if (inputParameters.device == paNoDevice) { fprintf(stderr,"Error: No default input device.\n"); goto error;