Android 录音详解(一)—— MediaRecorder、AudioRecord、生成wav格式、边录边播

2018-11-27 liuyingcong 安卓开发

越来越多的 APP 都用到了手机的录音功能,比如搜索、聊天、输入、K歌等...


本系列详细介绍 Android 中录音功能的实现,包括:

    Android 录音详解(一)—— MediaRecorder、AudioRecord、生成wav格式、边录边播

    Android 录音详解(二)—— 录制 mp3 格式音频( lame 库编译及使用)

    Android 录音详解(三)—— 音频解码

    Android 录音详解(四)—— 录音添加背景音乐


Android 中的录音主要有两种方式 MediaRecorder 和 AudioRecord,下面分别介绍:

    MediaRecorder(基于文件)

    可以录制音、视频;

    封装了录制、编码、压缩、线程等功能,直接生成可播放的音频文件;

    优点:封装度高,操作简单

    缺点:编码格式有限,.aac  .amr  .3gp,但是没有 mp3、wav 格式。


    AudioRecord(基于字节流)

    只能录制音频;

    输出的是 PCM 的声音数据,如果保存成文件是不能直接播放的,需要编码;

    可以捕获音频流,边录制边处理,比如编码、变声、添加背景音乐。

    优点:更灵活

    缺点:需自行处理编码、开线程等工作

    应用场景:语音聊天、汤姆猫、K歌...


PCM:

    Pulse Code Modulation(脉冲编码调制,是对连续变化的模拟信号进行抽样、量化和编码产生的数字信号。

    它不是一种音频格式,它是声音文件的元数据,也就是声音的内容,没有文件头。经过某种格式的压缩、编码算法处理以后,再加上这种格式的文件头,才是这种格式的音频文件。


音频参数:

    采样频率:

            自然界的声音转换成数字格式时,要对它进行采样,每秒钟采样的次数就是采样率。就好比电影的1秒24帧画面。最常用:44.1kHz。


    采样位数:

            一个采样样本用多少位二进制数编码,最常用:16位。


    声道数:

            分为单声道和双声道,双声道又叫立体声,双声道音频文件比单声道大一倍。


    比特率(码率):

            每秒钟音频文件所占的 bit 数。单位 :kbps(每秒千比特数)。比特率(原始音频 PCM) = 采样频率 * 采样位数 * 声道数,这是未经压缩的比特率,压缩后会远小于这个值。

            采用44.1kHz采样频率、16位采样位数、双声道编码的原始音频 PCM 比特率为:1411.2 kbps 。而最常见的 mp3 格式的比特率为:128kbps,约 1MB/分钟。


    编码格式:

            将原始音频 PCM 采用特定压缩算法处理后,加上文件头,所保存成的文件的格式。例如 mp3、wav、aac...


编码格式:

    mp3

       是当今最流行的一种数字音频编码和有损压缩格式,就是将 PCM 通过算法进行压缩,常规 mp3 文件约为 1MB/分钟

    aac

        是 mp3 的下一代格式,也是有损压缩,相对于mp3,aac 格式的音频更佳,文件更小。ios 平台也支持,跨平台性好。

    wav

        最流行的非压缩数据格式,微软开发。

    amr

        压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音。


riff

    一种文件描述的格式,wav文件就采用了riff描述,前面44字节就是 riff 描述内容,就是文件头。


MediaRecorder

    首先在 AndroidManifest 配置文件中添加录音权限:

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

    当然 Android 6.0 以上还要动态获取权限,具体实现方式请百度之。


    // 录音对象声明
    private MediaRecorder mRecorder;
    
    private void startRecording() {
        // 创建录音对象
        mRecorder = new MediaRecorder();
        // 设置声音来源 MIC 即手机麦克风
        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        // 设置音频格式 aac
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
        // 设置录音文件
        mRecorder.setOutputFile(getExternalCacheDir() + "/demo.aac");
        // 设置编码器
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        
        try {
            // 准备录音
            mRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 开始录音
        mRecorder.start();
    }


    录音是耗时操作,但是,由于 MediaRecorder 已经封装了线程,故以上代码放在主线程即可。

    MediaRecorder 是很占资源的,使用完毕需要释放掉:


    private void stopRecording() {
        // 停止录音
        mRecorder.stop();
        // 释放资源
        mRecorder.release();
        // 引用置空
        mRecorder = null;
    }


AudioRecord

    首先在 AndroidManifest 配置文件中添加录音权限:

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

    当然 Android 6.0 以上还要动态获取权限,具体实现方式请百度之。


    // 录音状态
    private boolean isRecording = true;
    
    private void startRecording(){
        // 耗时操作要开线程
        new Thread(){
            @Override
            public void run() {
                // 音源
                int audioSource = MediaRecorder.AudioSource.MIC;
                // 采样率
                int sampleRate = 44100;
                // 声道数
                int channelConfig = AudioFormat.CHANNEL_IN_STEREO;//双声道
                // 采样位数
                int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
                // 获取最小缓存区大小
                int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
                // 创建录音对象
                AudioRecord audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, minBufferSize);
                try {
                    // 创建随机读写流
                    RandomAccessFile raf = new RandomAccessFile(getExternalCacheDir() + "/demo.wav", "rw");
                    // 留出文件头的位置
                    raf.seek(44);
                    byte[] buffer = new byte[minBufferSize];
    
                    // 录音中
                    audioRecord.startRecording();
                    isRecording = true;
                    while (isRecording) {
                        int readSize = audioRecord.read(buffer, 0, minBufferSize);
                        raf.write(buffer,0,readSize);
                    }
                    
                    // 录音停止
                    audioRecord.stop();
                    audioRecord.release();
                    
                    // 写文件头
                    WriteWaveFileHeader(raf, raf.length(),sampleRate,2,sampleRate*16*2/8);
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    /**
     * 为 wav 文件添加文件头,前提是在头部预留了 44字节空间
     *
     * @param raf
     *              随机读写流
     * @param fileLength
     *              文件总长
     * @param sampleRate
     *              采样率
     * @param channels
     *              声道数量
     * @param byteRate
     *              码率 = 采样率 * 采样位数 * 声道数 / 8
     * @throws IOException
     */
    private void WriteWaveFileHeader(RandomAccessFile raf, long fileLength, long sampleRate, int channels, long byteRate) throws IOException {
        long totalDataLen = fileLength + 36;
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        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);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1; // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (sampleRate & 0xff);
        header[25] = (byte) ((sampleRate >> 8) & 0xff);
        header[26] = (byte) ((sampleRate >> 16) & 0xff);
        header[27] = (byte) ((sampleRate >> 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);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (fileLength & 0xff);
        header[41] = (byte) ((fileLength >> 8) & 0xff);
        header[42] = (byte) ((fileLength >> 16) & 0xff);
        header[43] = (byte) ((fileLength >> 24) & 0xff);
        raf.seek(0);
        raf.write(header, 0, 44);
    } 

    以下是停止录音的方法:


   private void stopRecording() {
        // 停止录音
        isRecording = false;
    }


边录边播(扩音器)

    AudioRecord + AudioTrack

    

    // 录音状态
    private boolean isRecording = true;
    
    private void start() {
        // 耗时操作要开线程
        new Thread() {
            @Override
            public void run() {
                
                // 音源
                int audioSource = MediaRecorder.AudioSource.MIC;
                // 采样率
                int sampleRate = 8000;
                // 声道数
                int channelConfig = AudioFormat.CHANNEL_IN_STEREO;//双声道
                // 采样位数
                int audioFormat = AudioFormat.ENCODING_PCM_8BIT;
                // 获取录音最小缓存区大小
                int recorderBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
                // 创建录音对象
                AudioRecord audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, recorderBufferSize);
                
                // 音频类型
                int streamType = AudioManager.STREAM_MUSIC;
                // 静态音频还是音频流
                int mode = AudioTrack.MODE_STREAM;
                //  获取播放最小缓存区大小
                int playerBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
                // 创建播放对象
                AudioTrack audioTrack = new AudioTrack(streamType, sampleRate, channelConfig, audioFormat, playerBufferSize, mode);
                
                // 缓存区
                byte[] buffer = new byte[recorderBufferSize];
                
                // 录音中
                audioTrack.play();
                audioRecord.startRecording();
                isRecording = true;
                while (isRecording) {
                    audioRecord.read(buffer, 0, recorderBufferSize);
                    audioTrack.write(buffer, 0, buffer.length);
                }
                
                // 录音停止
                audioRecord.stop();
                audioTrack.stop();
                audioRecord.release();
                audioTrack.release();
            }
        }.start();
    }

     停止录音和播放:

    private void stop() {
        // 停止录音
        isRecording = false;
    }


        本篇介绍了 Android 中两种录音方式的对比,介绍了录音中会涉及到的参数和一些概念。用 MediaRecorder 实现了 aac 格式音频的录制,当然也可以录制 amr、3gp、mp4 格式;其他常用的格式如 wav,就需要用到 AudioRecord,以上也同样实现了,至于更常见的 mp3 格式的录制留在下一篇讲解。由于 AudioRecord 可以很方便的控制字节流,那么实时转码、边录边播功能都得以实现。



标签: MediaRecorder AudioRecord 录音 wav 边录边播

网站备案号:京ICP备11043289号-1 北京市公安局网络备案 海1101084571
版权所有 北京育灵童科技发展有限公司 Copyright © 2002-2024 www.elight.cn, All Rights Reserved