欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 文化 > Android Studio使用soundtouch实现变声,AudioRecord,AudioTrack录音和播放,转换pcm文件为wav文件

Android Studio使用soundtouch实现变声,AudioRecord,AudioTrack录音和播放,转换pcm文件为wav文件

2025/2/19 7:56:08 来源:https://blog.csdn.net/weixin_52875110/article/details/144129355  浏览:    关键词:Android Studio使用soundtouch实现变声,AudioRecord,AudioTrack录音和播放,转换pcm文件为wav文件

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() {@Overridepublic 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() {@Overridepublic 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>() {@Overrideprotected 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;}@Overrideprotected 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 headerheader[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);// WAVEheader[8] = 'W';header[9] = 'A';header[10] = 'V';header[11] = 'E';// 'fmt ' chunkheader[12] = 'f';header[13] = 'm';header[14] = 't';header[15] = ' ';// 4 bytes: size of 'fmt ' chunkheader[16] = 16;header[17] = 0;header[18] = 0;header[19] = 0;// format = 1header[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 alignheader[32] = (byte) (2 * 16 / 8);header[33] = 0;// bits per sampleheader[34] = 16;header[35] = 0;//dataheader[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方法 把文件名和路径传入

// 播放wavplayInModeStatic("/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 headerheader[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);// WAVEheader[8] = 'W';header[9] = 'A';header[10] = 'V';header[11] = 'E';// 'fmt ' chunkheader[12] = 'f';header[13] = 'm';header[14] = 't';header[15] = ' ';// 4 bytes: size of 'fmt ' chunkheader[16] = 16;header[17] = 0;header[18] = 0;header[19] = 0;// format = 1header[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 alignheader[32] = (byte) (2 * 16 / 8);header[33] = 0;// bits per sampleheader[34] = 16;header[35] = 0;//dataheader[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"/><applicationandroid: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"><activityandroid: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;@Overrideprotected 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->{// 播放pcmplayInModeStream();});//转换成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->{// 播放wavplayInModeStatic("/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() {@Overridepublic void onGrated() {}@Overridepublic void onDenied(List<String> deniedPermissionList) {}};permissionUtils.onRequesetPermissions(this,permission,mlistener);}public void record(){new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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>() {@Overrideprotected 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;}@Overrideprotected 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略显臃肿,但是只是初次尝试操作是否能达到变音的目的,如今可以达成,解耦以及性能方面以后考虑和优化

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词