Boring introductory stuff first:
Windows Vista introduced a feature called the "Multimedia Class Scheduler Service" (MMCSS). This is designed to give audio and video threads regular, short, bursts of very high priority, so that audio and video can stream without glitching. The bursts are regular so that audio and video packets don't get delayed, and they are short so that non-multimedia activity doesn't get starved.
It's very important to note that apps which called into high-level audio and video APIs (e.g., the <audio> and <video> HTML tags) don't have to worry about this kind of thing; the implementation of the high-level API takes care of registering the right pieces of its code with MMCSS.
For apps which (for one reason or another) choose not to use the high-level APIs, and instead want to hook into low-level APIs directly, the original design was for the app to call AvSetMmThreadCharacteristics function from its streaming thread when streaming begins, and AvRevertMmThreadCharacteristics when streaming ends.
In Windows 8.1, the Real-Time Work Queue API was created. This is the preferred approach for apps which want to use low-level APIs.
(whew)
Now the good stuff. I got an email from a pro audio application developer who said that AvSetMmThreadCharacteristics was giving an ERROR_TOO_MANY_THREADS error in his app... even though he only registered a single thread with MMCSS! Surely one thread is not too many...
At this point you should pause and read the excellent book One Kitten is Not Too Many. I'll wait.
Welcome back! A bunch of different theories started popping up in my head, like "maybe some other process on the system is consuming all the MMCSS slots", or "maybe a plugin for the application is registering threads without the application developer's knowledge", or "maybe the call to AvRevertMmThreadCharacteristics isn't happening", or "maybe the task handle is being overwritten between Set and Revert".
But then I remembered Raymond Chen's advice: Theorize if you want, but if the problem is right there in front of you, why not go for the facts?
MMCSS has "Event Tracing for Windows" (ETW) logging. In particular, when a task is created, the Microsoft-Windows-MMCSS provider logs a Thread_Join event. This will shed light on what processes and threads are registering with MMCSS.
Also, ETW supports grabbing a stack at the point of an event being logged! This will shed light on whether the registration is happening from app code directly, or a plugin, or whatever.
So I sent the developer these instructions:
- Copy mmcss.wprp file to a repro device
- Open an elevated Command Prompt or PowerShell window
- Run wpr.exe -start mmcss.wprp
- Launch the app
- Create some audio objects and let them run for one second or so
- Close the app
- Run wpr.exe -stop mmcss.etl (you can change the output file name if you like)
- Inspect the resulting mmcss.etl file
In my local testing (I just used echo ^G from a command prompt) I was able to see a Microsoft-Windows-MMCSS/Thread_Join/ event with this stack (only the bold part is interesting)
[Root]
ntdll.dll!RtlUserThreadStart
kernel32.dll!BaseThreadInitThunk
wdmaud.drv!CWorker::_StaticThreadProc
wdmaud.drv!CWorker::_ThreadProc
wdmaud.drv!`CWaveHandle::Open'::`2'::COpenJob::Work
wdmaud.drv!CWaveOutHandle::_Open
wdmaud.drv!CWaveHandle::_Open
avrt.dll!AvSetMmThreadCharacteristicsA
avrt.dll!AiThreadConnect
avrt.dll!AiCreateMmcssTaskIndexOrThreadClient
ntdll.dll!ZwCreateFile
ntoskrnl.exe!KiSystemServiceCopyEnd
ntoskrnl.exe!NtCreateFile
ntoskrnl.exe!IopCreateFile
ntoskrnl.exe!ObOpenObjectByNameEx
ntoskrnl.exe!ObpLookupObjectName
ntoskrnl.exe!IopParseDevice
ntoskrnl.exe!IoCallDriverWithTracing
ntoskrnl.exe!IofCallDriver
mmcss.sys!CiDispatchCreate
mmcss.sys!CiDispatchCreateMmThreadClient
mmcss.sys!CiThreadCreate
mmcss.sys!CiLogThreadJoin
ntoskrnl.exe!EtwWrite