iOS音頻編程之變聲處理

需求:耳塞Mic實時錄音,變聲處理后實時輸出


初始化

程序使用44100HZ的頻率對原始的音頻數(shù)據(jù)進行采樣,并在音頻輸入的回調(diào)中處理采樣的數(shù)據(jù)。

  1. 對AVAudioSession的一些設置
<!-- more -->

NSError *error;
self.session = [AVAudioSession sharedInstance];
[self.session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
handleError(error);
//route變化監(jiān)聽
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionRouteChangeHandle:) name:AVAudioSessionRouteChangeNotification object:self.session];
[self.session setPreferredIOBufferDuration:0.005 error:&error];
handleError(error);
[self.session setPreferredSampleRate:kSmaple error:&error];
handleError(error);

[self.session setActive:YES error:&error];
handleError(error);

setPreferredIOBufferDurations文檔上解釋change to the I/O buffer duration,具體解釋參看官方文檔。我把它理解為在每次調(diào)用輸入或輸出的回調(diào),能提供多長時間(由設置的這個值決定)的音頻數(shù)據(jù)。
setPreferredSampleRate設置對音頻數(shù)據(jù)的采樣率。

  1. 獲取AudioComponentInstance
//Obtain a RemoteIO unit instance
AudioComponentDescription acd;
acd.componentType = kAudioUnitType_Output;
acd.componentSubType = kAudioUnitSubType_RemoteIO;
acd.componentFlags = 0;
acd.componentFlagsMask = 0;
acd.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &acd);
AudioComponentInstanceNew(inputComponent, &_toneUnit);
  1. 對AudioComponentInstance的一些初始化設置

這張圖藍色框中的部分就是一個I/O Unit(AudioComponentInstance的實例).圖中的Element 0連接Speaker,也叫Output Bus;Element 1連接Mic,也叫Input Bus.初始化它,就是對再這些Bus上的音頻流的格式,設置輸入輸出的回調(diào)函數(shù)等。

UInt32 enable = 1;
AudioUnitSetProperty(_toneUnit,
                     kAudioOutputUnitProperty_EnableIO,
                     kAudioUnitScope_Input,
                     kInputBus,
                     &enable,
                     sizeof(enable));
AudioUnitSetProperty(_toneUnit,
                     kAudioOutputUnitProperty_EnableIO,
                     kAudioUnitScope_Output,
                     kOutoutBus, &enable, sizeof(enable));

mAudioFormat.mSampleRate         = kSmaple;//采樣率
mAudioFormat.mFormatID           = kAudioFormatLinearPCM;//PCM采樣
mAudioFormat.mFormatFlags        = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
mAudioFormat.mFramesPerPacket    = 1;//每個數(shù)據(jù)包多少幀
mAudioFormat.mChannelsPerFrame   = 1;//1單聲道,2立體聲
mAudioFormat.mBitsPerChannel     = 16;//語音每采樣點占用位數(shù)
mAudioFormat.mBytesPerFrame      = mAudioFormat.mBitsPerChannel*mAudioFormat.mChannelsPerFrame/8;//每幀的bytes數(shù)
mAudioFormat.mBytesPerPacket     = mAudioFormat.mBytesPerFrame*mAudioFormat.mFramesPerPacket;//每個數(shù)據(jù)包的bytes總數(shù),每幀的bytes數(shù)*每個數(shù)據(jù)包的幀數(shù)
mAudioFormat.mReserved           = 0;

CheckError(AudioUnitSetProperty(_toneUnit,
                                kAudioUnitProperty_StreamFormat,
                                kAudioUnitScope_Input, kOutoutBus,
                                &mAudioFormat, sizeof(mAudioFormat)),
           "couldn't set the remote I/O unit's output client format");
CheckError(AudioUnitSetProperty(_toneUnit,
                                kAudioUnitProperty_StreamFormat,
                                kAudioUnitScope_Output, kInputBus,
                                &mAudioFormat, sizeof(mAudioFormat)),
           "couldn't set the remote I/O unit's input client format");

CheckError(AudioUnitSetProperty(_toneUnit,
                                kAudioOutputUnitProperty_SetInputCallback,
                                kAudioUnitScope_Output,
                                kInputBus,
                                &_inputProc, sizeof(_inputProc)),
           "couldnt set remote i/o render callback for input");

CheckError(AudioUnitSetProperty(_toneUnit,
                                kAudioUnitProperty_SetRenderCallback,
                                kAudioUnitScope_Input,
                                kOutoutBus,
                                &_outputProc, sizeof(_outputProc)),
           "couldnt set remote i/o render callback for output");

CheckError(AudioUnitInitialize(_toneUnit),
           "couldn't initialize the remote I/O unit");

注意kAudioUnitScope_Output/kAudioUnitScope_InputkOutput/kInput的組合。設置輸入輸出使能時,Scope_Input下的kInput直接和Mic相連,所以是選用它們兩;設置輸出使能也類似。而設置音頻的格式時,要選用Scope_Input下的kOutput和Scope_OutPut下的kInput,如果組合錯誤,為會返回-10865的錯誤碼,意思說設置了只讀屬性,而在官方文檔中也有說明,This hardware connection—at the input scope of element 1—is opaque to you. Your first access to audio data entering from the input hardware is at the output scope of element 1, output scope of element 0 is opaque。(疑問?在設置輸入輸出回調(diào)時以及Scope選擇Input和Output以及Global都可以,但是官方文檔中說Your first access to audio data entering from the input hardware is at the output scope of element 1)

音頻處理

預備知識

變聲操作實際是對聲音信號的頻譜進行搬移,在時域中乘以一個三角函數(shù)相當于在頻域上進行了頻譜的搬移。但使得頻譜搬移了±??。由下圖傅里葉變化公式說明


頻譜搬移后,要把搬移的F(w-w。)的部分濾除。將聲音的原始PCM放到Matlab中分析出頻譜,然后進行搬移(實際上,我濾波這一步是失敗的,還請小伙伴們告知我應該選一個怎樣的濾波器)

  1. 寫一個專門手機原始聲音數(shù)據(jù)的程序,將聲音數(shù)據(jù)保存到模擬上(用模擬器收集的聲音,方便直接將寫入到沙盒中的文件拷出來)。

  2. 將聲音數(shù)據(jù)用matlab讀出來(注意模擬器和matlab處理數(shù)據(jù)時的大小端,專門把數(shù)據(jù)轉(zhuǎn)換讀出來看了,兩邊都應該是小端模式),并分析和頻移其頻譜。

matlab代碼

FID=fopen('record.txt','r');
fseek(FID,0,'eof');
len=ftell(FID);
frewind(FID);
A=fread(FID,len/2,'short');
A=A*1.0-mean(A);
Y=fft(A);
Fs=44100;
f=Fs*(0:length(A)/2 - 1)/length(A);
subplot(211);
plot(f,abs(Y(1:length(A)/2)));
k=0:length(A)-1;
cos_y=cos(2*pi*1000*k/44100);
cos_y=cos_y';
A2=A.*cos_y;
Y2=fft(A2);
subplot(212);
plot(f,abs(Y2(1:length(A)/2)));

原始信號的頻譜從0頻開始?頻率1000Hz后,慮除的就是小于1000hz的頻率?實際在我的程序中對頻譜只進行了200hz的搬移,那選一個大于200hz的IIR高通濾波器?

  1. 用matlab設計濾波器,并得到濾波器參數(shù).我用matlab的fdatool工具設計了一個5階的IIR高通濾波器,截止頻率為200hz。導出參數(shù),用[Bb,Ba]=sos2tf(SOS,G);得出濾波器參數(shù)。

  2. 得到的Bb和Ba參數(shù)后,可以直接代入輸入輸出的差分方程得出濾波器的輸出y(n)。

音頻輸入輸出回調(diào)函數(shù)處理

  1. 輸入回調(diào)
OSStatus inputRenderTone(
                     void *inRefCon,
                     AudioUnitRenderActionFlags     *ioActionFlags,
                     const AudioTimeStamp         *inTimeStamp,
                     UInt32                         inBusNumber,
                     UInt32                         inNumberFrames,
                     AudioBufferList             *ioData)

{
ViewController *THIS=(__bridge ViewController*)inRefCon;

AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mData = NULL;
bufferList.mBuffers[0].mDataByteSize = 0;
OSStatus status = AudioUnitRender(THIS->_toneUnit,
                                  ioActionFlags,
                                  inTimeStamp,
                                  kInputBus,
                                  inNumberFrames,
                                  &bufferList);

SInt16 *rece = (SInt16 *)bufferList.mBuffers[0].mData;
for (int i = 0; i < inNumberFrames; i++) {
    rece[i] = rece[i]*THIS->_convertCos[i];//頻譜搬移
}

RawData *rawData = &THIS->_rawData;
//距離最大位置還有mDataByteSize/2 那就直接memcpy,否則要一個一個字節(jié)拷貝
if((rawData->rear+bufferList.mBuffers[0].mDataByteSize/2) <= kRawDataLen){
    memcpy((uint8_t *)&(rawData->receiveRawData[rawData->rear]), bufferList.mBuffers[0].mData, bufferList.mBuffers[0].mDataByteSize);
    rawData->rear = (rawData->rear+bufferList.mBuffers[0].mDataByteSize/2);
}else{
    uint8_t *pIOdata = (uint8_t *)bufferList.mBuffers[0].mData;
    for (int i = 0; i < rawData->rear+bufferList.mBuffers[0].mDataByteSize; i+=2) {
        SInt16 data = pIOdata[i] | pIOdata[i+1]<<8;
        rawData->receiveRawData[rawData->rear] = data;
        rawData->rear = (rawData->rear+1)%kRawDataLen;
    }
}
return status;
}

在頻移的處理時,本來要對頻移后的序列濾波的,但是濾波后,全部是雜音,所以刪除掉了這部分代碼,在提供的完整代碼中有這部分刪除掉的代碼。存儲數(shù)據(jù)中循環(huán)隊列來存。

  1. 輸出回調(diào)
OSStatus outputRenderTone(
                      void *inRefCon,
                      AudioUnitRenderActionFlags     *ioActionFlags,
                      const AudioTimeStamp         *inTimeStamp,
                      UInt32                         inBusNumber,
                      UInt32                         inNumberFrames,
                      AudioBufferList             *ioData)

{
ViewController *THIS=(__bridge ViewController*)inRefCon;

SInt16 *outSamplesChannelLeft   = (SInt16 *)ioData->mBuffers[0].mData;
RawData *rawData = &THIS->_rawData;
for (UInt32 frameNumber = 0; frameNumber < inNumberFrames; ++frameNumber) {
   if (rawData->front != rawData->rear) {
        outSamplesChannelLeft[frameNumber] = (rawData->receiveRawData[rawData->front]);
        rawData->front = (rawData->front+1)%kRawDataLen;

    }
}
return 0;
}

以上實現(xiàn)了對音頻的實時錄入變聲后實時輸出。沒有濾波,聽起來聲音有點怪。??????大學的時候?qū)W的數(shù)字信號處理已經(jīng)還給老師,關(guān)于信號處理這部分還請知道的小伙伴指點指點,想實現(xiàn)男女聲音轉(zhuǎn)化的效果。

代碼下載地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • title: iOS音頻編程之變聲處理date: 2016-06-09tags: Audio Unit,變聲處理博...
    JustinYang閱讀 7,095評論 7 13
  • 前言 前段時間在閱讀蘋果音頻文檔(均列在參考資料一節(jié)里面了),并做了一些音頻相關(guān)的開發(fā)(主要是帶回音消除的錄音)。...
    半島夏天閱讀 4,749評論 0 1
  • 前言 前段時間在閱讀蘋果音頻文檔(均列在參考資料一節(jié)里面了),并做了一些音頻相關(guān)的開發(fā)(主要是帶回音消除的錄音)。...
    ampire_dan閱讀 2,882評論 3 6
  • 在看LFLiveKit代碼的時候,看到音頻部分使用的是audioUnit做的,所以把audioUnit學習了一下。...
    FindCrt閱讀 12,384評論 6 28
  • 【1、上周行動】 投資工具箱1、mooc課程《財務分析與決策》【8/10weeks】2、復習長投網(wǎng)入門課程【0%】...
    ruofeng8閱讀 169評論 0 1

友情鏈接更多精彩內(nèi)容