网上不少人问如何通过代码去检测耳机的插入与拔出,但网上基本找不到有效的、完整的代码去实现对耳机的检测。最近我们的项目中也遇到这样的需求,所以在网上搜集了大量的资料,综合了多篇文章的内容,找到了一种实时检测耳机插拔的有效方法。在此和大家分享一下相关的代码,供大家参考,如果有更好的办法,可以在评论区留言。
我们讲的这种耳机(扬声器)是插入电脑3.5mm(毫米)插孔的耳机,本文中也是对3.5mm插孔插入的耳机进行实时检测。
首先我们要编写一个继承于IMMNotificationClient音视频设备通知接口类的类CMMNotificationClient,用来实时感知音视频设备变化的通知事件。 由于 IMMNotificationClient 继承自 IUnknown纯虚接口类,所以在CMMNotificationClient 类中要实现IUnknown接口类的AddRef、 Release 和 QueryInterface 等接口。
此外,CMMNotificationClient类中还要实现特定于 IMMNotificationClient 接口类的OnDefaultDeviceChanged 、OnDeviceAdded、OnDeviceRemoved、OnDeviceStateChanged和OnPropertyValueChanged等接口。这些接口的含义在MSDN在线网站上有详细的说明:
设备事件 (核心音频 Api)https://docs.microsoft.com/zh-cn/windows/win32/coreaudio/device-events?redirectedfrom=MSDN在MSDN该网页上,也给出了继承于接口类IMMNotificationClient的CMMNotificationClient类的实现,但MSDN上给出的示例代码是有问题的,代码中并没有初始化音视频COM组件的操作。
下面给出CMMNotificationClient类的完整实现。CMMNotificationClient类的头文件如下:
#pragma once
#define SAFE_RELEASE(punk) \
if ((punk) != NULL) \
{ (punk)->Release(); (punk) = NULL; }
#include
#include "iostream"
using namespace std;
#include "Functiondiscoverykeys_devpkey.h"
//-----------------------------------------------------------
// Example implementation of IMMNotificationClient interface.
// When the status of audio endpoint devices change, the
// MMDevice module calls these methods to notify the client.
//-----------------------------------------------------------
class CMMNotificationClient : public IMMNotificationClient
{
LONG _cRef;
IMMDeviceEnumerator *_pEnumerator;
BOOL m_bHave;
Private function to print device-friendly name
//HRESULT _PrintDeviceName(LPCWSTR pwstrId);
public:
CMMNotificationClient() :
_cRef(1),
_pEnumerator(NULL),
m_bHave(FALSE)
{
//初始化COM
::CoInitialize(NULL);
HRESULT hr = S_OK;
//创建接口
hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
(void**)&_pEnumerator);
if (hr == S_OK)
{
cout << "接口创建成功" << endl;
}
else
{
cout << "接口创建失败" << endl;
}
//注册事件
hr = _pEnumerator->RegisterEndpointNotificationCallback((IMMNotificationClient*)this);
if (hr == S_OK)
{
cout << "注册成功" << endl;
}
else
{
cout << "注册失败" << endl;
}
}
~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 = "?????";
//CString strName;
//_PrintDeviceName(pwstrDeviceId, strName);
//CString strLog = _T("[OnDefaultDeviceChanged]");
//strLog += strName;
AfxMessageBox( strLog );
//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)
{
//CString strName;
//_PrintDeviceName(pwstrDeviceId, strName);
//CString strLog = _T("[OnDeviceAdded]");
//strLog += strName;
AfxMessageBox( strLog );
//printf(" -->Added device\n");
return S_OK;
};
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)
{
// CString strName;
// _PrintDeviceName(pwstrDeviceId, strName);
// CString strLog = _T("[OnDeviceRemoved]");
// strLog += strName;
AfxMessageBox( strLog );
// printf(" -->Removed device\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
LPCWSTR pwstrDeviceId,
DWORD dwNewState)
{
//WCHAR* pszState = L"???????????????";
//CString strName;
//_PrintDeviceName(pwstrDeviceId, strName);
//CString strLog = _T("[OnDeviceStateChanged]");
//strLog += strName;
//
//switch (dwNewState)
//{
//case DEVICE_STATE_ACTIVE:
// pszState = L"ACTIVE";
// break;
//case DEVICE_STATE_DISABLED:
// pszState = L"DISABLED";
// break;
//case DEVICE_STATE_NOTPRESENT:
// pszState = L"NOTPRESENT";
// break;
//case DEVICE_STATE_UNPLUGGED:
// pszState = L"UNPLUGGED";
// break;
//}
//strLog += pszState;
AfxMessageBox( strLog );
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);
BOOL IsCurInsertEarPhone(CUIString& strDevId);
Given an endpoint ID string, print the friendly device name.
//HRESULT _PrintDeviceName(LPCWSTR pwstrId, CString& strName )
//{
// 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);
// }
// //CString str;
// strName.Format(_T("**Device name: %s\n Endpoint ID string: %s\n"),
// (hr == S_OK) ? varString.pwszVal : L"null device",
// (pwstrId != NULL) ? pwstrId : L"null ID");
// PROPVARIANT varString2;
// PropVariantInit(&varString2);
// hr = pProps->GetValue(PKEY_Device_DevType, &varString2);
// CString strType;
// strType.Format(_T("(nType = %d)"), varString2.ulVal );
// strName += strType;
// PropVariantClear(&varString2);
// PropVariantClear(&varString);
// SAFE_RELEASE(pProps)
// SAFE_RELEASE(pDevice)
// CoUninitialize();
// return hr;
//}
};
CMMNotificationClient类的cpp源文件如下:
#include "stdafx.h"
#include "mmnotifyclient.h"
#include "mainlogic.h"
#include "devicetopology.h"
HRESULT STDMETHODCALLTYPE CMMNotificationClient::OnPropertyValueChanged(
LPCWSTR pwstrDeviceId,
const PROPERTYKEY key)
{
// CString strName;
// _PrintDeviceName(pwstrDeviceId, strName);
// CString strLog = _T("[OnPropertyValueChanged]");
// strLog += strName;
AfxMessageBox( strLog );
//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);
if ( !GetChatPtr()->IsMainUISwitchSuccess() )
{
return S_OK;
}
CString strDevId;
BOOL bHave = IsCurInsertEarPhone(strDevId);
CUIString strLog;
strLog.Format(_T("[CMMNotificationClient::OnPropertyValueChanged] m_bHave:%d,bHave:%d ."), m_bHave, bHave);
WriteLog(strLog);
if (!m_bHave && bHave)
{
MessageBox(NULL, _T("耳机插入"), _T("提示"), MB_OK);
}
else if (m_bHave && !bHave)
{
MessageBox(NULL, _T("耳机拔出"), _T("提示"), MB_OK);
}
m_bHave = bHave;
//CStringA str;
//str.Format("pwstrDeviceId: %d");
//MessageBoxA(NULL, str, "Tip", MB_OK);
return S_OK;
}
BOOL CMMNotificationClient::IsCurInsertEarPhone(CString& strDevId)
{
IKsJackDescription *pJackDesc = NULL;
HRESULT hr = S_OK;
IMMDevice *pDevice = NULL;
IPropertyStore *pProps = NULL;
PROPVARIANT varString;
IDeviceTopology *pDeviceTopology = NULL;
IConnector *pConnFrom = NULL;
IConnector *pConnTo = NULL;
IPart *pPart = NULL;
//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);
}
IMMDeviceCollection *pMultiDevice = NULL;
hr = _pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pMultiDevice);
if (FAILED(hr))
return FALSE;
UINT deviceCount = 0;
hr = pMultiDevice->GetCount(&deviceCount);
if (FAILED(hr))
return FALSE;
for (UINT ii = 0; ii < deviceCount; ii++)
{
pDevice = NULL;
hr = pMultiDevice->Item(ii, &pDevice);
if (FAILED(hr))
{
continue;
}
hr = pDevice->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL,
NULL, (void**)&pDeviceTopology);
if (FAILED(hr))
{
SAFE_RELEASE(pDevice)
continue;
}
// The device topology for an endpoint device always
// contains just one connector (connector number 0).
hr = pDeviceTopology->GetConnector(0, &pConnFrom);
if (FAILED(hr))
{
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
continue;
}
// Step across the connection to the jack on the adapter.
hr = pConnFrom->GetConnectedTo(&pConnTo);
if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr)
{
// The adapter device is not currently active.
hr = E_NOINTERFACE;
}
if (FAILED(hr))
{
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
continue;
}
// Get the connector's IPart interface.
hr = pConnTo->QueryInterface(__uuidof(IPart), (void**)&pPart);
if (FAILED(hr))
{
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
continue;
}
//获取描述外接设备信息
hr = pPart->Activate(CLSCTX_INPROC_SERVER, __uuidof(IKsJackDescription), (void**)&pJackDesc);
if (FAILED(hr))
{
SAFE_RELEASE(pPart)
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
continue;
}
UINT num = 0;
pJackDesc->GetJackCount(&num);
//wprintf_s(_T("GetJackCount: %d\n"), num);
for (UINT j = 0; j < num; ++j)
{
KSJACK_DESCRIPTION ksjack_desc = { 0 };
hr = pJackDesc->GetJackDescription(j, &ksjack_desc);
if (hr == S_OK)
{
//wprintf_s(_T("Jack%d PortConnection: %d\n"), j + 1, ksjack_desc.PortConnection); //端口类型
//wprintf_s(_T("Jack%d ConnectionType: %d\n"), j + 1, ksjack_desc.ConnectionType); //连接类型
//wprintf_s(_T("Jack%d IsConnected: %d\n\n"), j + 1, ksjack_desc.IsConnected); //连接状态
if (ksjack_desc.IsConnected)
{
if (ksjack_desc.ConnectionType == eConnType3Point5mm)//连接类型
{
WCHAR achId[512] = { 0 };
WCHAR* pId = achId;
pDevice->GetId(&pId);
strDevId = pId;
SAFE_RELEASE(pJackDesc)
SAFE_RELEASE(pPart)
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
SAFE_RELEASE(pMultiDevice)
return 1;
}
}
}
}
SAFE_RELEASE(pJackDesc)
SAFE_RELEASE(pPart)
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
}
SAFE_RELEASE(pJackDesc)
SAFE_RELEASE(pPart)
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
SAFE_RELEASE(pMultiDevice)
return 0;
}
经过反复的实验和测试,当插入或拔出耳机时,在OnPropertyValueChanged接口中始终都有回调,所以就是通过该接口的通知去实现检测的。
上述代码中包含了实时检测耳机插拔的完整代码,可以直接拿去使用。下面我们就来针对代码中的细节作详细的说明。
CMMNotificationClient类的构造函数如下:
CMMNotificationClient() :
_cRef(1),
_pEnumerator(NULL),
m_bHave(FALSE)
{
//初始化COM
::CoInitialize(NULL);
HRESULT hr = S_OK;
//创建接口
hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
(void**)&_pEnumerator);
if (hr == S_OK)
{
cout << "接口创建成功" << endl;
}
else
{
cout << "接口创建失败" << endl;
}
//注册事件
hr = _pEnumerator->RegisterEndpointNotificationCallback((IMMNotificationClient*)this);
if (hr == S_OK)
{
cout << "注册成功" << endl;
}
else
{
cout << "注册失败" << endl;
}
}
在CMMNotificationClient类的构造函数中,先创建IMMDeviceEnumerator COM接口,并把当前的CMMNotificationClient类作为回调类设置到系统中,这样系统中有音视频设备变化时,就会回调CMMNotificationClient类中的OnDefaultDeviceChanged 、OnDeviceAdded、OnDeviceRemoved、OnDeviceStateChanged和OnPropertyValueChanged等接口了。
我们在将CMMNotificationClient类引入到代码后,只需要在代码中定义一个CMMNotificationClient类对象即可。
经实际测试发现,当插入或拔出耳机时,会多次回调OnPropertyValueChanged接口,回调接口中回调的信息中只包含设备id,根本没有设备类型的信息,也是没法判断哪个消息是和耳机插入与拔出有关系的。
我们通过搜集资料得知,可以通过遍历系统的音频接口上外设信息,就能知道在3.5mm的接口上插入耳机了。获取存放外设信息的结构体KSJACK_DESCRIPTION,其定义如下:
typedef /* [public][public] */ struct __MIDL___MIDL_itf_devicetopology_0000_0000_0009
{
DWORD ChannelMapping;
COLORREF Color;
EPcxConnectionType ConnectionType; // 设备类型
EPcxGeoLocation GeoLocation;
EPcxGenLocation GenLocation;
EPxcPortConnection PortConnection;
BOOL IsConnected; // 设备是否连接上
} KSJACK_DESCRIPTION;
通过上述结构体中的ConnectionType字段判断当前外设是不是有3.5mm插孔耳机,通过IsConnected字段判断耳机有没有连接上,相关代码如下所示:
BOOL CMMNotificationClient::IsCurInsertEarPhone(CString& strDevId)
{
IKsJackDescription *pJackDesc = NULL;
HRESULT hr = S_OK;
IMMDevice *pDevice = NULL;
IPropertyStore *pProps = NULL;
PROPVARIANT varString;
IDeviceTopology *pDeviceTopology = NULL;
IConnector *pConnFrom = NULL;
IConnector *pConnTo = NULL;
IPart *pPart = NULL;
//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);
}
IMMDeviceCollection *pMultiDevice = NULL;
hr = _pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pMultiDevice);
if (FAILED(hr))
return FALSE;
UINT deviceCount = 0;
hr = pMultiDevice->GetCount(&deviceCount);
if (FAILED(hr))
return FALSE;
for (UINT ii = 0; ii < deviceCount; ii++)
{
pDevice = NULL;
hr = pMultiDevice->Item(ii, &pDevice);
if (FAILED(hr))
{
continue;
}
hr = pDevice->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL,
NULL, (void**)&pDeviceTopology);
if (FAILED(hr))
{
SAFE_RELEASE(pDevice)
continue;
}
// The device topology for an endpoint device always
// contains just one connector (connector number 0).
hr = pDeviceTopology->GetConnector(0, &pConnFrom);
if (FAILED(hr))
{
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
continue;
}
// Step across the connection to the jack on the adapter.
hr = pConnFrom->GetConnectedTo(&pConnTo);
if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr)
{
// The adapter device is not currently active.
hr = E_NOINTERFACE;
}
if (FAILED(hr))
{
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
continue;
}
// Get the connector's IPart interface.
hr = pConnTo->QueryInterface(__uuidof(IPart), (void**)&pPart);
if (FAILED(hr))
{
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
continue;
}
//获取描述外接设备信息
hr = pPart->Activate(CLSCTX_INPROC_SERVER, __uuidof(IKsJackDescription), (void**)&pJackDesc);
if (FAILED(hr))
{
SAFE_RELEASE(pPart)
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
continue;
}
UINT num = 0;
pJackDesc->GetJackCount(&num);
//wprintf_s(_T("GetJackCount: %d\n"), num);
for (UINT j = 0; j < num; ++j)
{
KSJACK_DESCRIPTION ksjack_desc = { 0 };
hr = pJackDesc->GetJackDescription(j, &ksjack_desc);
if (hr == S_OK)
{
//wprintf_s(_T("Jack%d PortConnection: %d\n"), j + 1, ksjack_desc.PortConnection); //端口类型
//wprintf_s(_T("Jack%d ConnectionType: %d\n"), j + 1, ksjack_desc.ConnectionType); //连接类型
//wprintf_s(_T("Jack%d IsConnected: %d\n\n"), j + 1, ksjack_desc.IsConnected); //连接状态
if (ksjack_desc.IsConnected)
{
if (ksjack_desc.ConnectionType == eConnType3Point5mm)//连接类型
{
WCHAR achId[512] = { 0 };
WCHAR* pId = achId;
pDevice->GetId(&pId);
strDevId = pId;
SAFE_RELEASE(pJackDesc)
SAFE_RELEASE(pPart)
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
SAFE_RELEASE(pMultiDevice)
return 1;
}
}
}
}
SAFE_RELEASE(pJackDesc)
SAFE_RELEASE(pPart)
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
}
SAFE_RELEASE(pJackDesc)
SAFE_RELEASE(pPart)
SAFE_RELEASE(pConnTo)
SAFE_RELEASE(pConnFrom)
SAFE_RELEASE(pDeviceTopology)
SAFE_RELEASE(pDevice)
SAFE_RELEASE(pMultiDevice)
return 0;
}
CMMNotificationClient::IsCurInsertEarPhone接口只能判断耳机是否连入到系统中,但无法判断耳机是插入还是拔出了,所以我们要记录耳机的连接状态,如果是从未连接切换到连接状态,则表示是插入耳机;如果是从连接状态切换到未连接状态,则表示是拔出耳机。相关代码如下:
CString strDevId;
BOOL bHave = IsCurInsertEarPhone(strDevId);
CUIString strLog;
strLog.Format(_T("[CMMNotificationClient::OnPropertyValueChanged] m_bHave:%d,bHave:%d ."), m_bHave, bHave);
WriteLog(strLog);
if (!m_bHave && bHave)
{
MessageBox(NULL, _T("耳机插入"), _T("提示"), MB_OK);
}
else if (m_bHave && !bHave)
{
MessageBox(NULL, _T("耳机拔出"), _T("提示"), MB_OK);
}
当耳机插入或者拔出时,都会触发OnPropertyValueChanged接口的回调,我们就在这个接口中调用IsCurInsertEarPhone接口查看耳机的来连接状态,然后根据状态的记录,判断是插入耳机还是拔出耳机。
其实目前这种检测方法,其实是有点牵强的,因为插入或者拔出一次耳机时会触发OnPropertyValueChanged函数的多次调用,我们只能在该函数中以轮询的方式去调用IsCurInsertEarPhone接口去进行耳机插拔的检测的,所以目前这种检测效率也要稍差一点的。