安卓錄音和播放 文件模式 字節流模式 聲音播放

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

文件模式開啓錄音耗時20-30ms 定製錄音耗時30-50ms
字節流需要循環讀寫數據 必須再後臺線程

主線程和後臺線程狀態同步

後臺線程再循環中讀狀態值,主線程改變狀態值讓後臺線程退出。
不需要synchronized 互斥訪問
需要volatile保證主線程的修改後臺線程可見

避免錄音JNI函數閃退

JNI函數不能多線程調用
MediaRecorder : perpare() start() stop() reset() release()
AudioRecord : startRecording() read() stop() release()
以上都屬於JNI函數

語音參數

文件模式

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

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

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

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

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

字節流模式

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

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


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

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

聲音播放

同樣需要注意線程切換問題,狀態值的原子性volatile。還有JNI函數調用對異常的處理。

文件模式

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

字節流模式

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

模式mode 就是java和native層數據傳輸模式
流模式:AudioTrack.MODE_STREAM
用流的形式一直從java層write寫入native層
靜態模式:AudioTrack.MODE_STATIC
在調用play之前一次性把數據寫到native層

文件模式和字節流模式的主要代碼
文件模式

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;
    //主線程和後臺播放線程數據同步  使用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任務
        mMainHandler = new Handler(Looper.getMainLooper());


        //錄音jni 函數 不是線程安全的 所以使用單線程處理任務
        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) {
                //檢查當前狀態防止重複播放
                if (mAudioFile != null && !mIsPlaying) {
                    //設置當前播放狀態
                    mIsPlaying = true;

                    //提交後臺任務 開始播放
                    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;
            }
        });
    }



    /**
     * 停止執行錄音邏輯
     */

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

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

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


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


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

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


            //只接受指定時長的錄音  這裏是超過3秒
            int second = (int) ((mStopRecordTime - mStartRecordTime) / 1000);
            if (second > 3 ){
                //修改ui  在主線程執行
                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;
    }

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


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

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


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

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

            //創建錄音文件
            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);
            //音質比較好的編碼頻率
            mediaRecorder.setAudioEncodingBitRate(96000);
            //設置錄音文件的位置
            mediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());


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

            //記錄開始錄音的時間,用於統計時長

            mStartRecordTime = System.currentTimeMillis();


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

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

    /**
     * 錄音錯誤處理
     * 執行失敗 提示用戶
     */
    private void recordFail() {
        mAudioFile = null;
        //todo 這裏用handler 處理ui任務好 還是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();
        //停止後臺任務 防止內存泄漏
        mExecutorService.shutdownNow();

        releaseRecorder();

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

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

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

            //設置監聽回調  播放問題?

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

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

            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() {
        //重置播放狀態
        mIsPlaying = false;
        //釋放播放器
        if (mMediaPlayer != null){
            //重置監聽器 防止內存泄漏
            mMediaPlayer.setOnCompletionListener( null);
            mMediaPlayer.setOnErrorListener(null);


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

    }
}

字節流模式

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;

    //錄音狀態  volatile 保證多線程內存同步
    private volatile boolean mIsRecording;
    //播放狀態
    private volatile boolean mIsPlaying;
    private ExecutorService mExecutorService;

    private Handler mMainTheadHandler;
    //buffer 不能太大 避免oom  2k
    private static final int BUFFER_SIZE = 2048;
    //讀取字節數據
    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函數 不具備線程安全性 所以用單線程
        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) {
                //根據當前狀態,改變ui 執行開始或者定製錄音
                if(mIsRecording){
                    //改變ui狀態
                   btn_stream.setText("開始");
                   //改變錄音狀態
                    mIsRecording = false;

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

                        }
                    });
                }

            }
        });

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

                    //在後臺線程提交播放任務
                    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);

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


        FileInputStream inputStream = null;
        try {
            //從文件流讀數據
            inputStream =  new FileInputStream(mAudioFile);
            //循環讀取數據 寫到播放器去播放
            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 {
            //重置播放狀態
            mIsPlaying = false;

            //關閉文件輸入流

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

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


        //循環讀數據寫到播放器去播放

        //錯誤處理防止閃退
    }


    //錯誤處理
    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();
                //重置錄音狀態,修改ui
                mIsRecording = false;
                btn_stream.setText("開始");
            }
        });
    }

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


        try {
            //創建錄音文件
            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();
//                    }
//
//                }
//            }

            //創建文件輸出流

            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;
            //獲取緩衝區 計算Audiorecord 內部buffer最小大小
            int minBufferSize = AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat);

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

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

            mStartRecordTime = System.currentTimeMillis();

            //循環讀取數據,寫到輸出流中

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

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

                    return false;
                }


            }


            //退出循環 通過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;
    }


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


        try {

            //停止錄音 關閉文件輸出流
            mAudioRecord.stop();
            mAudioRecord.release();
            mAudioRecord = null;
            mFileOutputStream.close();
            //記錄結束時間,統計錄音時長
            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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章