0.demo地址
https://github.com/majianghai/record
1.iOS的两种录音方式
1.a AVAudioRecorder
第一种方式是将音频数据储存在文件中,不能做到实时的获取到录音的数据。
关于第一种录音的方式,我在之前的降噪篇中给了代码,这里就不做赘述。
优点:
这种方式录制的音频数据,是没有静音数据的,当进行语音识别的时候,不需要使用VAD依然可以进行识别
缺点:
但是由于不是实时的数据,所以会有一些延迟
1.b AudioToolbox
第二种方式是将音频数据储存到队列中,可以做到实时的获取到录音数据。
这篇文章主要讲的是,第二种方式的运用。
优点:
这种方式录制的音频数据是实时数据,即时性较强
缺点:
1.会产生静音数据,在做语音识别的时候,需要VAD进行过滤(这个真的很坑)
2.在结束说话之后,结束标记已经修改过之后,依然会产生语音数据,如果说对数据传递的结尾标记有要求的话,需要处理(我的代码里处理了)
2.实时录音
RecordTool.h
@interface RecordTool : NSObject
@property (nonatomic, assign) BOOL isRecording;
+ (instancetype)shared;
//开始录音
- (void)startRecordingWithBlock:(void (^_Nullable)(NSData *_Nullable))block;
//停止录音
- (void)stopRecording;
@end
RecordTool.m
//
// EYRecord.m
// 实时录音
//
// Created by majianghai on 2019/5/13.
// Copyright © 2019 com.cmcmid. All rights reserved.
//
#import "RecordTool.h"
#import <AudioToolbox/AudioToolbox.h>
#define QUEUE_BUFFER_SIZE 3 // 输出音频队列缓冲个数
#define kDefaultBufferDurationSeconds 0.02//调整这个值使得录音的缓冲区大小,0.01对应310好像是
#define kDefaultSampleRate 16000 //定义采样率为16000
extern NSString * const ESAIntercomNotifationRecordString;
static BOOL isRecording = NO;
@interface RecordTool(){
AudioQueueRef _audioQueue; //输出音频播放队列
AudioStreamBasicDescription _recordFormat;
AudioQueueBufferRef _audioBuffers[QUEUE_BUFFER_SIZE]; //输出音频缓存
}
@property (nonatomic, copy) void(^block)(NSData *);
@end
@implementation RecordTool
+ (instancetype)shared {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [self init];
});
return instance;
}
- (instancetype)init
{
self = [super init];
if (self) {
//重置下
memset(&_recordFormat, 0, sizeof(_recordFormat));
_recordFormat.mSampleRate = kDefaultSampleRate;
_recordFormat.mChannelsPerFrame = 1;
_recordFormat.mFormatID = kAudioFormatLinearPCM;
_recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
_recordFormat.mBitsPerChannel = 16;
_recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame;
_recordFormat.mFramesPerPacket = 1;
//初始化音频输入队列
AudioQueueNewInput(&_recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue);
//计算估算的缓存区大小
int frames = (int)ceil(kDefaultBufferDurationSeconds * _recordFormat.mSampleRate);
int bufferByteSize = frames * _recordFormat.mBytesPerFrame;
NSLog(@"缓存区大小%d",bufferByteSize);
//创建缓冲器
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++){
AudioQueueAllocateBuffer(_audioQueue, bufferByteSize, &_audioBuffers[i]);
AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[i], 0, NULL);
}
}
return self;
}
- (void)startRecordingWithBlock:(void (^_Nullable)(NSData *_Nullable))block {
// 开始录音
self.block = block;
AudioQueueStart(_audioQueue, NULL);
isRecording = YES;
self.isRecording = YES;
count = 0;
}
void inputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc)
{
if (inNumPackets > 0) {
RecordTool *recorder = (__bridge RecordTool*)inUserData;
[recorder processAudioBuffer:inBuffer withQueue:inAQ];
}
if (isRecording) {
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
}
static int count = 0;
- (void)processAudioBuffer:(AudioQueueBufferRef )audioQueueBufferRef withQueue:(AudioQueueRef )audioQueueRef
{
NSMutableData * dataM = [NSMutableData dataWithBytes:audioQueueBufferRef->mAudioData length:audioQueueBufferRef->mAudioDataByteSize];
//此处是发通知将dataM 传递出去
if (isRecording) {
self.block(dataM);
} else {
// 结束标记置NO之后,依然会有数据进行发送,这里我只取了一条(一般会出现3条。最后的基本是没有用的数据,并不是说话的内容)
// 这样当isRecording置NO之后,就会把最后一条数据返回到调用方,用来作为结束
count++;
if (count == 1) {
self.block(dataM);
}
}
}
-(void)stopRecording
{
if (isRecording)
{
isRecording = NO;
self.isRecording = NO;
//停止录音队列和移除缓冲区,以及关闭session,这里无需考虑成功与否
AudioQueueStop(_audioQueue, true);
//移除缓冲区,true代表立即结束录制,false代表将缓冲区处理完再结束
AudioQueueDispose(_audioQueue, true);
}
}
@end
外部调用
[self.recordTool startRecordingWithBlock:^(NSData * _Nullable data) {
Byte *buffer = (Byte *)[data bytes];
size_t length = data.length;
if (self.recordTool.isRecording) {
NSLog(@"正在上传leng =%zu", length);
[[OVSASREngine shared] inputG7221AudioBuffer:buffer
length:length
index:self.bufferIndex++
request:OVSBufferRequestTranslate];
self.beforeTime = CFAbsoluteTimeGetCurrent();
} else {
NSLog(@"----------结束上传length = %zu", length);
[[OVSASREngine shared] inputG7221AudioBuffer:buffer length:length index:-1 request:OVSBufferRequestTranslate];
self.bufferIndex = 0;
}
}];