Android 语音录制与播放的实现(文件模式+字节流模式)

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);
            }
        });
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值