让学习成为一种习惯
先来认识一下头文件 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.当音频缓冲区填充满时,把缓冲区的数据处理完后,需要把缓冲区重新添加到队列中去。