1.memset: 原型: void * memset(void * __b, int __c, size_t __len); 解釋:將s中當前位置後面的n個字節(typedef unsigned int size_t) 用ch替換並返回s 做用:在一段內存塊中填充某個特定的值,它是對較大的結構體或數組進行清零操做的一種最快方法。html
2.memcpy: 原型: void * memcpy(void * dest, const void * src, size_t n); 解釋:從源src所指的內存地址的起始位置開始拷貝n個字節到目標dest所指的內存地址的起始位置中ios
3.void free(void *); 解釋:釋放內存,須要將malloc出來的內存通通釋放掉,對於結構體要先將結構體中malloc出來的釋放掉最後再釋放掉結構體自己。git
1.OSStaus:狀態碼,若是沒有錯誤返回0:(即noErr)github
2.AudioFormatGetPropertyInfo:express
原型:
AudioFormatGetPropertyInfo(
AudioFormatPropertyID inPropertyID,
UInt32 inSpecifierSize,
const void * __nullable inSpecifier,
UInt32 * outPropertyDataSize);
* 做用:檢索給定屬性的信息,好比編碼器目標格式的size等
複製代碼
3.AudioSessionGetProperty:編程
原型:
extern OSStatus
AudioSessionGetProperty(
AudioSessionPropertyID inID,
UInt32 *ioDataSize,
void *outData);
* 做用:獲取指定AudioSession對象的inID屬性的值(好比採樣率,聲道數等等)
複製代碼
4.AudioUnitSetProperty數組
extern OSStatus
AudioUnitSetProperty( AudioUnit inUnit,
AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
const void * __nullable inData,
UInt32 inDataSize)
* 做用:設置AudioUnit特定屬性的值,其中scope,element不理解可參考下文audio unit概念部分,這裏能夠設置音頻流的各類參數,好比採樣頻率、量化位數、通道個數、每包中幀的個數等等
複製代碼
AVFoundation框架中的AVAudioPlayer和AVAudioRecorder類,用法簡單,可是不支持流式,也就意味着在播放音頻前,必須等到整個音頻加載完成後,才能開始播放音頻;錄音時,也必須等到錄音結束後才能得到錄音數據。緩存
在iOS和Mac OS X中,音頻隊列Audio Queues是一個用來錄製和播放音頻的軟件對象,也就是說,能夠用來錄音和播放,錄音可以獲取實時的PCM原始音頻數據。bash
數據介紹cookie
(1)In CBR (constant bit rate) formats, such as linear PCM and IMA/ADPCM, all packets are the same size.
(2)In VBR (variable bit rate) formats, such as AAC, Apple Lossless, and MP3, all packets have the same number of frames but the number of bits in each sample value can vary.
(3)In VFR (variable frame rate) formats, packets have a varying number of frames. There are no commonly used formats of this type.
(1)音頻文件的組成:文件格式(或者音頻容器)+數據格式(或者音頻編碼)
知識點:
(2).音頻文件計算大小 簡述:聲卡對聲音的處理質量能夠用三個基本參數來衡量,即採樣頻率,採樣位數和聲道數。
知識點:
採樣頻率:單位時間內採樣次數。採樣頻率越大,採樣點之間的間隔就越小,數字化後獲得的聲音就越逼真,但相應的數據量就越大,聲卡通常提供11.025kHz,22.05kHz和44.1kHz等不一樣的採樣頻率。
採樣位數:記錄每次採樣值數值大小的位數。採樣位數一般有8bits或16bits兩種,採樣位數越大,所能記錄的聲音變化度就越細膩,相應的數據量就越大。
聲道數:處理的聲音是單聲道仍是立體聲。單聲道在聲音處理過程當中只有單數據流,而立體聲則須要左右聲道的兩個數據流。顯然,立體聲的效果要好,但相應數據量要比單聲道數據量加倍。
聲音數據量的計算公式:數據量(字節 / 秒)=(採樣頻率(Hz)* 採樣位數(bit)* 聲道數)/ 8 單聲道的聲道數爲1,立體聲的聲道數爲2. 字節B,1MB=1024KB = 1024*1024B
(3)
(2).Audio Data Formats:經過設置一組屬性代碼能夠和操做系統支持的任何格式一塊兒工做。(包括採樣率,比特率),對於AudioQueue與AudioUnit設置略有不一樣。
struct AudioStreamBasicDescription
{
Float64 mSampleRate; // 採樣率 :Hz
AudioFormatID mFormatID; // 採樣數據的類型,PCM,AAC等
AudioFormatFlags mFormatFlags; // 每種格式特定的標誌,無損編碼 ,0表示沒有
UInt32 mBytesPerPacket; // 一個數據包中的字節數
UInt32 mFramesPerPacket; // 一個數據包中的幀數,每一個packet的幀數。若是是未壓縮的音頻數據,值是1。動態幀率格式,這個值是一個較大的固定數字,好比說AAC的1024。若是是動態大小幀數(好比Ogg格式)設置爲0。
UInt32 mBytesPerFrame; // 每一幀中的字節數
UInt32 mChannelsPerFrame; // 每一幀數據中的通道數,單聲道爲1,立體聲爲2
UInt32 mBitsPerChannel; // 每一個通道中的位數,1byte = 8bit
UInt32 mReserved; // 8字節對齊,填0
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
複製代碼
###---------------------------- Audio Queue ---------------------------
.音頻隊列 — 詳細請參考 Audio Queue,該文章中已有詳細描述,再也不重複介紹,不懂請參考。
你能夠將音頻隊列配合其餘Core Audio的接口使用,再加上相對少許的自定義代碼就能夠在你的應用程序中建立一套完整的數字音頻錄製或播放解決方案。
一組音頻隊列緩衝區(audio queue buffers),每一個音頻隊列緩衝區都是一個存儲音頻數據的臨時倉庫
一個緩衝區隊列(buffer queue),一個包含音頻隊列緩衝區的有序列表
一個你本身編寫的音頻隊列回調函數(audio queue callback)
本例中涉及的一些宏定義,具體能夠下載代碼詳細看
#define kBufferDurationSeconds .5
#define kXDXRecoderAudioBytesPerPacket 2
#define kXDXRecoderAACFramesPerPacket 1024
#define kXDXRecoderPCMTotalPacket 512
#define kXDXRecoderPCMFramesPerPacket 1
#define kXDXRecoderConverterEncodeBitRate 64000
#define kXDXAudioSampleRate 48000.0
複製代碼
-(void)startRecorder {
// Reset pcm_buffer to save convert handle, 每次開始音頻會話前初始化pcm_buffer, pcm_buffer用來在捕捉聲音的回調中存儲累加的PCM原始數據
memset(pcm_buffer, 0, pcm_buffer_size);
pcm_buffer_size = 0;
frameCount = 0;
// 是否正在錄製
if (isRunning) {
// log4cplus_info("pcm", "Start recorder repeat");
return;
}
// 本例中採用log4打印log信息,若你沒有能夠不用,刪除有關Log4的語句
// log4cplus_info("pcm", "starup PCM audio encoder");
// 設置採集的數據的類型爲PCM
[self setUpRecoderWithFormatID:kAudioFormatLinearPCM];
OSStatus status = 0;
UInt32 size = sizeof(dataFormat);
// 編碼器轉碼設置
[self convertBasicSetting];
// 這個if語句用來檢測是否初始化本例對象成功,若是不成功重啓三次,三次後若是失敗能夠進行其餘處理
if (err != nil) {
NSString *error = nil;
for (int i = 0; i < 3; i++) {
usleep(100*1000);
error = [self convertBasicSetting];
if (error == nil) break;
}
// if init this class failed then restart three times , if failed again,can handle at there
// [self exitWithErr:error];
}
// 新建一個隊列,第二個參數註冊回調函數,第三個防止內存泄露
status = AudioQueueNewInput(&dataFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &mQueue);
// log4cplus_info("pcm","AudioQueueNewInput status:%d",(int)status);
// 獲取隊列屬性
status = AudioQueueGetProperty(mQueue, kAudioQueueProperty_StreamDescription, &dataFormat, &size);
// log4cplus_info("pcm","AudioQueueNewInput status:%u",(unsigned int)dataFormat.mFormatID);
// 這裏將頭信息添加到寫入文件中,若文件數據爲CBR,不須要添加,爲VBR須要添加
[self copyEncoderCookieToFile];
// 能夠計算得到,在這裏使用的是固定大小
// bufferByteSize = [self computeRecordBufferSizeFrom:&dataFormat andDuration:kBufferDurationSeconds];
// log4cplus_info("pcm","pcm raw data buff number:%d, channel number:%u",
kNumberQueueBuffers,
dataFormat.mChannelsPerFrame);
// 設置三個音頻隊列緩衝區
for (int i = 0; i != kNumberQueueBuffers; i++) {
// 注意:爲每一個緩衝區分配大小,可根據具體需求進行修改,可是必定要注意必須知足轉換器的需求,轉換器只有每次給1024幀數據纔會完成一次轉換,若是需求爲採集數據量較少則用本例提供的pcm_buffer對數據進行累加後再處理
status = AudioQueueAllocateBuffer(mQueue, kXDXRecoderPCMTotalPacket*kXDXRecoderAudioBytesPerPacket*dataFormat.mChannelsPerFrame, &mBuffers[i]);
// 入隊
status = AudioQueueEnqueueBuffer(mQueue, mBuffers[i], 0, NULL);
}
isRunning = YES;
hostTime = 0;
status = AudioQueueStart(mQueue, NULL);
log4cplus_info("pcm","AudioQueueStart status:%d",(int)status);
}
複製代碼
初始化輸出流的結構體描述
struct AudioStreamBasicDescription
{
Float64 mSampleRate; // 採樣率 :Hz
AudioFormatID mFormatID; // 採樣數據的類型,PCM,AAC等
AudioFormatFlags mFormatFlags; // 每種格式特定的標誌,無損編碼 ,0表示沒有
UInt32 mBytesPerPacket; // 一個數據包中的字節數
UInt32 mFramesPerPacket; // 一個數據包中的幀數,每一個packet的幀數。若是是未壓縮的音頻數據,值是1。動態幀率格式,這個值是一個較大的固定數字,好比說AAC的1024。若是是動態大小幀數(好比Ogg格式)設置爲0。
UInt32 mBytesPerFrame; // 每一幀中的字節數
UInt32 mChannelsPerFrame; // 每一幀數據中的通道數,單聲道爲1,立體聲爲2
UInt32 mBitsPerChannel; // 每一個通道中的位數,1byte = 8bit
UInt32 mReserved; // 8字節對齊,填0
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
複製代碼
注意: kNumberQueueBuffers,音頻隊列可使用任意數量的緩衝區。你的應用程序制定它的數量。通常狀況下這個數字是3。這樣就可讓給一個忙於將數據寫入磁盤,同時另外一個在填充新的音頻數據,第三個緩衝區在須要作磁盤I/O延遲補償的時候可用
如何使用AudioQueue:
AudioQueueNewInput
// 做用:建立一個音頻隊列爲了錄製音頻數據
原型:extern OSStatus
AudioQueueNewInput( const AudioStreamBasicDescription *inFormat, 同上
AudioQueueInputCallback inCallbackProc, // 註冊回調函數
void * __nullable inUserData,
CFRunLoopRef __nullable inCallbackRunLoop,
CFStringRef __nullable inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef __nullable * __nonnull outAQ);
// 這個函數的第四個和第五個參數是有關於線程的,我設置成null,表明它默認使用內部線程去錄音,並且仍是異步的
複製代碼
-(void)setUpRecoderWithFormatID:(UInt32)formatID {
// Notice : The settings here are official recommended settings,can be changed according to specific requirements. 此處的設置爲官方推薦設置,可根據具體需求修改部分設置
//setup auido sample rate, channel number, and format ID
memset(&dataFormat, 0, sizeof(dataFormat));
UInt32 size = sizeof(dataFormat.mSampleRate);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate,
&size,
&dataFormat.mSampleRate);
dataFormat.mSampleRate = kXDXAudioSampleRate; // 設置採樣率
size = sizeof(dataFormat.mChannelsPerFrame);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels,
&size,
&dataFormat.mChannelsPerFrame);
dataFormat.mFormatID = formatID;
// 關於採集PCM數據是根據蘋果官方文檔給出的Demo設置,至於爲何這麼設置可能與採集回調函數內部實現有關,修改的話請謹慎
if (formatID == kAudioFormatLinearPCM)
{
/*
爲保存音頻數據的方式的說明,如能夠根據大端字節序或小端字節序,
浮點數或整數以及不一樣體位去保存數據
例如對PCM格式一般咱們以下設置:kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked等
*/
dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
// 每一個通道里,一幀採集的bit數目
dataFormat.mBitsPerChannel = 16;
// 8bit爲1byte,即爲1個通道里1幀須要採集2byte數據,再*通道數,即爲全部通道採集的byte數目
dataFormat.mBytesPerPacket = dataFormat.mBytesPerFrame = (dataFormat.mBitsPerChannel / 8) * dataFormat.mChannelsPerFrame;
// 每一個包中的幀數,採集PCM數據須要將dataFormat.mFramesPerPacket設置爲1,不然回調不成功
dataFormat.mFramesPerPacket = kXDXRecoderPCMFramesPerPacket;
}
}
複製代碼
-(NSString *)convertBasicSetting {
// 此處目標格式其餘參數均爲默認,系統會自動計算,不然沒法進入encodeConverterComplexInputDataProc回調
AudioStreamBasicDescription sourceDes = dataFormat; // 原始格式
AudioStreamBasicDescription targetDes; // 轉碼後格式
// 設置目標格式及基本信息
memset(&targetDes, 0, sizeof(targetDes));
targetDes.mFormatID = kAudioFormatMPEG4AAC;
targetDes.mSampleRate = kXDXAudioSampleRate;
targetDes.mChannelsPerFrame = dataFormat.mChannelsPerFrame;
targetDes.mFramesPerPacket = kXDXRecoderAACFramesPerPacket; // 採集的爲AAC須要將targetDes.mFramesPerPacket設置爲1024,AAC軟編碼須要餵給轉換器1024個樣點纔開始編碼,這與回調函數中inNumPackets有關,不可隨意更改
OSStatus status = 0;
UInt32 targetSize = sizeof(targetDes);
status = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &targetSize, &targetDes);
// log4cplus_info("pcm", "create target data format status:%d",(int)status);
memset(&_targetDes, 0, sizeof(_targetDes));
// 賦給全局變量
memcpy(&_targetDes, &targetDes, targetSize);
// 選擇軟件編碼
AudioClassDescription audioClassDes;
status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
sizeof(targetDes.mFormatID),
&targetDes.mFormatID,
&targetSize);
// log4cplus_info("pcm","get kAudioFormatProperty_Encoders status:%d",(int)status);
// 計算編碼器容量
UInt32 numEncoders = targetSize/sizeof(AudioClassDescription);
// 用數組存放編碼器內容
AudioClassDescription audioClassArr[numEncoders];
// 將編碼器屬性賦給數組
AudioFormatGetProperty(kAudioFormatProperty_Encoders,
sizeof(targetDes.mFormatID),
&targetDes.mFormatID,
&targetSize,
audioClassArr);
// log4cplus_info("pcm","wrirte audioClassArr status:%d",(int)status);
// 遍歷數組,設置軟編
for (int i = 0; i < numEncoders; i++) {
if (audioClassArr[i].mSubType == kAudioFormatMPEG4AAC && audioClassArr[i].mManufacturer == kAppleSoftwareAudioCodecManufacturer) {
memcpy(&audioClassDes, &audioClassArr[i], sizeof(AudioClassDescription));
break;
}
}
// 防止內存泄露
if (_encodeConvertRef == NULL) {
// 新建一個編碼對象,設置原,目標格式
status = AudioConverterNewSpecific(&sourceDes, &targetDes, 1,
&audioClassDes, &_encodeConvertRef);
if (status != noErr) {
// log4cplus_info("Audio Recoder","new convertRef failed status:%d \n",(int)status);
return @"Error : New convertRef failed \n";
}
}
// 獲取原始格式大小
targetSize = sizeof(sourceDes);
status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentInputStreamDescription, &targetSize, &sourceDes);
// log4cplus_info("pcm","get sourceDes status:%d",(int)status);
// 獲取目標格式大小
targetSize = sizeof(targetDes);
status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentOutputStreamDescription, &targetSize, &targetDes);;
// log4cplus_info("pcm","get targetDes status:%d",(int)status);
// 設置碼率,須要和採樣率對應
UInt32 bitRate = kXDXRecoderConverterEncodeBitRate;
targetSize = sizeof(bitRate);
status = AudioConverterSetProperty(_encodeConvertRef,
kAudioConverterEncodeBitRate,
targetSize, &bitRate);
// log4cplus_info("pcm","set covert property bit rate status:%d",(int)status);
if (status != noErr) {
// log4cplus_info("Audio Recoder","set covert property bit rate status:%d",(int)status);
return @"Error : Set covert property bit rate failed";
}
return nil;
}
複製代碼
AudioFormatGetProperty:
原型:
extern OSStatus
AudioFormatGetProperty( AudioFormatPropertyID inPropertyID,
UInt32 inSpecifierSize,
const void * __nullable inSpecifier,
UInt32 * __nullable ioPropertyDataSize,
void * __nullabl outPropertyData);
做用:檢索某個屬性的值
複製代碼
AudioClassDescription:
指的是一個可以對一個信號或者一個數據流進行變換的設備或者程序。這裏指的變換既包括將 信號或者數據流進行編碼(一般是爲了傳輸、存儲或者加密)或者提取獲得一個編碼流的操做,也包括爲了觀察或者處理從這個編碼流中恢復適合觀察或操做的形式的操做。編解碼器常常用在視頻會議和流媒體等應用中。
默認狀況下,Apple會建立一個硬件編碼器,若是硬件不可用,會建立軟件編碼器。
通過個人測試,硬件AAC編碼器的編碼時延很高,須要buffer大約2秒的數據纔會開始編碼。而軟件編碼器的編碼時延就是正常的,只要餵給1024個樣點,就會開始編碼。
AudioConverterNewSpecific:
原型: extern OSStatus
AudioConverterNewSpecific( const AudioStreamBasicDescription * inSourceFormat,
const AudioStreamBasicDescription * inDestinationFormat,
UInt32 inNumberClassDescriptions,
const AudioClassDescription * inClassDescriptions,
AudioConverterRef __nullable * __nonnull outAudioConverter);
解釋:建立一個轉換器
做用:設置一些轉碼基本信息
複製代碼
AudioConverterSetProperty:
原型:extern OSStatus
AudioConverterSetProperty( AudioConverterRef inAudioConverter,
AudioConverterPropertyID inPropertyID,
UInt32 inPropertyDataSize,
const void * inPropertyData);
做用:設置碼率,須要注意,AAC並非隨便的碼率均可以支持。好比若是PCM採樣率是44100KHz,那麼碼率能夠設置64000bps,若是是16K,能夠設置爲32000bps。
複製代碼
-(void)copyEncoderCookieToFile
{
// Grab the cookie from the converter and write it to the destination file.
UInt32 cookieSize = 0;
OSStatus error = AudioConverterGetPropertyInfo(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, NULL);
// If there is an error here, then the format doesn't have a cookie - this is perfectly fine as som formats do not. // log4cplus_info("cookie","cookie status:%d %d",(int)error, cookieSize); if (error == noErr && cookieSize != 0) { char *cookie = (char *)malloc(cookieSize * sizeof(char)); // UInt32 *cookie = (UInt32 *)malloc(cookieSize * sizeof(UInt32)); error = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, cookie); // log4cplus_info("cookie","cookie size status:%d",(int)error); if (error == noErr) { error = AudioFileSetProperty(mRecordFile, kAudioFilePropertyMagicCookieData, cookieSize, cookie); // log4cplus_info("cookie","set cookie status:%d ",(int)error); if (error == noErr) { UInt32 willEatTheCookie = false; error = AudioFileGetPropertyInfo(mRecordFile, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie); printf("Writing magic cookie to destination file: %u\n cookie:%d \n", (unsigned int)cookieSize, willEatTheCookie); } else { printf("Even though some formats have cookies, some files don't take them and that's OK\n"); } } else { printf("Could not Get kAudioConverterCompressionMagicCookie from Audio Converter!\n"); } free(cookie); } } 複製代碼
Magic cookie 是一種不透明的數據格式,它和壓縮數據文件與流聯繫密切,若是文件數據爲CBR格式(無損),則不須要添加頭部信息,若是爲VBR須要添加,// if collect CBR needn't set magic cookie , if collect VBR should set magic cookie, if needn't to convert format that can be setting by audio queue directly.
// AudioQueue中註冊的回調函數
static void inputBufferHandler(void * inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp * inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription* inPacketDesc) {
// 至關於本類對象實例
TVURecorder *recoder = (TVURecorder *)inUserData;
/*
inNumPackets 總包數:音頻隊列緩衝區大小 (在先前估算緩存區大小爲kXDXRecoderAACFramesPerPacket*2)/ (dataFormat.mFramesPerPacket (採集數據每一個包中有多少幀,此處在初始化設置中爲1) * dataFormat.mBytesPerFrame(每一幀中有多少個字節,此處在初始化設置中爲每一幀中兩個字節)),因此能夠根據該公式計算捕捉PCM數據時inNumPackets。
注意:若是採集的數據是PCM須要將dataFormat.mFramesPerPacket設置爲1,而本例中最終要的數據爲AAC,由於本例中使用的轉換器只有每次傳入1024幀才能開始工做,因此在AAC格式下須要將mFramesPerPacket設置爲1024.也就是採集到的inNumPackets爲1,在轉換器中傳入的inNumPackets應該爲AAC格式下默認的1,在此後寫入文件中也應該傳的是轉換好的inNumPackets,若是有特殊需求須要將採集的數據量小於1024,那麼須要將每次捕捉到的數據先預先存儲在一個buffer中,等到攢夠1024幀再進行轉換。
*/
// collect pcm data,能夠在此存儲
// First case : collect data not is 1024 frame, if collect data not is 1024 frame, we need to save data to pcm_buffer untill 1024 frame
memcpy(pcm_buffer+pcm_buffer_size, inBuffer->mAudioData, inBuffer->mAudioDataByteSize);
pcm_buffer_size = pcm_buffer_size + inBuffer->mAudioDataByteSize;
if(inBuffer->mAudioDataByteSize != kXDXRecoderAACFramesPerPacket*2)
NSLog(@"write pcm buffer size:%d, totoal buff size:%d", inBuffer->mAudioDataByteSize, pcm_buffer_size);
frameCount++;
// Second case : If the size of the data collection is not required, we can let mic collect 1024 frame so that don't need to write firtst case, but it is recommended to write the above code because of agility // if collect data is added to 1024 frame if(frameCount == totalFrames) { AudioBufferList *bufferList = convertPCMToAAC(recoder); pcm_buffer_size = 0; frameCount = 0; // free memory free(bufferList->mBuffers[0].mData); free(bufferList); // begin write audio data for record audio only // 出隊 AudioQueueRef queue = recoder.mQueue; if (recoder.isRunning) { AudioQueueEnqueueBuffer(queue, inBuffer, 0, NULL); } } } 複製代碼
解析回調函數:至關於中斷服務函數,每次錄取到音頻數據就進入這個函數
注意:inNumPackets 總包數:音頻隊列緩衝區大小 (在先前估算緩存區大小爲2048)/ (dataFormat.mFramesPerPacket (採集數據每一個包中有多少幀,此處在初始化設置中爲1) * dataFormat.mBytesPerFrame(每一幀中有多少個字節,此處在初始化設置中爲每一幀中兩個字節))
// PCM -> AAC
AudioBufferList* convertPCMToAAC (AudioQueueBufferRef inBuffer, XDXRecorder *recoder) {
UInt32 maxPacketSize = 0;
UInt32 size = sizeof(maxPacketSize);
OSStatus status;
status = AudioConverterGetProperty(_encodeConvertRef,
kAudioConverterPropertyMaximumOutputPacketSize,
&size,
&maxPacketSize);
// log4cplus_info("AudioConverter","kAudioConverterPropertyMaximumOutputPacketSize status:%d \n",(int)status);
// 初始化一個bufferList存儲數據
AudioBufferList *bufferList = (AudioBufferList *)malloc(sizeof(AudioBufferList));
bufferList->mNumberBuffers = 1;
bufferList->mBuffers[0].mNumberChannels = _targetDes.mChannelsPerFrame;
bufferList->mBuffers[0].mData = malloc(maxPacketSize);
bufferList->mBuffers[0].mDataByteSize = pcm_buffer_size;
AudioStreamPacketDescription outputPacketDescriptions;
/*
inNumPackets設置爲1表示編碼產生1幀數據即返回,官方:On entry, the capacity of outOutputData expressed in packets in the converter's output format. On exit, the number of packets of converted data that were written to outOutputData. 在輸入表示輸出數據的最大容納能力 在轉換器的輸出格式上,在轉換完成時表示多少個包被寫入 */ UInt32 inNumPackets = 1; status = AudioConverterFillComplexBuffer(_encodeConvertRef, encodeConverterComplexInputDataProc, // 填充數據的回調函數 pcm_buffer, // 音頻隊列緩衝區中數據 &inNumPackets, bufferList, // 成功後將值賦給bufferList &outputPacketDescriptions); // 輸出包包含的一些信息 log4cplus_info("AudioConverter","set AudioConverterFillComplexBuffer status:%d",(int)status); if (recoder.needsVoiceDemo) { // if inNumPackets set not correct, file will not normally play. 將轉換器轉換出來的包寫入文件中,inNumPackets表示寫入文件的起始位置 OSStatus status = AudioFileWritePackets(recoder.mRecordFile, FALSE, bufferList->mBuffers[0].mDataByteSize, &outputPacketDescriptions, recoder.mRecordPacket, &inNumPackets, bufferList->mBuffers[0].mData); // log4cplus_info("write file","write file status = %d",(int)status); recoder.mRecordPacket += inNumPackets; // Used to record the location of the write file,用於記錄寫入文件的位置 } return bufferList; } 複製代碼
解析
outputPacketDescriptions數組是每次轉換的AAC編碼後各個包的描述,但這裏每次只轉換一包數據(由傳入的packetSize決定)。調用AudioConverterFillComplexBuffer觸發轉碼,他的第二個參數是填充原始音頻數據的回調。轉碼完成後,會將轉碼的數據存放在它的第五個參數中(bufferList).
// 錄製聲音功能
-(void)startVoiceDemo
{
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:@"VoiceDemo"];
OSStatus status;
// Get the full path to our file.
NSString *fullFileName = [NSString stringWithFormat:@"%@.%@",[[XDXDateTool shareXDXDateTool] getDateWithFormat_yyyy_MM_dd_HH_mm_ss],@"caf"];
NSString *filePath = [documentPath stringByAppendingPathComponent:fullFileName];
[mRecordFilePath release];
mRecordFilePath = [filePath copy];;
CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)filePath, NULL);
// create the audio file
status = AudioFileCreateWithURL(url, kAudioFileMPEG4Type, &_targetDes, kAudioFileFlags_EraseFile, &mRecordFile);
if (status != noErr) {
// log4cplus_info("Audio Recoder","AudioFileCreateWithURL Failed, status:%d",(int)status);
}
CFRelease(url);
// add magic cookie contain header file info for VBR data
[self copyEncoderCookieToFile];
mNeedsVoiceDemo = YES;
NSLog(@"%s",__FUNCTION__);
}
複製代碼
##--------------------------- Audio Unit -----------------------------
1). AudioUnit是 iOS提供的爲了支持混音,均衡,格式轉換,實時輸入輸出用於錄製,回放,離線渲染和實時回話(VOIP),這讓咱們能夠動態加載和使用,即從iOS應用程序中接收這些強大而靈活的插件。它是iOS音頻中最低層,因此除非你須要合成聲音的實時播放,低延遲的I/O,或特定聲音的特定特色。 Audio unit scopes and elements :
上圖是一個AudioUnit的組成結構,A scope 主要使用到的輸入kAudioUnitScope_Input和輸出kAudioUnitScope_Output。Element 是嵌套在audio unit scope的編程上下文。
AudioUnit 的Remote IO有2個element,大部分代碼和文獻都用bus代替element,二者同義,bus0就是輸出,bus 1表明輸入,播放音頻文件就是在bus 0傳送數據,bus 1輸入在Remote IO 默認是關閉的,在錄音的狀態下 須要把bus 1設置成開啓狀態。
咱們能使用(kAudioOutputUnitProperty_EnableIO)屬性獨立地開啓或禁用每一個element,Element 1 直接與音頻輸入硬件相連(麥克風),Element 1 的input scope對咱們是不透明的,來自輸入硬件的音頻數據只能在Element 1的output scope中訪問。
一樣的element 0直接和輸出硬件相連(揚聲器),咱們能夠將audio數據傳輸到element 0的input scope中,可是output scope對咱們是不透明的。
注意:每一個element自己都有一個輸入範圍和輸出範圍,所以在代碼中若是不理解可能會比較懵逼,好比你從input element的 output scope 中受到音頻,並將音頻發送到output element的intput scope中,若是代碼中不理解,能夠再看看上圖。
2 - 1. I/O Units : iOS提供了3種I/O Units.
1). 導入所需動態庫與頭文件(At runtime, obtain a reference to the dynamically-linkable library that defines an audio unit you want to use.)
2). 實例化audio unit(Instantiate the audio unit.)
3). 配置audioUnit的類型去完成特定的需求(Configure the audio unit as required for its type and to accomodate the intent of your app.)
4). 初始化uandio unit(Initialize the audio unit to prepare it to handle audio. )
5). 開始audio flow(Start audio flow.)
6). 控制audio unit(Control the audio unit.)
7). 結束後回收audio unit(When finished, deallocate the audio unit.)
- (void)initAudioComponent {
OSStatus status;
// 配置AudioUnit基本信息
AudioComponentDescription audioDesc;
audioDesc.componentType = kAudioUnitType_Output;
// 若是你的應用程序須要去除回聲將componentSubType設置爲kAudioUnitSubType_VoiceProcessingIO,不然根據需求設置爲其餘,在博客中有介紹
audioDesc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;//kAudioUnitSubType_VoiceProcessingIO;
// 蘋果本身的標誌
audioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
audioDesc.componentFlags = 0;
audioDesc.componentFlagsMask = 0;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &audioDesc);
// 新建一個AudioComponent對象,只有這步完成才能進行後續步驟,因此順序不可顛倒
status = AudioComponentInstanceNew(inputComponent, &_audioUnit);
if (status != noErr) {
_audioUnit = NULL;
// log4cplus_info("Audio Recoder", "couldn't create a new instance of AURemoteIO, status : %d \n",status);
}
}
複製代碼
解析
- (void)initBuffer {
// 禁用AudioUnit默認的buffer而使用咱們本身寫的全局BUFFER,用來接收每次採集的PCM數據,Disable AU buffer allocation for the recorder, we allocate our own.
UInt32 flag = 0;
OSStatus status = AudioUnitSetProperty(_audioUnit,
kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output,
INPUT_BUS,
&flag,
sizeof(flag));
if (status != noErr) {
// log4cplus_info("Audio Recoder", "couldn't AllocateBuffer of AudioUnitCallBack, status : %d \n",status);
}
_buffList = (AudioBufferList*)malloc(sizeof(AudioBufferList));
_buffList->mNumberBuffers = 1;
_buffList->mBuffers[0].mNumberChannels = dataFormat.mChannelsPerFrame;
_buffList->mBuffers[0].mDataByteSize = kTVURecoderPCMMaxBuffSize * sizeof(short);
_buffList->mBuffers[0].mData = (short *)malloc(sizeof(short) * kTVURecoderPCMMaxBuffSize);
}
複製代碼
解析
本例經過禁用AudioUnit默認的buffer而使用咱們本身寫的全局BUFFER,用來接收每次採集的PCM數據,Disable AU buffer allocation for the recorder, we allocate our own.還有一種寫法是可使用回調中提供的ioData存儲採集的數據,這裏使用全局的buff是爲了供其餘地方使用,可根據須要自行決定採用哪一種方式,若不採用全局buffer則不可採用上述禁用操做。
// 由於本例只作錄音功能,未實現播放功能,因此沒有設置播放相關設置。
- (void)setAudioUnitPropertyAndFormat {
OSStatus status;
[self setUpRecoderWithFormatID:kAudioFormatLinearPCM];
// 應用audioUnit設置的格式
status = AudioUnitSetProperty(_audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
INPUT_BUS,
&dataFormat,
sizeof(dataFormat));
if (status != noErr) {
// log4cplus_info("Audio Recoder", "couldn't set the input client format on AURemoteIO, status : %d \n",status);
}
// 去除回聲開關
UInt32 echoCancellation;
AudioUnitSetProperty(_audioUnit,
kAUVoiceIOProperty_BypassVoiceProcessing,
kAudioUnitScope_Global,
0,
&echoCancellation,
sizeof(echoCancellation));
// AudioUnit輸入端默認是關閉,須要將他打開
UInt32 flag = 1;
status = AudioUnitSetProperty(_audioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
INPUT_BUS,
&flag,
sizeof(flag));
if (status != noErr) {
// log4cplus_info("Audio Recoder", "could not enable input on AURemoteIO, status : %d \n",status);
}
}
-(void)setUpRecoderWithFormatID:(UInt32)formatID {
// Notice : The settings here are official recommended settings,can be changed according to specific requirements. 此處的設置爲官方推薦設置,可根據具體需求修改部分設置
//setup auido sample rate, channel number, and format ID
memset(&dataFormat, 0, sizeof(dataFormat));
UInt32 size = sizeof(dataFormat.mSampleRate);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate,
&size,
&dataFormat.mSampleRate);
dataFormat.mSampleRate = kXDXAudioSampleRate;
size = sizeof(dataFormat.mChannelsPerFrame);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels,
&size,
&dataFormat.mChannelsPerFrame);
dataFormat.mFormatID = formatID;
dataFormat.mChannelsPerFrame = 1;
if (formatID == kAudioFormatLinearPCM) {
if (self.releaseMethod == XDXRecorderReleaseMethodAudioQueue) {
dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
}else if (self.releaseMethod == XDXRecorderReleaseMethodAudioQueue) {
dataFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
}
dataFormat.mBitsPerChannel = 16;
dataFormat.mBytesPerPacket = dataFormat.mBytesPerFrame = (dataFormat.mBitsPerChannel / 8) * dataFormat.mChannelsPerFrame;
dataFormat.mFramesPerPacket = kXDXRecoderPCMFramesPerPacket; // 用AudioQueue採集pcm須要這麼設置
}
}
複製代碼
解析
上述操做針對錄音功能須要對Audio Unit作出對應設置,首先設置ASBD採集數據爲PCM的格式,須要注意的是若是是使用AudioQueue與AudioUnit的dataFormat.mFormatFlags設置略有不一樣,經測試必須這樣設置,緣由暫不詳,設置完後使用AudioUnitSetProperty應用設置,這裏只作錄音,因此對kAudioOutputUnitProperty_EnableIO 的 kAudioUnitScope_Input 開啓,而對kAudioUnitScope_Output 輸入端輸出的音頻格式進行設置,若是不理解可參照1中概念解析進行理解,kAUVoiceIOProperty_BypassVoiceProcessing則是回聲的開關。
-(NSString *)convertBasicSetting {
// 此處目標格式其餘參數均爲默認,系統會自動計算,不然沒法進入encodeConverterComplexInputDataProc回調
AudioStreamBasicDescription sourceDes = dataFormat; // 原始格式
AudioStreamBasicDescription targetDes; // 轉碼後格式
// 設置目標格式及基本信息
memset(&targetDes, 0, sizeof(targetDes));
targetDes.mFormatID = kAudioFormatMPEG4AAC;
targetDes.mSampleRate = kXDXAudioSampleRate;
targetDes.mChannelsPerFrame = dataFormat.mChannelsPerFrame;
targetDes.mFramesPerPacket = kXDXRecoderAACFramesPerPacket; // 採集的爲AAC須要將targetDes.mFramesPerPacket設置爲1024,AAC軟編碼須要餵給轉換器1024個樣點纔開始編碼,這與回調函數中inNumPackets有關,不可隨意更改
OSStatus status = 0;
UInt32 targetSize = sizeof(targetDes);
status = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &targetSize, &targetDes);
// log4cplus_info("pcm", "create target data format status:%d",(int)status);
memset(&_targetDes, 0, sizeof(_targetDes));
// 賦給全局變量
memcpy(&_targetDes, &targetDes, targetSize);
// 選擇軟件編碼
AudioClassDescription audioClassDes;
status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
sizeof(targetDes.mFormatID),
&targetDes.mFormatID,
&targetSize);
// log4cplus_info("pcm","get kAudioFormatProperty_Encoders status:%d",(int)status);
// 計算編碼器容量
UInt32 numEncoders = targetSize/sizeof(AudioClassDescription);
// 用數組存放編碼器內容
AudioClassDescription audioClassArr[numEncoders];
// 將編碼器屬性賦給數組
AudioFormatGetProperty(kAudioFormatProperty_Encoders,
sizeof(targetDes.mFormatID),
&targetDes.mFormatID,
&targetSize,
audioClassArr);
// log4cplus_info("pcm","wrirte audioClassArr status:%d",(int)status);
// 遍歷數組,設置軟編
for (int i = 0; i < numEncoders; i++) {
if (audioClassArr[i].mSubType == kAudioFormatMPEG4AAC && audioClassArr[i].mManufacturer == kAppleSoftwareAudioCodecManufacturer) {
memcpy(&audioClassDes, &audioClassArr[i], sizeof(AudioClassDescription));
break;
}
}
// 防止內存泄露
if (_encodeConvertRef == NULL) {
// 新建一個編碼對象,設置原,目標格式
status = AudioConverterNewSpecific(&sourceDes, &targetDes, 1,
&audioClassDes, &_encodeConvertRef);
if (status != noErr) {
// log4cplus_info("Audio Recoder","new convertRef failed status:%d \n",(int)status);
return @"Error : New convertRef failed \n";
}
}
// 獲取原始格式大小
targetSize = sizeof(sourceDes);
status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentInputStreamDescription, &targetSize, &sourceDes);
// log4cplus_info("pcm","get sourceDes status:%d",(int)status);
// 獲取目標格式大小
targetSize = sizeof(targetDes);
status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentOutputStreamDescription, &targetSize, &targetDes);;
// log4cplus_info("pcm","get targetDes status:%d",(int)status);
// 設置碼率,須要和採樣率對應
UInt32 bitRate = kXDXRecoderConverterEncodeBitRate;
targetSize = sizeof(bitRate);
status = AudioConverterSetProperty(_encodeConvertRef,
kAudioConverterEncodeBitRate,
targetSize, &bitRate);
// log4cplus_info("pcm","set covert property bit rate status:%d",(int)status);
if (status != noErr) {
// log4cplus_info("Audio Recoder","set covert property bit rate status:%d",(int)status);
return @"Error : Set covert property bit rate failed";
}
return nil;
}
複製代碼
解析
設置原格式與轉碼格式並建立_encodeConvertRef轉碼器對象完成相關初始化操做,值得注意的是targetDes.mFramesPerPacket設置爲1024,AAC軟編碼須要餵給轉換器1024個樣點纔開始編碼,不可隨意更改,緣由以下圖,由AAC編碼器決定。
- (void)initRecordeCallback {
// 設置回調,有兩種方式,一種是採集pcm的BUFFER使用系統回調中的參數,另外一種是使用咱們本身的,本例中使用的是本身的,因此回調中的ioData爲空。
// 方法1:
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = RecordCallback;
recordCallback.inputProcRefCon = (__bridge void *)self;
OSStatus status = AudioUnitSetProperty(_audioUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
INPUT_BUS,
&recordCallback,
sizeof(recordCallback));
// 方法2:
AURenderCallbackStruct renderCallback;
renderCallback.inputProc = RecordCallback;
renderCallback.inputProcRefCon = (__bridge void *)self;
AudioUnitSetProperty(_rioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, & RecordCallback, sizeof(RecordCallback));
if (status != noErr) {
// log4cplus_info("Audio Recoder", "Audio Unit set record Callback failed, status : %d \n",status);
}
}
複製代碼
解析
以上爲設置採集回調,有兩種方式,1種爲使用咱們本身的buffer,這樣須要先在上述initBuffer中禁用系統的buffer,則回調函數中每次渲染的爲咱們本身的buffer,另外一種則是使用系統的buffer,對應須要在回調函數中將ioData放進渲染的函數中。
static OSStatus RecordCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
/*
注意:若是採集的數據是PCM須要將dataFormat.mFramesPerPacket設置爲1,而本例中最終要的數據爲AAC,由於本例中使用的轉換器只有每次傳入1024幀才能開始工做,因此在AAC格式下須要將mFramesPerPacket設置爲1024.也就是採集到的inNumPackets爲1,在轉換器中傳入的inNumPackets應該爲AAC格式下默認的1,在此後寫入文件中也應該傳的是轉換好的inNumPackets,若是有特殊需求須要將採集的數據量小於1024,那麼須要將每次捕捉到的數據先預先存儲在一個buffer中,等到攢夠1024幀再進行轉換。
*/
XDXRecorder *recorder = (XDXRecorder *)inRefCon;
// 將回調數據傳給_buffList
AudioUnitRender(recorder->_audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, recorder->_buffList);
void *bufferData = recorder->_buffList->mBuffers[0].mData;
UInt32 bufferSize = recorder->_buffList->mBuffers[0].mDataByteSize;
// printf("Audio Recoder Render dataSize : %d \n",bufferSize);
// 因爲PCM轉成AAC的轉換器每次須要有1024個採樣點(每一幀2個字節)才能完成一次轉換,因此每次須要2048大小的數據,這裏定義的pcm_buffer用來累加每次存儲的bufferData
memcpy(pcm_buffer+pcm_buffer_size, bufferData, bufferSize);
pcm_buffer_size = pcm_buffer_size + bufferSize;
if(pcm_buffer_size >= kTVURecoderPCMMaxBuffSize) {
AudioBufferList *bufferList = convertPCMToAAC(recorder);
// 由於採樣不可能每次都精準的採集到1024個樣點,因此若是大於2048大小就先填滿2048,剩下的跟着下一次採集一塊兒送給轉換器
memcpy(pcm_buffer, pcm_buffer + kTVURecoderPCMMaxBuffSize, pcm_buffer_size - kTVURecoderPCMMaxBuffSize);
pcm_buffer_size = pcm_buffer_size - kTVURecoderPCMMaxBuffSize;
// free memory
if(bufferList) {
free(bufferList->mBuffers[0].mData);
free(bufferList);
}
}
return noErr;
}
複製代碼
解析
在該回調中若是採用咱們本身定義的全局buffer,則回調函數參數中的ioData爲NULL,再也不使用,若是想使用ioData按照上述設置並將其放入AudioUnitRender函數中進行渲染,回調函數中採用pcm_buffer存儲滿2048個字節的數組傳給轉換器,這是編碼器的特性,因此若是採集的數據小於2048先取pcm_buffer的前2048個字節,後面的數據與下次採集的PCM數據累加在一塊兒。上述轉換過程在AudioQueue中已經有介紹,邏輯徹底相同,可在上文中閱讀。
-(void)copyEncoderCookieToFile
{
// Grab the cookie from the converter and write it to the destination file.
UInt32 cookieSize = 0;
OSStatus error = AudioConverterGetPropertyInfo(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, NULL);
// If there is an error here, then the format doesn't have a cookie - this is perfectly fine as som formats do not. // log4cplus_info("cookie","cookie status:%d %d",(int)error, cookieSize); if (error == noErr && cookieSize != 0) { char *cookie = (char *)malloc(cookieSize * sizeof(char)); // UInt32 *cookie = (UInt32 *)malloc(cookieSize * sizeof(UInt32)); error = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, cookie); // log4cplus_info("cookie","cookie size status:%d",(int)error); if (error == noErr) { error = AudioFileSetProperty(mRecordFile, kAudioFilePropertyMagicCookieData, cookieSize, cookie); // log4cplus_info("cookie","set cookie status:%d ",(int)error); if (error == noErr) { UInt32 willEatTheCookie = false; error = AudioFileGetPropertyInfo(mRecordFile, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie); printf("Writing magic cookie to destination file: %u\n cookie:%d \n", (unsigned int)cookieSize, willEatTheCookie); } else { printf("Even though some formats have cookies, some files don't take them and that's OK\n"); } } else { printf("Could not Get kAudioConverterCompressionMagicCookie from Audio Converter!\n"); } free(cookie); } } 複製代碼
解析
Magic cookie 是一種不透明的數據格式,它和壓縮數據文件與流聯繫密切,若是文件數據爲CBR格式(無損),則不須要添加頭部信息,若是爲VBR須要添加,// if collect CBR needn't set magic cookie , if collect VBR should set magic cookie, if needn't to convert format that can be setting by audio queue directly.
- (void)startAudioUnitRecorder {
OSStatus status;
if (isRunning) {
// log4cplus_info("Audio Recoder", "Start recorder repeat \n");
return;
}
[self initGlobalVar];
// log4cplus_info("Audio Recoder", "starup PCM audio encoder \n");
status = AudioOutputUnitStart(_audioUnit);
// log4cplus_info("Audio Recoder", "AudioOutputUnitStart status : %d \n",status);
if (status == noErr) {
isRunning = YES;
hostTime = 0;
}
}
-(void)stopAudioUnitRecorder {
if (isRunning == NO) {
// log4cplus_info("Audio Recoder", "Stop recorder repeat \n");
return;
}
// log4cplus_info("Audio Recoder","stop pcm encoder \n");
isRunning = NO;
[self copyEncoderCookieToFile];
OSStatus status = AudioOutputUnitStop(_audioUnit);
if (status != noErr){
// log4cplus_info("Audio Recoder", "stop AudioUnit failed. \n");
}
AudioFileClose(mRecordFile);
}
複製代碼
解析
因爲AudioUnit的初始化在本類中初始化方法中完成,因此只須要調用start,stop方法便可控制錄製轉碼過程。切記不可在start方法中完成audio unit對象的建立和初始化,不然會發生異常。