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"/>
源码下载:点击打开链接
阅读全文
0 0
- Android-文件及字符流方式的语音保存播放功能
- Android 录音功能(语音录制保存,播放)
- Android实现录音功能及播放语音功能
- android进行录音功能并保存播放
- 【转载】Flex调用外部JS实现播放语音文件功能
- Android音频处理之通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Android进阶:Google自带语音播放功能实现
- android 语音聊天播放动画的问题
- android TTS语音播放
- Android播放本地语音
- 初入门科大讯飞的语音合成(播放)功能
- Android手机数据保存的方式及注意事项
- Android 基于百度语音的语音交互功能
- Android采用SharedPreferences方式进行文件的保存与读取
- 即时通讯-语音录制及播放
- jPlayer播放后台返回的语音流
- Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Redis基本类型和常用简单操作
- Bugly热更新简单集成
- shell教程
- 自动化测试用例规范
- Mariadb安装之后的各种设置
- Android-文件及字符流方式的语音保存播放功能
- AudioRecorder实时录制mp3格式音频
- 决策树
- Spring MVC前端与后端5种ajax交互方式
- vue中的生命周期
- ABP框架心得系列-1.本土化和ABP核心
- Android 动画
- 集成Google登录
- php抓取通达官网的最新用户名单