From 2b0faea85cd2a23d114a8df5b2a901191e76e8f8 Mon Sep 17 00:00:00 2001 From: dmitrykos Date: Thu, 3 Feb 2011 15:15:44 +0000 Subject: [PATCH] =?utf8?q?wasapi:=20=20-=20fixed=20blocking=20interface,?= =?utf8?q?=20Input=20and=20Output=20=20-=20improved=20Exclusive=20Input=20?= =?utf8?q?device=20latency=20tunning=20=20-=20applied=20path=20provided=20?= =?utf8?q?by=20Jean-Fran=C3=A7ois=20Charron=20(D-BOX=20Technologies=20Inc.?= =?utf8?q?)=20which=20improves=20handling=20of=20COM=20objects=20in=20mult?= =?utf8?q?i-threaded=20environment=20and=20initialization=20stage=20which?= =?utf8?q?=20could=20under=20certain=20conditions=20return=20paNoError=20e?= =?utf8?q?vent=20when=20initialization=20failed?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- src/hostapi/wasapi/pa_win_wasapi.c | 1346 ++++++++++++++++++---------- 1 file changed, 893 insertions(+), 453 deletions(-) diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index bf70d91..9211320 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -71,8 +71,9 @@ #include "pa_stream.h" #include "pa_cpuload.h" #include "pa_process.h" -#include "pa_debugprint.h" #include "pa_win_wasapi.h" +#include "pa_debugprint.h" +#include "pa_ringbuffer.h" #ifndef NTDDI_VERSION @@ -222,6 +223,11 @@ PA_THREAD_FUNC ProcThreadPoll(void *param); enum { S_INPUT = 0, S_OUTPUT = 1, S_COUNT = 2, S_FULLDUPLEX = 0 }; +// Number of packets which compose single contignous buffer. With trial and error it was calculated +// that WASAPI Input sub-system uses 6 packets per whole buffer. Please provide more information +// or corrections if available. +enum { WASAPI_PACKETS_PER_INPUT_BUFFER = 6 }; + #define STATIC_ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) #define PRINT(x) PA_DEBUG(x); @@ -229,12 +235,16 @@ enum { S_INPUT = 0, S_OUTPUT = 1, S_COUNT = 2, S_FULLDUPLEX = 0 }; #define PA_SKELETON_SET_LAST_HOST_ERROR( errorCode, errorText ) \ PaUtil_SetLastHostErrorInfo( paWASAPI, errorCode, errorText ) -#define PA_WASAPI__IS_FULLDUPLEX(STREAM) ((STREAM)->in.client && (STREAM)->out.client) +#define PA_WASAPI__IS_FULLDUPLEX(STREAM) ((STREAM)->in.clientProc && (STREAM)->out.clientProc) #ifndef IF_FAILED_JUMP #define IF_FAILED_JUMP(hr, label) if(FAILED(hr)) goto label; #endif +#ifndef IF_FAILED_INTERNAL_ERROR_JUMP +#define IF_FAILED_INTERNAL_ERROR_JUMP(hr, error, label) if(FAILED(hr)) { error = paInternalError; goto label; } +#endif + #define SAFE_CLOSE(h) if ((h) != NULL) { CloseHandle((h)); (h) = NULL; } #define SAFE_RELEASE(punk) if ((punk) != NULL) { (punk)->lpVtbl->Release((punk)); (punk) = NULL; } @@ -353,10 +363,10 @@ PaWasapiDeviceInfo; typedef struct { PaUtilHostApiRepresentation inheritedHostApiRep; - PaUtilStreamInterface callbackStreamInterface; - PaUtilStreamInterface blockingStreamInterface; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; - PaUtilAllocationGroup *allocations; + PaUtilAllocationGroup *allocations; /* implementation specific data goes here */ @@ -395,12 +405,15 @@ PaWasapiAudioClientParams; /* PaWasapiStream - a stream data structure specifically for this implementation */ typedef struct PaWasapiSubStream { - IAudioClient *client; + IAudioClient *clientParent; + IStream *clientStream; + IAudioClient *clientProc; + WAVEFORMATEXTENSIBLE wavex; UINT32 bufferSize; - REFERENCE_TIME device_latency; + REFERENCE_TIME deviceLatency; REFERENCE_TIME period; - double latency_seconds; + double latencySeconds; UINT32 framesPerHostCallback; AUDCLNT_SHAREMODE shareMode; UINT32 streamFlags; // AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ... @@ -412,15 +425,14 @@ typedef struct PaWasapiSubStream UINT32 framesPerBuffer; //!< number of frames per 1 buffer BOOL userBufferAndHostMatch; - // Used by blocking interface: - UINT32 prevTime; // time ms between calls of WriteStream - UINT32 prevSleep; // time ms to sleep from frames written in previous call - // Used for Mono >> Stereo workaround, if driver does not support it // (in Exclusive mode WASAPI usually refuses to operate with Mono (1-ch) void *monoBuffer; //!< pointer to buffer UINT32 monoBufferSize; //!< buffer size in bytes MixMonoToStereoF monoMixer; //!< pointer to mixer function + + PaUtilRingBuffer *tailBuffer; //!< buffer with trailing sample for blocking mode operations (only for Input) + void *tailBufferMemory; //!< tail buffer memory region } PaWasapiSubStream; @@ -438,18 +450,22 @@ typedef struct PaWasapiStream { /* IMPLEMENT ME: rename this */ PaUtilStreamRepresentation streamRepresentation; - PaUtilCpuLoadMeasurer cpuLoadMeasurer; - PaUtilBufferProcessor bufferProcessor; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; // input - PaWasapiSubStream in; - IAudioCaptureClient *cclient; - IAudioEndpointVolume *inVol; + PaWasapiSubStream in; + IAudioCaptureClient *captureClientParent; + IStream *captureClientStream; + IAudioCaptureClient *captureClient; + IAudioEndpointVolume *inVol; // output - PaWasapiSubStream out; - IAudioRenderClient *rclient; - IAudioEndpointVolume *outVol; + PaWasapiSubStream out; + IAudioRenderClient *renderClientParent; + IStream *renderClientStream; + IAudioRenderClient *renderClient; + IAudioEndpointVolume *outVol; // event handles for event-driven processing mode HANDLE event[S_COUNT]; @@ -487,9 +503,13 @@ typedef struct PaWasapiStream PaWasapiStream; // Local stream methods -static void _OnStreamStop(PaWasapiStream *stream); -static void _FinishStream(PaWasapiStream *stream); -static void _CleanupStream(PaWasapiStream *stream); +void _StreamOnStop(PaWasapiStream *stream); +void _StreamFinish(PaWasapiStream *stream); +void _StreamCleanup(PaWasapiStream *stream); +HRESULT _PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *available); +HRESULT _PollGetInputFramesAvailable(PaWasapiStream *stream, UINT32 *available); +void *PaWasapi_ReallocateMemory(void *ptr, size_t size); +void PaWasapi_FreeMemory(void *ptr); // Local statics static volatile BOOL g_WasapiCOMInit = FALSE; @@ -557,6 +577,46 @@ static PaError __LogPaError(PaError err, const char *func, const char *file, int return err; } +// ------------------------------------------------------------------------------------------ +/*! \class ThreadSleepScheduler + Allows to emulate thread sleep of less than 1 millisecond under Windows. Scheduler + calculates number of times the thread must run untill next sleep of 1 millisecond. + It does not make thread sleeping for real number of microseconds but rather controls + how many of imaginary microseconds the thread task can allow thread to sleep. +*/ +typedef struct ThreadIdleScheduler +{ + UINT32 m_idle_microseconds; //!< number of microseconds to sleep + UINT32 m_next_sleep; //!< next sleep round + UINT32 m_i; //!< current round iterator position + UINT32 m_resolution; //!< resolution in number of milliseconds +} +ThreadIdleScheduler; +//! Setup scheduler. +static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds) +{ + assert(microseconds != 0); + assert(resolution != 0); + assert((resolution * 1000) >= microseconds); + + memset(sched, 0, sizeof(*sched)); + + sched->m_idle_microseconds = microseconds; + sched->m_resolution = resolution; + sched->m_next_sleep = (resolution * 1000) / microseconds; +} +//! Iterate and check if can sleep. +static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched) +{ + // advance and check if thread can sleep + if (++ sched->m_i == sched->m_next_sleep) + { + sched->m_i = 0; + return sched->m_resolution; + } + return 0; +} + // ------------------------------------------------------------------------------------------ /*static double nano100ToMillis(REFERENCE_TIME ref) { @@ -657,6 +717,16 @@ static UINT32 ALIGN_FWD(UINT32 v, UINT32 align) return v + (align - remainder); } +// ------------------------------------------------------------------------------------------ +// Get next value power of 2 +UINT32 ALIGN_NEXT_POW2(UINT32 v) +{ + UINT32 v2 = 1; + while (v > (v2 <<= 1)) { } + v = v2; + return v; +} + // ------------------------------------------------------------------------------------------ // Aligns WASAPI buffer to 128 byte packet boundary. HD Audio will fail to play if buffer // is misaligned. This problem was solved in Windows 7 were AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED @@ -1032,7 +1102,10 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd paWasapi->enumerator = NULL; hr = CoCreateInstance(&pa_CLSID_IMMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &pa_IID_IMMDeviceEnumerator, (void **)&paWasapi->enumerator); - IF_FAILED_JUMP(hr, error); + + // 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); // getting default device ids in the eMultimedia "role" { @@ -1041,14 +1114,19 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(paWasapi->enumerator, eRender, eMultimedia, &defaultRenderer); if (hr != S_OK) { - if (hr != E_NOTFOUND) - IF_FAILED_JUMP(hr, error); + 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(defaultRenderer, &pszDeviceId); - IF_FAILED_JUMP(hr, error); + // 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->defaultRenderer, pszDeviceId, MAX_STR_LEN-1); CoTaskMemFree(pszDeviceId); IMMDevice_Release(defaultRenderer); @@ -1060,14 +1138,19 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(paWasapi->enumerator, eCapture, eMultimedia, &defaultCapturer); if (hr != S_OK) { - if (hr != E_NOTFOUND) - IF_FAILED_JUMP(hr, error); + 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(defaultCapturer, &pszDeviceId); - IF_FAILED_JUMP(hr, error); + // 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->defaultCapturer, pszDeviceId, MAX_STR_LEN-1); CoTaskMemFree(pszDeviceId); IMMDevice_Release(defaultCapturer); @@ -1076,12 +1159,16 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd } hr = IMMDeviceEnumerator_EnumAudioEndpoints(paWasapi->enumerator, eAll, DEVICE_STATE_ACTIVE, &pEndPoints); - IF_FAILED_JUMP(hr, error); + // 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); hr = IMMDeviceCollection_GetCount(pEndPoints, &paWasapi->deviceCount); - IF_FAILED_JUMP(hr, error); + // 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); - paWasapi->devInfo = (PaWasapiDeviceInfo *)malloc(sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount); + paWasapi->devInfo = (PaWasapiDeviceInfo *)PaUtil_AllocateMemory(sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount); for (i = 0; i < paWasapi->deviceCount; ++i) memset(&paWasapi->devInfo[i], 0, sizeof(PaWasapiDeviceInfo)); @@ -1115,13 +1202,17 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd PA_DEBUG(("WASAPI: ---------------\n")); hr = IMMDeviceCollection_Item(pEndPoints, i, &paWasapi->devInfo[i].device); - IF_FAILED_JUMP(hr, error); + // 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); // getting ID { WCHAR *pszDeviceId = NULL; hr = IMMDevice_GetId(paWasapi->devInfo[i].device, &pszDeviceId); - IF_FAILED_JUMP(hr, error); + // We need to set the result to a value otherwise we will return paNoError + // [IF_FAILED_JUMP(hr, error);] + IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); wcsncpy(paWasapi->devInfo[i].szDeviceID, pszDeviceId, MAX_STR_LEN-1); CoTaskMemFree(pszDeviceId); @@ -1136,7 +1227,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd } hr = IMMDevice_GetState(paWasapi->devInfo[i].device, &paWasapi->devInfo[i].state); - IF_FAILED_JUMP(hr, error); + // 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); if (paWasapi->devInfo[i].state != DEVICE_STATE_ACTIVE) { @@ -1146,7 +1239,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd { IPropertyStore *pProperty; hr = IMMDevice_OpenPropertyStore(paWasapi->devInfo[i].device, STGM_READ, &pProperty); - IF_FAILED_JUMP(hr, error); + // 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); // "Friendly" Name { @@ -1154,7 +1249,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd PROPVARIANT value; PropVariantInit(&value); hr = IPropertyStore_GetValue(pProperty, &PKEY_Device_FriendlyName, &value); - IF_FAILED_JUMP(hr, error); + // 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); deviceInfo->name = NULL; deviceName = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, MAX_STR_LEN + 1); if (deviceName == NULL) @@ -1176,7 +1273,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd PROPVARIANT value; PropVariantInit(&value); hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEngine_DeviceFormat, &value); - IF_FAILED_JUMP(hr, error); + // 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); memcpy(&paWasapi->devInfo[i].DefaultFormat, value.blob.pBlobData, min(sizeof(paWasapi->devInfo[i].DefaultFormat), value.blob.cbSize)); // cleanup PropVariantClear(&value); @@ -1187,7 +1286,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd PROPVARIANT value; PropVariantInit(&value); hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEndpoint_FormFactor, &value); - IF_FAILED_JUMP(hr, error); + // 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); // set #if defined(DUMMYUNIONNAME) && defined(NONAMELESSUNION) // avoid breaking strict-aliasing rules in such line: (EndpointFormFactor)(*((UINT *)(((WORD *)&value.wReserved3)+1))); @@ -1224,12 +1325,16 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd hr = IMMDevice_Activate(paWasapi->devInfo[i].device, &pa_IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, (void **)&tmpClient); - IF_FAILED_JUMP(hr, error); + // 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); hr = IAudioClient_GetDevicePeriod(tmpClient, &paWasapi->devInfo[i].DefaultDevicePeriod, &paWasapi->devInfo[i].MinimumDevicePeriod); - IF_FAILED_JUMP(hr, error); + // 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); //hr = tmpClient->GetMixFormat(&paWasapi->devInfo[i].MixFormat); @@ -1242,6 +1347,8 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd //Digital Output (Realtek AC'97 Audio)'s GUID: {0x38f2cf50,0x7b4c,0x4740,0x86,0xeb,0xd4,0x38,0x66,0xd8,0xc8, 0x9f} //so something must be _really_ wrong with this device, TODO handle this better. We kind of need GetMixFormat LogHostError(hr); + // We need to set the result to a value otherwise we will return paNoError + result = paInternalError; goto error; } } @@ -1268,6 +1375,8 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd break; } default: PRINT(("WASAPI:%d| bad Data Flow!\n", i)); + // We need to set the result to a value otherwise we will return paNoError + result = paInternalError; //continue; // do not skip from list, allow to initialize break; } @@ -1310,6 +1419,11 @@ error: Terminate((PaUtilHostApiRepresentation *)paWasapi); + // Safety if error was not set so that we do not think initialize was a success + if (result == paNoError) { + result = paInternalError; + } + return result; } @@ -1332,7 +1446,7 @@ static void Terminate( PaUtilHostApiRepresentation *hostApi ) //if (info->MixFormat) // CoTaskMemFree(info->MixFormat); } - free(paWasapi->devInfo); + PaUtil_FreeMemory(paWasapi->devInfo); if (paWasapi->allocations) { @@ -2038,7 +2152,6 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu const UINT32 userFramesPerBuffer = framesPerLatency; IAudioClient *audioClient = NULL; - // Validate parameters if (!pSub || !pInfo || !params) return E_POINTER; @@ -2124,6 +2237,13 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu if (framesPerLatency == 0) framesPerLatency = MakeFramesFromHns(pInfo->DefaultDevicePeriod, pSub->wavex.Format.nSamplesPerSec); + //! Exclusive Input stream renders data in 6 packets, we must set then the size of + //! single packet, total buffer size, e.g. required latency will be PacketSize * 6 + if (!output && (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)) + { + framesPerLatency /= WASAPI_PACKETS_PER_INPUT_BUFFER; + } + // Calculate aligned period _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); @@ -2345,8 +2465,8 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu } // Set client - pSub->client = audioClient; - IAudioClient_AddRef(pSub->client); + pSub->clientParent = audioClient; + IAudioClient_AddRef(pSub->clientParent); // Recalculate buffers count _RecalculateBuffersCount(pSub, @@ -2389,7 +2509,7 @@ static PaError ActivateAudioClientOutput(PaWasapiStream *stream) return paInvalidDevice;*/ // Get max possible buffer size to check if it is not less than that we request - hr = IAudioClient_GetBufferSize(stream->out.client, &maxBufferSize); + hr = IAudioClient_GetBufferSize(stream->out.clientParent, &maxBufferSize); if (hr != S_OK) { LogHostError(hr); @@ -2402,14 +2522,14 @@ static PaError ActivateAudioClientOutput(PaWasapiStream *stream) // Get interface latency (actually uneeded as we calculate latency from the size // of maxBufferSize). - hr = IAudioClient_GetStreamLatency(stream->out.client, &stream->out.device_latency); + hr = IAudioClient_GetStreamLatency(stream->out.clientParent, &stream->out.deviceLatency); if (hr != S_OK) { LogHostError(hr); LogPaError(result = paInvalidDevice); goto error; } - //stream->out.latency_seconds = nano100ToSeconds(stream->out.device_latency); + //stream->out.latencySeconds = nano100ToSeconds(stream->out.deviceLatency); // Number of frames that are required at each period stream->out.framesPerHostCallback = maxBufferSize; @@ -2422,9 +2542,9 @@ static PaError ActivateAudioClientOutput(PaWasapiStream *stream) buffer_latency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec; // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes) - stream->out.latency_seconds = buffer_latency; + stream->out.latencySeconds = buffer_latency; - PRINT(("WASAPI::OpenStream(output): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (stream->out.params.wow64_workaround ? "YES" : "NO"), (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL"))); + PRINT(("WASAPI::OpenStream(output): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latencySeconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (stream->out.params.wow64_workaround ? "YES" : "NO"), (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL"))); return paNoError; @@ -2441,7 +2561,7 @@ static PaError ActivateAudioClientInput(PaWasapiStream *stream) UINT32 maxBufferSize = 0; PaTime buffer_latency = 0; - UINT32 framesPerBuffer = stream->out.params.frames_per_buffer; + UINT32 framesPerBuffer = stream->in.params.frames_per_buffer; // Create Audio client hr = CreateAudioClient(stream, &stream->in, FALSE, &result); @@ -2461,7 +2581,7 @@ static PaError ActivateAudioClientInput(PaWasapiStream *stream) return paInvalidDevice;*/ // Get max possible buffer size to check if it is not less than that we request - hr = IAudioClient_GetBufferSize(stream->in.client, &maxBufferSize); + hr = IAudioClient_GetBufferSize(stream->in.clientParent, &maxBufferSize); if (hr != S_OK) { LogHostError(hr); @@ -2474,14 +2594,14 @@ static PaError ActivateAudioClientInput(PaWasapiStream *stream) // Get interface latency (actually uneeded as we calculate latency from the size // of maxBufferSize). - hr = IAudioClient_GetStreamLatency(stream->in.client, &stream->in.device_latency); + hr = IAudioClient_GetStreamLatency(stream->in.clientParent, &stream->in.deviceLatency); if (hr != S_OK) { LogHostError(hr); LogPaError(result = paInvalidDevice); goto error; } - //stream->in.latency_seconds = nano100ToSeconds(stream->in.device_latency); + //stream->in.latencySeconds = nano100ToSeconds(stream->in.deviceLatency); // Number of frames that are required at each period stream->in.framesPerHostCallback = maxBufferSize; @@ -2494,9 +2614,9 @@ static PaError ActivateAudioClientInput(PaWasapiStream *stream) buffer_latency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec; // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes) - stream->in.latency_seconds = buffer_latency; + stream->in.latencySeconds = buffer_latency; - PRINT(("WASAPI::OpenStream(input): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (stream->in.params.wow64_workaround ? "YES" : "NO"), (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL"))); + PRINT(("WASAPI::OpenStream(input): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latencySeconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (stream->in.params.wow64_workaround ? "YES" : "NO"), (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL"))); return paNoError; @@ -2643,6 +2763,46 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->hostProcessOverrideInput.processor = inputStreamInfo->hostProcessorInput; stream->hostProcessOverrideInput.userData = userData; } + + // Only get IAudioCaptureClient input once here instead of getting it at multiple places based on the use + hr = IAudioClient_GetService(stream->in.clientParent, &pa_IID_IAudioCaptureClient, (void **)&stream->captureClientParent); + if (hr != S_OK) + { + LogHostError(hr); + LogPaError(result = paUnanticipatedHostError); + goto error; + } + + // Create ring buffer for blocking mode (It is needed because we fetch Input packets, not frames, + // and thus we have to save partial packet if such remains unread) + if (stream->in.params.blocking == TRUE) + { + UINT32 bufferFrames = ALIGN_NEXT_POW2((stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER) * 2); + UINT32 frameSize = stream->in.wavex.Format.nBlockAlign; + + // buffer + if ((stream->in.tailBuffer = PaUtil_AllocateMemory(sizeof(PaUtilRingBuffer))) == NULL) + { + LogPaError(result = paInsufficientMemory); + goto error; + } + memset(stream->in.tailBuffer, 0, sizeof(PaUtilRingBuffer)); + + // buffer memory region + stream->in.tailBufferMemory = PaUtil_AllocateMemory(frameSize * bufferFrames); + if (stream->in.tailBufferMemory == NULL) + { + LogPaError(result = paInsufficientMemory); + goto error; + } + + // initialize + if (PaUtil_InitializeRingBuffer(stream->in.tailBuffer, frameSize, bufferFrames, stream->in.tailBufferMemory) != 0) + { + LogPaError(result = paInternalError); + goto error; + } + } } else { @@ -2723,6 +2883,15 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->hostProcessOverrideOutput.processor = outputStreamInfo->hostProcessorOutput; stream->hostProcessOverrideOutput.userData = userData; } + + // Only get IAudioCaptureClient output once here instead of getting it at multiple places based on the use + hr = IAudioClient_GetService(stream->out.clientParent, &pa_IID_IAudioRenderClient, (void **)&stream->renderClientParent); + if (hr != S_OK) + { + LogHostError(hr); + LogPaError(result = paUnanticipatedHostError); + goto error; + } } else { @@ -2836,12 +3005,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, // Set Input latency stream->streamRepresentation.streamInfo.inputLatency = ((double)PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) / sampleRate) - + ((inputParameters)?stream->in.latency_seconds : 0); + + ((inputParameters)?stream->in.latencySeconds : 0); // Set Output latency stream->streamRepresentation.streamInfo.outputLatency = ((double)PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) / sampleRate) - + ((outputParameters)?stream->out.latency_seconds : 0); + + ((outputParameters)?stream->out.latencySeconds : 0); // Set SR stream->streamRepresentation.streamInfo.sampleRate = sampleRate; @@ -2866,24 +3035,29 @@ static PaError CloseStream( PaStream* s ) // abort active stream if (IsStreamActive(s)) { - if ((result = AbortStream(s)) != paNoError) - return result; + result = AbortStream(s); } - SAFE_RELEASE(stream->cclient); - SAFE_RELEASE(stream->rclient); - SAFE_RELEASE(stream->out.client); - SAFE_RELEASE(stream->in.client); + SAFE_RELEASE(stream->captureClientParent); + SAFE_RELEASE(stream->renderClientParent); + SAFE_RELEASE(stream->out.clientParent); + SAFE_RELEASE(stream->in.clientParent); SAFE_RELEASE(stream->inVol); SAFE_RELEASE(stream->outVol); CloseHandle(stream->event[S_INPUT]); CloseHandle(stream->event[S_OUTPUT]); - _CleanupStream(stream); + _StreamCleanup(stream); - free(stream->in.monoBuffer); - free(stream->out.monoBuffer); + PaWasapi_FreeMemory(stream->in.monoBuffer); + PaWasapi_FreeMemory(stream->out.monoBuffer); + + PaUtil_FreeMemory(stream->in.tailBuffer); + PaUtil_FreeMemory(stream->in.tailBufferMemory); + + PaUtil_FreeMemory(stream->out.tailBuffer); + PaUtil_FreeMemory(stream->out.tailBufferMemory); PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); @@ -2892,11 +3066,163 @@ static PaError CloseStream( PaStream* s ) return result; } +// ------------------------------------------------------------------------------------------ +HRESULT UnmarshalSubStreamComPointers(PaWasapiSubStream *substream) +{ + HRESULT hResult = S_OK; + HRESULT hFirstBadResult = S_OK; + substream->clientProc = NULL; + + // IAudioClient + hResult = CoGetInterfaceAndReleaseStream(substream->clientStream, &pa_IID_IAudioClient, (LPVOID*)&substream->clientProc); + substream->clientStream = NULL; + if (hResult != S_OK) + { + hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; + } + + return hFirstBadResult; +} + +// ------------------------------------------------------------------------------------------ +HRESULT UnmarshalStreamComPointers(PaWasapiStream *stream) +{ + HRESULT hResult = S_OK; + HRESULT hFirstBadResult = S_OK; + stream->captureClient = NULL; + stream->renderClient = NULL; + stream->in.clientProc = NULL; + stream->out.clientProc = NULL; + + if (NULL != stream->in.clientParent) + { + // SubStream pointers + hResult = UnmarshalSubStreamComPointers(&stream->in); + if (hResult != S_OK) + { + hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; + } + + // IAudioCaptureClient + hResult = CoGetInterfaceAndReleaseStream(stream->captureClientStream, &pa_IID_IAudioCaptureClient, (LPVOID*)&stream->captureClient); + stream->captureClientStream = NULL; + if (hResult != S_OK) + { + hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; + } + } + + if (NULL != stream->out.clientParent) + { + // SubStream pointers + hResult = UnmarshalSubStreamComPointers(&stream->out); + if (hResult != S_OK) + { + hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; + } + + // IAudioRenderClient + hResult = CoGetInterfaceAndReleaseStream(stream->renderClientStream, &pa_IID_IAudioRenderClient, (LPVOID*)&stream->renderClient); + stream->renderClientStream = NULL; + if (hResult != S_OK) + { + hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; + } + } + + return hFirstBadResult; +} + +// ----------------------------------------------------------------------------------------- +void ReleaseUnmarshaledSubComPointers(PaWasapiSubStream *substream) +{ + SAFE_RELEASE(substream->clientProc); +} + +// ----------------------------------------------------------------------------------------- +void ReleaseUnmarshaledComPointers(PaWasapiStream *stream) +{ + // Release AudioClient services first + SAFE_RELEASE(stream->captureClient); + SAFE_RELEASE(stream->renderClient); + + // Release AudioClients + ReleaseUnmarshaledSubComPointers(&stream->in); + ReleaseUnmarshaledSubComPointers(&stream->out); +} + +// ------------------------------------------------------------------------------------------ +HRESULT MarshalSubStreamComPointers(PaWasapiSubStream *substream) +{ + HRESULT hResult; + substream->clientStream = NULL; + + // IAudioClient + hResult = CoMarshalInterThreadInterfaceInStream(&pa_IID_IAudioClient, (LPUNKNOWN)substream->clientParent, &substream->clientStream); + if (hResult != S_OK) + goto marshal_sub_error; + + return hResult; + + // If marshaling error occurred, make sure to release everything. +marshal_sub_error: + + UnmarshalSubStreamComPointers(substream); + ReleaseUnmarshaledSubComPointers(substream); + return hResult; +} + +// ------------------------------------------------------------------------------------------ +HRESULT MarshalStreamComPointers(PaWasapiStream *stream) +{ + HRESULT hResult = S_OK; + stream->captureClientStream = NULL; + stream->in.clientStream = NULL; + stream->renderClientStream = NULL; + stream->out.clientStream = NULL; + + if (NULL != stream->in.clientParent) + { + // SubStream pointers + hResult = MarshalSubStreamComPointers(&stream->in); + if (hResult != S_OK) + goto marshal_error; + + // IAudioCaptureClient + hResult = CoMarshalInterThreadInterfaceInStream(&pa_IID_IAudioCaptureClient, (LPUNKNOWN)stream->captureClientParent, &stream->captureClientStream); + if (hResult != S_OK) + goto marshal_error; + } + + if (NULL != stream->out.clientParent) + { + // SubStream pointers + hResult = MarshalSubStreamComPointers(&stream->out); + if (hResult != S_OK) + goto marshal_error; + + // IAudioRenderClient + hResult = CoMarshalInterThreadInterfaceInStream(&pa_IID_IAudioRenderClient, (LPUNKNOWN)stream->renderClientParent, &stream->renderClientStream); + if (hResult != S_OK) + goto marshal_error; + } + + return hResult; + + // If marshaling error occurred, make sure to release everything. +marshal_error: + + UnmarshalStreamComPointers(stream); + ReleaseUnmarshaledComPointers(stream); + return hResult; +} + // ------------------------------------------------------------------------------------------ static PaError StartStream( PaStream *s ) { HRESULT hr; PaWasapiStream *stream = (PaWasapiStream*)s; + PaError result = paNoError; // check if stream is active already if (IsStreamActive(s)) @@ -2905,10 +3231,14 @@ static PaError StartStream( PaStream *s ) PaUtil_ResetBufferProcessor(&stream->bufferProcessor); // Cleanup handles (may be necessary if stream was stopped by itself due to error) - _CleanupStream(stream); + _StreamCleanup(stream); // Create close event - stream->hCloseRequest = CreateEvent(NULL, TRUE, FALSE, NULL); + if ((stream->hCloseRequest = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) + { + result = paInsufficientMemory; + goto start_error; + } // Create thread if (!stream->bBlocking) @@ -2916,78 +3246,118 @@ static PaError StartStream( PaStream *s ) // Create thread events stream->hThreadStart = CreateEvent(NULL, TRUE, FALSE, NULL); stream->hThreadExit = CreateEvent(NULL, TRUE, FALSE, NULL); + if ((stream->hThreadStart == NULL) || (stream->hThreadExit == NULL)) + { + result = paInsufficientMemory; + goto start_error; + } + + // Marshal WASAPI interface pointers for safe use in thread created below. + if ((hr = MarshalStreamComPointers(stream)) != S_OK) + { + PRINT(("Failed marshaling stream COM pointers.")); + result = paUnanticipatedHostError; + goto nonblocking_start_error; + } - if ((stream->in.client && (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) || - (stream->out.client && (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK))) + if ((stream->in.clientParent && (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) || + (stream->out.clientParent && (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK))) { - if ((stream->hThread = CREATE_THREAD(ProcThreadEvent)) == NULL) - return paUnanticipatedHostError; + if ((stream->hThread = CREATE_THREAD(ProcThreadEvent)) == NULL) + { + PRINT(("Failed creating thread: ProcThreadEvent.")); + result = paUnanticipatedHostError; + goto nonblocking_start_error; + } } else { - if ((stream->hThread = CREATE_THREAD(ProcThreadPoll)) == NULL) - return paUnanticipatedHostError; + if ((stream->hThread = CREATE_THREAD(ProcThreadPoll)) == NULL) + { + PRINT(("Failed creating thread: ProcThreadPoll.")); + result = paUnanticipatedHostError; + goto nonblocking_start_error; + } } // Wait for thread to start - if (WaitForSingleObject(stream->hThreadStart, 60*1000) == WAIT_TIMEOUT) - return paUnanticipatedHostError; + if (WaitForSingleObject(stream->hThreadStart, 60*1000) == WAIT_TIMEOUT) + { + PRINT(("Failed starting thread: timeout.")); + result = paUnanticipatedHostError; + goto nonblocking_start_error; + } } else { // Create blocking operation events (non-signaled event means - blocking operation is pending) - if (stream->out.client) - stream->hBlockingOpStreamWR = CreateEvent(NULL, TRUE, TRUE, NULL); - if (stream->in.client) - stream->hBlockingOpStreamRD = CreateEvent(NULL, TRUE, TRUE, NULL); - - // Initialize event & start INPUT stream - if (stream->in.client) + if (stream->out.clientParent != NULL) { - if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK) + if ((stream->hBlockingOpStreamWR = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL) { - LogHostError(hr); - return paUnanticipatedHostError; + result = paInsufficientMemory; + goto start_error; } - - if ((hr = IAudioClient_Start(stream->in.client)) != S_OK) + } + if (stream->in.clientParent != NULL) + { + if ((stream->hBlockingOpStreamRD = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL) { - LogHostError(hr); - return paUnanticipatedHostError; + result = paInsufficientMemory; + goto start_error; } } - - // Initialize event & start OUTPUT stream - if (stream->out.client) + // Initialize event & start INPUT stream + if (stream->in.clientParent != NULL) { - if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK) + if ((hr = IAudioClient_Start(stream->in.clientParent)) != S_OK) { LogHostError(hr); - return paUnanticipatedHostError; + result = paUnanticipatedHostError; + goto start_error; } + } + // Initialize event & start OUTPUT stream + if (stream->out.clientParent != NULL) + { // Start - if ((hr = IAudioClient_Start(stream->out.client)) != S_OK) + if ((hr = IAudioClient_Start(stream->out.clientParent)) != S_OK) { LogHostError(hr); - return paUnanticipatedHostError; + result = paUnanticipatedHostError; + goto start_error; } } - // Signal: stream running - stream->running = TRUE; + // Set parent to working pointers to use shared functions. + stream->captureClient = stream->captureClientParent; + stream->renderClient = stream->renderClientParent; + stream->in.clientProc = stream->in.clientParent; + stream->out.clientProc = stream->out.clientParent; - // Set current time - stream->out.prevTime = timeGetTime(); - stream->out.prevSleep = 0; + // Signal: stream running. + stream->running = TRUE; } - return paNoError; + return result; + +nonblocking_start_error: + + // Set hThreadExit event to prevent blocking during cleanup + SetEvent(stream->hThreadExit); + UnmarshalStreamComPointers(stream); + ReleaseUnmarshaledComPointers(stream); + +start_error: + + StopStream(s); + return result; } // ------------------------------------------------------------------------------------------ -static void _FinishStream(PaWasapiStream *stream) +void _StreamFinish(PaWasapiStream *stream) { // Issue command to thread to stop processing and wait for thread exit if (!stream->bBlocking) @@ -2998,23 +3368,23 @@ static void _FinishStream(PaWasapiStream *stream) // Blocking mode does not own thread { // Signal close event and wait for each of 2 blocking operations to complete - if (stream->out.client) + if (stream->out.clientParent) SignalObjectAndWait(stream->hCloseRequest, stream->hBlockingOpStreamWR, INFINITE, TRUE); - if (stream->out.client) + if (stream->out.clientParent) SignalObjectAndWait(stream->hCloseRequest, stream->hBlockingOpStreamRD, INFINITE, TRUE); // Process stop - _OnStreamStop(stream); + _StreamOnStop(stream); } // Cleanup handles - _CleanupStream(stream); + _StreamCleanup(stream); stream->running = FALSE; } // ------------------------------------------------------------------------------------------ -static void _CleanupStream(PaWasapiStream *stream) +void _StreamCleanup(PaWasapiStream *stream) { // Close thread handles to allow restart SAFE_CLOSE(stream->hThread); @@ -3029,7 +3399,7 @@ static void _CleanupStream(PaWasapiStream *stream) static PaError StopStream( PaStream *s ) { // Finish stream - _FinishStream((PaWasapiStream *)s); + _StreamFinish((PaWasapiStream *)s); return paNoError; } @@ -3037,7 +3407,7 @@ static PaError StopStream( PaStream *s ) static PaError AbortStream( PaStream *s ) { // Finish stream - _FinishStream((PaWasapiStream *)s); + _StreamFinish((PaWasapiStream *)s); return paNoError; } @@ -3061,11 +3431,6 @@ static PaTime GetStreamTime( PaStream *s ) /* suppress unused variable warnings */ (void) stream; - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - - //this is lame ds and mme does the same thing, quite useless method imho - //why dont we fetch the time in the pa callbacks? - //at least its doing to be clocked to something return PaUtil_GetTime(); } @@ -3076,28 +3441,32 @@ static double GetStreamCpuLoad( PaStream* s ) } // ------------------------------------------------------------------------------------------ -/* NOT TESTED */ -static PaError ReadStream( PaStream* s, void *_buffer, unsigned long _frames ) +static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) { PaWasapiStream *stream = (PaWasapiStream*)s; HRESULT hr = S_OK; - UINT32 frames; BYTE *user_buffer = (BYTE *)_buffer; BYTE *wasapi_buffer = NULL; DWORD flags = 0; - UINT32 i; + UINT32 i, available, sleep = 0; + unsigned long processed; + ThreadIdleScheduler sched; // validate if (!stream->running) return paStreamIsStopped; - if (stream->cclient == NULL) + if (stream->captureClient == NULL) return paBadStreamPtr; // Notify blocking op has begun ResetEvent(stream->hBlockingOpStreamRD); - // make a local copy of the user buffer pointer(s), this is necessary + // Use thread scheduling for 500 microseconds (emulated) when wait time for frames is less than + // 1 milliseconds, emulation helps to normalize CPU consumption and avoids too busy waiting + ThreadIdleScheduler_Setup(&sched, 1, 250/* microseconds */); + + // Make a local copy of the user buffer pointer(s), this is necessary // because PaUtil_CopyOutput() advances these pointers every time it is called if (!stream->bufferProcessor.userInputIsInterleaved) { @@ -3109,64 +3478,140 @@ static PaError ReadStream( PaStream* s, void *_buffer, unsigned long _frames ) ((BYTE **)user_buffer)[i] = ((BYTE **)_buffer)[i]; } - while (_frames != 0) + // Findout if there are tail frames, flush them all before reading hardware + if ((available = PaUtil_GetRingBufferReadAvailable(stream->in.tailBuffer)) != 0) { - UINT32 processed, processed_size; + UINT32 buf1_size = 0, buf2_size = 0, read, desired; + void *buf1 = NULL, *buf2 = NULL; + + // Limit desired to amount of requested frames + desired = available; + if (desired > frames) + desired = frames; + + // Get pointers to read regions + read = PaUtil_GetRingBufferReadRegions(stream->in.tailBuffer, desired, &buf1, &buf1_size, &buf2, &buf2_size); + + if (buf1 != NULL) + { + // Register available frames to processor + PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf1_size); - // Get the available data in the shared buffer. - if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &wasapi_buffer, &frames, &flags, NULL, NULL)) != S_OK) + // 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); + frames -= processed; + } + + if (buf2 != NULL) { - if (hr == AUDCLNT_S_BUFFER_EMPTY) + // Register available frames to processor + PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf2_size); + + // 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); + frames -= processed; + } + + // Advance + PaUtil_AdvanceRingBufferReadIndex(stream->in.tailBuffer, read); + } + + // Read hardware + while (frames != 0) + { + // Check if blocking call must be interrupted + if (WaitForSingleObject(stream->hCloseRequest, sleep) != WAIT_TIMEOUT) + break; + + // Get available frames (must be finding out available frames before call to IAudioCaptureClient_GetBuffer + // othervise audio glitches will occur inExclusive mode as it seems that WASAPI has some scheduling/ + // processing problems when such busy polling with IAudioCaptureClient_GetBuffer occurs) + if ((hr = _PollGetInputFramesAvailable(stream, &available)) != S_OK) + { + LogHostError(hr); + return paUnanticipatedHostError; + } + + // Wait for more frames to become available + if (available == 0) + { + // Exclusive mode may require latency of 1 millisecond, thus we shall sleep + // around 500 microseconds (emulated) to collect packets in time + if (stream->in.shareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) { - // Check if blocking call must be interrupted - if (WaitForSingleObject(stream->hCloseRequest, 1) != WAIT_TIMEOUT) - break; + UINT32 sleep_frames = (frames < stream->in.framesPerHostCallback ? frames : stream->in.framesPerHostCallback); + + sleep = GetFramesSleepTime(sleep_frames, stream->in.wavex.Format.nSamplesPerSec); + sleep /= 4; // wait only for 1/4 of the buffer + + // WASAPI input provides packets, thus expiring packet will result in bad audio + // limit waiting time to 2 seconds (will always work for smallest buffer in Shared) + if (sleep > 2) + sleep = 2; + + // Avoid busy waiting, schedule next 1 millesecond wait + if (sleep == 0) + sleep = ThreadIdleScheduler_NextSleep(&sched); + } + else + { + if ((sleep = ThreadIdleScheduler_NextSleep(&sched)) != 0) + { + Sleep(sleep); + sleep = 0; + } } - return LogHostError(hr); - goto stream_rd_end; + continue; } - // Detect silence - // if (flags & AUDCLNT_BUFFERFLAGS_SILENT) - // data = NULL; + // Get the available data in the shared buffer. + if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &wasapi_buffer, &available, &flags, NULL, NULL)) != S_OK) + { + // Buffer size is too small, waiting + if (hr != AUDCLNT_S_BUFFER_EMPTY) + { + LogHostError(hr); + goto end; + } - // Check if frames <= _frames - if (frames > _frames) - frames = _frames; + continue; + } // Register available frames to processor - PaUtil_SetInputFrameCount(&stream->bufferProcessor, frames); + 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, wasapi_buffer, stream->bufferProcessor.inputChannelCount); // Copy user data to host buffer (with conversion if applicable) processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, frames); + frames -= processed; - // Advance user buffer to consumed portion - processed_size = processed * stream->in.wavex.Format.nBlockAlign; - if (stream->bufferProcessor.userInputIsInterleaved) - { - user_buffer += processed_size; - } - else + // Save tail into buffer + if ((frames == 0) && (available > processed)) { - for (i = 0; i < stream->bufferProcessor.inputChannelCount; ++i) - ((BYTE **)user_buffer)[i] = ((BYTE **)user_buffer)[i] + processed_size; + UINT32 bytes_processed = processed * stream->in.wavex.Format.nBlockAlign; + UINT32 frames_to_save = available - processed; + + PaUtil_WriteRingBuffer(stream->in.tailBuffer, wasapi_buffer + bytes_processed, frames_to_save); } // Release host buffer - if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, processed)) != S_OK) + if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, available)) != S_OK) { LogHostError(hr); - goto stream_rd_end; + goto end; } - - _frames -= processed; } -stream_rd_end: +end: // Notify blocking op has ended SetEvent(stream->hBlockingOpStreamRD); @@ -3175,58 +3620,32 @@ stream_rd_end: } // ------------------------------------------------------------------------------------------ -static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long _frames ) +static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long frames ) { PaWasapiStream *stream = (PaWasapiStream*)s; - UINT32 frames; + //UINT32 frames; const BYTE *user_buffer = (const BYTE *)_buffer; BYTE *wasapi_buffer; HRESULT hr = S_OK; - UINT32 next_rev_sleep, blocks, block_sleep_ms; - UINT32 i; + UINT32 i, available, sleep = 0; + unsigned long processed; + ThreadIdleScheduler sched; // validate if (!stream->running) return paStreamIsStopped; - if (stream->rclient == NULL) + if (stream->renderClient == NULL) return paBadStreamPtr; // Notify blocking op has begun ResetEvent(stream->hBlockingOpStreamWR); - // Calculate sleep time for next call - { - UINT32 remainder = 0; - UINT32 sleep_ms = 0; - DWORD elapsed_ms; - blocks = _frames / stream->out.framesPerHostCallback; - block_sleep_ms = GetFramesSleepTime(stream->out.framesPerHostCallback, stream->out.wavex.Format.nSamplesPerSec); - if (blocks == 0) - { - blocks = 1; - sleep_ms = GetFramesSleepTime(_frames, stream->out.wavex.Format.nSamplesPerSec); // partial - } - else - { - remainder = _frames - blocks * stream->out.framesPerHostCallback; - sleep_ms = block_sleep_ms; // full - } - - // Sleep for remainder - elapsed_ms = timeGetTime() - stream->out.prevTime; - if (sleep_ms >= elapsed_ms) - sleep_ms -= elapsed_ms; + // Use thread scheduling for 500 microseconds (emulated) when wait time for frames is less than + // 1 milliseconds, emulation helps to normalize CPU consumption and avoids too busy waiting + ThreadIdleScheduler_Setup(&sched, 1, 500/* microseconds */); - next_rev_sleep = sleep_ms; - } - - // Sleep diff from last call - if (stream->out.prevSleep) - Sleep(stream->out.prevSleep); - stream->out.prevSleep = next_rev_sleep; - - // make a local copy of the user buffer pointer(s), this is necessary + // Make a local copy of the user buffer pointer(s), this is necessary // because PaUtil_CopyOutput() advances these pointers every time it is called if (!stream->bufferProcessor.userOutputIsInterleaved) { @@ -3238,91 +3657,75 @@ static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long _fra ((const BYTE **)user_buffer)[i] = ((const BYTE **)_buffer)[i]; } - // Feed engine - for (i = 0; i < blocks; ++i) + // Blocking (potentially, untill 'frames' are consumed) loop + while (frames != 0) { - UINT32 available, processed; - - // Get block frames - frames = stream->out.framesPerHostCallback; - if (frames > _frames) - frames = _frames; + // Check if blocking call must be interrupted + if (WaitForSingleObject(stream->hCloseRequest, sleep) != WAIT_TIMEOUT) + break; - if (i) - Sleep(block_sleep_ms); + // Get frames available + if ((hr = _PollGetOutputFramesAvailable(stream, &available)) != S_OK) + { + LogHostError(hr); + goto end; + } - while (frames != 0) + // Wait for more frames to become available + if (available == 0) { - UINT32 padding = 0; - UINT32 processed_size; + UINT32 sleep_frames = (frames < stream->out.framesPerHostCallback ? frames : stream->out.framesPerHostCallback); - // Check if blocking call must be interrupted - if (WaitForSingleObject(stream->hCloseRequest, 0) != WAIT_TIMEOUT) - break; + sleep = GetFramesSleepTime(sleep_frames, stream->out.wavex.Format.nSamplesPerSec); + sleep /= 2; // wait only for half of the buffer - // Get Read position - hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding); - if (hr != S_OK) - { - LogHostError(hr); - goto stream_wr_end; - } + // Avoid busy waiting, schedule next 1 millesecond wait + if (sleep == 0) + sleep = ThreadIdleScheduler_NextSleep(&sched); - // Calculate frames available - if (frames >= padding) - available = frames - padding; - else - available = frames; + continue; + } - // Get pointer to host buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, available, &wasapi_buffer)) != S_OK) - { - // Buffer size is too big, waiting - if (hr == AUDCLNT_E_BUFFER_TOO_LARGE) - continue; - LogHostError(hr); - goto stream_wr_end; - } + // Keep in 'frmaes' range + if (available > frames) + available = frames; - // Register available frames to processor - PaUtil_SetOutputFrameCount(&stream->bufferProcessor, available); + // Get pointer to host buffer + if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, available, &wasapi_buffer)) != S_OK) + { + // Buffer size is too big, waiting + if (hr == AUDCLNT_E_BUFFER_TOO_LARGE) + continue; - // Register host buffer pointer to processor - PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.outputChannelCount); + LogHostError(hr); + goto end; + } - // Copy user data to host buffer (with conversion if applicable) - processed = PaUtil_CopyOutput(&stream->bufferProcessor, (const void **)&user_buffer, available); + // 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) + continue; - // Advance user buffer to consumed portion - processed_size = processed * stream->out.wavex.Format.nBlockAlign; - if (stream->bufferProcessor.userOutputIsInterleaved) - { - user_buffer += processed_size; - } - else - { - for (i = 0; i < stream->bufferProcessor.outputChannelCount; ++i) - ((const BYTE **)user_buffer)[i] = ((const BYTE **)user_buffer)[i] + processed_size; - } + // Register available frames to processor + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, available); - // Release host buffer - if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, processed, 0)) != S_OK) - { - LogHostError(hr); - goto stream_wr_end; - } + // Register host buffer pointer to processor + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.outputChannelCount); - // Deduct frames - frames -= processed; - } + // 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); + frames -= processed; - _frames -= frames; + // Release host buffer + if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, available, 0)) != S_OK) + { + LogHostError(hr); + goto end; + } } -stream_wr_end: - - // Set prev time - stream->out.prevTime = timeGetTime(); +end: // Notify blocking op has ended SetEvent(stream->hBlockingOpStreamWR); @@ -3330,58 +3733,61 @@ stream_wr_end: return (hr != S_OK ? paUnanticipatedHostError : paNoError); } +unsigned long PaUtil_GetOutputFrameCount( PaUtilBufferProcessor* bp ) +{ + return bp->hostOutputFrameCount[0]; +} + // ------------------------------------------------------------------------------------------ -/* NOT TESTED */ static signed long GetStreamReadAvailable( PaStream* s ) { PaWasapiStream *stream = (PaWasapiStream*)s; + HRESULT hr; - UINT32 pending = 0; + UINT32 available = 0; // validate if (!stream->running) return paStreamIsStopped; - if (stream->cclient == NULL) + if (stream->captureClient == NULL) return paBadStreamPtr; - hr = IAudioClient_GetCurrentPadding(stream->in.client, &pending); - if (hr != S_OK) + // available in hardware buffer + if ((hr = _PollGetInputFramesAvailable(stream, &available)) != S_OK) { LogHostError(hr); return paUnanticipatedHostError; } - return (long)pending; + // available in software tail buffer + available += PaUtil_GetRingBufferReadAvailable(stream->in.tailBuffer); + + return available; } // ------------------------------------------------------------------------------------------ static signed long GetStreamWriteAvailable( PaStream* s ) { PaWasapiStream *stream = (PaWasapiStream*)s; - - UINT32 frames = stream->out.framesPerHostCallback; HRESULT hr; - UINT32 padding = 0; + UINT32 available = 0; // validate if (!stream->running) return paStreamIsStopped; - if (stream->rclient == NULL) + if (stream->renderClient == NULL) return paBadStreamPtr; - hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding); - if (hr != S_OK) + if ((hr = _PollGetOutputFramesAvailable(stream, &available)) != S_OK) { LogHostError(hr); return paUnanticipatedHostError; } - // Calculate - frames -= padding; - - return frames; + return (signed long)available; } + // ------------------------------------------------------------------------------------------ static void WaspiHostProcessingLoop( void *inputBuffer, long inputFrames, void *outputBuffer, long outputFrames, @@ -3404,24 +3810,24 @@ static void WaspiHostProcessingLoop( void *inputBuffer, long inputFrames, */ timeInfo.currentTime = PaUtil_GetTime(); // Query input latency - if (stream->in.client != NULL) + if (stream->in.clientProc != NULL) { PaTime pending_time; - if ((hr = IAudioClient_GetCurrentPadding(stream->in.client, &pending)) == S_OK) + if ((hr = IAudioClient_GetCurrentPadding(stream->in.clientProc, &pending)) == S_OK) pending_time = (PaTime)pending / (PaTime)stream->in.wavex.Format.nSamplesPerSec; else - pending_time = (PaTime)stream->in.latency_seconds; + pending_time = (PaTime)stream->in.latencySeconds; timeInfo.inputBufferAdcTime = timeInfo.currentTime + pending_time; } // Query output current latency - if (stream->out.client != NULL) + if (stream->out.clientProc != NULL) { PaTime pending_time; - if ((hr = IAudioClient_GetCurrentPadding(stream->out.client, &pending)) == S_OK) + if ((hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &pending)) == S_OK) pending_time = (PaTime)pending / (PaTime)stream->out.wavex.Format.nSamplesPerSec; else - pending_time = (PaTime)stream->out.latency_seconds; + pending_time = (PaTime)stream->out.latencySeconds; timeInfo.outputBufferDacTime = timeInfo.currentTime + pending_time; } @@ -3811,13 +4217,48 @@ error: } // ------------------------------------------------------------------------------------------ -static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor, UINT32 frames) +HRESULT _PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *available) +{ + HRESULT hr; + UINT32 frames = stream->out.framesPerHostCallback, + padding = 0; + + (*available) = 0; + + // get read position + if ((hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &padding)) != S_OK) + return LogHostError(hr); + + // get available + frames -= padding; + + // set + (*available) = frames; + return hr; +} + +// ------------------------------------------------------------------------------------------ +HRESULT _PollGetInputFramesAvailable(PaWasapiStream *stream, UINT32 *available) +{ + HRESULT hr; + + (*available) = 0; + + // GetCurrentPadding() has opposite meaning to Output stream + if ((hr = IAudioClient_GetCurrentPadding(stream->in.clientProc, available)) != S_OK) + return LogHostError(hr); + + return hr; +} + +// ------------------------------------------------------------------------------------------ +HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor, UINT32 frames) { HRESULT hr; BYTE *data = NULL; // Get buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, frames, &data)) != S_OK) + if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, frames, &data)) != S_OK) { if (stream->out.shareMode == AUDCLNT_SHAREMODE_SHARED) { @@ -3828,7 +4269,7 @@ static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor #if 0 // Get Read position UINT32 padding = 0; - hr = stream->out.client->GetCurrentPadding(&padding); + hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &padding); if (hr != S_OK) return LogHostError(hr); @@ -3837,7 +4278,7 @@ static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor if (frames == 0) return S_OK; - if ((hr = stream->rclient->GetBuffer(frames, &data)) != S_OK) + if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, frames, &data)) != S_OK) return LogHostError(hr); #else if (hr == AUDCLNT_E_BUFFER_TOO_LARGE) @@ -3851,10 +4292,10 @@ static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor // Process data if (stream->out.monoMixer != NULL) { - // expand buffer (one way only for better performancedue to no calls to realloc) + // expand buffer UINT32 mono_frames_size = frames * (stream->out.wavex.Format.wBitsPerSample / 8); if (mono_frames_size > stream->out.monoBufferSize) - stream->out.monoBuffer = realloc(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); + stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); // process processor[S_OUTPUT].processor(NULL, 0, (BYTE *)stream->out.monoBuffer, frames, processor[S_OUTPUT].userData); @@ -3868,14 +4309,14 @@ static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor } // Release buffer - if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, frames, 0)) != S_OK) + if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, frames, 0)) != S_OK) LogHostError(hr); return hr; } // ------------------------------------------------------------------------------------------ -static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor) +HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor) { HRESULT hr = S_OK; UINT32 frames; @@ -3888,13 +4329,22 @@ static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor if (WaitForSingleObject(stream->hCloseRequest, 0) != WAIT_TIMEOUT) break; + // Findout if any frames available + frames = 0; + if ((hr = _PollGetInputFramesAvailable(stream, &frames)) != S_OK) + return hr; + + // Empty/consumed buffer + if (frames == 0) + break; + // Get the available data in the shared buffer. - if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &data, &frames, &flags, NULL, NULL)) != S_OK) + if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &data, &frames, &flags, NULL, NULL)) != S_OK) { if (hr == AUDCLNT_S_BUFFER_EMPTY) { hr = S_OK; - break; // capture buffer exhausted + break; // Empty/consumed buffer } return LogHostError(hr); @@ -3908,10 +4358,10 @@ static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor // Process data if (stream->in.monoMixer != NULL) { - // expand buffer (one way only for better performancedue to no calls to realloc) + // expand buffer UINT32 mono_frames_size = frames * (stream->in.wavex.Format.wBitsPerSample / 8); if (mono_frames_size > stream->in.monoBufferSize) - stream->in.monoBuffer = realloc(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); + stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); // mix 1 to 2 channels stream->in.monoMixer(stream->in.monoBuffer, data, frames); @@ -3925,25 +4375,33 @@ static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor } // Release buffer - if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, frames)) != S_OK) + if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, frames)) != S_OK) return LogHostError(hr); - break; + //break; } return hr; } // ------------------------------------------------------------------------------------------ -void _OnStreamStop(PaWasapiStream *stream) +void _StreamOnStop(PaWasapiStream *stream) { - // Stop INPUT client - if (stream->in.client != NULL) - IAudioClient_Stop(stream->in.client); - - // Stop OUTPUT client - if (stream->out.client != NULL) - IAudioClient_Stop(stream->out.client); + // Stop INPUT/OUTPUT clients + if (!stream->bBlocking) + { + if (stream->in.clientProc != NULL) + IAudioClient_Stop(stream->in.clientProc); + if (stream->out.clientProc != NULL) + IAudioClient_Stop(stream->out.clientProc); + } + else + { + if (stream->in.clientParent != NULL) + IAudioClient_Stop(stream->in.clientParent); + if (stream->out.clientParent != NULL) + IAudioClient_Stop(stream->out.clientParent); + } // Restore thread priority if (stream->hAvTask != NULL) @@ -3952,10 +4410,6 @@ void _OnStreamStop(PaWasapiStream *stream) stream->hAvTask = NULL; } - // Release Render/Capture clients (if Exclusive mode was used it will release devices to other applications) - SAFE_RELEASE(stream->cclient); - SAFE_RELEASE(stream->rclient); - // Notify if (stream->streamRepresentation.streamFinishedCallback != NULL) stream->streamRepresentation.streamFinishedCallback(stream->streamRepresentation.userData); @@ -3970,10 +4424,34 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) PaWasapiStream *stream = (PaWasapiStream *)param; PaWasapiHostProcessor defaultProcessor; BOOL set_event[S_COUNT] = { FALSE, FALSE }; + BOOL bWaitAllEvents = FALSE; + BOOL bThreadComInitialized = FALSE; + + /* + If COM is already initialized CoInitialize will either return + FALSE, or RPC_E_CHANGED_MODE if it was initialized in a different + threading mode. In either case we shouldn't consider it an error + but we need to be careful to not call CoUninitialize() if + RPC_E_CHANGED_MODE was returned. + */ + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE)) + { + PRINT(("WASAPI: failed ProcThreadEvent CoInitialize")); + return paUnanticipatedHostError; + } + if (hr != RPC_E_CHANGED_MODE) + bThreadComInitialized = TRUE; + + // Unmarshal stream pointers for safe COM operation + hr = UnmarshalStreamComPointers(stream); + if (hr != S_OK) { + PRINT(("Error unmarshaling stream COM pointers. HRESULT: %i\n", hr)); + goto thread_end; + } // Waiting on all events in case of Full-Duplex/Exclusive mode. - BOOL bWaitAllEvents = FALSE; - if ((stream->in.client != NULL) && (stream->out.client != NULL)) + if ((stream->in.clientProc != NULL) && (stream->out.clientProc != NULL)) { bWaitAllEvents = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE); @@ -4006,22 +4484,12 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) } // Initialize event & start INPUT stream - if (stream->in.client) + if (stream->in.clientProc) { // Create & set handle if (set_event[S_INPUT]) { - if ((hr = IAudioClient_SetEventHandle(stream->in.client, stream->event[S_INPUT])) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - // Create Capture client - if (stream->cclient == NULL) - { - if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK) + if ((hr = IAudioClient_SetEventHandle(stream->in.clientProc, stream->event[S_INPUT])) != S_OK) { LogHostError(hr); goto thread_error; @@ -4029,7 +4497,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) } // Start - if ((hr = IAudioClient_Start(stream->in.client)) != S_OK) + if ((hr = IAudioClient_Start(stream->in.clientProc)) != S_OK) { LogHostError(hr); goto thread_error; @@ -4037,22 +4505,12 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) } // Initialize event & start OUTPUT stream - if (stream->out.client) + if (stream->out.clientProc) { // Create & set handle if (set_event[S_OUTPUT]) { - if ((hr = IAudioClient_SetEventHandle(stream->out.client, stream->event[S_OUTPUT])) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - // Create Render client - if (stream->rclient == NULL) - { - if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK) + if ((hr = IAudioClient_SetEventHandle(stream->out.clientProc, stream->event[S_OUTPUT])) != S_OK) { LogHostError(hr); goto thread_error; @@ -4067,7 +4525,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) } // Start - if ((hr = IAudioClient_Start(stream->out.client)) != S_OK) + if ((hr = IAudioClient_Start(stream->out.clientProc)) != S_OK) { LogHostError(hr); goto thread_error; @@ -4103,7 +4561,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) // Input stream case WAIT_OBJECT_0 + S_INPUT: { - if (stream->cclient == NULL) + if (stream->captureClient == NULL) break; if ((hr = ProcessInputBuffer(stream, processor)) != S_OK) @@ -4117,7 +4575,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) // Output stream case WAIT_OBJECT_0 + S_OUTPUT: { - if (stream->rclient == NULL) + if (stream->renderClient == NULL) break; if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK) @@ -4133,14 +4591,21 @@ PA_THREAD_FUNC ProcThreadEvent(void *param) thread_end: // Process stop - _OnStreamStop(stream); + _StreamOnStop(stream); - // Notify: thread exited - SetEvent(stream->hThreadExit); + // Release unmarshaled COM pointers + ReleaseUnmarshaledComPointers(stream); + + // Cleanup COM for this thread + if (bThreadComInitialized == TRUE) + CoUninitialize(); // Notify: not running stream->running = FALSE; + // Notify: thread exited + SetEvent(stream->hThreadExit); + return 0; thread_error: @@ -4152,67 +4617,6 @@ thread_error: goto thread_end; } -// ------------------------------------------------------------------------------------------ -static HRESULT PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *available) -{ - HRESULT hr; - UINT32 frames = stream->out.framesPerHostCallback, - padding = 0; - - (*available) = 0; - - // get read position - if ((hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding)) != S_OK) - return LogHostError(hr); - - // get available - frames -= padding; - - // set - (*available) = frames; - return hr; -} - -// ------------------------------------------------------------------------------------------ -/*! \class ThreadSleepScheduler - Allows to emulate thread sleep of less than 1 millisecond under Windows. Scheduler - calculates number of times the thread must run untill next sleep of 1 millisecond. - It does not make thread sleeping for real number of microseconds but rather controls - how many of imaginary microseconds the thread task can allow thread to sleep. -*/ -typedef struct ThreadIdleScheduler -{ - UINT32 m_idle_microseconds; //!< number of microseconds to sleep - UINT32 m_next_sleep; //!< next sleep round - UINT32 m_i; //!< current round iterator position - UINT32 m_resolution; //!< resolution in number of milliseconds -} -ThreadIdleScheduler; -//! Setup scheduler. -static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds) -{ - assert(microseconds != 0); - assert(resolution != 0); - assert((resolution * 1000) >= microseconds); - - memset(sched, 0, sizeof(*sched)); - - sched->m_idle_microseconds = microseconds; - sched->m_resolution = resolution; - sched->m_next_sleep = (resolution * 1000) / microseconds; -} -//! Iterate and check if can sleep. -static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched) -{ - // advance and check if thread can sleep - if (++ sched->m_i == sched->m_next_sleep) - { - sched->m_i = 0; - return sched->m_resolution; - } - return 0; -} - // ------------------------------------------------------------------------------------------ PA_THREAD_FUNC ProcThreadPoll(void *param) { @@ -4225,13 +4629,49 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // Calculate the actual duration of the allocated buffer. DWORD sleep_ms = 0; - DWORD sleep_ms_in = GetFramesSleepTime(stream->in.framesPerBuffer, stream->in.wavex.Format.nSamplesPerSec); - DWORD sleep_ms_out = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec); + DWORD sleep_ms_in; + DWORD sleep_ms_out; + + BOOL bThreadComInitialized = FALSE; + + /* + If COM is already initialized CoInitialize will either return + FALSE, or RPC_E_CHANGED_MODE if it was initialized in a different + threading mode. In either case we shouldn't consider it an error + but we need to be careful to not call CoUninitialize() if + RPC_E_CHANGED_MODE was returned. + */ + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE)) + { + PRINT(("WASAPI: failed ProcThreadPoll CoInitialize")); + return paUnanticipatedHostError; + } + if (hr != RPC_E_CHANGED_MODE) + bThreadComInitialized = TRUE; - // Adjust polling time + // Unmarshal stream pointers for safe COM operation + hr = UnmarshalStreamComPointers(stream); + if (hr != S_OK) + { + PRINT(("Error unmarshaling stream COM pointers. HRESULT: %i\n", hr)); + return 0; + } + + // Calculate timeout for next polling attempt. + sleep_ms_in = GetFramesSleepTime(stream->in.framesPerHostCallback/WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec); + sleep_ms_out = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec); + + // WASAPI Input packets tend to expire very easily, let's limit sleep time to 2 milliseconds + // for all cases. Please propose better solution if any. + if (sleep_ms_in > 2) + sleep_ms_in = 2; + + // Adjust polling time for non-paUtilFixedHostBufferSize. Input stream is not adjustable as it is being + // polled according its packet length. if (stream->bufferMode != paUtilFixedHostBufferSize) { - sleep_ms_in = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec); + //sleep_ms_in = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec); sleep_ms_out = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->out.wavex.Format.nSamplesPerSec); } @@ -4245,7 +4685,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // Make sure not 0, othervise use ThreadIdleScheduler if (sleep_ms == 0) { - sleep_ms_in = GetFramesSleepTimeMicroseconds(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec); + sleep_ms_in = GetFramesSleepTimeMicroseconds(stream->in.framesPerHostCallback/WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec); sleep_ms_out = GetFramesSleepTimeMicroseconds(stream->bufferProcessor.framesPerUserBuffer, stream->out.wavex.Format.nSamplesPerSec); // Choose smallest @@ -4271,43 +4711,24 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) PaWasapi_ThreadPriorityBoost((void **)&stream->hAvTask, stream->nThreadPriority); // Initialize event & start INPUT stream - if (stream->in.client) + if (stream->in.clientProc) { - if (stream->cclient == NULL) - { - if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - if ((hr = IAudioClient_Start(stream->in.client)) != S_OK) + if ((hr = IAudioClient_Start(stream->in.clientProc)) != S_OK) { LogHostError(hr); goto thread_error; } } - // Initialize event & start OUTPUT stream - if (stream->out.client) + if (stream->out.clientProc) { - if (stream->rclient == NULL) - { - if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - // Preload buffer (obligatory, othervise ->Start() will fail), avoid processing // when in full-duplex mode as it requires input processing as well if (!PA_WASAPI__IS_FULLDUPLEX(stream)) { UINT32 frames = 0; - if ((hr = PollGetOutputFramesAvailable(stream, &frames)) == S_OK) + if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) == S_OK) { if (stream->bufferMode == paUtilFixedHostBufferSize) { @@ -4330,7 +4751,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) } // Start - if ((hr = IAudioClient_Start(stream->out.client)) != S_OK) + if ((hr = IAudioClient_Start(stream->out.clientProc)) != S_OK) { LogHostError(hr); goto thread_error; @@ -4363,7 +4784,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // Input stream case S_INPUT: { - if (stream->cclient == NULL) + if (stream->captureClient == NULL) break; if ((hr = ProcessInputBuffer(stream, processor)) != S_OK) @@ -4378,11 +4799,11 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) case S_OUTPUT: { UINT32 frames; - if (stream->rclient == NULL) + if (stream->renderClient == NULL) break; // get available frames - if ((hr = PollGetOutputFramesAvailable(stream, &frames)) != S_OK) + if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) != S_OK) { LogHostError(hr); goto thread_error; @@ -4429,7 +4850,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) UINT32 o_frames = 0; // get host input buffer - if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK) + if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK) { if (hr == AUDCLNT_S_BUFFER_EMPTY) continue; // no data in capture buffer @@ -4439,7 +4860,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) } // get available frames - if ((hr = PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK) + if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK) { LogHostError(hr); break; @@ -4452,7 +4873,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) UINT32 o_processed = i_frames; // get host output buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, o_processed, &o_data)) == S_OK) + if ((hr = IAudioRenderClient_GetBuffer(stream->procRCClient, o_processed, &o_data)) == S_OK) { // processed amount of i_frames i_processed = i_frames; @@ -4462,10 +4883,10 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) if (stream->out.monoMixer) { UINT32 mono_frames_size = o_processed * (stream->out.wavex.Format.wBitsPerSample / 8); - // expand buffer (one way only for better performance due to no calls to realloc) + // expand buffer if (mono_frames_size > stream->out.monoBufferSize) { - stream->out.monoBuffer = realloc(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); + stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); if (stream->out.monoBuffer == NULL) { LogPaError(paInsufficientMemory); @@ -4481,10 +4902,10 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) if (stream->in.monoMixer) { UINT32 mono_frames_size = i_processed * (stream->in.wavex.Format.wBitsPerSample / 8); - // expand buffer (one way only for better performance due to no calls to realloc) + // expand buffer if (mono_frames_size > stream->in.monoBufferSize) { - stream->in.monoBuffer = realloc(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); + stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); if (stream->in.monoBuffer == NULL) { LogPaError(paInsufficientMemory); @@ -4507,7 +4928,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) stream->out.monoMixer(o_data_host, stream->out.monoBuffer, o_processed); // release host output buffer - if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, o_processed, 0)) != S_OK) + if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, o_processed, 0)) != S_OK) LogHostError(hr); } else @@ -4518,7 +4939,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) } // release host input buffer - if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, i_processed)) != S_OK) + if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, i_processed)) != S_OK) { LogHostError(hr); break; @@ -4546,7 +4967,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) } // get available frames - if ((hr = PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK) + if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK) { LogHostError(hr); break; @@ -4555,7 +4976,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) while (o_frames != 0) { // get host input buffer - if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK) + if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK) { if (hr == AUDCLNT_S_BUFFER_EMPTY) break; // no data in capture buffer @@ -4574,7 +4995,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) UINT32 o_processed = i_frames; // get host output buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, o_processed, &o_data)) == S_OK) + if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, o_processed, &o_data)) == S_OK) { // processed amount of i_frames i_processed = i_frames; @@ -4584,10 +5005,10 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) if (stream->out.monoMixer) { UINT32 mono_frames_size = o_processed * (stream->out.wavex.Format.wBitsPerSample / 8); - // expand buffer (one way only for better performance due to no calls to realloc) + // expand buffer if (mono_frames_size > stream->out.monoBufferSize) { - stream->out.monoBuffer = realloc(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); + stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); if (stream->out.monoBuffer == NULL) { LogPaError(paInsufficientMemory); @@ -4603,10 +5024,10 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) if (stream->in.monoMixer) { UINT32 mono_frames_size = i_processed * (stream->in.wavex.Format.wBitsPerSample / 8); - // expand buffer (one way only for better performance due to no calls to realloc) + // expand buffer if (mono_frames_size > stream->in.monoBufferSize) { - stream->in.monoBuffer = realloc(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); + stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); if (stream->in.monoBuffer == NULL) { LogPaError(paInsufficientMemory); @@ -4629,7 +5050,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) stream->out.monoMixer(o_data_host, stream->out.monoBuffer, o_processed); // release host output buffer - if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, o_processed, 0)) != S_OK) + if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, o_processed, 0)) != S_OK) LogHostError(hr); o_frames -= o_processed; @@ -4649,7 +5070,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) fd_release_buffer_in: // release host input buffer - if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, i_processed)) != S_OK) + if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, i_processed)) != S_OK) { LogHostError(hr); break; @@ -4666,7 +5087,14 @@ fd_release_buffer_in: thread_end: // Process stop - _OnStreamStop(stream); + _StreamOnStop(stream); + + // Release unmarshaled COM pointers + ReleaseUnmarshaledComPointers(stream); + + // Cleanup COM for this thread + if (bThreadComInitialized == TRUE) + CoUninitialize(); // Notify: not running stream->running = FALSE; @@ -4685,6 +5113,18 @@ thread_error: goto thread_end; } +// ------------------------------------------------------------------------------------------ +void *PaWasapi_ReallocateMemory(void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +// ------------------------------------------------------------------------------------------ +void PaWasapi_FreeMemory(void *ptr) +{ + free(ptr); +} + //#endif //VC 2005 -- 2.43.0