如何检测耳机接入Windows系统

author:Leen



对于这个问题,网上很多人说处理WM_DEVICECHANGE消息即可。
于是快速建立个MFC小程序测试一下:
BEGIN_MESSAGE_MAP(CVolumeHelperDlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_DEVICECHANGE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

BOOL CVolumeHelperDlg::OnDeviceChange(UINT nEventType,DWORD_PTR dwData)
{
	DEV_BROADCAST_HDR* pdbh= NULL;
	switch(nEventType)
	{
	case DBT_DEVICEARRIVAL:
		pdbh = (DEV_BROADCAST_HDR*)(dwData);
		if(pdbh->dbch_devicetype == DBT_DEVTYP_VOLUME)
		{
			AfxMessageBox(_T("DBT_DEVTYP_VOLUME"));
		}
		break;
	case DBT_DEVICEREMOVECOMPLETE:
		AfxMessageBox(_T("DBT_DEVICEREMOVECOMPLETE"));
		break;
	default:
		break;
	}

	return TRUE;
}
实测发现有很多局限性,对于那种大个头的USB耳机来说会收的到事件,但是对于平时用的那种小插孔耳机完全没效果。。
难道Windows就无法检测耳机的插入么?
后来发现网上有人说要用 IMMNotificationClient接口

Minimum supported client

Windows Vista [desktop apps only]

但是这个接口怎么用呢?
在MSDN上找到个例子,可以监测耳机的声音,
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

#pragma once 

class CVolumeMonitor : IMMNotificationClient, IAudioEndpointVolumeCallback
{
private:
    BOOL                            m_bRegisteredForEndpointNotifications;
    BOOL                            m_bRegisteredForVolumeNotifications;
    CComPtr<IMMDeviceEnumerator>    m_spEnumerator;
    CComPtr<IMMDevice>              m_spAudioEndpoint;
    CComPtr<IAudioEndpointVolume>   m_spVolumeControl;
    CCriticalSection                m_csEndpoint;

    long                            m_cRef;

    ~CVolumeMonitor();       // refcounted object... make the destructor private
    HRESULT AttachToDefaultEndpoint();
    void    DetachFromEndpoint();


    // IMMNotificationClient (only need to really implement OnDefaultDeviceChanged)
	IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/);// {   return S_OK;    }
	IFACEMETHODIMP OnDeviceAdded(LPCWSTR /*pwstrDeviceId*/);// {   return S_OK;    }
    IFACEMETHODIMP OnDeviceRemoved(LPCWSTR /*pwstrDeviceId*/) {   return S_OK;    }
    IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId);   // ****
	IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/);// {   return S_OK;    }
    IFACEMETHODIMP OnDeviceQueryRemove()   {   return S_OK;    }
    IFACEMETHODIMP OnDeviceQueryRemoveFailed() {   return S_OK;    }
    IFACEMETHODIMP OnDeviceRemovePending() {   return S_OK;    }

    // IAudioEndpointVolumeCallback
    IFACEMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify);

    // IUnknown
    IFACEMETHODIMP QueryInterface(const IID& iid, void** ppUnk);

public:
    CVolumeMonitor();

    HRESULT Initialize();
    void    Dispose();
    HRESULT GetLevelInfo(VOLUME_INFO* pInfo);
    void    ChangeEndpoint();
	HRESULT PrintDeviceName(LPCWSTR pwstrDeviceId, const PROPERTYKEY key);


    // IUnknown
    IFACEMETHODIMP_(ULONG) AddRef();
    IFACEMETHODIMP_(ULONG) Release();
};

这个例子原本并没有实现HRESULT CVolumeMonitor::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key)这个接口
但是经过实测这个接口是可以收到耳机插拔事件的

HRESULT CVolumeMonitor::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
{
	PrintDeviceName(pwstrDeviceId, key);
	if (g_hwndOSD != NULL)
		PostMessage(g_hwndOSD, WM_ENDPOINTPROPERTYCHANGE, key.pid, 0);

	TCHAR szkey[2048] = { 0 };
	StringCbPrintf(szkey, RTL_NUMBER_OF(szkey),_T("-->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);
	OutputDebugString(szkey);

	return S_OK; 
}

但是这种方法有个弊端,无法知道是插入还是拔出,因为插入跟拔出都会收到一大堆的事件、。
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

#include "stdafx.h"
#include "osd.h"
#include "endpointMonitor.h"
#include <functiondiscoverykeys.h>
#include <strsafe.h>

CVolumeMonitor::CVolumeMonitor()
:   m_bRegisteredForEndpointNotifications(FALSE),
    m_bRegisteredForVolumeNotifications(FALSE),
    m_cRef(1)
{
}

CVolumeMonitor::~CVolumeMonitor()
{
}

// ----------------------------------------------------------------------
//  Call when the app is done with this object before calling release.
//  This detaches from the endpoint and releases all audio service references.
//
// ----------------------------------------------------------------------
void CVolumeMonitor::Dispose()
{
    DetachFromEndpoint();

    if (m_bRegisteredForEndpointNotifications)
    {
        m_spEnumerator->UnregisterEndpointNotificationCallback(this);
        m_bRegisteredForEndpointNotifications = FALSE;
    }
}

// ----------------------------------------------------------------------
//  Initialize this object.  Call after constructor.
//
// ----------------------------------------------------------------------
HRESULT CVolumeMonitor::Initialize()
{
    HRESULT hr;

    // create enumerator
    hr = m_spEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
    if (SUCCEEDED(hr))
    {
        hr = m_spEnumerator->RegisterEndpointNotificationCallback(this);
        if (SUCCEEDED(hr))
        {
            hr = AttachToDefaultEndpoint();
        }
    }

    return hr;
}

// ----------------------------------------------------------------------
//  Called from the UI thread when the volume is changed (see OSD.cpp 
//  WM_VOLUMECHANGE handler)
//
// ----------------------------------------------------------------------
HRESULT CVolumeMonitor::GetLevelInfo(VOLUME_INFO* pInfo)
{
    HRESULT hr = E_FAIL;

    m_csEndpoint.Enter();

    if (m_spVolumeControl != NULL)
    {
        hr = m_spVolumeControl->GetMute(&pInfo->bMuted);
        if (SUCCEEDED(hr))
        {
            hr = m_spVolumeControl->GetVolumeStepInfo(&pInfo->nStep, &pInfo->cSteps);
        }
    }

    m_csEndpoint.Leave();

    return hr;
}

// ----------------------------------------------------------------------
//  Start monitoring the current default device
//
// ----------------------------------------------------------------------
HRESULT CVolumeMonitor::AttachToDefaultEndpoint()
{
    m_csEndpoint.Enter();

    // get the default music & movies playback device
    HRESULT hr = m_spEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &m_spAudioEndpoint);
    if (SUCCEEDED(hr))
    {
        // get the volume control for it
        hr = m_spAudioEndpoint->Activate(__uuidof(m_spVolumeControl), CLSCTX_INPROC_SERVER, NULL, (void**)&m_spVolumeControl);
        if (SUCCEEDED(hr))
        {
            // register for callbacks
            hr = m_spVolumeControl->RegisterControlChangeNotify(this);
            m_bRegisteredForVolumeNotifications = SUCCEEDED(hr);
        }
    }

    m_csEndpoint.Leave();

    return hr;
}

// ----------------------------------------------------------------------
//  Stop monitoring the device and release all associated references
//
// ----------------------------------------------------------------------
void CVolumeMonitor::DetachFromEndpoint()
{
    m_csEndpoint.Enter();

    if (m_spVolumeControl != NULL)
    {
        // be sure to unregister...
        if (m_bRegisteredForVolumeNotifications)
        {
            m_spVolumeControl->UnregisterControlChangeNotify(this);
            m_bRegisteredForVolumeNotifications = FALSE;
        }

        m_spVolumeControl.Release();
    }

    if (m_spAudioEndpoint != NULL)
    {
        m_spAudioEndpoint.Release();
    }

    m_csEndpoint.Leave();
}

// ----------------------------------------------------------------------
//  Call this from the UI thread when the default device changes
//
// ----------------------------------------------------------------------
void CVolumeMonitor::ChangeEndpoint()
{
    DetachFromEndpoint();

    AttachToDefaultEndpoint();
}

// ----------------------------------------------------------------------
//  Implementation of IMMNotificationClient::OnDefaultDeviceChanged
//
//  When the user changes the default output device we want to stop monitoring the
//  former default and start monitoring the new default
//
// ----------------------------------------------------------------------
HRESULT CVolumeMonitor::OnDefaultDeviceChanged
(
    EDataFlow   flow, 
    ERole       /*role*/, 
    LPCWSTR     /*pwstrDefaultDeviceId*/
)
{
    if (flow == eRender)
    {
        if (g_hwndOSD != NULL)
            PostMessage(g_hwndOSD, WM_ENDPOINTCHANGE, 0, 0);
    }

    // return value of this callback is ignored
    return S_OK;
}

// ----------------------------------------------------------------------
//  Implementation of IAudioEndpointVolumeCallback::OnNotify
//
//  This is called by the audio core when anyone in any process changes the volume or 
//  mute state for the endpoint we are monitoring
//
// ----------------------------------------------------------------------
HRESULT CVolumeMonitor::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA /*pNotify*/)
{
    if (g_hwndOSD != NULL)
        PostMessage(g_hwndOSD, WM_VOLUMECHANGE, 0, 0);

    return S_OK;
}

HRESULT CVolumeMonitor::OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/)
{ 
	return S_OK; 
}
HRESULT CVolumeMonitor::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
{
	PrintDeviceName(pwstrDeviceId, key);
	if (g_hwndOSD != NULL)
		PostMessage(g_hwndOSD, WM_ENDPOINTPROPERTYCHANGE, key.pid, 0);

	TCHAR szkey[2048] = { 0 };
	StringCbPrintf(szkey, RTL_NUMBER_OF(szkey),_T("-->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);
	OutputDebugString(szkey);

	return S_OK; 
}

HRESULT CVolumeMonitor::OnDeviceAdded(LPCWSTR pwstrDeviceId)
{
	return S_OK;
}

HRESULT CVolumeMonitor::PrintDeviceName(LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
{
	HRESULT hr = S_OK;
	CComPtr<IMMDevice> pDevice = NULL;
	CComPtr<IPropertyStore> pProps = NULL;
	PROPVARIANT varString;

	CoInitialize(NULL);
	PropVariantInit(&varString);

	if (m_spEnumerator == NULL)
	{
		// Get enumerator for audio endpoint devices.
		hr = m_spEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
	}
	if (hr == S_OK)
	{
		hr = m_spEnumerator->GetDevice(pwstrDeviceId, &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_DeviceInterface_FriendlyName, &varString);
	}
	/*printf("----------------------\nDevice name: \"%S\"\n"
		"  Endpoint ID string: \"%S\"\n",
		(hr == S_OK) ? varString.pwszVal : L"null device",
		(pwstrDeviceId != NULL) ? pwstrDeviceId : L"null ID");*/

	PropVariantClear(&varString);

	return hr;
}



//  IUnknown methods

HRESULT CVolumeMonitor::QueryInterface(REFIID iid, void** ppUnk)
{
    if ((iid == __uuidof(IUnknown)) ||
        (iid == __uuidof(IMMNotificationClient)))
    {
        *ppUnk = static_cast<IMMNotificationClient*>(this);
    }
    else if (iid == __uuidof(IAudioEndpointVolumeCallback))
    {
        *ppUnk = static_cast<IAudioEndpointVolumeCallback*>(this);
    }
    else
    {
        *ppUnk = NULL;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}

ULONG CVolumeMonitor::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

ULONG CVolumeMonitor::Release()
{
    long lRef = InterlockedDecrement(&m_cRef);
    if (lRef == 0)
    {
        delete this;
    }
    return lRef;
}
最后只得投机取巧来实现接入耳机时放声音,拔出是静音。
case WM_ENDPOINTPROPERTYCHANGE:
		{
			DWORD time = GetTickCount();
			if ((time - g_Time) > 3000)
			{
				g_Time = time;
			}
			else
			{
				break;
			}
			switch (wParam)
			{
			case 0:
				VolumOffWithoutOsd();
				break;
			default:
				VolumeOnWithoutOsd();
				break;
			}
			return 0;
		}

//  Entry to the app
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR /*lpCmdLine*/, int /*nCmdShow*/)
{
    g_hInstance = hInstance;

    //  Mark that this process is DPI aware.
    SetProcessDPIAware();

    // Init COM and double-buffered painting
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        hr = BufferedPaintInit();
        g_bDblBuffered = SUCCEEDED(hr);

        // Init volume monitor
        g_pVolumeMonitor = new (std::nothrow) CVolumeMonitor();
        if (g_pVolumeMonitor)
        {
            hr = g_pVolumeMonitor->Initialize();
            if (SUCCEEDED(hr))
            {
                // Get initial volume level so that we can figure out a good window size
                g_pVolumeMonitor->GetLevelInfo(&g_currentVolume);

                WNDCLASSEX wcex = { sizeof(wcex) };
                wcex.style          = CS_HREDRAW | CS_VREDRAW;
                wcex.lpfnWndProc    = WndProc;
                wcex.hInstance      = g_hInstance;
                wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
                wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
                wcex.lpszClassName  = g_szWindowClass;

                RegisterClassEx(&wcex);

                // Create the (only) window
                DWORD const dwStyle = WS_POPUP;     // no border or title bar
                DWORD const dwStyleEx = WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_NOACTIVATE;   // transparent, topmost, with no taskbar item
                g_hwndOSD = CreateWindowEx(dwStyleEx, g_szWindowClass, NULL, dwStyle, 0, 0, 0, 0, NULL, NULL, g_hInstance, NULL);
                if (g_hwndOSD)
                {
                    // Hide the window
                    ShowWindow(g_hwndOSD, SW_HIDE);

                    // Main message loop
                    MSG msg;
                    while (GetMessage(&msg, NULL, 0, 0))
                    {
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                    }
                }

                if (g_bDblBuffered)
                    BufferedPaintUnInit();

                g_pVolumeMonitor->Dispose();
                g_pVolumeMonitor->Release();
            }
        }
        CoUninitialize();
    }

    return 0;
}

代码下载链接
点击打开链接


你可能感兴趣的:(如何检测耳机接入Windows系统)