安卓錄音和播放

安卓界面刷新24幀/s 每一幀是16ms 主線程16ms的執(zhí)行限制 主線耗時操作導致16ms執(zhí)行不完 導致卡頓問題

文件模式開啟錄音耗時20-30ms 定制錄音耗時30-50ms
字節(jié)流需要循環(huán)讀寫數(shù)據(jù) 必須再后臺線程

主線程和后臺線程狀態(tài)同步

后臺線程再循環(huán)中讀狀態(tài)值,主線程改變狀態(tài)值讓后臺線程退出。
不需要synchronized 互斥訪問
需要volatile保證主線程的修改后臺線程可見

避免錄音JNI函數(shù)閃退

JNI函數(shù)不能多線程調(diào)用
MediaRecorder : perpare() start() stop() reset() release()
AudioRecord : startRecording() read() stop() release()
以上都屬于JNI函數(shù)

語音參數(shù)

文件模式

            //配置 MediaRecorder
            //從麥克風采集
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            //保存文件為mp4格式
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            //采樣頻率
            mediaRecorder.setAudioSamplingRate(44100);
            //通用的AAC編碼格式
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            //音質(zhì)比較好的編碼頻率
            mediaRecorder.setAudioEncodingBitRate(96000);
            //設(shè)置錄音文件的位置
            mediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());

setAudioSource
1.麥克風  MediaRecorder.AudioSource.MIC
2.語音識別 VOICE_RECOGNITION  
3.語音通話 VOICE_COMMUNICATION   如果系統(tǒng)支持 回音消除噪音抑制

setOutputFormat /setAudioEncoder
1.文件容器 MediaRecorder.OutputFormat.MPEG_4  文件頭信息
2.聲音編碼 MediaRecorder.AudioEncoder.AAC 里面的數(shù)據(jù)編碼格式

setAudioSamplingRate
1.說話聲音是模擬信號 需要采樣為數(shù)字信號(01)
2.采樣頻率越高(密集) 數(shù)據(jù)越大 音質(zhì)越好
3.常用頻率 8kHz 11.025kHz  22.05kHz  16kHz 37.8kHz  44.1kHz 48kHz 96kHz 192kHz 其中44.1安卓手機都支持

setAudioEncodingBitRate
1.聲音編碼,碼率越大,壓縮越小,音質(zhì)越好
2.AAC HE(High Efficiency) : 32kbps~96kbps 碼率越低 帶寬越小 音質(zhì)一般
3.AAC LC(Low Complexity): 96kbps~192kbps 平衡低碼率 高音質(zhì)

字節(jié)流模式

  /**
             * 配置AudioRecord
             */
            int audioSource = MediaRecorder.AudioSource.MIC;
            int sampleRate = 44100;
            //單聲道輸入
            int channelConfig = AudioFormat.CHANNEL_IN_MONO;
            //PCM 16 是所有安卓支持
            int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
            //獲取緩沖區(qū) 計算Audiorecord 內(nèi)部buffer最小大小
            int minBufferSize = AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat);

            //創(chuàng)建AudioRecord對象  buffer 不能小于最低要求 也不能小于我們每次讀取的大小
            mAudioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat
                    , Math.max(minBufferSize, BUFFER_SIZE));


channelConfig
1.音頻的采集和播放可以疊加
2.同時從多個音頻源采集,分別輸出到不同的揚聲器
3.單聲道(Mono)和雙聲道(Stereo)比較常見

audioFormat
1.量化精度:原始PCM數(shù)據(jù),每個采樣點的數(shù)據(jù)大小
2.4bit ,8bit,16bit,32bit... 位數(shù)越多,音質(zhì)越好,數(shù)據(jù)越大
3.常用16bit 兼容所有安卓手機

聲音播放

同樣需要注意線程切換問題,狀態(tài)值的原子性volatile。還有JNI函數(shù)調(diào)用對異常的處理。

文件模式

設(shè)置聲音文件
mMediaPlayer.setDataSource(audioFile.getAbsolutePath())
配置音量 是否循環(huán)
mMediaPlayer.setVolume(1,1); 0~1 的范圍
mMediaPlayer.setLooping(false); 支持循環(huán)播放
準備,開始
mMediaPlayer.prepare();
mMediaPlayer.start();

字節(jié)流模式

音樂類型 揚聲器播放
int streamType = AudioManager.STREAM_MUSIC
錄音時采用的采樣頻率,所以播放時使用同樣的采樣
int sampleRate = 44100
MONO表示單聲道,錄音輸入單聲道,播放用的時候保持一致
int channelConfig = Audioformat.CHANNEL_OUT_MONO
錄音時使用16bit數(shù)據(jù)位寬 所以播放的時候使用同樣的格式
int audioFormat = AudioFormat.ENCODING_PCM_16BIT
流模式
int mode =AudioTrack.MODE_STREAM

模式mode 就是java和native層數(shù)據(jù)傳輸模式
流模式:AudioTrack.MODE_STREAM
用流的形式一直從java層write寫入native層
靜態(tài)模式:AudioTrack.MODE_STATIC
在調(diào)用play之前一次性把數(shù)據(jù)寫到native層

文件模式和字節(jié)流模式的主要代碼
文件模式

package com.tencent.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FileActivity extends AppCompatActivity {

    private static final String TAG = FileActivity.class.getSimpleName();
    private ExecutorService mExecutorService ;
    private MediaRecorder mediaRecorder;
    private File mAudioFile;
    private long mStartRecordTime , mStopRecordTime;
    private Handler mMainHandler ;
    private TextView tv_file;
    private Button btn_speech;
    private Button btn_play;
    //主線程和后臺播放線程數(shù)據(jù)同步  使用volatile
    private volatile boolean mIsPlaying;

    private MediaPlayer mMediaPlayer;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file);

        init();
    }

    @SuppressLint("ClickableViewAccessibility")
    private void init() {

        //主線程處理ui任務(wù)
        mMainHandler = new Handler(Looper.getMainLooper());


        //錄音jni 函數(shù) 不是線程安全的 所以使用單線程處理任務(wù)
        mExecutorService = Executors.newSingleThreadExecutor();

        tv_file = findViewById(R.id.tv_file);

        btn_speech = findViewById(R.id.btn_speech);

        btn_play = findViewById(R.id.btn_play);

        tv_file.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

        btn_play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //檢查當前狀態(tài)防止重復播放
                if (mAudioFile != null && !mIsPlaying) {
                    //設(shè)置當前播放狀態(tài)
                    mIsPlaying = true;

                    //提交后臺任務(wù) 開始播放
                    mExecutorService.submit(new Runnable() {
                        @Override
                        public void run() {
                            doPlay(mAudioFile);
                        }
                    });
                }

            }
        });

//        btn_speech.setOnClickListener(new View.OnClickListener() {
//            @Override
//            public void onClick(View v) {
//
//            }
//        });

        btn_speech.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        startRecord();
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        stopRecord();
                        break;
                    default:break;
                }

                return false;
            }
        });
    }



    /**
     * 停止執(zhí)行錄音邏輯
     */

    private void stopRecord() {
        //修改ui狀態(tài)

        //提交后臺任務(wù)
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                //執(zhí)行停止錄音邏輯 如果失敗
                if (!doStop()){
                    recordFail();
                }

                //釋放recorder
                releaseRecorder();
            }
        });
    }


    /**
     * 停止執(zhí)行錄音 執(zhí)行失敗返回false
     * @return
     */
    private boolean doStop() {


        try {
            //停止錄音
            mediaRecorder.stop();

            //記錄停止時間,統(tǒng)計時長
            mStopRecordTime = System.currentTimeMillis();


            //只接受指定時長的錄音  這里是超過3秒
            int second = (int) ((mStopRecordTime - mStartRecordTime) / 1000);
            if (second > 3 ){
                //修改ui  在主線程執(zhí)行
                runOnUiThread(new Runnable() {
                    @SuppressLint("SetTextI18n")
                    @Override
                    public void run() {
                        tv_file.setText(tv_file.getText() + "\n錄音成功" + second +"秒");
                    }
                });

//                mMainHandler.post(new Runnable() {
//                    @Override
//                    public void run() {
//
//                    }
//                });

            }


        } catch (RuntimeException e) {
            e.printStackTrace();
            //捕獲異常,避免閃退,返回false 提醒用戶失敗


            return false;
        }


        //停止成功
        return true;
    }

    /**
     * 開始執(zhí)行錄音邏輯
     *
     */
    private void startRecord() {
        //修改ui狀態(tài)


        //提交后臺任務(wù)
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                //釋放之前錄音的  recorder
                releaseRecorder();

                //執(zhí)行錄音邏輯  如果失敗提示用戶
                if (!doStart()){
                    recordFail();
                }
            }
        });
    }


    /**
     * 啟動錄音邏輯
     * 判斷執(zhí)行錄音是否成功 執(zhí)行失敗返回false
     * @return
     */
    private boolean doStart() {

        try {
            //創(chuàng)建MediaRecorder
            mediaRecorder = new MediaRecorder();

            //創(chuàng)建錄音文件
            mAudioFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/AudioDemo/" + System.currentTimeMillis() + ".m4a");
            mAudioFile.getParentFile().mkdirs();
            mAudioFile.createNewFile();
            Log.e(TAG, "doStart: mAudioFile ="+mAudioFile.getAbsolutePath());

            //配置 MediaRecorder
            //從麥克風采集
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            //保存文件為mp4格式
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            //采樣頻率
            mediaRecorder.setAudioSamplingRate(44100);
            //通用的AAC編碼格式
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            //音質(zhì)比較好的編碼頻率
            mediaRecorder.setAudioEncodingBitRate(96000);
            //設(shè)置錄音文件的位置
            mediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());


            //開始錄音
            // prepare  start 都會拋出IllegalStateException 所以catch中添加runtimeException
            mediaRecorder.prepare();
            mediaRecorder.start();

            //記錄開始錄音的時間,用于統(tǒng)計時長

            mStartRecordTime = System.currentTimeMillis();


        } catch (IOException  | RuntimeException e) {
            e.printStackTrace();
            //捕獲異常,避免閃退,返回false 提醒用戶失敗

            return false;
        }
        //啟動成功
        return true;
    }

    /**
     * 錄音錯誤處理
     * 執(zhí)行失敗 提示用戶
     */
    private void recordFail() {
        mAudioFile = null;
        //todo 這里用handler 處理ui任務(wù)好 還是runOnUiThread 好???
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(FileActivity.this, "語音錄制失敗", Toast.LENGTH_SHORT).show();
            }
        });

//        mMainHandler.post(new Runnable() {
//            @Override
//            public void run() {
//                Toast.makeText(FileActivity.this, "語音錄制失敗", Toast.LENGTH_SHORT).show();
//
//            }
//        });

    }

    /**
     * 釋放recorder
     */
    private void releaseRecorder() {
        //檢查MediaRecorder 不為null
        if (mediaRecorder != null){
            mediaRecorder.release();
            mediaRecorder = null;
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        //停止后臺任務(wù) 防止內(nèi)存泄漏
        mExecutorService.shutdownNow();

        releaseRecorder();

        stopPlay();
    }
    /**
     * 實際播放的邏輯
     * @param mAudioFile
     */

    private void doPlay(File mAudioFile) {
        //配置播放器 MediaPlayer
        mMediaPlayer = new MediaPlayer();
        try {

            //設(shè)置聲音文件  告訴播放器播放什么
            mMediaPlayer.setDataSource(mAudioFile.getAbsolutePath());

            //設(shè)置監(jiān)聽回調(diào)  播放問題?

            //完成播放的監(jiān)聽配置
            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    //播放結(jié)束 釋放播放器
                    stopPlay();
                }
            });
            //播放出錯的監(jiān)聽配置
            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
                @Override
                public boolean onError(MediaPlayer mp, int what, int extra) {
                    //提示用戶
                    playFail();
                    //釋放播放器
                    stopPlay();
                    //錯誤已經(jīng)處理返回true
                    return true;
                }
            });

            //配置音量 是否循環(huán)
            mMediaPlayer.setVolume(1,1);
            mMediaPlayer.setLooping(false); //不循環(huán)
            //準備  開始

            mMediaPlayer.prepare();
            mMediaPlayer.start();

        }catch (RuntimeException  | IOException e){
            //異常處理 防止閃退
            e.printStackTrace();
            //提示用戶
            playFail();

            //釋放播放器
            stopPlay();
        }



    }

    /**
     * 提醒用戶播放失敗
     */
    private void playFail() {
        //主線程 toast 提示
        mMainHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(FileActivity.this, "播放失敗", Toast.LENGTH_SHORT).show();
            }
        });

    }

    /**
     * 停止播放的邏輯
     */
    private void stopPlay() {
        //重置播放狀態(tài)
        mIsPlaying = false;
        //釋放播放器
        if (mMediaPlayer != null){
            //重置監(jiān)聽器 防止內(nèi)存泄漏
            mMediaPlayer.setOnCompletionListener( null);
            mMediaPlayer.setOnErrorListener(null);


            mMediaPlayer.stop();
            mMediaPlayer.reset();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }

    }
}

字節(jié)流模式

package com.tencent.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class StreamActivity extends AppCompatActivity {

    private static final String TAG = StreamActivity.class.getSimpleName();
    private TextView tv_stream;
    private Button btn_stream;

    //錄音狀態(tài)  volatile 保證多線程內(nèi)存同步
    private volatile boolean mIsRecording;
    //播放狀態(tài)
    private volatile boolean mIsPlaying;
    private ExecutorService mExecutorService;

    private Handler mMainTheadHandler;
    //buffer 不能太大 避免oom  2k
    private static final int BUFFER_SIZE = 2048;
    //讀取字節(jié)數(shù)據(jù)
    private byte[] mBuffer;
    private File mAudioFile;
    private long mStartRecordTime,mStopRecordTime;
    private FileOutputStream mFileOutputStream;
    private AudioRecord mAudioRecord;
    private Button btn_stream_speech;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_stream);

        init();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mExecutorService.shutdownNow();
    }

    private void init() {

        mBuffer = new byte[BUFFER_SIZE];

        //錄音JNI函數(shù) 不具備線程安全性 所以用單線程
        mExecutorService = Executors.newSingleThreadExecutor();
        mMainTheadHandler = new Handler(Looper.getMainLooper());

        btn_stream = findViewById(R.id.btn_stream);

        tv_stream = findViewById(R.id.tv_stream);

        btn_stream_speech = findViewById(R.id.btn_stream_speech);

        btn_stream.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //根據(jù)當前狀態(tài),改變ui 執(zhí)行開始或者定制錄音
                if(mIsRecording){
                    //改變ui狀態(tài)
                   btn_stream.setText("開始");
                   //改變錄音狀態(tài)
                    mIsRecording = false;

//                    //提交后臺任務(wù),執(zhí)行停止邏輯
//                    mExecutorService.submit(new Runnable() {
//                        @Override
//                        public void run() {
//                            //執(zhí)行開停止錄音邏輯  失敗提示用戶
//                        }
//                    });
                }else{
                    //改變ui狀態(tài)
                    btn_stream.setText("停止");
                    //改變錄音狀態(tài)
                    mIsRecording = true;
                    //提交后臺任務(wù),執(zhí)行錄音邏輯
                    mExecutorService.submit(new Runnable() {
                        @Override
                        public void run() {
                            //執(zhí)行開始錄音邏輯  失敗提示用戶
                            if (!startRecord()){
                                recordFail();
                            }

                        }
                    });
                }

            }
        });

        btn_stream_speech.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //檢查播放狀態(tài) 防止重復播放
                if (mAudioFile != null && !mIsPlaying){
                    //設(shè)置當前為播放狀態(tài)
                    mIsPlaying = true;

                    //在后臺線程提交播放任務(wù)
                    mExecutorService.submit(new Runnable() {
                        @Override
                        public void run() {
                            doPlay(mAudioFile);
                        }
                    });
                }
            }
        });

    }


    /**
     * 播放錄音文件邏輯
     * @param mAudioFile
     */
    private void doPlay(File mAudioFile) {
        //配置播放器
        //音樂播放類型 揚聲器播放
        int streamType = AudioManager.STREAM_MUSIC;
        //錄音時采用的采樣頻率,所以播放的時候使用同樣的采樣頻率
        int sampleRate = 44100;
        //錄音用輸入單聲道  播放用輸出單聲道
        int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
        //錄音的時候使用的16bit 所以播放的時候也要采用同樣的格式
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        //流模式
        int mode = AudioTrack.MODE_STREAM;
        //計算最小 buffer大小
        int minBufferSize =  AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);

        //構(gòu)造AudioTrack
        AudioTrack audioTrack = new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
        //不能小于AudioTrack的最低要求 也不能小于我們每次讀的大小
        Math.max(minBufferSize,BUFFER_SIZE),mode);


        FileInputStream inputStream = null;
        try {
            //從文件流讀數(shù)據(jù)
            inputStream =  new FileInputStream(mAudioFile);
            //循環(huán)讀取數(shù)據(jù) 寫到播放器去播放
            int read;
            while ((read = inputStream.read(mBuffer))> 0){
                int ret = audioTrack.write(mBuffer,0,read);
            //檢查 write返回值 錯誤處理
                switch (ret){
                    case AudioTrack.ERROR_INVALID_OPERATION:
                    case AudioTrack.ERROR_BAD_VALUE:
                    case AudioManager.ERROR_DEAD_OBJECT:
                        playFail();
                        return;
                    default:
                        break;

                }

            }

        }catch (RuntimeException | IOException e){
            e.printStackTrace();

        }finally {
            //重置播放狀態(tài)
            mIsPlaying = false;

            //關(guān)閉文件輸入流

            if (inputStream != null){
                coloseQuietly(inputStream);
            }

            //播放器釋放
            resetQuietly(audioTrack);
        }


        //循環(huán)讀數(shù)據(jù)寫到播放器去播放

        //錯誤處理防止閃退
    }


    //錯誤處理
    private void playFail() {
        mAudioFile = null;

        //toast 提示用戶
        mMainTheadHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(StreamActivity.this, "播放失敗", Toast.LENGTH_SHORT).show();
            }
        });

    }

    private void resetQuietly(AudioTrack audioTrack) {
       try {
           audioTrack.stop();
           audioTrack.release();
       }catch (RuntimeException e){
           e.printStackTrace();
       }
    }

    private void coloseQuietly(FileInputStream inputStream) {
        try {
            inputStream.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    /**
     * 錄音錯誤的處理
     */
    private void recordFail() {
        mMainTheadHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(StreamActivity.this,"錄音失敗",Toast.LENGTH_SHORT).show();
                //重置錄音狀態(tài),修改ui
                mIsRecording = false;
                btn_stream.setText("開始");
            }
        });
    }

    /**
     * 啟動錄音邏輯
     * @return
     */
    private boolean startRecord() {


        try {
            //創(chuàng)建錄音文件
            mAudioFile = new File(getExternalCacheDir().getPath() +"/AudioDemo/" +
                    System.currentTimeMillis() +".pcm");

            mAudioFile.getParentFile().mkdirs();
            mAudioFile.createNewFile();
//            if (!mAudioFile.exists()){
//                boolean mkdirs = mAudioFile.mkdirs();
//                Log.e(TAG, "startRecord: mkdirs="+mkdirs );
//                if (!mkdirs){
//                    try {
//                        throw new IOException("file is not create");
//                    } catch(IOException e){
//                        e.printStackTrace();
//                    }
//
//                }
//            }

            //創(chuàng)建文件輸出流

            mFileOutputStream = new FileOutputStream(mAudioFile);

            /**
             * 配置AudioRecord
             */
            int audioSource = MediaRecorder.AudioSource.MIC;
            int sampleRate = 44100;
            //單聲道輸入
            int channelConfig = AudioFormat.CHANNEL_IN_MONO;
            //PCM 16 是所有安卓支持
            int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
            //獲取緩沖區(qū) 計算Audiorecord 內(nèi)部buffer最小大小
            int minBufferSize = AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat);

            //創(chuàng)建AudioRecord對象  buffer 不能小于最低要求 也不能小于我們每次讀取的大小
            mAudioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat
                    , Math.max(minBufferSize, BUFFER_SIZE));

            //開始錄音
            mAudioRecord.startRecording();
            //記錄開始錄音時間 統(tǒng)計時長

            mStartRecordTime = System.currentTimeMillis();

            //循環(huán)讀取數(shù)據(jù),寫到輸出流中

            while (mIsRecording){
                //只要還在錄音狀態(tài),就一直讀取數(shù)據(jù)
                int read = mAudioRecord.read(mBuffer,0,BUFFER_SIZE);
                if (read > 0){
                    //讀取成功 寫到文件中
                    mFileOutputStream.write(mBuffer,0,read);

                }else{
                    //讀取失敗 返回false 提示用戶

                    return false;
                }


            }


            //退出循環(huán) 通過mIsRecording判斷  停止錄音 釋放資源

            return stopRecord();

        } catch (IOException | RuntimeException e) {
            e.printStackTrace();
            //捕獲異常 避免閃退 返回false 提示用戶
            Log.e(TAG, "startRecord: exception = "+e.toString());
            return false;
        } finally {
            //釋放 AudioRecord資源
            if (mAudioRecord != null){
                mAudioRecord.release();
            }
        }



//        return true;
    }


    /**
     * 結(jié)束錄音邏輯
     * @return
     */
    private boolean stopRecord() {


        try {

            //停止錄音 關(guān)閉文件輸出流
            mAudioRecord.stop();
            mAudioRecord.release();
            mAudioRecord = null;
            mFileOutputStream.close();
            //記錄結(jié)束時間,統(tǒng)計錄音時長
            mStopRecordTime = System.currentTimeMillis();
            //大于3秒成功  改變ui

            int second = (int)((mStopRecordTime - mStartRecordTime) /1000);
            if (second > 3){
                mMainTheadHandler.post(new Runnable() {
                    @SuppressLint("SetTextI18n")
                    @Override
                    public void run() {
                        tv_stream.setText(tv_stream.getText() + "\n錄音成功" + second + " 秒 ");
                    }
                });
            }else {
                mMainTheadHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(StreamActivity.this,"當前錄制時間不足3秒",Toast.LENGTH_SHORT).show();
                    }
                });
            }

        } catch (IOException e) {
            e.printStackTrace();
            //捕獲異常 避免閃退 返回false 提示用戶

            return false;
        }
        return true;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 上一篇實現(xiàn)了Android端文字的傳輸 點擊打開鏈接,由于此系列要實現(xiàn)Android端語音的傳輸,所以這篇就先研究...
    孖彧閱讀 362評論 0 0
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,041評論 2 59
  • 在上篇中,我們學習了音頻的基礎(chǔ)知識以及如何使用AudioRecord采集音頻數(shù)據(jù)。在這篇文章中,我們來學習: 使用...
    kamlin閱讀 2,211評論 0 10
  • 前言 本篇文章屬于 Android NDK 模塊,需要讀者有一點 NDK 相關(guān)的基礎(chǔ)和 C/C++ 基礎(chǔ),不然其中...
    you的日常閱讀 949評論 1 6
  • 本文屬于Android局域網(wǎng)內(nèi)的語音對講項目系列,《通過UDP廣播實現(xiàn)Android局域網(wǎng)Peer Discove...
    yhthu閱讀 19,668評論 9 53

友情鏈接更多精彩內(nèi)容