AudioQueue的工作模式
在使用AudioQueue之前首先必须理解其工作模式,它之所以这么命名是因为在其内部有一套缓冲队列(Buffer Queue)的机制。在AudioQueue启动之后需要通过AudioQueueAllocateBuffer生成若干个AudioQueueBufferRef结构,这些Buffer将用来存储即将要播放的音频数据,并且这些Buffer是受生成他们的AudioQueue实例管理的,内存空间也已经被分配(按照Allocate方法的参数),当AudioQueue被Dispose时这些Buffer也会随之被销毁。
当有音频数据需要被播放时首先需要被memcpy到AudioQueueBufferRef的mAudioData中(mAudioData所指向的内存已经被分配,之前AudioQueueAllocateBuffer所做的工作),并给mAudioDataByteSize字段赋值传入的数据大小。完成之后需要调用AudioQueueEnqueueBuffer把存有音频数据的Buffer插入到AudioQueue内置的Buffer队列中。在Buffer队列中有buffer存在的情况下调用AudioQueueStart,此时AudioQueue就回按照Enqueue顺序逐个使用Buffer队列中的buffer进行播放,每当一个Buffer使用完毕之后就会从Buffer队列中被移除并且在使用者指定的RunLoop上触发一个回调来告诉使用者,某个AudioQueueBufferRef对象已经使用完成,你可以继续重用这个对象来存储后面的音频数据。如此循环往复音频数据就会被逐个播放直到结束。
这里封装了一个c++文件类,对外提供两个接口
bool startCollect(); //开启录制
void stopCollect(); //停止录制
对内的方法和成员
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers; //装载采集到的音频数据
AudioStreamBasicDescription mRecordFormat; //音频数据输入格式结构体(下面有对应的介绍)
bool m_bAudioPlayFlag;
PGThread m_SendThread;
NSMutableArray *m_inputArray;
void SetupAudioFormat(UInt32 inFormatID); //配置音频数据输出格式参数
static void MyInputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumPackets, const AudioStreamPacketDescription* inPacketDesc); //采集到音频数据的回到方法
structAudioStreamBasicDescription{
Float64 mSampleRate;// 采样率 :Hz
AudioFormatID mFormatID;// 采样数据的类型,PCM,AAC等
AudioFormatFlags mFormatFlags;// 每种格式特定的标志,无损编码 ,0表示没有
UInt32 mBytesPerPacket;// 一个数据包中的字节数
UInt32 mFramesPerPacket;// 一个数据包中的帧数,每个packet的帧数。如果是未压缩的音频数据,值是1。动态帧率格式,这个值是一个较大的固定数字,比如说AAC的1024。如果是动态大小帧数(比如Ogg格式)设置为0。
UInt32 mBytesPerFrame;// 每一帧中的字节数
UInt32 mChannelsPerFrame;// 每一帧数据中的通道数,单声道为1,立体声为2
UInt32 mBitsPerChannel;// 每个通道中的位数,1byte = 8bit
UInt32 mReserved;// 8字节对齐,填0
};typedefstructAudioStreamBasicDescription AudioStreamBasicDescription;
对外构造方法中初始化相关数据
AudioInput::AudioInput()
{
m_bAudioPlayFlag = false; //是否开启录制
m_inputArray= [[NSMutableArray alloc] init]; //存储采集到的音频数据NSData
//创建线程来发送采集到的数据到设备端(相当于服务端),写入到文件就不需要这里
GThread::ThreadFunc runSendFunction(bind(&AudioInput::send,this));
m_SendThread=new GThread(runSendFunction,"sendThread");
m_SendThread->setPalpitationTime(50);
}
开始录制
bool AudioInput::startCollect()
{
m_SendThread->start(); //启动发送数据线程
SetupAudioFormat(kAudioFormatLinearPCM); //配置音频数据输出格式参数
OSStatusstate =AudioQueueNewInput(&mRecordFormat,AudioInput::MyInputBufferHandler,this,NULL,NULL,0, &mQueue); //开始采集,传入音频参数、回调方法以及音频采集队列
if(state !=noErr){
return false;
}
intbufferByteSize =5*1024;
state =AudioQueueAllocateBuffer(mQueue, bufferByteSize, &mBuffers);
if(state !=noErr){
return false;
}
state =AudioQueueEnqueueBuffer(mQueue, mBuffers, 0, NULL);
if(state !=noErr){
return false;
}
state =AudioQueueStart(mQueue,NULL);
if(state !=noErr){
return false;
}
m_bAudioPlayFlag = true;
return true;
}
配置音频输出参数
void AudioInput::SetupAudioFormat(UInt32inFormatID)
{
memset(&mRecordFormat, 0, sizeof(mRecordFormat));
UInt32 size = sizeof(mRecordFormat.mSampleRate);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate, &size, &mRecordFormat.mSampleRate);
mRecordFormat.mSampleRate=8000;
size =sizeof(mRecordFormat.mChannelsPerFrame);
AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels, &size, &mRecordFormat.mChannelsPerFrame);
mRecordFormat.mFormatID= inFormatID;
if (inFormatID == kAudioFormatLinearPCM){
// if we want pcm, default to signed 16-bit little-endian
mRecordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
mRecordFormat.mBitsPerChannel = 16;
mRecordFormat.mBytesPerPacket = mRecordFormat.mBytesPerFrame = (mRecordFormat.mBitsPerChannel / 8) * mRecordFormat.mChannelsPerFrame;
mRecordFormat.mFramesPerPacket = 1;
}
}
采集数据后的回调方法
void AudioInput::MyInputBufferHandler(void*inUserData,AudioQueueRefinAQ,AudioQueueBufferRefinBuffer,
constAudioTimeStamp*inStartTime,UInt32inNumPackets,
constAudioStreamPacketDescription*inPacketDesc)
{
AudioInput*aqr = (AudioInput*)inUserData; //当前this
if(inNumPackets >0){
NSData*data = [NSData dataWithBytes:(char*)inBuffer->mAudioDatalength:inBuffer->mAudioDataByteSize];
[handle seekToEndOfFile];
[handlewriteData:data]; //将data数据写入到文件中
[handlecloseFile];
[m_inputArray addObject:data]; // 放到数组中,以便发送数据线程从数组中获取
}
AudioQueueEnqueueBuffer(inAQ, inBuffer,0,NULL);
}
创建文件
staticNSString*aPath =nil;
aPath = [NSString stringWithFormat:@"%@/Documents/%@",NSHomeDirectory(),@"test.pcm"];
if(![fileMfileExistsAtPath:aPath]) {
[fileM createFileAtPath:aPath contents:nil attributes:nil];
}
handle = [NSFileHandle fileHandleForWritingAtPath:aPath];
发送数据线程
int AudioInput::send()
{
if (m_inputArray.count != 0 && m_bAudioPlayFlag)
{
NSData*data = [m_inputArray objectAtIndex:0];
[m_inputArray removeObject:data];
// 发送数据到设备端
// 。。。。。。。。
}
return0;
}
停止录制
void AudioInput::stopCollect()
{
if(!m_bAudioPlayFlag){
return;
}
m_SendThread->stop();
m_bAudioPlayFlag = false;
AudioQueueStop(mQueue, false);
AudioQueueDispose(mQueue, false);
}
析构方法
AudioInput::~AudioInput()
{
stopCollect();
}
写入到文件的pcm数据可以通过Cool Edit Pro工具来播放