使用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;
}
复制代码