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.

This commit is contained in:
dmitrykos 2010-10-20 17:00:54 +00:00
commit 738b8ad327

View file

@ -46,6 +46,7 @@
#include <windows.h>
#include <stdio.h>
#include <process.h>
#include <assert.h>
#include <mmsystem.h>
#include <mmreg.h> // 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)
{