Android-文件及字符流方式的语音保存播放功能
2017-06-21 16:04
1001 查看
目标效果:
录音并播放共两种方式,一种是文件方式,一种是字符流方式,图4为保存的文件,后缀为.m4a的为文件方式的,可以使用系统录音机播放,后缀为.pcm的为字符流方式的,不可以使用系统录音机播放
1.activity_main.xml页面:
2.MainActivity.java页面:
3.activity_file.xml页面:
4.FileActivity.java页面:
5.activity_stream.xml页面:
6.StreamActivity.java页面:
7.添加权限:
源码下载:点击打开链接
录音并播放共两种方式,一种是文件方式,一种是字符流方式,图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"/>
源码下载:点击打开链接
相关文章推荐
- Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Android音频处理之通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
- Android 录音功能(语音录制保存,播放)
- android 播放语音文件出现 prepare failed ,不能下载amr文件
- android保存文件到sd卡,读取和清空记录功能(可以用来保存用户名和密码)
- (转)【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- Android数据保存的3种方式 SharedPreferences存储、文件存储、数据库存储SQLite 未完
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- Android实现文件的保存与读取功能示例
- android进行录音功能并保存播放
- android_before_day08_小结_文件保存三种方式
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- Android 实现文件打开方式可供选择功能
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!