Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能


音频这方面很博大精深,我这里肯定讲不了什么高级的东西,最多也只是一些基础类知识,首先,我们要介绍一下Android他提供的录音类,实际上他有两个,一个是MediaRecorder,还有一个就是我们今天要用到的AudioRecord,那他们有什么区别呢?

一.区别

MediaRecorder和AudioRecord都可以录制音频,区别是MediaRecorder录制的音频文件是经过压缩后的,需要设置编码器。并且录制的音频文件可以用系统自带的Music播放器播放。

而AudioRecord录制的是PCM格式的音频文件,需要用AudioTrack来播放,AudioTrack更接近底层。

PCM可能更加可以理解为音频的源文件

二.优缺点

  • AudioRecord

主要是实现边录边播以及对音频的实时处理,这个特性让他更适合在语音方面有优势

优点:语音的实时处理,可以用代码实现各种音频的封装

缺点:输出是PCM格式文件,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩

  • MediaRecorder

已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有,aac,amr,3gp等

优点:集成,直接调用相关接口即可,代码量小

缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件

三.准备工作

我们要实现的是一个实时的去录音,播放,停止等功能的测试案例,那我们肯定要准备点什么,比如说,我这里先创建一个项目——PCMSample

然后写个布局

layout_main.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:padding="10dp">    <Button        android:id="@+id/startAudio"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="@drawable/button_bg"        android:text="开始录音"        android:textColor="@android:color/white"/>    <Button        android:id="@+id/stopAudio"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginBottom="10dp"        android:layout_marginTop="5dp"        android:background="@drawable/button_bg"        android:enabled="false"        android:text="停止录音"        android:textColor="@android:color/white"/>    <Button        android:id="@+id/playAudio"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="@drawable/button_bg"        android:enabled="false"        android:text="播放音频"        android:textColor="@android:color/white"/>    <Button        android:id="@+id/deleteAudio"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="5dp"        android:background="@drawable/button_bg"        android:text="删除PCM"        android:textColor="@android:color/white"/>    <ScrollView        android:id="@+id/mScrollView"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_marginTop="5dp"        android:layout_weight="1">        <TextView            android:id="@+id/tv_audio_succeess"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="初始化完成...."            android:textColor="@color/colorAccent"/>    ScrollView>LinearLayout>

可以预览一下

这里我给按钮加了一个扁平的效果,实际上写了一个xml,很简单

button_bg.xml

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <item android:state_pressed="true">        <shape>            <corners android:radius="30dp"/>            <solid android:color="@color/colorPrimary"/>        shape>    item>    <item android:state_pressed="false">        <shape>            <corners android:radius="30dp"/>            <solid android:color="@color/colorPrimaryDark"/>        shape>    item>selector>

好的,回到正题,我们这里有四个按钮,分别是开始。停止,播放,和删除,我们就是要实现这四个功能,在此之前,我们还需要做的事情就是添加权限,因为我们要录音和写内存卡文件,所有需要这两个权限即可

        <uses-permission android:name="android.permission.RECORD_AUDIO" />        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

这里初始化什么的就不说了,我们直接进入正题

四.开始录音

开始录音的话,这里,我们定义一个变量isRecording去控制,这样就比较好结束了,而且要注意的是,录音是不能放在UI线程的,你懂的,所以我们可以写一个开始录音的方法

    //开始录音    public void StartRecord() {        Log.i(TAG,"开始录音");        //16K采集率        int frequency = 16000;        //格式        int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;        //16Bit        int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;        //生成PCM文件        file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/reverseme.pcm");        Log.i(TAG,"生成文件");        //如果存在,就先删除再创建        if (file.exists())            file.delete();            Log.i(TAG,"删除文件");        try {            file.createNewFile();            Log.i(TAG,"创建文件");        } catch (IOException e) {            Log.i(TAG,"未能创建");            throw new IllegalStateException("未能创建" + file.toString());        }        try {            //输出流            OutputStream os = new FileOutputStream(file);            BufferedOutputStream bos = new BufferedOutputStream(os);            DataOutputStream dos = new DataOutputStream(bos);            int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);            AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);            short[] buffer = new short[bufferSize];            audioRecord.startRecording();            Log.i(TAG, "开始录音");            isRecording = true;            while (isRecording) {                int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);                for (int i = 0; i < bufferReadResult; i++) {                    dos.writeShort(buffer[i]);                }            }            audioRecord.stop();            dos.close();        } catch (Throwable t) {            Log.e(TAG, "录音失败");        }    }

首先,这里我们了解一下采样率,编码,音频流等基本的概念,剩下的大多是读写流的操作了,我们通过创建一个AudioRecord去写pcm文件,定义一个while循环,用我们刚才定义的isRecording控制,所以,我们的点击事件就

        case R.id.startAudio:                Thread thread = new Thread(new Runnable() {                    @Override                    public void run() {                        StartRecord();                        Log.e(TAG,"start");                    }                });                thread.start();                printLog("开始录音");                ButtonEnabled(false, true, false);                break;

这里要注意一下thread.start();开启线程,同时打印出log,具体代码如下

    //打印log    private void printLog(final String resultString) {        tv_audio_succeess.post(new Runnable() {            @Override            public void run() {                tv_audio_succeess.append(resultString + "\n");                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);            }        });    }

这里,我为了防止ANR,所以控制了一下按钮的焦点

    //获取/失去焦点    private void ButtonEnabled(boolean start, boolean stop, boolean play) {        startAudio.setEnabled(start);        stopAudio.setEnabled(stop);        playAudio.setEnabled(play);    }

好的,我们运行一下

看起来没什么变化,但是你去内存卡中就会发现多了一个pcm文件

当然,你只是点击启动录音是不会生成这个pcm文件的,你需要点击停止停止录音的按钮

五.停止录音

停止录音很简单,我们控制通过改变写入流就好了

            case R.id.stopAudio:                isRecording = false;                ButtonEnabled(true, false, true);                printLog("停止录音");                break;

这样才会生成PCM

六播放音频

现在有了PCM我们可以试着去播放了,写一个播放的方法

   //播放文件    public void PlayRecord() {        if(file == null){            return;        }        //读取文件        int musicLength = (int) (file.length() / 2);        short[] music = new short[musicLength];        try {            InputStream is = new FileInputStream(file);            BufferedInputStream bis = new BufferedInputStream(is);            DataInputStream dis = new DataInputStream(bis);            int i = 0;            while (dis.available() > 0) {                music[i] = dis.readShort();                i++;            }            dis.close();            AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,                    16000, AudioFormat.CHANNEL_CONFIGURATION_MONO,                    AudioFormat.ENCODING_PCM_16BIT,                    musicLength * 2,                    AudioTrack.MODE_STREAM);            audioTrack.play();            audioTrack.write(music, 0, musicLength);            audioTrack.stop();        } catch (Throwable t) {            Log.e(TAG, "播放失败");        }    }

正如上面所说,我们播放需要用到AudioTrack,调用他的play方法以及设置一些参数即可

七.删除音频

删除音频只需要删除这个pcm文件就行

     //删除文件    private void deleFile() {        if(file == null){            return;        }        file.delete();        printLog("文件删除成功");    }

这就是大致的录音逻辑,虽然看起来很简单,但是这正是现在很多语音和音频的最基础部分,特别是语音,如果你从事语音的工作,我相信你会感谢我的!

好了,最后放上完整的代码:

MainActivity

package com.liuguilin.pcmsample;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.support.v7.app.AppCompatActivity;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.ScrollView;import android.widget.TextView;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;public class MainActivity extends AppCompatActivity implements View.OnClickListener {    public static final String TAG = "PCMSample";    //是否在录制    private boolean isRecording = false;    //开始录音    private Button startAudio;    //结束录音    private Button stopAudio;    //播放录音    private Button playAudio;    //删除文件    private Button deleteAudio;    private ScrollView mScrollView;    private TextView tv_audio_succeess;    //pcm文件    private File file;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initView();    }    //初始化View    private void initView() {        mScrollView = (ScrollView) findViewById(R.id.mScrollView);        tv_audio_succeess = (TextView) findViewById(R.id.tv_audio_succeess);        printLog("初始化成功");        startAudio = (Button) findViewById(R.id.startAudio);        startAudio.setOnClickListener(this);        stopAudio = (Button) findViewById(R.id.stopAudio);        stopAudio.setOnClickListener(this);        playAudio = (Button) findViewById(R.id.playAudio);        playAudio.setOnClickListener(this);        deleteAudio = (Button) findViewById(R.id.deleteAudio);        deleteAudio.setOnClickListener(this);    }    //点击事件    @Override    public void onClick(View v) {        switch (v.getId()) {            case R.id.startAudio:                Thread thread = new Thread(new Runnable() {                    @Override                    public void run() {                        StartRecord();                        Log.e(TAG,"start");                    }                });                thread.start();                printLog("开始录音");                ButtonEnabled(false, true, false);                break;            case R.id.stopAudio:                isRecording = false;                ButtonEnabled(true, false, true);                printLog("停止录音");                break;            case R.id.playAudio:                PlayRecord();                ButtonEnabled(true, false, false);                printLog("播放录音");                break;            case R.id.deleteAudio:                deleFile();                break;        }    }    //打印log    private void printLog(final String resultString) {        tv_audio_succeess.post(new Runnable() {            @Override            public void run() {                tv_audio_succeess.append(resultString + "\n");                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);            }        });    }    //获取/失去焦点    private void ButtonEnabled(boolean start, boolean stop, boolean play) {        startAudio.setEnabled(start);        stopAudio.setEnabled(stop);        playAudio.setEnabled(play);    }    //开始录音    public void StartRecord() {        Log.i(TAG,"开始录音");        //16K采集率        int frequency = 16000;        //格式        int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;        //16Bit        int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;        //生成PCM文件        file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/reverseme.pcm");        Log.i(TAG,"生成文件");        //如果存在,就先删除再创建        if (file.exists())            file.delete();            Log.i(TAG,"删除文件");        try {            file.createNewFile();            Log.i(TAG,"创建文件");        } catch (IOException e) {            Log.i(TAG,"未能创建");            throw new IllegalStateException("未能创建" + file.toString());        }        try {            //输出流            OutputStream os = new FileOutputStream(file);            BufferedOutputStream bos = new BufferedOutputStream(os);            DataOutputStream dos = new DataOutputStream(bos);            int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);            AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);            short[] buffer = new short[bufferSize];            audioRecord.startRecording();            Log.i(TAG, "开始录音");            isRecording = true;            while (isRecording) {                int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);                for (int i = 0; i < bufferReadResult; i++) {                    dos.writeShort(buffer[i]);                }            }            audioRecord.stop();            dos.close();        } catch (Throwable t) {            Log.e(TAG, "录音失败");        }    }    //播放文件    public void PlayRecord() {        if(file == null){            return;        }        //读取文件        int musicLength = (int) (file.length() / 2);        short[] music = new short[musicLength];        try {            InputStream is = new FileInputStream(file);            BufferedInputStream bis = new BufferedInputStream(is);            DataInputStream dis = new DataInputStream(bis);            int i = 0;            while (dis.available() > 0) {                music[i] = dis.readShort();                i++;            }            dis.close();            AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,                    16000, AudioFormat.CHANNEL_CONFIGURATION_MONO,                    AudioFormat.ENCODING_PCM_16BIT,                    musicLength * 2,                    AudioTrack.MODE_STREAM);            audioTrack.play();            audioTrack.write(music, 0, musicLength);            audioTrack.stop();        } catch (Throwable t) {            Log.e(TAG, "播放失败");        }    }    //删除文件    private void deleFile() {        if(file == null){            return;        }        file.delete();        printLog("文件删除成功");    }}

如果你想去调试这些pcm文件做音频测试的话,我推荐使用Audacity这个软件,可以看到,我直接点击左上角的file-导入-源文件,然后设置16K

这样就可以调试了

最后,放一张完整的截图

嗯,这篇文章就到这里,还是有些基础了,希望下次能给大家带来高深点的文章吧,有兴趣的加群:555974449

Sample下载:http://download.csdn.net/detail/qq_26787115/9676003

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. Android文件(File)操作
  6. Android(安卓)Bugly Tinker 热更新
  7. android 使用Realm数据库
  8. 四、Android.mk之编译生成可执行文件
  9. android AVD 启动时报错(emulator: erroremulator:ERROR:This AVD'

随机推荐

  1. Android中不常见的监听: 鼠标划过/双击/右
  2. Android(安卓)闹钟详解
  3. Android WebView 支持H5图片上传
  4. 最新Android(安卓)SDK_API_开发包_离线包
  5. Http的15种请求
  6. Android×××方法详解
  7. [Android] Handler源码解析 (Java层)
  8. Android取得长宽的问题
  9. android WindowManager可拖动悬浮按钮
  10. Xamarin 中开发Android实现全屏或者不显