天天看点

android平台下基于MediaRecorder和AudioRecord实现录制AAC、PCM音频数据

音视频实践学习

  • android全平台编译ffmpeg以及x264与fdk-aac实践
  • ubuntu下使用nginx和nginx-rtmp-module配置直播推流服务器
  • android全平台编译ffmpeg合并为单个库实践
  • android-studio使用cmake编译ffmpeg实践
  • android全平台下基于ffmpeg解码MP4视频文件为YUV文件
  • android全平台编译ffmpeg支持命令行实践
  • android全平台基于ffmpeg解码本地MP4视频推流到RTMP服务器
  • android平台下音频编码之编译LAME库转码PCM为MP3
  • ubuntu平台下编译vlc-android视频播放器实践
  • 图解YU12、I420、YV12、NV12、NV21、YUV420P、YUV420SP、YUV422P、YUV444P的区别
  • 图解RGB565、RGB555、RGB16、RGB24、RGB32、ARGB32等格式的区别
  • YUV420P、YUV420SP、NV12、NV21和RGB互相转换并存储为JPEG以及PNG图片
  • android全平台编译libyuv库实现YUV和RGB的转换
  • android平台下基于ffmpeg对相机采集的NV21数据编码为MP4视频文件
  • android平台下基于ffmpeg采集Camera数据编码成H.264推流到RTMP服务器
  • android平台下基于ffmpeg和ANativeWindow实现简单的视频播放器
  • android平台下基于ffmpeg实现对相机预览截图的功能(NV21数据编码为JPEG文件)
  • android平台下基于ffmpeg的swscale模块实现对YUV和RGB数据进行转换
  • android平台下基于MediaRecorder和AudioRecord实现录制AAC、PCM音频数据

概述

android sdk

中提供了两种方式来实现音频的采集:

MediaRecorder

AudioRecord

,其中的

MediaRecorder

处于更上层,它可以对音频录制的数据编码成

AMR

,

MP3

等格式,并存储为文件,而

AudioRecord

则更灵活,因为它可以录制最原始的

PCM流数据

,这个在直播中很常见。

录制接口

使用不同的方式处理音频,API肯定有所不同,这里暂时先抽象一个接口:

public interface IAudioRecorder {


    interface RECORD_STATE {
        int STATE_RECORDING = 0;
        int STATE_SUCCESS = 1;
        int STATE_ERROR = 2;
    }

    /**
     * 初始化
     */
    void initRecorder();

    /**
     * 开始录制音频
     */
    int recordStart();

    /**
     * 停止录制音频
     */
    void recordStop();

}
           

基于MediaRecorder

系统都已经给你提供好了的轮子,我们只需要设置几个参数就可以了:

public class MediaRecordRecorder implements IAudioRecorder {

    /**
     * 基于MediaRecorder录制音频
     */
    private MediaRecorder mMediaRecorder;

    /**
     * 是否正在录制
     */
    private boolean isRecord = false;

    private String filePath;

    public MediaRecordRecorder(String filePath){
        this.filePath = filePath;
    }

    @Override
    public void initRecorder() {
        //实例化MediaRecorder对象
        mMediaRecorder = new MediaRecorder();

        //从麦克风采集声音数据
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        //设置输出格式为MP4
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        //设置采样频率44100 频率越高,音质越好,文件越大
        mMediaRecorder.setAudioSamplingRate(44100);
        //设置声音数据编码格式,音频通用格式是AAC
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        //设置编码频率
        mMediaRecorder.setAudioEncodingBitRate(96000);
        //设置输出文件
        mMediaRecorder.setOutputFile(filePath);
    }

    @Override
    public int recordStart() {
        //判断是否有外部存储设备sdcard
        if (isRecord) {
            return RECORD_STATE.STATE_RECORDING;
        } else {
            if (mMediaRecorder != null) {
                try {
                    mMediaRecorder.prepare();
                    mMediaRecorder.start();
                    // 让录制状态为true
                    isRecord = true;
                    return RECORD_STATE.STATE_SUCCESS;
                } catch (IOException e) {
                    e.printStackTrace();

                }
            }
            return RECORD_STATE.STATE_ERROR;
        }
    }

    @Override
    public void recordStop() {
        if (mMediaRecorder != null) {
            isRecord = false;
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
    }

}
           

笔者这里只是作演示操作,因此部分参数直接硬编码,实际过程最好以参数的形式对外提供

基于AudioRecord

这种方式需要我们自己在线程中,循环读取音频数据:

public class AudioRecordRecorder implements IAudioRecorder {

    private static final String TAG = "AudioRecordRecorder";

    private int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;

    /**
     * 音频采样率
     */
    public static int SAMPLE_RATE = 44100;

    /**
     * 单声道
     */
    public final static int CHANNEL = AudioFormat.CHANNEL_IN_STEREO;

    /**
     * 16比特
     */
    public final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;

    /**
     * 音频录制实例
     */
    protected AudioRecord audioRecord;

    /**
     * 录制线程
     */
    private Thread recordThread;

    /**
     * 输出的文件路径
     */
    private String pcmPath;

    /**
     * 缓冲区大小
     */
    private int bufferSize = 0;

    /**
     * 是否正在录制
     */
    private boolean isRecording = false;

    /**
     * 回调原始的PCM数据
     */
    private OnAudioRecordListener mOnAudioRecordListener;

    public AudioRecordRecorder(String filePath) {
        this.pcmPath = filePath;
    }

    @Override
    public void initRecorder() {
        if (null != audioRecord) {
            audioRecord.release();
        }
        try {
            bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AUDIO_FORMAT);
            audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, bufferSize);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public int recordStart() {
        if (isRecording) {
            return RECORD_STATE.STATE_RECORDING;
        } else if (audioRecord != null && audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
            try {
                audioRecord.startRecording();
                isRecording = true;
                recordThread = new Thread(new AudioRecordRunnable());
                recordThread.start();
                return RECORD_STATE.STATE_SUCCESS;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return RECORD_STATE.STATE_ERROR;
    }

    /**
     * 停止音频录制
     */
    @Override
    public void recordStop() {
        try {
            if (audioRecord != null) {
                isRecording = false;
                try {
                    if (recordThread != null) {
                        recordThread.join();
                        recordThread = null;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //释放资源
                recordRelease();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void recordRelease() {
        if (audioRecord != null) {
            if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
                audioRecord.stop();
            }
            audioRecord.release();
            audioRecord = null;
        }
    }

    private class AudioRecordRunnable implements Runnable {

        private FileOutputStream outputStream = null;

        @Override
        public void run() {
            try {
                if (!TextUtils.isEmpty(pcmPath)) {
                    outputStream = new FileOutputStream(pcmPath);
                }
                byte[] audioBuffer = new byte[bufferSize];
                while (isRecording && audioRecord != null) {
                    int audioSampleSize = audioRecord.read(audioBuffer, 0, bufferSize);
                    if (audioSampleSize > 0) {
                        if (outputStream != null) {
                            outputStream.write(audioBuffer);
                        }
                        if (mOnAudioRecordListener != null) {
                            mOnAudioRecordListener.onAudioBuffer(audioBuffer, audioBuffer.length);
                        }
                    }
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                close(outputStream);
                outputStream = null;
            }
        }
    }

    private void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void setOnAudioRecordListener(OnAudioRecordListener onAudioRecordListener) {
        this.mOnAudioRecordListener = onAudioRecordListener;
    }

    public interface OnAudioRecordListener {

        void onAudioBuffer(byte[] buffer, int length);

    }

}
           

工程实践

新建工程

ffmpeg-audio-encode

,在应用的点击事件中处理如下:

/**
     * 点击开始录制按钮
     * @param view
     */
    public void onRecordStart(View view) {
        File targetDir = getExternalFilesDir(null);
        if (audioRecorder == null) {
            audioRecorder = new MediaRecordRecorder(targetDir.getAbsolutePath()+ File.separator+"output.aac");
            //audioRecorder = new AudioRecordRecorder(targetDir.getAbsolutePath()+ File.separator+"output.pcm");
        }
        audioRecorder.initRecorder();
        audioRecorder.recordStart();
        //更新按钮状态
        mBtnStart.setEnabled(false);
        mBtnStop.setEnabled(true);
    }

    /**
     * 点击停止按钮
     * @param view
     */
    public void onRecordStop(View view) {
        if (audioRecorder != null) {
            audioRecorder.recordStop();
            audioRecorder = null;
        }
        //更新按钮状态
        mBtnStart.setEnabled(true);
        mBtnStop.setEnabled(false);
    }
           

基于

MediaRecorder

方式我们可以直接得到

output.aac

文件,这个直接使用系统的

音频播放器

即可播放

android平台下基于MediaRecorder和AudioRecord实现录制AAC、PCM音频数据

基于

AudioRecord

方式我们可以直接得到

output.ocm

文件,需要导出到电脑上,使用

ffplay播放器

进行播放

ffplay -ar 44100 -channels 2 -f s16le -i output.pcm
           
android平台下基于MediaRecorder和AudioRecord实现录制AAC、PCM音频数据

项目地址:

ffmpeg-audio-encode

https://github.com/byhook/ffmpeg4android