做Mac的音頻采集播放,總免不了要處理過程中音頻設(shè)備插拔的問題稍算。而Mac并不像IOS一樣有好用的插拔通知可用典尾,只能用比較底層的接口,自己去處理邪蛔。
(為了簡(jiǎn)介,代碼里去掉了異常檢測(cè)代碼)
使用數(shù)據(jù)源檢測(cè)麥克風(fēng)插拔
- 注冊(cè)通知
///注冊(cè)input設(shè)備的插拔通知
-(void) checkMicByDataSource
{
//1.首先獲取默認(rèn)設(shè)備的ID
AudioDeviceID defaultDevice;
UInt32 defaultSize = sizeof( AudioDeviceID );
const AudioObjectPropertyAddress defaultAddr = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus status = 0;
status = AudioObjectGetPropertyData( kAudioObjectSystemObject, & defaultAddr, 0 , NULL, &defaultSize, &defaultDevice );
//2.聲明該設(shè)備要注冊(cè)的類型扎狱,input的數(shù)據(jù)源
AudioObjectPropertyAddress sourceAddr;
sourceAddr.mSelector = kAudioDevicePropertyDataSource;
sourceAddr.mScope = kAudioDevicePropertyScopeInput;
sourceAddr.mElement = kAudioObjectPropertyElementMaster;
//3.編寫通知的回調(diào)處理
inputChangeBlockData = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses){
//inAddresses是個(gè)列表侧到,要跳過不需要的
AudioObjectPropertyAddress* pAddr = (AudioObjectPropertyAddress*)inAddresses;
for( int i = 0 ;i < inNumberAddresses; i++, pAddr++ )
{
if( pAddr->mSelector != kAudioDevicePropertyDataSource )
{
continue;
}
//這里可以做自己的處理了
// [_self checkInputDeviceChange:false ];
}
};
//4.注冊(cè)通知
status = AudioObjectAddPropertyListenerBlock(defaultDevice, &sourceAddr, dispatch_get_current_queue(), inputChangeBlockData );
}
- 判斷插入還是拔出
//獲取DataSource的屬性,用dataSourceId與kIOAudioOutputPortSubType**比較來確定當(dāng)前的設(shè)備時(shí)啥淤击,從而判斷是插入了還是拔出了
status = AudioObjectGetPropertyData(defaultDevice, &sourceAddr, 0, NULL, &dataSourceIdSize, &dataSourceId);
if( dataSourceId == kIOAudioInputPortSubTypeExternalMicrophone )
{
//當(dāng)前是外部麥克風(fēng)匠抗,那就是插入了
}
else if( dataSourceId == kIOAudioInputPortSubTypeInternalMicrophone )
{
//當(dāng)前是內(nèi)部麥克風(fēng),那就是拔出了污抬。
}
- 刪除通知
-(void)stopCheckMicDataSource
//1.申請(qǐng)要移除的類型
AudioObjectPropertyAddress sourceAddr;
sourceAddr.mSelector = kAudioDevicePropertyDataSource;
sourceAddr.mScope = kAudioDevicePropertyScopeInput;
sourceAddr.mElement = kAudioObjectPropertyElementMaster;
//2.移除, inputDeviceID 注冊(cè)時(shí)的設(shè)備號(hào), inputChangeBlockData 注冊(cè)時(shí)的回調(diào)處理
AudioObjectRemovePropertyListenerBlock( inputDeviceID, &sourceAddr,dispatch_get_current_queue(), inputChangeBlockData );
注意
切換默認(rèn)設(shè)備就會(huì)引起這個(gè)通知汞贸,不一定真的就是插拔了。缺點(diǎn)
此方法需要一開始就能知道設(shè)備號(hào)印机。對(duì)于Mac Mini這樣沒有麥克風(fēng)的設(shè)備矢腻,如果一開始啟動(dòng)時(shí)沒有麥克風(fēng),會(huì)檢測(cè)不到麥克風(fēng)插入射赛。所以還需要補(bǔ)充下面方法多柑。
使用默認(rèn)設(shè)備ID檢測(cè)麥克風(fēng)插拔
如果原本沒有麥克風(fēng),默認(rèn)input設(shè)備號(hào)為0楣责,而插入設(shè)備后竣灌,設(shè)備號(hào)變成非0。這種情況秆麸,可以通過檢測(cè)默認(rèn)設(shè)備ID這個(gè)屬性的變化來得到通知初嘹。
- 注冊(cè)通知
-(void) checkMicByID
{
//1.聲明要注冊(cè)的是默認(rèn)輸入設(shè)備(DefaultInputDevice)
const AudioObjectPropertyAddress defaultAddr = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
//2.聲明回調(diào)處理函數(shù)
inputChangeBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses){
AudioObjectPropertyAddress* pAddr = (AudioObjectPropertyAddress*)inAddresses;
for( int i = 0 ;i < inNumberAddresses; i++, pAddr++ )
{
//跳過非需要的通知
if( pAddr->mSelector != kAudioHardwarePropertyDefaultInputDevice )
{
continue;
}
//對(duì)插拔的判斷和處理
// [_self checkInputDeviceChange: true ];
}
};
//3.注冊(cè)
AudioObjectAddPropertyListenerBlock(kAudioObjectSystemObject, &defaultAddr, dispatch_get_current_queue(), inputChangeBlock );
}
- 判斷插入拔出
//獲取當(dāng)前的input設(shè)備號(hào)
const AudioObjectPropertyAddress defaultAddr = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
status = AudioObjectGetPropertyData( kAudioObjectSystemObject, & defaultAddr, 0 , NULL, &defaultSize, &defaultDevice );
//獲取當(dāng)前input設(shè)備號(hào)的數(shù)據(jù)源
AudioObjectPropertyAddress sourceAddr;
sourceAddr.mSelector = kAudioDevicePropertyDataSource;
sourceAddr.mScope = kAudioDevicePropertyScopeInput;
sourceAddr.mElement = kAudioObjectPropertyElementMaster;
UInt32 dataSourceId = 0;
UInt32 dataSourceIdSize = sizeof(UInt32);
status = AudioObjectGetPropertyData(defaultDevice, &sourceAddr, 0, NULL, &dataSourceIdSize, &dataSourceId);
//根據(jù)數(shù)據(jù)源ID判斷當(dāng)前設(shè)備類型,從而推測(cè)是插入還是拔出,同前一個(gè)方法
if( dataSourceId == kIOAudioInputPortSubTypeExternalMicrophone )
{
}
else if( dataSourceId == kIOAudioInputPortSubTypeInternalMicrophone )
{
}
- 刪除通知
const AudioObjectPropertyAddress defaultInputAddr = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
AudioObjectRemovePropertyListenerBlock( kAudioObjectSystemObject, &defaultInputAddr,dispatch_get_current_queue(), inputChangeBlock );
揚(yáng)聲器和耳機(jī)的插拔檢測(cè)
方法同上沮趣,只需要把對(duì)應(yīng)的Input換成Output屯烦。設(shè)備默認(rèn)應(yīng)該都有揚(yáng)聲器,所以對(duì)于揚(yáng)聲器房铭,可以不用檢測(cè)默認(rèn)設(shè)備號(hào)的變更漫贞。
關(guān)于DataSource的說明
上面的方法中,是使用DataSource來判斷當(dāng)前設(shè)備類型育叁。但是并不是所有的設(shè)備都能獲取DataSource迅脐。比如虛擬設(shè)備,比如藍(lán)牙耳機(jī)豪嗽。
如何判斷藍(lán)牙耳機(jī)
DataSource的類型里谴蔑,并沒有藍(lán)牙相關(guān)的區(qū)分豌骏。而且唯一測(cè)試測(cè)藍(lán)牙耳機(jī)也取不到DataSource屬性。經(jīng)過查找隐锭,可以按一下方法判斷出藍(lán)牙窃躲。
int tran = 0;
AudioObjectPropertyAddress transportAddrIn;
transportAddrIn.mSelector = kAudioDevicePropertyTransportType;
transportAddrIn.mScope = kAudioDevicePropertyScopeInput;
transportAddrIn.mElement = kAudioObjectPropertyElementMaster;
tran = 0;
dataSourceIdSize = sizeof(UInt32);
status = AudioObjectGetPropertyData(defaultDevice, &transportAddrIn, 0, NULL, &dataSourceIdSize, &tran);
if( status == 0 )
{
switch( tran )
{
case kAudioDeviceTransportTypeBluetooth:
case kAudioDeviceTransportTypeBluetoothLE:
return true; //這里表示是藍(lán)牙
break;
default:
break;
}
}
遇到的問題
在整個(gè)過程中,遇到了很多問題钦睡,有的解決了蒂窒,有的還未解決。部分已在上面提到荞怒,剩下的列舉如下:
- 麥克風(fēng)拔出的通知不穩(wěn)定
當(dāng)使用默認(rèn)設(shè)備ID來檢測(cè)麥克風(fēng)的插拔時(shí)洒琢,麥克風(fēng)拔出可能觸發(fā)一次/兩次通知。在這個(gè)通知里去獲得默認(rèn)input設(shè)備號(hào)時(shí)褐桌,可能是0衰抑,可能是跟output設(shè)備一樣。從而可能引起判斷失誤荧嵌。 - 使用虛擬設(shè)備時(shí)呛踊,曾遇到AudioUnit啟動(dòng)或停止觸發(fā)設(shè)備變更的通知,復(fù)現(xiàn)幾次后不重現(xiàn)了啦撮。
- DataSource和默認(rèn)ID的兩種檢測(cè)方式谭网,只會(huì)有一個(gè)觸發(fā)。所以可以兩個(gè)都注冊(cè)上赃春。
- Mac mini 上反復(fù)插拔耳機(jī)很多次后蜻底,偶爾出現(xiàn)監(jiān)聽失效的情況。之后再怎么插拔都不會(huì)有通知聘鳞。且這個(gè)時(shí)候去獲取默認(rèn)設(shè)備薄辅,也認(rèn)為設(shè)備ID一直沒變。原因不明抠璃。
我是如何找到藍(lán)牙的判斷方法的
找到MAC的系統(tǒng)庫目錄站楚,在目錄里直接嘗試搜索"BlueTooth"這樣的相關(guān)關(guān)鍵詞。在找到的結(jié)果里去分析哪些有可能有用搏嗡。