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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269