iOS Audio Queue播放本地MP3文件

2018-11-29  本文已影响0人  iOS_tree

在播放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

上一篇 下一篇

猜你喜欢

热点阅读