iOS开发技巧直播转载

Audio Queue Services 解读之 Playing

2017-01-24  本文已影响955人  gitKong

前言:

Audio Queue Services 解读之 Playing Audio(下)

Playing Audio

当你使用 Audio Queue Services 播放音频的时候,音频源可以是任何东西-磁盘文件、基于软件的音频合成、内存中的音频对象等等,这节介绍的是播放磁盘文件

注意:本章介绍了基于ANSI-C的播放实现,以及一些来自Mac OS X Core Audio SDK的C ++类。 对于基于Objective-C的示例,请参阅 iOS Dev Center 中的SpeakHere示例代码。

为你的应用添加播放功能,你通常需要实现下面步骤:


一、Define a Custom Structure to Manage State

开始之前,先定义一个自定义的结构体,用来管理你的音频样式和音频队列状态信息,例如:

static const int kNumberBuffers = 3;                              // 1
struct AQPlayerState {
    AudioStreamBasicDescription   mDataFormat;                    // 2
    AudioQueueRef                 mQueue;                         // 3
    AudioQueueBufferRef           mBuffers[kNumberBuffers];       // 4
    AudioFileID                   mAudioFile;                     // 5
    UInt32                        bufferByteSize;                 // 6
    SInt64                        mCurrentPacket;                 // 7
    UInt32                        mNumPacketsToRead;              // 8
    AudioStreamPacketDescription  *mPacketDescs;                  // 9
    bool                          mIsRunning;                     // 10
};

此结构中的大多数字段与用于记录的自定义结构中的字段相同(或接近),如定义管理状态的Define a Custom Structure to Manage State中的录音音频章节所述。 例如,mDataFormat字段在此用于保存正在播放的文件的格式。 当记录时,类似字段保存正在写入磁盘的文件的格式。

下面介绍一下结构体中的字段:


二、 Write a Playback Audio Queue Callback

下一步就是写一个音频队列 Audio queue 的回调函数,这个函数做了三件事:

此部分显示回调声明示例,分别描述每个任务,最后呈现整个回放回调。 有关回放回调的作用的说明,可以参考下图。


图

(1)、The Playback Audio Queue Callback Declaration

下面展示的 音频队列 播放回调实例声明,在AudioQueue.h头文件中声明为AudioQueueOutputCallback

static void HandleOutputBuffer (
    void                 *aqData,                 // 1
    AudioQueueRef        inAQ,                    // 2
    AudioQueueBufferRef  inBuffer                 // 3
)

介绍一下方法参数:

(2)、Reading From a File into an Audio Queue Buffer(从文件中读取到音频队列缓冲区)

回放音频队列回调的第一个动作是从音频文件读取数据并将其放置在音频队列缓冲器中。看下面代码:

AudioFileReadPackets (                        // 1
    pAqData->mAudioFile,                      // 2
    false,                                    // 3
    &numBytesReadFromFile,                    // 4
    pAqData->mPacketDescs,                    // 5
    pAqData->mCurrentPacket,                  // 6
    &numPackets,                              // 7
    inBuffer->mAudioData                      // 8
);

参数说明:

(3)、Enqueuing an Audio Queue Buffer (将音频队列缓冲区 buffers 加入缓冲区队列 audio queue 中)

现在,已经从音频文件读取数据并将其放入音频队列缓冲区中,回调将缓冲区排入队列,如下面代码所示。 一旦进入缓冲器队列,缓冲器中的音频数据就可供音频队列发送到输出设备

AudioQueueEnqueueBuffer (                      // 1
    pAqData->mQueue,                           // 2
    inBuffer,                                  // 3
    (pAqData->mPacketDescs ? numPackets : 0),  // 4
    pAqData->mPacketDescs                      // 5
);

下面介绍一下参数:

(4)、Stopping an Audio Queue(停止音频队列)

回调函数的最后一件事是检查是否没有更多的数据要从你正在播放的音频文件中读取。 一旦发现文件的结尾,回调就要告诉播放音频队列停止,看如下代码处理:

if (numPackets == 0) {                          // 1
    AudioQueueStop (                            // 2
        pAqData->mQueue,                        // 3
        false                                   // 4
    );
    pAqData->mIsRunning = false;                // 5
}

介绍一下代码:

(5)、A Full Playback Audio Queue Callback(完整的回调函数)

static void HandleOutputBuffer (
    void                *aqData,
    AudioQueueRef       inAQ,
    AudioQueueBufferRef inBuffer
) {
    // 官方文档这里写的有问题
    //AQPlayerState *pAqData = (AQPlayerState *) aqData;        // 1
    struct AQPlayerState *pAqData = aqData;
    if (pAqData->mIsRunning == 0) return;                     // 2
    UInt32 numBytesReadFromFile;                              // 3
    UInt32 numPackets = pAqData->mNumPacketsToRead;           // 4
    AudioFileReadPackets (
        pAqData->mAudioFile,
        false,
        &numBytesReadFromFile,
        pAqData->mPacketDescs, 
        pAqData->mCurrentPacket,
        &numPackets,
        inBuffer->mAudioData 
    );
    if (numPackets > 0) {                                     // 5
        inBuffer->mAudioDataByteSize = numBytesReadFromFile;  // 6
       AudioQueueEnqueueBuffer ( 
            pAqData->mQueue,
            inBuffer,
            (pAqData->mPacketDescs ? numPackets : 0),
            pAqData->mPacketDescs
        );
        pAqData->mCurrentPacket += numPackets;                // 7 
    } else {
        AudioQueueStop (
            pAqData->mQueue,
            false
        );
        pAqData->mIsRunning = false; 
    }
}

部分代码介绍:


三、Write a Function to Derive Playback Audio Queue Buffer Size(编写一个函数去获取播放音频队列缓冲区的大小)

Audio Queue Services 希望你在应用里面给你的音频队列缓冲区指定大小,下面提供的代码可以导出足够大的缓冲器大小以容纳给定持续时间的音频数据。

创建音频队列后,可以通过调用 DeriveBufferSize(下面的方法)来作为请求音频队列分配缓冲区的先决条件。可查看 Set Sizes for a Playback Audio Queue

这里的代码做了两个额外的事情,对比 Write a Function to Derive Recording Audio Queue Buffer Size 类似的

这里的计算考虑了从磁盘读取的音频数据格式。 格式包括可能影响缓冲区大小的所有因素,如音频通道数

void DeriveBufferSize (
    // 不应该有&
    //AudioStreamBasicDescription &ASBDesc,                            // 1
    AudioStreamBasicDescription ASBDesc,
    UInt32                      maxPacketSize,                       // 2
    Float64                     seconds,                             // 3
    UInt32                      *outBufferSize,                      // 4
    UInt32                      *outNumPacketsToRead                 // 5
) {
    static const int maxBufferSize = 0x50000;                        // 6
    static const int minBufferSize = 0x4000;                         // 7
 
    if (ASBDesc.mFramesPerPacket != 0) {                             // 8
        Float64 numPacketsForTime =
            ASBDesc.mSampleRate / ASBDesc.mFramesPerPacket * seconds;
        *outBufferSize = numPacketsForTime * maxPacketSize;
    } else {                                                         // 9
        *outBufferSize =
            maxBufferSize > maxPacketSize ?
                maxBufferSize : maxPacketSize;
    }
 
    if (                                                             // 10
        *outBufferSize > maxBufferSize &&
        *outBufferSize > maxPacketSize
    )
        *outBufferSize = maxBufferSize;
    else {                                                           // 11
        if (*outBufferSize < minBufferSize)
            *outBufferSize = minBufferSize;
    }
 
    *outNumPacketsToRead = *outBufferSize / maxPacketSize;           // 12
}

代码介绍:


四、Open an Audio File for Playback(打开音频文件播放)

现在播放音频文件只需要下面三个步骤:

(1)、Obtaining a CFURL Object for an Audio File(获取播放文件的CFURL对象)

通过下面代码获取:

CFURLRef audioFileURL =
    CFURLCreateFromFileSystemRepresentation (           // 1
        NULL,                                           // 2
        (const UInt8 *) filePath,                       // 3
        strlen (filePath),                              // 4
        false                                           // 5
    );

代码介绍:

还有一种方法,我觉得是比较常用的,在我demo就使用这个,这个是通过传入一个NSString 路径实现的

CFStringRef strRef = (__bridge CFStringRef)filePath;
// CFURLPathStyle 不建议使用kCFURLHFSPathStyle。 使用HFS样式路径的Carbon文件管理器已被弃用。 HFS样式路径不可靠,因为它们可以随意引用多个卷(如果这些卷具有相同的卷名称)。 您应该尽可能使用kCFURLPOSIXPathStyle。
    CFURLRef audioFileURL =
    CFURLCreateWithFileSystemPath(NULL,
                                  strRef,
                                  kCFURLPOSIXPathStyle,
                                  YES
                                  );

(2)、Opening an Audio File(打开音频文件)

下面示例演示怎么去打开一个音频文件去播放

AQPlayerState aqData;                                   // 1
 
OSStatus result =
    AudioFileOpenURL (                                  // 2
        audioFileURL,                                   // 3
        fsRdPerm,                                       // 4
        0,                                              // 5
        &aqData.mAudioFile                              // 6
    );
 
CFRelease (audioFileURL); 

代码解释:

(3)、Obtaining a File’s Audio Data Format(获取文件的音频数据格式)

上代码:

UInt32 dataFormatSize = sizeof (aqData.mDataFormat);    // 1
 
AudioFileGetProperty (                                  // 2
    aqData.mAudioFile,                                  // 3
    kAudioFilePropertyDataFormat,                       // 4
    &dataFormatSize,                                    // 5
    &aqData.mDataFormat                                 // 6
);

下篇将介绍 创建音频播放队列并实现播放,会附上Demo,前往:Audio Queue Services 解读之 Playing Audio(下)


欢迎大家关注我,喜欢就点个like和star,你的支持将是我的动力~

翻译过来的可能有出入,如果大家发现有什么问题或者写错的,欢迎留言,谢谢

上一篇下一篇

猜你喜欢

热点阅读