1. 程式人生 > >iOS裝置上回聲消除的例子

iOS裝置上回聲消除的例子

工業上的聲音處理中,回聲消除是一個重要的話題,重要性不亞於噪聲消除、人聲放大、自動增益等,尤其是在VoIP功能上,回聲消除是每一個做VoIP功能團隊的必修課。QQ、Skype等等,回聲消除的效果是一個重要的考查指標。

具體的回聲消除演算法比較複雜,我現在還沒有研究的很明白。簡單來說,就是在即將播放出來的聲音中,將回聲的那部分減去。其中一個關鍵,是如何估計回聲大小,這需要用到自適應演算法。研究不透,多說無益。有興趣的同學可以一起學習。

  1. 將聲音輸出route到speaker,這樣聲音比較大,回聲明顯:

    AVAudioSession* session = [AVAudioSession sharedInstance];
    [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
    [session setActive:YES error:nil];
    
  2. 初始化一個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 subtype
    inputcd.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
    inputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
    
  3. //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 output
    streamFormat.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 callback
    AURenderCallbackStruct input;
    input.inputProc = InputCallback;
    input.inputProcRefCon = myStruct;
    CheckError(AudioUnitSetProperty(myStruct->remoteIOUnit,
                                kAudioUnitProperty_SetRenderCallback,
                                kAudioUnitScope_Global,
                                0,//input mic
                                &input,
                                sizeof(input)),
           "kAudioUnitProperty_SetRenderCallback failed");
    
  4. 在回撥函式inputCallback中,用AudioUnitRender() 函式獲取麥克風的聲音,存在一個bufferList中。這個bufferList是一個ring結構,儲存最新的聲音,然後播放舊聲音。這樣,聲音的輸入和輸出之間,就有了0.5s(可調節)左右的延遲,形成了明顯的回聲。

  5. 給回聲消除新增一個開關。VoiceProcessingIO有一個屬性可用來開啟/關閉回聲消除功能:kAUVoiceIOProperty_BypassVoiceProcessing

    UInt32 echoCancellation;
    UInt32 size = sizeof(echoCancellation);
    CheckError(AudioUnitGetProperty(myStruct.remoteIOUnit,
                                kAUVoiceIOProperty_BypassVoiceProcessing,
                                kAudioUnitScope_Global,
                                0,
                                &echoCancellation,
                                &size),
           "kAUVoiceIOProperty_BypassVoiceProcessing failed");
    
  6. 現在可以開始graph了:

    CheckError(AUGraphInitialize(graph),
           "AUGraphInitialize failed");
    CheckError(AUGraphStart(graph),
           "AUGraphStart failed");
    

    在示例中,有一個簡單的開關按鈕,可以明顯感覺到開啟/關閉回聲消除的區別。

    在實測中,打開回聲消除功能時,仍然能聽到一點點的回聲,不過很小,一般情況下足夠使用了。