美文网首页Android知识Android技术知识Android开发
音视频开发03-android-AudioRecord录制PCM

音视频开发03-android-AudioRecord录制PCM

作者: 昵称真难选 | 来源:发表于2019-01-08 17:31 被阅读2次

    android平台上录制音频主要有两种方式,MediaRecorder、AudioRecord。

    • MediaRecorder 封装的层次比较高,可以直接将手机麦克风录入的音频数据进行编码压缩并存储,生成如AMR、MP3等音频文件。
    • AudioRecord 接近底层,录制的数据是PCM格式的原始音频裸数据,可以对其做进一步的算法处理、编码压缩等应用,能够自由进行开发控制。

    音频开发应用场景很多,仅仅录制保存满足不了大部分实际需求,所以,掌握 AudioRecord 的使用是必须的,本文针对AudioRecord的使用进行总结。

    AudioRecord 的使用主要有以下几个步骤:

    1. 初始化AudioRecord
    2. 初始化音频数据buffer
    3. 开启采集
    4. 启动新线程,从音频数据buffer中读取音频数据
    5. 停止采集、释放资源

    初始化AudioRecord

    
        public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
    
    • audioSource:音频源,在类 MediaRecorder.AudioSource 中以常量的形式定义,常用的有DEFAULT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAULT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等。
    • sampleRateInHz:音频采样率,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。
    • channelConfig:音频通道数,在类AudioFormat中以常量的形式定义,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道),注意,前者能够兼容所有android手机。
    • audioFormat:音频数据位宽(量化精度),在类AudioFormat中以常量的形式定义,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机。
    • bufferSizeInBytes:AudioRecord内部音频缓冲区大小,不能低于一帧音频的大小,而一帧音频的大小(int size = 采样率 x 位宽 x 采样时间 x 通道数)与音频采样时间有关,一般取 2.5ms~120ms 之间,不同厂商取值不同,所以AudioRecord类提供了 int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) 函数来获取改值,实际开发中,强烈建议由该函数计算出需要传入的 bufferSizeInBytes,而不是自己手动计算。虽然不同的厂商的底层实现是不一样的,但无外乎就是根据上面的计算公式得到一帧的大小,音频缓冲区的大小则必须是一帧大小的2~N倍。

    初始化音频数据buffer

    通过前面获取的 bufferSizeInBytes ,初始化读取音频数据的buffer。

    final byte[] data = new byte[minBufferSize];
    

    开启采集

    调用 AudioRecord 实例的 startRecording() 方法,开始采集音频

    特别注意:开始采集后,要尽快通过音频读取数据取走音频,一旦缓冲区中的数据大于前面设置的bufferSizeInBytes,会发生 over-running

    启动新线程,从音频数据buffer中读取音频数据

    开启新线程,调用 AudioRecord 实例的 public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) 方法,获取音频数据。

    停止采集、释放资源

    调用 AudioRecord 实例的 stop()release() 方法,将改实例置为 null。

    示例代码

    别忘了配录音权限

    public class MainActivity extends AppCompatActivity {
        //采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
        public static final int SAMPLE_RATE_INHZ = 44100;
        //声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
        public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
        //返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
        public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    
        Button btnBegin;
        Button btnEnd;
    
        private AudioRecord audioRecord;
        private boolean isRecording;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btnBegin = (Button) findViewById(R.id.btnBegin);
            btnEnd = (Button) findViewById(R.id.btnEnd);
    
            btnBegin.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    beginRecord();
                }
            });
    
            btnEnd.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    endRecorde();
                }
            });
        }
    
        private void beginRecord() {
            final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
            audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);
            final byte[] data = new byte[minBufferSize];
            audioRecord.startRecording();
            isRecording = true;
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (isRecording) {
                        int read = audioRecord.read(data, 0, minBufferSize);
                        if (read != AudioRecord.ERROR_INVALID_OPERATION) {
                            //处理音频数据 data
                        }
                    }
                }
            }).start();
        }
    
        private void endRecorde() {
            isRecording = false;
            if (audioRecord != null) {
                audioRecord.stop();
                audioRecord.release();
                audioRecord = null;
            }
        }
    }
    

    封装

    参考网上相关资源,这里给出一个简单的AudioRecord录音的封装类:AudioRecorder。

    import android.media.AudioFormat;
    import android.media.AudioRecord;
    import android.media.MediaRecorder;
    import android.util.Log;
    
    /**
     * Created by wangyt on 2018/10/26
     */
    public class AudioRecorder {
    
        private static final String TAG = "AudioRecorder";
    
        public interface OnAudioDataArrivedListener {
            void onAudioDataArrived(byte[] audioData);
        }
    
        //声源
        private static final int DEFFAULT_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
        //采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
        private static final int DEFAULT_SAMPLE_RATE_INHZ = 44100;
        //声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
        private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
        //返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
        private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
        //内部缓冲区大小
        private int minBufferSize = 0;
        //是否已启动录音
        private boolean isStarted = false;
        //是否可以从缓冲区中读取数据
        private boolean canReadDataFromBuffer = true;
        //从缓冲区中读取数据的回调方法
        private OnAudioDataArrivedListener onAudioDataArrivedListener;
    
        private AudioRecord audioRecord;
    
    
        public boolean startRecord() {
            return startRecord(DEFFAULT_AUDIO_SOURCE,
                    DEFAULT_SAMPLE_RATE_INHZ,
                    DEFAULT_CHANNEL_CONFIG,
                    DEFAULT_AUDIO_FORMAT);
        }
    
        public boolean startRecord(int audioSource, int sampleRate, int channel, int audioFormat) {
            if (isStarted) {
                Log.e(TAG, "startRecord: AudioRecorder has been already started");
                return false;
            }
    
            //获取内部缓冲区最小size
            minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat);
            if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
                Log.e(TAG, "startRecord: minBufferSize is error_bad_value");
                return false;
            }
            Log.d(TAG, "startRecord: minBufferSize = " + minBufferSize + "bytes");
    
            //初始化 audioRecord
            audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, minBufferSize);
            if (audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
                Log.e(TAG, "startRecord: audioRecord is uninitialized");
                return false;
            }
    
            //启动录制
            audioRecord.startRecording();
    
            //可以从内部缓冲区中读取数据
            canReadDataFromBuffer = true;
    
            //启动子线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (canReadDataFromBuffer){
                        //初始化缓冲区数据接收数组
                        byte[] data = new byte[minBufferSize];
    
                        //读取内部缓冲区中读取数据
                        int result = audioRecord.read(data, 0, minBufferSize);
    
                        if (result == AudioRecord.ERROR_BAD_VALUE){
                            Log.e(TAG, "run: audioRecord.read result is ERROR_BAD_VALUE");
                        }else if (result == AudioRecord.ERROR_INVALID_OPERATION){
                            Log.e(TAG, "run: audioRecord.read result is ERROR_INVALID_OPERATION");
                        }else {
                            if (onAudioDataArrivedListener != null){
                                //调用读取数据回调方法
                                onAudioDataArrivedListener.onAudioDataArrived(data);
                            }
                            Log.d(TAG, "run: audioRecord read " + result + "bytes");
                        }
                    }
                }
            }).start();
    
            //设置录音已启动
            isStarted = true;
            Log.d(TAG, "startRecord: audioRecorder has been already started");
            return true;
        }
    
        public void stopRecord(){
            //如果录音尚未启动,直接返回
            if (!isStarted) return;
            //设置内部缓冲区数据不可读取
            canReadDataFromBuffer = false;
            //停止录音
            if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){
                audioRecord.stop();
            }
            //释放资源
            audioRecord.release();
            //设置录音未启动
            isStarted = false;
            //回调置为空
            onAudioDataArrivedListener = null;
        }
    }
    

    相关文章

      网友评论

        本文标题:音视频开发03-android-AudioRecord录制PCM

        本文链接:https://www.haomeiwen.com/subject/scybrqtx.html