一般情況下,如果我們只需要實現(xiàn)簡單的錄音功能,那我們可以使用AVAudioRecorder這個類就可以了变秦,優(yōu)點是容易使用,系統(tǒng)已經(jīng)幫我們把功能都封裝好了席爽,缺點就是難以做更加細致的控制意荤。
我們項目里由于涉及到音頻數(shù)據(jù)編碼,所以使用的是AudioQueue來實現(xiàn)只锻。
實現(xiàn)原理:
首先看下蘋果官方提供的原理圖:
我們可以看到其實AudioQueue就是一個處理輸入和輸出過程玖像。
AudioQueue有一個緩沖區(qū)隊列,硬件設(shè)備采集到數(shù)據(jù)后炬藤,填充到緩沖區(qū)御铃,然后我們通過回調(diào)函數(shù)拿到緩沖區(qū)的數(shù)據(jù),依次寫入文件或者進行其他操作沈矿。
實現(xiàn)步驟:
先定義好用到的參數(shù):
@interface YTAudioRecordManager() {
AudioQueueRef audioQRef; //音頻隊列對象指針
AudioStreamBasicDescription recordFormat; //音頻流配置
AudioQueueBufferRef audioBuffers[YBufferCount]; //音頻流緩沖區(qū)對象
}
@property(nonatomic,strong)NSString* recordFileName; //音頻目錄
@property(nonatomic,assign)AudioFileID recordFileID; //音頻文件標識 用于關(guān)聯(lián)音頻文件
@property(nonatomic,assign)SInt64 recordPacket; //錄音文件的當前包
配置好音頻流的采集參數(shù)以及要寫入的文件路勁:
- (void)initFormat {
recordFormat.mSampleRate = YDefaultSampleRate; //采樣率
recordFormat.mChannelsPerFrame = YDefalutChannel; //聲道數(shù)量
//編碼格式
recordFormat.mFormatID = kAudioFormatLinearPCM;
recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
//每采樣點占用位數(shù)
recordFormat.mBitsPerChannel = YBitsPerChannel;
//每幀的字節(jié)數(shù)
recordFormat.mBytesPerFrame = (recordFormat.mBitsPerChannel / 8) * recordFormat.mChannelsPerFrame;
//每包的字節(jié)數(shù)
recordFormat.mBytesPerPacket = recordFormat.mBytesPerFrame;
//每幀的字節(jié)數(shù)
recordFormat.mFramesPerPacket = 1;
}
- (void)initFile {
self.recordFileName = [YTRecordFileManager cacheFileWidthPath:@"tempRecordPath" Name:@"tempRecord.wav"] ;
NSLog(@"recordFile:%@",_recordFileName);
}
設(shè)計音頻流輸入信息和回調(diào)發(fā)方法上真,關(guān)聯(lián)文件路徑和AudioFileID,
- (void)initAudio {
//設(shè)置音頻輸入信息和回調(diào)
OSStatus status = AudioQueueNewInput(&recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &audioQRef);
if( status != kAudioSessionNoError )
{
NSLog(@"初始化出錯");
return ;
}
CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)self.recordFileName, NULL);
//創(chuàng)建音頻文件
AudioFileCreateWithURL(url, kAudioFileCAFType, &recordFormat, kAudioFileFlags_EraseFile,&_recordFileID);
CFRelease(url);
//計算估算的緩存區(qū)大小
int frames = [self computeRecordBufferSize:&recordFormat seconds:YBufferDurationSeconds];
int bufferByteSize = frames * recordFormat.mBytesPerFrame;
// NSLog(@"緩存區(qū)大小%d",bufferByteSize);
//創(chuàng)建緩沖器
for (int i = 0; i < YBufferCount; i++){
AudioQueueAllocateBuffer(audioQRef, bufferByteSize, &audioBuffers[i]);
AudioQueueEnqueueBuffer(audioQRef, audioBuffers[i], 0, NULL);
}
self.recordPacket = 0;
}
開始錄音
{
//當有音頻設(shè)備(比如播放音樂)導致改變時 需要配置
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
//開始錄音
OSStatus status = AudioQueueStart(audioQRef, NULL);
if( status != kAudioSessionNoError )
{
NSLog(@"開始出錯");
return;
}
self.isRecording = true;
}
回調(diào)函數(shù)處理:
void inputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc)
{
YTAudioRecordManager *audioManager = [YTAudioRecordManager sharedManager];
if (inNumPackets > 0) {
//寫入文件
AudioFileWritePackets(audioManager.recordFileID, FALSE, inBuffer->mAudioDataByteSize,inPacketDesc, audioManager.recordPacket, &inNumPackets, inBuffer->mAudioData);
//記錄位置
audioManager.recordPacket += inNumPackets;
//此處可以對音頻數(shù)據(jù)進行采集羹膳,以供其他處理睡互。
}
if (audioManager.isRecording) {
//把Buffer重新插回AudioQueue內(nèi)置的Buffer隊列中,以便循環(huán)使用
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
}
停止錄音:
- (void)stopRecord
{
if (self.isRecording)
{
self.isRecording = NO;
//停止錄音隊列和移陵像,這里無需考慮成功與否
AudioQueueStop(audioQRef, true);
AudioFileClose(_recordFileID);
}
}
對象釋放的時候銷毀:
- (void)dealloc {
AudioQueueDispose(audioQRef, TRUE);
AudioFileClose(_recordFileID);
}
將錄制好的本地音頻進行播放:使用AudioQueue進行音頻播放