这一节主要介绍如何采集一帧音频数据,如果你对音频的基础概念比较陌生,建议看我的上一篇Android 音频开发(一) 基础入门篇。因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的基础知识后,开发过程中的很多参数和流程就会更加容易理解。
@H_419_6@1:AndroID SDK 常用的2种音频采集APIAndroID 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 音频开发(二) 采集一帧音频数据所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)