音频程序猿阵线联盟-汇总各类技术干货

Audio Queue录制 播放原理

2019-05-03  本文已影响0人  小东邪啊

阅读前提:

Audio Queue Services是官方推荐的方式以一种直接的,低开销的方式在iOS与Mac OS X中完成录制与播放的操作.不像上层的API,它可以通过回调拿到音频帧数据,以完成更加精细的操作.

使用场景:

比上层API而言,可以直接获取每一帧音频数据,因此可以对音频帧做一些需要的处理. 但是无法对声音做一些更加精细的处理,如回声消除,混音,降噪等等,如果需要做更底层的操作,需要使用Audio Unit.

Overview

Audio Queue Service是Core Audio的Audio Toolbox框架中的基于C语言的一套接口.

Audio Queue Services是一套高级的API. 它不仅可以在无需了解硬件的基础上使程序与音频硬件(麦克风,扬声器等)之间完成交互,也在无需了解编解码器的原理情况下让我们使用复杂的编解码器.

同时,Audio Queue Services还提供了更加精细的定时控制以支持预定的播放与同步任务.可以使用它同步多个音频播放队列或者音视频间进行同步.

支持以下格式

注意: Audio Queue Services是一套纯C的接口,所以基础的C,C++需要有一定了解.

1. Audio Queues概述

在iOS, Mac OS X中audio queue是一个软件层面的对象,可以用来做录制与播放操作.使用AudioQueueRef代表其数据结构.

作用

1.1. Audio Queue架构

1.2. 录制

如果要使用audio queue的录制功能,通过AudioQueueNewInput创建录音队列.

1.record

录制使用的audio queue的输入端通常是当前设备连接的音频设备,如内置的麦克风,或外置的带麦克风功能的输入设备.输出端是我们定义的回调函数.如果将音频数据录制成文件,可以在回调函数中将从audio queue中取出的音频数据写入文件.当然录制的音频数据也可以直接送给当前App以实现边录制边播放的功能.

每个audio queue,不管是用于录制或播放,都至少有一个或多个音频数据.所有的音频数据被放在一个被称为音频队列buffer特殊的数据结构中,可以理解成队列中的结点.如上图所示,指定数量的buffer按顺序依次被放入音频队列中,它们最终也将在回调函数中按顺序取出.

1.3. 播放

如果要使用audio queue的播放功能,通过AudioQueueNewOutput创建播放队列对象.

2.play

播放使用的音频队列,回调函数在输入端.该回调函数将从本地或其他音频数据源获取到的数据交给音频队列中.当没有数据装入播放回调函数也会告诉音频队列停止播放.

用于播放的音频队列的输出端则连接着音频输出硬件,如扬声器或外接的具有扬声器功能的音频设备(如:耳机,音响等).

1.4. 音频队列数据

AudioQueueBuffer用于存放音频队列数据.

typedef struct AudioQueueBuffer {
    const UInt32   mAudioDataBytesCapacity;
    void *const    mAudioData;
    UInt32         mAudioDataByteSize;
    void           *mUserData;
} AudioQueueBuffer;
typedef AudioQueueBuffer *AudioQueueBufferRef;

音频队列可以使用任意数量的音频数据结点,但一般建议用3个即可.因为如果太少则存取过于频繁,太多则增加应用程序内存消耗,正常情况下两个即可,我们可以使用第三个当有延迟情况出现时作为补偿数据.

因为Audio Queue的纯C函数,内存需要我们手动管理.

通过内存管理,可以使录制播放更加稳定,同时优化App资源使用.

1.5. 音频队列与入队操作

audio queue: 音频队列, 即Audio Queue Services的名字

audio queue buffer : 音频队列中存放一个或多个结点数据

做录制操作时,一个audio queue buffer将被从输入设备(如:麦克风)采集的音频数据填充.音频队列中剩余的buffer按顺序排列在当前填充数据的buffer之后,依次等待被填充数据.在输出端,回调函数将按照指定时间间隔依次接收音频队列中按顺序排列好的音频数据.工作原理如下图:

3.recording_process

图一: 录制开始,音频队列中填充需要的音频数据.

图二: 第一个buffer被填充,对调函数取出buffer 1并将其写入文件,同时buffer2也被填充完数据.

图三: 在第4步,回调函数将用完的buffer 1重新放回音频队列,随后第五步回调函数再次取出音频数据buffer2,最终将其写入文件而后重新放回音频队列此后循环往复直到录制停止.

做播放操作时,一个audio queue buffer需要交给输出设备(如:扬声器).剩余的音频数据也将按顺序排列在当前取出播放的音频数据之后,等待播放.回调函数将按顺序取出音频队列中的数据交给扬声器,随后将用完的audio queue buffer重新放入音频队列.

4.playback_process

图1: 应用程序启动音频播放队列,每调用依次回调函数填充一个audio queue buffers,填充完后将其放入音频队列. 当应用程序调用AudioQueueStart立即开始播放.

图2: 音频队列输出第一个音频数据

图3: 用完的audio queue buffer重新放入音频队列.一旦播放了第一个音频数据,音频队列会进入一个循环稳定的状态,即开始播放下一个buffer2(第4步)然后调用回调函数准备填充数据(第5步),最后(第6步)buffer1重新被填充并装入音频队列依次循环直到音频队列停止.

Audio queue buffers始终按照入队顺序进行播放.然而可以使用AudioQueueEnqueueBufferWithParameters函数做一些额外控制

a. 设置缓冲区精确的播放时间,用于同步

b. 可以裁剪开始或结尾的audio queue buffer,这使我们可以做到开始或结尾的静音效果.

c. 增加播放的声音

后文播放章节中将具体介绍.

1.6. 回调函数

无论录制还是播放,一旦注册好回调函数,它将频繁的被调用.调用时间取决于我们的设置.回调函数的一个重要职责是将用完的数据重新交给音频队列.使用AudioQueueEnqueueBuffer入队.

1.6.1. 录制的回调函数
AudioQueueInputCallback (
    void                               *inUserData,
    AudioQueueRef                      inAQ,
    AudioQueueBufferRef                inBuffer,
    const AudioTimeStamp               *inStartTime,
    UInt32                             inNumberPacketDescriptions,
    const AudioStreamPacketDescription *inPacketDescs
);

当输入端采集到音频数据时就会触发回调,可以从回调函数中取出装有音频数据的audio queue buffer.

1.6.2. 播放的回调函数
AudioQueueOutputCallback (
    void                  *inUserData,
    AudioQueueRef         inAQ,
    AudioQueueBufferRef   inBuffer
);

在回调函数中将读取音频数据以用来播放

如果应用程序正在播放VBR格式数据,这个回调函数需要通过AudioFileReadPackets获取音频数据包信息.然后,回调将数据包信息放入自定义数据结构中,以使其可用于播放音频队列。

1.7. 使用编解码器

Audio Queue Services使音频编解码器用于转换音频数据格式.你的录制或播放可以使用编解码器支持的任意格式.

每个audio queue有一个自己的音频数据格式,被封装在AudioStreamBasicDescription中,通过mFormatID可以指定音频数据格式,audio queue会自动选择适当编解码器对其压缩.开发者可以指定采样率,声道数等等参数自定义音频数据.

5.record_convert

如上图,应用程序告诉音频队列使用指定格式开始录制,音频队列在获取到原生的PCM数据后使用编码器将其转换为AAC类型数据,然后音频队列通知回调函数,将转换好的数据放入audio queue buffer中传给回调函数.最后,回调函数拿到转换好的AAC数据进行使用.

6.play_convert

如上图,应用程序告诉音频队列播放指定的格式(AAC)的文件,音频队列调用回调函数从音频文件中读取音频数据,回调函数将原始格式的数据传给音频队列.最后,音频队列使用合适的解码器将音频数据(PCM)交给扬声器.

音频队列可以利用任何编解码器无论是系统自带的还是第三方安装的(仅Mac OS)

1.7. 生命周期

音频队列在创建与销毁间的活动范围称为它的声明周期.

AudioQueueStop可以选择以同步或异步的方式停止.

1.8. 参数设置

音频队列有一个可以调节的设置称为参数,每个参数都有一个枚举常量作为其键,一个浮点型作为其值,该值仅用于播放.

以下有两种方式设置参数

使用kAudioQueueParam_Volume可以调节播放音量(0.0~1.0)

2. 录制

使用Audio Queue Services进行录制,输出端可以是一个文件,网络协议传输,拷贝给一个对象等等.这里仅介绍输出到文件.

流程

2.1. 使用自定义结构体管理状态信息

第一步是自定义一个结构体管理音频格式及状态信息.

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

2.2. 回调函数

static void HandleInputBuffer (
    void                                *aqData,             // 1
    AudioQueueRef                       inAQ,                // 2
    AudioQueueBufferRef                 inBuffer,            // 3
    const AudioTimeStamp                *inStartTime,        // 4
    UInt32                              inNumPackets,        // 5
    const AudioStreamPacketDescription  *inPacketDesc        // 6
)

2.3. 将数据写入本地文件

使用AudioFileWritePackets将数据写入音频文件.

AudioFileWritePackets (                     // 1
    pAqData->mAudioFile,                    // 2
    false,                                  // 3
    inBuffer->mAudioDataByteSize,           // 4
    inPacketDesc,                           // 5
    pAqData->mCurrentPacket,                // 6
    &inNumPackets,                          // 7
    inBuffer->mAudioData                    // 8
);

2.4. 入队

当音频数据在回调函数中用完后,需要重新放回音频队列以便存储新的音频数据

AudioQueueEnqueueBuffer (                    // 1
    pAqData->mQueue,                         // 2
    inBuffer,                                // 3
    0,                                       // 4
    NULL                                     // 5
);

2.5. 完整的录制回调

static void HandleInputBuffer (
    void                                 *aqData,
    AudioQueueRef                        inAQ,
    AudioQueueBufferRef                  inBuffer,
    const AudioTimeStamp                 *inStartTime,
    UInt32                               inNumPackets,
    const AudioStreamPacketDescription   *inPacketDesc
) {
    AQRecorderState *pAqData = (AQRecorderState *) aqData;               // 1
 
    if (inNumPackets == 0 &&                                             // 2
          pAqData->mDataFormat.mBytesPerPacket != 0)
       inNumPackets =
           inBuffer->mAudioDataByteSize / pAqData->mDataFormat.mBytesPerPacket;
 
    if (AudioFileWritePackets (                                          // 3
            pAqData->mAudioFile,
            false,
            inBuffer->mAudioDataByteSize,
            inPacketDesc,
            pAqData->mCurrentPacket,
            &inNumPackets,
            inBuffer->mAudioData
        ) == noErr) {
            pAqData->mCurrentPacket += inNumPackets;                     // 4
    }
   if (pAqData->mIsRunning == 0)                                         // 5
      return;
 
    AudioQueueEnqueueBuffer (                                            // 6
        pAqData->mQueue,
        inBuffer,
        0,
        NULL
    );
}

2.6. 获取Audio Queue Buffer大小

void DeriveBufferSize (
    AudioQueueRef                audioQueue,                  // 1
    AudioStreamBasicDescription  &ASBDescription,             // 2
    Float64                      seconds,                     // 3
    UInt32                       *outBufferSize               // 4
) {
    static const int maxBufferSize = 0x50000;                 // 5
 
    int maxPacketSize = ASBDescription.mBytesPerPacket;       // 6
    if (maxPacketSize == 0) {                                 // 7
        UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
        AudioQueueGetProperty (
                audioQueue,
                kAudioQueueProperty_MaximumOutputPacketSize,
                // in Mac OS X v10.5, instead use
                //   kAudioConverterPropertyMaximumOutputPacketSize
                &maxPacketSize,
                &maxVBRPacketSize
        );
    }
 
    Float64 numBytesForTime =
        ASBDescription.mSampleRate * maxPacketSize * seconds; // 8
    *outBufferSize =
    UInt32 (numBytesForTime < maxBufferSize ?
        numBytesForTime : maxBufferSize);                     // 9
}

2.7. 为音频文件设置magin cookie

对于一些压缩音频数据格式,如AAC,MPEG 4 AAC等,必须包含音频元数据.包含该元数据信息的数据结构称为magic cookies.当你录制压缩音频数据格式的音频文件时,必须从audio queue中获取元数据并将其设置给音频文件.

注意: 我们在录制前与停止录制后两个时间点都设置一次magin cookie,因为有的编码器需要在停止录制后更新magin cookie.

OSStatus SetMagicCookieForFile (
    AudioQueueRef inQueue,                                      // 1
    AudioFileID   inFile                                        // 2
) {
    OSStatus result = noErr;                                    // 3
    UInt32 cookieSize;                                          // 4
 
    if (
            AudioQueueGetPropertySize (                         // 5
                inQueue,
                kAudioQueueProperty_MagicCookie,
                &cookieSize
            ) == noErr
    ) {
        char* magicCookie =
            (char *) malloc (cookieSize);                       // 6
        if (
                AudioQueueGetProperty (                         // 7
                    inQueue,
                    kAudioQueueProperty_MagicCookie,
                    magicCookie,
                    &cookieSize
                ) == noErr
        )
            result =    AudioFileSetProperty (                  // 8
                            inFile,
                            kAudioFilePropertyMagicCookieData,
                            cookieSize,
                            magicCookie
                        );
        free (magicCookie);                                     // 9
    }
    return result;                                              // 10
}

2.8.设置录制音频的格式.

主要关注以下参数

AQRecorderState aqData;                                       // 1
 
aqData.mDataFormat.mFormatID         = kAudioFormatLinearPCM; // 2
aqData.mDataFormat.mSampleRate       = 44100.0;               // 3
aqData.mDataFormat.mChannelsPerFrame = 2;                     // 4
aqData.mDataFormat.mBitsPerChannel   = 16;                    // 5
aqData.mDataFormat.mBytesPerPacket   =                        // 6
   aqData.mDataFormat.mBytesPerFrame =
      aqData.mDataFormat.mChannelsPerFrame * sizeof (SInt16);
aqData.mDataFormat.mFramesPerPacket  = 1;                     // 7
 
AudioFileTypeID fileType             = kAudioFileAIFFType;    // 8
aqData.mDataFormat.mFormatFlags =                             // 9
    kLinearPCMFormatFlagIsBigEndian
    | kLinearPCMFormatFlagIsSignedInteger
    | kLinearPCMFormatFlagIsPacked;

2.9. 创建录制的Audio Queue

AudioQueueNewInput (                              // 1
    &aqData.mDataFormat,                          // 2
    HandleInputBuffer,                            // 3
    &aqData,                                      // 4
    NULL,                                         // 5
    kCFRunLoopCommonModes,                        // 6
    0,                                            // 7
    &aqData.mQueue                                // 8
);

2.10. 获取完整的音频格式.

当audio queue开始工作后,它可能会产生更多音频格式信息比我们初始化设置时,所以我们需要对获取到的音频数据做一个检查.

UInt32 dataFormatSize = sizeof (aqData.mDataFormat);       // 1
 
AudioQueueGetProperty (                                    // 2
    aqData.mQueue,                                         // 3
    kAudioQueueProperty_StreamDescription,                 // 4
    // in Mac OS X, instead use
    //    kAudioConverterCurrentInputStreamDescription
    &aqData.mDataFormat,                                   // 5
    &dataFormatSize                                        // 6
);

2.11. 创建一个音频文件

CFURLRef audioFileURL =
    CFURLCreateFromFileSystemRepresentation (            // 1
        NULL,                                            // 2
        (const UInt8 *) filePath,                        // 3
        strlen (filePath),                               // 4
        false                                            // 5
    );
 
AudioFileCreateWithURL (                                 // 6
    audioFileURL,                                        // 7
    fileType,                                            // 8
    &aqData.mDataFormat,                                 // 9
    kAudioFileFlags_EraseFile,                           // 10
    &aqData.mAudioFile                                   // 11
);

2.12. 设置音频队列数据大小

使用2.6.章节中的函数设置音频队列数据的大小以便后续使用.

DeriveBufferSize (                               // 1
    aqData.mQueue,                               // 2
    aqData.mDataFormat,                          // 3
    0.5,                                         // 4
    &aqData.bufferByteSize                       // 5
);

2.13. 为Audio Queue准备指定数量的buffer

for (int i = 0; i < kNumberBuffers; ++i) {           // 1
    AudioQueueAllocateBuffer (                       // 2
        aqData.mQueue,                               // 3
        aqData.bufferByteSize,                       // 4
        &aqData.mBuffers[i]                          // 5
    );
 
    AudioQueueEnqueueBuffer (                        // 6
        aqData.mQueue,                               // 7
        aqData.mBuffers[i],                          // 8
        0,                                           // 9
        NULL                                         // 10
    );
}

2.14. 录制音频

aqData.mCurrentPacket = 0;                           // 1
aqData.mIsRunning = true;                            // 2
 
AudioQueueStart (                                    // 3
    aqData.mQueue,                                   // 4
    NULL                                             // 5
);
// Wait, on user interface thread, until user stops the recording
AudioQueueStop (                                     // 6
    aqData.mQueue,                                   // 7
    true                                             // 8
);
 
aqData.mIsRunning = false;                           // 9

2.15. 录制完成清理内存

录制完成后,回收音频队列数据,关闭音频文件.

AudioQueueDispose (                                 // 1
    aqData.mQueue,                                  // 2
    true                                            // 3
);
 
AudioFileClose (aqData.mAudioFile);                 // 4

3. 播放

使用 Audio Queue Services播放音频时,源数据可以是本地文件, 内存中的对象或者其他音频存储方式.本章中仅介绍通过本地文件播放.

3.1. 定义一个结构体管理音频状态

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
};

此结构体中的数据基本与录制时相同.

3.2.回调函数

作用

3.2.1. 定义回调函数
static void HandleOutputBuffer (
    void                 *aqData,                 // 1
    AudioQueueRef        inAQ,                    // 2
    AudioQueueBufferRef  inBuffer                 // 3
)

3.2.2. 读取音频文件
AudioFileReadPackets (                        // 1
    pAqData->mAudioFile,                      // 2
    false,                                    // 3
    &numBytesReadFromFile,                    // 4
    pAqData->mPacketDescs,                    // 5
    pAqData->mCurrentPacket,                  // 6
    &numPackets,                              // 7
    inBuffer->mAudioData                      // 8
);
3.2.3. 入队

读取完音频数据后,执行入队操作.

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

3.2.4. 停止音频队列

如果检查到当前音频文件读取完毕,应该停止音频队列.

if (numPackets == 0) {                          // 1
    AudioQueueStop (                            // 2
        pAqData->mQueue,                        // 3
        false                                   // 4
    );
    pAqData->mIsRunning = false;                // 5
}
3.2.5. 完整的回调
static void HandleOutputBuffer (
    void                *aqData,
    AudioQueueRef       inAQ,
    AudioQueueBufferRef inBuffer
) {
    AQPlayerState *pAqData = (AQPlayerState *) aqData;        // 1
    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; 
    }
}

3.3. 计算音频队列数据

我们需要指定一个音频队列buffer的大小.根据计算出来的大小为音频队列数据分配内存.

void DeriveBufferSize (
    AudioStreamBasicDescription &ASBDesc,                            // 1
    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
}

3.4. 打开音频文件

3.4.1. 获取一个CFURL对象表示音频文件路径
CFURLRef audioFileURL =
    CFURLCreateFromFileSystemRepresentation (           // 1
        NULL,                                           // 2
        (const UInt8 *) filePath,                       // 3
        strlen (filePath),                              // 4
        false                                           // 5
    );
3.4.2. 打开音频文件
AQPlayerState aqData;                                   // 1
 
OSStatus result =
    AudioFileOpenURL (                                  // 2
        audioFileURL,                                   // 3
        fsRdPerm,                                       // 4
        0,                                              // 5
        &aqData.mAudioFile                              // 6
    );
 
CFRelease (audioFileURL);                               // 7
3.4.3. 获取文件格式
UInt32 dataFormatSize = sizeof (aqData.mDataFormat);    // 1
 
AudioFileGetProperty (                                  // 2
    aqData.mAudioFile,                                  // 3
    kAudioFilePropertyDataFormat,                       // 4
    &dataFormatSize,                                    // 5
    &aqData.mDataFormat                                 // 6
);

3.5. 创建播放音频队列

AudioQueueNewOutput (                                // 1
    &aqData.mDataFormat,                             // 2
    HandleOutputBuffer,                              // 3
    &aqData,                                         // 4
    CFRunLoopGetCurrent (),                          // 5
    kCFRunLoopCommonModes,                           // 6
    0,                                               // 7
    &aqData.mQueue                                   // 8
);

3.6. 设置播放音频队列大小

3.6.1. 设置buffer size与读取的音频数据包数量
UInt32 maxPacketSize;
UInt32 propertySize = sizeof (maxPacketSize);
AudioFileGetProperty (                               // 1
    aqData.mAudioFile,                               // 2
    kAudioFilePropertyPacketSizeUpperBound,          // 3
    &propertySize,                                   // 4
    &maxPacketSize                                   // 5
);
 
DeriveBufferSize (                                   // 6
    aqData.mDataFormat,                              // 7
    maxPacketSize,                                   // 8
    0.5,                                             // 9
    &aqData.bufferByteSize,                          // 10
    &aqData.mNumPacketsToRead                        // 11
);
3.6.2. 为数据包描述数组分配内存
bool isFormatVBR = (                                       // 1
    aqData.mDataFormat.mBytesPerPacket == 0 ||
    aqData.mDataFormat.mFramesPerPacket == 0
);
 
if (isFormatVBR) {                                         // 2
    aqData.mPacketDescs =
      (AudioStreamPacketDescription*) malloc (
        aqData.mNumPacketsToRead * sizeof (AudioStreamPacketDescription)
      );
} else {                                                   // 3
    aqData.mPacketDescs = NULL;
}

3.7. 设置magic cookie

对于压缩的音频数据格式(AAC...),我们在播放前必须为音频队列设置magic cookies,即元数据信息.

UInt32 cookieSize = sizeof (UInt32);                   // 1
bool couldNotGetProperty =                             // 2
    AudioFileGetPropertyInfo (                         // 3
        aqData.mAudioFile,                             // 4
        kAudioFilePropertyMagicCookieData,             // 5
        &cookieSize,                                   // 6
        NULL                                           // 7
    );
 
if (!couldNotGetProperty && cookieSize) {              // 8
    char* magicCookie =
        (char *) malloc (cookieSize);
 
    AudioFileGetProperty (                             // 9
        aqData.mAudioFile,                             // 10
        kAudioFilePropertyMagicCookieData,             // 11
        &cookieSize,                                   // 12
        magicCookie                                    // 13
    );
 
    AudioQueueSetProperty (                            // 14
        aqData.mQueue,                                 // 15
        kAudioQueueProperty_MagicCookie,               // 16
        magicCookie,                                   // 17
        cookieSize                                     // 18
    );
 
    free (magicCookie);                                // 19
}

3.8. 分配音频队列数据

aqData.mCurrentPacket = 0;                                // 1
 
for (int i = 0; i < kNumberBuffers; ++i) {                // 2
    AudioQueueAllocateBuffer (                            // 3
        aqData.mQueue,                                    // 4
        aqData.bufferByteSize,                            // 5
        &aqData.mBuffers[i]                               // 6
    );
 
    HandleOutputBuffer (                                  // 7
        &aqData,                                          // 8
        aqData.mQueue,                                    // 9
        aqData.mBuffers[i]                                // 10
    );
}

3.9. 设置音量

开始播放前,可以设置音量(0~1)

Float32 gain = 1.0;                                       // 1
    // Optionally, allow user to override gain setting here
AudioQueueSetParameter (                                  // 2
    aqData.mQueue,                                        // 3
    kAudioQueueParam_Volume,                              // 4
    gain                                                  // 5
);

3.10. 启动Audio Queue

aqData.mIsRunning = true;                          // 1
 
AudioQueueStart (                                  // 2
    aqData.mQueue,                                 // 3
    NULL                                           // 4
);
 
do {                                               // 5
    CFRunLoopRunInMode (                           // 6
        kCFRunLoopDefaultMode,                     // 7
        0.25,                                      // 8
        false                                      // 9
    );
} while (aqData.mIsRunning);
 
CFRunLoopRunInMode (                               // 10
    kCFRunLoopDefaultMode,
    1,
    false
);

3.11. 清理

播放完成后应该回收音频队列,关闭音频文件,释放所有相关资源

AudioQueueDispose (                            // 1
    aqData.mQueue,                             // 2
    true                                       // 3
);
 
AudioFileClose (aqData.mAudioFile);            // 4
 
free (aqData.mPacketDescs);                    // 5

Apple官方文档

上一篇 下一篇

猜你喜欢

热点阅读