Android-文件及字符流方式的语音保存播放功能

来源:互联网 发布:医学专业英语翻译软件 编辑:程序博客网 时间:2024/06/01 09:23

目标效果:

   

录音并播放共两种方式,一种是文件方式,一种是字符流方式,图4为保存的文件,后缀为.m4a的为文件方式的,可以使用系统录音机播放,后缀为.pcm的为字符流方式的,不可以使用系统录音机播放


1.activity_main.xml页面:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.example.vivinia.imspeech.MainActivity">    <Button        android:id="@+id/mBtnFileMode"        android:layout_marginTop="100dp"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:onClick="toFile"        android:text="文件模式" />    <Button        android:id="@+id/mBtnStreamMode"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:onClick="toStream"        android:layout_marginTop="20dp"        android:text="字节流模式"/></LinearLayout>


2.MainActivity.java页面:
package com.example.vivinia.imspeech;import android.content.Intent;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void toFile(View v){        startActivity(new Intent(this,FileActivity.class));    }    public void toStream(View v){        startActivity(new Intent(this,StreamActivity.class));    }}


3.activity_file.xml页面:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.example.vivinia.imspeech.FileActivity">    <Button        android:id="@+id/mBtnPlay"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:textSize="30sp"        android:layout_marginTop="50dp"        android:onClick="btnPlay"        android:text="播放"/>    <TextView        android:id="@+id/mTvLog"        android:layout_marginTop="50dp"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:textSize="30sp"        android:text="123"/>    <TextView        android:id="@+id/mTvPressToSay"        android:layout_marginTop="150sp"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:gravity="center"        android:text="按住说话"        android:textColor="#000000"        android:textSize="30sp"/></LinearLayout>


4.FileActivity.java页面:
package com.example.vivinia.imspeech;import android.media.MediaPlayer;import android.media.MediaRecorder;import android.os.Environment;import android.os.Handler;import android.os.Looper;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;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.ExecutorService;import java.util.concurrent.Executors;public class FileActivity extends AppCompatActivity {    private TextView mTvLog, mTvPressToSay;    private ExecutorService mExecutorService;    private MediaRecorder mMediaRecorder;    private File mAudioFile;    private long mStartRecorderTime, mStopRecorderTime;    private Handler mMainThreadHandler;    //主线程和后台播放线程数据同步    private volatile boolean mIsPlaying;    private MediaPlayer mMediaPlayer;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_file);        mTvLog = (TextView) findViewById(R.id.mTvLog);        mTvPressToSay = (TextView) findViewById(R.id.mTvPressToSay);        //录音JNI函数不具备线程安全性,所以要用单线程        mExecutorService = Executors.newSingleThreadExecutor();    //初始化单线程的后台线程池        mMainThreadHandler = new Handler(Looper.getMainLooper());        //按下说话,释放发送,所以我们不要OnClickListener        mTvPressToSay.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View view, MotionEvent motionEvent) {                //根据不同touch action,执行不同逻辑                switch (motionEvent.getAction()) {                    case MotionEvent.ACTION_DOWN:                        startRecord();                        break;                    case MotionEvent.ACTION_UP:                    case MotionEvent.ACTION_CANCEL:                        stopRecord();                        break;                    default:                        break;                }                //处理了touch事件返回true                return true;            }        });    }    @Override    protected void onDestroy() {        super.onDestroy();        //activity销毁时,停止后台任务,避免内存泄漏        mExecutorService.shutdownNow();        releaseRecorder();        stopPlay();    }    /**     * 开始录音     */    private void startRecord() {        //改变UI状态        mTvPressToSay.setText("正在说话...");        //提交后台任务,执行录音逻辑        mExecutorService.submit(new Runnable() {            @Override            public void run() {                //释放之前录音的MediaRecorder                releaseRecorder();                //执行录音逻辑,如果失败提示用户                if (!doStart()) {                    recordFail();                }            }        });    }    /**     * 启动录音逻辑     *     * @return     */    private boolean doStart() {        try {            //创建MediaRecorder            mMediaRecorder = new MediaRecorder();            //创建录音文件            mAudioFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/IMSpeechDemo/" + System.currentTimeMillis() + ".m4a");  //m4a是MP4格式的声音文件的后缀            mAudioFile.getParentFile().mkdirs();            mAudioFile.createNewFile();            //配置MediaRecorder            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);   //从麦克风采集            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //保存文件为MP4格式            mMediaRecorder.setAudioSamplingRate(44100);  //所有安卓系统都支持的采样频率            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);  //通用的AAC编码格式            mMediaRecorder.setAudioEncodingBitRate(96000);  //音质比较好的频率            mMediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());   //设置录音文件位置            //开始录音            mMediaRecorder.prepare();  //准备            mMediaRecorder.start();            //记录开始录音时间,用于统计时长            mStartRecorderTime = System.currentTimeMillis();        } catch (IOException | RuntimeException e) {            e.printStackTrace();            //捕获异常,避免闪退,返回false提醒用户失败            return false;        }        //录音成功        return true;    }    /**     * 停止录音逻辑     *     * @return     */    private boolean doStop() {        try {            //停止录音            mMediaRecorder.stop();            //记录停止时间,统计时长            mStopRecorderTime = System.currentTimeMillis();            //只接受超过3秒录音            final int second = (int) ((mStopRecorderTime - mStartRecorderTime) / 1000);            if (second > 3) {                //在主线程改UI,显示出来                mMainThreadHandler.post(new Runnable() {                    @Override                    public void run() {                        mTvLog.setText("\n录音成功" + second + "秒");                    }                });            }        } catch (RuntimeException e) {            e.printStackTrace();            //捕获异常,避免闪退,返回false提醒用户失败            return false;        }        //停止成功        return true;    }    /**     * 录音错误处理     */    private void recordFail() {        mAudioFile = null;        //给用户toast提示失败,主要在主线程执行        mMainThreadHandler.post(new Runnable() {            @Override            public void run() {                Toast.makeText(FileActivity.this, "录音失败", Toast.LENGTH_SHORT).show();            }        });    }    /**     * 释放MediaRecorder     */    private void releaseRecorder() {        //检查MediaRecorder不为null        if (mMediaRecorder != null) {            mMediaRecorder.release();            mMediaRecorder = null;        }    }    /**     * 停止录音     */    private void stopRecord() {        //改变UI状态        mTvPressToSay.setText("按住说话");        //提交后台任务,执行停止逻辑        mExecutorService.submit(new Runnable() {            @Override            public void run() {                //执行停止录音逻辑,失败就要提醒用户                if (!doStop()) {                    recordFail();                }                //释放MediaRecorder                releaseRecorder();            }        });    }    public void btnPlay(View v) {        //检查当前状态,防止重复播放        if (mAudioFile != null && !mIsPlaying) {            //设置当前播放状态            mIsPlaying = true;            //提交后台任务,开始播放            mExecutorService.submit(new Runnable() {                @Override                public void run() {                    doPlay(mAudioFile);                }            });        }    }    /**     * 实际播放的逻辑     *     * @param audioFile     */    private void doPlay(File audioFile) {        //配置播放器MediaPlayer        mMediaPlayer = new MediaPlayer();        try {            //设置声音文件            mMediaPlayer.setDataSource(audioFile.getAbsolutePath());            //设置监听回掉            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {                @Override                public void onCompletion(MediaPlayer mediaPlayer) {                    //播放结束,释放播放器                    stopPlay();                }            });            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {                @Override                public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {                    //提示用户                    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 stopPlay() {        //充值播放状态        mIsPlaying = false;        //释放播放器        if(mMediaPlayer!=null){            //充值监听器,防止内存泄漏            mMediaPlayer.setOnCompletionListener(null);            mMediaPlayer.setOnErrorListener(null);            mMediaPlayer.stop();            mMediaPlayer.reset();            mMediaPlayer.release();            mMediaPlayer=null;        }    }    /**     * 提醒用户,播放失败     */    private void playFail() {        //在主线程toast提示        mMainThreadHandler.post(new Runnable() {            @Override            public void run() {                Toast.makeText(FileActivity.this, "播放失败", Toast.LENGTH_SHORT).show();            }        });    }}


5.activity_stream.xml页面:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="com.example.vivinia.imspeech.StreamActivity">    <Button        android:id="@+id/mBtnStreamPlay"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:textSize="30sp"        android:layout_marginTop="50dp"        android:onClick="btnStreamPlay"        android:text="播放"/>    <Button        android:id="@+id/mBtnStart"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="开始"        android:onClick="streamStart"        android:layout_marginTop="50dp"        android:textSize="30sp"/>    <TextView        android:id="@+id/mTvLog"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="50dp"        android:text="123"        android:gravity="center"        android:textSize="30sp"/></LinearLayout>


6.StreamActivity.java页面:
package com.example.vivinia.imspeech;import android.media.AudioFormat;import android.media.AudioManager;import android.media.AudioRecord;import android.media.AudioTrack;import android.media.MediaRecorder;import android.os.Environment;import android.os.Handler;import android.os.Looper;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;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 Button mBtnStart;    private TextView mTvLog;    //录音状态,volatile保证多线程内存同步,避免出问题    private volatile boolean mIsRecording;    private ExecutorService mExecutorService;    private Handler mMainThreadHandler;    private File mAudioFile;    private FileOutputStream mFileOutputStream;    private AudioRecord mAudioRecord;    private long mStartRecorderTime,mStopRecorderTime;    //buffer不能太大,避免OOM(内存耗尽)    private static final int BUFFER_SIZE=2048;    private byte[] mBuffer;    //主线程和后台播放线程数据同步    private volatile boolean mIsPlaying;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_stream);        mBtnStart= (Button) findViewById(R.id.mBtnStart);        mTvLog= (TextView) findViewById(R.id.mTvLog);        mBuffer=new byte[BUFFER_SIZE];        //录音JNI函数不具备线程安全性,所以要用单线程        mExecutorService= Executors.newSingleThreadExecutor();    //初始化单线程的后台线程池        mMainThreadHandler=new Handler(Looper.getMainLooper());    }    @Override    protected void onDestroy() {        super.onDestroy();        //activity销毁时,释放资源,避免内存泄漏        mExecutorService.shutdownNow();    }    public void streamStart(View v){        //根据当前状态,改变UI,执行开始/停止录音的逻辑        if(mIsRecording){            //改变UI状态            mBtnStart.setText("开始");            //改变录音状态            mIsRecording=false;        }else{            mBtnStart.setText("停止");            //改变录音状态            mIsRecording=true;            //提交后台任务,执行录音逻辑            mExecutorService.submit(new Runnable() {                @Override                public void run() {                    //执行开始录音逻辑,失败提示用户                    if(!startRecord()){                        recordFail();                    }                }            });        }    }    /**     * 启动录音逻辑     * @return     */    private boolean startRecord() {        try {            //创建录音文件            mAudioFile=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/IMSpeechDemo/"+System.currentTimeMillis()+".pcm");            mAudioFile.getParentFile().mkdirs();            mAudioFile.createNewFile();            //创建文件输出流            mFileOutputStream=new FileOutputStream(mAudioFile);            //配置AnduioRecord            int audioSource= MediaRecorder.AudioSource.MIC;  //从麦克风采集            int sampleRate=44100;  //所有安卓系统都支持的采样频率            int channelConfig= AudioFormat.CHANNEL_IN_MONO;  //单声道输入            int audioFormat=AudioFormat.ENCODING_PCM_16BIT;  //PCM16是所有安卓系统都支持的一个格式            int minBufferSize=AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat);  //计算AudioRecord内部buffer的最小的大小            mAudioRecord=new AudioRecord(audioSource,sampleRate,channelConfig,audioFormat,Math.max(minBufferSize,BUFFER_SIZE));  //buffer不能小于最低要求,也不能小于我们每次读取的大小            //开始录音            mAudioRecord.startRecording();            //记录开始录音时间,用于统计时长            mStartRecorderTime=System.currentTimeMillis();            //循环读取数据,写道输出流中            while (mIsRecording){                //只要还在录音状态,就一直读取数据                int read=mAudioRecord.read(mBuffer,0,BUFFER_SIZE);  //返回值为这次都到了多少                if(read>0){                    //读取成功及写到文件中                    mFileOutputStream.write(mBuffer,0,read);                }else{                    //读取失败,返回false提示用户                    return false;                }            }            //退出循环,停止录音,释放资源            return stopRecord();        } catch (IOException | RuntimeException e) {            e.printStackTrace();            //捕获异常,避免闪退,返回false提醒用户失败            return false;        }   finally {            //释放AudioRecord            if(mAudioRecord!=null){                mAudioRecord.release();            }        }    }    /**     * 结束录音逻辑     */    private boolean stopRecord() {        try {            //停止录音,关闭文件输出流            mAudioRecord.stop();            mAudioRecord.release();            mAudioRecord=null;            mFileOutputStream.close();            //记录结束时间,统计录音时长            mStopRecorderTime=System.currentTimeMillis();            //大于3秒才算成功,在主线程改变UI显示            final int second= (int) ((mStopRecorderTime-mStartRecorderTime)/1000);            if(second>3){                //在主线程改UI,显示出来                mMainThreadHandler.post(new Runnable() {                    @Override                    public void run() {                        mTvLog.setText("\n录音成功"+second+"秒");                    }                });            }        } catch (IOException e) {            e.printStackTrace();            //捕获异常,避免闪退,返回false提醒用户失败            return false;        }        return true;    }    /**     * 录音错误处理     */    private void recordFail() {        //给用户toast提示失败,主要在主线程执行        mMainThreadHandler.post(new Runnable() {            @Override            public void run() {                Toast.makeText(StreamActivity.this,"录音失败",Toast.LENGTH_SHORT).show();                //重置录音状态,以及UI状态                mIsRecording=false;                mBtnStart.setText("开始");            }        });    }    public void btnStreamPlay(View v){        //检查当前状态,防止重复播放        if (mAudioFile != null && !mIsPlaying) {            //设置当前播放状态            mIsPlaying = true;            //提交后台任务,防止阻塞主线程            mExecutorService.submit(new Runnable() {                @Override                public void run() {                    doPlay(mAudioFile);                }            });        }    }    /**     * 实际播放逻辑     * @param audioFile     */    private void doPlay(File audioFile) {        //配置播放器        int streamType= AudioManager.STREAM_MUSIC;  //音乐类型,扬声器播放        int sampleRate=44100;  //录音时采用的采样频率,所以播放时候使用同样的采用频率        int channelCofig=AudioFormat.CHANNEL_OUT_MONO;   //MONO表示录音输入单声道,OUT_MONO为播放输出单声道        int audioFormat=AudioFormat.ENCODING_PCM_16BIT;  //录音时使用16bit,所以播放时使用同样的格式        int mode= AudioTrack.MODE_STREAM;   //流模式        int minBufferSize=AudioTrack.getMinBufferSize(sampleRate,channelCofig,audioFormat);  //计算最小buffer大小        AudioTrack audioTrack=new AudioTrack(streamType,sampleRate,channelCofig,audioFormat,Math.max(minBufferSize,BUFFER_SIZE),mode);  //不能小于AudioTrack的最低要求,也不能小于我们每次读的大小        FileInputStream inputStream=null;   //从文件流读取数据        try {            inputStream=new FileInputStream(audioFile);            //循环读数据,写道播放器去播放            int read;            //只要没读完,循环写播放            audioTrack.play();            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();            //错误处理,防止闪退            playFail();        }finally {            mIsPlaying=false;            //关闭文件输入流            if(inputStream!=null){                closeQuitly(inputStream);            }        }        //播放器释放        resetQuitly(audioTrack);    }    private void resetQuitly(AudioTrack audioTrack) {        try {            audioTrack.stop();            audioTrack.release();        }catch (RuntimeException e){            e.printStackTrace();        }    }    /**     *  静默关闭输入流     */    private void closeQuitly(FileInputStream inputStream) {        try {            inputStream.close();        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 提醒用户,播放失败     */    private void playFail() {        //在主线程toast提示        mMainThreadHandler.post(new Runnable() {            @Override            public void run() {                Toast.makeText(StreamActivity.this, "播放失败", Toast.LENGTH_SHORT).show();            }        });    }}


7.添加权限:
<!--录音和写磁盘权限-->    <uses-permission android:name="android.permission.RECORD_AUDIO"/>    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


源码下载:点击打开链接


原创粉丝点击