在音频处理的开源项目中,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;@Overridepublic 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() {@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {float vol = (float) (seekBar.getProgress()) / (float) (seekBar.getMax());audioTrack.setStereoVolume(vol, vol);//设置音量}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}});((CheckBox) findViewById(R.id.cb_ap)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(CompoundButton view, boolean checked) {isProcessing = checked;}});initProccesor();}@Overrideprotected void onDestroy() {releaseProcessor();android.os.Process.killProcess(android.os.Process.myPid());super.onDestroy();}@Overridepublic 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_sampleshort 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 handleNsHandle* NS_instance = (NsHandle* )ns_handle;//noise suppressionif(//此处这么做,是因为,真正的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

android音频降噪webrtc相关推荐

  1. Android 音频降噪 webrtc 去回声

    Android 音频降噪 webrtc 去回声 集成AECM模块 集成NS模块 需要源码请留言 集成AECM模块 1.通过 webrtc官网下载需要模块\modules\audio_processin ...

  2. java webrtc降噪_android音频降噪webrtc

    [实例简介] android音频降噪,采用webrtc技术,提供底层源码和so库,亲测效果不错,请戴上耳机测试效果. [实例截图] [核心代码] WebrtcNoise ├── __MACOSX │  ...

  3. java webrtc ns降噪_单独编译和使用webrtc音频降噪模块(附完整源码+测试音频文件)...

    webrtc的音频处理模块分为降噪ns,回音消除aec,回声控制acem,音频增益agc,静音检测部分.另外webrtc已经封装好了一套音频处理模块APM,如果不是有特殊必要,使用者如果要用到回声消除 ...

  4. 单独编译和使用webrtc音频降噪模块(NS)

    原文转载于:http://www.cnblogs.com/mod109/p/5469799.html   非常感谢. webrtc的音频处理模块分为降噪ns,回音消除aec(回声控制acem),自动控 ...

  5. android降噪算法,面向Android设备的音频降噪系统设计

    摘要: 在语音信号采集过程中,由于系统工作环境及电路设计等原因,采集到的语音信号中无可避免地引入了噪声信号,降低了语音的信噪比.由于Android2.3.1原生音频系统未提供针对语音信号进行降噪处理的 ...

  6. 单独编译和使用webrtc音频降噪模块(附完整源码+测试音频文件)

    单独编译和使用webrtc音频增益模块(附完整源码+测试音频文件) 单独编译和使用webrtc音频回声消除模块(附完整源码+测试音频文件) webrtc的音频处理模块分为降噪ns,回音消除aec,回声 ...

  7. Android 音频开发(一) 基础入门篇

    今天主要讲解下Android音频开发的入门知识,希望对想入门却不知如何下手的朋友有所帮助,同时希望能得到高手的指点和帮助. 深入细化基础技能知识点 大致细化如下10个知识点. 音频开发的主要应用有哪些 ...

  8. 一次搞懂 Android 音频开发

    在接触Android音频开发后,陆陆续续的看了不少的文章,如果说查缺补漏把这些文章梳理清楚,然后逐个整合,那么确实也能完整的推导出音频开发需要掌握的技术.但是对于初学者来说,可能在开发中产生很多障碍以 ...

  9. 音频降噪算法 附完整C代码

    本文转载自博客:https://cloud.tencent.com/developer/article/1117226 降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言 ...

最新文章

  1. 误删除的文件夹还能恢复吗?
  2. Docker学习笔记(4) — 开启Docker远程访问
  3. 显示并查询纯真官方最新IP地址的免费代码
  4. 安装keras and theano于google colab上
  5. php中html富文本编辑器,php + wangEditor 富文本编辑器的配置
  6. mybatis支持驼峰自动转换sql吗_四、SpringBoot整合mybatis——配置mybatis驼峰命名规则自动转换...
  7. 29 _react-router说明
  8. 在objective-C中#pragma mark是什么意思?
  9. signal.h-----信号函数
  10. camel Java to xml_java – 当Camel从XML文件加载路由时,在注册表中找不到Bean
  11. 命运歌姬服务器停服维护中,命运歌姬3月4日更新什么?命运歌姬3月4日更新维护公告[多图]...
  12. Linux复制指定目录及子目录下特定类型的文件
  13. POJ1273 裸裸的网络流
  14. [No00005A]word多文档合一
  15. 「leetcode」C++题解:226.翻转二叉树,递归法与迭代法详解
  16. jsoncpp用法简述
  17. 无线通信与编码_新型OFDM波形集_使用MATLAB仿真实现UFMC并与OFDM作对比_含实现代码
  18. 发红包的程序代码java_Java实现微信发红包
  19. win7搜索文件 服务器,win7系统搜索不到文件的解决方法
  20. 调css p 段落间距,CSS段落第一个文字空两格缩进text-indent 和 文字之间间距调整letter-spacing...

热门文章

  1. 树莓派自己加内存条_拔掉MacBookPro,用8GB树莓派4工作一天,体验原来是这样的...
  2. 【从零开始学深度学习编译器】十三,如何在MLIR里面写Pass?
  3. C++之vector<int> nums
  4. 月星当空,月光洒落树梢透进窗户,白的苦楚,寥寂随之上心头
  5. 178685-33-1,Azide-PEG3-Tos叠氮化物(N3)基团通过点击化学与炔烃、BCN、DBCO反应
  6. Linux工控主板的多通道高速并行采集方案(基于libiio)
  7. 《深入理解Android 卷III》第八章深入理解Android壁纸
  8. 教育公司邮箱申请哪个好?
  9. 唯品会导航栏简单制作
  10. Oracle EBS 常用表 查询语句