基于RNN的音频降噪算法 (附完整C代码)

本文介绍了一个基于RNN的音频降噪项目rnnoise,并分享了对其的改进实践,包括支持wav格式、全采样率及增加CMake文件等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

前几天无意间看到一个项目rnnoise。

项目地址: https://github.com/xiph/rnnoise

基于RNN的音频降噪算法。

采用的是 GRU/LSTM 模型。

阅读下训练代码,可惜的是作者没有提供数据训练集。

不过基本可以断定他采用的数据集里,肯定有urbansound8k。

urbansound8k 数据集地址:

https://serv.cusp.nyu.edu/projects/urbansounddataset/urbansound8k.html

也可以考虑采用用作者训练的模型来构建数据集的做法,不过即费事,也麻烦。

经过实测,降噪效果很不错,特别是在背景噪声比较严重的情况下。

不过作者仅仅提供 pcm 的代码示例,并且还只支持48K采样率,

( 明显是为了兼容其另一个 项目  opus)

在很多应用场景下,这很不方便。

尽管稍微有点麻烦,但是事在人为,花了点时间,稍作修改。

 

具体修改如下:

1.支持wav格式

 采用dr_wav(https://github.com/mackron/dr_libs/blob/master/dr_wav.h )

2.支持全部采样率

采样率的处理问题,采用简单粗暴法,

详情请移步博主另一篇小文《简洁明了的插值音频重采样算法例子 (附完整C代码)

3.增加CMake文件

4.增加测试用 示例音频sample.wav 

取自(https://github.com/orctom/rnnoise-java)

 

贴上完整示例代码 :

/* Copyright (c) 2017 Mozilla */
/*
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   - Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

   - Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdio.h>
#include "rnnoise.h"
#include <stdlib.h>
#include <stdint.h>

#define DR_WAV_IMPLEMENTATION

#include "dr_wav.h"


void wavWrite_int16(char *filename, int16_t *buffer, int sampleRate, uint32_t totalSampleCount) {
    drwav_data_format format;
    format.container = drwav_container_riff;
    format.format = DR_WAVE_FORMAT_PCM;
    format.channels = 1;
    format.sampleRate = (drwav_uint32) sampleRate;
    format.bitsPerSample = 16;
    drwav *pWav = drwav_open_file_write(filename, &format);
    if (pWav) {
        drwav_uint64 samplesWritten = drwav_write(pWav, totalSampleCount, buffer);
        drwav_uninit(pWav);
        if (samplesWritten != totalSampleCount) {
            fprintf(stderr, "ERROR\n");
            exit(1);
        }
    }
}

int16_t *wavRead_int16(char *filename, uint32_t *sampleRate, uint64_t *totalSampleCount) {
    unsigned int channels;
    int16_t *buffer = drwav_open_and_read_file_s16(filename, &channels, sampleRate, totalSampleCount);
    if (buffer == NULL) {
        fprintf(stderr, "ERROR\n");
        exit(1);
    }
    if (channels != 1) {
        drwav_free(buffer);
        buffer = NULL;
        *sampleRate = 0;
        *totalSampleCount = 0;
    }
    return buffer;
}

void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) {
    const char *end;
    const char *p;
    const char *s;
    if (path[0] && path[1] == ':') {
        if (drv) {
            *drv++ = *path++;
            *drv++ = *path++;
            *drv = '\0';
        }
    } else if (drv)
        *drv = '\0';
    for (end = path; *end && *end != ':';)
        end++;
    for (p = end; p > path && *--p != '\\' && *p != '/';)
        if (*p == '.') {
            end = p;
            break;
        }
    if (ext)
        for (s = end; (*ext = *s++);)
            ext++;
    for (p = end; p > path;)
        if (*--p == '\\' || *p == '/') {
            p++;
            break;
        }
    if (name) {
        for (s = p; s < end;)
            *name++ = *s++;
        *name = '\0';
    }
    if (dir) {
        for (s = path; s < p;)
            *dir++ = *s++;
        *dir = '\0';
    }
}

void resampleData(const int16_t *sourceData, int32_t sampleRate, uint32_t srcSize, int16_t *destinationData,
                  int32_t newSampleRate) {
    if (sampleRate == newSampleRate) {
        memcpy(destinationData, sourceData, srcSize * sizeof(int16_t));
        return;
    }
    uint32_t last_pos = srcSize - 1;
    uint32_t dstSize = (uint32_t) (srcSize * ((float) newSampleRate / sampleRate));
    for (uint32_t idx = 0; idx < dstSize; idx++) {
        float index = ((float) idx * sampleRate) / (newSampleRate);
        uint32_t p1 = (uint32_t) index;
        float coef = index - p1;
        uint32_t p2 = (p1 == last_pos) ? last_pos : p1 + 1;
        destinationData[idx] = (int16_t) ((1.0f - coef) * sourceData[p1] + coef * sourceData[p2]);
    }
}

void denoise_proc(int16_t *buffer, uint32_t buffen_len) {
    const int frame_size = 480;
    DenoiseState *st;
    st = rnnoise_create();
    int16_t patch_buffer[frame_size];
    if (st != NULL) {
        uint32_t frames = buffen_len / frame_size;
        uint32_t lastFrame = buffen_len % frame_size;
        for (int i = 0; i < frames; ++i) {
            rnnoise_process_frame(st, buffer, buffer);
            buffer += frame_size;
        }
        if (lastFrame != 0) {
            memset(patch_buffer, 0, frame_size * sizeof(int16_t));
            memcpy(patch_buffer, buffer, lastFrame * sizeof(int16_t));
            rnnoise_process_frame(st, patch_buffer, patch_buffer);
            memcpy(buffer, patch_buffer, lastFrame * sizeof(int16_t));
        }
    }
    rnnoise_destroy(st);
}

void rnnDeNoise(char *in_file, char *out_file) {
    uint32_t in_sampleRate = 0;
    uint64_t in_size = 0;
    int16_t *data_in = wavRead_int16(in_file, &in_sampleRate, &in_size);
    uint32_t out_sampleRate = 48000;
    uint32_t out_size = (uint32_t) (in_size * ((float) out_sampleRate / in_sampleRate));
    int16_t *data_out = (int16_t *) malloc(out_size * sizeof(int16_t));
    if (data_in != NULL && data_out != NULL) {
        resampleData(data_in, in_sampleRate, (uint32_t) in_size, data_out, out_sampleRate);
        denoise_proc(data_out, out_size);
        resampleData(data_out, out_sampleRate, (uint32_t) out_size, data_in, in_sampleRate);
        wavWrite_int16(out_file, data_in, in_sampleRate, (uint32_t) in_size);
        free(data_in);
        free(data_out);
    } else {
        if (data_in) free(data_in);
        if (data_out) free(data_out);
    }
}


int main(int argc, char **argv) {
    printf("Audio Noise Reduction\n");
    printf("blog:http://cpuimage.cnblogs.com/\n");
    printf("e-mail:gaozhihan@vip.qq.com\n");
    if (argc < 2)
        return -1;

    char *in_file = argv[1];
    char drive[3];
    char dir[256];
    char fname[256];
    char ext[256];
    char out_file[1024];
    splitpath(in_file, drive, dir, fname, ext);
    sprintf(out_file, "%s%s%s_out%s", drive, dir, fname, ext);
    rnnDeNoise(in_file, out_file);
    printf("press any key to exit.\n");
    getchar();
    return 0;
}

 

不多写注释,直接看代码吧。

 项目地址:https://github.com/cpuimage/rnnoise

 

示例具体流程为:

加载wav(拖放wav文件到可执行文件上)->重采样降噪->保存wav

 

若有其他相关问题或者需求也可以邮件联系俺探讨。

邮箱地址是: 
gaozhihan@vip.qq.com

转载于:https://www.cnblogs.com/cpuimage/p/8733742.html

<think>好的,用户问的是如何用RNN实现音频降噪。首先,我需要回忆一下RNN的基本原理和应用场景。RNN,也就是循环神经网络,擅长处理序列数据,比如时间序列、自然语言处理,以及音频信号。音频信号本身就是时间序列数据,所以RNN在这里应该有用武之地。 接下来,音频降噪的基本思路应该是通过模型学习从带噪音频到干净音频的映射。可能需要设计一个有监督的模型,输入是带噪声的音频片段,输出是降噪后的音频。这时候,RNN的结构比如LSTM或者GRU可能会有帮助,因为它们能捕捉时间上的长期依赖关系,这在音频处理中可能很重要。 然后,数据准备方面,用户需要大量的带噪和对应的干净音频样本。可能需要数据增强,比如在干净音频上添加不同类型的噪声(白噪声、环境噪声等),并调整信噪比来生成训练数据。预处理步骤可能包括分帧、傅里叶变换将时域信号转为频域,这样可能更有效,因为频域特征更容易处理。 模型结构方面,输入可能是带噪音频的频谱特征,输出是对应的干净频谱。RNN层可能处理这些时间序列,然后通过全连接层或者其他层重构信号。训练时,损失函数可能是均方误差(MSE),用来比较输出和干净音频的差异。 然后,用户可能需要示例代码代码部分应该包括数据加载、模型构建、训练循环等。需要注意使用适合处理序列的RNN层,比如LSTM或者GRU。可能还要考虑双向RNN,或者堆叠多层RNN来增强模型能力。 另外,实际应用中可能遇到的问题包括实时处理的需求,这时候模型的计算效率需要优化。还有,音频的重构质量,可能需要后处理步骤,比如使用Griffin-Lim算法将频谱转换回时域信号。 最后,我应该提醒用户,这个示例代码只是一个简化版本,实际应用可能需要更复杂的结构,比如结合CNN和RNN的CRNN模型,或者使用注意力机制。此外,数据集的选择和预处理对结果影响很大,可能需要更多的数据增强和调整超参数。 相关问题部分,应该涵盖RNN的其他应用、音频处理的不同方法、数据预处理的重要性、模型优化等,帮助用户扩展相关知识。</think><< 使用RNN(循环神经网络)实现音频降噪通常涉及以下核心步骤: 1. **数据准备**: - 需要成对的带噪声音频和对应的干净音频作为训练数据 - 常用数据集:VoiceBank-DEMAND、DNS Challenge等 - 预处理步骤:分帧、傅里叶变换(STFT)、幅度谱/相位谱分离 2. **模型架构**: ```java // 示例基于Deeplearning4j的伪代码实现 public class DenoisingRNN { public static MultiLayerNetwork buildModel(int inputSize) { return new MultiLayerNetwork(new NeuralNetConfiguration.Builder() .weightInit(WeightInit.XAVIER) .updater(new Adam(0.001)) .list() .layer(new LSTM.Builder() .nIn(inputSize) .nOut(256) .activation(Activation.TANH) .build()) .layer(new RnnOutputLayer.Builder(LossFunction.MSE) .nIn(256) .nOut(inputSize) .activation(Activation.IDENTITY) .build()) .build()); } } ``` 3. **关键实现细节**: - 输入输出:带噪音频的频谱特征作为输入,干净音频的频谱作为目标 - 特征表示:通常使用对数幅度谱(log-magnitude spectrogram) - 序列处理:利用LSTM/GRU处理时间维度上的依赖关系 - 后处理:通过相位重建算法(如Griffin-Lim)恢复时域信号 4. **训练流程**: 1. 对音频进行分帧和STFT变换 2. 提取幅度谱和相位谱 3. 输入带噪幅度谱,预测干净幅度谱 4. 使用MSE损失函数优化网络参数 5. 结合预测幅度谱和原始相位进行信号重建 **典型问题原因**: - 相位处理困难:传统方法忽略相位信息,可能导致重建质量下降 - 实时性限制:RNN的序列处理特性可能影响实时处理效率 - 噪声多样性:训练数据与真实场景的噪声分布不匹配会导致性能下降
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值