android音频降噪webrtc

http://www.itwendao.com/article/detail/81229.html
转自
android音频降噪webrtc
移动开发 来源:hesong1120 2016-11-20 22:27 8℃ 0评论
在音频处理的开源项目中,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);
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
以上代码主要是实现一边录音,一边播放录音的声音,类似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();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
此处你可能需要将字节数据转换为short数据,要特别小心,如果不小心转错了,你的音频数据就乱码了,来的后果是,听到的声音基本都是沙沙声,我之前就是在这里踩了坑,底层调试了很久也没解决,后面才意识到可能上层出错了,调试之后发现是这里。正常呢,调试时看short数据时,如果它们的数值不是很大,那应该是没问题的,如果大部分都是4000以上,或者-4000以下的,那很可能是转换的时候出问题了,一般来说数值都是几十,几百的样子。

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

include

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

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
上面代码描述的比较清晰了,就是实际上webrtc降噪一次性只处理了80个short数据,在8000采样率中是这样的,意思就是说webrtc每次只能处理10毫秒,0.01秒的数据。那么依次类推,针对44100采样率的数据处理的话,每次能处理的数据长度就应该是441个short数据了,有不同采样率需求的朋友,可以自行修改测试。接下来看看webrtc的降噪是如何初始化和处理的:

include “audio_ns.h”

include “noise_suppression.h”

include

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值