Android 开发 AudioRecord音频录制

前言

  Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。

实现流程

  1. 获取权限
  2. 初始化获取每一帧流的Size
  3. 初始化音频录制AudioRecord
  4. 开始录制与保存录制音频文件
  5. 停止录制
  6. 给音频文件添加头部信息,并且转换格式成wav
  7. 释放AudioRecord,录制流程完毕

获取权限

    <!--录音权限-->
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>

另外调用录音麦克MIC需要动态授权

初始化获取每一帧流的Size

private Integer mRecordBufferSize;
private void initMinBufferSize(){
        //获取每一帧的字节流大小
        mRecordBufferSize = AudioRecord.getMinBufferSize(8000
                , AudioFormat.CHANNEL_IN_MONO
                , AudioFormat.ENCODING_PCM_16BIT);
    }

第一个参数sampleRateInHz 采样率(赫兹),方法注释里有说明

只能在4000到192000的范围内取值

在AudioFormat类里
public static final int SAMPLE_RATE_HZ_MIN = 4000; 最小4000
public static final int SAMPLE_RATE_HZ_MAX = 192000; 最大192000

第二个参数channelConfig 声道配置 描述音频声道的配置,例如左声道/右声道/前声道/后声道。

在AudioFormat类录
public static final int CHANNEL_IN_LEFT = 0x4;//左声道
public static final int CHANNEL_IN_RIGHT = 0x8;//右声道
public static final int CHANNEL_IN_FRONT = 0x10;//前声道
public static final int CHANNEL_IN_BACK = 0x20;//后声道
public static final int CHANNEL_IN_LEFT_PROCESSED = 0x40;
public static final int CHANNEL_IN_RIGHT_PROCESSED = 0x80;
public static final int CHANNEL_IN_FRONT_PROCESSED = 0x100;
public static final int CHANNEL_IN_BACK_PROCESSED = 0x200;
public static final int CHANNEL_IN_PRESSURE = 0x400;
public static final int CHANNEL_IN_X_AXIS = 0x800;
public static final int CHANNEL_IN_Y_AXIS = 0x1000;
public static final int CHANNEL_IN_Z_AXIS = 0x2000;
public static final int CHANNEL_IN_VOICE_UPLINK = 0x4000;
public static final int CHANNEL_IN_VOICE_DNLINK = 0x8000;
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;//单声道
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);//立体声道(左右声道)

第三个参数audioFormat 音频格式 表示音频数据的格式。

注意!一般的手机设备可能只支持 16位PCM编码,如果其他的都会报错为坏值.

public static final int ENCODING_PCM_16BIT = 2; //16位PCM编码
public static final int ENCODING_PCM_8BIT = 3; //8位PCM编码
public static final int ENCODING_PCM_FLOAT = 4; //4位PCM编码
public static final int ENCODING_AC3 = 5;
public static final int ENCODING_E_AC3 = 6;
public static final int ENCODING_DTS = 7;
public static final int ENCODING_DTS_HD = 8;
public static final int ENCODING_MP3 = 9; //MP3编码 此格式可能会因为不设备不支持报错
public static final int ENCODING_AAC_LC = 10;
public static final int ENCODING_AAC_HE_V1 = 11;
public static final int ENCODING_AAC_HE_V2 = 12;

初始化音频录制AudioRecord

private AudioRecord mAudioRecord;
private void initAudioRecord(){
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC
                , 8000
                , AudioFormat.CHANNEL_IN_MONO
                , AudioFormat.ENCODING_PCM_16BIT
                , mRecordBufferSize);
    }
  • 第一个参数audioSource 音频源   这里选择使用麦克风:MediaRecorder.AudioSource.MIC
  • 第二个参数sampleRateInHz 采样率(赫兹)  与前面初始化获取每一帧流的Size保持一致
  • 第三个参数channelConfig 声道配置 描述音频声道的配置,例如左声道/右声道/前声道/后声道。   与前面初始化获取每一帧流的Size保持一致
  • 第四个参数audioFormat 音频格式  表示音频数据的格式。  与前面初始化获取每一帧流的Size保持一致
  • 第五个参数缓存区大小,就是上面我们配置的AudioRecord.getMinBufferSize

开始录制与保存录制音频文件

private boolean mWhetherRecord;
private File pcmFile;
private void startRecord(){
        pcmFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord.pcm");
        mWhetherRecord = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                mAudioRecord.startRecording();//开始录制
                FileOutputStream fileOutputStream = null;
                try {
                    fileOutputStream = new FileOutputStream(pcmFile);
                    byte[] bytes = new byte[mRecordBufferSize];
                    while (mWhetherRecord){
                        mAudioRecord.read(bytes, 0, bytes.length);//读取流
                        fileOutputStream.write(bytes);
                        fileOutputStream.flush();

                    }
                    Log.e(TAG, "run: 暂停录制" );
                    mAudioRecord.stop();//停止录制
                    fileOutputStream.flush();
                    fileOutputStream.close();
                   addHeadData();//添加音频头部信息并且转成wav格式
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mAudioRecord.stop();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }

这里说明一下为什么用布尔值,来关闭录制.有些小伙伴会发现AudioRecord是可以获取到录制状态的.那么肯定有人会用状态来判断while是否还需要处理流.这种是错误的做法.因为MIC属于硬件层任何硬件的东西都是异步的而且会有很大的延时.所以回调的状态也是有延时的,有时候流没了,但是状态还是显示为正在录制.

停止录制

就是调用mAudioRecord.stop();方法来停止录制,但是因为我在上面的保存流后做了调用停止视频录制,所以我这里只需要切换布尔值就可以关闭音频录制

private void stopRecord(){
        mWhetherRecord = false;
    }

给音频文件添加头部信息,并且转换格式成wav

音频录制完成后,这个时候去存储目录找到音频文件部分,会提示无法播放文件.其实是因为没有加入音频头部信息.一般通过麦克风采集的录音数据都是PCM格式的,即不包含头部信息,播放器无法知道音频采样率、位宽等参数,导致无法播放,显然是非常不方便的。pcm转换成wav,我们只需要在pcm的文件起始位置加上至少44个字节的WAV头信息即可。

偏移地址   命名       内容

00-03   ChunkId       "RIFF"

04-07   ChunkSize      下个地址开始到文件尾的总字节数(此Chunk的数据大小)

08-11   fccType       "WAVE"

12-15   SubChunkId1       "fmt ",最后一位空格。

16-19   SubChunkSize1    一般为16,表示fmt Chunk的数据块大小为16字节

20-21   FormatTag      1:表示是PCM 编码

22-23   Channels         声道数,单声道为1,双声道为2

24-27   SamplesPerSec      采样率

28-31   BytesPerSec     码率 :采样率 * 采样位数 * 声道个数,bytePerSecond = sampleRate * (bitsPerSample / 8) * channels

32-33   BlockAlign       每次采样的大小:位宽*声道数/8

34-35   BitsPerSample     位宽

36-39   SubChunkId2     "data"

40-43   SubChunkSize2      音频数据的长度

44-...   data         音频数据

private void addHeadData(){
        pcmFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord.pcm");
        handlerWavFile = new File(AudioRecordActivity.this.getExternalCacheDir().getPath(),"audioRecord_handler.wav");
        PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);
        pcmToWavUtil.pcmToWav(pcmFile.toString(),handlerWavFile.toString());
    }

写入头部信息的工具类

注意输入File和输出File不能同一个,因为没有做缓存.

public class PcmToWavUtil {
    private static final String TAG = "PcmToWavUtil";

    /**
     * 缓存的音频大小
     */
    private int mBufferSize;
    /**
     * 采样率
     */
    private int mSampleRate;
    /**
     * 声道数
     */
    private int mChannel;

    /**
     * @param sampleRate sample rate、采样率
     * @param channel channel、声道
     * @param encoding Audio data format、音频格式
     */
    PcmToWavUtil(int sampleRate, int channel, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannel = channel;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
    }

    /**
     * pcm文件转wav文件
     *
     * @param inFilename 源文件路径
     * @param outFilename 目标文件路径
     */
    public void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;//总录音长度
        long totalDataLen;//总数据长度
        long longSampleRate = mSampleRate;
        int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
        long byteRate = 16 * mSampleRate * channels / 8;
        byte[] data = new byte[mBufferSize];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
                out.flush();

            }
            Log.e(TAG, "pcmToWav: 停止处理");
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = ‘R‘;
        header[1] = ‘I‘;
        header[2] = ‘F‘;
        header[3] = ‘F‘;
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        //WAVE
        header[8] = ‘W‘;
        header[9] = ‘A‘;
        header[10] = ‘V‘;
        header[11] = ‘E‘;
        // ‘fmt ‘ chunk
        header[12] = ‘f‘;
        header[13] = ‘m‘;
        header[14] = ‘t‘;
        header[15] = ‘ ‘;
        // 4 bytes: size of ‘fmt ‘ chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 1;
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // block align
        header[32] = (byte) (2 * 16 / 8);
        header[33] = 0;
        // bits per sample
        header[34] = 16;
        header[35] = 0;
        //data
        header[36] = ‘d‘;
        header[37] = ‘a‘;
        header[38] = ‘t‘;
        header[39] = ‘a‘;
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
}

释放AudioRecord,录制流程完毕

调用release()方法释放资源

mAudioRecord.release();

最后你就可以在指定目录下找到音频文件播放了

最后介绍下其他API

获取AudioRecord初始化状态

public int getState() {
    return mState;
}

注意!这里是初始化状态,不是录制状态,它只会返回2个状态

  • AudioRecord#STATE_INITIALIZED    //已经初始化
  • AudioRecord#STATE_UNINITIALIZED  //没有初始化

获取AudioRecord录制状态

public int getRecordingState() {
        synchronized (mRecordingStateLock) {
            return mRecordingState;
        }
    }

返回录制状态,它只返回2个状态

  • AudioRecord#RECORDSTATE_STOPPED    //停止录制
  • AudioRecord#RECORDSTATE_RECORDING    //正在录制
 

原文地址:https://www.cnblogs.com/guanxinjing/p/10969824.html

时间: 2024-10-08 12:54:55

Android 开发 AudioRecord音频录制的相关文章

Android开发之视频录制1

Android开发之视频录制 (2011-10-18 17:47:46) 转载▼ 标签: android 视频录制 surfaceview 杂谈 分类: 3GAndroid学习笔记 要使用视频录制功能,要用到MediaRecorder这个类,以及SurfaceView组建 public class MainActivity extends Activity { public static final String TAG = "MainActivity"; @Override prot

android开发,静音录制视频,在一般清晰度的前提下保证文件大小越小越好

public void startRecord() { mediarecorder = new MediaRecorder();// 创建mediarecorder对象 mCamera = getCameraInstance(); Parameters parameters = mCamera.getParameters(); mCamera.autoFocus(null); // 解锁camera mCamera.setDisplayOrientation(90); mCamera.unloc

Android实现音频录制的两种方式

在移动APP开发中,每逢APP应用设计到多媒体开发的时候,都会让很多的程序员头疼不已,而且项目的开发进度会放慢.项目 的难度也会加大蛮多,同时APP的测试也会增加.Android中的多媒体开发,有音频的播放.音频的录制.视频的播放.视频的录制 等,虽然Android的SDK中提供了一些基础的开发API类,如音频的录制就提供了两种方式:AudioRecord录制音频和MediaRecorder录 制音频.AudioRecord类相对于MediaRecorder来说,更加接近底层,为我们封装的方法也

Android开发之使用MediaRecorder录制声音

为了在 Android 应用中录制声音,Android提供了 MediaRecorder 类,关于MediaRecorder的详解大家可以参考<Android开发之MediaRecorder类详解>. 使用MediaRecorder录制声音的步骤: 1) 创建 MediaRecorder 对象. 2) 调用MediaRecorder对象的setAudioSource()方法设置声音来源,一般传入 MediaRecorder. AudioSource.MIC参数指定录制来自麦克风的声音. 3)

Android开发之使用MediaRecorder录制视频

MediaRecorder除了可以用于录制音频,还可用于录制视频.关于MediaRecorder的详解大家可以参考<Android开发之MediaRecorder类详解>.使用MediaRecorder录制视频与录制音频的步骤基本相同.只是录制视频时不仅需要采集声音,还需要采集图像.为了让MediaRecorder录制时采集图像,应该在调用setAudioSource(int audio source)方法时再调用setVideoSource(int video source)方法来设置图像来

IOS开发Q&amp;A-IOS8定位应用定位失败及音频录制的相关参数

一. 问题描述:使用xcode6和ios8开发定位应用时,发现执行操作之后,不会调用到定位之后的delegate方法中.查看了一下手机上对应用的定位权限界面,发现我的应用的访问用户的地理位置的权限是空的,即使设置了定位权限依然不会生效.当查阅了相关资料以后,得到如下解决方案: 解决方案: step1: 在 info.plist里加入: NSLocationWhenInUseDescription,允许在前台获取GPS的描述 NSLocationAlwaysUsageDescription,允许在

iOS开发系列--音频播放、录音、视频播放、拍照、视频录制

iOS开发系列--音频播放.录音.视频播放.拍照.视频录制 转载:http://www.cnblogs.com/kenshincui/p/4186022.html#avFoundationCamera --iOS多媒体 概览 随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操作都提供了多套API.在今天的文章中将会对这些内容进行一一介绍: 音频 音

Android开发之控制手机音频

本实例通过MediaPlayer播放一首音乐并通过AudioManager控制手机音频,关于AudioManager的详解可参照:Android开发之AudioManager(音频管理器)详解 程序运行效果图: 实例代码: package com.jph.audiomanagerdemo; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; import an

Android底层开发之音频输入通道的软硬件分析

Android底层开发之音频输入通道的软硬件分析 我们都知道耳机Mic集成在一直的那种四段耳机Mic插头是Android设备上比较常用.但是也会有分开的情况,比较如果在普通的PC机中装Android系统,那么就是这种情况.所以就有必要对音频输入通道的软件硬件进行统一的分析一下,接下来分析一个实例. 该设备的硬件连接为:基于3157的模拟开关实现的 通道切换. 设备是完全靠硬件实现的,那么就没有软件的什么工作了.但是这并不是一个理想的实现方法,真下的实现方法应该是所有的Mic都是并行的,每个Mic