使用Audio Queue實現實時播放音頻流數據.這裏以一個裝着pcm數據的caf文件爲例進行播放.node
藉助數據傳輸隊列,將不管任務數據源的音頻數據裝入隊列中,而後開啓audio queue後從隊列中循環取出音頻數據以進行播放.ios
本例藉助隊列實現音頻數據的中轉, 這裏用隊列是由於audio queue是靠數據驅動以支持播放的,因此有數據回調函數才能持續調用,若是咱們不借助隊列,就只能在audio queue的類中從回調函數中取來自音頻文件的數據,並且假設之後有別的數據源過來,使得音頻播放模塊代碼耦合度愈來愈高,而這裏藉助隊列的好處是外界不管是音頻文件仍是音頻流僅僅須要放入隊列中就好,開啓音頻模塊後咱們會從音頻隊列回調函數中取出隊列中的數據,而無需關心數據的來源.git
AudioStreamBasicDescription
: 配置傳入的音頻數據格式AudioQueueNewOutput
: 新建audio queueAudioQueueAddPropertyListener
: 監聽audio queue是否正在工做AudioQueueSetParameter
: 設置音量AudioQueueAllocateBuffer
: 爲audio queue buffer 分配內存AudioQueueEnqueueBuffer
AudioQueueStart
: 開啓audio queue下面是本例中的格式,其餘文件須要按文件格式自行配置github
// This is only for the testPCM.caf file.
AudioStreamBasicDescription audioFormat = {
.mSampleRate = 44100,
.mFormatID = kAudioFormatLinearPCM,
.mChannelsPerFrame = 1,
.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked,
.mBitsPerChannel = 16,
.mBytesPerPacket = 2,
.mBytesPerFrame = 2,
.mFramesPerPacket = 1,
};
複製代碼
// Configure Audio Queue Player
[[XDXAudioQueuePlayer getInstance] configureAudioPlayerWithAudioFormat:&audioFormat bufferSize:kXDXReadAudioPacketsNum * audioFormat.mBytesPerPacket];
複製代碼
// Configure Audio File
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"testPCM" ofType:@"caf"];
XDXAudioFileHandler *fileHandler = [XDXAudioFileHandler getInstance];
[fileHandler configurePlayFilePath:filePath];
複製代碼
開始播放前先從文件中讀取音頻數據並放入隊列,咱們這裏先讓隊列中緩存5幀音頻數據,而後再啓動audio queue player. 關於音頻文件讀取以及隊列原理這裏不作過多說明.如需幫助請參考上文閱讀前提.緩存
// Put audio data from audio file into audio data queue
[self putAudioDataIntoDataQueue];
// First put 5 frame audio data to work queue then start audio queue to read it to play.
[NSTimer scheduledTimerWithTimeInterval:0.01 repeats:YES block:^(NSTimer * _Nonnull timer) {
dispatch_async(dispatch_get_main_queue(), ^{
XDXCustomQueueProcess *audioBufferQueue = [XDXAudioQueuePlayer getInstance]->_audioBufferQueue;
int size = audioBufferQueue->GetQueueSize(audioBufferQueue->m_work_queue);
if (size > 5) {
[[XDXAudioQueuePlayer getInstance] startAudioPlayer];
[timer invalidate];
}
});
}];
複製代碼
#define kXDXAudioPCMFramesPerPacket 1
#define kXDXAudioPCMBitsPerChannel 16
static const int kNumberBuffers = 3;
struct XDXAudioInfo {
AudioStreamBasicDescription mDataFormat;
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
int mbufferSize;
};
typedef struct XDXAudioInfo *XDXAudioInfoRef;
static XDXAudioInfoRef m_audioInfo;
+ (void)initialize {
int size = sizeof(XDXAudioInfo);
m_audioInfo = (XDXAudioInfoRef)malloc(size);
}
複製代碼
在初始化方法中初始化音頻隊列,由於本例藉助另外一個類進行傳輸,因此這裏做爲實例對象,以便使用.bash
- (instancetype)init {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [super init];
self->_isInitFinish = NO;
self->_audioBufferQueue = new XDXCustomQueueProcess();
});
return _instace;
}
複製代碼
- (void)configureAudioPlayerWithAudioFormat:(AudioStreamBasicDescription *)audioFormat bufferSize:(int)bufferSize {
memcpy(&m_audioInfo->mDataFormat, audioFormat, sizeof(XDXAudioInfo));
m_audioInfo->mbufferSize = bufferSize;
BOOL isSuccess = [self configureAudioPlayerWithAudioInfo:m_audioInfo
playCallback:PlayAudioDataCallback
listenerCallback:AudioQueuePlayerPropertyListenerProc];
self.isInitFinish = isSuccess;
}
複製代碼
經過傳入的視頻數據格式ASBD, 及回調函數名稱便可建立一個對應的audio queue對象.這裏將本類做爲實例傳入,以便回調函數與本類交流.架構
注意: 由於回調函數是C語言函數的形式,因此沒法直接調用類的實例方法.async
// Create audio queue
OSStatus status = AudioQueueNewOutput(&audioInfo->mDataFormat,
playCallback,
(__bridge void *)(self),
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&audioInfo->mQueue);
if (status != noErr) {
NSLog(@"Audio Player: audio queue new output failed status:%d \n",(int)status);
return NO;
}
複製代碼
// Listen the queue is whether working
AudioQueueAddPropertyListener (audioInfo->mQueue,
kAudioQueueProperty_IsRunning,
listenerCallback,
(__bridge void *)(self));
......
static void AudioQueuePlayerPropertyListenerProc (void * inUserData,
AudioQueueRef inAQ,
AudioQueuePropertyID inID) {
XDXAudioQueuePlayer * instance = (__bridge XDXAudioQueuePlayer *)inUserData;
UInt32 isRunning = 0;
UInt32 size = sizeof(isRunning);
if(instance == NULL)
return ;
OSStatus err = AudioQueueGetProperty (inAQ, kAudioQueueProperty_IsRunning, &isRunning, &size);
if (err) {
instance->_isRunning = NO;
}else {
instance->_isRunning = isRunning;
}
NSLog(@"The audio queue work state: %d",instance->_isRunning);
}
複製代碼
// Get audio ASBD
UInt32 size = sizeof(audioInfo->mDataFormat);
status = AudioQueueGetProperty(audioInfo->mQueue,
kAudioQueueProperty_StreamDescription,
&audioInfo->mDataFormat,
&size);
if (status != noErr) {
NSLog(@"Audio Player: get ASBD status:%d",(int)status);
return NO;
}
// Set volume
status = AudioQueueSetParameter(audioInfo->mQueue, kAudioQueueParam_Volume, 1.0);
if (status != noErr) {
NSLog(@"Audio Player: set volume failed:%d",(int)status);
return NO;
}
複製代碼
// Allocate buffer for audio queue buffer
for (int i = 0; i != kNumberBuffers; i++) {
status = AudioQueueAllocateBuffer(audioInfo->mQueue,
audioInfo->mbufferSize,
&audioInfo->mBuffers[i]);
if (status != noErr) {
NSLog(@"Audio Player: Allocate buffer status:%d",(int)status);
}
}
複製代碼
由於audio queue是驅動播放的模式,因此只有數據先入隊以後纔會繼續從回調函數中輪循播放,也就是咱們須要將前面分配好內存的buffer入隊來完成播放.函數
播放採用從原始音頻數據隊列中讀取音頻數據,以下,先出隊,而後將音頻數據拷貝到AudioQueueBufferRef
實例,取出須要的信息(此隊列仍可繼續擴展).oop
for (int i = 0; i != kNumberBuffers; i++) {
[self receiveAudioDataWithAudioQueueBuffer:audioInfo->mBuffers[i]
audioInfo:audioInfo
audioBufferQueue:_audioBufferQueue];
}
......
- (void)receiveAudioDataWithAudioQueueBuffer:(AudioQueueBufferRef)inBuffer audioInfo:(XDXAudioInfoRef)audioInfo audioBufferQueue:(XDXCustomQueueProcess *)audioBufferQueue {
XDXCustomQueueNode *node = audioBufferQueue->DeQueue(audioBufferQueue->m_work_queue);
if (node != NULL) {
if (node->size > 0) {
UInt32 size = (UInt32)node->size;
inBuffer->mAudioDataByteSize = size;
memcpy(inBuffer->mAudioData, node->data, size);
AudioStreamPacketDescription *packetDesc = (AudioStreamPacketDescription *)node->userData;
AudioQueueEnqueueBuffer (
audioInfo->mQueue,
inBuffer,
(packetDesc ? size : 0),
packetDesc);
}
free(node->data);
node->data = NULL;
audioBufferQueue->EnQueue(audioBufferQueue->m_free_queue, node);
}else {
AudioQueueStop (
audioInfo->mQueue,
false
);
}
}
複製代碼
OSStatus status;
status = AudioQueueStart(m_audioInfo->mQueue, NULL);
if (status != noErr) {
NSLog(@"Audio Player: Audio Queue Start failed status:%d \n",(int)status);
return NO;
}else {
NSLog(@"Audio Player: Audio Queue Start successful");
return YES;
}
複製代碼
正如前面所說, audio queue的播放模式是數據驅動式,也就是咱們已經預先入隊了幾個音頻隊列數據,而後開啓audio queue後咱們它會自動播放前面已經入隊的數據,每當播放完會自動觸發回調函數讀取數據以完成下一次播放.
static void PlayAudioDataCallback(void * aqData,AudioQueueRef inAQ , AudioQueueBufferRef inBuffer) {
XDXAudioQueuePlayer *instance = (__bridge XDXAudioQueuePlayer *)aqData;
if(instance == NULL){
return;
}
/* Debug
static Float64 lastTime = 0;
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]*1000;
NSLog(@"Test duration - %f",currentTime - lastTime);
lastTime = currentTime;
*/
[instance receiveAudioDataWithAudioQueueBuffer:inBuffer
audioInfo:m_audioInfo
audioBufferQueue:instance->_audioBufferQueue];
}
複製代碼
Demo中還有音頻隊列的暫停, 恢復, 中止, 銷燬等功能,較爲簡單,這裏再也不說明.
CFURLRef
對象- (void)configurePlayFilePath:(NSString *)filePath {
char path[256];
[filePath getCString:path maxLength:sizeof(path) encoding:NSUTF8StringEncoding];
self->m_playFileURL = CFURLCreateFromFileSystemRepresentation (
NULL,
(const UInt8 *)path,
strlen (path),
false
);
}
複製代碼
函數中可配置文件權限及類型,本例中文件類型爲caf文件.
OSStatus status;
status = AudioFileOpenURL(self->m_playFileURL,
kAudioFileReadPermission,
kAudioFileCAFType,
&self->m_playFile);
if (status != noErr) {
NSLog(@"open file failed: %d", (int)status);
}
複製代碼
首先指定每次讀取多少個音頻數據包, 該函數會返回最終讀取的字節數. 這裏經過m_playCurrentPacket
記錄當前讀取的音頻包數以便下次繼續讀取.讀取完成後關閉文件.
UInt32 bytesRead = 0;
UInt32 numPackets = readPacketsNum;
OSStatus status = AudioFileReadPackets(m_playFile,
false,
&bytesRead,
packetDesc,
m_playCurrentPacket,
&numPackets,
audioDataRef);
if (status != noErr) {
NSLog(@"read packet failed: %d", (int)status);
}
if (bytesRead > 0) {
m_playCurrentPacket += numPackets;
}else {
status = AudioFileClose(m_playFile);
if (status != noErr) {
NSLog(@"close file failed: %d", (int)status);
}
self.isPlayFileWorking = NO;
m_playCurrentPacket = 0;
}
複製代碼