iOS音頻編程之變聲處理

title: iOS音頻編程之變聲處理
date: 2016-06-09
tags: Audio Unit,變聲處理
博客地址

iOS音頻編程之變聲處理

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

初始化

程序使用44100HZ的頻率對原始的音頻數據進行采樣,并在音頻輸入的回調中處理采樣的數據。

1)對AVAudioSession的一些設置

NSError *error;
self.session = [AVAudioSession sharedInstance];
[self.session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
handleError(error);
//route變化監聽
[[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,具體解釋參看官方文檔。我把它理解為在每次調用輸入或輸出的回調,能提供多長時間(由設置的這個值決定)的音頻數據。但當我用0.005和0.93分別設置它的時候,發現回調中的inNumberFrames的值并未改變,一直是512,相當于每次輸入或輸出提供了512/44100=0.0116s的數據。(設置有問題?
setPreferredSampleRate設置對音頻數據的采樣率。

2)獲取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);

3)對AudioComponentInstance的一些初始化設置

![I/O unit](https://raw.githubusercontent.com/JustinYangJing/Resource/master/Image/Audio/1.png)

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

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;//每個數據包多少幀
mAudioFormat.mChannelsPerFrame   = 1;//1單聲道,2立體聲
mAudioFormat.mBitsPerChannel     = 16;//語音每采樣點占用位數
mAudioFormat.mBytesPerFrame      = mAudioFormat.mBitsPerChannel*mAudioFormat.mChannelsPerFrame/8;//每幀的bytes數
mAudioFormat.mBytesPerPacket     = mAudioFormat.mBytesPerFrame*mAudioFormat.mFramesPerPacket;//每個數據包的bytes總數,每幀的bytes數*每個數據包的幀數
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。(疑問?在設置輸入輸出回調時以及Scope選擇Input和Output以及Global都可以,但是官方文檔中說Your first access to audio data entering from the input hardware is at the output scope of element 1)

音頻處理

預備知識

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

Fourier
Fourier

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

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

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

    FID=fopen('record.txt','r');
    fseek(FID,0,'eof');
    len=ftell(FID);
    frewind(FID);
    A=fread(FID,len/2,'short');
    A=A1.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(2pi1000k/44100);
    cos_y=cos_y';
    A2=A.
    cos_y;
    Y2=fft(A2);
    subplot(212);
    plot(f,abs(Y2(1:length(A)/2)));

FFT
FFT

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

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

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

音頻輸入輸出回調函數處理

1)輸入回調

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,否則要一個一個字節拷貝
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;
}

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

2)輸出回調

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;
}

以上實現了對音頻的實時錄入變聲后實時輸出。沒有濾波,聽起來聲音有點怪。??????大學的時候學的數字信號處理已經還給老師,關于信號處理這部分還請知道的小伙伴指點指點,想實現男女聲音轉化的效果。

代碼下載地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容