您的位置:首页 > 移动开发 > Android开发

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

2017-06-21 16:04 1001 查看
目标效果:


 


 


 



录音并播放共两种方式,一种是文件方式,一种是字符流方式,图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"/>


源码下载:点击打开链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐