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.当音频缓冲区填充满时,把缓冲区的数据处理完后,需要把缓冲区重新添加到队列中去。

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

推荐阅读更多精彩内容