From 738b8ad3279e5da4e79a5191983b4084e2394477 Mon Sep 17 00:00:00 2001 From: dmitrykos Date: Wed, 20 Oct 2010 17:00:54 +0000 Subject: [PATCH] wasapi: improved audio quality stability in Shared mode if framesPerBuffer (from user-side) value is lower than 10 milliseconds, now even 1 frame provides stable quality. Exclusive mode is also affected by this improvement. --- src/hostapi/wasapi/pa_win_wasapi.c | 115 +++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 13 deletions(-) diff --git a/src/hostapi/wasapi/pa_win_wasapi.c b/src/hostapi/wasapi/pa_win_wasapi.c index aa03748..3b08abc 100644 --- a/src/hostapi/wasapi/pa_win_wasapi.c +++ b/src/hostapi/wasapi/pa_win_wasapi.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include // must be before other Wasapi headers #if defined(_MSC_VER) && (_MSC_VER >= 1400) @@ -653,6 +654,11 @@ static UINT32 AlignFramesPerBuffer(UINT32 nFrames, UINT32 nSamplesPerSec, UINT32 // align to packet size frame_bytes = pAlignFunc(frame_bytes, HDA_PACKET_SIZE); // use ALIGN_FWD if bigger but safer period is more desired + + // atlest 1 frame must be available + if (frame_bytes < HDA_PACKET_SIZE) + frame_bytes = HDA_PACKET_SIZE; + nFrames = frame_bytes / nBlockAlign; packets = frame_bytes / HDA_PACKET_SIZE; @@ -677,6 +683,19 @@ static UINT32 GetFramesSleepTime(UINT32 nFrames, UINT32 nSamplesPerSec) return (UINT32)(nDuration/REFTIMES_PER_MILLISEC/2); } +// ------------------------------------------------------------------------------------------ +static UINT32 GetFramesSleepTimeMicroseconds(UINT32 nFrames, UINT32 nSamplesPerSec) +{ + REFERENCE_TIME nDuration; + if (nSamplesPerSec == 0) + return 0; +#define REFTIMES_PER_SEC 10000000 +#define REFTIMES_PER_MILLISEC 10000 + // Calculate the actual duration of the allocated buffer. + nDuration = (REFERENCE_TIME)((double)REFTIMES_PER_SEC * nFrames / nSamplesPerSec); + return (UINT32)(nDuration/10/2); +} + // ------------------------------------------------------------------------------------------ static BOOL SetupAVRT() { @@ -1371,20 +1390,20 @@ static void LogWAVEFORMATEXTENSIBLE(const WAVEFORMATEXTENSIBLE *in) { case WAVE_FORMAT_EXTENSIBLE: { - PRINT(("wFormatTag=WAVE_FORMAT_EXTENSIBLE\n")); + PRINT(("wFormatTag =WAVE_FORMAT_EXTENSIBLE\n")); if (IsEqualGUID(&in->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { - PRINT(("SubFormat=KSDATAFORMAT_SUBTYPE_IEEE_FLOAT\n")); + PRINT(("SubFormat =KSDATAFORMAT_SUBTYPE_IEEE_FLOAT\n")); } else if (IsEqualGUID(&in->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_PCM)) { - PRINT(("SubFormat=KSDATAFORMAT_SUBTYPE_PCM\n")); + PRINT(("SubFormat =KSDATAFORMAT_SUBTYPE_PCM\n")); } else { - PRINT(("SubFormat=CUSTOM GUID{%d:%d:%d:%d%d%d%d%d%d%d%d}\n", + PRINT(("SubFormat =CUSTOM GUID{%d:%d:%d:%d%d%d%d%d%d%d%d}\n", in->SubFormat.Data1, in->SubFormat.Data2, in->SubFormat.Data3, @@ -1397,14 +1416,15 @@ static void LogWAVEFORMATEXTENSIBLE(const WAVEFORMATEXTENSIBLE *in) (int)in->SubFormat.Data4[6], (int)in->SubFormat.Data4[7])); } - PRINT(("Samples.wValidBitsPerSample=%d\n", in->Samples.wValidBitsPerSample)); + PRINT(("Samples.wValidBitsPerSample =%d\n", in->Samples.wValidBitsPerSample)); PRINT(("dwChannelMask =0x%X\n",in->dwChannelMask)); break; } - case WAVE_FORMAT_PCM: PRINT(("wFormatTag=WAVE_FORMAT_PCM\n")); break; - case WAVE_FORMAT_IEEE_FLOAT: PRINT(("wFormatTag=WAVE_FORMAT_IEEE_FLOAT\n")); break; - default : PRINT(("wFormatTag=UNKNOWN(%d)\n",old->wFormatTag)); break; + case WAVE_FORMAT_PCM: PRINT(("wFormatTag =WAVE_FORMAT_PCM\n")); break; + case WAVE_FORMAT_IEEE_FLOAT: PRINT(("wFormatTag =WAVE_FORMAT_IEEE_FLOAT\n")); break; + default: + PRINT(("wFormatTag =UNKNOWN(%d)\n",old->wFormatTag)); break; } PRINT(("nChannels =%d\n",old->nChannels)); @@ -3976,6 +3996,46 @@ static HRESULT PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *avai return hr; } +// ------------------------------------------------------------------------------------------ +/*! \class ThreadSleepScheduler + Allows to emulate thread sleep of less than 1 millisecond under Windows. Scheduler + calculates number of times the thread must run untill next sleep of 1 millisecond. + It does not make thread sleeping for real number of microseconds but rather controls + how many of imaginary microseconds the thread task can allow thread to sleep. +*/ +typedef struct ThreadIdleScheduler +{ + UINT32 m_idle_microseconds; //!< number of microseconds to sleep + UINT32 m_next_sleep; //!< next sleep round + UINT32 m_i; //!< current round iterator position + UINT32 m_resolution; //!< resolution in number of milliseconds +} +ThreadIdleScheduler; +//! Setup scheduler. +static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds) +{ + assert(microseconds != 0); + assert(resolution != 0); + assert((resolution * 1000) >= microseconds); + + memset(sched, 0, sizeof(*sched)); + + sched->m_idle_microseconds = microseconds; + sched->m_resolution = resolution; + sched->m_next_sleep = (resolution * 1000) / microseconds; +} +//! Iterate and check if can sleep. +static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched) +{ + // advance and check if thread can sleep + if (++ sched->m_i == sched->m_next_sleep) + { + sched->m_i = 0; + return sched->m_resolution; + } + return 0; +} + // ------------------------------------------------------------------------------------------ PA_THREAD_FUNC ProcThreadPoll(void *param) { @@ -3984,6 +4044,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) PaWasapiStream *stream = (PaWasapiStream *)param; PaWasapiHostProcessor defaultProcessor; INT32 i; + ThreadIdleScheduler scheduler; // Calculate the actual duration of the allocated buffer. DWORD sleep_ms = 0; @@ -4004,9 +4065,24 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) { sleep_ms = (sleep_ms_in ? sleep_ms_in : sleep_ms_out); } - // Make sure not 0 + // Make sure not 0, othervise use ThreadIdleScheduler if (sleep_ms == 0) - sleep_ms = 1; + { + sleep_ms_in = GetFramesSleepTimeMicroseconds(stream->bufferProcessor.framesPerUserBuffer, stream->in.wavex.Format.nSamplesPerSec); + sleep_ms_out = GetFramesSleepTimeMicroseconds(stream->bufferProcessor.framesPerUserBuffer, stream->out.wavex.Format.nSamplesPerSec); + + // Choose smallest + if ((sleep_ms_in != 0) && (sleep_ms_out != 0)) + sleep_ms = min(sleep_ms_in, sleep_ms_out); + else + { + sleep_ms = (sleep_ms_in ? sleep_ms_in : sleep_ms_out); + } + + // Setup thread sleep scheduler + ThreadIdleScheduler_Setup(&scheduler, 1, sleep_ms/* microseconds here actually */); + sleep_ms = 0; + } // Setup data processors defaultProcessor.processor = WaspiHostProcessingLoop; @@ -4093,8 +4169,15 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) if (!PA_WASAPI__IS_FULLDUPLEX(stream)) { // Processing Loop - while (WaitForSingleObject(stream->hCloseRequest, sleep_ms) == WAIT_TIMEOUT) + UINT32 next_sleep = sleep_ms; + while (WaitForSingleObject(stream->hCloseRequest, next_sleep) == WAIT_TIMEOUT) { + // Get next sleep time + if (sleep_ms == 0) + { + next_sleep = ThreadIdleScheduler_NextSleep(&scheduler); + } + for (i = 0; i < S_COUNT; ++i) { // Process S_INPUT/S_OUTPUT @@ -4267,8 +4350,8 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) } #else // Processing Loop - //sleep_ms = 1; - while (WaitForSingleObject(stream->hCloseRequest, sleep_ms) == WAIT_TIMEOUT) + UINT32 next_sleep = sleep_ms; + while (WaitForSingleObject(stream->hCloseRequest, next_sleep) == WAIT_TIMEOUT) { UINT32 i_frames = 0, i_processed = 0; BYTE *i_data = NULL, *o_data = NULL, *o_data_host = NULL; @@ -4280,6 +4363,12 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) //if (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) // sleep_ms = !sleep_ms; + // Get next sleep time + if (sleep_ms == 0) + { + next_sleep = ThreadIdleScheduler_NextSleep(&scheduler); + } + // get available frames if ((hr = PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK) { -- 2.43.0