iOS学习笔记2-使用Audio Queues录音,取得实时PCM数据
2015-07-30 17:55
453 查看
1.学iOS接到的第一个项目就是需要用到实时录音,所以也就接触到了Audio Queues,苹果的录音相对安卓的较麻烦些,有以下两种常见录音方式:
(1)苹果推荐我们使用AVFoundation框架中的AVAudioPlayer和AVAudioRecorder类。虽然用法比较简单,但是不支持流式;这就意味着:在播放音频前,必须等到整个音频加载完成后,才能开始播放音频;录音时,也必须等到录音结束后,才能获取到录音数据。这给应用造成了很大的局限性。
适用场合:不需要实时处理音频的时候,比如录备忘录等。
(2)在iOS和Mac OS X中,音频队列Audio Queues是一个用来录制和播放音频的软件对象,也就是说,可以用来录音和播放,录音能够获取实时的PCM原始音频数据。
使用场合:需要拿到实时的PCM录音数据或者需要利用实时的PCM的音频数据去播放。
2.这里不详细介绍音频队列Audio Queues的实现原理,主要讲代码,如果大家仍未熟悉Audio Queues,可以参考这位牛人的博客:/article/2791365.html
实现代码如下:(录音部分)
(1)首先,需要定义一些常数:
(2)接着,需要初始化录音的参数,在初始化时调用:
调用的setupAudioFormat函数如下:
(3)设置好格式后,可以继续下一步,
(4)执行AudioQueueStart后,接下来的就剩下编写回调函数的内容了:
(5)关于如何停止:
至此,你应该能够录到实时的PCM语音数据了。
但,在我实际写的过程中,我遇到了一下几个问题,特此笔记,供大家讨论:
(1)网上有人的代码是用c++写的,官网给的例子speakhere也是用c++写的,而我用的是objective-c写的,我查了下,发现有人说用objective-c写会有内存泄露,发生在这句:
而用c++的写法是:
差异在于(__bridge void *)(self)和this,有人说这里导致了内存泄露,我这里还搞不明白;
(2)录音时调用回调函数的时间问题:
理论上讲,我们录音的时候将参数设置好,那么回调函数就会根据我们设置的缓冲区的buffer大小去进行等间隔调用,比如我8000hz的采样率,每次采16bit,那么1s的话总共会采了16000bytes,我的buffer设置成2048个字节的话,那么应该是2048/16000=0.128s左右调用一次回调函数,但实际上我发现不是这样子的,比如我的调用回调函数的打印
结果如下:
2015-07-20 16:45:53.235 HelloWorld[4115:239431] bufferByteSize is :2048
2015-07-20 16:45:53.291 HelloWorld[4115:239431] we are turely begin recording
2015-07-20 16:45:53.802 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:53.803 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:53.803 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:53.803 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.313 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.313 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.314 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.314 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.824 HelloWorld[4115:239511] we are in callback
实际的现象是开始录音后,从53.291-53.802s用了0.5s左右开始进入第一个回调函数,接着,是几乎同时调用了四个回调函数,然后再间隔0.5s左右又重新调用4个回调函数,我试着仅修改官网的例子speakhere里的缓冲区buffer的大小,但是也出现同样地情况,这个类似于在前面提到的一篇博客《音频队列服务编程指南(Audio
Queue Services Programming Guide)(二)》“在录制或播放过程中,音频队列将反复的调用它所拥有的音频队列回调函数。调用的时间间隔取决于音频队列缓冲区的容量,并且一般来一说这个时间在半秒或者几秒”。
这个问题我也纠结了很久,后来自己总结了问题所在,但不确定是否正确:
原因:
这个函数的第四个和第五个参数是有关于线程的,我设置成null,代表它默认使用内部线程去录音,而且还是异步的,所以在我的缓冲区buffer比较小的情况下,就会出现同时出现4个回调函数的情况,应该是这个原因。
我还在stackoverflow寻找这个问题的答案,发现也有人遇到这个问题,相关问题网址是:
http://stackoverflow.com/questions/4595532/audioqueuenewinput-callback-latency,
最后指出解决方法:
好了,到此,我的笔记也写完了,希望大家一起探讨,多多指教;
我参考了以下网址的内容或者代码:
http://www.cnblogs.com/anjohnlv/p/3383908.html http://blog.sina.com.cn/s/blog_c13ee7440102ux0t.html
大家转载的话记得附上本博客地址!
(1)苹果推荐我们使用AVFoundation框架中的AVAudioPlayer和AVAudioRecorder类。虽然用法比较简单,但是不支持流式;这就意味着:在播放音频前,必须等到整个音频加载完成后,才能开始播放音频;录音时,也必须等到录音结束后,才能获取到录音数据。这给应用造成了很大的局限性。
适用场合:不需要实时处理音频的时候,比如录备忘录等。
(2)在iOS和Mac OS X中,音频队列Audio Queues是一个用来录制和播放音频的软件对象,也就是说,可以用来录音和播放,录音能够获取实时的PCM原始音频数据。
使用场合:需要拿到实时的PCM录音数据或者需要利用实时的PCM的音频数据去播放。
2.这里不详细介绍音频队列Audio Queues的实现原理,主要讲代码,如果大家仍未熟悉Audio Queues,可以参考这位牛人的博客:/article/2791365.html
实现代码如下:(录音部分)
(1)首先,需要定义一些常数:
#define kNumberAudioQueueBuffers 3 //定义了三个缓冲区 #define kDefaultBufferDurationSeconds 0.1279 //调整这个值使得录音的缓冲区大小为2048bytes #define kDefaultSampleRate 8000 //定义采样率为8000
(2)接着,需要初始化录音的参数,在初始化时调用:
[self setupAudioFormat:kAudioFormatLinearPCM SampleRate:(int)self.sampleRate];</span>
调用的setupAudioFormat函数如下:
// 设置录音格式 - (void)setupAudioFormat:(UInt32) inFormatID SampleRate:(int)sampeleRate { //重置下 memset(&_recordFormat, 0, sizeof(_recordFormat)); //设置采样率,这里先获取系统默认的测试下 //TODO: //采样率的意思是每秒需要采集的帧数 _recordFormat.mSampleRate = sampeleRate;//[[AVAudioSession sharedInstance] sampleRate]; //设置通道数,这里先使用系统的测试下 //TODO: _recordFormat.mChannelsPerFrame = 1;//(UInt32)[[AVAudioSession sharedInstance] inputNumberOfChannels]; // NSLog(@"sampleRate:%f,通道数:%d",_recordFormat.mSampleRate,_recordFormat.mChannelsPerFrame); //设置format,怎么称呼不知道。 _recordFormat.mFormatID = inFormatID; if (inFormatID == kAudioFormatLinearPCM){ //这个屌属性不知道干啥的。,//要看看是不是这里属性设置问题 _recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; //每个通道里,一帧采集的bit数目 _recordFormat.mBitsPerChannel = 16; //结果分析: 8bit为1byte,即为1个通道里1帧需要采集2byte数据,再*通道数,即为所有通道采集的byte数目。 //所以这里结果赋值给每帧需要采集的byte数目,然后这里的packet也等于一帧的数据。 //至于为什么要这样。。。不知道。。。 _recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame; _recordFormat.mFramesPerPacket = 1; } }
(3)设置好格式后,可以继续下一步,
-(void)startRecording { NSError *error = nil; //设置audio session的category BOOL ret = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];//注意,这里选的是AVAudioSessionCategoryPlayAndRecord参数,如果只需要录音,就选择Record就可以了,如果需要录音和播放,则选择PlayAndRecord,这个很重要 if (!ret) { NSLog(@"设置声音环境失败"); return; } //启用audio session ret = [[AVAudioSession sharedInstance] setActive:YES error:&error]; if (!ret) { NSLog(@"启动失败"); return; } _recordFormat.mSampleRate = self.sampleRate;//设置采样率,8000hz //初始化音频输入队列 AudioQueueNewInput(&_recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue);//inputBufferHandler这个是回调函数名 //计算估算的缓存区大小 int frames = (int)ceil(self.bufferDurationSeconds * _recordFormat.mSampleRate);//返回大于或者等于指定表达式的最小整数 int bufferByteSize = frames * _recordFormat.mBytesPerFrame;//缓冲区大小在这里设置,这个很重要,在这里设置的缓冲区有多大,那么在回调函数的时候得到的inbuffer的大小就是多大。 NSLog(@"缓冲区大小:%d",bufferByteSize); //创建缓冲器 for (int i = 0; i < kNumberAudioQueueBuffers; i++){ AudioQueueAllocateBuffer(_audioQueue, bufferByteSize, &_audioBuffers[i]); AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[i], 0, NULL);//将 _audioBuffers[i]添加到队列中 } // 开始录音 AudioQueueStart(_audioQueue, NULL); self.isRecording = YES; }
(4)执行AudioQueueStart后,接下来的就剩下编写回调函数的内容了:
//相当于中断服务函数,每次录取到音频数据就进入这个函数 //inAQ 是调用回调函数的音频队列 //inBuffer 是一个被音频队列填充新的音频数据的音频队列缓冲区,它包含了回调函数写入文件所需要的新数据 //inStartTime 是缓冲区中的一采样的参考时间,对于基本的录制,你的毁掉函数不会使用这个参数 //inNumPackets是inPacketDescs参数中包描述符(packet descriptions)的数量,如果你正在录制一个VBR(可变比特率(variable bitrate))格式, 音频队列将会提供这个参数给你的回调函数,这个参数可以让你传递给AudioFileWritePackets函数. CBR (常量比特率(constant bitrate)) 格式不使用包描述符。对于CBR录制,音频队列会设置这个参数并且将inPacketDescs这个参数设置为NULL,官方解释为The number of packets of audio data sent to the callback in the inBuffer parameter.
void inputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc) { NSLog(@"we are in the 回调函数\n"); CSRecorder *recorder = (__bridge CSRecorder*)inUserData; if (inNumPackets > 0) { NSLog(@"in the callback the current thread is %@\n",[NSThread currentThread]); [recorder processAudioBuffer:inBuffer withQueue:inAQ]; //在这个函数你可以用录音录到得PCM数据:inBuffer,去进行处理了 } if (recorder.isRecording) { AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL); } }
(5)关于如何停止:
-(void)stopRecording { NSLog(@"stop recording out\n");//为什么没有显示 if (self.isRecording) { self.isRecording = NO; //停止录音队列和移除缓冲区,以及关闭session,这里无需考虑成功与否 AudioQueueStop(_audioQueue, true); AudioQueueDispose(_audioQueue, true);//移除缓冲区,true代表立即结束录制,false代表将缓冲区处理完再结束 [[AVAudioSession sharedInstance] setActive:NO error:nil]; } }
至此,你应该能够录到实时的PCM语音数据了。
但,在我实际写的过程中,我遇到了一下几个问题,特此笔记,供大家讨论:
(1)网上有人的代码是用c++写的,官网给的例子speakhere也是用c++写的,而我用的是objective-c写的,我查了下,发现有人说用objective-c写会有内存泄露,发生在这句:
AudioQueueNewInput(&_recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue);//inputBufferHandler这个是回调函数名(objective-c的写法)
而用c++的写法是:
AudioQueueNewOutput(&mDataFormat, AQPlayer::AQBufferCallback, this,CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &mQueue);(speakhere中C++的写法)
差异在于(__bridge void *)(self)和this,有人说这里导致了内存泄露,我这里还搞不明白;
(2)录音时调用回调函数的时间问题:
理论上讲,我们录音的时候将参数设置好,那么回调函数就会根据我们设置的缓冲区的buffer大小去进行等间隔调用,比如我8000hz的采样率,每次采16bit,那么1s的话总共会采了16000bytes,我的buffer设置成2048个字节的话,那么应该是2048/16000=0.128s左右调用一次回调函数,但实际上我发现不是这样子的,比如我的调用回调函数的打印
结果如下:
2015-07-20 16:45:53.235 HelloWorld[4115:239431] bufferByteSize is :2048
2015-07-20 16:45:53.291 HelloWorld[4115:239431] we are turely begin recording
2015-07-20 16:45:53.802 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:53.803 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:53.803 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:53.803 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.313 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.313 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.314 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.314 HelloWorld[4115:239511] we are in callback
2015-07-20 16:45:54.824 HelloWorld[4115:239511] we are in callback
实际的现象是开始录音后,从53.291-53.802s用了0.5s左右开始进入第一个回调函数,接着,是几乎同时调用了四个回调函数,然后再间隔0.5s左右又重新调用4个回调函数,我试着仅修改官网的例子speakhere里的缓冲区buffer的大小,但是也出现同样地情况,这个类似于在前面提到的一篇博客《音频队列服务编程指南(Audio
Queue Services Programming Guide)(二)》“在录制或播放过程中,音频队列将反复的调用它所拥有的音频队列回调函数。调用的时间间隔取决于音频队列缓冲区的容量,并且一般来一说这个时间在半秒或者几秒”。
这个问题我也纠结了很久,后来自己总结了问题所在,但不确定是否正确:
原因:
AudioQueueNewInput(&_recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue);//inputBufferHandler这个是回调函数名
这个函数的第四个和第五个参数是有关于线程的,我设置成null,代表它默认使用内部线程去录音,而且还是异步的,所以在我的缓冲区buffer比较小的情况下,就会出现同时出现4个回调函数的情况,应该是这个原因。
我还在stackoverflow寻找这个问题的答案,发现也有人遇到这个问题,相关问题网址是:
http://stackoverflow.com/questions/4595532/audioqueuenewinput-callback-latency,
最后指出解决方法:
好了,到此,我的笔记也写完了,希望大家一起探讨,多多指教;
我参考了以下网址的内容或者代码:
http://www.cnblogs.com/anjohnlv/p/3383908.html http://blog.sina.com.cn/s/blog_c13ee7440102ux0t.html
大家转载的话记得附上本博客地址!
相关文章推荐
- UITableView 实现类似btn单选功能
- Selenium2学习-019-WebUI自动化实战实例-017-获取浏览器类型
- ui automation viewer 工具
- 解析iptables中SNAT和MASQUERADE之间的区别
- navigationItem中加入UISegmentedControl
- IOS开发 UITableView empty 简单实用
- the constructor AlertDialog.Builder(new View.OnClickListener() ) is undefined
- pcDuino使用问题总结
- UIViewController的生命周期及iOS程序执行顺序
- zozoui
- Arduino开发环境搭建
- 百度编辑器ueditor的简单使用
- [Android实例] Handler+ExecutorService(线程池)+MessageQueue模式+缓存模式
- Truncated incorrect DOUBLE value: '172.31.27.3'错误原因及解决方案
- 杂烩:QWiget、QGraphics、QtQuick
- iOS阶段学习第28天笔记(UIView的介绍)
- EasyUI基础searchbox&progressbar(搜索框,进度条)
- KVM虚拟机IO处理过程(一) ----Guest VM I/O 处理过程
- iOS开发UI篇—使用xib自定义UItableviewcell实现一个简单的团购应用界面布局
- HDOJ 题目4027 Can you answer these queries?(线段树,区间减为平方根,区间求和)