1.准备工作
(1)权限声明
AndroidManifest.xm:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
build.gradle(Module:app):
apply plugin: 'com.android.application'
apply plugin: 'android-apt'//添加使用apt插件
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.example.rxzero.yuyingdemo"
minSdkVersion 15
targetSdkVersion 22 //需要修改这里,采用22,简化操作
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
task clean(type:Delete){
delete rootProject.buildDir
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.2.0'
compile 'com.jakewharton:butterknife:8.2.1'
apt 'com.jakewharton:butterknife-compiler:8.2.1'
//添加2个以上依赖compile 'com.jakewharton:butterknife:8.2.1'
//apt 'com.jakewharton:butterknife-compiler:8.2.1'
testCompile 'junit:junit:4.12'
}
build.gradle(Project:Yuying):
buildscript {
repositories {
jcenter()
}
dependencies {
//添加插件
//声明使用插件
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
(2)strings.xml:
<resources>
<string name="app_name">YuyingDemo</string>
<string name="speaking">正在说话...</string>
<string name="start">开始</string>
<string name="stop">停止</string>
<string name="press_to_say">按住说话</string>
<string name="file">文件模式</string>
<string name="stream">字节流模式</string>
</resources>
2.布局文件
布局文件分为activity_main.xml、activity_file.xml、activity_stream三个,结构简单,采用LinearLayout布局
(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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.rxzero.yuyingdemo.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/mBtnFileMode"
android:text="@string/file"
android:textSize="30sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/mBtnStreamMode"
android:text="@string/stream"
android:textSize="30sp"/>
</LinearLayout>
(2)activity_file.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/mBtnPlay"
android:textSize="30sp"
android:text="播放"/>
<TextView
android:id="@+id/mTvLog"
android:layout_marginTop="80dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="@android:color/white"
android:textSize="30sp"
android:text="123"/>
<TextView
android:id="@+id/mTvPressToSay"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:layout_marginBottom="20dp"
android:background="#ffffff"
android:text="@string/press_to_say"
android:gravity="center"
android:textColor="#333333"
android:textSize="30sp"
/>
</FrameLayout>
(3)activity_stream.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<Button
android:text="@string/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/mBthStart"
android:textSize="30dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/mBtnPlay"
android:textSize="30sp"
android:text="播放"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mTvLog"
android:text="123"
android:textColor="@android:color/white"
android:textSize="30dp"/>
</LinearLayout>
3.主要代码
(1)MainActivity:
package com.example.rxzero.yuyingdemo;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.mBtnFileMode)
public void fileMode(){
startActivity(new Intent(this,FileActivity.class));
}
@OnClick(R.id.mBtnStreamMode)
public void streamMode(){
startActivity(new Intent(this,StreamActivity.class));
}
}
(2)FileActivity:
package com.example.rxzero.yuyingdemo;
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.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
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;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by rxzero on 17-3-17.
*/
public class FileActivity extends AppCompatActivity {
@BindView(R.id.mTvLog)
TextView mTvlog;
@BindView(R.id.mTvPressToSay)
TextView mTvpressToSay;
private ExecutorService mExecutorService;
private MediaRecorder mMediaRecorder;
private File mAudioFile;
private long mStartRecordTime,mStopRecordTime;
private Handler mMainThreadHandler;
private volatile boolean mIsPlaying;
private MediaPlayer mMediaPlayer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file);
ButterKnife.bind(this);
mExecutorService= Executors.newSingleThreadExecutor();
mMainThreadHandler=new Handler(Looper.getMainLooper());
mTvpressToSay.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;
}
return true;
}
});
}
@OnClick(R.id.mBtnPlay)
public void play(){
if(mAudioFile!=null&&!mIsPlaying){
mIsPlaying=true;
mExecutorService.submit(new Runnable() {
@Override
public void run() {
doPlay(mAudioFile);
}
});
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mExecutorService.shutdownNow();
releaseRecorder();
stopPlay();
}
private void startRecord() {
mTvpressToSay.setText(R.string.speaking);
mTvpressToSay.setBackgroundResource(R.drawable.button_press_to_say_pressed_bg);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
releaseRecorder();
if(!doStart()){
recordFail();
}
}
});
}
private void stopRecord() {
mTvpressToSay.setText(R.string.press_to_say);
mTvpressToSay.setBackgroundResource(R.drawable.button_press_to_say_bg);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
if(!doStop()){
recordFail();
}
releaseRecorder();
}
});
}
private boolean doStart() {
try {
mMediaRecorder=new MediaRecorder();
//获取储存路径,创建录音文件
mAudioFile=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/YuyingDemo/"+System
.currentTimeMillis()+".m4a");
mAudioFile.getParentFile().mkdirs();
mAudioFile.createNewFile();
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//从麦克风采集数据
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//保存文件为mp4
mMediaRecorder.setAudioSamplingRate(44100);//44100所有android都适合的采样频率
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//通用的AAC编码格式
mMediaRecorder.setAudioEncodingBitRate(96000);音质比较好的96000频率
mMediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());//设置录音位置
//开始录音
mMediaRecorder.prepare();
mMediaRecorder.start();
//记录时间
mStartRecordTime=System.currentTimeMillis();
} catch (IOException | RuntimeException e) {//捕获异常
e.printStackTrace();
return false;
}
return true;
}
private boolean doStop() {
try{
mMediaRecorder.stop();
mStopRecordTime=System.currentTimeMillis();
final int second= (int) ((mStopRecordTime-mStartRecordTime)/1000);
if(second>3){
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
mTvlog.setText(mTvlog.getText()+"\n录音成功"+second+"秒");
}
});
}
}catch (RuntimeException e){
e.printStackTrace();
return false;
}
return true;
}
private void releaseRecorder() {
if(mMediaRecorder!=null){
mMediaRecorder.release();
mMediaRecorder=null;
}
}
private void recordFail() {
//录音错误处理,toast
mAudioFile=null;
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(FileActivity.this,"录音失败",Toast.LENGTH_SHORT).show();
}
});
}
private void doPlay(File audioFile) {
mMediaPlayer=new MediaPlayer();
try{
mMediaPlayer.setDataSource(audioFile.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();
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() {
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(FileActivity.this,"播放失败",Toast.LENGTH_SHORT).show();
}
});
}
}
(3)StreamActivity:
package com.example.rxzero.yuyingdemo;
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.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
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;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by rxzero on 17-3-17.
*/
public class StreamActivity extends AppCompatActivity {
@BindView(R.id.mBthStart)
Button mBtnStart;
@BindView(R.id.mTvLog)
TextView mTvlog;
private volatile boolean mIsRecording;
private ExecutorService mExecutorService;
private Handler mMainThreadHandler;
private static final int BUFFER_SIZE=2048;
private byte[] mBuffer;
private File mAudioFile;
private FileOutputStream mFileOutputStream;
private AudioRecord mAudioRecord;
private long mStartRecordTime,mStopRecordTime;
private volatile boolean mIsPlaying;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stream);
ButterKnife.bind(this);
mExecutorService= Executors.newSingleThreadExecutor();
mMainThreadHandler=new Handler(Looper.getMainLooper());
mBuffer=new byte[BUFFER_SIZE];
}
@Override
protected void onDestroy(){
super.onDestroy();
mExecutorService.shutdownNow();
}
@OnClick(R.id.mBthStart)
public void start(){
if(mIsRecording){
mBtnStart.setText(R.string.start);
mIsRecording=false;
}else {
mBtnStart.setText(R.string.stop);
mIsRecording=true;
mExecutorService.submit(new Runnable() {
@Override
public void run() {
if(startRecord()){
recoedFail();
}
}
});
}
}
@OnClick(R.id.mBtnPlay)
public void play(){
if(mAudioFile!=null&&!mIsPlaying){
mIsPlaying=true;
mExecutorService.submit(new Runnable() {
@Override
public void run() {
doPlay(mAudioFile);
}
});
}
}
private void doPlay(File audioFile) {
//配置播放器
int streamType= AudioManager.STREAM_MUSIC;//音乐类型,扬声器播放
int sampleRate=44100;//录音时采用的采样频率,采用相同的
int channelConfig=AudioFormat.CHANNEL_OUT_MONO;//mono表示单声道,播放采用输出单声道
int audioFormat=AudioFormat.ENCODING_PCM_16BIT;
int mode= AudioTrack.MODE_STREAM;//流模式
int minBufferSize=AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);//计算需要最小buffer大小
AudioTrack audioTrack=new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
Math.max(minBufferSize,BUFFER_SIZE),mode);//构造AudioTrack,不能小于AudioTrack的最低要求,也不能小于我们每次读的大小
//从文件流中读数据
FileInputStream inputStream=null;
try{ //循环读数据
inputStream=new FileInputStream(audioFile);
int read;
while((read=inputStream.read(mBuffer))>0){
int ret=audioTrack.write(mBuffer,0,read);
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){
closeQuietly(inputStream);
}
resetQuietly(audioTrack);//播放器释放
}
}
private void resetQuietly(AudioTrack audioTrack) {
try{
audioTrack.stop();
audioTrack.release();
}catch (RuntimeException e){
e.printStackTrace();
}
}
private void closeQuietly(FileInputStream inputStream) {//静默关闭输出流
try{
inputStream.close();
}catch (IOException e) {
e.printStackTrace();
}
}
private void playFail() {
mAudioFile=null;
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(StreamActivity.this,"播放失败",Toast.LENGTH_SHORT).show();
}
});
}
private boolean startRecord() {
try {
mAudioFile=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/YuyingDemo/"+System
.currentTimeMillis()+".pcm");
mAudioFile.getParentFile().mkdirs();
mAudioFile.createNewFile();
mFileOutputStream=new FileOutputStream(mAudioFile);
int audioSource= MediaRecorder.AudioSource.MIC;//从麦克风采集
int sampleRate=44100;
int channelConfig= AudioFormat.CHANNEL_IN_MONO;//单声道输入
int audioFormat=AudioFormat.ENCODING_PCM_16BIT;//PCM16所有android都支持
//计算AudioRecord内部Buffer最小的大小
int minBufferSize=AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat);
mAudioRecord=new AudioRecord(audioSource,sampleRate,channelConfig,
audioFormat,Math.max(minBufferSize,BUFFER_SIZE));//buffer不能小于最低要求,也不能小于我们每次读取的大小
//开始录音
mAudioRecord.startRecording();;
mStartRecordTime=System.currentTimeMillis();
while(mIsRecording){//循环读取数据,写到输出流中
int read=mAudioRecord.read(mBuffer,0,BUFFER_SIZE);
if(read>0){
//读取成功就写到文件中
mFileOutputStream.write(mBuffer,0,read);
}else {
//读取失败
return false;
}
}
//停止循环,停止录音,释放资源
return stopRecoed();
} catch (IOException |RuntimeException e) {
e.printStackTrace();
return false;
}finally {
//释放AudioRecord
if(mAudioRecord!=null){
mAudioRecord.release();
}
}
}
private boolean stopRecoed() {
//结束录音逻辑
try {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord=null;
mFileOutputStream.close();
mStopRecordTime=System.currentTimeMillis();
final int second= (int) ((mStopRecordTime-mStartRecordTime)/1000);
if(second>3){
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
mTvlog.setText(mTvlog.getText()+"\n录音成功"+second+"秒");
}
});
}
} catch (IOException e) {
return false;
}
return false;
}
private void recoedFail() {
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(StreamActivity.this,"录音失败",Toast.LENGTH_SHORT).show();
mIsRecording=false;
mBtnStart.setText(R.string.start);
}
});
}
}