wasapi: Improve stability of playback in Polling Shared mode with a low host latency requsted (22 ms):
- Implement workaround for system timer coarse granularity causing underruns by using timeBeginPeriod/timeEndPeriod APIs inside the Event and Poll thread handlers. - Update next sleep time of the Poll rendering loop dynamically depending on time taken for processing/rendering of audio data into the available host buffer to avoid underruns and fit processing time into periodic polling time slots. - Add support for time slots logging in Poll mode if PA_WASAPI_LOG_TIME_SLOTS is defined. Fixed audible glitches by preloading the whole host buffer before stream is started. - Fix audible glitches on stream start by preloading the whole host buffer before stream is started.
This commit is contained in:
parent
d7c67287b8
commit
697fcddce5
1 changed files with 314 additions and 281 deletions
|
|
@ -61,6 +61,9 @@
|
||||||
// not change the Event mode to Polling and use the mode which user provided.
|
// not change the Event mode to Polling and use the mode which user provided.
|
||||||
//#define PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER
|
//#define PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER
|
||||||
|
|
||||||
|
//! Poll mode time slots logging.
|
||||||
|
//#define PA_WASAPI_LOG_TIME_SLOTS
|
||||||
|
|
||||||
// WinRT
|
// WinRT
|
||||||
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
||||||
#define PA_WINRT
|
#define PA_WINRT
|
||||||
|
|
@ -741,6 +744,7 @@ typedef struct ThreadIdleScheduler
|
||||||
UINT32 m_resolution; //!< resolution in number of milliseconds
|
UINT32 m_resolution; //!< resolution in number of milliseconds
|
||||||
}
|
}
|
||||||
ThreadIdleScheduler;
|
ThreadIdleScheduler;
|
||||||
|
|
||||||
//! Setup scheduler.
|
//! Setup scheduler.
|
||||||
static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds)
|
static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds)
|
||||||
{
|
{
|
||||||
|
|
@ -754,11 +758,12 @@ static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolut
|
||||||
sched->m_resolution = resolution;
|
sched->m_resolution = resolution;
|
||||||
sched->m_next_sleep = (resolution * 1000) / microseconds;
|
sched->m_next_sleep = (resolution * 1000) / microseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Iterate and check if can sleep.
|
//! Iterate and check if can sleep.
|
||||||
static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched)
|
static inline UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched)
|
||||||
{
|
{
|
||||||
// advance and check if thread can sleep
|
// advance and check if thread can sleep
|
||||||
if (++ sched->m_i == sched->m_next_sleep)
|
if (++sched->m_i == sched->m_next_sleep)
|
||||||
{
|
{
|
||||||
sched->m_i = 0;
|
sched->m_i = 0;
|
||||||
return sched->m_resolution;
|
return sched->m_resolution;
|
||||||
|
|
@ -766,6 +771,81 @@ static UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
typedef struct _SystemTimer
|
||||||
|
{
|
||||||
|
UINT32 granularity;
|
||||||
|
|
||||||
|
} SystemTimer;
|
||||||
|
static LARGE_INTEGER g_SystemTimerFrequency;
|
||||||
|
static BOOL g_SystemTimerUseQpc = FALSE;
|
||||||
|
|
||||||
|
//! Set granularity of the system timer.
|
||||||
|
static BOOL SystemTimer_SetGranularity(SystemTimer *timer, UINT32 granularity)
|
||||||
|
{
|
||||||
|
#ifndef PA_WINRT
|
||||||
|
TIMECAPS caps;
|
||||||
|
|
||||||
|
timer->granularity = granularity;
|
||||||
|
|
||||||
|
if (timeGetDevCaps(&caps, sizeof(caps)) == MMSYSERR_NOERROR)
|
||||||
|
{
|
||||||
|
if (timer->granularity < caps.wPeriodMin)
|
||||||
|
timer->granularity = caps.wPeriodMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeBeginPeriod(timer->granularity) != TIMERR_NOERROR)
|
||||||
|
{
|
||||||
|
PRINT(("SetSystemTimer: timeBeginPeriod(1) failed!\n"));
|
||||||
|
|
||||||
|
timer->granularity = 0;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Restore granularity of the system timer.
|
||||||
|
static void SystemTimer_RestoreGranularity(SystemTimer *timer)
|
||||||
|
{
|
||||||
|
#ifndef PA_WINRT
|
||||||
|
if (timer->granularity != 0)
|
||||||
|
{
|
||||||
|
if (timeEndPeriod(timer->granularity) != TIMERR_NOERROR)
|
||||||
|
{
|
||||||
|
PRINT(("RestoreSystemTimer: timeEndPeriod(1) failed!\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Initialize high-resolution time getter.
|
||||||
|
static void SystemTimer_InitializeTimeGetter()
|
||||||
|
{
|
||||||
|
g_SystemTimerUseQpc = QueryPerformanceFrequency(&g_SystemTimerFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Get high-resolution time in milliseconds (using QPC by default).
|
||||||
|
static inline LONGLONG SystemTimer_GetTime(SystemTimer *timer)
|
||||||
|
{
|
||||||
|
// QPC: https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
|
||||||
|
if (g_SystemTimerUseQpc)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER now;
|
||||||
|
QueryPerformanceCounter(&now);
|
||||||
|
return (now.QuadPart * 1000LL) / g_SystemTimerFrequency.QuadPart;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef PA_WINRT
|
||||||
|
return GetTickCount64();
|
||||||
|
#else
|
||||||
|
return timeGetTime();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
/*static double nano100ToMillis(REFERENCE_TIME ref)
|
/*static double nano100ToMillis(REFERENCE_TIME ref)
|
||||||
{
|
{
|
||||||
|
|
@ -2140,6 +2220,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
|
||||||
// Detect if platform workaround is required
|
// Detect if platform workaround is required
|
||||||
paWasapi->useWOW64Workaround = UseWOW64Workaround();
|
paWasapi->useWOW64Workaround = UseWOW64Workaround();
|
||||||
|
|
||||||
|
// Initialize time getter
|
||||||
|
SystemTimer_InitializeTimeGetter();
|
||||||
|
|
||||||
PaUtil_InitializeStreamInterface( &paWasapi->callbackStreamInterface, CloseStream, StartStream,
|
PaUtil_InitializeStreamInterface( &paWasapi->callbackStreamInterface, CloseStream, StartStream,
|
||||||
StopStream, AbortStream, IsStreamStopped, IsStreamActive,
|
StopStream, AbortStream, IsStreamStopped, IsStreamActive,
|
||||||
GetStreamTime, GetStreamCpuLoad,
|
GetStreamTime, GetStreamCpuLoad,
|
||||||
|
|
@ -5507,19 +5590,9 @@ void _StreamOnStop(PaWasapiStream *stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
PA_THREAD_FUNC ProcThreadEvent(void *param)
|
static BOOL PrepareComPointers(PaWasapiStream *stream, BOOL *threadComInitialized)
|
||||||
{
|
{
|
||||||
PaWasapiHostProcessor processor[S_COUNT];
|
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
DWORD dwResult;
|
|
||||||
PaWasapiStream *stream = (PaWasapiStream *)param;
|
|
||||||
PaWasapiHostProcessor defaultProcessor;
|
|
||||||
BOOL set_event[S_COUNT] = { FALSE, FALSE };
|
|
||||||
BOOL bWaitAllEvents = FALSE;
|
|
||||||
BOOL bThreadComInitialized = FALSE;
|
|
||||||
|
|
||||||
// Notify: state
|
|
||||||
NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If COM is already initialized CoInitialize will either return
|
If COM is already initialized CoInitialize will either return
|
||||||
|
|
@ -5532,22 +5605,61 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
|
||||||
if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE))
|
if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE))
|
||||||
{
|
{
|
||||||
PRINT(("WASAPI: failed ProcThreadEvent CoInitialize"));
|
PRINT(("WASAPI: failed ProcThreadEvent CoInitialize"));
|
||||||
return (UINT32)paUnanticipatedHostError;
|
return FALSE;
|
||||||
}
|
}
|
||||||
if (hr != RPC_E_CHANGED_MODE)
|
if (hr != RPC_E_CHANGED_MODE)
|
||||||
bThreadComInitialized = TRUE;
|
*threadComInitialized = TRUE;
|
||||||
|
|
||||||
// Unmarshal stream pointers for safe COM operation
|
// Unmarshal stream pointers for safe COM operation
|
||||||
hr = UnmarshalStreamComPointers(stream);
|
hr = UnmarshalStreamComPointers(stream);
|
||||||
if (hr != S_OK) {
|
if (hr != S_OK)
|
||||||
PRINT(("Error unmarshaling stream COM pointers. HRESULT: %i\n", hr));
|
{
|
||||||
goto thread_end;
|
PRINT(("WASAPI: Error unmarshaling stream COM pointers. HRESULT: %i\n", hr));
|
||||||
|
CoUninitialize();
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
static void FinishComPointers(PaWasapiStream *stream, BOOL threadComInitialized)
|
||||||
|
{
|
||||||
|
// Release unmarshaled COM pointers
|
||||||
|
ReleaseUnmarshaledComPointers(stream);
|
||||||
|
|
||||||
|
// Cleanup COM for this thread
|
||||||
|
if (threadComInitialized == TRUE)
|
||||||
|
CoUninitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
PA_THREAD_FUNC ProcThreadEvent(void *param)
|
||||||
|
{
|
||||||
|
PaWasapiHostProcessor processor[S_COUNT];
|
||||||
|
HRESULT hr;
|
||||||
|
DWORD dwResult;
|
||||||
|
PaWasapiStream *stream = (PaWasapiStream *)param;
|
||||||
|
PaWasapiHostProcessor defaultProcessor;
|
||||||
|
BOOL setEvent[S_COUNT] = { FALSE, FALSE };
|
||||||
|
BOOL waitAllEvents = FALSE;
|
||||||
|
BOOL threadComInitialized = FALSE;
|
||||||
|
SystemTimer timer;
|
||||||
|
|
||||||
|
// Notify: state
|
||||||
|
NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS);
|
||||||
|
|
||||||
|
// Prepare COM pointers
|
||||||
|
if (!PrepareComPointers(stream, &threadComInitialized))
|
||||||
|
return (UINT32)paUnanticipatedHostError;
|
||||||
|
|
||||||
|
// Request fine (1 ms) granularity of the system timer functions for precise operation of waitable timers
|
||||||
|
SystemTimer_SetGranularity(&timer, 1);
|
||||||
|
|
||||||
// Waiting on all events in case of Full-Duplex/Exclusive mode.
|
// Waiting on all events in case of Full-Duplex/Exclusive mode.
|
||||||
if ((stream->in.clientProc != NULL) && (stream->out.clientProc != NULL))
|
if ((stream->in.clientProc != NULL) && (stream->out.clientProc != NULL))
|
||||||
{
|
{
|
||||||
bWaitAllEvents = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) &&
|
waitAllEvents = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) &&
|
||||||
(stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE);
|
(stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5564,12 +5676,12 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
|
||||||
if (stream->event[S_OUTPUT] == NULL)
|
if (stream->event[S_OUTPUT] == NULL)
|
||||||
{
|
{
|
||||||
stream->event[S_OUTPUT] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
stream->event[S_OUTPUT] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
set_event[S_OUTPUT] = TRUE;
|
setEvent[S_OUTPUT] = TRUE;
|
||||||
}
|
}
|
||||||
if (stream->event[S_INPUT] == NULL)
|
if (stream->event[S_INPUT] == NULL)
|
||||||
{
|
{
|
||||||
stream->event[S_INPUT] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
stream->event[S_INPUT] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
set_event[S_INPUT] = TRUE;
|
setEvent[S_INPUT] = TRUE;
|
||||||
}
|
}
|
||||||
if ((stream->event[S_OUTPUT] == NULL) || (stream->event[S_INPUT] == NULL))
|
if ((stream->event[S_OUTPUT] == NULL) || (stream->event[S_INPUT] == NULL))
|
||||||
{
|
{
|
||||||
|
|
@ -5581,7 +5693,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
|
||||||
if (stream->in.clientProc)
|
if (stream->in.clientProc)
|
||||||
{
|
{
|
||||||
// Create & set handle
|
// Create & set handle
|
||||||
if (set_event[S_INPUT])
|
if (setEvent[S_INPUT])
|
||||||
{
|
{
|
||||||
if ((hr = IAudioClient_SetEventHandle(stream->in.clientProc, stream->event[S_INPUT])) != S_OK)
|
if ((hr = IAudioClient_SetEventHandle(stream->in.clientProc, stream->event[S_INPUT])) != S_OK)
|
||||||
{
|
{
|
||||||
|
|
@ -5602,7 +5714,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
|
||||||
if (stream->out.clientProc)
|
if (stream->out.clientProc)
|
||||||
{
|
{
|
||||||
// Create & set handle
|
// Create & set handle
|
||||||
if (set_event[S_OUTPUT])
|
if (setEvent[S_OUTPUT])
|
||||||
{
|
{
|
||||||
if ((hr = IAudioClient_SetEventHandle(stream->out.clientProc, stream->event[S_OUTPUT])) != S_OK)
|
if ((hr = IAudioClient_SetEventHandle(stream->out.clientProc, stream->event[S_OUTPUT])) != S_OK)
|
||||||
{
|
{
|
||||||
|
|
@ -5640,7 +5752,7 @@ PA_THREAD_FUNC ProcThreadEvent(void *param)
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
// 10 sec timeout (on timeout stream will auto-stop when processed by WAIT_TIMEOUT case)
|
// 10 sec timeout (on timeout stream will auto-stop when processed by WAIT_TIMEOUT case)
|
||||||
dwResult = WaitForMultipleObjects(S_COUNT, stream->event, bWaitAllEvents, 10*1000);
|
dwResult = WaitForMultipleObjects(S_COUNT, stream->event, waitAllEvents, 10*1000);
|
||||||
|
|
||||||
// Check for close event (after wait for buffers to avoid any calls to user
|
// Check for close event (after wait for buffers to avoid any calls to user
|
||||||
// callback when hCloseRequest was set)
|
// callback when hCloseRequest was set)
|
||||||
|
|
@ -5691,11 +5803,10 @@ thread_end:
|
||||||
_StreamOnStop(stream);
|
_StreamOnStop(stream);
|
||||||
|
|
||||||
// Release unmarshaled COM pointers
|
// Release unmarshaled COM pointers
|
||||||
ReleaseUnmarshaledComPointers(stream);
|
FinishComPointers(stream, threadComInitialized);
|
||||||
|
|
||||||
// Cleanup COM for this thread
|
// Restore system timer granularity
|
||||||
if (bThreadComInitialized == TRUE)
|
SystemTimer_RestoreGranularity(&timer);
|
||||||
CoUninitialize();
|
|
||||||
|
|
||||||
// Notify: not running
|
// Notify: not running
|
||||||
stream->running = FALSE;
|
stream->running = FALSE;
|
||||||
|
|
@ -5717,6 +5828,105 @@ thread_error:
|
||||||
goto thread_end;
|
goto thread_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
static UINT32 GetSleepTime(PaWasapiStream *stream, UINT32 sleepTimeIn, UINT32 sleepTimeOut, UINT32 userFramesOut)
|
||||||
|
{
|
||||||
|
UINT32 sleepTime;
|
||||||
|
|
||||||
|
// According to the issue [https://github.com/PortAudio/portaudio/issues/303] glitches may occur when user frames
|
||||||
|
// equal to 1/2 of the host buffer frames, therefore the emperical workaround for this problem is to lower
|
||||||
|
// the sleep time by 2
|
||||||
|
if (userFramesOut != 0)
|
||||||
|
{
|
||||||
|
UINT32 chunks = stream->out.framesPerHostCallback / userFramesOut;
|
||||||
|
if (chunks <= 2)
|
||||||
|
{
|
||||||
|
sleepTimeOut /= 2;
|
||||||
|
PRINT(("WASAPI: underrun workaround, sleep [%d] ms - 1/2 of the user buffer[%d] | host buffer[%d]\n", sleepTimeOut, userFramesOut, stream->out.framesPerHostCallback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose the smallest
|
||||||
|
if ((sleepTimeIn != 0) && (sleepTimeOut != 0))
|
||||||
|
sleepTime = min(sleepTimeIn, sleepTimeOut);
|
||||||
|
else
|
||||||
|
sleepTime = (sleepTimeIn ? sleepTimeIn : sleepTimeOut);
|
||||||
|
|
||||||
|
return sleepTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
static UINT32 ConfigureLoopSleepTimeAndScheduler(PaWasapiStream *stream, ThreadIdleScheduler *scheduler)
|
||||||
|
{
|
||||||
|
UINT32 sleepTime, sleepTimeIn, sleepTimeOut;
|
||||||
|
UINT32 userFramesIn = stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER;
|
||||||
|
UINT32 userFramesOut = stream->out.framesPerBuffer;
|
||||||
|
|
||||||
|
// Adjust polling time for non-paUtilFixedHostBufferSize, input stream is not adjustable as it is being
|
||||||
|
// polled according its packet length
|
||||||
|
if (stream->bufferMode != paUtilFixedHostBufferSize)
|
||||||
|
{
|
||||||
|
userFramesOut = (stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer :
|
||||||
|
stream->out.params.frames_per_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate timeout for the next polling attempt
|
||||||
|
sleepTimeIn = GetFramesSleepTime(userFramesIn, stream->in.wavex.Format.nSamplesPerSec);
|
||||||
|
sleepTimeOut = GetFramesSleepTime(userFramesOut, stream->out.wavex.Format.nSamplesPerSec);
|
||||||
|
|
||||||
|
// WASAPI input packets tend to expire very easily, let's limit sleep time to 2 milliseconds
|
||||||
|
// for all cases. Please propose better solution if any
|
||||||
|
if (sleepTimeIn > 2)
|
||||||
|
sleepTimeIn = 2;
|
||||||
|
|
||||||
|
sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut);
|
||||||
|
|
||||||
|
// Make sure not 0, othervise use ThreadIdleScheduler to bounce between [0, 1] ms to avoid too busy loop
|
||||||
|
if (sleepTime == 0)
|
||||||
|
{
|
||||||
|
sleepTimeIn = GetFramesSleepTimeMicroseconds(userFramesIn, stream->in.wavex.Format.nSamplesPerSec);
|
||||||
|
sleepTimeOut = GetFramesSleepTimeMicroseconds(userFramesOut, stream->out.wavex.Format.nSamplesPerSec);
|
||||||
|
|
||||||
|
sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut);
|
||||||
|
|
||||||
|
// Setup thread sleep scheduler
|
||||||
|
ThreadIdleScheduler_Setup(scheduler, 1, sleepTime/* microseconds here */);
|
||||||
|
sleepTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sleepTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
static inline INT32 GetNextSleepTime(SystemTimer *timer, ThreadIdleScheduler *scheduler, LONGLONG startTime,
|
||||||
|
UINT32 sleepTime)
|
||||||
|
{
|
||||||
|
INT32 nextSleepTime;
|
||||||
|
|
||||||
|
// Get next sleep time
|
||||||
|
if (sleepTime == 0)
|
||||||
|
nextSleepTime = ThreadIdleScheduler_NextSleep(scheduler);
|
||||||
|
else
|
||||||
|
nextSleepTime = sleepTime;
|
||||||
|
|
||||||
|
// Adjust next sleep time dynamically depending on how much time was spent in ProcessOutputBuffer/ProcessInputBuffer
|
||||||
|
// therefore periodicity will not jitter or be increased for the amount of time spent in processing;
|
||||||
|
// example when sleepTime is 10 ms where [] is polling time slot, {} processing time slot:
|
||||||
|
//
|
||||||
|
// [9],{2},[8],{1},[9],{1},[9],{3},[7],{2},[8],{3},[7],{2},[8],{2},[8],{3},[7],{2},[8],...
|
||||||
|
//
|
||||||
|
INT32 procTime = (INT32)(SystemTimer_GetTime(timer) - startTime);
|
||||||
|
nextSleepTime -= procTime;
|
||||||
|
if (nextSleepTime < 0)
|
||||||
|
nextSleepTime = 0;
|
||||||
|
|
||||||
|
#ifdef PA_WASAPI_LOG_TIME_SLOTS
|
||||||
|
printf("{%d},", procTime);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return nextSleepTime;
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
PA_THREAD_FUNC ProcThreadPoll(void *param)
|
PA_THREAD_FUNC ProcThreadPoll(void *param)
|
||||||
{
|
{
|
||||||
|
|
@ -5726,83 +5936,27 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
|
||||||
PaWasapiHostProcessor defaultProcessor;
|
PaWasapiHostProcessor defaultProcessor;
|
||||||
INT32 i;
|
INT32 i;
|
||||||
ThreadIdleScheduler scheduler;
|
ThreadIdleScheduler scheduler;
|
||||||
|
SystemTimer timer;
|
||||||
// Calculate the actual duration of the allocated buffer.
|
LONGLONG startTime;
|
||||||
DWORD sleep_ms = 0;
|
UINT32 sleepTime;
|
||||||
DWORD sleep_ms_in;
|
INT32 nextSleepTime = 0; //! Do first loop without waiting as time could be spent when calling other APIs before ProcessXXXBuffer.
|
||||||
DWORD sleep_ms_out;
|
BOOL threadComInitialized = FALSE;
|
||||||
|
#ifdef PA_WASAPI_LOG_TIME_SLOTS
|
||||||
BOOL bThreadComInitialized = FALSE;
|
LONGLONG startWaitTime;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Notify: state
|
// Notify: state
|
||||||
NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS);
|
NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS);
|
||||||
|
|
||||||
/*
|
// Prepare COM pointers
|
||||||
If COM is already initialized CoInitialize will either return
|
if (!PrepareComPointers(stream, &threadComInitialized))
|
||||||
FALSE, or RPC_E_CHANGED_MODE if it was initialized in a different
|
|
||||||
threading mode. In either case we shouldn't consider it an error
|
|
||||||
but we need to be careful to not call CoUninitialize() if
|
|
||||||
RPC_E_CHANGED_MODE was returned.
|
|
||||||
*/
|
|
||||||
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
||||||
if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE))
|
|
||||||
{
|
|
||||||
PRINT(("WASAPI: failed ProcThreadPoll CoInitialize"));
|
|
||||||
return (UINT32)paUnanticipatedHostError;
|
return (UINT32)paUnanticipatedHostError;
|
||||||
}
|
|
||||||
if (hr != RPC_E_CHANGED_MODE)
|
|
||||||
bThreadComInitialized = TRUE;
|
|
||||||
|
|
||||||
// Unmarshal stream pointers for safe COM operation
|
// Request fine (1 ms) granularity of the system timer functions to guarantee correct logic around WaitForSingleObject
|
||||||
hr = UnmarshalStreamComPointers(stream);
|
SystemTimer_SetGranularity(&timer, 1);
|
||||||
if (hr != S_OK)
|
|
||||||
{
|
|
||||||
PRINT(("Error unmarshaling stream COM pointers. HRESULT: %i\n", hr));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate timeout for next polling attempt.
|
// Claculate sleep time of the processing loop (inside WaitForSingleObject)
|
||||||
sleep_ms_in = GetFramesSleepTime(stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec);
|
sleepTime = ConfigureLoopSleepTimeAndScheduler(stream, &scheduler);
|
||||||
sleep_ms_out = GetFramesSleepTime(stream->out.framesPerBuffer, stream->out.wavex.Format.nSamplesPerSec);
|
|
||||||
sleep_ms_out /= 2; // wait only for half of the buffer
|
|
||||||
|
|
||||||
// WASAPI Input packets tend to expire very easily, let's limit sleep time to 2 milliseconds
|
|
||||||
// for all cases. Please propose better solution if any.
|
|
||||||
if (sleep_ms_in > 2)
|
|
||||||
sleep_ms_in = 2;
|
|
||||||
|
|
||||||
// Adjust polling time for non-paUtilFixedHostBufferSize. Input stream is not adjustable as it is being
|
|
||||||
// polled according its packet length.
|
|
||||||
if (stream->bufferMode != paUtilFixedHostBufferSize)
|
|
||||||
{
|
|
||||||
//sleep_ms_in = GetFramesSleepTime((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->in.params.frames_per_buffer), stream->in.wavex.Format.nSamplesPerSec);
|
|
||||||
sleep_ms_out = GetFramesSleepTime((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->out.params.frames_per_buffer), stream->out.wavex.Format.nSamplesPerSec);
|
|
||||||
sleep_ms_out /= 2; // wait only for half of the buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Make sure not 0, othervise use ThreadIdleScheduler
|
|
||||||
if (sleep_ms == 0)
|
|
||||||
{
|
|
||||||
sleep_ms_in = GetFramesSleepTimeMicroseconds(stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER, stream->in.wavex.Format.nSamplesPerSec);
|
|
||||||
sleep_ms_out = GetFramesSleepTimeMicroseconds((stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : stream->out.params.frames_per_buffer), stream->out.wavex.Format.nSamplesPerSec);
|
|
||||||
sleep_ms_out /= 2; // wait only for half of the buffer
|
|
||||||
|
|
||||||
// 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 */);
|
|
||||||
sleep_ms = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup data processors
|
// Setup data processors
|
||||||
defaultProcessor.processor = WaspiHostProcessingLoop;
|
defaultProcessor.processor = WaspiHostProcessingLoop;
|
||||||
|
|
@ -5835,14 +5989,17 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
|
||||||
{
|
{
|
||||||
if (stream->bufferMode == paUtilFixedHostBufferSize)
|
if (stream->bufferMode == paUtilFixedHostBufferSize)
|
||||||
{
|
{
|
||||||
if (frames >= stream->out.framesPerBuffer)
|
// It is important to preload whole host buffer to avoid underruns/glitches when stream is started,
|
||||||
|
// for more details see the discussion: https://github.com/PortAudio/portaudio/issues/303
|
||||||
|
while (frames >= stream->out.framesPerBuffer)
|
||||||
{
|
{
|
||||||
frames = stream->out.framesPerBuffer;
|
if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK)
|
||||||
|
|
||||||
if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK)
|
|
||||||
{
|
{
|
||||||
LogHostError(hr); // not fatal, just log
|
LogHostError(hr); // not fatal, just log
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frames -= stream->out.framesPerBuffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -5886,16 +6043,20 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
|
||||||
// Notify: state
|
// Notify: state
|
||||||
NotifyStateChanged(stream, paWasapiStreamStateThreadStart, ERROR_SUCCESS);
|
NotifyStateChanged(stream, paWasapiStreamStateThreadStart, ERROR_SUCCESS);
|
||||||
|
|
||||||
|
#ifdef PA_WASAPI_LOG_TIME_SLOTS
|
||||||
|
startWaitTime = SystemTimer_GetTime(&timer);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!PA_WASAPI__IS_FULLDUPLEX(stream))
|
if (!PA_WASAPI__IS_FULLDUPLEX(stream))
|
||||||
{
|
{
|
||||||
UINT32 next_sleep = sleep_ms;
|
|
||||||
|
|
||||||
// Processing Loop
|
// Processing Loop
|
||||||
while (WaitForSingleObject(stream->hCloseRequest, next_sleep) == WAIT_TIMEOUT)
|
while (WaitForSingleObject(stream->hCloseRequest, nextSleepTime) == WAIT_TIMEOUT)
|
||||||
{
|
{
|
||||||
// Get next sleep time
|
startTime = SystemTimer_GetTime(&timer);
|
||||||
if (sleep_ms == 0)
|
|
||||||
next_sleep = ThreadIdleScheduler_NextSleep(&scheduler);
|
#ifdef PA_WASAPI_LOG_TIME_SLOTS
|
||||||
|
printf("[%d|%d],", nextSleepTime, (INT32)(startTime - startWaitTime));
|
||||||
|
#endif
|
||||||
|
|
||||||
for (i = 0; i < S_COUNT; ++i)
|
for (i = 0; i < S_COUNT; ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -5919,12 +6080,13 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
|
||||||
// Output stream
|
// Output stream
|
||||||
case S_OUTPUT: {
|
case S_OUTPUT: {
|
||||||
|
|
||||||
UINT32 frames;
|
UINT32 framesAvail;
|
||||||
|
|
||||||
if (stream->renderClient == NULL)
|
if (stream->renderClient == NULL)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Get available frames
|
// Get available frames
|
||||||
if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) != S_OK)
|
if ((hr = _PollGetOutputFramesAvailable(stream, &framesAvail)) != S_OK)
|
||||||
{
|
{
|
||||||
LogHostError(hr);
|
LogHostError(hr);
|
||||||
goto thread_error;
|
goto thread_error;
|
||||||
|
|
@ -5933,21 +6095,33 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
|
||||||
// Output data to the user callback
|
// Output data to the user callback
|
||||||
if (stream->bufferMode == paUtilFixedHostBufferSize)
|
if (stream->bufferMode == paUtilFixedHostBufferSize)
|
||||||
{
|
{
|
||||||
while (frames >= stream->out.framesPerBuffer)
|
UINT32 framesProc = stream->out.framesPerBuffer;
|
||||||
|
|
||||||
|
// If we got less frames avoid sleeping again as it might be the corner case and buffer
|
||||||
|
// has sufficient number of frames now, in case 'out.framesPerBuffer' is 1/2 of the host
|
||||||
|
// buffer sleeping again may cause underruns. Do short busy waiting (normally might take
|
||||||
|
// 1-2 iterations)
|
||||||
|
if (framesAvail < framesProc)
|
||||||
{
|
{
|
||||||
if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK)
|
nextSleepTime = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (framesAvail >= framesProc)
|
||||||
|
{
|
||||||
|
if ((hr = ProcessOutputBuffer(stream, processor, framesProc)) != S_OK)
|
||||||
{
|
{
|
||||||
LogHostError(hr);
|
LogHostError(hr);
|
||||||
goto thread_error;
|
goto thread_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
frames -= stream->out.framesPerBuffer;
|
framesAvail -= framesProc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (frames != 0)
|
if (framesAvail != 0)
|
||||||
{
|
{
|
||||||
if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK)
|
if ((hr = ProcessOutputBuffer(stream, processor, framesAvail)) != S_OK)
|
||||||
{
|
{
|
||||||
LogHostError(hr);
|
LogHostError(hr);
|
||||||
goto thread_error;
|
goto thread_error;
|
||||||
|
|
@ -5957,143 +6131,29 @@ PA_THREAD_FUNC ProcThreadPoll(void *param)
|
||||||
break; }
|
break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#if 0
|
|
||||||
// Processing Loop
|
|
||||||
while (WaitForSingleObject(stream->hCloseRequest, 1) == WAIT_TIMEOUT)
|
|
||||||
{
|
|
||||||
UINT32 i_frames = 0, i_processed = 0;
|
|
||||||
BYTE *i_data = NULL, *o_data = NULL, *o_data_host = NULL;
|
|
||||||
DWORD i_flags = 0;
|
|
||||||
UINT32 o_frames = 0;
|
|
||||||
|
|
||||||
// get host input buffer
|
|
||||||
if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK)
|
|
||||||
{
|
|
||||||
if (hr == AUDCLNT_S_BUFFER_EMPTY)
|
|
||||||
continue; // no data in capture buffer
|
|
||||||
|
|
||||||
LogHostError(hr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get available frames
|
|
||||||
if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK)
|
|
||||||
{
|
|
||||||
// release input buffer
|
|
||||||
IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0);
|
|
||||||
|
|
||||||
LogHostError(hr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// process equal ammount of frames
|
|
||||||
if (o_frames >= i_frames)
|
|
||||||
{
|
|
||||||
// process input ammount of frames
|
|
||||||
UINT32 o_processed = i_frames;
|
|
||||||
|
|
||||||
// get host output buffer
|
|
||||||
if ((hr = IAudioRenderClient_GetBuffer(stream->procRCClient, o_processed, &o_data)) == S_OK)
|
|
||||||
{
|
|
||||||
// processed amount of i_frames
|
|
||||||
i_processed = i_frames;
|
|
||||||
o_data_host = o_data;
|
|
||||||
|
|
||||||
// convert output mono
|
|
||||||
if (stream->out.monoMixer)
|
|
||||||
{
|
|
||||||
UINT32 mono_frames_size = o_processed * (stream->out.wavex.Format.wBitsPerSample / 8);
|
|
||||||
// expand buffer
|
|
||||||
if (mono_frames_size > stream->out.monoBufferSize)
|
|
||||||
{
|
|
||||||
stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size));
|
|
||||||
if (stream->out.monoBuffer == NULL)
|
|
||||||
{
|
|
||||||
// release input buffer
|
|
||||||
IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0);
|
|
||||||
// release output buffer
|
|
||||||
IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0);
|
|
||||||
|
|
||||||
LogPaError(paInsufficientMemory);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace buffer pointer
|
|
||||||
o_data = (BYTE *)stream->out.monoBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert input mono
|
|
||||||
if (stream->in.monoMixer)
|
|
||||||
{
|
|
||||||
UINT32 mono_frames_size = i_processed * (stream->in.wavex.Format.wBitsPerSample / 8);
|
|
||||||
// expand buffer
|
|
||||||
if (mono_frames_size > stream->in.monoBufferSize)
|
|
||||||
{
|
|
||||||
stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size));
|
|
||||||
if (stream->in.monoBuffer == NULL)
|
|
||||||
{
|
|
||||||
// release input buffer
|
|
||||||
IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0);
|
|
||||||
// release output buffer
|
|
||||||
IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0);
|
|
||||||
|
|
||||||
LogPaError(paInsufficientMemory);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mix 2 to 1 input channels
|
|
||||||
stream->in.monoMixer(stream->in.monoBuffer, i_data, i_processed);
|
|
||||||
|
|
||||||
// replace buffer pointer
|
|
||||||
i_data = (BYTE *)stream->in.monoBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// process
|
|
||||||
processor[S_FULLDUPLEX].processor(i_data, i_processed, o_data, o_processed, processor[S_FULLDUPLEX].userData);
|
|
||||||
|
|
||||||
// mix 1 to 2 output channels
|
|
||||||
if (stream->out.monoBuffer)
|
|
||||||
stream->out.monoMixer(o_data_host, stream->out.monoBuffer, o_processed);
|
|
||||||
|
|
||||||
// release host output buffer
|
|
||||||
if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, o_processed, 0)) != S_OK)
|
|
||||||
LogHostError(hr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (stream->out.shareMode != AUDCLNT_SHAREMODE_SHARED)
|
|
||||||
LogHostError(hr); // be silent in shared mode, try again next time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// release host input buffer
|
|
||||||
if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, i_processed)) != S_OK)
|
|
||||||
{
|
|
||||||
LogHostError(hr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Processing Loop
|
|
||||||
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;
|
|
||||||
DWORD i_flags = 0;
|
|
||||||
UINT32 o_frames = 0;
|
|
||||||
|
|
||||||
// Get next sleep time
|
// Get next sleep time
|
||||||
if (sleep_ms == 0)
|
nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime);
|
||||||
{
|
|
||||||
next_sleep = ThreadIdleScheduler_NextSleep(&scheduler);
|
#ifdef PA_WASAPI_LOG_TIME_SLOTS
|
||||||
|
startWaitTime = SystemTimer_GetTime(&timer);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Processing Loop (full-duplex)
|
||||||
|
while (WaitForSingleObject(stream->hCloseRequest, nextSleepTime) == WAIT_TIMEOUT)
|
||||||
|
{
|
||||||
|
UINT32 i_frames = 0, i_processed = 0, o_frames = 0;
|
||||||
|
BYTE *i_data = NULL, *o_data = NULL, *o_data_host = NULL;
|
||||||
|
DWORD i_flags = 0;
|
||||||
|
|
||||||
|
startTime = SystemTimer_GetTime(&timer);
|
||||||
|
|
||||||
|
#ifdef PA_WASAPI_LOG_TIME_SLOTS
|
||||||
|
printf("[%d|%d],", nextSleepTime, (INT32)(startTime - startWaitTime));
|
||||||
|
#endif
|
||||||
|
|
||||||
// get available frames
|
// get available frames
|
||||||
if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK)
|
if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK)
|
||||||
|
|
@ -6216,8 +6276,14 @@ fd_release_buffer_in:
|
||||||
if (i_processed == 0)
|
if (i_processed == 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get next sleep time
|
||||||
|
nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime);
|
||||||
|
|
||||||
|
#ifdef PA_WASAPI_LOG_TIME_SLOTS
|
||||||
|
startWaitTime = SystemTimer_GetTime(&timer);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_end:
|
thread_end:
|
||||||
|
|
@ -6226,11 +6292,10 @@ thread_end:
|
||||||
_StreamOnStop(stream);
|
_StreamOnStop(stream);
|
||||||
|
|
||||||
// Release unmarshaled COM pointers
|
// Release unmarshaled COM pointers
|
||||||
ReleaseUnmarshaledComPointers(stream);
|
FinishComPointers(stream, threadComInitialized);
|
||||||
|
|
||||||
// Cleanup COM for this thread
|
// Restore system timer granularity
|
||||||
if (bThreadComInitialized == TRUE)
|
SystemTimer_RestoreGranularity(&timer);
|
||||||
CoUninitialize();
|
|
||||||
|
|
||||||
// Notify: not running
|
// Notify: not running
|
||||||
stream->running = FALSE;
|
stream->running = FALSE;
|
||||||
|
|
@ -6269,35 +6334,3 @@ void PaWasapi_FreeMemory(void *ptr)
|
||||||
{
|
{
|
||||||
free(ptr);
|
free(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endif //VC 2005
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
if(bFirst) {
|
|
||||||
float masteur;
|
|
||||||
hr = stream->outVol->GetMasterVolumeLevelScalar(&masteur);
|
|
||||||
if (hr != S_OK)
|
|
||||||
LogHostError(hr);
|
|
||||||
float chan1, chan2;
|
|
||||||
hr = stream->outVol->GetChannelVolumeLevelScalar(0, &chan1);
|
|
||||||
if (hr != S_OK)
|
|
||||||
LogHostError(hr);
|
|
||||||
hr = stream->outVol->GetChannelVolumeLevelScalar(1, &chan2);
|
|
||||||
if (hr != S_OK)
|
|
||||||
LogHostError(hr);
|
|
||||||
|
|
||||||
BOOL bMute;
|
|
||||||
hr = stream->outVol->GetMute(&bMute);
|
|
||||||
if (hr != S_OK)
|
|
||||||
LogHostError(hr);
|
|
||||||
|
|
||||||
stream->outVol->SetMasterVolumeLevelScalar(0.5, NULL);
|
|
||||||
stream->outVol->SetChannelVolumeLevelScalar(0, 0.5, NULL);
|
|
||||||
stream->outVol->SetChannelVolumeLevelScalar(1, 0.5, NULL);
|
|
||||||
stream->outVol->SetMute(FALSE, NULL);
|
|
||||||
bFirst = FALSE;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue