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
(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_Input
和kOutput/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
)
音頻處理
預備知識
變聲操作實際是對聲音信號的頻譜進行搬移,在時域中乘以一個三角函數相當于在頻域上進行了頻譜的搬移。但使得頻譜搬移了±??。由下圖傅里葉變化公式說明

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

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