Android 音频开发(二) 采集一帧音频数据

Android 音频开发(二) 采集一帧音频数据,第1张

概述这一节主要介绍如何采集一帧音频数据,如果你对音频的基础概念比较陌生,建议看我的上一篇Android音频开发(一)基础入门篇。因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的基础知识后,开发过程中的很多参数和流程就会更加容易理解。1:AndroidSDK常用的2种音频采

这一节主要介绍如何采集一帧音频数据,如果你对音频的基础概念比较陌生,建议看我的上一篇Android 音频开发(一) 基础入门篇。因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的基础知识后,开发过程中的很多参数和流程就会更加容易理解。

@H_419_6@1:AndroID SDK 常用的2种音频采集API

AndroID SDK 提供了两套音频采集的API,分别如下:

MediaRecorder

MediaRecorder是更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件

AudioRecord

AudioRecord更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。

@H_419_6@2:MediaRecorder和AudioRecord区别和使用场景

如果想简单地做一个录音机,录制成音频文件,则推荐使用 MediaRecorder;而如果需要对音频做进一步的算法处理、或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议使用 AudioRecord,其实 MediaRecorder 底层也是调用了 AudioRecord 与 AndroID Framework 层的 audioflinger 进行交互的。

音频的开发,更广泛地应用不仅仅局限于本地录音,因此,我们需要重点掌握如何利用更加底层的 AudioRecord API 来采集音频数据(注意,使用它采集到的音频数据是原始的PCM格式,想压缩为mp3,aac等格式的话,还需要专门调用编码器进行编码)。下面就着重介绍AudioRecord的使用。

@H_419_6@3:AudioRecord 的工作流程

先看看AudioRecord 的构造函数,以及对应参数,官方代码如下:

    public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,            int bufferSizeInBytes)    throws IllegalArgumentException {        this((new AudioAttributes.Builder())                    .setInternalCapturePreset(audioSource)                    .build(),                (new AudioFormat.Builder())                    .setChannelMask(getChannelMaskFromLegacyConfig(channelConfig,                                        true/*allow legacy configurations*/))                    .setEnCoding(audioFormat)                    .setSampleRate(sampleRateInHz)                    .build(),                bufferSizeInBytes,                AudioManager.AUdio_SESSION_ID_GENERATE);    }

看构造方法你会发现有五个重要参数,它主要是靠构造函数来配置采集参数的,下面我们来一一解释这些参数的含义:

audioSource

audioSource是音频采集的输入源,可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAulT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAulT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等等。

sampleRateInHz

sampleRateInHz表示采样率,注意,目前44100Hz是唯一可以保证兼容所有AndroID手机的采样率。

channelConfig

通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)

audioFormat

这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCoding_PCM_16BIT(16bit),ENCoding_PCM_8BIT(8bit),注意,前者是可以保证兼容所有AndroID手机的。

bufferSizeInBytes

这个是最难理解又最重要的一个参数,它配置的是 AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,而前一篇文章介绍过,一帧音频帧的大小计算如下:

int bufferSizeInBytesSize= 采样率 x 位宽 x 采样时间 x 通道数

采样时间一般取 2.5ms~120ms 之间,由厂商或者具体的应用决定,我们其实可以推断,每一帧的采样时间取得越短,产生的延时就应该会越小,当然,碎片化的数据也就会越多。

由于AndroID的定制化比较严重,不建议采用以上的计算公式计算,幸好AudioRecord 类提供了一个帮助你确定这个 bufferSizeInBytes 的函数,源码如下:

/**     * Returns the minimum buffer size required for the successful creation of an AudioRecord     * object, in byte units.     * Note that this size doesn't guarantee a smooth recording under load, and higher values     * should be chosen according to the expected frequency at which the AudioRecord instance     * will be polled for new data.     * See {@link #AudioRecord(int, int, int, int, int)} for more information on valID     * configuration values.     * @param sampleRateInHz the sample rate expressed in Hertz.     *   {@link AudioFormat#SAMPLE_RATE_UnspecIFIED} is not permitted.     * @param channelConfig describes the configuration of the audio channels.     *   See {@link AudioFormat#CHANNEL_IN_MONO} and     *   {@link AudioFormat#CHANNEL_IN_STEREO}     * @param audioFormat the format in which the audio data is represented.     *   See {@link AudioFormat#ENCoding_PCM_16BIT}.     * @return {@link #ERROR_BAD_VALUE} if the recording parameters are not supported by the     *  harDWare, or an invalID parameter was passed,     *  or {@link #ERROR} if the implementation was unable to query the harDWare for its     *  input propertIEs,     *   or the minimum buffer size expressed in bytes.     * @see #AudioRecord(int, int, int, int, int)     */    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {        int channelCount = 0;        switch (channelConfig) {        case AudioFormat.CHANNEL_IN_DEFAulT: // AudioFormat.CHANNEL_CONfigURATION_DEFAulT        case AudioFormat.CHANNEL_IN_MONO:        case AudioFormat.CHANNEL_CONfigURATION_MONO:            channelCount = 1;            break;        case AudioFormat.CHANNEL_IN_STEREO:        case AudioFormat.CHANNEL_CONfigURATION_STEREO:        case (AudioFormat.CHANNEL_IN_FRONT | AudioFormat.CHANNEL_IN_BACK):            channelCount = 2;            break;        case AudioFormat.CHANNEL_INVALID:        default:            loge("getMinBufferSize(): InvalID channel configuration.");            return ERROR_BAD_VALUE;        }        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);        if (size == 0) {            return ERROR_BAD_VALUE;        }        else if (size == -1) {            return ERROR;        }        else {            return size;        }    }
@H_419_6@4:AudioRecord 的工作流程

配置参数,初始化内部的音频缓冲区

配置初始化参数,初始化参数大概有五个,具体的参数和说明见下面代码:

    /**     * 伴生对象:用来定义初始化的一些配置参数     */    companion object {        private const val TAG = "AudioCapturer"        //设置audioSource音频采集的输入源(可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAulT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAulT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用))        private const val DEFAulT_SOURCE = MediaRecorder.AudioSource.MIC        //设置sampleRateInHz采样率(注意,目前44100Hz是唯一可以保证兼容所有AndroID手机的采样率。)        private const val DEFAulT_SAMPLE_RATE = 44100        //设置channelConfig通道数,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)        private const val DEFAulT_CHANNEL_CONfig = AudioFormat.CHANNEL_IN_MONO        //设置audioFormat数据位宽,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCoding_PCM_16BIT(16bit),ENCoding_PCM_8BIT(8bit),注意,前者是可以保证兼容所有AndroID手机的。        private const val DEFAulT_AUdio_FORMAT = AudioFormat.ENCoding_PCM_16BIT        //注意还有第五个最重要参数bufferSizeInBytes,它配置的是 AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,一帧音频帧的大小计算如下        //int size = 采样率 x 位宽 x 采样时间 x 通道数(由于厂商的定制化,强烈建议通过AudioRecord类的getMinBufferSize方法确定bufferSizeInBytes的大小,getMinBufferSize方法:int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);)    }
开始采集
当配置好了初始化参数后,就可以通过构造函数创建好AudioRecord,创建好AudioRecord象之后,就可以开始进行音频数据的采集,通过AudioRecord.startRecording()函数控制采集。
 /**     * 开始采集     */    @JvmOverloads    fun startCapture(audioSource: Int = DEFAulT_SOURCE, sampleRateInHz: Int = DEFAulT_SAMPLE_RATE, channelConfig: Int = DEFAulT_CHANNEL_CONfig, audioFormat: Int = DEFAulT_AUdio_FORMAT): Boolean {        if (isCaptureStarted) {            Log.e(TAG, "Capture already started !")            return false        }        mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)        if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {            Log.e(TAG, "InvalID parameter !")            return false        }        Log.d(TAG, "getMinBufferSize = $mMinBufferSize bytes !")        mAudioRecord = AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, mMinBufferSize)        if (mAudioRecord!!.state == AudioRecord.STATE_UNINITIAliZED) {            Log.e(TAG, "AudioRecord initialize fail !")            return false        }        mAudioRecord!!.startRecording()        mIsLoopExit = false        mCaptureThread = Thread(AudioCaptureRunnable())        mCaptureThread!!.start()        isCaptureStarted = true        Log.d(TAG, "Start audio capture success !")        return true    }

开启线程,实时读取音频缓冲区

在读取缓冲区的时候我们会遇到过这样的问题,就是一直报**“overrun”**的错误,这是为什么了,原来是因为没有及时从AudioRecord 的缓冲区将音频数据“读”出来。所以我们要注意,在开启开启采集数据的时候,我们需要开线程实时的读取AudioRecord 的缓冲区的数据,读的过程一定要及时,否则就会出现“overrun”的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“取走”音频数据,导致内部的音频缓冲区溢出。

 /**     * 定义采集线程     */    private inner class AudioCaptureRunnable : Runnable {        overrIDe fun run() {            while (!mIsLoopExit) {                val buffer = ByteArray(mMinBufferSize)                val ret = mAudioRecord!!.read(buffer, 0, mMinBufferSize)                if (ret == AudioRecord.ERROR_INVALID_OPERATION) {                    Log.e(TAG, "Error ERROR_INVALID_OPERATION")                } else if (ret == AudioRecord.ERROR_BAD_VALUE) {                    Log.e(TAG, "Error ERROR_BAD_VALUE")                } else {                    if (mAudioFrameCapturedListener != null) {                        mAudioFrameCapturedListener!!.onAudioFrameCaptured(buffer)                    }                    Log.d(TAG, "OK, Captured $ret bytes !")                }            }        }    }
停止采集,释放资源
因为读取是用到了io流的技术,老生常谈的问题就是在停止采集的时候要关闭流,及时的释放资源。
  /**     * 停止采集,释放资源     */    fun stopCapture() {        if (!isCaptureStarted) {            return        }        mIsLoopExit = true        try {            mCaptureThread!!.interrupt()            mCaptureThread!!.join(1000)        } catch (e: InterruptedException) {            e.printstacktrace()        }        if (mAudioRecord!!.recordingState == AudioRecord.RECORDSTATE_RECORDING) {            mAudioRecord!!.stop()        }        mAudioRecord!!.release()        isCaptureStarted = false        mAudioFrameCapturedListener = null        Log.d(TAG, "Stop audio capture success !")    }

下面列出简单的完整封装列子如下:

@H_419_6@5:完整实例代码
package com.bnd.myaudioandvIDeo.utilsimport androID.media.AudioFormatimport androID.media.AudioRecordimport androID.media.MediaRecorderimport androID.util.Log/*** * AudioRecord简单封装 */class AudioCapturer {    private var mAudioRecord: AudioRecord? = null    private var mMinBufferSize = 0    private var mCaptureThread: Thread? = null    var isCaptureStarted = false        private set    @Volatile    private var mIsLoopExit = false    private var mAudioFrameCapturedListener: OnAudioFrameCapturedListener? = null    interface OnAudioFrameCapturedListener {        fun onAudioFrameCaptured(audioData: ByteArray?)    }    fun setonAudioFrameCapturedListener(Listener: OnAudioFrameCapturedListener?) {        mAudioFrameCapturedListener = Listener    }    /**     * 开始采集     */    @JvmOverloads    fun startCapture(audioSource: Int = DEFAulT_SOURCE, sampleRateInHz: Int = DEFAulT_SAMPLE_RATE, channelConfig: Int = DEFAulT_CHANNEL_CONfig, audioFormat: Int = DEFAulT_AUdio_FORMAT): Boolean {        if (isCaptureStarted) {            Log.e(TAG, "Capture already started !")            return false        }        mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)        if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {            Log.e(TAG, "InvalID parameter !")            return false        }        Log.d(TAG, "getMinBufferSize = $mMinBufferSize bytes !")        mAudioRecord = AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, mMinBufferSize)        if (mAudioRecord!!.state == AudioRecord.STATE_UNINITIAliZED) {            Log.e(TAG, "AudioRecord initialize fail !")            return false        }        mAudioRecord!!.startRecording()        mIsLoopExit = false        mCaptureThread = Thread(AudioCaptureRunnable())        mCaptureThread!!.start()        isCaptureStarted = true        Log.d(TAG, "Start audio capture success !")        return true    }    /**     * 停止采集,释放资源     */    fun stopCapture() {        if (!isCaptureStarted) {            return        }        mIsLoopExit = true        try {            mCaptureThread!!.interrupt()            mCaptureThread!!.join(1000)        } catch (e: InterruptedException) {            e.printstacktrace()        }        if (mAudioRecord!!.recordingState == AudioRecord.RECORDSTATE_RECORDING) {            mAudioRecord!!.stop()        }        mAudioRecord!!.release()        isCaptureStarted = false        mAudioFrameCapturedListener = null        Log.d(TAG, "Stop audio capture success !")    }    /**     * 定义采集线程     */    private inner class AudioCaptureRunnable : Runnable {        overrIDe fun run() {            while (!mIsLoopExit) {                val buffer = ByteArray(mMinBufferSize)                val ret = mAudioRecord!!.read(buffer, 0, mMinBufferSize)                if (ret == AudioRecord.ERROR_INVALID_OPERATION) {                    Log.e(TAG, "Error ERROR_INVALID_OPERATION")                } else if (ret == AudioRecord.ERROR_BAD_VALUE) {                    Log.e(TAG, "Error ERROR_BAD_VALUE")                } else {                    if (mAudioFrameCapturedListener != null) {                        mAudioFrameCapturedListener!!.onAudioFrameCaptured(buffer)                    }                    Log.d(TAG, "OK, Captured $ret bytes !")                }            }        }    }    /**     * 伴生对象:用来定义初始化的一些配置参数     */    companion object {        private const val TAG = "AudioCapturer"        //设置audioSource音频采集的输入源(可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAulT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAulT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用))        private const val DEFAulT_SOURCE = MediaRecorder.AudioSource.MIC        //设置sampleRateInHz采样率(注意,目前44100Hz是唯一可以保证兼容所有AndroID手机的采样率。)        private const val DEFAulT_SAMPLE_RATE = 44100        //设置channelConfig通道数,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)        private const val DEFAulT_CHANNEL_CONfig = AudioFormat.CHANNEL_IN_MONO        //设置audioFormat数据位宽,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCoding_PCM_16BIT(16bit),ENCoding_PCM_8BIT(8bit),注意,前者是可以保证兼容所有AndroID手机的。        private const val DEFAulT_AUdio_FORMAT = AudioFormat.ENCoding_PCM_16BIT        //注意还有第五个最重要参数bufferSizeInBytes,它配置的是 AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,一帧音频帧的大小计算如下        //int size = 采样率 x 位宽 x 采样时间 x 通道数(由于厂商的定制化,强烈建议通过AudioRecord类的getMinBufferSize方法确定bufferSizeInBytes的大小,getMinBufferSize方法:int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);)    }}

使用前要注意,添加如下权限:

<uses-permission androID:name="androID.permission.RECORD_AUdio" />
@H_419_6@6:总结

音频开发的知识点还是很多的,学习音频开发需要大家有足够的耐心,一步一个脚印的积累,只有这样才能把音频开发学好。如果你对基础知识比较模糊,建议先看我的上一篇博客《Android 音频开发(一) 基础入门篇》。下面推荐几个比较好的博主,希望对大家有所帮助。

csdn博主:《雷神雷霄骅》51CTO博客:《Jhuster的专栏》 总结

以上是内存溢出为你收集整理的Android 音频开发(二) 采集一帧音频数据全部内容,希望文章能够帮你解决Android 音频开发(二) 采集一帧音频数据所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1041454.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存