AVFoundation 拍片专用

AudioToolbox_如何录制PCM格式的数据

2016-08-29  本文已影响1226人  酷走天涯
让学习成为一种习惯 先来认识一下头文件
AudioConverter.h: 音频转换接口。定义用于创建和使用音频转换器的接口
AudioFile.h: 定义一个用于读取和写入文件中的音频数据的接口。
AudioFileStream.h: 定义了一个用于解析音频文件流的接口。
AudioFormat.h: 定义用于分配和读取音频文件中的音频格式元数据的接口。
AudioQueue.h: 定义播放和录制音频的接口。
AudioServices.h: 定义三个接口。系统健全的服务让你播放简短的声音和警报。音频硬件服务提供了一个轻量级的接口,用于与音频硬件交互。音频会议服务,让iPhone和iPod触摸应用管理音频会议。
AudioToolbox.h: 顶层包括音频工具箱框架的文件。
AuGraph.h:定义用于创建和使用音频处理图形界面。
ExtendedAudioFile.h: 定义用于将音频数据从文件直接转化为线性PCM接口,反之亦然。

接下来我们一个个头文件包含的函数都能干神马,加油!

以上几个头文件包含的函数的基本作用我们已经了解了.


接下来,我们录制一段声音试试!

音频数据采样这一步,比较繁琐,我们详细讲解一下。
录音当然在 AudioQueue.h找方法了,我找到下面的方法

extern OSStatus             
AudioQueueNewInput(             const       AudioStreamBasicDescription *inFormat,
                                AudioQueueInputCallback         inCallbackProc,
                                void * __nullable               inUserData,
                                CFRunLoopRef __nullable         inCallbackRunLoop,
                                CFStringRef __nullable          inCallbackRunLoopMode,
                                UInt32                          inFlags,
                                AudioQueueRef __nullable * __nonnull outAQ)

1.函数的作用: 创建一个新的用于记录音频数据的音频队列
2.这个函数的参数描述?

参数说明:
inFormat: 描述了被记录的音频格式(对于线性PCM,只支持交错格式和压缩格式)
inCallbackPro: 队列缓冲区被填满时,被调用的回调函数的指针。
inUserData:用户想要传给回调函数的值或者指针.
inCallBackRunLoop:循环队列,如果你只能为空,它将运行在内置的线程
inCallBackRunLoopMode: (kCFRunLoopCommonModes) 或者NULL (NULL 也是kCFRunLoopCommonModes),可以选择创建自己的线程和自己的运行循环.
inFlags:保留 ,填0
outAQ:在返回时,这个变量包含指向新创建的记录音频队列的指针.

这个参数看起来也挺陌生的哈,没关系,我们一个个看,有的是时间和耐心!
参数1: 在CoreAudio 框架下的CoreAudioTypes.h 文件中

 struct AudioStreamBasicDescription
{
Float64             mSampleRate;
AudioFormatID       mFormatID;
AudioFormatFlags    mFormatFlags;
UInt32              mBytesPerPacket;
UInt32              mFramesPerPacket;
UInt32              mBytesPerFrame;
UInt32              mChannelsPerFrame;
UInt32              mBitsPerChannel;
UInt32              mReserved;
};
typedef struct AudioStreamBasicDescription      AudioStreamBasicDescription;

首先,它是一个结构体,包含了音频数据流的所有基本属性,参数老多了!要有耐心哈!
参数说明:
mSampleRate: 数据流中每秒钟的样本帧的数量
mFormatID: 指示流中的数据格式
mFormatFlags: 格式标识
mBytesPerPacket: 每个包数据的字节数量
mFramesPerPacket:每个包数据的样本帧的数量
mBytesPerFrame: 单帧包含的字节数据
mChannelsPerFrame:每一帧数据包含的通道数
mBitsPerChannel: 每一帧数据的每一个通道的采样位的数量
mReserved: 让其8字节对齐.

参数2: 在AudioQueue.h文件中

typedef void (*AudioQueueInputCallback)(
                                void * __nullable               inUserData,
                                AudioQueueRef                   inAQ,
                                AudioQueueBufferRef             inBuffer,
                                const AudioTimeStamp *          inStartTime,
                                UInt32                          inNumberPacketDescriptions,
                                const AudioStreamPacketDescription * __nullable inPacketDescs);

描述:定义一个回调函数的指针,当录音队列填满一个缓冲区是回调。当你将buffer数据写入文件时,你应该重新把音频缓冲区重新入队去接受更多数据。
参数说明:
inUserData: 你指定的的数据
inAQ:定义一个表示音频队列的不透明数据类型
inBuffer: 音频队列缓冲区,包含新的音频数据
inStartTime:指向对应于第一个样本的音频时间戳结构的指针.又是一个结构体,一会接说.
inNumberPacketDescriptions: 回调函数包含的音频包的数量
inPacketDescs:结构描述了数据包布局的一个缓冲区的数据大小
每个包可能不是相同的或有外部数据之间的
小包,一会接着说。

接下来分析一下时间结构体:

struct AudioTimeStamp
 {
Float64             mSampleTime;
UInt64              mHostTime;
Float64             mRateScalar;
UInt64              mWordClockTime;
SMPTETime           mSMPTETime;
AudioTimeStampFlags mFlags;
UInt32              mReserved;
 };
 typedef struct AudioTimeStamp   AudioTimeStamp;

作用: 包含不同时间的状态信息
参数说明:
mSampleTime: 完整的样本帧时间
mHostTime: 机器的时间
mRateScaler: 每一帧时间的主滴答
mWordClock:世界时间
mSMPTETime:从一段视频的起始帧到终止帧,其间的每一帧都有一个唯一的时间码地址,记录时间
mFlags:暗示时间是否有效
mReserved:强制八位数据.

继续了解帧缓冲区的结构体:

struct  AudioStreamPacketDescription
{
SInt64  mStartOffset;
UInt32  mVariableFramesInPacket;
UInt32  mDataByteSize;
};
typedef struct AudioStreamPacketDescription     AudioStreamPacketDescription;

参数:
mStartOffset: 从缓冲区开始,到数据包开始的字节数量.
mVariableFramesInPacket:数据包,包含有效的样本帧的数量
mDataByteSize: 每个包的字节数量。

基本的几个函数和结构体,我们了解了,开始我们的第一次练习,获取到音频包数据!

代码走一波

第一步:先定义一个音频流结构体:

AudioStreamBasicDescription mDataFormat;
mDataFormat.mSampleRate = 8000;
mDataFormat.mFormatID = kAudioFormatLinearPCM;
mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsPacked|kLinearPCMFormatFlagIsSignedInteger;
mDataFormat.mFramesPerPacket = 1;
mDataFormat.mChannelsPerFrame = 2;
mDataFormat.mBitsPerChannel =  (sizeof(SInt16) * 8);
mDataFormat.mBytesPerPacket = 2*sizeof(SInt16);
mDataFormat.mBytesPerFrame = 2*sizeof(SInt16);

第二步,定义一个队列

@property(nonatomic,assign)AudioQueueRef recordQueue;

第三步,创建音频输入队列

AudioQueueNewInput(&mDataFormat, AQueueInputCallback, (__bridge void*)(self), NULL, kCFRunLoopCommonModes, 0, &_recordQueue);

第四步,创建音频缓冲区

 // 给队列添加缓冲区
AudioQueueBufferRef buffer[3];
int frameSize = 1000;
for (int i=0;i<3 ;i++)
{
    //请求音频队列对象来分配一个音频队列缓存。
    AudioQueueAllocateBuffer(_recordQueue, frameSize, &buffer[i]);
    //给录音或者回放音频队列的缓存中添加一个缓存数据
    AudioQueueEnqueueBuffer(_recordQueue, buffer[i], 0, NULL);
}

第五步. 定义音频回调函数

static void AQueueInputCallback(
                                   void * __nullable               inUserData,
                                   AudioQueueRef                   inAQ,
                                   AudioQueueBufferRef             inBuffer,
                                   const AudioTimeStamp *          inStartTime,
                                   UInt32                          inNumberPacketDescriptions,
                                   const AudioStreamPacketDescription * __nullable inPacketDescs)
{

// 处理数据 inUserData 传入的是我们的控制器,因为要用到在它内部定义的队列属性
 ViewController * engine = (__bridge ViewController *) inUserData;

 AudioQueueEnqueueBuffer(engine.recordQueue, inBuffer, 0, NULL);
   }

第六步,开始录音

AudioQueueStart(_recordQueue, NULL);

下面是我在回调函数的部分

  NSLog(@"%d",inBuffer->mAudioDataByteSize);
  NSLog(@"%d",inBuffer->mAudioDataBytesCapacity);

日志输出

2016-08-29 21:48:01.334 AudioToolbox_Learn_01[922:32802] 1000
2016-08-29 21:48:01.334 AudioToolbox_Learn_01[922:32802] 1000

总结

使用AudioQueue 录制音频步骤:
1.你要告诉系统,你要录制什么类型的音频文件
2.创建一个音频缓冲区填满时的回调函数
3.设置一个专门负责音频录制的队列
4.创建音频缓冲区,添加到队列中去
5.当音频缓冲区填充满时,把缓冲区的数据处理完后,需要把缓冲区重新添加到队列中去。

上一篇下一篇

猜你喜欢

热点阅读