標籤:ios 聲音 回聲消除
工業上的聲音處理中,回聲消除是一個重要的話題,重要性不亞於雜訊消除、人聲放大、自動增益等,尤其是在VoIP功能上,回聲消除是每一個做VoIP功能團隊的必修課。QQ、Skype等等,回聲消除的效果是一個重要的考查指標。
具體的回聲消除演算法比較複雜,我現在還沒有研究的很明白。簡單來說,就是在即將播放出來的聲音中,將回聲的那部分減去。其中一個關鍵,是如何估計回聲大小,這需要用到自適應演算法。研究不透,多說無益。有興趣的同學可以一起學習。
Apple在Core Audio中提供了回聲消除的介面,我寫了一個測試APP,測試了其效果。連結:https://github.com/lixing123/iOSEchoCancellation
下面講一下如何?。
將聲音輸出route到speaker,這樣聲音比較大,回聲明顯:
AVAudioSession* session = [AVAudioSession sharedInstance];[session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];[session setActive:YES error:nil];
初始化一個AUGraph,建立一個AUNode,並將之添加到graph上。一般來說,溝通麥克風/擴音器的AUNode,其類型應該是RemoteIO,但是RemoteIO不帶回聲消除功能,VoiceProcessingIO類型的才帶。
AudioComponentDescription inputcd = {0};inputcd.componentType = kAudioUnitType_Output;//inputcd.componentSubType = kAudioUnitSubType_RemoteIO;//we can access the system‘s echo cancellation by using kAudioUnitSubType_VoiceProcessingIO subtypeinputcd.componentSubType = kAudioUnitSubType_VoiceProcessingIO;inputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
配置AudioUnit的屬性,開啟與麥克風/擴音器的串連(這個比較難以理解,可以參考Apple文檔:https://developer.apple.com/library/ios/documentation/MusicAudio/Conceptual/AudioUnitHostingGuide_iOS/UsingSpecificAudioUnits/UsingSpecificAudioUnits.html),並配置client data format(僅支援Linear PCM格式);配置回呼函數。
//Open input of the bus 1(input mic)UInt32 enableFlag = 1;CheckError(AudioUnitSetProperty(myStruct->remoteIOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &enableFlag, sizeof(enableFlag)), "Open input of bus 1 failed");//Open output of bus 0(output speaker)CheckError(AudioUnitSetProperty(myStruct->remoteIOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &enableFlag, sizeof(enableFlag)), "Open output of bus 0 failed");//Set up stream format for input and outputstreamFormat.mFormatID = kAudioFormatLinearPCM;streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;streamFormat.mSampleRate = 44100;streamFormat.mFramesPerPacket = 1;streamFormat.mBytesPerFrame = 2;streamFormat.mBytesPerPacket = 2;streamFormat.mBitsPerChannel = 16;streamFormat.mChannelsPerFrame = 1;CheckError(AudioUnitSetProperty(myStruct->remoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(streamFormat)), "kAudioUnitProperty_StreamFormat of bus 0 failed");CheckError(AudioUnitSetProperty(myStruct->remoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &streamFormat, sizeof(streamFormat)), "kAudioUnitProperty_StreamFormat of bus 1 failed");//Set up input callbackAURenderCallbackStruct input;input.inputProc = InputCallback;input.inputProcRefCon = myStruct;CheckError(AudioUnitSetProperty(myStruct->remoteIOUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0,//input mic &input, sizeof(input)), "kAudioUnitProperty_SetRenderCallback failed");
在回呼函數inputCallback中,用AudioUnitRender() 函數擷取麥克風的聲音,存在一個bufferList中。這個bufferList是一個ring結構,儲存最新的聲音,然後播放舊聲音。這樣,聲音的輸入和輸出之間,就有了0.5s(可調節)左右的延遲,形成了明顯的回聲。
給回聲消除添加一個開關。VoiceProcessingIO有一個屬性可用來開啟/關閉回聲消除功能:kAUVoiceIOProperty_BypassVoiceProcessing
UInt32 echoCancellation;UInt32 size = sizeof(echoCancellation);CheckError(AudioUnitGetProperty(myStruct.remoteIOUnit, kAUVoiceIOProperty_BypassVoiceProcessing, kAudioUnitScope_Global, 0, &echoCancellation, &size), "kAUVoiceIOProperty_BypassVoiceProcessing failed");
現在可以開始graph了:
CheckError(AUGraphInitialize(graph), "AUGraphInitialize failed");CheckError(AUGraphStart(graph), "AUGraphStart failed");
在樣本中,有一個簡單的開關按鈕,可以明顯感覺到開啟/關閉回聲消除的區別。
在實測中,開啟回聲消除功能時,仍然能聽到一點點的回聲,不過很小,一般情況下足夠使用了。
iOS裝置上回聲消除的例子