iOS Audio Queue播放本地MP3文件
在播放MP3时我们可以使用AVAudioPlayer这种高级的OC类进行播放,可以更简便的实现播放功能。如果没有需要我们应该尽量使用高级API实现功能,使用AudioQueue播放本地MP3文件过程相对来说较为复杂,我们可以使用AudioQueue播放MP3达到熟悉AudioQueue的目的。
我们准备好MP3文件,就可以开始写代码了。
1、打开MP3文件,获取相关信息
在获取到MP3文件路径后,我们需要打开MP3文件,使用下面的函数:
extern OSStatus
AudioFileOpenURL ( CFURLRef inFileRef,
AudioFilePermissions inPermissions,
AudioFileTypeID inFileTypeHint,
AudioFileID __nullable * __nonnull outAudioFile);
第一个参数为MP3文件路径,第二个参数为打开权限,填写kAudioFileReadPermission即可,第三个为类型,我们填写0,让系统自行判断即可,第四个参数为打开文件成功后保存的ID句柄。方法执行成功则返回0。
2、获取文件格式信息
我们可以通过AudioFileGetProperty方法获取文件的相关信息,方法参数如下:
extern OSStatus
AudioFileGetProperty( AudioFileID inAudioFile,
AudioFilePropertyID inPropertyID,
UInt32 *ioDataSize,
void *outPropertyData)
第一个参数为上一部获取的文件句柄;第二个参数为指定我们要获取的文件信息的内容,这里我们填写kAudioFilePropertyDataFormat,表示我们需要获取文件的格式信息;第三个参数为第四个参数的可填充大小;第四个参数为获取信息后的存放的地址,这里我们填写AudioStreamBasicDescription的结构体指针。
方法执行成功则返回0。
3、创建AudioQueue对象
extern OSStatus
AudioQueueNewOutput( const AudioStreamBasicDescription *inFormat,
AudioQueueOutputCallback inCallbackProc,
void * __nullable inUserData,
CFRunLoopRef __nullable inCallbackRunLoop,
CFStringRef __nullable inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef __nullable * __nonnull outAQ)
参数格式如下:
参数解释:
const AudioStreamBasicDescription *inFormat, //音频格式
AudioQueueOutputCallback inCallbackProc, //回调函数指针
void * __nullable inUserData, //自定义回调函数数据
CFRunLoopRef __nullable inCallbackRunLoop, //回调函数调用的RunLoop
CFStringRef __nullable inCallbackRunLoopMode, //回调函数调用的RunLoopMode
UInt32 inFlags, //保留标志位,填0
AudioQueueRef __nullable * __nonnull outAQ //存放创建的AudioQueueRef对象指针的地址
第一个填写我们第2部获取到的格式,第二个为我们的回调函数指针,第三个为自定义回调数据,根据自己需要填写,第四个和第五个可填null,第六个填0,第七个为AudioQueueRef对象指针,创建成功则会把AudioQueueRef对象指针保存到指定地址。
方法调用成功则返回0。
4、创建缓冲区
创建缓冲区之前我们需要获取我们创建的缓冲区的大小,我们要获取MP3的最大包的大小,
使用第2部的方法,第二个参数填写kAudioFilePropertyPacketSizeUpperBound,第四个参数填写为UInt32的数据的地址。
获取到最大包的大小后,计算缓冲区的大小。方法如下
void DeriveBufferSize (AudioStreamBasicDescription inDesc,UInt32 maxPacketSize,Float64 inSeconds,UInt32 *outBufferSize,UInt32 *outNumPacketsToRead) {
static const int maxBufferSize = 0x10000;
static const int minBufferSize = 0x4000;
if (inDesc.mFramesPerPacket != 0) {
//如果每个Packet不止一个Frame,则按照包进行计算
Float64 numPacketsForTime = inDesc.mSampleRate / inDesc.mFramesPerPacket * inSeconds;
*outBufferSize = numPacketsForTime * maxPacketSize;
} else {
//如果每个Packet只有一个Frame,则直接确定缓冲区大小
*outBufferSize = maxBufferSize > maxPacketSize ? maxBufferSize : maxPacketSize;
}
if (*outBufferSize > maxBufferSize && *outBufferSize > maxPacketSize){
*outBufferSize = maxBufferSize;
}
else {
if (*outBufferSize < minBufferSize){
*outBufferSize = minBufferSize;
}
}
*outNumPacketsToRead = *outBufferSize / maxPacketSize;
}
然后我们判断文件格式是否为可变码率的格式,如为可变码率则需要获取每个包的格式信息,代码如下:
bool isFormatVBR = (mDataFormat.mBytesPerPacket == 0 ||mDataFormat.mFramesPerPacket == 0);
if (isFormatVBR) {
mPacketDescs =(AudioStreamPacketDescription*) malloc (_playerState.mNumPacketsToRead * sizeof (AudioStreamPacketDescription));
} else {
mPacketDescs = NULL;
}
获取到最大缓冲区大小后我们就可以创建缓冲区对象,使用如下方法:
extern OSStatus
AudioQueueAllocateBuffer( AudioQueueRef inAQ,
UInt32 inBufferByteSize,
AudioQueueBufferRef __nullable * __nonnull outBuffer)
第一个为第2步创建的AudioQueueRef对象,第二个为创建的缓冲区的大小,第三个为保存的缓冲区对象AudioQueueBufferRef的地址。
5、初始化缓冲区
我们需要从文件中读取数据,并把数据填充到要进行播放的AudioQueueRef对象的队列中去,读取数数据的函数如下:
extern OSStatus
AudioFileReadPacketData ( AudioFileID inAudioFile,
Boolean inUseCache,
UInt32 * ioNumBytes,
AudioStreamPacketDescription * __nullable outPacketDescriptions,
SInt64 inStartingPacket,
UInt32 * ioNumPackets,
void * __nullable outBuffer)
第一个参数为我们第一步创建的文件句柄ID;第二个参数为是否使用缓存,我们填写false;第三个为读取的字节数,填写4096;第四个参数为我们上一步创建的mPacketDescs对象,若为固定码率则填null即可;第五个填写我们从哪个包开始读取,开始我们从0开始,这里我们需要记录我们当前读取的位置,以便下次进行从上次读取完的位置连续读取数据;第六个参数为存储读取的packet的数量;第7个参数保存读取数据的指针。读取出数据后,把outBuffer的数据拷贝到AudioQueueBufferRef对象的mAudioData地址。
读取数据完毕后我们将数据填充到缓冲区队列,使用如下方法:
extern OSStatus
AudioQueueEnqueueBuffer( AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
UInt32 inNumPacketDescs,
const AudioStreamPacketDescription * __nullable inPacketDescs)
第一个参数为我们需要填充的目标对象,填入我们第三步创建的AudioQueueRef对象即可;第二个参数为我们填充好数据的缓冲区对象了;第三个参数为上一步的AudioStreamPacketDescription对象的大小;第四个参数为上一步创建的AudioStreamPacketDescription对象指针。固定码率的第三个和第四个参数填0。
6、实现回调函数
回调函数函数指针填入第三步的第二个参数,回调函数类型如下:
typedef void (*AudioQueueOutputCallback)(
void * __nullable inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer);
回调函数在AudioQueueRef播放完一个缓冲区对象数据后会立马调用回调函数,让使用者对缓冲区对象的数据进行重新赋值,并填充到播放队列里面去,就是重复第5部分的数据读取及填充的工作。每次都从文件中读取新的数据,直到数据读取完毕为止。
7、启动AudioQueueRef播放队列
启动函数如下:
extern OSStatus
AudioQueueStart( AudioQueueRef inAQ,
const AudioTimeStamp * __nullable inStartTime)
启动队列第一个参数为启动的对象,为第三步创建的对象,第二个为开始时间,立即启动填写null即可。
在子线程启动还需要同时启动子线程的runloop。如下:
[[NSRunLoop currentRunLoop] run];
8、暂停播放、停止播放
extern OSStatus
AudioQueuePause( AudioQueueRef inAQ); //暂停播放
extern OSStatus
AudioQueueStop( AudioQueueRef inAQ,
Boolean inImmediate); //停止播放
其中的参数inAQ为我们需要暂停或者停止的AudioQueueRef对象,inImmediate为是否立即停止,我们填写true即可。
暂停播放后可以调用开始播放方法进行继续播放,停止播放后需要重新打开文件进行播放。
最后欢迎大家留言交流,同时附上Demo地址:
Demo地址:https://github.com/XMSECODE/ESAudioQueueDemo