您的位置:首页 > 移动开发 > Android开发

android音频降噪webrtc

2017-02-14 18:01 1651 查看
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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: