AudioToolbox_如何录制PCM格式的数据
AudioConverter.h: 音频转换接口。定义用于创建和使用音频转换器的接口
AudioFile.h: 定义一个用于读取和写入文件中的音频数据的接口。
AudioFileStream.h: 定义了一个用于解析音频文件流的接口。
AudioFormat.h: 定义用于分配和读取音频文件中的音频格式元数据的接口。
AudioQueue.h: 定义播放和录制音频的接口。
AudioServices.h: 定义三个接口。系统健全的服务让你播放简短的声音和警报。音频硬件服务提供了一个轻量级的接口,用于与音频硬件交互。音频会议服务,让iPhone和iPod触摸应用管理音频会议。
AudioToolbox.h: 顶层包括音频工具箱框架的文件。
AuGraph.h:定义用于创建和使用音频处理图形界面。
ExtendedAudioFile.h: 定义用于将音频数据从文件直接转化为线性PCM接口,反之亦然。
接下来我们一个个头文件包含的函数都能干神马,加油!
-
AudioConverter.h
作用: 转换各种线性PCM和压缩之间。
支持的转换:
1.PCM浮点数/整数/比特深度转换
2.PCM采样率转换
3.PCM交织和去交织
4.编码PCM压缩格式
5.PCM解码压缩格式
注意:一个audioconverter可以执行一个以上的
上述变换 -
AudioFile.h
作用: 在文件系统或内存中读取和写入音频文件 -
AudioFileStream.h
作用:简单的将流式音频文件解析成数据包的音频文件
分析:每隔一段时间,系统会把有限数量的音频数据放到一块内存地址中去,这样能够保证随机获取的音频文件都是被分割好的!我们总想让系统支持读取不包含EOF的相邻的音频数据,这样就使得解析非常的简单。但是,在流的情况下,这种假设是不成立的,解析器的请求,可能只有部分被满足,任何满足的请求,都必须被记住和审查,否则将会永远的丢失这部分数据,解析器必须能够停止和恢复解析。
客户端调用解析器使用 AudioFileStreamParseBytes 解析器回调客户端属性或者包 使用 AudioFileStream_PropertyListenerProc 和
AudioFileStream_PacketsProc -
AudioFormat.h
作用:定义用于分配和读取音频文件中的音频格式元数据的接口 -
AudioQueue.h
作用:来记录和播放音频缓冲区
队列执行以下任务:
-连接到音频硬件
-管理音频数据缓冲区
-使用编解码器,是必要的,压缩的音频格式
-介导播放或录音
音频队列可以记录和线性PCM音频播放,在压缩格式(如苹果
无损,AAC,MP3),和其他格式的用户所安装的解码器。API集
包括高层次的硬件记录和播放设备的使用支持,并让你
使用先进的编解码器,它们是如何工作的知识。
额外的高级功能支持预定的多个音频的播放和同步
带视频的音频的队列和同步。
名词解释:
PCM -
AudioServices.h
audioservices提供了一种手段来播放音频比如UI音效。
音频硬件服务(AHS)提供查询和操作的应用程序的方法
在不产生加载全部音频 HAL的开销的情况下,音频硬件设备的方面
AHS提供存取所有的audioobjects及其性能对系统。然而,
访问仅限于那些不直接影响输入输出的属性。例如,你可以
查询设备的格式,但您不能查询其输入输出缓冲区大小。因此,AHS API直接采用在HAL的API的各种结构和常数,与警告,
在AHS AudioObjectIds不能用于HAL.
名词解释:
HAL -
AUGraph.h
作用:管理图AudioUnits。
描述:
一个AUGraph是音频信号处理网络的完整描述。AUGraph API的维一套AudioUnits,其输入和输出之间的音频连接,任何回调用于提供输入。它也允许子图嵌入到父图中,组成一个完成合法的完成的数据信号链。AudioUnits做实际的音频处理。为了在在图AudioUnits获取所有的完整信息,AUGraph可以是内省的,各个节点(AUNode)代表的AUGraph AudioUnits或子图可以添加或删除,并修改它们之间的相互作用。一个AUGraph的状态可以在渲染线程和其他线程操作。因此,影响图的状态的任何活动都是用锁和一个消息模型之间的任何调用线程和线程的AUGraph输出单元被调用(渲染线程)。一个AUGraph将有单头节点-什么是以下简称输出单元。这个输出单元用于启动和停止图形的绘制操作,并作为在运行图的状态时的安全操作的调度点。 -
ExtendedAudioFile.h
作用: 用以支持在编码的音频格式中读取和写入文件
讨论:它提供高级音频文件访问,在顶部的AudioFile和audioconverter API集。它提供了一个单一的阅读和写作的编码与未编码的文件统一接口。
以上几个头文件包含的函数的基本作用我们已经了解了.
接下来,我们录制一段声音试试!
音频数据采样这一步,比较繁琐,我们详细讲解一下。
录音当然在 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.这个函数的参数描述?
- 创建输入队列
- 分配缓冲区
- 队列缓冲区(audioQueueEnqueueBuffer,没有参数,没有包的描述)
- 回调接收缓冲器和将他们重新加入队列
参数说明:
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.当音频缓冲区填充满时,把缓冲区的数据处理完后,需要把缓冲区重新添加到队列中去。