\r
#define MAX_STR_LEN 512\r
\r
-enum { S_INPUT = 0, S_OUTPUT, S_COUNT };\r
+enum { S_INPUT = 0, S_OUTPUT = 1, S_COUNT = 2, S_FULLDUPLEX = 0 };\r
\r
#define STATIC_ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0]))\r
\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
+\r
#ifndef IF_FAILED_JUMP\r
#define IF_FAILED_JUMP(hr, label) if(FAILED(hr)) goto label;\r
#endif\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-#define _WASAPI_MONO_TO_STEREO_MIXER(TYPE)\\r
+typedef enum EMixerDir { MIX_DIR__1TO2, MIX_DIR__2TO1 } EMixerDir;\r
+\r
+// ------------------------------------------------------------------------------------------\r
+#define _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(TYPE)\\r
TYPE * __restrict to = __to;\\r
TYPE * __restrict from = __from;\\r
TYPE * __restrict end = from + count;\\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-static void _MixMonoToStereo_8(void *__to, void *__from, UINT32 count)\r
-{\r
- _WASAPI_MONO_TO_STEREO_MIXER(BYTE);\r
-}\r
-\r
-// ------------------------------------------------------------------------------------------\r
-static void _MixMonoToStereo_16(void *__to, void *__from, UINT32 count)\r
-{\r
- _WASAPI_MONO_TO_STEREO_MIXER(short);\r
-}\r
-\r
-// ------------------------------------------------------------------------------------------\r
-static void _MixMonoToStereo_24(void *__to, void *__from, UINT32 count)\r
-{\r
- _WASAPI_MONO_TO_STEREO_MIXER(int); // !!! int24 data is contained in 32-bit containers\r
-}\r
+#define _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1(TYPE)\\r
+ TYPE * __restrict to = (TYPE *)__to;\\r
+ TYPE * __restrict from = (TYPE *)__from;\\r
+ TYPE * __restrict end = to + count;\\r
+ while (to != end)\\r
+ {\\r
+ *to ++ = (TYPE)((float)(from[0] + from[1]) * 0.5f);\\r
+ from += 2;\\r
+ }\r
\r
// ------------------------------------------------------------------------------------------\r
-static void _MixMonoToStereo_32(void *__to, void *__from, UINT32 count)\r
-{\r
- _WASAPI_MONO_TO_STEREO_MIXER(int);\r
-}\r
+static void _MixMonoToStereo_1TO2_8(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(BYTE); }\r
+static void _MixMonoToStereo_1TO2_16(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(short); }\r
+static void _MixMonoToStereo_1TO2_24(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(int); /* !!! int24 data is contained in 32-bit containers*/ }\r
+static void _MixMonoToStereo_1TO2_32(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(int); }\r
+static void _MixMonoToStereo_1TO2_32f(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(float); }\r
\r
// ------------------------------------------------------------------------------------------\r
-static void _MixMonoToStereo_32f(void *__to, void *__from, UINT32 count)\r
-{\r
- _WASAPI_MONO_TO_STEREO_MIXER(float);\r
-}\r
+static void _MixMonoToStereo_2TO1_8(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1(BYTE); }\r
+static void _MixMonoToStereo_2TO1_16(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1(short); }\r
+static void _MixMonoToStereo_2TO1_24(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1(int); /* !!! int24 data is contained in 32-bit containers*/ }\r
+static void _MixMonoToStereo_2TO1_32(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1(int); }\r
+static void _MixMonoToStereo_2TO1_32f(void *__to, void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1(float); }\r
\r
// ------------------------------------------------------------------------------------------\r
-static MixMonoToStereoF _GetMonoToStereoMixer(PaSampleFormat format)\r
+static MixMonoToStereoF _GetMonoToStereoMixer(PaSampleFormat format, EMixerDir dir)\r
{\r
- switch (format)\r
+ switch (dir)\r
{\r
- case paUInt8: return _MixMonoToStereo_8;\r
- case paInt16: return _MixMonoToStereo_16;\r
- case paInt24: return _MixMonoToStereo_24;\r
- case paInt32: return _MixMonoToStereo_32;\r
- case paFloat32: return _MixMonoToStereo_32f;\r
+ case MIX_DIR__1TO2:\r
+ switch (format)\r
+ {\r
+ case paUInt8: return _MixMonoToStereo_1TO2_8;\r
+ case paInt16: return _MixMonoToStereo_1TO2_16;\r
+ case paInt24: return _MixMonoToStereo_1TO2_24;\r
+ case paInt32: return _MixMonoToStereo_1TO2_32;\r
+ case paFloat32: return _MixMonoToStereo_1TO2_32f;\r
+ }\r
+ break;\r
+\r
+ case MIX_DIR__2TO1:\r
+ switch (format)\r
+ {\r
+ case paUInt8: return _MixMonoToStereo_2TO1_8;\r
+ case paInt16: return _MixMonoToStereo_2TO1_16;\r
+ case paInt24: return _MixMonoToStereo_2TO1_24;\r
+ case paInt32: return _MixMonoToStereo_2TO1_32;\r
+ case paFloat32: return _MixMonoToStereo_2TO1_32f;\r
+ }\r
+ break;\r
}\r
+\r
return NULL;\r
}\r
\r
\r
// Validate Channel count\r
if ((WORD)params->channelCount != outWavex->Format.nChannels)\r
- return paInvalidChannelCount;\r
+ {\r
+ // If mono, then driver does not support 1 channel, we use internal workaround\r
+ // of tiny software mixing functionality, e.g. we provide to user buffer 1 channel\r
+ // but then mix into 2 for device buffer\r
+ if ((params->channelCount == 1) && (outWavex->Format.nChannels == 2))\r
+ return paFormatIsSupported;\r
+ else\r
+ return paInvalidChannelCount;\r
+ }\r
\r
// Validate Sample format\r
if ((bitsPerSample = PaSampleFormatToBitsPerSample(params->sampleFormat)) == 0)\r
}\r
\r
// select mixer\r
- pSubStream->monoMixer = _GetMonoToStereoMixer(params->sampleFormat);\r
+ pSubStream->monoMixer = _GetMonoToStereoMixer(params->sampleFormat, (info->flow == eRender ? MIX_DIR__1TO2 : MIX_DIR__2TO1));\r
if (pSubStream->monoMixer == NULL)\r
{\r
LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT);\r
}\r
\r
// select mixer\r
- pSubStream->monoMixer = _GetMonoToStereoMixer(params->sampleFormat);\r
+ pSubStream->monoMixer = _GetMonoToStereoMixer(params->sampleFormat, (info->flow == eRender ? MIX_DIR__1TO2 : MIX_DIR__2TO1));\r
if (pSubStream->monoMixer == NULL)\r
{\r
LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT);\r
stream->in.streamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;\r
if (paWasapi->useWOW64Workaround)\r
stream->in.streamFlags = 0; // polling interface\r
+ else\r
if (streamCallback == NULL)\r
stream->in.streamFlags = 0; // polling interface\r
+ else\r
if ((inputStreamInfo != NULL) && (inputStreamInfo->flags & paWinWasapiPolling))\r
stream->in.streamFlags = 0; // polling interface\r
+ else\r
+ if (outputParameters != NULL)\r
+ stream->in.streamFlags = 0; // polling interface is implemented for full-duplex mode also\r
\r
// Create Audio client\r
hr = CreateAudioClient(&stream->in, info, inputParameters, 0/*framesPerLatency*/,\r
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)\r
stream->in.latency_seconds += buffer_latency;\r
\r
- PRINT(("PaWASAPIOpenStream(input): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));\r
+ PRINT(("WASAPI::OpenStream(input): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));\r
}\r
else\r
{\r
stream->out.streamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;\r
if (paWasapi->useWOW64Workaround)\r
stream->out.streamFlags = 0; // polling interface\r
+ else\r
if (streamCallback == NULL)\r
stream->out.streamFlags = 0; // polling interface\r
+ else\r
if ((outputStreamInfo != NULL) && (outputStreamInfo->flags & paWinWasapiPolling))\r
stream->out.streamFlags = 0; // polling interface\r
+ else\r
+ if (inputParameters != NULL)\r
+ stream->out.streamFlags = 0; // polling interface is implemented for full-duplex mode also\r
\r
// Create Audio client\r
hr = CreateAudioClient(&stream->out, info, outputParameters, 0/*framesPerLatency*/,\r
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)\r
stream->out.latency_seconds += buffer_latency;\r
\r
- PRINT(("PaWASAPIOpenStream(output): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));\r
+ PRINT(("WASAPI::OpenStream(output): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useWOW64Workaround ? "YES" : "NO")));\r
}\r
else\r
{\r
outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */\r
}\r
\r
+ // og full-duplex\r
+ if ((inputParameters != NULL) && (outputParameters != NULL))\r
+ PRINT(("WASAPI::OpenStream: full-duplex mode\n"));\r
+\r
// paWinWasapiPolling must be on/or not on both streams\r
if ((inputParameters != NULL) && (outputParameters != NULL))\r
{\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-static HRESULT FillOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor, UINT32 frames)\r
+static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor, UINT32 frames)\r
{\r
HRESULT hr;\r
BYTE *data = NULL;\r
// expand buffer (one way only for better performancedue to no calls to realloc)\r
UINT32 mono_frames_size = frames * __DIV_8(stream->out.wavex.Format.wBitsPerSample);\r
if (mono_frames_size > stream->out.monoBufferSize)\r
- {\r
- stream->out.monoBuffer = realloc(stream->out.monoBuffer, mono_frames_size);\r
- }\r
+ stream->out.monoBuffer = realloc(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));\r
\r
- // feed to user\r
+ // process\r
processor[S_OUTPUT].processor(NULL, 0, (BYTE *)stream->out.monoBuffer, frames, processor[S_OUTPUT].userData);\r
\r
// mix 1 to 2 channels\r
}\r
\r
// ------------------------------------------------------------------------------------------\r
-static HRESULT PollInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor)\r
+static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor)\r
{\r
HRESULT hr;\r
UINT32 frames;\r
// expand buffer (one way only for better performancedue to no calls to realloc)\r
UINT32 mono_frames_size = frames * __DIV_8(stream->in.wavex.Format.wBitsPerSample);\r
if (mono_frames_size > stream->in.monoBufferSize)\r
- {\r
- stream->in.monoBuffer = realloc(stream->in.monoBuffer, mono_frames_size);\r
- }\r
-\r
- // feed to user\r
- processor[S_INPUT].processor((BYTE *)stream->in.monoBuffer, frames, NULL, 0, processor[S_INPUT].userData);\r
+ stream->in.monoBuffer = realloc(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));\r
\r
// mix 1 to 2 channels\r
- stream->in.monoMixer(data, stream->in.monoBuffer, frames);\r
+ stream->in.monoMixer(stream->in.monoBuffer, data, frames);\r
+\r
+ // process\r
+ processor[S_INPUT].processor((BYTE *)stream->in.monoBuffer, frames, NULL, 0, processor[S_INPUT].userData);\r
\r
#undef __DIV_8\r
}\r
}\r
\r
// Preload buffer before start\r
- if ((hr = FillOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK)\r
+ if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK)\r
{\r
LogHostError(hr);\r
goto thread_error;\r
case WAIT_OBJECT_0 + S_INPUT: {\r
if (stream->cclient == NULL)\r
break;\r
- PollInputBuffer(stream, processor);\r
+ ProcessInputBuffer(stream, processor);\r
break; }\r
\r
// Output stream\r
case WAIT_OBJECT_0 + S_OUTPUT: {\r
if (stream->rclient == NULL)\r
break;\r
- FillOutputBuffer(stream, processor, stream->out.framesPerHostCallback);\r
+ ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback);\r
break; }\r
}\r
}\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
PA_THREAD_FUNC ProcThreadPoll(void *param)\r
{\r
}\r
}\r
\r
- // Preload buffer (obligatory, othervise ->Start() will fail)\r
- if ((hr = FillOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK)\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
- LogHostError(hr);\r
- goto thread_error;\r
+ if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ goto thread_error;\r
+ }\r
}\r
\r
// Start\r
// Notify: thread started\r
SetEvent(stream->hThreadStart);\r
\r
- // Processing Loop\r
- while (WaitForSingleObject(stream->hCloseRequest, sleep_ms) == WAIT_TIMEOUT)\r
- {\r
- for (i = 0; i < S_COUNT; ++i)\r
+ if (!PA_WASAPI__IS_FULLDUPLEX(stream))\r
+ {\r
+ // Processing Loop\r
+ while (WaitForSingleObject(stream->hCloseRequest, sleep_ms) == WAIT_TIMEOUT)\r
{\r
- // Process S_INPUT/S_OUTPUT\r
- switch (i)\r
+ for (i = 0; i < S_COUNT; ++i)\r
{\r
- // Input stream\r
- case S_INPUT: {\r
- if (stream->cclient == NULL)\r
- break;\r
- PollInputBuffer(stream, processor);\r
- break; }\r
+ // Process S_INPUT/S_OUTPUT\r
+ switch (i)\r
+ {\r
+ // Input stream\r
+ case S_INPUT: {\r
+ if (stream->cclient == NULL)\r
+ break;\r
+ ProcessInputBuffer(stream, processor);\r
+ break; }\r
+\r
+ // Output stream\r
+ case S_OUTPUT: {\r
+\r
+ UINT32 frames;\r
+ if (stream->rclient == NULL)\r
+ break;\r
+\r
+ // get available frames\r
+ if ((hr = PollGetOutputFramesAvailable(stream, &frames)) != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ break;\r
+ }\r
+\r
+ // output\r
+ if (frames != 0)\r
+ ProcessOutputBuffer(stream, processor, frames);\r
+\r
+ break; }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // Processing Loop\r
+ while (WaitForSingleObject(stream->hCloseRequest, sleep_ms) == WAIT_TIMEOUT)\r
+ {\r
+ UINT32 i_frames = 0, i_processed = 0;\r
+ BYTE *i_data = NULL, *o_data = NULL;\r
+ DWORD i_flags = 0;\r
+ UINT32 o_frames = 0;\r
\r
- // Output stream\r
- case S_OUTPUT: {\r
+ // get host input buffer\r
+ if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &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
- UINT32 frames, padding;\r
+ LogHostError(hr);\r
+ break;\r
+ }\r
\r
- if (stream->rclient == NULL)\r
- break;\r
+ // get available frames\r
+ if ((hr = PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ break;\r
+ }\r
\r
- frames = stream->out.framesPerHostCallback;\r
+ // process equal ammount of frames\r
+ if (o_frames >= i_frames)\r
+ {\r
+ // process input ammount of frames\r
+ UINT32 o_processed = i_frames;\r
\r
- // Get Read position\r
- padding = 0;\r
- hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding);\r
- if (hr != S_OK)\r
+ // get host output buffer\r
+ if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, o_processed, &o_data)) == S_OK)\r
{\r
- LogHostError(hr);\r
- break;\r
- }\r
+ // processed amount of i_frames\r
+ i_processed = i_frames;\r
\r
- // Fill buffer\r
- frames -= padding;\r
- if (frames != 0)\r
- FillOutputBuffer(stream, processor, frames);\r
+ // convert output mono\r
+ if (stream->out.monoMixer)\r
+ {\r
+ #define __DIV_8(v) ((v) >> 3) //!< (v / 8)\r
+ UINT32 mono_frames_size = o_processed * __DIV_8(stream->out.wavex.Format.wBitsPerSample);\r
+ #undef __DIV_8\r
+ // expand buffer (one way only for better performance due to no calls to realloc)\r
+ if (mono_frames_size > stream->out.monoBufferSize)\r
+ stream->out.monoBuffer = realloc(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));\r
+ }\r
+\r
+ // convert input mono\r
+ if (stream->in.monoMixer)\r
+ {\r
+ #define __DIV_8(v) ((v) >> 3) //!< (v / 8)\r
+ UINT32 mono_frames_size = i_processed * __DIV_8(stream->in.wavex.Format.wBitsPerSample);\r
+ #undef __DIV_8\r
+ // expand buffer (one way only for better performance due to no calls to realloc)\r
+ if (mono_frames_size > stream->in.monoBufferSize)\r
+ stream->in.monoBuffer = realloc(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));\r
+\r
+ // mix 2 to 1 input channels\r
+ stream->in.monoMixer(stream->in.monoBuffer, i_data, i_processed);\r
+\r
+ // replace buffer pointer\r
+ i_data = (BYTE *)stream->in.monoBuffer;\r
+ }\r
+\r
+ // process\r
+ processor[S_FULLDUPLEX].processor(i_data, i_processed, o_data, o_processed, processor[S_FULLDUPLEX].userData);\r
+\r
+ // mix 1 to 2 output channels\r
+ if (stream->out.monoBuffer)\r
+ stream->out.monoMixer(o_data, stream->out.monoBuffer, o_processed);\r
+\r
+ // release host output buffer\r
+ if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, o_processed, 0)) != S_OK)\r
+ LogHostError(hr);\r
+ }\r
+ else\r
+ {\r
+ if (stream->out.shareMode != AUDCLNT_SHAREMODE_SHARED)\r
+ LogHostError(hr); // be silent in shared mode, try again next time\r
+ }\r
+ }\r
\r
- break; }\r
+ // release host input buffer\r
+ if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, i_processed)) != S_OK)\r
+ {\r
+ LogHostError(hr);\r
+ break;\r
}\r
}\r
}\r