Yet Another (WA)SAPI New Technology (YASAPI/NT) Output Plugin for Winamp
Documenation v0.1

Copyright © 2015-2018 by Peter Belkner (http://home.snafu.de/pbelkner/)

Yet Another (WA)SAPI New Technology (YASAPI/NT) Output Plugin for Winamp (out_yasapi-nt) is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

out_yasapi-nt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with out_yasapi-nt.  If not, see <http://www.gnu.org/licenses/>.

Last generated 200603-1550.

Nanos gigantum humeris insidentes: This project is dedicated to my European heritage. It is strictly to be understood as a statement against the "sweet" liberal lie of "multiculturalism" which is going to destroy Europe as we know it, in particular against the Merkel regime selling out Europe for nothing as we watch. #TeamWhite

PLEASE NOTE THAT THIS PROJECT IS AN EXPERIMENTAL RATHER THEN AN INDUSTRIAL STRENGTH EFFORT. THIS PROJECT IS NOT FOR YOU. IT IS FOR ME IN ORDER TO LEARN SOMETHING. IF THER'S SOMETHING ALONG THE WAY I CAN DO FOR YOU THAT'S GREAT!

The UML-diagrams are created by means of the impressive plantuml tool.

Note: This document in some details might be ahead of time


Table of Contents

1.   Preface
2.   Class Player
    2.1.   Overview to Class Player
3.   Class Cache
    3.1.   Overview to Class Cache
4.   Class Engine
    4.1.   Overview to Class Engine
    4.2.   Overview to the States of Class Engine
    4.3.   State eEngineBase
    4.4.   State eEngineInitialized
    4.5.   State eEngineStarted
5.   Class Engine's Render/Server Thread
    5.1.   Requesting a State Transition from Class Engine's Render/Server Thread
    5.2.   Class Engine's Render/Server Thread's Procedure
    5.3.   Creating Class Engine's Render/Server Thread
    5.4.   Stopping and Destroying Class Engine's Render/Server Thread
    5.5.   Class Engine's Render/Server Thread's Utility Structures and Functions
        5.5.1.   Utility Strcture Thread
        5.5.2.   Class Engine's Render/Server Thread's Utility Function _ThreadCreate
        5.5.3.   Class Engine's Render/Server Thread's Utility Function _ThreadDestroy
        5.5.4.   Class Engine's Render/Server Thread's Utility Function _ThreadWait
        5.5.5.   Class Engine's Render/Server Thread's Utility Function _ThreadHandshake
        5.5.6.   Class Engine's Render/Server Thread's Utility Function _ThreadRequestStart
        5.5.7.   Class Engine's Render/Server Thread's Utility Function _ThreadRequestKill
        5.5.8.   Class Engine's Render/Server Thread's Utility Function _ThreadRender

List of Figures

2.1   Class Player
3.1   Class Cache
4.1   Class Engine
4.2   Overview to the States of Class Engine
4.3   A User Playing a Gapless Sequence
4.4   State eEngineBase
4.5   State eEngineInitialized
4.6   State eEngineStarted

List of Source Code Listings

5.1   Requesting a State Transition from the Render/Server Thread: Function EngineRequestState from "yasapi_engine_common.c"
5.2   The Heart of YASAPI/NT - The Render/Server Thread's Procedure: Excerpt from Function EngineThreadProc from "yasapi_thread.c"
5.3   Creating Class Engine's Render/Server Thread: Function EngineCreateThread from "yasapi_engine_common.c"
5.4   Stopping and Destroying Class Engine's Render/Server Thread: Excerpt from Function EngineKill from "yasapi_engine_common.c"
5.5   Utility Structure Thread local to "yasapi_thread.c"
5.6   Class Engine's Render/Server Thread's Utility Function _ThreadCreate from "yasapi_thread.c"
5.7   Class Engine's Render/Server Thread's Utility Function _ThreadDestroy from "yasapi_thread.c"
5.8   Class Engine's Render/Server Thread's Utility Function _ThreadWait from "yasapi_thread.c"
5.9   Class Engine's Render/Server Thread's Utility Function _ThreadHandshake from "yasapi_thread.c"
5.10   Class Engine's Render/Server Thread's Utility Function _ThreadRequestStart from "yasapi_thread.c"
5.11   Class Engine's Render/Server Thread's Utility Function _ThreadRequestKill from "yasapi_thread.c"
5.12   Class Engine's Render/Server Thread's Utility Function _ThreadRender from "yasapi_thread.c"

List of Tables

4.1   Overview to the States of Class Engine
4.2   Inner Transitions of State eEngineBase
4.3   Inner Transitions of State eEngineInitialized
4.4   Inner Transitions of State eEngineStarted
5.5   Notes to Function EngineRequestState
5.6   Notes to the Excerpt of Function EngineThreadProc
5.7   Notes to Function EngineCreateThread
5.8   Notes to the Excerpt of Function EngineKill
5.9   Notes to Function _ThreadCreate
5.10   Notes to Function _ThreadDestroy
5.11   Notes to Function _ThreadWait
5.12   Notes to Function _ThreadHandshake
5.13   Notes to Function _ThreadRequestStart
5.14   Notes to Function _ThreadRequestKill
5.15   Notes to Function _ThreadRender
Note:    You may toggle the size of an image by clicking on it.

1. Preface

The aim of this document is to give a rough overview on the ideas the YASAPI/NT plug-in is based on. This document to some extend describes YASAPI/NT v2.2.0-β5. Later we decided to work out the concept of a state more sharply and moved on.

2. Class Player

2.1. Overview to Class Player


Figure 2.1: Class Player.

3. Class Cache

3.1. Overview to Class Cache

The aim of class Cache is to cache several objects and to provide an interface to the configuration dialog.


Figure 3.1: Class Cache.

4. Class Engine

4.1. Overview to Class Engine

The aim of class Engine is to implement a WASAPI playback engine.


Figure 4.1: Class Engine.

4.2. Overview to the States of Class Engine

At the core of the plug-in is class Engine. The main idea is to implement class Engine in such a way that at each point in time it is in a definite state.


Figure 4.2: Overview to the States of Class Engine.

State   Transition to State Action Taken by
Winamp's / the Active Input Plug-in's Threads
(mostly triggered by User Intervention)
YASAPI/NT's Render Thread
(at constant Time Intervals called a Playback Cycle)
start end When exit and no track has ever been played: no operation.  
eEngineBase When the first track is played: acquire several non-WASAPI objects from Windows persisting during the plug-in's lifetime (including starting the render thread.)  
eEngineBase end When exit: release the non-WASAPI objects.  
eEngineInitialized When next track, i.e. this plug-in's Open method is called: from an initially cached (by means of class Cache) instance of IMMDevice, aquire and initialize an instance of IAudioClient along with an instance of YASAPI/NT's buffer, i.e. class Ring.  
eEngineInitialized eEngineBase When stop or exit: release the instance of IAudioClient along with the instance of YASAPI/NT's buffer, i.e. class Ring.  
eEngineInitialized This plug-in's Write method is called and the minimum amount of audio frames is not already written to YASAPI/NT's buffer.  
eEngineStarted This plug-in's Write method is called and the minimum amount of audio frames is written to YASAPI/NT's buffer: dispatch the request for entering state eEngineStarted to the render thread. The minimum amount of audio frames is written to YASAPI/NT's buffer: from the instance of IAudioClient, acquire an instance of IAudioRenderClient.
eEngineStarted eEngineInitialized   When stop or exit: release the instance of IAudioRenderClient.
eEngineStarted This plug-in's Write method (writing to YASAPI/NT's buffer) is periodically called by the active input plug-in in a time interval which is
  • vastly fluctuating (at least compared to the high accuracy of the render thread's playback cycle), and
  • by no means correlated to the the render thread's playback cycle.
Note: These two are the main reasons for letting YASAPI/NT have its own buffer, i.e. for decoupling calls to this plug-in's Write method from really writing to WASAPI's buffer.
While decoding of the active input plug-in hasn't come to an end: with each playback cycle play, i.e.
  • read a certain amount of audio frames from YASAPI/NT's buffer, and
  • render that amount of audio frames by means of IAudioRenderClient's instance (in case no sufficient audio frames are available insert silence and count the event as dropout)
eEngineDraining This plug-in's IsPlaying method is called for the first time telling YASAP/NT that
  • decoding of the current track has come to an end,
  • no further calls to the Write method will follow, i.e.
  • the active input plugin-in is waiting for YASAPI/NT to be drained (for that means the active input plug-in periodically re-issues the call if needed.)
When decoding of the active input plug-in has come to an end (signaled due to the active input plug-in by calling this ouput plug-in's IsPlaying method for the first time).
eEngineDraining eEngineDraining   While YASAPI/NT's buffer is not empty: with each playback cycle play, i.e.
  • read a certain amount of audio frames from YASAPI/NT's buffer, and
  • render that amount of audio frames by means of IAudioRenderClient's instance
eEngineStarted   When in gapless mode, the active input plug-in is able to issue the next Open() call before YASAP/NT's buffer is drained, and the input format hasn't changed.
Note: This is a perfect example of contradicting constraints:
  • when in gapless mode you might want to have YASAPI/NT's buffer as large as possible in order to make sure that it's not already drained before the active input plug-in is able to issue the next Open() call, and
  • for low latency playback you need YASAPI/NT's buffer as small as possible.
The only way to resolve this is to put the burdon upon you: you need to configure this plug-in according to you're own preferences.
eEngineEot   When YASAPI/NT's buffer is empty.
Note: In case of double bufferd playback (i.e. exclusive pulled playback) an artificial (i.e. non WASAPI supported) extra cycle needs to be added (cf. the usage of a Waitable Timer Object by Microsoft's text-book example for rendering Exclusive-Mode Streams.) That's the reason why state eEngineEot is really needed because we cannnot in each case immediately go to state eEngineBase.
eEngineEot eEngineBase   Unconditionally.
Table 4.1: Overview to the States of Class Engine.


Figure 4.3: A User Playing a Gapless Sequence.

4.3. State eEngineBase


Figure 4.4: State eEngineBase.

State   Transition to State Action Taken by
Winamp's / the Active Input Plug-in's Threads
(the Clients/Producers)
YASAPI/NT's Render Thread
(the Server/Consumer)
start eEngineThreadRunning When destruction, i.e. when coming from state eEngineInitialized: no operation.  
eEngineCriticalSection When creation: acquire Engine::cs by means of InitializeCriticalSection.  
eEngineCriticalSection end When destruction: release Engine::cs by means of DeleteCriticalSection.  
eEngineTimerEvent When creation: acquire Engine::timer.hEvent by means of CreateEvent.  
eEngineTimerEvent eEngineCriticalSection When destruction: release Engine::timer.hEvent by means of CloseHandle.  
eEngineTimer When creation: Acquire Engine:timer.hTimer by means of CreateWaitableTimer.  
eEngineTimer eEngineTimerEvent When destruction: release Engine::timer.hTimer by means of CloseHandle.  
eEngineProducer When creation: acquire Engine::thread.hProducer by means of CreateEvent.
Note: It's
  • Engine::thread.hProducer,
  • Engine::timer.hTimerEvent, and
  • Engine::timer.hTimer
where the render thread (i.e. the server/consumer thread) in a forever loop waits on by means of WaitForMultipleObjects for some request it might serve. It will wake up when the clients/producers call SetEvent on Engine::thread.hProducer in order to dispatch some request.
 
eEngineProducer eEngineTimer When destruction: release Engine::thread.hProducer by means of CloseHandle.
eEngineConsumer When creation: acquire Engine::thread.hConsumer by means of CreateEvent.
Note: It's Engine::thread.hConsumer where the client/producer threads wait on by means of WaitForSingleObject until they're signaled by the the render thread (i.e. the server/consumer thread). They will wake up when the the render thread (i.e. the server/consumer thread) call SetEvent on Engine::thread.hConsumer in order to hand-shake having received some request.
 
eEngineConsumer eEngineProducer When destruction: release Engine::thread.hConsumer by means of CloseHandle.  
eEngineEot When creation: acquire Engine::thread.hEot by means of CreateEvent.  
eEngineEot eEngineConsumer When destruction: release Engine::thread.hEot by means of CloseHandle.
eEngineApi When creation: acquire Engine::thread.hApi by means of CreateEvent.  
eEngineApi eEngineEot When destruction: release Engine::thread.hApi by means of CloseHandle.  
eEngineThread When creation: acquire Engine::thread.hThread by means of CreateThread (i.e. start the render thread.)  
eEngineThread eEngineApi When destruction:
  • wait on Engine::thread.hThread by means of WaitForSingleObject until the thread has vanished, and
  • release Engine::thread.hThread by means of CloseHandle.
eEngineThreadRunning When creation: wait on Engine:thread.hConsumer by means of WaitForSingleObject that the render thread (i.e. the server/consumer thread) signals that it has entered the eEngineThreadRunning state and hence is ready to serve requests from clients/producers. Before entering a forever loop waiting for client's/producer's requests to serve enter the eEngineThreadRunning state and signal Engine::thread.hConsumer of that event by means of SetEvent.
eEngineThreadRunning eEngineThread When destruction:
  • signal the render thread (i.e. the server/consumer thread) a eRequestExit by means of SetEvent on Engine::thread.hProducer, and
  • wait on Engine::thread.hConsumer by means of WaitForSingleObject that the render thread (i.e. the server/consumer thread) signals that it is going to quit, i.e. that it has made the transition to state eEngineThread.
In it's forever loop the the render thread (i.e. the server/consumer thread) most of the time spends with WaitForMultipleObjects on
  • Engine::thread.hProducer,
  • Engine::timer.hTimerEvent, and
  • Engine::timer.hTimer.
When woke-up on Engine::thread.hProducer and signaled with a eRequestExit:
  • break out of the forever loop,
  • go to state eEngineThread, and
  • signal the client/producer thread of the state transition by means of SetEvent on Engine::hConsumer.
end When creation: no operation.  
Table 4.2: Inner Transitions of State eEngineBase.

4.4. State eEngineInitialized


Figure 4.5: State eEngineInitialized.

State   Transition to State Action Taken by Winamp's / the Active Input Plug-in's Threads
(the Clients/Producers)
start eEngineRing When stop or exit: no operation.
eEngineClose When next track: no operation.
eEngineClose end When stop or exit: no operation.
eEngineAudioClientInititial When next track: from an initially cached (by means of class Cache) instance of IMMDevice, aquire and initialize an instance of IAudioClient.
eEngineAudioClientInititial eEngineClose When stop or exit: no operation (because the instance of IAudioClient is already released.)
eEngineRing When next track and for the current gapless sequence the active input plug-in calls this output plug-in's Write method for the first time: initialize the instance of YASAPI/NT's buffer (i.e. class Ring) considering parmeter len (i.e. ceil YASAPI/NT' buffer size to a multiple of parameter len in order to avoid the following deadlock: the active input plug-in tries to Write a block of audio samples of size len needed to reach the minimum amount and this output plug-in avoids doing it because it doesn't fit even if there's still room left - too less. Note: prior and most likely as well hijacked versions of this plug-in where vulnerable for this kind of deadlock you might have noticed when playback doesn't start without seeing an obvious reason.)
eEngineRing eEngineAudioClientInititial When stop or exit: destroy the instance of YASAPI/NT's buffer, i.e. class Ring.
end When next track: no operation.
Table 4.3: Inner Transitions of State eEngineInitialized.

4.5. State eEngineStarted


Figure 4.6: State eEngineStarted.

State   Transition to State Action Taken by
Winamp's / the Active Input Plug-in's Thread
(mostly triggered by User Intervention)
YASAPI/NT's Render Thread
start eEngineTimerStarted   When destruction.
eEnginePauseDisconnected When within the current gapless session the active input plug-in calls for the first time Write with the consequence that the minimum number of audio frames written to YASAPI/NT's buffer is reached: dispatch the request to go to state eEnginePauseDisconnected to the the render thread (i.e. the server/consumer thread):
  • SetEvent on Engine::thread.hProducer in order to wake-up the the render thread (i.e. the server/consumer thread), and
  • WaitForSingleObject on Engine::thread.hConsumer in order to wait for the the render thread (i.e. the server/consumer thread) to hand-shake.
In it's forever loop the render thread (i.e. the server/consumer thread) most of the time spends with WaitForMultipleObjects on
  • Engine::thread.hProducer,
  • Engine::timer.hTimer, and
  • Engine::timer.hTimerEvent.
When woke-up on Engine::thread.hProducer and signaled with a eRequestStart:
eEnginePauseDisconnected end   When destruction.
eEngineAudioClient   When creation.
eEngineAudioClient eEnginePauseDisconnected   When destruction.
eEngineVolume   When creation.
eEngineVolume eEngineAudioClient   When destruction.
eEngineAudioClock   When creation.
eEngineAudioClock eEngineVolume   When destruction.
eEngineAudioRenderClient   When creation.
eEngineAudioRenderClient eEngineAudioClock   When destruction.
eEnginePauseConnected   When creation.
eEnginePauseConnected eEngineAudioRenderClient   When destruction.
eEngineAudioClientStarted   When creation.
eEngineAudioClientStarted eEnginePauseConnected   When destruction.
eEngineTimerStarted   When creation.
eEngineTimerStarted eEnginePauseDisconnected   When destruction.
end   When creation.
Table 4.4: Inner Transitions of State eEngineStarted.

5. Class Engine's Render/Server Thread

5.1. Requesting a State Transition from Class Engine's Render/Server Thread


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
  

void EngineRequestState(Engine *pEngine, EEngine eState)
{
  pEngine->thread.eStateRequest=eState;
  SetEvent(pEngine->thread.hProducer);

  while (pEngine->eState!=eState) {
    ResetEvent(pEngine->thread.hConsumer); // {
    LeaveCriticalSection(&pEngine->cs); // {
    WaitForSingleObject(pEngine->thread.hConsumer,INFINITE);
    EnterCriticalSection(&pEngine->cs); // }
  }
}
Listing 5.1: Requesting a State Transition from the Render/Server Thread: Function EngineRequestState from "yasapi_engine_common.c".

Lines    Remark
3 Place the state the render/server thread should enter as a request into commonly used memory.
4 Signal the render/server thread of that request.
6-10 In a loop wait until the render/server thread signals that the requested state is entered.
Table 5.5: Notes to Function EngineRequestState.

5.2. Class Engine's Render/Server Thread's Procedure


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
  

DWORD WINAPI EngineThreadProc(LPVOID lpParameter)
{
  Engine *pEngine=lpParameter;
  Thread thread;
  DWORD dwWait;

  _ThreadCreate(&thread,pEngine); // {

  for (;;) {
    dwWait=_ThreadWait(&thread);

    switch (dwWait) {
    case WAIT_OBJECT_0+eThreadProducer:
      switch (pEngine->thread.eStateRequest) {
      case eEngineThread:
        goto exit;
      case eEngineThreadRunning:
        // The request is issued when not playing.
        _ThreadRequestKill(&thread,eEngineThreadRunning);
        continue;
      case eEnginePauseDisconnected:
      case eEnginePauseConnected:
        // The request is deferred.
        continue;
      case eEngineStarted:
        // The request is executed immediately.
        if (_ThreadRequestStart(&thread)<0)
          goto exit;

        continue;
      default:
        continue;
      }

      continue;
    case WAIT_OBJECT_0+eThreadTimerEvent:
    case WAIT_OBJECT_0+eThreadTimer:
      if (_ThreadRender(&thread)<0)
        goto exit;

      continue;
    default:
      continue;
    }
  }
exit:
  _ThreadDestroy(&thread); // }
  return 0ul;
}
Listing 5.2: The Heart of YASAPI/NT - The Render/Server Thread's Procedure: Excerpt from Function EngineThreadProc from "yasapi_thread.c".

Lines    Remark
to be continued ...
Table 5.6: Notes to the Excerpt of Function EngineThreadProc.

5.3. Creating Class Engine's Render/Server Thread


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
20:
31:
32:
33:
  

int EngineCreateThread(Engine *pEngine)
{
  int code=-1;

  EnterCriticalSection(&pEngine->cs); // {
  pEngine->thread.eStateRequest=eEngineNull;

  pEngine->thread.hThread=CreateThread(
    NULL,     // _In_opt_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
    0ul,      // _In_      SIZE_T                 dwStackSize,
    EngineThreadProc,
              // _In_      LPTHREAD_START_ROUTINE lpStartAddress,
    pEngine,  // _In_opt_  LPVOID                 lpParameter,
    0ul,      // _In_      DWORD                  dwCreationFlags,
    NULL      // _Out_opt_ LPDWORD                lpThreadId
  );

  if (!pEngine->thread.hThread)
    goto exit;

  ++pEngine->eState;

  while (pEngine->eState<eEngineThreadRunning) {
    ResetEvent(pEngine->thread.hConsumer); // {
    LeaveCriticalSection(&pEngine->cs); // {
    WaitForSingleObject(pEngine->thread.hConsumer,INFINITE);
    EnterCriticalSection(&pEngine->cs); // }
  }

  code=0;
exit:
  LeaveCriticalSection(&pEngine->cs); // }
  return code;
}
Listing 5.3: Creating Class Engine's Render/Server Thread: Function EngineCreateThread from "yasapi_engine_common.c".

Lines    Remark
to be continued ...
Table 5.7: Notes to Function EngineCreateThread.

5.4. Stopping and Destroying Class Engine's Render/Server Thread


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
  

void EngineKill(Engine *pEngine, const EEngine eState)
{
  // Left out ...

  switch (pEngine->eState) {
  // Left out ...
  case eEngineThreadRunning:
    if (eState==pEngine->eState)
      break;

    EnterCriticalSection(&pEngine->cs); // {
    EngineRequestState(pEngine,pEngine->eState-1);
    LeaveCriticalSection(&pEngine->cs); // }
    // Intentional fall-through.
  case eEngineThread:
    if (eState==pEngine->eState)
      break;

    --pEngine->eState;
    WaitForSingleObject(pEngine->thread.hThread,INFINITE);
    CloseHandle(pEngine->thread.hThread);
    pEngine->thread.hThread=NULL;
    // Intentional fall-through.
  // Left out ...
  default:
    break;
  }
}
Listing 5.4: Stopping and Destroying Class Engine's Render/Server Thread: Excerpt from Function EngineKill from "yasapi_engine_common.c".

Lines    Remark
to be continued ...
Table 5.8: Notes to the Excerpt of Function EngineKill.

5.5. Class Engine's Render/Server Thread's Utility Structures and Functions

5.5.1. Utility Strcture Thread


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
  

typedef enum _EThread EThread;
typedef struct _Thread Thread;

enum _EThread {
  eThreadProducer,
  eThreadTimerEvent,
  eThreadTimer,
  eThreadCount,
};

struct _Thread {
  Engine *pEngine;
  HANDLE aHandles[eThreadCount];
  HANDLE hTask;
  DWORD dwTaskIndex;
};
Listing 5.5: Utility Structure Thread local to "yasapi_thread.c".

5.5.2. Class Engine's Render/Server Thread's Utility Function _ThreadCreate


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
  

static void _ThreadCreate(Thread *pThread, Engine *pEngine)
{
  pThread->pEngine=pEngine;
  pThread->aHandles[eThreadProducer]=pEngine->thread.hProducer;
  pThread->aHandles[eThreadTimerEvent]=pEngine->timer.hEvent;
  pThread->aHandles[eThreadTimer]=pEngine->timer.hTimer;
  pThread->hTask=NULL;
  pThread->dwTaskIndex=0ul;

  EnterCriticalSection(&pEngine->cs);
  ++pEngine->eState;
  SetEvent(pEngine->thread.hConsumer);
}
Listing 5.6: Class Engine's Render/Server Thread's Utility Function _ThreadCreate from "yasapi_thread.c".

Lines    Remark
to be continued ...
Table 5.9: Notes to Function _ThreadCreate.

5.5.3. Class Engine's Render/Server Thread's Utility Function _ThreadDestroy


1:
2:
3:
4:
5:
6:
7:
8:
9:
  

static void _ThreadDestroy(Thread *pThread)
{
  Engine *pEngine=pThread->pEngine;

  --pEngine->eState;
  SetEvent(pEngine->thread.hConsumer);
  LeaveCriticalSection(&pEngine->cs);
  ExitThread(0ul);
}
Listing 5.7: Class Engine's Render/Server Thread's Utility Function _ThreadDestroy from "yasapi_thread.c".

Lines    Remark
to be continued ...
Table 5.10: Notes to Function _ThreadDestroy.

5.5.4. Class Engine's Render/Server Thread's Utility Function _ThreadWait


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
  

static DWORD _ThreadWait(Thread *pThread)
{
  Engine *pEngine=pThread->pEngine;
  DWORD dwWait;

  ResetEvent(pEngine->thread.hProducer); // {
  LeaveCriticalSection(&pEngine->cs); // {

  dwWait=WaitForMultipleObjects(
    eThreadCount,       // _In_       DWORD  nCount,
    pThread->aHandles,  // _In_ const HANDLE *lpHandles,
    FALSE,              // _In_       BOOL   bWaitAll,
    INFINITE            // _In_       DWORD  dwMilliseconds
  );

  EnterCriticalSection(&pEngine->cs); // }

  return dwWait;
}
Listing 5.8: Class Engine's Render/Server Thread's Utility Function _ThreadWait from "yasapi_thread.c".

Lines    Remark
to be continued ...
Table 5.11: Notes to Function _ThreadWait.

5.5.5. Class Engine's Render/Server Thread's Utility Function _ThreadHandshake


1:
2:
3:
4:
5:
6:
7:
  

static void _ThreadHandshake(Thread *pThread)
{
  Engine *pEngine=pThread->pEngine;

  pEngine->thread.eStateRequest=eEngineNull;
  SetEvent(pEngine->thread.hConsumer);
}
Listing 5.9: Class Engine's Render/Server Thread's Utility Function _ThreadHandshake from "yasapi_thread.c".

Lines    Remark
to be continued ...
Table 5.12: Notes to Function _ThreadHandshake.

5.5.6. Class Engine's Render/Server Thread's Utility Function _ThreadRequestStart


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
  

static int _ThreadRequestStart(Thread *pThread)
{
  int code=-1;
  Engine *pEngine=pThread->pEngine;

  if (eEngineStarted<=pEngine->eState)
    goto estart;

  if (pEngine->options.device.bProAudio&&!pThread->hTask) {
    pThread->hTask=AvSetMmThreadCharacteristicsW(L"Pro Audio",
        &pThread->dwTaskIndex);
  }

  EngineRender(pEngine);
  _ThreadHandshake(pThread);
  code=0;
estart:
  return code;
}
Listing 5.10: Class Engine's Render/Server Thread's Utility Function _ThreadRequestStart from "yasapi_thread.c".

Lines    Remark
to be continued ...
Table 5.13: Notes to Function _ThreadRequestStart.

5.5.7. Class Engine's Render/Server Thread's Utility Function _ThreadRequestKill


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
  

static void _ThreadRequestKill(Thread *pThread, const EEngine eState)
{
  Engine *pEngine=pThread->pEngine;

  EngineKill(pEngine,eState);

  if (pEngine->eState<eEngineAudioClient&&pThread->hTask) {
    AvRevertMmThreadCharacteristics(pThread->hTask);
    pThread->hTask=NULL;
    pThread->dwTaskIndex=0ul;
  }

  _ThreadHandshake(pThread);
}
Listing 5.11: Class Engine's Render/Server Thread's Utility Function _ThreadRequestKill from "yasapi_thread.c".

Lines    Remark
to be continued ...
Table 5.14: Notes to Function _ThreadRequestKill.

5.5.8. Class Engine's Render/Server Thread's Utility Function _ThreadRender


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
  

static int _ThreadRender(Thread *pThread)
{
  int code=-1;
  Engine *pEngine=pThread->pEngine;
  Ring *pRing=&pEngine->ring;
  EEngine eStateRequest=pEngine->thread.eStateRequest;

  switch (eStateRequest) {
  case eEngineNull:
    break;
  case eEngineBase:
  case eEnginePauseConnected:
  case eEnginePauseDisconnected:
    _ThreadRequestKill(pThread,eStateRequest);
    goto success;
  default:
    // Emit some error message (left out) ...
    break;
  }

  switch (pEngine->eState) {
  case eEnginePauseConnected:
  case eEnginePauseDisconnected:
    break;
  case eEngineDrainingDoubleBuffered:
    SetEvent(pEngine->thread.hApi);
    _ThreadRequestKill(pThread,eEngineBase);
    break;
  case eEngineDraining:
    if (0u==RingTargetSizeWritten(pRing)) {
      union {
        struct {
          UINT32 uFrames;
          IAudioRenderClient *pAudioRenderClient;
          BYTE *pData;
          HRESULT hr;
        };

        struct {
          LARGE_INTEGER DueTime;
          int bCode;
        };
      } u;


      if (!pEngine->param.bDoubleBuffered) {
        SetEvent(pEngine->thread.hApi);
        _ThreadRequestKill(pThread,eEngineBase);
        break;
      }

      // In case of double buffering we need to wait for another
      // playcack cycle.
      ++pEngine->eState;
      DWRITELN(EngineState(pEngine));
      DASSERT_ENGINE_STATE(pEngine,eEngineDrainingDoubleBuffered,exit);

      switch (pEngine->options.device.eCycle) {
      case eCycleSilence:
        u.uFrames=pEngine->param.uNumFramesDevice;
        u.pAudioRenderClient=pEngine->pAudioRenderClient;

        u.hr=u.pAudioRenderClient->lpVtbl->GetBuffer(u.pAudioRenderClient,
          u.uFrames,                    // [in]  UINT32 NumFramesRequested,
          &u.pData                      // [out] BYTE   **ppData
        );

        if (FAILED(u.hr)) {
          // Emit some error message (left out) ...
          goto exit;
        }

        u.hr=u.pAudioRenderClient->lpVtbl->ReleaseBuffer(u.pAudioRenderClient,
          u.uFrames,                    // [in] UINT32 NumFramesWritten,
          AUDCLNT_BUFFERFLAGS_SILENT    // [in] DWORD  dwFlags
        );

        if (FAILED(u.hr)) {
          // Emit some error message (left out) ...
          // nowhere to go ...
        }

        break;
      default:
        u.DueTime=pEngine->param.DueTime;

        u.bCode=SetWaitableTimer(
          pEngine->timer.hTimer,
                      // _In_           HANDLE           hTimer,
          &u.DueTime, // _In_     const LARGE_INTEGER    *pDueTime,
          0l,         // _In_           LONG             lPeriod,
          NULL,       // _In_opt_       PTIMERAPCROUTINE pfnCompletionRoutine,
          NULL,       // _In_opt_       LPVOID           lpArgToCompletionRoutine,
          FALSE       // _In_           BOOL             fResume
        );

        if (!u.bCode) {
          // Emit some error message (left out) ...
          goto exit;
        }

        break;
      }

      break;
    }

    // Intentional fall-through.
  default:
    if (eEngineStarted<=pEngine->eState&&EngineRender(pEngine)<0) {
      // Emit some error message (left out) ...
      goto exit;
    }

    break;
  }
success:
  code=0;
exit:
  return code;
}
Listing 5.12: Class Engine's Render/Server Thread's Utility Function _ThreadRender from "yasapi_thread.c".

Lines    Remark
to be continued ...
Table 5.15: Notes to Function _ThreadRender.