几种播放音频文件的方式(七) —— 音频队列服务(Audio Queue Services)之录制音频(四)

版本记录

版本号 时间
V1.0 2017.12.28

前言

ios系统中有很多方式可以播放音频文件,这里我们就详细的说明下播放音乐文件的原理和实例。感兴趣的可以看我写的上面几篇。
1. 几种播放音频文件的方式(一) —— 播放本地音乐
2. 几种播放音频文件的方式(二) —— 音效播放
3. 几种播放音频文件的方式(三) —— 网络音乐播放
4. 几种播放音频文件的方式(四) —— 音频队列服务(Audio Queue Services)(一)
5. 几种播放音频文件的方式(五) —— 音频队列服务(Audio Queue Services)简介(二)
6. 几种播放音频文件的方式(六) —— 音频队列服务(Audio Queue Services)之关于音频队列(三)

Recording Audio - 录制音频

使用音频队列服务进行录制时,存储目标可以是任何事物 —— 磁盘上的文件,网络连接,内存中的对象等等。 本章介绍最常见的情况:基本录制到磁盘文件。

注意:本章描述了一个基于ANSI-C的录制实现,并使用了 Mac OS X Core Audio SDK中的C ++类。 对于基于Objective-C的示例,请参阅iOS Dev CenterSpeakHere示例代码。

要将录制功能添加到应用程序中,通常需要执行以下步骤:

  • 定义一个自定义结构来管理状态,格式和路径信息。
  • 编写一个音频队列callback回调函数来执行实际录制。
  • 可以编写代码来确定音频队列缓冲区的大小。 如果您将以使用cookie的格式进行录制,编写代码与magic cookies一起配合。
  • 填写自定义结构的字段。 这包括指定音频队列发送到正在记录的文件的数据流,以及该文件的路径。
  • 创建一个录制音频队列,并要求它创建一组音频队列缓冲区。 还要创建一个文件来记录。
  • 告诉音频队列开始录制。
  • 完成后,告诉音频队列停止并销毁。 音频队列销毁其缓冲区。

本章的其余部分将详细介绍这些步骤。


Define a Custom Structure to Manage State - 定义一个自定义结构来管理状态

使用音频队列服务开发录音解决方案的第一步是定义一个自定义结构。 您将使用此结构来管理音频格式和音频队列状态信息。 Listing 2-1说明了这样一个结构:

// Listing 2-1  A custom structure for a recording audio queue

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

以下是这个结构中的字段的描述:

    1. 设置要使用的音频队列缓冲区的数量。
    1. 表示要写入磁盘的音频数据格式的AudioStreamBasicDescription结构(来自CoreAudioTypes.h)。该格式被mQueue字段中指定的音频队列使用。
      mDataFormat字段最初由程序中的代码填充,如 Set Up an Audio Format for Recording中所述。最好通过查询音频队列的kAudioQueueProperty_StreamDescription属性来更新此字段的值,如Getting the Full Audio Format from an Audio Queue中所述。在Mac OS X v10.5上,改用kAudioConverterCurrentInputStreamDescription属性。有关AudioStreamBasicDescription结构的详细信息,请参阅Core Audio Data Types Reference
    1. 由您的应用程序创建录制音频队列。
    1. 一个数组,指向由音频队列管理的音频队列缓冲区的指针。
    1. 音频文件对象,表示您的程序将音频数据记录到其中的文件。
      每个音频队列缓冲区的大小(以字节为单位)。在创建音频队列之后并在启动之前,在DeriveBufferSize函数的这些示例中计算此值。请参阅Write a Function to Derive Recording Audio Queue Buffer Size
    1. 要从当前音频队列缓冲区写入的第一个数据包的数据包索引。
    1. 指示音频队列是否正在运行的布尔值。

Write a Recording Audio Queue Callback - 写一个录制音频队列回调函数

接下来,写一个录音音频队列回调函数。 这个回调做了两件事:

  • 将新填充的音频队列缓冲区的内容写入到正在录制的音频文件中
  • 将音频队列缓冲区(其内容刚刚写入磁盘)排入缓冲区队列

本节展示一个示例回调声明,然后分别描述这两个任务,最后展示一个完整的录制回调。 有关录制音频队列回调角色的说明,请参阅Figure 1-3

1. The Recording Audio Queue Callback Declaration - 录制音频队列回调函数声明

Listing 2-2显示了一个记录音频队列回调函数的示例声明,声明为AudioQueue.h头文件中的AudioQueueInputCallback:

// Listing 2-2  The recording audio queue callback declaration

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

以下是这段代码的工作原理:

    1. 通常,aqData是包含音频队列的状态数据的自定义结构,如 Define a Custom Structure to Manage State所述。
    1. 拥有此回调的音频队列。
    1. 包含要录制的传入音频数据的音频队列缓冲区。
    1. 音频队列缓冲区中第一个采样的采样时间(简单记录不需要)。
    1. inPacketDesc参数中的数据包描述数量。 值为0表示CBR数据。
    1. 对于需要数据包描述的压缩音频数据格式,编码器为缓冲区中数据包生成的数据包描述。

2. Writing an Audio Queue Buffer to Disk - 将音频队列缓冲区写入磁盘

录音的音频队列回调的第一项任务是将音频队列缓冲区写入磁盘。 这个缓冲区是回调的音频队列刚刚完成填充来自输入设备的新的音频数据的那个缓冲区。 回调使用AudioFile.h头文件中的AudioFileWritePackets函数,如Listing 2-3所示。

// Listing 2-3  Writing an audio queue buffer to disk

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

以下是这段代码的工作原理:

    1. AudioFile.h头文件中声明的AudioFileWritePackets函数将缓冲区的内容写入音频数据文件。
    1. 音频文件对象(类型AudioFileID),表示要写入的音频文件。 pAqData变量是一个指向Listing 2-1中描述的数据结构的指针。
    1. 使用false值表示函数在写入时不应该缓存数据。
    1. 正在写入的音频数据的字节数。 inBuffer变量表示由音频队列交给回调的音频队列缓冲区。
    1. 音频数据的数据包描述数组。 值为NULL表示不需要数据包描述(例如用于CBR音频数据)。
    1. 要写入的第一个数据包的数据包索引。
    1. 在输入时,要写入的数据包的数量。 输出时,实际写入的数据包数量。
    1. 将新的音频数据写入音频文件。

3. Enqueuing an Audio Queue Buffer - 将一个音频队列缓冲入队

现在来自音频队列缓冲区的音频数据已被写入音频文件,回调将缓冲区排入队列,如Listing 2-4所示。 一旦回到缓冲区队列中,缓冲区就准备好接受更多的输入音频数据。

// Listing 2-4  Enqueuing an audio queue buffer after writing to disk

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

以下是这段代码的工作原理:

    1. AudioQueueEnqueueBuffer函数将音频队列缓冲区添加到音频队列的缓冲区队列中。
    1. 将音频队列添加到指定的音频队列缓冲区。 pAqData变量是一个指向Listing 2-1中描述的数据结构的指针。
    1. 要排队的音频队列缓冲区。
    1. 音频队列缓冲区数据中的数据包描述数量。 设置为0,因为此参数未用于记录。
    1. 描述音频队列缓冲区数据的数据包描述数组。 设置为NULL,因为此参数未用于记录。

4. A Full Recording Audio Queue Callback - 一个完整的录制音频队列回调

Listing 2-5显示了完整记录音频队列回调的基本版本。 与本文档中的其他代码示例一样,此列表不包括错误处理。

// Listing 2-5  A recording audio queue callback function

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

以下是这段代码的工作原理:

    1. 实例化时提供给音频队列对象的自定义结构,包括表示要录制的音频文件的音频文件对象以及各种状态数据。请参阅Define a Custom Structure to Manage State
    1. 如果音频队列缓冲区包含CBR数据,则计算缓冲区中的数据包数量。此数字等于缓冲区中的数据总字节数除以每个数据包的(恒定)字节数。对于VBR数据,音频队列在调用回调时提供缓冲区中的数据包数量。
    1. 将缓冲区的内容写入音频数据文件。有关详细说明,请参阅Writing an Audio Queue Buffer to Disk
    1. 如果成功写入音频数据,则增加音频数据文件的包索引以准备好写入下一个缓冲器的音频数据。
    1. 如果音频队列已经停止,则返回。
    1. 将刚刚写入内容的音频队列缓冲区重新入队。有关详细说明,请参阅Enqueuing an Audio Queue Buffer

Write a Function to Derive Recording Audio Queue Buffer Size - 编写一个函数来获取记录音频队列缓冲区大小

音频队列服务期望您的应用程序指定您使用的音频队列缓冲区的大小。 Listing 2-6显示了一个这样做的方法。 它产生足够大的缓冲区大小来保存给定持续时间的音频数据。

这里的计算考虑了您正在记录的音频数据格式。 格式包括可能影响缓冲区大小的所有因素,例如音频通道的数量。

// Listing 2-6  Deriving a recording audio queue buffer size

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
}

以下是这段代码的工作原理:

    1. 音频队列拥有您要指定大小的缓冲区。
    1. 音频队列的AudioStreamBasicDescription结构。
    1. 按照音频的秒数指定每个音频队列缓冲区的大小。
    1. 输出时,按字节计算每个音频队列缓冲区的大小。
    1. 音频队列缓冲区大小的上限(以字节为单位)。在这个例子中,上限设置为320 KB。这对应于大约五秒钟的立体声,采样率为96kHz的24位音频。
    1. 对于CBR音频数据,从AudioStreamBasicDescription结构获取(恒定)数据包大小。使用此值作为最大数据包大小。该做法具有确定要记录的音频数据是CBR还是VBR的副作用。如果是VBR,则音频队列的AudioStreamBasicDescription结构将每个数据包的字节数值列为0。
    1. 对于VBR音频数据,查询音频队列以获取估计的最大数据包大小。
    1. 以字节为单位获取缓冲区大小。
    1. 将缓冲区大小(如果需要)限制到先前设置的上限。

Set a Magic Cookie for an Audio File - 为声音文件设置一个Magic Cookie

某些压缩音频格式(如MPEG 4 AAC)利用包含音频元数据的结构。 这些结构被称为Magic Cookie。 当您使用音频队列服务录制这种格式时,您必须从音频队列中获取Magic Cookie并将其添加到音频文件,然后再开始录制。

Listing 2-7显示了如何从音频队列中获取Magic Cookie并将其应用于音频文件。 在录制之前,您的代码会调用这样的函数,然后在录制之后再次调用一些函数 —— 一些编解码器在录制停止时更新Magic Cookie数据。

// Listing 2-7  Setting a magic cookie for an audio file

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
}

以下是这段代码的工作原理:

    1. 您用于录制的音频队列。
    1. 您正在录制写入的音频文件。
    1. 指示此函数成功或失败的结果变量。
    1. 一个变量来保存magic cooki的数据大小。
    1. 从音频队列中获取magic cooki的数据大小,并将其存储在cookieSize变量中。
    1. 分配一个字节数组来保存魔法cookie信息。
    1. 通过查询音频队列的kAudioQueueProperty_MagicCookie属性获取magic cooki。
    1. 为正在录制写入的音频文件设置magic cookieAudioFileSetProperty函数在AudioFile.h头文件中声明。
    1. 释放临时cookie变量的内存。
    1. 返回此函数的成功或失败。

Set Up an Audio Format for Recording - 设置录制的音频格式

本节介绍如何为音频队列设置音频数据格式。 音频队列使用这种格式来记录到一个文件。

要设置音频数据格式,请指定:

  • 音频数据格式类型(如线性PCM,AAC等)
  • 采样率(如44.1 kHz)
  • 音频通道数量(如2,立体声)
  • 比特深度(如16比特)
  • 每个数据包的帧数(线性PCM,例如,每个数据包使用一个帧)
  • 音频文件类型(如CAF,AIFF等)
  • 文件类型所需的音频数据格式的详细信息

Listing 2-8说明了如何为每个属性设置一个固定的选项来设置录制的音频格式。 在产品代码中,通常允许用户指定音频格式的一些或全部方面。 无论采取哪种方法,目标都是填充 AQRecorderState自定义结构的mDataFormat字段,如 Define a Custom Structure to Manage State中所述。

Listing 2-8  Specifying an audio queue’s audio data format

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;

以下是这段代码的工作原理:

    1. 创建AQRecorderState自定义结构的一个实例。结构的mDataFormat字段包含一个AudioStreamBasicDescription结构。在mDataFormat字段中设置的值为音频队列提供了音频格式的初始定义,这也是您录制文件的音频格式。在Listing 2-10中,您可以获得更完整的音频格式规范,Core Audio根据格式类型和文件类型为您提供了音频格式。
    1. 将音频数据格式类型定义为线性PCM。请参阅Core Audio Data Types Reference以获取可用数据格式的完整列表。
    1. 定义采样率为44.1 kHz。
    1. 将通道数量定义为2。
    1. 将每个通道的位深度定义为16。
    1. 将每个数据包的字节数和每个字节的字节数定义为4(即每个采样2个通道乘以2个字节)。
    1. 将每个数据包的帧数定义为1。
    1. 将文件类型定义为AIFF。请参阅AudioFile.h头文件中的音频文件类型枚举以获取可用文件类型的完整列表。您可以指定已安装编解码器的任何文件类型,如Using Codecs and Audio Data Formats中所述。
    1. 设置指定文件类型所需的格式标志。

Create a Recording Audio Queue - 创建一个录制音频队列

现在,通过设置录制回调和音频数据格式,您可以创建并配置一个音频队列进行录制。

1. Creating a Recording Audio Queue - 创建一个录制音频队列

Listing 2-9演示了如何创建一个记录音频队列。 请注意,AudioQueueNewInput函数使用前面步骤中配置的回调,自定义结构和音频数据格式

// Listing 2-9  Creating a recording audio queue

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

以下是这段代码的工作原理:

    1. AudioQueueNewInput函数创建一个新的录音音频队列。
    1. 用于录制的音频数据格式。 请参阅Set Up an Audio Format for Recording
    1. 用于录音音频队列的回调函数。 请参阅Write a Recording Audio Queue Callback
    1. 记录音频队列的自定义数据结构。 请参阅Define a Custom Structure to Manage State
    1. 将在其上调用回调的运行循环。 使用NULL来指定默认行为,在该行为中将在音频队列内部的线程上调用回调。 这是典型的用法 - 它允许音频队列在您的应用程序的用户界面线程等待用户输入停止录制时进行录制。
    1. 可以调用回调的运行循环模式。 通常,在这里使用kCFRunLoopCommonModes常量。
    1. 保留。 必须为0。
    1. 输出时,新分配录音音频队列。

2. Getting the Full Audio Format from an Audio Queue - 从音频队列中获取完整的音频格式

当音频队列存在时(请参阅Creating a Recording Audio Queue),它可能已经比您更完整地填充了AudioStreamBasicDescription结构,特别是对于压缩格式。 要获得完整的格式描述,请调用AudioQueueGetProperty函数,如代码Listing 2-10所示。 创建要录制的音频文件时,请使用完整的音频格式(请参阅Create an Audio File)。

Listing 2-10  Getting the audio format from an 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
);

以下是这段代码的工作原理:

    1. 获取预期的属性值大小,以便在查询有关其音频数据格式的音频队列时使用。
    1. AudioQueueGetProperty函数获取音频队列中指定属性的值。
    1. 音频队列从中获取音频数据格式。
    1. 用于获取音频队列数据格式值的属性ID。
    1. 在输出时,从音频队列中获得完整的音频数据格式,以AudioStreamBasicDescription结构的形式。
    1. 在输入时,AudioStreamBasicDescription结构的预期大小。 在输出上,实际的大小。 您的录制应用程序不需要使用此值。

Create an Audio File - 创建一个音频文件

通过创建和配置音频队列,您可以创建将录制音频数据的音频文件,如Listing 2-11所示。 音频文件使用先前存储在音频队列的自定义结构中的数据格式和文件格式规范。

// Listing 2-11  Creating an audio file for recording

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

以下是这段代码的工作原理:

    1. CFURL.h头文件中声明的CFURLCreateFromFileSystemRepresentation函数创建一个表示要记录的文件的CFURL对象。
    1. 使用NULL(或kCFAllocatorDefault)来使用当前的默认内存分配器。
    1. 您要转换为CFURL对象的文件系统路径。在生产代码中,通常会从用户获取filePath的值。
    1. 文件系统路径中的字节数。
    1. 值为false表示代表文件的filePath,而不是目录directory
    1. AudioFile.h头文件中的AudioFileCreateWithURL函数创建一个新的音频文件或初始化现有的文件。
    1. 用于创建新音频文件或者在现有文件的情况下进行初始化的URL,该URL是从步骤1中的CFURLCreateFromFileSystemRepresentation派生的。
    1. 新文件的文件类型。在本章的示例代码中,这是以前通过kAudioFileAIFFType文件类型常量设置为AIFF的。请参阅 Set Up an Audio Format for Recording
    1. 将被记录到文件中的音频的数据格式,指定为AudioStreamBasicDescription结构。在本章的示例代码中,这也是在 Set Up an Audio Format for Recording中设置的。
    1. 在文件已经存在的情况下擦除文件。
    1. 在输出上,表示要录制的音频文件的音频文件对象(类型AudioFileID)。

Set an Audio Queue Buffer Size - 设置音频队列的缓冲大小

在准备录制时使用的一组音频队列缓冲区之前,请使用您之前编写的DeriveBufferSize函数(请参阅 Write a Function to Derive Recording Audio Queue Buffer Size)。 您将此大小分配给正在使用的录音音频队列。 Listing 2-12说明了这一点。

// Listing 2-12  Setting an audio queue buffer size

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

以下是这段代码的工作原理:

    1. Write a Function to Derive Recording Audio Queue Buffer Size中描述的DeriveBufferSize函数设置适当的音频队列缓冲区大小。
    1. 您正在设置缓冲区大小的音频队列。
    1. 您正在录制的文件的音频数据格式。 请参阅Set Up an Audio Format for Recording
    1. 每个音频队列缓冲区应该容纳的音频的秒数。 半秒钟,如这里设置,通常是一个不错的选择。
    1. 输出时,每个音频队列缓冲区的大小(以字节为单位)。 该值放置在音频队列的自定义结构中。

Prepare a Set of Audio Queue Buffers - 准备一组音频队列缓冲

现在您可以询问您创建的音频队列(在Create a Recording Audio Queue中)以准备一组音频队列缓冲区。 Listing 2-13演示了如何做到这一点。

Listing 2-13  Preparing a set of audio queue buffers

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

以下是这段代码的工作原理:

    1. 迭代分配和排队每个音频队列缓冲区。
    1. AudioQueueAllocateBuffer函数为音频队列分配一个音频队列缓冲区。
    1. 执行分配并拥有缓冲区的音频队列。
    1. 新分配的音频队列缓冲区的大小(以字节为单位)。 请参阅Write a Function to Derive Recording Audio Queue Buffer Size
    1. 输出时,新分配的音频队列缓冲区。 指向缓冲区的指针位于您正在使用音频队列的自定义结构中。
    1. AudioQueueEnqueueBuffer函数将音频队列缓冲区添加到缓冲区队列的末尾。
    1. 将缓冲添加到缓冲队列的音频队列。
    1. 您排队的音频队列缓冲区。
    1. 入队用于记录的缓冲区时,此参数未使用。
    1. 入队用于记录的缓冲区时,此参数未使用。

Record Audio - 录制音频

所有前面的代码都导致了非常简单的记录过程,如Listing 2-14所示。

// Listing 2-14  Recording audio

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

以下是这段代码的工作原理:

    1. 将数据包索引初始化为0,开始在音频文件的开头进行录制。
    1. 在自定义结构中设置一个标志来指示音频队列正在运行。 记录音频队列回调callback使用此标志。
    1. AudioQueueStart函数在其自己的线程上启动音频队列。
    1. 音频队列开始。
    1. 使用NULL来指示音频队列应该立即开始记录。
    1. AudioQueueStop函数停止并重置录音音频队列。
    1. 音频队列停止。
    1. 使用true来使用同步停止。 有关同步和异步停止的说明,请参阅Audio Queue Control and State
    1. 在自定义结构中设置一个标志来指示音频队列没有运行。

Clean Up After Recording - 录制后的清除工作

当您完成录制时,请销毁音频队列并关闭音频文件。 Listing 2-15说明了这些步骤。

// Listing 2-15  Cleaning up after recording

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

以下是这段代码的工作原理:

    1. AudioQueueDispose函数销毁音频队列及其所有资源,包括其缓冲区。
    1. 您想要销毁的音频队列。
    1. 使用true来同步销毁音频队列(即立即)。
    1. 关闭用于录制的音频文件。 AudioFileClose函数在AudioFile.h头文件中声明。

后记

未完,待续~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容