android音频降噪webrtc

在音频处理的开源项目中,webrtc是一个很不错的例子。它包含降噪,去回声,增益,均衡等音频处理。这里我讲讲我所使用到的如何使用降噪方式。当然,具体它是如何降噪的,大家可以细看源码处理了。好了,线上源码。

以下是java 层MainActivity.java:

package com.test.jni;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.SeekBar;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    SeekBar skbVolume;//调节音量
    boolean isProcessing = true;//是否录放的标记
    boolean isRecording = false;//是否录放的标记

    static final int frequency = 8000;
    static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
    int recBufSize, playBufSize;
    AudioRecord audioRecord;
    AudioTrack audioTrack;

    private String outFilePath;
    private OutputStream mOutputStream;
    private static final int FLAG_RECORD_START = 1;
    private static final int FLAG_RECORDING = 2;
    private static final int FLAG_RECORD_FINISH = 3;

    private WebrtcProcessor mProcessor;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获得合适的录音缓存大小
        recBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
        Log.e("", "recBufSize:" + recBufSize);
        //获得合适的播放缓存大小
        playBufSize = AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding);

        //创建录音和播放实例
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, recBufSize);
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, frequency, channelConfiguration, audioEncoding, playBufSize, AudioTrack.MODE_STREAM);

        findViewById(R.id.btnRecord).setOnClickListener(this);
        findViewById(R.id.btnStop).setOnClickListener(this);

        skbVolume = (SeekBar) this.findViewById(R.id.skbVolume);
        skbVolume.setMax(100);//音量调节的极限
        skbVolume.setProgress(50);//设置seekbar的位置值
        audioTrack.setStereoVolume(0.7f, 0.7f);//设置当前音量大小
        skbVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                float vol = (float) (seekBar.getProgress()) / (float) (seekBar.getMax());
                audioTrack.setStereoVolume(vol, vol);//设置音量
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            }
        });
        ((CheckBox) findViewById(R.id.cb_ap)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton view, boolean checked) {
                isProcessing = checked;
            }
        });

        initProccesor();
    }

    @Override
    protected void onDestroy() {

        releaseProcessor();

        android.os.Process.killProcess(android.os.Process.myPid());
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btnRecord) {
            isRecording = true;

            //启动线程,开始录音和一边播放

            new RecordPlayThread().start();

        } else if (v.getId() == R.id.btnStop) {
            isRecording = false;
        }
    }

    class RecordPlayThread extends Thread {
        public void run() {
            try {

                short[] buffer = new short[recBufSize/2];
                audioRecord.startRecording();//开始录制
                audioTrack.play();//开始播放

                saveToFile(FLAG_RECORD_START, null);

                while (isRecording) {
                    //setp 1 从MIC保存数据到缓冲区
                    int bufferReadResult = audioRecord.read(buffer, 0, recBufSize/2);
                    short[] tmpBuf_src = new short[bufferReadResult];
                    System.arraycopy(buffer, 0, tmpBuf_src, 0, bufferReadResult);

                    //setp 2 进行处理
                    if (isProcessing) {

                        processData(tmpBuf_src);

                    } else {
                    }
                    //写入数据即播放
                    audioTrack.write(tmpBuf_src, 0, tmpBuf_src.length);

                    //saveToFile(FLAG_RECORDING, tmpBuf_src);

                }

                saveToFile(FLAG_RECORD_FINISH, null);

                audioTrack.stop();
                audioRecord.stop();
            } catch (Exception t) {
                t.printStackTrace();
            }
        }
    };

    class RecordPlayThread2 extends Thread {
        public void run() {
            try {

                byte[] buffer = new byte[recBufSize];
                audioRecord.startRecording();//开始录制
                audioTrack.play();//开始播放

                saveToFile(FLAG_RECORD_START, null);

                while (isRecording) {
                    //setp 1 从MIC保存数据到缓冲区
                    int bufferReadResult = audioRecord.read(buffer, 0, recBufSize);
                    byte[] tmpBuf_src = new byte[bufferReadResult];
                    System.arraycopy(buffer, 0, tmpBuf_src, 0, bufferReadResult);

                    //setp 2 进行处理
                    if (isProcessing) {

                        processData(tmpBuf_src);

                    } else {
                    }
                    //写入数据即播放
                    audioTrack.write(tmpBuf_src, 0, tmpBuf_src.length);

                    saveToFile(FLAG_RECORDING, tmpBuf_src);

                }

                saveToFile(FLAG_RECORD_FINISH, null);

                audioTrack.stop();
                audioRecord.stop();
            } catch (Exception t) {
                t.printStackTrace();
            }
        }
    };

    /**
     * 保存录音数据到本地wav文件
     * @param flag
     * @param data
     */
    private void saveToFile(int flag, byte[] data){

        switch (flag){
            case FLAG_RECORD_START:

                String pcmPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/record/record.pcm";
                try {
                    mOutputStream = new FileOutputStream(pcmPath);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

                break;
            case FLAG_RECORDING:

                if(mOutputStream != null){
                    try {
                        mOutputStream.write(data);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                break;
            case FLAG_RECORD_FINISH:

                try {
                    if(mOutputStream != null){
                        mOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                pcmPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/record/record.pcm";
                String wavePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/record/record.wav";

                AudioEncodeUtil.convertPcm2Wav(pcmPath, wavePath);

                break;
        }

    }

    /**
     *  初始化降噪
     */
    private void initProccesor(){
        mProcessor = new WebrtcProcessor();
        mProcessor.init(frequency);
    }

    /**
     * 释放降噪资源
     */
    private void releaseProcessor(){
        if(mProcessor != null){
            mProcessor.release();
        }
    }

    /**
     * 处理需要降噪的音频数据
     * @param data
     */
    private void processData(byte[] data){
        if(mProcessor != null){
            mProcessor.processNoise(data);
        }
    }

    /**
     * 处理需要降噪的音频数据
     * @param data 
     */
    private void processData(short[] data){
        if(mProcessor != null){
            mProcessor.processNoise(data);
        }
    }

}

以上代码主要是实现一边录音,一边播放录音的声音,类似ktv,在其中对每次获取的录音数据tmpBuf_src交给WebrtcProcessor处理,这里可以读取为byte[]或者short[] 数据,但是交给底层webrtc处理时,都是需要转换为short[] 数据的。然后这边采样率我采用8000,采样编码位16,单声道。

接下来看看WebrtcProcessor.java的处理:

package com.test.jni;

import android.util.Log;

/**
 * 音频降噪处理
 */
public class WebrtcProcessor {

    static {
        try {
            //加载降噪库
            System.loadLibrary("webrtc");
        } catch (UnsatisfiedLinkError e) {
            Log.e("TAG", "Couldn't load lib:   - " + e.getMessage());
        }

    }

    /**
     * 处理降噪
     * @param data
     */
    public void processNoise(byte[] data){

        if(data == null) return;

        int newDataLength = data.length/2;
        if(data.length % 2 == 1){
            newDataLength += 1;
        }

        //此处是将字节数据转换为short数据
        short[] newData = new short[newDataLength];

        for(int i=0; i<newDataLength; i++){
            byte low = 0;
            byte high = 0;

            if(2*i < data.length){
                low = data[2*i];
            }
            if((2*i+1) < data.length){
                high = data[2*i+1];
            }

            newData[i] = (short) (((high << 8) & 0xff00) | (low & 0x00ff));
        }

        // 交给底层处理
        processNoise(newData);

        //处理完之后, 又将short数据转换为字节数据
        for(int i=0; i<newDataLength; i++){
            if(2*i < data.length){
                data[2*i] = (byte) (newData[i] & 0xff);
            }
            if((2*i+1) < data.length){
                data[2*i+1] = (byte) ((newData[i] >> 8) & 0xff);
            }
        }

    }

    /**
     * 初始化降噪设置
     * @param sampleRate 采样率
     * @return 是否初始化成功
     */
    public native boolean init(int sampleRate);

    /**
     * 处理降噪
     * @param data
     * @return
     */
    public native boolean processNoise(short[] data);

    /**
     *  释放降噪资源
     */
    public native void release();

}

此处你可能需要将字节数据转换为short数据,要特别小心,如果不小心转错了,你的音频数据就乱码了,来的后果是,听到的声音基本都是沙沙声,我之前就是在这里踩了坑,底层调试了很久也没解决,后面才意识到可能上层出错了,调试之后发现是这里。正常呢,调试时看short数据时,如果它们的数值不是很大,那应该是没问题的,如果大部分都是4000以上,或者-4000以下的,那很可能是转换的时候出问题了,一般来说数值都是几十,几百的样子。

好了,现在看看底层大概是如何实现的:

#include <jni.h>
#include "audio_ns.h"
#include "noise_suppression.h"

//此处是为了里面的底层方法能被java层识别
extern "C" {

//降噪的实例,句柄
NsHandle* handle = NULL;

//降噪处理
void innerProcess(short in_sample[], short out_sample[], int length){

    int curPosition = 0;

    //此处以160为单位, 依次调用audio_ns_process处理数据,因为这个方法一次只能处理160个short音频数据
    while(curPosition < length){

        audio_ns_process((int) handle, in_sample + curPosition, out_sample + curPosition);

        curPosition += 160;

    }

}

JNIEXPORT jboolean JNICALL
Java_com_test_jni_WebrtcProcessor_init(JNIEnv *env, jobject instance, jint sample_rate) {

    //初始化降噪实例
    handle = (NsHandle *) audio_ns_init(sample_rate);

    return false;
}

JNIEXPORT jboolean JNICALL
Java_com_test_jni_WebrtcProcessor_processNoise(JNIEnv *env, jobject instance, jshortArray sample) {

    if(!handle)
        return false;

    //获取数据长度
    jsize length = env->GetArrayLength(sample);

    //转换为jshort数组
    jshort *sam = env->GetShortArrayElements(sample, 0);

    //将sam的数据全部复制给新的in_sample
    short in_sample[length];
    for(int i=0; i<length; i++){
        in_sample[i] = sam[i];
    }

    //传入in_sample作为需要处理音频数据, 处理之后的数据返回到sam中
    innerProcess(in_sample, sam, length);

    //将sam中的数据,再转换回sample中
    env->ReleaseShortArrayElements(sample, sam, 0);

    return true;
}

JNIEXPORT void JNICALL
Java_com_test_jni_WebrtcProcessor_release(JNIEnv *env, jobject instance) {

    // 释放降噪资源
    if(handle){
        audio_ns_destroy((int) handle);
    }


}

}

上面代码描述的比较清晰了,就是实际上webrtc降噪一次性只处理了80个short数据,在8000采样率中是这样的,意思就是说webrtc每次只能处理10毫秒,0.01秒的数据。那么依次类推,针对44100采样率的数据处理的话,每次能处理的数据长度就应该是441个short数据了,有不同采样率需求的朋友,可以自行修改测试。接下来看看webrtc的降噪是如何初始化和处理的:

#include "audio_ns.h"
#include "noise_suppression.h"

#include <stdio.h>

int audio_ns_init(int sample_rate){

    NsHandle* NS_instance;
    int ret;
    //创建WebRtcNs实例
    if ((ret = WebRtcNs_Create(&NS_instance) )) {
        printf("WebRtcNs_Create failed with error code = %d", ret);
        return ret;
    }

    //初始化WebRtcNs实例,此处需要指定采样,告诉它一次可以处理多少个short音频数据,
    //如果是8000, 则一次可以处理80,如果是44100, 则一次可以处理441个
    //也就是说,一次性可以处理10ms时间的数据
    if ((ret = WebRtcNs_Init(NS_instance, sample_rate) )) {
        printf("WebRtcNs_Init failed with error code = %d", ret);
        return ret;
    }

    //设置降噪的力度,0,1,2, 0最弱,2最强
    if ( ( ret =  WebRtcNs_set_policy(NS_instance, 2) ) ){
        printf("WebRtcNs_set_policy failed with error code = %d", ret);
        return ret;
    }

    return (int)NS_instance;
}


int audio_ns_process(int ns_handle ,  short *src_audio_data ,short *dest_audio_data){
    //get handle
    NsHandle* NS_instance = (NsHandle* )ns_handle;

    //noise suppression
    if(
        //此处这么做,是因为,真正的WebRtcNs_Process,一次只能处理80个shorts音频数据
        WebRtcNs_Process(NS_instance ,src_audio_data ,NULL ,dest_audio_data , NULL) ||
        WebRtcNs_Process(NS_instance ,&src_audio_data[80] ,NULL ,&dest_audio_data[80] , NULL) ){
            printf("WebRtcNs_Process failed with error code = " );
            return -1;
    }

    return 0;
}


void audio_ns_destroy(int ns_handle){
    //释放WebRtcNs资源
    WebRtcNs_Free((NsHandle *) ns_handle);
}

以上是调用真正的webrtc代码处理降噪了,注释也比较详细,大家自己看。

那么真正底层的webrtc处理降噪是怎么样的呢?这个,呵呵,我觉得吧,浅尝则止,这个不是一般能看懂的,我是看不懂,核心大部分是算法,如果不熟悉降噪的算法和各个数据的意义的话,那看着简直是看天书啊。当然啦,大家想看的或者想要源码进行测试的话,我后面会提供项目源码下载的。

你以为这样就完了吗?那好像还不够丰富啊,因此还有一点我想分享给大家的,就是音频处理中,还有最开始我所说过的各种音频处理,绝不仅仅只有降噪,在当前网上开放的android音频处理项目源码如此稀缺的环境中(我想说,真是百度了好久的android音频处理,却找不出几个可以运行测试的android项目源码,实在香菇),该如何进行其它的音频处理呢。幸运的是,webrtc这个项目里,提供了很多音频处理的模块,大家可以去网上把它下载下来,找到对应的模块,比如增益,在webrtc/modules/audio_processing/agc目录下,把里面的文件拷到自己项目中编译,当然可能还会设计到其它目录的文件,找到拷过来,后面应该就可以编译了。至于怎么编译,找到我项目中的CMakeList.txt文件,依葫芦画瓢,替换修改就是了。

可是好像还有一个问题,那就是编译之后我该怎么用啊??这个,才是重点啊!是啊,我当时也是一头雾水。好吧,本着助人为乐的精神(嘿嘿),我就传授一个从不外传的绝技吧(好像好高级,好期待啊),那就是搜索github(程序员都该知道的超牛逼网站),比如我想找webrtc降噪,那么我就搜WebRtcNs_Process, 找到一些合适的项目,看人家是如何调用实现的,这样就可以实现啦。

好了,就说到这里,等着吃饭了。下面是项目下载地址。

http://download.csdn.net/detail/hesong1120/9687830

  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值