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() {
                    @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略显臃肿,但是只是初次尝试操作是否能达到变音的目的,如今可以达成,解耦以及性能方面以后考虑和优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值