iOS 降噪(PCM,任何文件格式都适用)

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;
}

你可能感兴趣的:(iOS 降噪(PCM,任何文件格式都适用))