CoreAudioApi-音频终端设备事件-检测耳机插拔

设备事件通知客户端系统中音频端点设备的状态更改。以下是设备事件的示例:

       *用户从设备管理器或Windows多媒体控制面板mmsys.cpl启用或禁用音频端点设备。

       *用户向系统添加音频适配器或从系统中删除音频适配器。

       *用户将音频端点设备插入带有插孔存在检测功能的音频插孔,或从此类插孔中删除音频端点设备。

       *用户更改设备角色分配给设备。

       *设备更改的属性值。

添加或删除音频适配器会为连接到适配器的所有音频端点设备生成设备事件。前面列表中的前四项是设备状态更改的示例。有关音频终结点设备的设备状态的详细信息,请参阅设备状态xxx常量(DEVICE_STATE_XXX Constants)。有关插孔存在检测的更多信息,请参阅音频端点设备(Audio Endpoint Devices)。

客户端可以注册以在设备事件发生时得到通知。作为对这些通知的响应,客户机可以动态更改其使用特定设备的方式,或者选择其他设备用于特定目的。

例如,如果应用程序正在通过一组USB扬声器播放音频曲目,并且用户从USB接口断开扬声器连接,则应用程序将收到设备事件通知。作为对事件的响应,如果应用程序检测到一组桌面扬声器连接到系统主板上的集成音频适配器,则应用程序可以通过桌面扬声器继续播放音频曲目。在本例中,从USB扬声器到桌面扬声器的转换将自动进行,而无需用户通过显式重定向应用程序进行干预。

要注册以接收设备通知,客户端将调用IMMDeviceEnumerator::RegisterEndpointNotificationCallback方法 。当客户端不再需要通知时,它通过调用IMMDeviceEnumerator::UnregisterEndpointNotificationCallback来取消通知。这两个方法都采用输入参数namedpNotify,这是指向IMMNotificationClient接口实例的指针。

IMMNotificationClient接口由客户端实现。接口包含多个方法,每个方法都用作特定类型设备事件的回调例程。当音频终结点设备中发生设备事件时,MMDevice模块在当前注册接收设备事件通知的每个客户端的IMMNotificationClient接口调用适当的方法。这些调用将事件的描述传递给客户机。有关详细信息,请参阅IMMNotificationClient Interface

注册接收设备事件通知的客户端将接收系统中所有音频终结点设备中发生的所有类型设备事件的通知。如果客户机只对某些事件类型或某些设备感兴趣,则其IMMNotificationClient 实现中的方法应适当地筛选事件。

Windows SDK提供的示例包括IMMNotificationClient接口的多个实现。有关更多信息,请参阅SDK Samples That Use the Core Audio APIs

以下代码示例显示了immNotificationClient接口的可能实现:

//-----------------------------------------------------------
// Example implementation of IMMNotificationClient interface.
// When the status of audio endpoint devices change, the
// MMDevice module calls these methods to notify the client.
//-----------------------------------------------------------

#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

class CMMNotificationClient : public IMMNotificationClient
{
    LONG _cRef;
    IMMDeviceEnumerator *_pEnumerator;

    // Private function to print device-friendly name
    HRESULT _PrintDeviceName(LPCWSTR  pwstrId);

public:
    CMMNotificationClient() :
        _cRef(1),
        _pEnumerator(NULL)
    {
    }

    ~CMMNotificationClient()
    {
        SAFE_RELEASE(_pEnumerator)
    }

    // IUnknown methods -- AddRef, Release, and QueryInterface

    ULONG STDMETHODCALLTYPE AddRef()
    {
        return InterlockedIncrement(&_cRef);
    }

    ULONG STDMETHODCALLTYPE Release()
    {
        ULONG ulRef = InterlockedDecrement(&_cRef);
        if (0 == ulRef)
        {
            delete this;
        }
        return ulRef;
    }

    HRESULT STDMETHODCALLTYPE QueryInterface(
                                REFIID riid, VOID **ppvInterface)
    {
        if (IID_IUnknown == riid)
        {
            AddRef();
            *ppvInterface = (IUnknown*)this;
        }
        else if (__uuidof(IMMNotificationClient) == riid)
        {
            AddRef();
            *ppvInterface = (IMMNotificationClient*)this;
        }
        else
        {
            *ppvInterface = NULL;
            return E_NOINTERFACE;
        }
        return S_OK;
    }

    // Callback methods for device-event notifications.

    HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(
                                EDataFlow flow, ERole role,
                                LPCWSTR pwstrDeviceId)
    {
        char  *pszFlow = "?????";
        char  *pszRole = "?????";

        _PrintDeviceName(pwstrDeviceId);

        switch (flow)
        {
        case eRender:
            pszFlow = "eRender";
            break;
        case eCapture:
            pszFlow = "eCapture";
            break;
        }

        switch (role)
        {
        case eConsole:
            pszRole = "eConsole";
            break;
        case eMultimedia:
            pszRole = "eMultimedia";
            break;
        case eCommunications:
            pszRole = "eCommunications";
            break;
        }

        printf("  -->New default device: flow = %s, role = %s\n",
               pszFlow, pszRole);
        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId)
    {
        _PrintDeviceName(pwstrDeviceId);

        printf("  -->Added device\n");
        return S_OK;
    };

    HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)
    {
        _PrintDeviceName(pwstrDeviceId);

        printf("  -->Removed device\n");
        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
                                LPCWSTR pwstrDeviceId,
                                DWORD dwNewState)
    {
        char  *pszState = "?????";

        _PrintDeviceName(pwstrDeviceId);

        switch (dwNewState)
        {
        case DEVICE_STATE_ACTIVE:
            pszState = "ACTIVE";
            break;
        case DEVICE_STATE_DISABLED:
            pszState = "DISABLED";
            break;
        case DEVICE_STATE_NOTPRESENT:
            pszState = "NOTPRESENT";
            break;
        case DEVICE_STATE_UNPLUGGED:
            pszState = "UNPLUGGED";
            break;
        }

        printf("  -->New device state is DEVICE_STATE_%s (0x%8.8x)\n",
               pszState, dwNewState);

        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
                                LPCWSTR pwstrDeviceId,
                                const PROPERTYKEY key)
    {
        _PrintDeviceName(pwstrDeviceId);

        printf("  -->Changed device property "
               "{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}#%d\n",
               key.fmtid.Data1, key.fmtid.Data2, key.fmtid.Data3,
               key.fmtid.Data4[0], key.fmtid.Data4[1],
               key.fmtid.Data4[2], key.fmtid.Data4[3],
               key.fmtid.Data4[4], key.fmtid.Data4[5],
               key.fmtid.Data4[6], key.fmtid.Data4[7],
               key.pid);
        return S_OK;
    }
};

// Given an endpoint ID string, print the friendly device name.
HRESULT CMMNotificationClient::_PrintDeviceName(LPCWSTR pwstrId)
{
    HRESULT hr = S_OK;
    IMMDevice *pDevice = NULL;
    IPropertyStore *pProps = NULL;
    PROPVARIANT varString;

    CoInitialize(NULL);
    PropVariantInit(&varString);

    if (_pEnumerator == NULL)
    {
        // Get enumerator for audio endpoint devices.
        hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
                              NULL, CLSCTX_INPROC_SERVER,
                              __uuidof(IMMDeviceEnumerator),
                              (void**)&_pEnumerator);
    }
    if (hr == S_OK)
    {
        hr = _pEnumerator->GetDevice(pwstrId, &pDevice);
    }
    if (hr == S_OK)
    {
        hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);
    }
    if (hr == S_OK)
    {
        // Get the endpoint device's friendly-name property.
        hr = pProps->GetValue(PKEY_Device_FriendlyName, &varString);
    }
    printf("----------------------\nDevice name: \"%S\"\n"
           "  Endpoint ID string: \"%S\"\n",
           (hr == S_OK) ? varString.pwszVal : L"null device",
           (pwstrId != NULL) ? pwstrId : L"null ID");

    PropVariantClear(&varString);

    SAFE_RELEASE(pProps)
    SAFE_RELEASE(pDevice)
    CoUninitialize();
    return hr;
}

前面代码示例中的CMMNotificationClient类是IMMNotificationClient接口的实现。因为IMMNotificationClient继承自IUnknown,所以类定义包含IUnknown方法AddRef,Release, 和QueryInterface的实现。类定义中的其余公共方法特定于IMMNotificationClient接口。这些方法是:

       OnDefaultDeviceChanged, 当用户更改音频终结点设备的设备角色时调用。

       OnDeviceAdded, 当用户向系统添加音频端点设备时调用。

       OnDeviceRemoved, 当用户从系统中删除音频终结点设备时调用。

       OnDeviceStateChanged, 当音频端点设备的设备状态更改时调用。(有关设备状态的详细信息,请参阅DEVICE_STATE_ XXX Constants.)

       OnPropertyValueChanged, 当音频终结点设备的属性值更改时调用。

每个方法都接受一个输入参数pwstrDeviceId,它是指向端点ID字符串的指针。字符串标识发生设备事件的音频终结点设备。

在前面的代码示例中,_PrintDeviceNameCMMNotificationClient类中打印设备友好名称的私有方法。_ _PrintDeviceName将端点ID字符串作为输入参数。它将字符串传递给IMMDeviceEnumerator::GetDeviceGetDevice创建一个端点设备对象来表示该设备,并向该对象提供IMMDevice接口。接下来,_PrintDeviceName调用IMMDevice::OpenPropertyStore方法来检索设备属性存储的IPropertyStore接口。最后,_PrintDeviceName调用IPropertyStore::GetItem方法以获取设备的友好名称属性。有关IPropertyStore的详细信息,请参阅Windows SDK文档。

除了设备事件外,客户端还可以注册以接收音频会话事件和终结音量事件的通知。更多信息,请参考IAudioSessionEvents InterfaceIAudioEndpointVolumeCallback Interface.

原文链接: https://docs.microsoft.com/zh-cn/windows/desktop/CoreAudio/device-events

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的示例代码: ```csharp using System; using System.Collections.Generic; using System.Threading; using NAudio.CoreAudioApi; using NAudio.Wave; using System.Speech.Synthesis; namespace MultiAudioOutput { class Program { static void Main(string[] args) { // 获取所有音频设备 MMDeviceEnumerator enumerator = new MMDeviceEnumerator(); MMDeviceCollection devices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); // 创建一个文本到语音合成器 SpeechSynthesizer synthesizer = new SpeechSynthesizer(); synthesizer.SetOutputToDefaultAudioDevice(); // 创建多个输出线程 List<Thread> outputThreads = new List<Thread>(); foreach (MMDevice device in devices) { Thread outputThread = new Thread(() => { // 创建一个音频播放器 WaveOutEvent player = new WaveOutEvent(); player.DeviceNumber = device.DeviceNumber; // 在输出设备上播放语音 synthesizer.SetOutputToAudioStream(player); synthesizer.Speak("Hello, world!"); // 释放播放器并关闭线程 player.Dispose(); }); outputThreads.Add(outputThread); } // 启动所有输出线程 foreach (Thread outputThread in outputThreads) { outputThread.Start(); } // 等待所有输出线程结束 foreach (Thread outputThread in outputThreads) { outputThread.Join(); } } } } ``` 这个示例程序会获取所有活动的音频设备,然后创建多个线程并在每个线程上输出文本语音到不同的音频设备。注意,这个程序使用了 NAudio 库来处理音频设备和 WaveOut 事件。同时,为了防止多个线程同时占用同一个音频设备,我们在每个线程中创建一个新的 WaveOut 事件并将它与对应的音频设备绑定,这样就可以同时输出多个音频流了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值