1.目标概要
分步骤实现为
1.0 集成使用soundtouch so文件 done
1.1 audiorecord和audiotrack 录音(pcm文件)并播放 done
1.2 把录音后文件转成wav文件 并播放 done
1.3 soundtouch变音后播放 done
2.实现
2.1 集成使用soundtouch so文件
编译和使用so文件见另一条blog
编译和使用so文件 利用cmake
直接使用so文件的话 arm64-v8a使用的soundtouch.so
如图所示,在与java文件夹同级别新建一个jniLibs文件夹,名称需一样就不需要修改build.gradle文件;然后同so文件编译出一致在java文件夹底下新建一个名称一样的包名;
类名,方法名需一致
SoundTouch类代码如下
package net.surina.soundtouch;
import androidx.annotation.FloatRange;
public class SoundTouch {
/**
* 获取SoundTouch版本
* @return
*/
public native final static String getVersionString();
//速率
/**
* 指定节拍,原始值为1.0,大快小慢
* @param handle
* @param tempo
*/
private native final void setTempo(long handle, float tempo);
/**
* 指定播放速率,原始值为1.0,大快小慢
* @param handle
* @param speed
*/
private native final void setSpeed(long handle, float speed);
// 音调:
/**
*在原音调基础上以半音为单位进行调整,取值为[-12.0,+12.0]
* @param handle
* @param pitch
*/
private native final void setPitchSemiTones(long handle,@FloatRange(from = -12.0,to = 12.0) float pitch);
/**
* 指定音调值,原始值为1.0
* @param handle
* @param pitch
*/
private native final void setPitch(long handle,float pitch);
/**
* 在原音调基础上以八度音为单位进行调整,取值为[-1.00,+1.00]
* @param handle
* @param pitch
*/
private native final void setPitchOctaves(long handle,@FloatRange(from = -1.0,to = 1.0) float pitch);
/**
* 指定wav源文件和转化的输出文件
* @param handle
* @param inputFile
* @param outputFile
* @return
*/
private native final int processFile(long handle, String inputFile, String outputFile);
/**
* 错误信息打印
* @return
*/
public native final static String getErrorString();
/**
* 实例化SoundTouch对象
* @return
*/
private native final static long newInstance();
/**
* 销毁SoundTouch对象
* @param handle
*/
private native final void deleteInstance(long handle);
long handle = 0;
public SoundTouch()
{
handle = newInstance();
}
public void close()
{
deleteInstance(handle);
handle = 0;
}
public void setTempo(float tempo)
{
setTempo(handle, tempo);
}
public void setPitchSemiTones(float pitch)
{
setPitchSemiTones(handle, pitch);
}
public void setSpeed(float speed)
{
setSpeed(handle, speed);
}
public int processFile(String inputFile, String outputFile)
{
return processFile(handle, inputFile, outputFile);
}
static
{
System.loadLibrary("soundtouch");
}
}
System.loadLibrary("soundtouch") library名字和导入的so文件对应去掉“lib”
可以在MainActivity中调用测试效果 是否成功
SoundTouch soundTouch=new SoundTouch();
Log.d("soundtouch version",soundTouch.getVersionString());
2.2 audiorecord和audiotrack 录音(pcm文件)
Audiorecord使用时候需要 采样频率+ 采样位数 +声道数 +声音源 +缓冲区大小
采样率一般8000或者16000 通道数为1
AcousticEchoCanceler 可以拿到audiorecord和audiotrack的AudioSessionId进行回声消除
AudioTrack有MODE_STATIC和MODE_STREAM ;STATIC一开始构建的时候,就写入buffer区,直接传给audiotrack;STREAM需要把数据通过write方法一次次写入audiotrack里面;static可以直接wav文件播放
2.2.1 audiorecord
初始化--计算缓冲区大小
buffersize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
audioRecord=new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, buffersize);
private static int frequency= 44100;
private static int channelConfiguration = AudioFormat.CHANNEL_IN_STEREO;
private static int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
开始录音
//初始化需要把isRecording=true
public void record(){
new Thread(
new Runnable() {
@Override
public void run() {
try {
audioRecord.startRecording();//开始录音
startrecording();//开线程写数据
}catch (IOException e){
e.printStackTrace();
}
}
}
).start();
}
public void startrecording()throws IOException{
//录音文件存储路径
final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
if (!file.mkdirs()) {
Log.e("OneStateActivity", "Directory not created");
}
if (file.exists()) {
file.delete();
}
FileOutputStream fos=null;
try {
fos = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
final byte data[] = new byte[buffersize];
if (null != fos) {
while (isRecording) {
int read = audioRecord.read(data, 0, buffersize);
// 如果读取音频数据没有出现错误,就将数据写入到文件
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
fos.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
Log.i("OneStateActivity", "run: close file output stream !");
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
停止录音,释放audiorecord
//需要把isRecording=false
public void stoprecord(){
if (null != audioRecord) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
}
2.2.2 audiotrack
//播放pcm文件
/**
* AudioTrack播放以stream形式--
* 通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似
* 但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时
*/
public void playInModeStream() {
/*
* SAMPLE_RATE_INHZ 对应pcm音频的采样率
* channelConfig 对应pcm音频的声道
* AUDIO_FORMAT 对应pcm音频的格式
* */
final int minBufferSize = AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(frequency)
.setEncoding(audioEncoding)
.setChannelMask(channelConfiguration)
.build(),
minBufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.play();
File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
try {
fileInputStream = new FileInputStream(file);
new Thread(new Runnable() {
@Override
public void run() {
try {
//开线程播放录音
byte[] tempBuffer = new byte[minBufferSize];
while (fileInputStream.available() > 0) {
int readCount = fileInputStream.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
if (readCount != 0 && readCount != -1) {
audioTrack.write(tempBuffer, 0, readCount);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
static模式播放
/**
* 播放,使用static模式
* 如果采用STATIC模式,须先调用write写数据,然后再调用play
*/
private void playInModeStatic(String filename) {
// static模式,需要将音频数据一次性write到AudioTrack的内部缓冲区
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
// 读取wav数据
File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), filename);
Log.d("file path",file.getAbsolutePath());
InputStream in = new FileInputStream(file);
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int b; (b = in.read()) != -1; ) {
out.write(b);
}
audioData = out.toByteArray();
} finally {
in.close();
}
} catch (IOException e) {
}
return null;
}
@Override
protected void onPostExecute(Void v) {
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(frequency)
.setEncoding(audioEncoding)
.setChannelMask(channelConfiguration)
.build(),
audioData.length,
AudioTrack.MODE_STATIC,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.write(audioData, 0, audioData.length);
audioTrack.play();
}
}.execute();
}
2.2.3 把录音后文件转成wav文件 并播放
pcm转成wav工具类
public class PcmToWavUtil {
/**
* 缓存的音频大小
*/
private int mBufferSize;
/**
* 采样率
*/
private int mSampleRate;
/**
* 声道数
*/
private int mChannel;
/**
* @param sampleRate sample rate、采样率
* @param channel channel、声道
* @param encoding Audio data format、音频格式
*/
public PcmToWavUtil(int sampleRate, int channel, int encoding) {
this.mSampleRate = sampleRate;
this.mChannel = channel;
this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
}
/**
* pcm文件转wav文件
*
* @param inFilename 源文件路径
* @param outFilename 目标文件路径
*/
public void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;
long totalDataLen;
long longSampleRate = mSampleRate;
int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
long byteRate = 16 * mSampleRate * channels / 8;
byte[] data = new byte[mBufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加入wav文件头
*/
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
// WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// format = 1
header[20] = 1;
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// block align
header[32] = (byte) (2 * 16 / 8);
header[33] = 0;
// bits per sample
header[34] = 16;
header[35] = 0;
//data
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}
播放wav文件,调用static方法 把文件名和路径传入
// 播放wav
playInModeStatic("/test.wav");
2.2.4 soundtouch变音后播放
SoundTouch soundTouch=new SoundTouch();
Log.d("soundtouch version",soundTouch.getVersionString());
float pitch=(float)-5.0;
//从-12.0到12.0之间取值;改变音调 实现变音 不改变速度
soundTouch.setPitchSemiTones(pitch);
Log.d("file path",getExternalFilesDir(Environment.DIRECTORY_MUSIC).toString());
String inurl=getExternalFilesDir(Environment.DIRECTORY_MUSIC)+"/test.wav";
String outurl=getExternalFilesDir(Environment.DIRECTORY_MUSIC)+"/change.wav";
soundTouch.processFile(inurl,outurl);
Log.d("file path",outurl);
playInModeStatic("/change.wav");
尝试后成功播放,将手机连接到windows访问其中data文件夹,获取下载变音后的文件
3.代码
3.1 权限申请工具类
public class PermissionUtils {
private static PermissionUtils permissionUtils;
private final int mrequestCode = 100;//请求码
private OnPermissionCallbackListener mOnListener;
public interface OnPermissionCallbackListener {
void onGrated();
void onDenied(List<String> deniedPermissionList);
}
private PermissionUtils() {
}
public static PermissionUtils getInstance() {
if (permissionUtils == null) {
synchronized (PermissionUtils.class) {
if (permissionUtils == null) {
permissionUtils = new PermissionUtils();
}
}
}
return permissionUtils;
}
public void onRequesetPermissions(Activity context, String[] permissions, OnPermissionCallbackListener listener) {
mOnListener = listener;
//判断手机版本 6.0以上
List<String> mperimissionList = new ArrayList<>();
if (Build.VERSION.SDK_INT >= 23) {
for (int seu = 0; seu < permissions.length; seu++) {
int result = ContextCompat.checkSelfPermission(context, permissions[seu]);
if (result != PackageManager.PERMISSION_GRANTED) {
mperimissionList.add(permissions[seu]);
}
}
if (!mperimissionList.isEmpty()) {
String[] permission_arr = mperimissionList.toArray(new String[mperimissionList.size()]);
ActivityCompat.requestPermissions(context, permission_arr, mrequestCode);
} else {
//权限都通过了-回调出去
mOnListener.onGrated();
}
}
}
public void onRequestPerResult(Activity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
if (requestCode==mrequestCode) {
List<String> deniedPermissions=new ArrayList<>();
if (grantResults.length>0) {
for(int i=0;i<grantResults.length;i++){
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i]);
}
}
}
if (grantResults.length==0) {
mOnListener.onGrated();
}else{
mOnListener.onDenied(deniedPermissions);
}
}else{
mOnListener.onGrated();
}
}
//提示用户手动开启权限在应用设置页面
public void showDialogTipUserGotoAppSetting(){
}
}
3.2 pcm转wav工具类
public class PcmToWavUtil {
/**
* 缓存的音频大小
*/
private int mBufferSize;
/**
* 采样率
*/
private int mSampleRate;
/**
* 声道数
*/
private int mChannel;
/**
* @param sampleRate sample rate、采样率
* @param channel channel、声道
* @param encoding Audio data format、音频格式
*/
public PcmToWavUtil(int sampleRate, int channel, int encoding) {
this.mSampleRate = sampleRate;
this.mChannel = channel;
this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
}
/**
* pcm文件转wav文件
*
* @param inFilename 源文件路径
* @param outFilename 目标文件路径
*/
public void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;
long totalDataLen;
long longSampleRate = mSampleRate;
int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
long byteRate = 16 * mSampleRate * channels / 8;
byte[] data = new byte[mBufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加入wav文件头
*/
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
// WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// format = 1
header[20] = 1;
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// block align
header[32] = (byte) (2 * 16 / 8);
header[33] = 0;
// bits per sample
header[34] = 16;
header[35] = 0;
//data
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}
3.2 Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.WithNewest"
tools:targetApi="31">
<activity
android:name=".OneStateActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3.3 Activity
public class OneStateActivity extends AppCompatActivity {
private AudioRecord audioRecord;
private Button btnrecord;
private Button btnstop;
private Button btnplay_ordinary;
private Button btntransfer_wav;
private Button btnplay_wav;
private Button btn_playfast;
private LinearLayout ll;
private boolean isRecording;
private static int frequency= 44100;
private static int channelConfiguration = AudioFormat.CHANNEL_IN_STEREO;
private static int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
private int buffersize;
private AudioTrack audioTrack;
private FileInputStream fileInputStream;
private byte[] audioData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPermission();
initData();
setContentView(ll);
}
public void initData(){
buffersize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
if(null==audioRecord){
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
audioRecord=new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, buffersize);
}
btnrecord=new Button(this); btnstop=new Button(this);
btnplay_ordinary=new Button(this);
btntransfer_wav =new Button(this);
btnplay_wav =new Button(this);
btn_playfast =new Button(this);
ll=new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
btnstop.setText("stop ");
btnrecord.setText("record ");
btnplay_ordinary.setText("play ordinary pcm file");
btntransfer_wav.setText("transfer to wav file");
btnplay_wav.setText("play ordinary wav file");
btn_playfast.setText("change vocal");
ll.addView(btnrecord);
ll.addView(btnstop);
ll.addView(btnplay_ordinary);
ll.addView(btntransfer_wav);
ll.addView(btnplay_wav);
ll.addView(btn_playfast);
isRecording=true;
//开始录制
btnrecord.setOnClickListener(v->{
record();
});
//停止录制
btnstop.setOnClickListener(v->{
isRecording=false;
stoprecord();
});
//原速播放pcm文件
btnplay_ordinary.setOnClickListener(v->{
// 播放pcm
playInModeStream();
});
//转换成wav文件
btntransfer_wav.setOnClickListener(v->{
PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(frequency, channelConfiguration, audioEncoding);
File pcmFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
File wavFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.wav");
if (!wavFile.mkdirs()) {
Log.e("MainActivity", "wavFile Directory not created");
}
if (wavFile.exists()) {
wavFile.delete();
}
pcmToWavUtil.pcmToWav(pcmFile.getAbsolutePath(), wavFile.getAbsolutePath());
});
//播放转换后的wav文件
btnplay_wav.setOnClickListener(v->{
// 播放wav
playInModeStatic("/test.wav");
});
//利用soundtouch改变音调
btn_playfast.setOnClickListener(v->{
changepitch();
});
}
public void changepitch(){
SoundTouch soundTouch=new SoundTouch();
Log.d("soundtouch version",soundTouch.getVersionString());
float pitch=(float)-5.0;
soundTouch.setPitchSemiTones(pitch);
Log.d("file path",getExternalFilesDir(Environment.DIRECTORY_MUSIC).toString());
String inurl=getExternalFilesDir(Environment.DIRECTORY_MUSIC)+"/test.wav";
String outurl=getExternalFilesDir(Environment.DIRECTORY_MUSIC)+"/change.wav";
soundTouch.processFile(inurl,outurl);
Log.d("file path",outurl);
playInModeStatic("/change.wav");
}
public void getPermission(){
//获取权限
PermissionUtils permissionUtils=PermissionUtils.getInstance();
String[] permission={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
PermissionUtils.OnPermissionCallbackListener mlistener=new PermissionUtils.OnPermissionCallbackListener() {
@Override
public void onGrated() {
}
@Override
public void onDenied(List<String> deniedPermissionList) {
}
};
permissionUtils.onRequesetPermissions(this,permission,mlistener);
}
public void record(){
new Thread(
new Runnable() {
@Override
public void run() {
try {
audioRecord.startRecording();//开始录音
startrecording();//开线程写数据
}catch (IOException e){
e.printStackTrace();
}
}
}
).start();
}
public void startrecording()throws IOException{
//录音文件存储路径
final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
if (!file.mkdirs()) {
Log.e("OneStateActivity", "Directory not created");
}
if (file.exists()) {
file.delete();
}
FileOutputStream fos=null;
try {
fos = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
final byte data[] = new byte[buffersize];
if (null != fos) {
while (isRecording) {
int read = audioRecord.read(data, 0, buffersize);
// 如果读取音频数据没有出现错误,就将数据写入到文件
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
fos.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
Log.i("OneStateActivity", "run: close file output stream !");
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void stoprecord(){
if (null != audioRecord) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
}
/**
* AudioTrack播放以stream形式--
* 通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似
* 但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时
*/
public void playInModeStream() {
/*
* SAMPLE_RATE_INHZ 对应pcm音频的采样率
* channelConfig 对应pcm音频的声道
* AUDIO_FORMAT 对应pcm音频的格式
* */
final int minBufferSize = AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(frequency)
.setEncoding(audioEncoding)
.setChannelMask(channelConfiguration)
.build(),
minBufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.play();
File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
try {
fileInputStream = new FileInputStream(file);
new Thread(new Runnable() {
@Override
public void run() {
try {
byte[] tempBuffer = new byte[minBufferSize];
while (fileInputStream.available() > 0) {
int readCount = fileInputStream.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
if (readCount != 0 && readCount != -1) {
audioTrack.write(tempBuffer, 0, readCount);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 播放,使用static模式
* 如果采用STATIC模式,须先调用write写数据,然后再调用play
*/
private void playInModeStatic(String filename) {
// static模式,需要将音频数据一次性write到AudioTrack的内部缓冲区
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
// 读取wav数据
File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), filename);
Log.d("file path",file.getAbsolutePath());
InputStream in = new FileInputStream(file);
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int b; (b = in.read()) != -1; ) {
out.write(b);
}
audioData = out.toByteArray();
} finally {
in.close();
}
} catch (IOException e) {
}
return null;
}
@Override
protected void onPostExecute(Void v) {
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(frequency)
.setEncoding(audioEncoding)
.setChannelMask(channelConfiguration)
.build(),
audioData.length,
AudioTrack.MODE_STATIC,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.write(audioData, 0, audioData.length);
audioTrack.play();
}
}.execute();
}
}
没用解耦的操作,所以Activity略显臃肿,但是只是初次尝试操作是否能达到变音的目的,如今可以达成,解耦以及性能方面以后考虑和优化