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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/926430.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 架构的新冠密接者跟踪系统:安全防护体系深度解读

第3章 系统分析 在进行系统分析之前&#xff0c;需要从网络上或者是图书馆的开发类书籍中收集大量的资料&#xff0c;因为这个环节也是帮助即将开发的程序软件制定一套最优的方案&#xff0c;一旦确定了程序软件需要具备的功能&#xff0c;就意味着接下来的工作和任务都是围绕着…

Opencv+ROS实现颜色识别应用

目录 一、工具 二、原理 概念 本质 三、实践 添加发布话题 主要代码 四、成果 五、总结 一、工具 opencvros ubuntu18.04 摄像头 二、原理 概念 彩色图像&#xff1a;RGB&#xff08;红&#xff0c;绿&#xff0c;蓝&#xff09; HSV图像&#xff1a;H&#xff0…

图解人工智能:从规则到深度学习的全景解析

&#x1f31f;作者简介&#xff1a;热爱数据分析&#xff0c;学习Python、Stata、SPSS等统计语言的小高同学~&#x1f34a;个人主页&#xff1a;小高要坚强的博客&#x1f353;当前专栏&#xff1a;Python之机器学习&#x1f34e;本文内容&#xff1a;图解人工智能&#xff1a;…

【FPGA开发】Vivado自定义封装IP核,绑定总线

支持单个文件的封装、整个工程的封装&#xff0c;这里用单个文件举例。 在文件工程目录下&#xff0c;自建一个文件夹&#xff0c;里面放上需要封装的verilog文件。 选择第三个&#xff0c;指定路径封装&#xff0c;找到文件所在目录 取个名&#xff0c;选择封装IP的路径 会…

fiddler安卓雷电模拟器配置踩坑篇

一、fiddler端配置 和网页版fiddler一样&#xff0c;需要首先再本机安装证书&#xff0c;可以参考我之前的fiddler浏览器配置文章&#xff0c;前期操作一致&#xff1a; 此处需要注意的是connections里面需要勾选allow remote这个选项&#xff0c;这个主要是为了后来再安卓模拟…

六、文本搜索工具(grep)和正则表达式

一、grep工具的使用 1、概念 grep&#xff1a; 是 linux 系统中的一个强大的文本搜索工具&#xff0c;可以按照 正则表达式 搜索文本&#xff0c;并把匹配到的行打印出来&#xff08;匹配到的内容标红&#xff09;。 2、语法 grep [options]…… pattern [file]…… 工作方式…

时序论文28|CycleNet:通过对周期模式进行建模增强时间序列预测

论文标题&#xff1a;CycleNet: Enhancing Time Series Forecasting through Modeling Periodic Patterns 论文链接&#xff1a;https://arxiv.org/abs/2409.18479v1 代码链接&#xff1a;https://github.com/ACAT-SCUT/CycleNet 前言 这是今年NIPS的一篇时序论文&#xff…

LuaForWindows_v5.1.5-52.exe

Releases rjpcomputing/luaforwindows GitHub #lua C:\Users\Administrator\Desktop\test.lua print("Hello lua&#xff01;") print("ZengWenFeng 13805029595")

Spring Boot教程之十一:获取Request 请求 和 Put请求

如何在 Spring Boot 中获取Request Body&#xff1f; Java 语言是所有编程语言中最流行的语言之一。使用 Java 编程语言有几个优点&#xff0c;无论是出于安全目的还是构建大型分发项目。使用 Java 的优点之一是 Java 试图借助类、继承、多态等概念将语言中的每个概念与现实世…

【力扣热题100】[Java版] 刷题笔记-3. 无重复字符的最长子串

题目:3. 无重复字符的最长子串 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 解题思路 根据题目&#xff0c;只需要返回无重复字符串的最长子串的长度&#xff0c;所以我们不需要知道知道字符串内容是什么&#xff0c;在整个字符串 s 中&…

如何监控Elasticsearch集群状态?

大家好&#xff0c;我是锋哥。今天分享关于【如何监控Elasticsearch集群状态&#xff1f;】面试题。希望对大家有帮助&#xff1b; 如何监控Elasticsearch集群状态&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 监控 Elasticsearch 集群的状态对于确保…

Y20030018基于Java+Springboot+mysql+jsp+layui的家政服务系统的设计与实现 源代码 文档

家政服务系统的设计与实现 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 随着人们生活水平的提高&#xff0c;老龄化、少子化等多重因素影响&#xff0c;我国对家政服务人群的需求与日俱增。家政服务行业对我国的就业和社会效益贡献也与日俱增&#…

FreeRTOS——列表及列表项

目录 一、概念及其应用 1.1列表List_t 1.2列表项ListItem_t 1.3迷你列表项MiniListItem_t 二、相关API 三、实现原理 3.1列表初始化 3.2列表项初始化 3.3插入列表项 3.4尾插列表项 3.5列表项的删除 3.6列表的遍历 一、概念及其应用 作为多任务的核心&#xff0c;列…

搭建AD域服务器

搭建AD域服务器 使用深信服HCI搭建AD域服务器 1、新建虚拟机 2、填写参数 3、省略安装过程 4、进入服务器管理器 5、 6、 7、 8、 9、 10、 11、 12、 13、 14、 15、 16、 17、 18、 19、 20、 21、 22、 23、

【iOS】设计模式的六大原则

【iOS】设计模式的六大原则 文章目录 【iOS】设计模式的六大原则前言开闭原则——OCP单一职能原则——SRP里氏替换原则——LSP依赖倒置原则——DLP接口隔离原则——ISP迪米特法则——LoD小结 前言 笔者这段时间看了一下有关于设计模式的七大原则&#xff0c;下面代码示例均为OC…

一个实用的 Maven localRepository 工具

目录 1 现状2 当前解决3 更好的解决3.1 下载 Maven localRepository 工具包3.2 上传本地 localRepository 包3.3 清理 localRepository 中指定后缀的文件 1 现状 在使用 Maven 时&#xff0c;我们可能会经常与本地仓库和私服仓库打交道。 例如对于本地仓库&#xff0c;因为某…

【linux学习指南】详解Linux进程信号保存

文章目录 &#x1f4dd;保存信号&#x1f320; 信号其他相关常⻅概念&#x1f309;在内核中的表⽰ &#x1f320; sigset_t&#x1f320;信号集操作函数&#x1f309;sigprocmask&#x1f309;sigpending &#x1f6a9;总结 &#x1f4dd;保存信号 &#x1f320; 信号其他相关常…

【C++】 算术操作符与数据类型溢出详解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;C 算术操作符详解基本算术操作符整数除法与取模行为类型转换在算术运算中的作用自增与自减操作符 &#x1f4af;数值溢出&#xff1a;当值超出类型范围时数据类型的取值范围…

【继承】—— 我与C++的不解之缘(十九)

前言&#xff1a; 面向对象编程语言的三大特性&#xff1a;封装、继承和多态 本篇博客来学习C中的继承&#xff0c;加油&#xff01; 一、什么是继承&#xff1f; ​ 继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段&#xff0c;它允许我们在保持原有类…

使用redis-plus-plus库连接redis

使用redis-plus-plus库连接redis 一、安装redis-plus-plus1.1安装hiredis1.2编译安装redis-plus-plus 二、redis的连接使用2.1创建redis对象2.2向redis中添加元素2.3判断元素是否存在2.4获取元素2.5设置获取过期时间2.6获取类型2.7 删除当前数据库 一、安装redis-plus-plus C …