0.Demo地址
https://github.com/majianghai/denoise
1.为什么要降噪
最近在做语音识别的东西,但是使用iPhone手机直接录制的语音中含有噪声,导致语音识别的时候,一直识别不出来,所以出现了降噪的需求。
但是-----网上的资料真的很少,能用的更少!!!
费了半天劲,终于找到一个wav格式降噪的iOS的,但是我要降噪的文件格式是PCM啊。这里其实降噪的函数用法是一样的,只是换一个文件就ok了。但是我也是初涉音频处理,里面有一个函数的参数,实在是刚开始不知道要传什么,都是一个一个试出来,加百度百科,开始研究这些声音格式和采样原理,最后终于搞定了。知道了这些概念之后,再做起来真的不难,就是刚开始没基础知识,没入门。
废话不多说,直接讲代码
2.降噪
2.1 使用到的库
这里我用到的库是WebRTC,个人感觉直接操作底层函数比较有安全感,改起来也方便,但是问题是很多函数的参数是没有具体的解释的,配置起来真的是费劲,需要反复尝试,查相关资料。值得庆幸的是,网上的资料真的少,对进度产生了非常大的影响。
其实设计到的函数并不多,就四五个。
导致耗时的问题,
1、降噪函数需要一个,计算音频数据的总采样数,这样一个参数。刚开始不知道这个采样总数是如何计算的,网上搜索也没有给出什么有用的解释,最后还是找到一个似是而非的公式,抱着试一试的心态写了,然后和Demo中的wav音频的采样总数做对比,发现一样,才相信是这么回事。
总音频采样数 = 音频总时长(毫秒) / 10 * 采样率
就是这么个公式,但是真的不好搜,你可以试试。
2.2 音频录制
这里先用iphone录制音频,然后进行降噪处理,录制音频的采样率一定要和降噪处理的采样率保持一致
// 开始录音
- (void)startRecorder {
AVAudioSession * session = [AVAudioSession sharedInstance];
NSError *sessionError;
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:&sessionError];
if (session == nil) {
}else{
[session setActive:YES error:nil];
}
//录音设置
NSMutableDictionary * recordSetting = [[NSMutableDictionary alloc]init];
//设置录音格式
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
//设置录音采样率(HZ)
[recordSetting setValue:[NSNumber numberWithFloat:16000] forKey:AVSampleRateKey];
//录音通道数
[recordSetting setValue:[NSNumber numberWithInt:1] forKey:AVNumberOfChannelsKey];
//线性采样位数
[recordSetting setValue:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
//录音的质量
[recordSetting setValue:[NSNumber numberWithInt:AVAudioQualityMax] forKey:AVEncoderAudioQualityKey];
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:docPath]) {
[fileManager createDirectoryAtPath:docPath withIntermediateDirectories:YES attributes:nil error:nil];
}
//创建url
self.playerPath = [VoiceTool filePath];
NSURL * url = [NSURL fileURLWithPath:self.playerPath];//voice.aac
NSError *error;
//初始化AVAudioRecorder
if(!self.recorder){
self.recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:&error];
//开启音量监测
self.recorder.meteringEnabled = YES;
self.recorder.delegate = self;
}
if(error){
NSLog(@"创建录音对象时发生错误,错误信息:%@",error.localizedDescription);
}
[self.recorder record];
}
// 结束录音
- (void)cancelRecorder {
[self.recorder stop];
}
// 播放录音
- (void)playVoice {
NSLog(@"-------");
NSString *filePath = [VoiceTool filePath];
NSURL * url = [NSURL fileURLWithPath:filePath];//voice.aac
NSData *data = [NSData dataWithContentsOfURL:url];
self.player = [[AVAudioPlayer alloc] initWithData:data error:nil];
[self.player prepareToPlay];
[self.player play];
}
+ (NSString *)filePath {
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];
NSString *filePa = [NSString stringWithFormat:@"%@/voice.pcm",docPath];
return filePa;
}
2.3 音频降噪
降噪代码如下
+(int)nsProcess {
NSString *filePath = [VoiceTool filePath];
NSData *sourceData = [NSData dataWithContentsOfFile:filePath];
int16_t *buffer = (int16_t *)[sourceData bytes];
uint32_t sampleRate = 16000;
int samplesCount = [VoiceTool voiceLength];
int level = kLow;
if (buffer == nullptr) return -1;
if (samplesCount == 0) return -1;
size_t samples = MIN(160, sampleRate / 100);
if (samples == 0) return -1;
uint32_t num_bands = 1;
int16_t *input = buffer;
size_t nTotal = (samplesCount / samples);
NsHandle *nsHandle = WebRtcNs_Create();
int status = WebRtcNs_Init(nsHandle, sampleRate);
if (status != 0) {
printf("WebRtcNs_Init fail\n");
return -1;
}
status = WebRtcNs_set_policy(nsHandle, level);
if (status != 0) {
printf("WebRtcNs_set_policy fail\n");
return -1;
}
for (int i = 0; i < nTotal; i++) {
int16_t *nsIn[1] = {input}; //ns input[band][data]
int16_t *nsOut[1] = {input}; //ns output[band][data]
WebRtcNs_Analyze(nsHandle, nsIn[0]);
WebRtcNs_Process(nsHandle, (const int16_t *const *) nsIn, num_bands, nsOut);
input += samples;
}
WebRtcNs_Free(nsHandle);
NSData *data = [NSData dataWithBytes:buffer length:[sourceData length]];
BOOL isWrite = [data writeToFile:filePath atomically:YES];
if (isWrite) {
NSLog(@"----写入成功");
}
return 1;
}
涉及到的函数
// 1.创建句柄
NsHandle *WebRtcNs_Create()
// 2.根据句柄和采样率进行初始化
第一个参数是句柄,第二个参数是采样率
int WebRtcNs_Init(NsHandle *NS_inst, uint32_t fs)
// 3.根据句柄和降噪模式,设置降噪策略
// mode:4种模式分别对应:0/1/2/3,数值越高,效果越明显
int WebRtcNs_set_policy(NsHandle *NS_inst, int mode)
// 4.降噪,根据采样间隔,将数据输入到降噪函数
void WebRtcNs_Process(NsHandle *NS_inst,
const int16_t *const *spframe,
size_t num_bands,
int16_t *const *outframe)
3.降噪之后的音频播放
降噪之后的音频,用之前写的播放功能是播不出来的,需要使用VLC工具,这个可以自行下载。
打开mac终端,输入如下命令:
/Applications/VLC.app/Contents/MacOS/VLC --demux=rawaud --rawaud-channels 1 --rawaud-samplerate 16000 + {$pcm文件路径}
4.遇到的坑
4.1 音频总采样数
这个上面说了
4.2 读取音频数据为空
+ (int)voiceLength {
NSString *filePath = [VoiceTool filePath];
NSURL *url = [NSURL fileURLWithPath:filePath];
NSData *data = [NSData dataWithContentsOfURL:url];
AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithData:data error:nil];
float duration = player.duration;
float lengMs = duration * 1000;
int len = lengMs / 10 * VOICE_RATE_UNIT;
return len;
}
在这个方法中,上面这样写才能读取到文件
但是还有一种写法,下面就是这种写法,下面这种写法,当文件在电脑上的时候可以,但是当文件在document中的时候,就读取不到数据了
- (void)voiceLength {
NSString *inpath = [[NSBundle mainBundle] pathForResource:@"a.wav" ofType:nil];
AVURLAsset* audioAsset =[AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath: inpath] options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
float lengMs = audioDurationSeconds * 1000;
int len = lengMs / 10 * 80;
}