上次要寫Audio Queue相關記錄的時候過了太久,也懶得寫,索性就把草稿給刪掉,結果好死不死今天又在寫了一次QQ
今天因為產品需要做聲音串流,在Apple device上面的Audio framework很多,最簡單的應該就是AVAudioSession,原本以為該framework具備像AVVideoOutputSession之類的framework有delegate可以獲得sample buffer的data,結果很不巧的AVAudioSession並沒有,囧~估狗了一下,結果網路上竟然推薦的事AudioQueue的方式,好吧!既然這樣,就把之前的code拿出來好好複習一下!
1. 首先需要先在Build Phases裡面先載入AudioToolBox.framework
2. example code about Recorder(Audio Manager *.h)
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioFile.h>
#define NUM_BUFFERS 3
@interface L17AudioManager : NSObject{
AudioStreamBasicDescription dataFormat;
AudioQueueRef queue;
AudioQueueBufferRef buffers[NUM_BUFFERS];
}
@property AudioQueueRef queue;
//Audio file
@property SInt64 currentPacket;
@property AudioFileID audioFile;
+ (L17AudioManager *)defaultAudioManager;
- (void)start;
- (void)stop;
@end
上面的code是有關Recorder的.h檔,在該檔案中主要需要宣告三個property
- queue(用來存放音訊的buffer)
- Buffer(用來存放音訊)
- dataFormat(用來設定音訊的基本設定ex:sample rate)
3. example code(*.m):接下來的code主要分成四個部分,分別是:
- AudioInputCallback
- init dataFormat
- Start Record
- Stop Record
a. AudioInputCallback
static void AudioInputCallback(void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBUffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumberPacketDescriptions,
const AudioStreamPacketDescription *inPacketDescs){
L17AudioManager *audioManager = (__bridge L17AudioManager *)inUserData;
//Write Audio data to File
OSStatus status = AudioFileWritePackets(audioManager.audioFile, NO, inBUffer->mAudioDataByteSize, inPacketDescs, audioManager.currentPacket, &inNumberPacketDescriptions, inBUffer->mAudioData);
if(status == noErr){
audioManager.currentPacket += inNumberPacketDescriptions;
// Enqueue buffer to AudioQueue again
AudioQueueEnqueueBuffer(audioManager.queue,inBUffer,0,nil);
}
NSLog(@"Audio callback");
}
b. Init dataFormat
- (id)init{
if(self = [super init]){
dataFormat.mSampleRate = 44100.0f;
dataFormat.mFormatID = kAudioFormatLinearPCM;
dataFormat.mFramesPerPacket = 1;
dataFormat.mChannelsPerFrame = 1;
dataFormat.mBytesPerFrame = 2;
dataFormat.mBytesPerPacket = 2;
dataFormat.mBitsPerChannel = 16;
dataFormat.mReserved = 0;
dataFormat.mFormatFlags =
kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
}
return self;
}
c. Start Record
- (void)start{
NSLog(@"Audio start");
//Setting Input
AudioQueueNewInput(&dataFormat,
AudioInputCallback,
(__bridge void *)(self),
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&queue);
//Setting Buffer
for(int i=0;i<NUM_BUFFERS;i++){
AudioQueueAllocateBuffer(queue, (dataFormat.mSampleRate/10.0f)*dataFormat.mBytesPerFrame, &buffers[i]);
AudioQueueEnqueueBuffer(queue, buffers[i], 0, nil);
}
//Setting property
UInt32 enabledLevelMeter = true;
AudioQueueSetProperty(queue, kAudioQueueProperty_EnableLevelMetering, &enabledLevelMeter, sizeof(UInt32));
//Setting file name which will be write
NSString *fileName = [NSString stringWithFormat:@"%@/Desktop/record.wav",NSHomeDirectory()];
NSLog(@"fileName = %@",fileName);
self.currentPacket = 0;
//Create write file
AudioFileCreateWithURL((__bridge CFURLRef)([NSURL URLWithString:fileName]),
kAudioFileWAVEType,
&dataFormat,
kAudioFileFlags_EraseFile,
&audioFile);
//Start record
AudioQueueStart(queue, nil);
}
d. Stop Record
- (void)stop{
NSLog(@"Audio stop");
AudioQueuePause(queue);
AudioQueueFlush(queue);
AudioQueueStop(queue, NO);
//free buffer
for(int i=0;i<NUM_BUFFERS;i++){
AudioQueueFreeBuffer(queue, buffers[i]);
}
//Dispose AudioQueue
AudioQueueDispose(queue, YES);
//Close AudioFile
AudioFileClose(audioFile);
}
上面的stop需要先pause在stop,否則會出現如下圖的error
其實AudioQueue有還有許多的細節,但是大觀念上如下圖(Recorder):
再上圖中Audio Queue裡面其實放了很多buffer,所以Queue裡面的內容就是Audio的buffer,在聲音被device接收之後,會先放入最前面的buffer之中,然後會被取出,當取出的時候會有一個callback即AudioInputCallback,所以在這個時候我們便可以針對該小段音訊做一些處理然後再寫入disk中,最後有一個enqueue的動作是要把剛剛取出的buffer再塞回Audio queue之中,這樣可以保證Queue裡面的buffer數量和原先設定的一樣,才不會造成到最後沒buffer可以用的情況發生。
Reference:
沒有留言:
張貼留言