#include "pa_stream.h"\r
#include "pa_cpuload.h"\r
#include "pa_process.h"\r
-#include "pa_debugprint.h"\r
#include "pa_win_wasapi.h"\r
+#include "pa_debugprint.h"\r
+#include "pa_ringbuffer.h"\r
\r
#ifndef NTDDI_VERSION\r
\r
\r
enum { S_INPUT = 0, S_OUTPUT = 1, S_COUNT = 2, S_FULLDUPLEX = 0 };\r
\r
+// Number of packets which compose single contignous buffer. With trial and error it was calculated\r
+// that WASAPI Input sub-system uses 6 packets per whole buffer. Please provide more information\r
+// or corrections if available.\r
+enum { WASAPI_PACKETS_PER_INPUT_BUFFER = 6 };\r
+\r
#define STATIC_ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0]))\r
\r
#define PRINT(x) PA_DEBUG(x);\r
#define PA_SKELETON_SET_LAST_HOST_ERROR( errorCode, errorText ) \\r
PaUtil_SetLastHostErrorInfo( paWASAPI, errorCode, errorText )\r
\r
-#define PA_WASAPI__IS_FULLDUPLEX(STREAM) ((STREAM)->in.client && (STREAM)->out.client)\r
+#define PA_WASAPI__IS_FULLDUPLEX(STREAM) ((STREAM)->in.clientProc && (STREAM)->out.clientProc)\r
\r
#ifndef IF_FAILED_JUMP\r
#define IF_FAILED_JUMP(hr, label) if(FAILED(hr)) goto label;\r
#endif\r
\r
+#ifndef IF_FAILED_INTERNAL_ERROR_JUMP\r
+#define IF_FAILED_INTERNAL_ERROR_JUMP(hr, error, label) if(FAILED(hr)) { error = paInternalError; goto label; }\r
+#endif\r
+\r
#define SAFE_CLOSE(h) if ((h) != NULL) { CloseHandle((h)); (h) = NULL; }\r
#define SAFE_RELEASE(punk) if ((punk) != NULL) { (punk)->lpVtbl->Release((punk)); (punk) = NULL; }\r
\r
typedef struct\r
{\r
PaUtilHostApiRepresentation inheritedHostApiRep;\r
- PaUtilStreamInterface callbackStreamInterface;\r
- PaUtilStreamInterface blockingStreamInterface;\r
+ PaUtilStreamInterface callbackStreamInterface;\r
+ PaUtilStreamInterface blockingStreamInterface;\r
\r
- PaUtilAllocationGroup *allocations;\r
+ PaUtilAllocationGroup *allocations;\r
\r
/* implementation specific data goes here */\r
\r
/* PaWasapiStream - a stream data structure specifically for this implementation */\r
typedef struct PaWasapiSubStream\r
{\r
- IAudioClient *client;\r
+ IAudioClient *clientParent;\r
+ IStream *clientStream;\r
+ IAudioClient *clientProc;\r
+\r
WAVEFORMATEXTENSIBLE wavex;\r
UINT32 bufferSize;\r
- REFERENCE_TIME device_latency;\r
+ REFERENCE_TIME deviceLatency;\r
REFERENCE_TIME period;\r
- double latency_seconds;\r
+ double latencySeconds;\r
UINT32 framesPerHostCallback;\r
AUDCLNT_SHAREMODE shareMode;\r
UINT32 streamFlags; // AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ...\r
UINT32 framesPerBuffer; //!< number of frames per 1 buffer\r
BOOL userBufferAndHostMatch;\r
\r
- // Used by blocking interface:\r
- UINT32 prevTime; // time ms between calls of WriteStream\r
- UINT32 prevSleep; // time ms to sleep from frames written in previous call\r
-\r
// Used for Mono >> Stereo workaround, if driver does not support it\r
// (in Exclusive mode WASAPI usually refuses to operate with Mono (1-ch)\r
void *monoBuffer; //!< pointer to buffer\r
UINT32 monoBufferSize; //!< buffer size in bytes\r
MixMonoToStereoF monoMixer; //!< pointer to mixer function\r
+\r
+ PaUtilRingBuffer *tailBuffer; //!< buffer with trailing sample for blocking mode operations (only for Input)\r
+ void *tailBufferMemory; //!< tail buffer memory region\r
}\r
PaWasapiSubStream;\r
\r
{\r
/* IMPLEMENT ME: rename this */\r
PaUtilStreamRepresentation streamRepresentation;\r
- PaUtilCpuLoadMeasurer cpuLoadMeasurer;\r
- PaUtilBufferProcessor bufferProcessor;\r
+ PaUtilCpuLoadMeasurer cpuLoadMeasurer;\r
+ PaUtilBufferProcessor bufferProcessor;\r
\r
// input\r
- PaWasapiSubStream in;\r
- IAudioCaptureClient *cclient;\r
- IAudioEndpointVolume *inVol;\r
+ PaWasapiSubStream in;\r
+ IAudioCaptureClient *captureClientParent;\r
+ IStream *captureClientStream;\r
+ IAudioCaptureClient *captureClient;\r
+ IAudioEndpointVolume *inVol;\r
\r
// output\r
- PaWasapiSubStream out;\r
- IAudioRenderClient *rclient;\r
- IAudioEndpointVolume *outVol;\r
+ PaWasapiSubStream out;\r
+ IAudioRenderClient *renderClientParent;\r
+ IStream *renderClientStream;\r
+ IAudioRenderClient *renderClient;\r
+ IAudioEndpointVolume *outVol;\r
\r
// event handles for event-driven processing mode\r
HANDLE event[S_COUNT];\r
PaWasapiStream;\r
\r
// Local stream methods\r
-static void _OnStreamStop(PaWasapiStream *stream);\r
-static void _FinishStream(PaWasapiStream *stream);\r
-static void _CleanupStream(PaWasapiStream *stream);\r
+void _StreamOnStop(PaWasapiStream *stream);\r
+void _StreamFinish(PaWasapiStream *stream);\r
+void _StreamCleanup(PaWasapiStream *stream);\r
+HRESULT _PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *available);\r
+HRESULT _PollGetInputFramesAvailable(PaWasapiStream *stream, UINT32 *available);\r
+void *PaWasapi_ReallocateMemory(void *ptr, size_t size);\r
+void PaWasapi_FreeMemory(void *ptr);\r
\r
// Local statics\r
static volatile BOOL g_WasapiCOMInit = FALSE;\r
return err;\r
}\r
\r
+// ------------------------------------------------------------------------------------------\r
+/*! \class ThreadSleepScheduler\r
+ Allows to emulate thread sleep of less than 1 millisecond under Windows. Scheduler\r
+ calculates number of times the thread must run untill next sleep of 1 millisecond.\r
+ It does not make thread sleeping for real number of microseconds but rather controls\r
+ how many of imaginary microseconds the thread task can allow thread to sleep.\r
+*/\r
+typedef struct ThreadIdleScheduler\r
+{\r
+ UINT32 m_idle_microseconds; //!< number of microseconds to sleep\r
+ UINT32 m_next_sleep; //!< next sleep round\r
+ UINT32 m_i; //!< current round iterator position\r
+ UINT32 m_resolution; //!< resolution in number of milliseconds\r
+}\r
+ThreadIdleScheduler;\r
+//! Setup scheduler.\r
+static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds)\r
+{\r
+ assert(microseconds != 0);\r
+ assert(resolution != 0);\r
+ assert((resolution * 1000) >= microseconds);\r
+\r
+ memset(sched, 0, sizeof(*sched));\r
+\r
+ sched->m_idle_microseconds = microseconds;\r
+ sched->m_resolution = resolution;\r
+ sched->m_next_sleep = (resolution * 1000) / microseconds;\r
+}\r
+//! Iterate and check if can sleep.\r
+static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched)\r
+{\r
+ // advance and check if thread can sleep\r
+ if (++ sched->m_i == sched->m_next_sleep)\r
+ {\r
+ sched->m_i = 0;\r
+ return sched->m_resolution;\r
+ }\r
+ return 0;\r
+}\r
+\r
// ------------------------------------------------------------------------------------------\r
/*static double nano100ToMillis(REFERENCE_TIME ref)\r
{\r
return v + (align - remainder);\r
}\r
\r
+// ------------------------------------------------------------------------------------------\r
+// Get next value power of 2\r
+UINT32 ALIGN_NEXT_POW2(UINT32 v)\r
+{\r
+ UINT32 v2 = 1;\r
+ while (v > (v2 <<= 1)) { }\r
+ v = v2;\r
+ return v;\r
+}\r
+\r
// ------------------------------------------------------------------------------------------\r
// Aligns WASAPI buffer to 128 byte packet boundary. HD Audio will fail to play if buffer\r
// is misaligned. This problem was solved in Windows 7 were AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED\r
paWasapi->enumerator = NULL;\r
hr = CoCreateInstance(&pa_CLSID_IMMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER,\r
&pa_IID_IMMDeviceEnumerator, (void **)&paWasapi->enumerator);\r
- IF_FAILED_JUMP(hr, error);\r
+ \r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
\r
// getting default device ids in the eMultimedia "role"\r
{\r
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(paWasapi->enumerator, eRender, eMultimedia, &defaultRenderer);\r
if (hr != S_OK)\r
{\r
- if (hr != E_NOTFOUND)\r
- IF_FAILED_JUMP(hr, error);\r
+ if (hr != E_NOTFOUND) {\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
+ }\r
}\r
else\r
{\r
WCHAR *pszDeviceId = NULL;\r
hr = IMMDevice_GetId(defaultRenderer, &pszDeviceId);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
wcsncpy(paWasapi->defaultRenderer, pszDeviceId, MAX_STR_LEN-1);\r
CoTaskMemFree(pszDeviceId);\r
IMMDevice_Release(defaultRenderer);\r
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(paWasapi->enumerator, eCapture, eMultimedia, &defaultCapturer);\r
if (hr != S_OK)\r
{\r
- if (hr != E_NOTFOUND)\r
- IF_FAILED_JUMP(hr, error);\r
+ if (hr != E_NOTFOUND) {\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
+ }\r
}\r
else\r
{\r
WCHAR *pszDeviceId = NULL;\r
hr = IMMDevice_GetId(defaultCapturer, &pszDeviceId);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
wcsncpy(paWasapi->defaultCapturer, pszDeviceId, MAX_STR_LEN-1);\r
CoTaskMemFree(pszDeviceId);\r
IMMDevice_Release(defaultCapturer);\r
}\r
\r
hr = IMMDeviceEnumerator_EnumAudioEndpoints(paWasapi->enumerator, eAll, DEVICE_STATE_ACTIVE, &pEndPoints);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
\r
hr = IMMDeviceCollection_GetCount(pEndPoints, &paWasapi->deviceCount);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
\r
- paWasapi->devInfo = (PaWasapiDeviceInfo *)malloc(sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount);\r
+ paWasapi->devInfo = (PaWasapiDeviceInfo *)PaUtil_AllocateMemory(sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount);\r
for (i = 0; i < paWasapi->deviceCount; ++i)\r
memset(&paWasapi->devInfo[i], 0, sizeof(PaWasapiDeviceInfo));\r
\r
PA_DEBUG(("WASAPI: ---------------\n"));\r
\r
hr = IMMDeviceCollection_Item(pEndPoints, i, &paWasapi->devInfo[i].device);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
\r
// getting ID\r
{\r
WCHAR *pszDeviceId = NULL;\r
hr = IMMDevice_GetId(paWasapi->devInfo[i].device, &pszDeviceId);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hr, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
wcsncpy(paWasapi->devInfo[i].szDeviceID, pszDeviceId, MAX_STR_LEN-1);\r
CoTaskMemFree(pszDeviceId);\r
\r
}\r
\r
hr = IMMDevice_GetState(paWasapi->devInfo[i].device, &paWasapi->devInfo[i].state);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
\r
if (paWasapi->devInfo[i].state != DEVICE_STATE_ACTIVE)\r
{\r
{\r
IPropertyStore *pProperty;\r
hr = IMMDevice_OpenPropertyStore(paWasapi->devInfo[i].device, STGM_READ, &pProperty);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
\r
// "Friendly" Name\r
{\r
PROPVARIANT value;\r
PropVariantInit(&value);\r
hr = IPropertyStore_GetValue(pProperty, &PKEY_Device_FriendlyName, &value);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
deviceInfo->name = NULL;\r
deviceName = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, MAX_STR_LEN + 1);\r
if (deviceName == NULL)\r
PROPVARIANT value;\r
PropVariantInit(&value);\r
hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEngine_DeviceFormat, &value);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
memcpy(&paWasapi->devInfo[i].DefaultFormat, value.blob.pBlobData, min(sizeof(paWasapi->devInfo[i].DefaultFormat), value.blob.cbSize));\r
// cleanup\r
PropVariantClear(&value);\r
PROPVARIANT value;\r
PropVariantInit(&value);\r
hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEndpoint_FormFactor, &value);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
// set\r
#if defined(DUMMYUNIONNAME) && defined(NONAMELESSUNION)\r
// avoid breaking strict-aliasing rules in such line: (EndpointFormFactor)(*((UINT *)(((WORD *)&value.wReserved3)+1)));\r
\r
hr = IMMDevice_Activate(paWasapi->devInfo[i].device, &pa_IID_IAudioClient,\r
CLSCTX_INPROC_SERVER, NULL, (void **)&tmpClient);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
\r
hr = IAudioClient_GetDevicePeriod(tmpClient,\r
&paWasapi->devInfo[i].DefaultDevicePeriod,\r
&paWasapi->devInfo[i].MinimumDevicePeriod);\r
- IF_FAILED_JUMP(hr, error);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ // [IF_FAILED_JUMP(hResult, error);]\r
+ IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error);\r
\r
//hr = tmpClient->GetMixFormat(&paWasapi->devInfo[i].MixFormat);\r
\r
//Digital Output (Realtek AC'97 Audio)'s GUID: {0x38f2cf50,0x7b4c,0x4740,0x86,0xeb,0xd4,0x38,0x66,0xd8,0xc8, 0x9f}\r
//so something must be _really_ wrong with this device, TODO handle this better. We kind of need GetMixFormat\r
LogHostError(hr);\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ result = paInternalError;\r
goto error;\r
}\r
}\r
break; }\r
default:\r
PRINT(("WASAPI:%d| bad Data Flow!\n", i));\r
+ // We need to set the result to a value otherwise we will return paNoError\r
+ result = paInternalError;\r
//continue; // do not skip from list, allow to initialize\r
break;\r
}\r
\r
Terminate((PaUtilHostApiRepresentation *)paWasapi);\r
\r
+ // Safety if error was not set so that we do not think initialize was a success\r
+ if (result == paNoError) {\r
+ result = paInternalError;\r
+ }\r
+\r
return result;\r
}\r
\r
//if (info->MixFormat)\r
// CoTaskMemFree(info->MixFormat);\r
}\r
- free(paWasapi->devInfo);\r
+ PaUtil_FreeMemory(paWasapi->devInfo);\r
\r
if (paWasapi->allocations)\r
{\r
const UINT32 userFramesPerBuffer = framesPerLatency;\r
IAudioClient *audioClient = NULL;\r
\r
-\r
// Validate parameters\r
if (!pSub || !pInfo || !params)\r
return E_POINTER;\r
if (framesPerLatency == 0)\r
framesPerLatency = MakeFramesFromHns(pInfo->DefaultDevicePeriod, pSub->wavex.Format.nSamplesPerSec);\r
\r
+ //! Exclusive Input stream renders data in 6 packets, we must set then the size of\r
+ //! single packet, total buffer size, e.g. required latency will be PacketSize * 6\r
+ if (!output && (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE))\r
+ {\r
+ framesPerLatency /= WASAPI_PACKETS_PER_INPUT_BUFFER;\r
+ }\r
+\r
// Calculate aligned period\r
_CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD);\r
\r
}\r
\r
// Set client\r
- pSub->client = audioClient;\r
- IAudioClient_AddRef(pSub->client);\r
+ pSub->clientParent = audioClient;\r
+ IAudioClient_AddRef(pSub->clientParent);\r
\r
// Recalculate buffers count\r
_RecalculateBuffersCount(pSub,\r
return paInvalidDevice;*/\r
\r
// Get max possible buffer size to check if it is not less than that we request\r
- hr = IAudioClient_GetBufferSize(stream->out.client, &maxBufferSize);\r
+ hr = IAudioClient_GetBufferSize(stream->out.clientParent, &maxBufferSize);\r
if (hr != S_OK)\r
{\r
LogHostError(hr);\r
\r
// Get interface latency (actually uneeded as we calculate latency from the size\r
// of maxBufferSize).\r
- hr = IAudioClient_GetStreamLatency(stream->out.client, &stream->out.device_latency);\r
+ hr = IAudioClient_GetStreamLatency(stream->out.clientParent, &stream->out.deviceLatency);\r
if (hr != S_OK)\r
{\r
LogHostError(hr);\r
LogPaError(result = paInvalidDevice);\r
goto error;\r
}\r
- //stream->out.latency_seconds = nano100ToSeconds(stream->out.device_latency);\r
+ //stream->out.latencySeconds = nano100ToSeconds(stream->out.deviceLatency);\r
\r
// Number of frames that are required at each period\r
stream->out.framesPerHostCallback = maxBufferSize;\r
buffer_latency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec;\r
\r
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)\r
- stream->out.latency_seconds = buffer_latency;\r
+ stream->out.latencySeconds = buffer_latency;\r
\r
- 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")));\r
+ 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")));\r
\r
return paNoError;\r
\r
\r
UINT32 maxBufferSize = 0;\r
PaTime buffer_latency = 0;\r
- UINT32 framesPerBuffer = stream->out.params.frames_per_buffer;\r
+ UINT32 framesPerBuffer = stream->in.params.frames_per_buffer;\r
\r
// Create Audio client\r
hr = CreateAudioClient(stream, &stream->in, FALSE, &result);\r
return paInvalidDevice;*/\r
\r
// Get max possible buffer size to check if it is not less than that we request\r
- hr = IAudioClient_GetBufferSize(stream->in.client, &maxBufferSize);\r
+ hr = IAudioClient_GetBufferSize(stream->in.clientParent, &maxBufferSize);\r
if (hr != S_OK)\r
{\r
LogHostError(hr);\r
\r
// Get interface latency (actually uneeded as we calculate latency from the size\r
// of maxBufferSize).\r
- hr = IAudioClient_GetStreamLatency(stream->in.client, &stream->in.device_latency);\r
+ hr = IAudioClient_GetStreamLatency(stream->in.clientParent, &stream->in.deviceLatency);\r
if (hr != S_OK)\r
{\r
LogHostError(hr);\r
LogPaError(result = paInvalidDevice);\r
goto error;\r
}\r
- //stream->in.latency_seconds = nano100ToSeconds(stream->in.device_latency);\r
+ //stream->in.latencySeconds = nano100ToSeconds(stream->in.deviceLatency);\r
\r
// Number of frames that are required at each period\r
stream->in.framesPerHostCallback = maxBufferSize;\r
buffer_latency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec;\r
\r
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)\r
- stream->in.latency_seconds = buffer_latency;\r
+ stream->in.latencySeconds = buffer_latency;\r
\r
- 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")));\r
+ 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")));\r
\r
return paNoError;\r
\r
stream->hostProcessOverrideInput.processor = inputStreamInfo->hostProcessorInput;\r
stream->hostProcessOverrideInput.userData = userData;\r
}\r
+\r
+ // Only get IAudioCaptureClient input once here instead of getting it at multiple places based on the use\r
+ hr = IAudioClient_GetService(stream->in.clientParent, &pa_IID_IAudioCaptureClient, (void **)&stream->captureClientParent);\r
+ if (hr != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ LogPaError(result = paUnanticipatedHostError);\r
+ goto error;\r
+ }\r
+\r
+ // Create ring buffer for blocking mode (It is needed because we fetch Input packets, not frames,\r
+ // and thus we have to save partial packet if such remains unread)\r
+ if (stream->in.params.blocking == TRUE)\r
+ {\r
+ UINT32 bufferFrames = ALIGN_NEXT_POW2((stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER) * 2);\r
+ UINT32 frameSize = stream->in.wavex.Format.nBlockAlign;\r
+\r
+ // buffer\r
+ if ((stream->in.tailBuffer = PaUtil_AllocateMemory(sizeof(PaUtilRingBuffer))) == NULL)\r
+ {\r
+ LogPaError(result = paInsufficientMemory);\r
+ goto error;\r
+ }\r
+ memset(stream->in.tailBuffer, 0, sizeof(PaUtilRingBuffer));\r
+\r
+ // buffer memory region\r
+ stream->in.tailBufferMemory = PaUtil_AllocateMemory(frameSize * bufferFrames);\r
+ if (stream->in.tailBufferMemory == NULL)\r
+ {\r
+ LogPaError(result = paInsufficientMemory);\r
+ goto error;\r
+ }\r
+\r
+ // initialize\r
+ if (PaUtil_InitializeRingBuffer(stream->in.tailBuffer, frameSize, bufferFrames, stream->in.tailBufferMemory) != 0)\r
+ {\r
+ LogPaError(result = paInternalError);\r
+ goto error;\r
+ }\r
+ }\r
}\r
else\r
{\r
stream->hostProcessOverrideOutput.processor = outputStreamInfo->hostProcessorOutput;\r
stream->hostProcessOverrideOutput.userData = userData;\r
}\r
+\r
+ // Only get IAudioCaptureClient output once here instead of getting it at multiple places based on the use\r
+ hr = IAudioClient_GetService(stream->out.clientParent, &pa_IID_IAudioRenderClient, (void **)&stream->renderClientParent);\r
+ if (hr != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ LogPaError(result = paUnanticipatedHostError);\r
+ goto error;\r
+ }\r
}\r
else\r
{\r
// Set Input latency\r
stream->streamRepresentation.streamInfo.inputLatency =\r
((double)PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) / sampleRate)\r
- + ((inputParameters)?stream->in.latency_seconds : 0);\r
+ + ((inputParameters)?stream->in.latencySeconds : 0);\r
\r
// Set Output latency\r
stream->streamRepresentation.streamInfo.outputLatency =\r
((double)PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) / sampleRate)\r
- + ((outputParameters)?stream->out.latency_seconds : 0);\r
+ + ((outputParameters)?stream->out.latencySeconds : 0);\r
\r
// Set SR\r
stream->streamRepresentation.streamInfo.sampleRate = sampleRate;\r
// abort active stream\r
if (IsStreamActive(s))\r
{\r
- if ((result = AbortStream(s)) != paNoError)\r
- return result;\r
+ result = AbortStream(s);\r
}\r
\r
- SAFE_RELEASE(stream->cclient);\r
- SAFE_RELEASE(stream->rclient);\r
- SAFE_RELEASE(stream->out.client);\r
- SAFE_RELEASE(stream->in.client);\r
+ SAFE_RELEASE(stream->captureClientParent);\r
+ SAFE_RELEASE(stream->renderClientParent);\r
+ SAFE_RELEASE(stream->out.clientParent);\r
+ SAFE_RELEASE(stream->in.clientParent);\r
SAFE_RELEASE(stream->inVol);\r
SAFE_RELEASE(stream->outVol);\r
\r
CloseHandle(stream->event[S_INPUT]);\r
CloseHandle(stream->event[S_OUTPUT]);\r
\r
- _CleanupStream(stream);\r
+ _StreamCleanup(stream);\r
\r
- free(stream->in.monoBuffer);\r
- free(stream->out.monoBuffer);\r
+ PaWasapi_FreeMemory(stream->in.monoBuffer);\r
+ PaWasapi_FreeMemory(stream->out.monoBuffer);\r
+\r
+ PaUtil_FreeMemory(stream->in.tailBuffer);\r
+ PaUtil_FreeMemory(stream->in.tailBufferMemory);\r
+\r
+ PaUtil_FreeMemory(stream->out.tailBuffer);\r
+ PaUtil_FreeMemory(stream->out.tailBufferMemory);\r
\r
PaUtil_TerminateBufferProcessor(&stream->bufferProcessor);\r
PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation);\r
return result;\r
}\r
\r
+// ------------------------------------------------------------------------------------------\r
+HRESULT UnmarshalSubStreamComPointers(PaWasapiSubStream *substream) \r
+{\r
+ HRESULT hResult = S_OK;\r
+ HRESULT hFirstBadResult = S_OK;\r
+ substream->clientProc = NULL;\r
+\r
+ // IAudioClient\r
+ hResult = CoGetInterfaceAndReleaseStream(substream->clientStream, &pa_IID_IAudioClient, (LPVOID*)&substream->clientProc);\r
+ substream->clientStream = NULL;\r
+ if (hResult != S_OK) \r
+ {\r
+ hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult;\r
+ }\r
+\r
+ return hFirstBadResult;\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+HRESULT UnmarshalStreamComPointers(PaWasapiStream *stream) \r
+{\r
+ HRESULT hResult = S_OK;\r
+ HRESULT hFirstBadResult = S_OK;\r
+ stream->captureClient = NULL;\r
+ stream->renderClient = NULL;\r
+ stream->in.clientProc = NULL;\r
+ stream->out.clientProc = NULL;\r
+\r
+ if (NULL != stream->in.clientParent) \r
+ {\r
+ // SubStream pointers\r
+ hResult = UnmarshalSubStreamComPointers(&stream->in);\r
+ if (hResult != S_OK) \r
+ {\r
+ hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult;\r
+ }\r
+\r
+ // IAudioCaptureClient\r
+ hResult = CoGetInterfaceAndReleaseStream(stream->captureClientStream, &pa_IID_IAudioCaptureClient, (LPVOID*)&stream->captureClient);\r
+ stream->captureClientStream = NULL;\r
+ if (hResult != S_OK) \r
+ {\r
+ hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult;\r
+ }\r
+ }\r
+\r
+ if (NULL != stream->out.clientParent) \r
+ {\r
+ // SubStream pointers\r
+ hResult = UnmarshalSubStreamComPointers(&stream->out);\r
+ if (hResult != S_OK) \r
+ {\r
+ hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult;\r
+ }\r
+\r
+ // IAudioRenderClient\r
+ hResult = CoGetInterfaceAndReleaseStream(stream->renderClientStream, &pa_IID_IAudioRenderClient, (LPVOID*)&stream->renderClient);\r
+ stream->renderClientStream = NULL;\r
+ if (hResult != S_OK) \r
+ {\r
+ hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult;\r
+ }\r
+ }\r
+\r
+ return hFirstBadResult;\r
+}\r
+\r
+// -----------------------------------------------------------------------------------------\r
+void ReleaseUnmarshaledSubComPointers(PaWasapiSubStream *substream) \r
+{\r
+ SAFE_RELEASE(substream->clientProc);\r
+}\r
+\r
+// -----------------------------------------------------------------------------------------\r
+void ReleaseUnmarshaledComPointers(PaWasapiStream *stream) \r
+{\r
+ // Release AudioClient services first\r
+ SAFE_RELEASE(stream->captureClient);\r
+ SAFE_RELEASE(stream->renderClient);\r
+\r
+ // Release AudioClients\r
+ ReleaseUnmarshaledSubComPointers(&stream->in);\r
+ ReleaseUnmarshaledSubComPointers(&stream->out);\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+HRESULT MarshalSubStreamComPointers(PaWasapiSubStream *substream) \r
+{\r
+ HRESULT hResult;\r
+ substream->clientStream = NULL;\r
+\r
+ // IAudioClient\r
+ hResult = CoMarshalInterThreadInterfaceInStream(&pa_IID_IAudioClient, (LPUNKNOWN)substream->clientParent, &substream->clientStream);\r
+ if (hResult != S_OK)\r
+ goto marshal_sub_error;\r
+\r
+ return hResult;\r
+\r
+ // If marshaling error occurred, make sure to release everything.\r
+marshal_sub_error:\r
+\r
+ UnmarshalSubStreamComPointers(substream);\r
+ ReleaseUnmarshaledSubComPointers(substream);\r
+ return hResult;\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+HRESULT MarshalStreamComPointers(PaWasapiStream *stream) \r
+{\r
+ HRESULT hResult = S_OK;\r
+ stream->captureClientStream = NULL;\r
+ stream->in.clientStream = NULL;\r
+ stream->renderClientStream = NULL;\r
+ stream->out.clientStream = NULL;\r
+\r
+ if (NULL != stream->in.clientParent) \r
+ {\r
+ // SubStream pointers\r
+ hResult = MarshalSubStreamComPointers(&stream->in);\r
+ if (hResult != S_OK) \r
+ goto marshal_error;\r
+\r
+ // IAudioCaptureClient\r
+ hResult = CoMarshalInterThreadInterfaceInStream(&pa_IID_IAudioCaptureClient, (LPUNKNOWN)stream->captureClientParent, &stream->captureClientStream);\r
+ if (hResult != S_OK) \r
+ goto marshal_error;\r
+ }\r
+\r
+ if (NULL != stream->out.clientParent) \r
+ {\r
+ // SubStream pointers\r
+ hResult = MarshalSubStreamComPointers(&stream->out);\r
+ if (hResult != S_OK) \r
+ goto marshal_error;\r
+\r
+ // IAudioRenderClient\r
+ hResult = CoMarshalInterThreadInterfaceInStream(&pa_IID_IAudioRenderClient, (LPUNKNOWN)stream->renderClientParent, &stream->renderClientStream);\r
+ if (hResult != S_OK) \r
+ goto marshal_error;\r
+ }\r
+\r
+ return hResult;\r
+\r
+ // If marshaling error occurred, make sure to release everything.\r
+marshal_error:\r
+\r
+ UnmarshalStreamComPointers(stream);\r
+ ReleaseUnmarshaledComPointers(stream);\r
+ return hResult;\r
+}\r
+\r
// ------------------------------------------------------------------------------------------\r
static PaError StartStream( PaStream *s )\r
{\r
HRESULT hr;\r
PaWasapiStream *stream = (PaWasapiStream*)s;\r
+ PaError result = paNoError;\r
\r
// check if stream is active already\r
if (IsStreamActive(s))\r
PaUtil_ResetBufferProcessor(&stream->bufferProcessor);\r
\r
// Cleanup handles (may be necessary if stream was stopped by itself due to error)\r
- _CleanupStream(stream);\r
+ _StreamCleanup(stream);\r
\r
// Create close event\r
- stream->hCloseRequest = CreateEvent(NULL, TRUE, FALSE, NULL);\r
+ if ((stream->hCloseRequest = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) \r
+ {\r
+ result = paInsufficientMemory;\r
+ goto start_error;\r
+ }\r
\r
// Create thread\r
if (!stream->bBlocking)\r
// Create thread events\r
stream->hThreadStart = CreateEvent(NULL, TRUE, FALSE, NULL);\r
stream->hThreadExit = CreateEvent(NULL, TRUE, FALSE, NULL);\r
+ if ((stream->hThreadStart == NULL) || (stream->hThreadExit == NULL))\r
+ {\r
+ result = paInsufficientMemory;\r
+ goto start_error;\r
+ }\r
+\r
+ // Marshal WASAPI interface pointers for safe use in thread created below.\r
+ if ((hr = MarshalStreamComPointers(stream)) != S_OK) \r
+ {\r
+ PRINT(("Failed marshaling stream COM pointers."));\r
+ result = paUnanticipatedHostError;\r
+ goto nonblocking_start_error;\r
+ }\r
\r
- if ((stream->in.client && (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) ||\r
- (stream->out.client && (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)))\r
+ if ((stream->in.clientParent && (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) ||\r
+ (stream->out.clientParent && (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)))\r
{\r
- if ((stream->hThread = CREATE_THREAD(ProcThreadEvent)) == NULL)\r
- return paUnanticipatedHostError;\r
+ if ((stream->hThread = CREATE_THREAD(ProcThreadEvent)) == NULL) \r
+ {\r
+ PRINT(("Failed creating thread: ProcThreadEvent."));\r
+ result = paUnanticipatedHostError;\r
+ goto nonblocking_start_error;\r
+ }\r
}\r
else\r
{\r
- if ((stream->hThread = CREATE_THREAD(ProcThreadPoll)) == NULL)\r
- return paUnanticipatedHostError;\r
+ if ((stream->hThread = CREATE_THREAD(ProcThreadPoll)) == NULL) \r
+ {\r
+ PRINT(("Failed creating thread: ProcThreadPoll."));\r
+ result = paUnanticipatedHostError;\r
+ goto nonblocking_start_error;\r
+ }\r
}\r
\r
// Wait for thread to start\r
- if (WaitForSingleObject(stream->hThreadStart, 60*1000) == WAIT_TIMEOUT)\r
- return paUnanticipatedHostError;\r
+ if (WaitForSingleObject(stream->hThreadStart, 60*1000) == WAIT_TIMEOUT) \r
+ {\r
+ PRINT(("Failed starting thread: timeout."));\r
+ result = paUnanticipatedHostError;\r
+ goto nonblocking_start_error;\r
+ }\r
}\r
else\r
{\r
// Create blocking operation events (non-signaled event means - blocking operation is pending)\r
- if (stream->out.client)\r
- stream->hBlockingOpStreamWR = CreateEvent(NULL, TRUE, TRUE, NULL);\r
- if (stream->in.client)\r
- stream->hBlockingOpStreamRD = CreateEvent(NULL, TRUE, TRUE, NULL);\r
-\r
- // Initialize event & start INPUT stream\r
- if (stream->in.client)\r
+ if (stream->out.clientParent != NULL) \r
{\r
- if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK)\r
+ if ((stream->hBlockingOpStreamWR = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL) \r
{\r
- LogHostError(hr);\r
- return paUnanticipatedHostError;\r
+ result = paInsufficientMemory;\r
+ goto start_error;\r
}\r
-\r
- if ((hr = IAudioClient_Start(stream->in.client)) != S_OK)\r
+ }\r
+ if (stream->in.clientParent != NULL) \r
+ {\r
+ if ((stream->hBlockingOpStreamRD = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL) \r
{\r
- LogHostError(hr);\r
- return paUnanticipatedHostError;\r
+ result = paInsufficientMemory;\r
+ goto start_error;\r
}\r
}\r
\r
-\r
- // Initialize event & start OUTPUT stream\r
- if (stream->out.client)\r
+ // Initialize event & start INPUT stream\r
+ if (stream->in.clientParent != NULL)\r
{\r
- if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK)\r
+ if ((hr = IAudioClient_Start(stream->in.clientParent)) != S_OK)\r
{\r
LogHostError(hr);\r
- return paUnanticipatedHostError;\r
+ result = paUnanticipatedHostError;\r
+ goto start_error;\r
}\r
+ }\r
\r
+ // Initialize event & start OUTPUT stream\r
+ if (stream->out.clientParent != NULL)\r
+ {\r
// Start\r
- if ((hr = IAudioClient_Start(stream->out.client)) != S_OK)\r
+ if ((hr = IAudioClient_Start(stream->out.clientParent)) != S_OK)\r
{\r
LogHostError(hr);\r
- return paUnanticipatedHostError;\r
+ result = paUnanticipatedHostError;\r
+ goto start_error;\r
}\r
}\r
\r
- // Signal: stream running\r
- stream->running = TRUE;\r
+ // Set parent to working pointers to use shared functions.\r
+ stream->captureClient = stream->captureClientParent;\r
+ stream->renderClient = stream->renderClientParent;\r
+ stream->in.clientProc = stream->in.clientParent;\r
+ stream->out.clientProc = stream->out.clientParent;\r
\r
- // Set current time\r
- stream->out.prevTime = timeGetTime();\r
- stream->out.prevSleep = 0;\r
+ // Signal: stream running.\r
+ stream->running = TRUE;\r
}\r
\r
- return paNoError;\r
+ return result;\r
+\r
+nonblocking_start_error:\r
+\r
+ // Set hThreadExit event to prevent blocking during cleanup\r
+ SetEvent(stream->hThreadExit);\r
+ UnmarshalStreamComPointers(stream);\r
+ ReleaseUnmarshaledComPointers(stream);\r
+\r
+start_error:\r
+\r
+ StopStream(s);\r
+ return result;\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-static void _FinishStream(PaWasapiStream *stream)\r
+void _StreamFinish(PaWasapiStream *stream)\r
{\r
// Issue command to thread to stop processing and wait for thread exit\r
if (!stream->bBlocking)\r
// Blocking mode does not own thread\r
{\r
// Signal close event and wait for each of 2 blocking operations to complete\r
- if (stream->out.client)\r
+ if (stream->out.clientParent)\r
SignalObjectAndWait(stream->hCloseRequest, stream->hBlockingOpStreamWR, INFINITE, TRUE);\r
- if (stream->out.client)\r
+ if (stream->out.clientParent)\r
SignalObjectAndWait(stream->hCloseRequest, stream->hBlockingOpStreamRD, INFINITE, TRUE);\r
\r
// Process stop\r
- _OnStreamStop(stream);\r
+ _StreamOnStop(stream);\r
}\r
\r
// Cleanup handles\r
- _CleanupStream(stream);\r
+ _StreamCleanup(stream);\r
\r
stream->running = FALSE;\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-static void _CleanupStream(PaWasapiStream *stream)\r
+void _StreamCleanup(PaWasapiStream *stream)\r
{\r
// Close thread handles to allow restart\r
SAFE_CLOSE(stream->hThread);\r
static PaError StopStream( PaStream *s )\r
{\r
// Finish stream\r
- _FinishStream((PaWasapiStream *)s);\r
+ _StreamFinish((PaWasapiStream *)s);\r
return paNoError;\r
}\r
\r
static PaError AbortStream( PaStream *s )\r
{\r
// Finish stream\r
- _FinishStream((PaWasapiStream *)s);\r
+ _StreamFinish((PaWasapiStream *)s);\r
return paNoError;\r
}\r
\r
/* suppress unused variable warnings */\r
(void) stream;\r
\r
- /* IMPLEMENT ME, see portaudio.h for required behavior*/\r
-\r
- //this is lame ds and mme does the same thing, quite useless method imho\r
- //why dont we fetch the time in the pa callbacks?\r
- //at least its doing to be clocked to something\r
return PaUtil_GetTime();\r
}\r
\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-/* NOT TESTED */\r
-static PaError ReadStream( PaStream* s, void *_buffer, unsigned long _frames )\r
+static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames )\r
{\r
PaWasapiStream *stream = (PaWasapiStream*)s;\r
\r
HRESULT hr = S_OK;\r
- UINT32 frames;\r
BYTE *user_buffer = (BYTE *)_buffer;\r
BYTE *wasapi_buffer = NULL;\r
DWORD flags = 0;\r
- UINT32 i;\r
+ UINT32 i, available, sleep = 0;\r
+ unsigned long processed;\r
+ ThreadIdleScheduler sched;\r
\r
// validate\r
if (!stream->running)\r
return paStreamIsStopped;\r
- if (stream->cclient == NULL)\r
+ if (stream->captureClient == NULL)\r
return paBadStreamPtr;\r
\r
// Notify blocking op has begun\r
ResetEvent(stream->hBlockingOpStreamRD);\r
\r
- // make a local copy of the user buffer pointer(s), this is necessary\r
+ // Use thread scheduling for 500 microseconds (emulated) when wait time for frames is less than\r
+ // 1 milliseconds, emulation helps to normalize CPU consumption and avoids too busy waiting\r
+ ThreadIdleScheduler_Setup(&sched, 1, 250/* microseconds */);\r
+\r
+ // Make a local copy of the user buffer pointer(s), this is necessary\r
// because PaUtil_CopyOutput() advances these pointers every time it is called\r
if (!stream->bufferProcessor.userInputIsInterleaved)\r
{\r
((BYTE **)user_buffer)[i] = ((BYTE **)_buffer)[i];\r
}\r
\r
- while (_frames != 0)\r
+ // Findout if there are tail frames, flush them all before reading hardware\r
+ if ((available = PaUtil_GetRingBufferReadAvailable(stream->in.tailBuffer)) != 0)\r
{\r
- UINT32 processed, processed_size;\r
+ UINT32 buf1_size = 0, buf2_size = 0, read, desired;\r
+ void *buf1 = NULL, *buf2 = NULL;\r
+\r
+ // Limit desired to amount of requested frames\r
+ desired = available;\r
+ if (desired > frames)\r
+ desired = frames;\r
+ \r
+ // Get pointers to read regions\r
+ read = PaUtil_GetRingBufferReadRegions(stream->in.tailBuffer, desired, &buf1, &buf1_size, &buf2, &buf2_size);\r
+\r
+ if (buf1 != NULL)\r
+ {\r
+ // Register available frames to processor\r
+ PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf1_size);\r
\r
- // Get the available data in the shared buffer.\r
- if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &wasapi_buffer, &frames, &flags, NULL, NULL)) != S_OK)\r
+ // Register host buffer pointer to processor\r
+ PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, buf1, stream->bufferProcessor.inputChannelCount);\r
+\r
+ // Copy user data to host buffer (with conversion if applicable)\r
+ processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, buf1_size);\r
+ frames -= processed;\r
+ }\r
+\r
+ if (buf2 != NULL)\r
{\r
- if (hr == AUDCLNT_S_BUFFER_EMPTY)\r
+ // Register available frames to processor\r
+ PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf2_size);\r
+\r
+ // Register host buffer pointer to processor\r
+ PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, buf2, stream->bufferProcessor.inputChannelCount);\r
+\r
+ // Copy user data to host buffer (with conversion if applicable)\r
+ processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, buf2_size);\r
+ frames -= processed;\r
+ }\r
+\r
+ // Advance\r
+ PaUtil_AdvanceRingBufferReadIndex(stream->in.tailBuffer, read);\r
+ }\r
+\r
+ // Read hardware\r
+ while (frames != 0)\r
+ {\r
+ // Check if blocking call must be interrupted\r
+ if (WaitForSingleObject(stream->hCloseRequest, sleep) != WAIT_TIMEOUT)\r
+ break;\r
+\r
+ // Get available frames (must be finding out available frames before call to IAudioCaptureClient_GetBuffer\r
+ // othervise audio glitches will occur inExclusive mode as it seems that WASAPI has some scheduling/\r
+ // processing problems when such busy polling with IAudioCaptureClient_GetBuffer occurs)\r
+ if ((hr = _PollGetInputFramesAvailable(stream, &available)) != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ return paUnanticipatedHostError;\r
+ }\r
+\r
+ // Wait for more frames to become available\r
+ if (available == 0)\r
+ {\r
+ // Exclusive mode may require latency of 1 millisecond, thus we shall sleep\r
+ // around 500 microseconds (emulated) to collect packets in time\r
+ if (stream->in.shareMode != AUDCLNT_SHAREMODE_EXCLUSIVE)\r
{\r
- // Check if blocking call must be interrupted\r
- if (WaitForSingleObject(stream->hCloseRequest, 1) != WAIT_TIMEOUT)\r
- break;\r
+ UINT32 sleep_frames = (frames < stream->in.framesPerHostCallback ? frames : stream->in.framesPerHostCallback);\r
+\r
+ sleep = GetFramesSleepTime(sleep_frames, stream->in.wavex.Format.nSamplesPerSec);\r
+ sleep /= 4; // wait only for 1/4 of the buffer\r
+\r
+ // WASAPI input provides packets, thus expiring packet will result in bad audio\r
+ // limit waiting time to 2 seconds (will always work for smallest buffer in Shared)\r
+ if (sleep > 2)\r
+ sleep = 2;\r
+\r
+ // Avoid busy waiting, schedule next 1 millesecond wait\r
+ if (sleep == 0)\r
+ sleep = ThreadIdleScheduler_NextSleep(&sched);\r
+ }\r
+ else\r
+ {\r
+ if ((sleep = ThreadIdleScheduler_NextSleep(&sched)) != 0)\r
+ {\r
+ Sleep(sleep);\r
+ sleep = 0;\r
+ }\r
}\r
\r
- return LogHostError(hr);\r
- goto stream_rd_end;\r
+ continue;\r
}\r
\r
- // Detect silence\r
- // if (flags & AUDCLNT_BUFFERFLAGS_SILENT)\r
- // data = NULL;\r
+ // Get the available data in the shared buffer.\r
+ if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &wasapi_buffer, &available, &flags, NULL, NULL)) != S_OK)\r
+ {\r
+ // Buffer size is too small, waiting\r
+ if (hr != AUDCLNT_S_BUFFER_EMPTY)\r
+ {\r
+ LogHostError(hr);\r
+ goto end;\r
+ }\r
\r
- // Check if frames <= _frames\r
- if (frames > _frames)\r
- frames = _frames;\r
+ continue;\r
+ }\r
\r
// Register available frames to processor\r
- PaUtil_SetInputFrameCount(&stream->bufferProcessor, frames);\r
+ PaUtil_SetInputFrameCount(&stream->bufferProcessor, available);\r
\r
// Register host buffer pointer to processor\r
- PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.inputChannelCount);\r
+ PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.inputChannelCount);\r
\r
// Copy user data to host buffer (with conversion if applicable)\r
processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, frames);\r
+ frames -= processed;\r
\r
- // Advance user buffer to consumed portion\r
- processed_size = processed * stream->in.wavex.Format.nBlockAlign;\r
- if (stream->bufferProcessor.userInputIsInterleaved)\r
- {\r
- user_buffer += processed_size;\r
- }\r
- else\r
+ // Save tail into buffer\r
+ if ((frames == 0) && (available > processed))\r
{\r
- for (i = 0; i < stream->bufferProcessor.inputChannelCount; ++i)\r
- ((BYTE **)user_buffer)[i] = ((BYTE **)user_buffer)[i] + processed_size;\r
+ UINT32 bytes_processed = processed * stream->in.wavex.Format.nBlockAlign;\r
+ UINT32 frames_to_save = available - processed;\r
+\r
+ PaUtil_WriteRingBuffer(stream->in.tailBuffer, wasapi_buffer + bytes_processed, frames_to_save);\r
}\r
\r
// Release host buffer\r
- if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, processed)) != S_OK)\r
+ if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, available)) != S_OK)\r
{\r
LogHostError(hr);\r
- goto stream_rd_end;\r
+ goto end;\r
}\r
-\r
- _frames -= processed;\r
}\r
\r
-stream_rd_end:\r
+end:\r
\r
// Notify blocking op has ended\r
SetEvent(stream->hBlockingOpStreamRD);\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long _frames )\r
+static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long frames )\r
{\r
PaWasapiStream *stream = (PaWasapiStream*)s;\r
\r
- UINT32 frames;\r
+ //UINT32 frames;\r
const BYTE *user_buffer = (const BYTE *)_buffer;\r
BYTE *wasapi_buffer;\r
HRESULT hr = S_OK;\r
- UINT32 next_rev_sleep, blocks, block_sleep_ms;\r
- UINT32 i;\r
+ UINT32 i, available, sleep = 0;\r
+ unsigned long processed;\r
+ ThreadIdleScheduler sched;\r
\r
// validate\r
if (!stream->running)\r
return paStreamIsStopped;\r
- if (stream->rclient == NULL)\r
+ if (stream->renderClient == NULL)\r
return paBadStreamPtr;\r
\r
// Notify blocking op has begun\r
ResetEvent(stream->hBlockingOpStreamWR);\r
\r
- // Calculate sleep time for next call\r
- {\r
- UINT32 remainder = 0;\r
- UINT32 sleep_ms = 0;\r
- DWORD elapsed_ms;\r
- blocks = _frames / stream->out.framesPerHostCallback;\r
- block_sleep_ms = GetFramesSleepTime(stream->out.framesPerHostCallback, stream->out.wavex.Format.nSamplesPerSec);\r
- if (blocks == 0)\r
- {\r
- blocks = 1;\r
- sleep_ms = GetFramesSleepTime(_frames, stream->out.wavex.Format.nSamplesPerSec); // partial\r
- }\r
- else\r
- {\r
- remainder = _frames - blocks * stream->out.framesPerHostCallback;\r
- sleep_ms = block_sleep_ms; // full\r
- }\r
-\r
- // Sleep for remainder\r
- elapsed_ms = timeGetTime() - stream->out.prevTime;\r
- if (sleep_ms >= elapsed_ms)\r
- sleep_ms -= elapsed_ms;\r
+ // Use thread scheduling for 500 microseconds (emulated) when wait time for frames is less than\r
+ // 1 milliseconds, emulation helps to normalize CPU consumption and avoids too busy waiting\r
+ ThreadIdleScheduler_Setup(&sched, 1, 500/* microseconds */);\r
\r
- next_rev_sleep = sleep_ms;\r
- }\r
-\r
- // Sleep diff from last call\r
- if (stream->out.prevSleep)\r
- Sleep(stream->out.prevSleep);\r
- stream->out.prevSleep = next_rev_sleep;\r
-\r
- // make a local copy of the user buffer pointer(s), this is necessary\r
+ // Make a local copy of the user buffer pointer(s), this is necessary\r
// because PaUtil_CopyOutput() advances these pointers every time it is called\r
if (!stream->bufferProcessor.userOutputIsInterleaved)\r
{\r
((const BYTE **)user_buffer)[i] = ((const BYTE **)_buffer)[i];\r
}\r
\r
- // Feed engine\r
- for (i = 0; i < blocks; ++i)\r
+ // Blocking (potentially, untill 'frames' are consumed) loop\r
+ while (frames != 0)\r
{\r
- UINT32 available, processed;\r
-\r
- // Get block frames\r
- frames = stream->out.framesPerHostCallback;\r
- if (frames > _frames)\r
- frames = _frames;\r
+ // Check if blocking call must be interrupted\r
+ if (WaitForSingleObject(stream->hCloseRequest, sleep) != WAIT_TIMEOUT)\r
+ break;\r
\r
- if (i)\r
- Sleep(block_sleep_ms);\r
+ // Get frames available\r
+ if ((hr = _PollGetOutputFramesAvailable(stream, &available)) != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ goto end;\r
+ }\r
\r
- while (frames != 0)\r
+ // Wait for more frames to become available\r
+ if (available == 0)\r
{\r
- UINT32 padding = 0;\r
- UINT32 processed_size;\r
+ UINT32 sleep_frames = (frames < stream->out.framesPerHostCallback ? frames : stream->out.framesPerHostCallback);\r
\r
- // Check if blocking call must be interrupted\r
- if (WaitForSingleObject(stream->hCloseRequest, 0) != WAIT_TIMEOUT)\r
- break;\r
+ sleep = GetFramesSleepTime(sleep_frames, stream->out.wavex.Format.nSamplesPerSec);\r
+ sleep /= 2; // wait only for half of the buffer\r
\r
- // Get Read position\r
- hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding);\r
- if (hr != S_OK)\r
- {\r
- LogHostError(hr);\r
- goto stream_wr_end;\r
- }\r
+ // Avoid busy waiting, schedule next 1 millesecond wait\r
+ if (sleep == 0)\r
+ sleep = ThreadIdleScheduler_NextSleep(&sched);\r
\r
- // Calculate frames available\r
- if (frames >= padding)\r
- available = frames - padding;\r
- else\r
- available = frames;\r
+ continue;\r
+ }\r
\r
- // Get pointer to host buffer\r
- if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, available, &wasapi_buffer)) != S_OK)\r
- {\r
- // Buffer size is too big, waiting\r
- if (hr == AUDCLNT_E_BUFFER_TOO_LARGE)\r
- continue;\r
- LogHostError(hr);\r
- goto stream_wr_end;\r
- }\r
+ // Keep in 'frmaes' range\r
+ if (available > frames)\r
+ available = frames;\r
\r
- // Register available frames to processor\r
- PaUtil_SetOutputFrameCount(&stream->bufferProcessor, available);\r
+ // Get pointer to host buffer\r
+ if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, available, &wasapi_buffer)) != S_OK)\r
+ {\r
+ // Buffer size is too big, waiting\r
+ if (hr == AUDCLNT_E_BUFFER_TOO_LARGE)\r
+ continue;\r
\r
- // Register host buffer pointer to processor\r
- PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.outputChannelCount);\r
+ LogHostError(hr);\r
+ goto end;\r
+ }\r
\r
- // Copy user data to host buffer (with conversion if applicable)\r
- processed = PaUtil_CopyOutput(&stream->bufferProcessor, (const void **)&user_buffer, available);\r
+ // Keep waiting again (on Vista it was noticed that WASAPI could SOMETIMES return NULL pointer \r
+ // to buffer without returning AUDCLNT_E_BUFFER_TOO_LARGE instead)\r
+ if (wasapi_buffer == NULL)\r
+ continue;\r
\r
- // Advance user buffer to consumed portion\r
- processed_size = processed * stream->out.wavex.Format.nBlockAlign;\r
- if (stream->bufferProcessor.userOutputIsInterleaved)\r
- {\r
- user_buffer += processed_size;\r
- }\r
- else\r
- {\r
- for (i = 0; i < stream->bufferProcessor.outputChannelCount; ++i)\r
- ((const BYTE **)user_buffer)[i] = ((const BYTE **)user_buffer)[i] + processed_size;\r
- }\r
+ // Register available frames to processor\r
+ PaUtil_SetOutputFrameCount(&stream->bufferProcessor, available);\r
\r
- // Release host buffer\r
- if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, processed, 0)) != S_OK)\r
- {\r
- LogHostError(hr);\r
- goto stream_wr_end;\r
- }\r
+ // Register host buffer pointer to processor\r
+ PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.outputChannelCount);\r
\r
- // Deduct frames\r
- frames -= processed;\r
- }\r
+ // Copy user data to host buffer (with conversion if applicable), this call will advance\r
+ // pointer 'user_buffer' to consumed portion of data\r
+ processed = PaUtil_CopyOutput(&stream->bufferProcessor, (const void **)&user_buffer, frames);\r
+ frames -= processed;\r
\r
- _frames -= frames;\r
+ // Release host buffer\r
+ if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, available, 0)) != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ goto end;\r
+ }\r
}\r
\r
-stream_wr_end:\r
-\r
- // Set prev time\r
- stream->out.prevTime = timeGetTime();\r
+end:\r
\r
// Notify blocking op has ended\r
SetEvent(stream->hBlockingOpStreamWR);\r
return (hr != S_OK ? paUnanticipatedHostError : paNoError);\r
}\r
\r
+unsigned long PaUtil_GetOutputFrameCount( PaUtilBufferProcessor* bp )\r
+{\r
+ return bp->hostOutputFrameCount[0];\r
+}\r
+\r
// ------------------------------------------------------------------------------------------\r
-/* NOT TESTED */\r
static signed long GetStreamReadAvailable( PaStream* s )\r
{\r
PaWasapiStream *stream = (PaWasapiStream*)s;\r
+\r
HRESULT hr;\r
- UINT32 pending = 0;\r
+ UINT32 available = 0;\r
\r
// validate\r
if (!stream->running)\r
return paStreamIsStopped;\r
- if (stream->cclient == NULL)\r
+ if (stream->captureClient == NULL)\r
return paBadStreamPtr;\r
\r
- hr = IAudioClient_GetCurrentPadding(stream->in.client, &pending);\r
- if (hr != S_OK)\r
+ // available in hardware buffer\r
+ if ((hr = _PollGetInputFramesAvailable(stream, &available)) != S_OK)\r
{\r
LogHostError(hr);\r
return paUnanticipatedHostError;\r
}\r
\r
- return (long)pending;\r
+ // available in software tail buffer\r
+ available += PaUtil_GetRingBufferReadAvailable(stream->in.tailBuffer);\r
+\r
+ return available;\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
static signed long GetStreamWriteAvailable( PaStream* s )\r
{\r
PaWasapiStream *stream = (PaWasapiStream*)s;\r
-\r
- UINT32 frames = stream->out.framesPerHostCallback;\r
HRESULT hr;\r
- UINT32 padding = 0;\r
+ UINT32 available = 0;\r
\r
// validate\r
if (!stream->running)\r
return paStreamIsStopped;\r
- if (stream->rclient == NULL)\r
+ if (stream->renderClient == NULL)\r
return paBadStreamPtr;\r
\r
- hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding);\r
- if (hr != S_OK)\r
+ if ((hr = _PollGetOutputFramesAvailable(stream, &available)) != S_OK)\r
{\r
LogHostError(hr);\r
return paUnanticipatedHostError;\r
}\r
\r
- // Calculate\r
- frames -= padding;\r
-\r
- return frames;\r
+ return (signed long)available;\r
}\r
\r
+\r
// ------------------------------------------------------------------------------------------\r
static void WaspiHostProcessingLoop( void *inputBuffer, long inputFrames,\r
void *outputBuffer, long outputFrames,\r
*/\r
timeInfo.currentTime = PaUtil_GetTime();\r
// Query input latency\r
- if (stream->in.client != NULL)\r
+ if (stream->in.clientProc != NULL)\r
{\r
PaTime pending_time;\r
- if ((hr = IAudioClient_GetCurrentPadding(stream->in.client, &pending)) == S_OK)\r
+ if ((hr = IAudioClient_GetCurrentPadding(stream->in.clientProc, &pending)) == S_OK)\r
pending_time = (PaTime)pending / (PaTime)stream->in.wavex.Format.nSamplesPerSec;\r
else\r
- pending_time = (PaTime)stream->in.latency_seconds;\r
+ pending_time = (PaTime)stream->in.latencySeconds;\r
\r
timeInfo.inputBufferAdcTime = timeInfo.currentTime + pending_time;\r
}\r
// Query output current latency\r
- if (stream->out.client != NULL)\r
+ if (stream->out.clientProc != NULL)\r
{\r
PaTime pending_time;\r
- if ((hr = IAudioClient_GetCurrentPadding(stream->out.client, &pending)) == S_OK)\r
+ if ((hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &pending)) == S_OK)\r
pending_time = (PaTime)pending / (PaTime)stream->out.wavex.Format.nSamplesPerSec;\r
else\r
- pending_time = (PaTime)stream->out.latency_seconds;\r
+ pending_time = (PaTime)stream->out.latencySeconds;\r
\r
timeInfo.outputBufferDacTime = timeInfo.currentTime + pending_time;\r
}\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor, UINT32 frames)\r
+HRESULT _PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *available)\r
+{\r
+ HRESULT hr;\r
+ UINT32 frames = stream->out.framesPerHostCallback,\r
+ padding = 0;\r
+\r
+ (*available) = 0;\r
+\r
+ // get read position\r
+ if ((hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &padding)) != S_OK)\r
+ return LogHostError(hr);\r
+\r
+ // get available\r
+ frames -= padding;\r
+\r
+ // set\r
+ (*available) = frames;\r
+ return hr;\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+HRESULT _PollGetInputFramesAvailable(PaWasapiStream *stream, UINT32 *available)\r
+{\r
+ HRESULT hr;\r
+\r
+ (*available) = 0;\r
+\r
+ // GetCurrentPadding() has opposite meaning to Output stream \r
+ if ((hr = IAudioClient_GetCurrentPadding(stream->in.clientProc, available)) != S_OK)\r
+ return LogHostError(hr);\r
+\r
+ return hr;\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor, UINT32 frames)\r
{\r
HRESULT hr;\r
BYTE *data = NULL;\r
\r
// Get buffer\r
- if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, frames, &data)) != S_OK)\r
+ if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, frames, &data)) != S_OK)\r
{\r
if (stream->out.shareMode == AUDCLNT_SHAREMODE_SHARED)\r
{\r
#if 0\r
// Get Read position\r
UINT32 padding = 0;\r
- hr = stream->out.client->GetCurrentPadding(&padding);\r
+ hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &padding);\r
if (hr != S_OK)\r
return LogHostError(hr);\r
\r
if (frames == 0)\r
return S_OK;\r
\r
- if ((hr = stream->rclient->GetBuffer(frames, &data)) != S_OK)\r
+ if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, frames, &data)) != S_OK)\r
return LogHostError(hr);\r
#else\r
if (hr == AUDCLNT_E_BUFFER_TOO_LARGE)\r
// Process data\r
if (stream->out.monoMixer != NULL)\r
{\r
- // expand buffer (one way only for better performancedue to no calls to realloc)\r
+ // expand buffer\r
UINT32 mono_frames_size = frames * (stream->out.wavex.Format.wBitsPerSample / 8);\r
if (mono_frames_size > stream->out.monoBufferSize)\r
- stream->out.monoBuffer = realloc(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));\r
+ stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));\r
\r
// process\r
processor[S_OUTPUT].processor(NULL, 0, (BYTE *)stream->out.monoBuffer, frames, processor[S_OUTPUT].userData);\r
}\r
\r
// Release buffer\r
- if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, frames, 0)) != S_OK)\r
+ if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, frames, 0)) != S_OK)\r
LogHostError(hr);\r
\r
return hr;\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor)\r
+HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor)\r
{\r
HRESULT hr = S_OK;\r
UINT32 frames;\r
if (WaitForSingleObject(stream->hCloseRequest, 0) != WAIT_TIMEOUT)\r
break;\r
\r
+ // Findout if any frames available\r
+ frames = 0;\r
+ if ((hr = _PollGetInputFramesAvailable(stream, &frames)) != S_OK)\r
+ return hr;\r
+\r
+ // Empty/consumed buffer\r
+ if (frames == 0)\r
+ break;\r
+\r
// Get the available data in the shared buffer.\r
- if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &data, &frames, &flags, NULL, NULL)) != S_OK)\r
+ if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &data, &frames, &flags, NULL, NULL)) != S_OK)\r
{\r
if (hr == AUDCLNT_S_BUFFER_EMPTY)\r
{\r
hr = S_OK;\r
- break; // capture buffer exhausted\r
+ break; // Empty/consumed buffer\r
}\r
\r
return LogHostError(hr);\r
// Process data\r
if (stream->in.monoMixer != NULL)\r
{\r
- // expand buffer (one way only for better performancedue to no calls to realloc)\r
+ // expand buffer\r
UINT32 mono_frames_size = frames * (stream->in.wavex.Format.wBitsPerSample / 8);\r
if (mono_frames_size > stream->in.monoBufferSize)\r
- stream->in.monoBuffer = realloc(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));\r
+ stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));\r
\r
// mix 1 to 2 channels\r
stream->in.monoMixer(stream->in.monoBuffer, data, frames);\r
}\r
\r
// Release buffer\r
- if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, frames)) != S_OK)\r
+ if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, frames)) != S_OK)\r
return LogHostError(hr);\r
\r
- break;\r
+ //break;\r
}\r
\r
return hr;\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-void _OnStreamStop(PaWasapiStream *stream)\r
+void _StreamOnStop(PaWasapiStream *stream)\r
{\r
- // Stop INPUT client\r
- if (stream->in.client != NULL)\r
- IAudioClient_Stop(stream->in.client);\r
-\r
- // Stop OUTPUT client\r
- if (stream->out.client != NULL)\r
- IAudioClient_Stop(stream->out.client);\r
+ // Stop INPUT/OUTPUT clients\r
+ if (!stream->bBlocking) \r
+ {\r
+ if (stream->in.clientProc != NULL)\r
+ IAudioClient_Stop(stream->in.clientProc);\r
+ if (stream->out.clientProc != NULL)\r
+ IAudioClient_Stop(stream->out.clientProc);\r
+ } \r
+ else \r
+ {\r
+ if (stream->in.clientParent != NULL)\r
+ IAudioClient_Stop(stream->in.clientParent);\r
+ if (stream->out.clientParent != NULL)\r
+ IAudioClient_Stop(stream->out.clientParent);\r
+ }\r
\r
// Restore thread priority\r
if (stream->hAvTask != NULL)\r
stream->hAvTask = NULL;\r
}\r
\r
- // Release Render/Capture clients (if Exclusive mode was used it will release devices to other applications)\r
- SAFE_RELEASE(stream->cclient);\r
- SAFE_RELEASE(stream->rclient);\r
-\r
// Notify\r
if (stream->streamRepresentation.streamFinishedCallback != NULL)\r
stream->streamRepresentation.streamFinishedCallback(stream->streamRepresentation.userData);\r
PaWasapiStream *stream = (PaWasapiStream *)param;\r
PaWasapiHostProcessor defaultProcessor;\r
BOOL set_event[S_COUNT] = { FALSE, FALSE };\r
+ BOOL bWaitAllEvents = FALSE;\r
+ BOOL bThreadComInitialized = FALSE;\r
+\r
+ /*\r
+ If COM is already initialized CoInitialize will either return\r
+ FALSE, or RPC_E_CHANGED_MODE if it was initialized in a different\r
+ threading mode. In either case we shouldn't consider it an error\r
+ but we need to be careful to not call CoUninitialize() if \r
+ RPC_E_CHANGED_MODE was returned.\r
+ */\r
+ hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);\r
+ if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE))\r
+ {\r
+ PRINT(("WASAPI: failed ProcThreadEvent CoInitialize"));\r
+ return paUnanticipatedHostError;\r
+ }\r
+ if (hr != RPC_E_CHANGED_MODE)\r
+ bThreadComInitialized = TRUE;\r
+\r
+ // Unmarshal stream pointers for safe COM operation\r
+ hr = UnmarshalStreamComPointers(stream);\r
+ if (hr != S_OK) {\r
+ PRINT(("Error unmarshaling stream COM pointers. HRESULT: %i\n", hr));\r
+ goto thread_end;\r
+ }\r
\r
// Waiting on all events in case of Full-Duplex/Exclusive mode.\r
- BOOL bWaitAllEvents = FALSE;\r
- if ((stream->in.client != NULL) && (stream->out.client != NULL))\r
+ if ((stream->in.clientProc != NULL) && (stream->out.clientProc != NULL))\r
{\r
bWaitAllEvents = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) &&\r
(stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE);\r
}\r
\r
// Initialize event & start INPUT stream\r
- if (stream->in.client)\r
+ if (stream->in.clientProc)\r
{\r
// Create & set handle\r
if (set_event[S_INPUT])\r
{\r
- if ((hr = IAudioClient_SetEventHandle(stream->in.client, stream->event[S_INPUT])) != S_OK)\r
- {\r
- LogHostError(hr);\r
- goto thread_error;\r
- }\r
- }\r
-\r
- // Create Capture client\r
- if (stream->cclient == NULL)\r
- {\r
- if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK)\r
+ if ((hr = IAudioClient_SetEventHandle(stream->in.clientProc, stream->event[S_INPUT])) != S_OK)\r
{\r
LogHostError(hr);\r
goto thread_error;\r
}\r
\r
// Start\r
- if ((hr = IAudioClient_Start(stream->in.client)) != S_OK)\r
+ if ((hr = IAudioClient_Start(stream->in.clientProc)) != S_OK)\r
{\r
LogHostError(hr);\r
goto thread_error;\r
}\r
\r
// Initialize event & start OUTPUT stream\r
- if (stream->out.client)\r
+ if (stream->out.clientProc)\r
{\r
// Create & set handle\r
if (set_event[S_OUTPUT])\r
{\r
- if ((hr = IAudioClient_SetEventHandle(stream->out.client, stream->event[S_OUTPUT])) != S_OK)\r
- {\r
- LogHostError(hr);\r
- goto thread_error;\r
- }\r
- }\r
-\r
- // Create Render client\r
- if (stream->rclient == NULL)\r
- {\r
- if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK)\r
+ if ((hr = IAudioClient_SetEventHandle(stream->out.clientProc, stream->event[S_OUTPUT])) != S_OK)\r
{\r
LogHostError(hr);\r
goto thread_error;\r
}\r
\r
// Start\r
- if ((hr = IAudioClient_Start(stream->out.client)) != S_OK)\r
+ if ((hr = IAudioClient_Start(stream->out.clientProc)) != S_OK)\r
{\r
LogHostError(hr);\r
goto thread_error;\r
// Input stream\r
case WAIT_OBJECT_0 + S_INPUT: {\r
\r
- if (stream->cclient == NULL)\r
+ if (stream->captureClient == NULL)\r
break;\r
\r
if ((hr = ProcessInputBuffer(stream, processor)) != S_OK)\r
// Output stream\r
case WAIT_OBJECT_0 + S_OUTPUT: {\r
\r
- if (stream->rclient == NULL)\r
+ if (stream->renderClient == NULL)\r
break;\r
\r
if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK)\r
thread_end:\r
\r
// Process stop\r
- _OnStreamStop(stream);\r
+ _StreamOnStop(stream);\r
\r
- // Notify: thread exited\r
- SetEvent(stream->hThreadExit);\r
+ // Release unmarshaled COM pointers\r
+ ReleaseUnmarshaledComPointers(stream);\r
+\r
+ // Cleanup COM for this thread\r
+ if (bThreadComInitialized == TRUE)\r
+ CoUninitialize();\r
\r
// Notify: not running\r
stream->running = FALSE;\r
\r
+ // Notify: thread exited\r
+ SetEvent(stream->hThreadExit);\r
+\r
return 0;\r
\r
thread_error:\r
goto thread_end;\r
}\r
\r
-// ------------------------------------------------------------------------------------------\r
-static HRESULT PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *available)\r
-{\r
- HRESULT hr;\r
- UINT32 frames = stream->out.framesPerHostCallback,\r
- padding = 0;\r
-\r
- (*available) = 0;\r
-\r
- // get read position\r
- if ((hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding)) != S_OK)\r
- return LogHostError(hr);\r
-\r
- // get available\r
- frames -= padding;\r
-\r
- // set\r
- (*available) = frames;\r
- return hr;\r
-}\r
-\r
-// ------------------------------------------------------------------------------------------\r
-/*! \class ThreadSleepScheduler\r
- Allows to emulate thread sleep of less than 1 millisecond under Windows. Scheduler\r
- calculates number of times the thread must run untill next sleep of 1 millisecond.\r
- It does not make thread sleeping for real number of microseconds but rather controls\r
- how many of imaginary microseconds the thread task can allow thread to sleep.\r
-*/\r
-typedef struct ThreadIdleScheduler\r
-{\r
- UINT32 m_idle_microseconds; //!< number of microseconds to sleep\r
- UINT32 m_next_sleep; //!< next sleep round\r
- UINT32 m_i; //!< current round iterator position\r
- UINT32 m_resolution; //!< resolution in number of milliseconds\r
-}\r
-ThreadIdleScheduler;\r
-//! Setup scheduler.\r
-static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds)\r
-{\r
- assert(microseconds != 0);\r
- assert(resolution != 0);\r
- assert((resolution * 1000) >= microseconds);\r
-\r
- memset(sched, 0, sizeof(*sched));\r
-\r
- sched->m_idle_microseconds = microseconds;\r
- sched->m_resolution = resolution;\r
- sched->m_next_sleep = (resolution * 1000) / microseconds;\r
-}\r
-//! Iterate and check if can sleep.\r
-static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched)\r
-{\r
- // advance and check if thread can sleep\r
- if (++ sched->m_i == sched->m_next_sleep)\r
- {\r
- sched->m_i = 0;\r
- return sched->m_resolution;\r
- }\r
- return 0;\r
-}\r
-\r
// ------------------------------------------------------------------------------------------\r
PA_THREAD_FUNC ProcThreadPoll(void *param)\r
{\r
\r
// Calculate the actual duration of the allocated buffer.\r
DWORD sleep_ms = 0;\r
- DWORD sleep_ms_in = GetFramesSleepTime(stream->in.framesPerBuffer, stream->in.wavex.Format.nSamplesPerSec);\r
- DWORD sleep_ms_out = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec);\r
+ DWORD sleep_ms_in;\r
+ DWORD sleep_ms_out;\r
+\r
+ BOOL bThreadComInitialized = FALSE;\r
+\r
+ /*\r
+ If COM is already initialized CoInitialize will either return\r
+ FALSE, or RPC_E_CHANGED_MODE if it was initialized in a different\r
+ threading mode. In either case we shouldn't consider it an error\r
+ but we need to be careful to not call CoUninitialize() if \r
+ RPC_E_CHANGED_MODE was returned.\r
+ */\r
+ hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);\r
+ if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE))\r
+ {\r
+ PRINT(("WASAPI: failed ProcThreadPoll CoInitialize"));\r
+ return paUnanticipatedHostError;\r
+ }\r
+ if (hr != RPC_E_CHANGED_MODE)\r
+ bThreadComInitialized = TRUE;\r
\r
- // Adjust polling time\r
+ // Unmarshal stream pointers for safe COM operation\r
+ hr = UnmarshalStreamComPointers(stream);\r
+ if (hr != S_OK) \r
+ {\r
+ PRINT(("Error unmarshaling stream COM pointers. HRESULT: %i\n", hr));\r
+ return 0;\r
+ }\r
+\r
+ // Calculate timeout for next polling attempt.\r
+ sleep_ms_in = GetFramesSleepTime(stream->in.framesPerHostCallback/WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec);\r
+ sleep_ms_out = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec);\r
+\r
+ // WASAPI Input packets tend to expire very easily, let's limit sleep time to 2 milliseconds\r
+ // for all cases. Please propose better solution if any.\r
+ if (sleep_ms_in > 2)\r
+ sleep_ms_in = 2;\r
+\r
+ // Adjust polling time for non-paUtilFixedHostBufferSize. Input stream is not adjustable as it is being\r
+ // polled according its packet length.\r
if (stream->bufferMode != paUtilFixedHostBufferSize)\r
{\r
- sleep_ms_in = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec);\r
+ //sleep_ms_in = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec);\r
sleep_ms_out = GetFramesSleepTime(stream->bufferProcessor.framesPerUserBuffer, stream->out.wavex.Format.nSamplesPerSec);\r
}\r
\r
// Make sure not 0, othervise use ThreadIdleScheduler\r
if (sleep_ms == 0)\r
{\r
- sleep_ms_in = GetFramesSleepTimeMicroseconds(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec);\r
+ sleep_ms_in = GetFramesSleepTimeMicroseconds(stream->in.framesPerHostCallback/WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec);\r
sleep_ms_out = GetFramesSleepTimeMicroseconds(stream->bufferProcessor.framesPerUserBuffer, stream->out.wavex.Format.nSamplesPerSec);\r
\r
// Choose smallest\r
PaWasapi_ThreadPriorityBoost((void **)&stream->hAvTask, stream->nThreadPriority);\r
\r
// Initialize event & start INPUT stream\r
- if (stream->in.client)\r
+ if (stream->in.clientProc)\r
{\r
- if (stream->cclient == NULL)\r
- {\r
- if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK)\r
- {\r
- LogHostError(hr);\r
- goto thread_error;\r
- }\r
- }\r
-\r
- if ((hr = IAudioClient_Start(stream->in.client)) != S_OK)\r
+ if ((hr = IAudioClient_Start(stream->in.clientProc)) != S_OK)\r
{\r
LogHostError(hr);\r
goto thread_error;\r
}\r
}\r
\r
-\r
// Initialize event & start OUTPUT stream\r
- if (stream->out.client)\r
+ if (stream->out.clientProc)\r
{\r
- if (stream->rclient == NULL)\r
- {\r
- if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK)\r
- {\r
- LogHostError(hr);\r
- goto thread_error;\r
- }\r
- }\r
-\r
// Preload buffer (obligatory, othervise ->Start() will fail), avoid processing\r
// when in full-duplex mode as it requires input processing as well\r
if (!PA_WASAPI__IS_FULLDUPLEX(stream))\r
{\r
UINT32 frames = 0;\r
- if ((hr = PollGetOutputFramesAvailable(stream, &frames)) == S_OK)\r
+ if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) == S_OK)\r
{\r
if (stream->bufferMode == paUtilFixedHostBufferSize)\r
{\r
}\r
\r
// Start\r
- if ((hr = IAudioClient_Start(stream->out.client)) != S_OK)\r
+ if ((hr = IAudioClient_Start(stream->out.clientProc)) != S_OK)\r
{\r
LogHostError(hr);\r
goto thread_error;\r
// Input stream\r
case S_INPUT: {\r
\r
- if (stream->cclient == NULL)\r
+ if (stream->captureClient == NULL)\r
break;\r
\r
if ((hr = ProcessInputBuffer(stream, processor)) != S_OK)\r
case S_OUTPUT: {\r
\r
UINT32 frames;\r
- if (stream->rclient == NULL)\r
+ if (stream->renderClient == NULL)\r
break;\r
\r
// get available frames\r
- if ((hr = PollGetOutputFramesAvailable(stream, &frames)) != S_OK)\r
+ if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) != S_OK)\r
{\r
LogHostError(hr);\r
goto thread_error;\r
UINT32 o_frames = 0;\r
\r
// get host input buffer\r
- if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK)\r
+ if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK)\r
{\r
if (hr == AUDCLNT_S_BUFFER_EMPTY)\r
continue; // no data in capture buffer\r
}\r
\r
// get available frames\r
- if ((hr = PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK)\r
+ if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK)\r
{\r
LogHostError(hr);\r
break;\r
UINT32 o_processed = i_frames;\r
\r
// get host output buffer\r
- if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, o_processed, &o_data)) == S_OK)\r
+ if ((hr = IAudioRenderClient_GetBuffer(stream->procRCClient, o_processed, &o_data)) == S_OK)\r
{\r
// processed amount of i_frames\r
i_processed = i_frames;\r
if (stream->out.monoMixer)\r
{\r
UINT32 mono_frames_size = o_processed * (stream->out.wavex.Format.wBitsPerSample / 8);\r
- // expand buffer (one way only for better performance due to no calls to realloc)\r
+ // expand buffer\r
if (mono_frames_size > stream->out.monoBufferSize)\r
{\r
- stream->out.monoBuffer = realloc(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));\r
+ stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));\r
if (stream->out.monoBuffer == NULL)\r
{\r
LogPaError(paInsufficientMemory);\r
if (stream->in.monoMixer)\r
{\r
UINT32 mono_frames_size = i_processed * (stream->in.wavex.Format.wBitsPerSample / 8);\r
- // expand buffer (one way only for better performance due to no calls to realloc)\r
+ // expand buffer\r
if (mono_frames_size > stream->in.monoBufferSize)\r
{\r
- stream->in.monoBuffer = realloc(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));\r
+ stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));\r
if (stream->in.monoBuffer == NULL)\r
{\r
LogPaError(paInsufficientMemory);\r
stream->out.monoMixer(o_data_host, stream->out.monoBuffer, o_processed);\r
\r
// release host output buffer\r
- if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, o_processed, 0)) != S_OK)\r
+ if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, o_processed, 0)) != S_OK)\r
LogHostError(hr);\r
}\r
else\r
}\r
\r
// release host input buffer\r
- if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, i_processed)) != S_OK)\r
+ if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, i_processed)) != S_OK)\r
{\r
LogHostError(hr);\r
break;\r
}\r
\r
// get available frames\r
- if ((hr = PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK)\r
+ if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK)\r
{\r
LogHostError(hr);\r
break;\r
while (o_frames != 0)\r
{\r
// get host input buffer\r
- if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK)\r
+ if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK)\r
{\r
if (hr == AUDCLNT_S_BUFFER_EMPTY)\r
break; // no data in capture buffer\r
UINT32 o_processed = i_frames;\r
\r
// get host output buffer\r
- if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, o_processed, &o_data)) == S_OK)\r
+ if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, o_processed, &o_data)) == S_OK)\r
{\r
// processed amount of i_frames\r
i_processed = i_frames;\r
if (stream->out.monoMixer)\r
{\r
UINT32 mono_frames_size = o_processed * (stream->out.wavex.Format.wBitsPerSample / 8);\r
- // expand buffer (one way only for better performance due to no calls to realloc)\r
+ // expand buffer\r
if (mono_frames_size > stream->out.monoBufferSize)\r
{\r
- stream->out.monoBuffer = realloc(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));\r
+ stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));\r
if (stream->out.monoBuffer == NULL)\r
{\r
LogPaError(paInsufficientMemory);\r
if (stream->in.monoMixer)\r
{\r
UINT32 mono_frames_size = i_processed * (stream->in.wavex.Format.wBitsPerSample / 8);\r
- // expand buffer (one way only for better performance due to no calls to realloc)\r
+ // expand buffer\r
if (mono_frames_size > stream->in.monoBufferSize)\r
{\r
- stream->in.monoBuffer = realloc(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));\r
+ stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));\r
if (stream->in.monoBuffer == NULL)\r
{\r
LogPaError(paInsufficientMemory);\r
stream->out.monoMixer(o_data_host, stream->out.monoBuffer, o_processed);\r
\r
// release host output buffer\r
- if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, o_processed, 0)) != S_OK)\r
+ if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, o_processed, 0)) != S_OK)\r
LogHostError(hr);\r
\r
o_frames -= o_processed;\r
fd_release_buffer_in:\r
\r
// release host input buffer\r
- if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, i_processed)) != S_OK)\r
+ if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, i_processed)) != S_OK)\r
{\r
LogHostError(hr);\r
break;\r
thread_end:\r
\r
// Process stop\r
- _OnStreamStop(stream);\r
+ _StreamOnStop(stream);\r
+\r
+ // Release unmarshaled COM pointers\r
+ ReleaseUnmarshaledComPointers(stream);\r
+\r
+ // Cleanup COM for this thread\r
+ if (bThreadComInitialized == TRUE)\r
+ CoUninitialize();\r
\r
// Notify: not running\r
stream->running = FALSE;\r
goto thread_end;\r
}\r
\r
+// ------------------------------------------------------------------------------------------\r
+void *PaWasapi_ReallocateMemory(void *ptr, size_t size)\r
+{\r
+ return realloc(ptr, size);\r
+}\r
+\r
+// ------------------------------------------------------------------------------------------\r
+void PaWasapi_FreeMemory(void *ptr)\r
+{\r
+ free(ptr);\r
+}\r
+\r
//#endif //VC 2005\r
\r
\r