Exclusive 您所在的位置:网站首页 win10没有mmcss Exclusive

Exclusive

2023-07-26 15:54| 来源: 网络整理| 查看: 265

Exclusive-Mode流 项目 06/13/2023

如前所述,如果应用程序以独占模式打开流,则应用程序将独占使用播放或录制流的音频终结点设备。 相比之下,多个应用程序可以通过在设备上打开共享模式流来共享音频终结点设备。

对音频设备的独占模式访问可能会阻止关键系统声音,阻止与其他应用程序的互操作性,并降低用户体验。 为了缓解这些问题,当应用程序不是前台进程或未主动流式处理时,具有独占模式流的应用程序通常会放弃对音频设备的控制。

流延迟是连接应用程序的终结点缓冲区与音频终结点设备的数据路径中固有的延迟。 对于呈现流,延迟是从应用程序将示例写入终结点缓冲区到通过扬声器听到样本的时间的最大延迟。 对于捕获流,延迟是从声音进入麦克风到应用程序可以从终结点缓冲区读取该声音样本的最大延迟。

使用独占模式流的应用程序通常会这样做,因为它们需要音频终结点设备和访问终结点缓冲区的应用程序线程之间的数据路径的低延迟。 通常,这些线程以相对较高的优先级运行,并计划自身按接近或相同的定期间隔运行,该时间间隔由音频硬件分隔连续处理传递。 在每次传递期间,音频硬件都会处理终结点缓冲区中的新数据。

若要实现最小的流延迟,应用程序可能需要特殊的音频硬件和轻负载的计算机系统。 驱动音频硬件超出其计时限制或加载具有竞争性高优先级任务的系统可能会导致低延迟音频流出现故障。 例如,对于呈现流,如果应用程序在音频硬件读取缓冲区之前无法写入终结点缓冲区,或者硬件在计划播放缓冲区之前无法读取缓冲区,则可能会出现故障。 通常,打算在各种音频硬件和各种系统中运行的应用程序应放宽其计时要求,以避免在所有目标环境中出现故障。

Windows Vista 具有多个功能来支持需要低延迟音频流的应用程序。 如 用户模式音频组件中所述,执行时间关键操作的应用程序可以调用多媒体类计划程序服务 (MMCSS) 函数来增加线程优先级,而无需拒绝将 CPU 资源用于低优先级应用程序。 此外, IAudioClient::Initialize 方法支持AUDCLNT_STREAMFLAGS_EVENTCALLBACK标志,使应用程序的缓冲区服务线程能够计划当音频设备提供新缓冲区时执行。 通过使用这些功能,应用程序线程可以减少有关何时执行的不确定性,从而降低低延迟音频流中出现故障的风险。

较旧音频适配器的驱动程序可能使用 WaveCyclic 或 WavePci 设备驱动程序接口 (DDI) ,而较新音频适配器的驱动程序则更有可能支持 WaveRT DDI。 对于独占模式应用程序,WaveRT 驱动程序可以提供比 WaveCyclic 或 WavePci 驱动程序更好的性能,但 WaveRT 驱动程序需要其他硬件功能。 这些功能包括直接与应用程序共享硬件缓冲区的功能。 使用直接共享时,无需系统干预即可在独占模式应用程序和音频硬件之间传输数据。 相比之下,WaveCyclic 和 WavePci 驱动程序适用于较旧的、功能较差的音频适配器。 这些适配器依赖于系统软件来传输附加到系统 I/O 请求数据包的数据块 (,或者在应用程序缓冲区和硬件缓冲区之间) IRP。 此外,USB 音频设备依赖于系统软件在应用程序缓冲区和硬件缓冲区之间传输数据。 为了提高连接到依赖系统进行数据传输的音频设备的独占模式应用程序的性能,WASAPI 会自动提高在应用程序和硬件之间传输数据的系统线程的优先级。 WASAPI 使用 MMCSS 来增加线程优先级。 在 Windows Vista 中,如果系统线程管理 PCM 格式且设备周期小于 10 毫秒的独占模式音频播放流的数据传输,则 WASAPI 会将 MMCSS 任务名称“Pro Audio”分配给该线程。 如果流的设备周期大于或等于 10 毫秒,WASAPI 会将 MMCSS 任务名称“Audio”分配给线程。 有关 WaveCyclic、WavePci 和 WaveRT DDI 的详细信息,请参阅 Windows DDK 文档。 有关选择适当设备周期的信息,请参阅 IAudioClient::GetDevicePeriod。

如 会话音量控制中所述, WASAPI 提供 ISimpleAudioVolume、 IChannelAudioVolume 和 IAudioStreamVolume 接口,用于控制共享模式音频流的音量级别。 但是,这些接口中的控件对独占模式流没有影响。 相反,管理独占模式流的应用程序通常使用 EndpointVolume API 中的 IAudioEndpointVolume 接口来控制这些流的音量级别。 有关此接口的信息,请参阅 Endpoint Volume Controls。

对于系统中的每个播放设备和捕获设备,用户可以控制设备是否可以在独占模式下使用。 如果用户禁用设备的独占模式使用,则设备可用于仅播放或录制共享模式流。

如果用户允许以独占模式使用设备,则用户还可以控制应用程序在独占模式下使用设备的请求是否会抢占当前可能正在通过设备播放或录制共享模式流的应用程序使用设备。 如果启用了抢占,则如果设备当前未使用,或者设备正在共享模式下使用,则应用程序对设备进行独占控制的请求将成功,但如果另一个应用程序已对设备拥有独占控制权,则请求会失败。 如果已禁用抢占,则如果设备当前未使用,应用程序对设备进行独占控制的请求将成功,但如果设备已在共享模式或独占模式下使用,则请求将失败。

在 Windows Vista 中,音频终结点设备的默认设置如下:

设备可用于播放或录制独占模式流。 使用设备播放或录制独占模式流的请求会抢占当前正在通过设备播放或录制的任何共享模式流。

更改播放或录制设备的独占模式设置

右键单击位于任务栏右侧的通知区域中的扬声器图标,然后选择“ 播放设备 ”或“ 录制设备”。 (作为替代方法,请从命令提示符窗口Mmsys.cpl运行 Windows 多媒体控制面板。有关详细信息,请参阅 DEVICE_STATE_XXX Constants.) 显示 “声音 ”窗口后,选择“ 播放 ”或“ 录制”。 接下来,在设备名称列表中选择一个条目,然后单击“ 属性”。 显示 “属性” 窗口后,单击“ 高级”。 若要使应用程序能够在独占模式下使用设备,检查标有“允许应用程序独占控制此设备”的框。 若要禁用设备的独占模式使用,请清除“检查”框。 如果启用了设备的独占模式使用,则可以指定当设备当前正在播放或录制共享模式流时,对设备的独占控制请求是否成功。 若要使独占模式应用程序优先于共享模式应用程序,检查标有“授予独占模式应用程序优先级”的框。 若要拒绝独占模式应用程序优先于共享模式应用程序的优先级,请清除“检查”框。

下面的代码示例演示如何在配置为在独占模式下使用的音频呈现设备上播放低延迟音频流:

//----------------------------------------------------------- // Play an exclusive-mode stream on the default audio // rendering device. The PlayExclusiveStream function uses // event-driven buffering and MMCSS to play the stream at // the minimum latency supported by the device. //----------------------------------------------------------- // REFERENCE_TIME time units per second and per millisecond #define REFTIMES_PER_SEC 10000000 #define REFTIMES_PER_MILLISEC 10000 #define EXIT_ON_ERROR(hres) \ if (FAILED(hres)) { goto Exit; } #define SAFE_RELEASE(punk) \ if ((punk) != NULL) \ { (punk)->Release(); (punk) = NULL; } const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); const IID IID_IAudioClient = __uuidof(IAudioClient); const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); HRESULT PlayExclusiveStream(MyAudioSource *pMySource) { HRESULT hr; REFERENCE_TIME hnsRequestedDuration = 0; IMMDeviceEnumerator *pEnumerator = NULL; IMMDevice *pDevice = NULL; IAudioClient *pAudioClient = NULL; IAudioRenderClient *pRenderClient = NULL; WAVEFORMATEX *pwfx = NULL; HANDLE hEvent = NULL; HANDLE hTask = NULL; UINT32 bufferFrameCount; BYTE *pData; DWORD flags = 0; DWORD taskIndex = 0; hr = CoCreateInstance( CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator); EXIT_ON_ERROR(hr) hr = pEnumerator->GetDefaultAudioEndpoint( eRender, eConsole, &pDevice); EXIT_ON_ERROR(hr) hr = pDevice->Activate( IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient); EXIT_ON_ERROR(hr) // Call a helper function to negotiate with the audio // device for an exclusive-mode stream format. hr = GetStreamFormat(pAudioClient, &pwfx); EXIT_ON_ERROR(hr) // Initialize the stream to play at the minimum latency. hr = pAudioClient->GetDevicePeriod(NULL, &hnsRequestedDuration); EXIT_ON_ERROR(hr) hr = pAudioClient->Initialize( AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, hnsRequestedDuration, hnsRequestedDuration, pwfx, NULL); if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { // Align the buffer if needed, see IAudioClient::Initialize() documentation UINT32 nFrames = 0; hr = pAudioClient->GetBufferSize(&nFrames); EXIT_ON_ERROR(hr) hnsRequestedDuration = (REFERENCE_TIME)((double)REFTIMES_PER_SEC / pwfx->nSamplesPerSec * nFrames + 0.5); hr = pAudioClient->Initialize( AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, hnsRequestedDuration, hnsRequestedDuration, pwfx, NULL); } EXIT_ON_ERROR(hr) // Tell the audio source which format to use. hr = pMySource->SetFormat(pwfx); EXIT_ON_ERROR(hr) // Create an event handle and register it for // buffer-event notifications. hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (hEvent == NULL) { hr = E_FAIL; goto Exit; } hr = pAudioClient->SetEventHandle(hEvent); EXIT_ON_ERROR(hr); // Get the actual size of the two allocated buffers. hr = pAudioClient->GetBufferSize(&bufferFrameCount); EXIT_ON_ERROR(hr) hr = pAudioClient->GetService( IID_IAudioRenderClient, (void**)&pRenderClient); EXIT_ON_ERROR(hr) // To reduce latency, load the first buffer with data // from the audio source before starting the stream. hr = pRenderClient->GetBuffer(bufferFrameCount, &pData); EXIT_ON_ERROR(hr) hr = pMySource->LoadData(bufferFrameCount, pData, &flags); EXIT_ON_ERROR(hr) hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags); EXIT_ON_ERROR(hr) // Ask MMCSS to temporarily boost the thread priority // to reduce glitches while the low-latency stream plays. hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex); if (hTask == NULL) { hr = E_FAIL; EXIT_ON_ERROR(hr) } hr = pAudioClient->Start(); // Start playing. EXIT_ON_ERROR(hr) // Each loop fills one of the two buffers. while (flags != AUDCLNT_BUFFERFLAGS_SILENT) { // Wait for next buffer event to be signaled. DWORD retval = WaitForSingleObject(hEvent, 2000); if (retval != WAIT_OBJECT_0) { // Event handle timed out after a 2-second wait. pAudioClient->Stop(); hr = ERROR_TIMEOUT; goto Exit; } // Grab the next empty buffer from the audio device. hr = pRenderClient->GetBuffer(bufferFrameCount, &pData); EXIT_ON_ERROR(hr) // Load the buffer with data from the audio source. hr = pMySource->LoadData(bufferFrameCount, pData, &flags); EXIT_ON_ERROR(hr) hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags); EXIT_ON_ERROR(hr) } // Wait for the last buffer to play before stopping. Sleep((DWORD)(hnsRequestedDuration/REFTIMES_PER_MILLISEC)); hr = pAudioClient->Stop(); // Stop playing. EXIT_ON_ERROR(hr) Exit: if (hEvent != NULL) { CloseHandle(hEvent); } if (hTask != NULL) { AvRevertMmThreadCharacteristics(hTask); } CoTaskMemFree(pwfx); SAFE_RELEASE(pEnumerator) SAFE_RELEASE(pDevice) SAFE_RELEASE(pAudioClient) SAFE_RELEASE(pRenderClient) return hr; }

在前面的代码示例中,PlayExclusiveStream 函数在播放呈现流时在为终结点缓冲区提供服务的应用程序线程中运行。 该函数采用单个参数 pMySource,该参数是指向属于客户端定义类 MyAudioSource 的对象指针。 此类有两个成员函数,即 LoadData 和 SetFormat,在代码示例中调用。 呈现流中介绍了 MyAudioSource。

PlayExclusiveStream 函数调用帮助程序函数 GetStreamFormat,该函数与默认呈现设备协商以确定设备是否支持适合应用程序使用的独占模式流格式。 GetStreamFormat 函数的代码不显示在代码示例中;这是因为其实现的详细信息完全取决于应用程序的要求。 但是,GetStreamFormat 函数的操作可以简单地描述 ,它调用 IAudioClient::IsFormatSupported 方法一次或多次,以确定设备是否支持合适的格式。 应用程序的要求决定了 GetStreamFormat 向 IsFormatSupported 方法呈现的格式以及呈现这些格式的顺序。 有关 IsFormatSupported 的详细信息,请参阅 设备格式。

在 GetStreamFormat 调用后,PlayExclusiveStream 函数调用 IAudioClient::GetDevicePeriod 方法以获取音频硬件支持的最小设备周期。 接下来,函数调用 IAudioClient::Initialize 方法来请求等于最小周期的缓冲区持续时间。 如果调用成功, 则 Initialize 方法将分配两个终结点缓冲区,每个缓冲区的持续时间等于最小时间段。 稍后,当音频流开始运行时,应用程序和音频硬件将以“ping-pong”方式共享两个缓冲区,即当应用程序写入一个缓冲区时,硬件将从另一个缓冲区读取数据。

在启动流之前,PlayExclusiveStream 函数执行以下操作:

创建并注册事件句柄,当缓冲区准备好填充时,它将通过该句柄接收通知。 使用来自音频源的数据填充第一个缓冲区,以减少从流开始运行时到听到初始声音的延迟。 调用 AvSetMmThreadCharacteristics 函数以请求 MMCSS 增加执行 PlayExclusiveStream 的线程的优先级。 (当流停止运行时, AvRevertMmThreadCharacteristics 函数调用将还原原始线程 priority.)

有关 AvSetMmThreadCharacteristics 和 AvRevertMmThreadCharacteristics 的详细信息,请参阅 Windows SDK 文档。

在流运行时,上述代码示例中 while 循环的每次迭代都会填充一个终结点缓冲区。 在迭代之间, WaitForSingleObject 函数调用将等待事件句柄发出信号。 当向句柄发出信号时,循环体将执行以下操作:

调用 IAudioRenderClient::GetBuffer 方法以获取下一个缓冲区。 填充缓冲区。 调用 IAudioRenderClient::ReleaseBuffer 方法以释放缓冲区。

有关 WaitForSingleObject 的详细信息,请参阅 Windows SDK 文档。

如果音频适配器由 WaveRT 驱动程序控制,则事件句柄的信号将绑定到来自音频硬件的 DMA 传输通知。 对于 USB 音频设备,或者对于由 WaveCyclic 或 WavePci 驱动程序控制的音频设备,事件句柄的信号将绑定到将数据从应用程序缓冲区传输到硬件缓冲区的 IRP 完成。

前面的代码示例将音频硬件和计算机系统推送到其性能限制。 首先,为了降低流延迟,应用程序将其缓冲区服务线程计划为使用音频硬件将支持的最短设备周期。 其次,为了确保线程在每个设备周期内可靠地执行, AvSetMmThreadCharacteristics 函数调用将 TaskName 参数设置为“Pro Audio”,即 Windows Vista 中具有最高优先级的默认任务名称。 考虑是否放宽了应用程序的计时要求,而不会损害其效用。 例如,应用程序可能会计划其缓冲区服务线程使用比最小值更长的时间段。 较长的时间可能会安全地允许使用较低的线程优先级。

相关主题

流管理

 

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有