我正在参与「启航方案」
PS:本周关键词『考虑』、『行动』、『坚持』、『自律』。
本文介绍些 Android
音视频开发中的AudioRecord
的运用,案例将会在前面MediaCodec
录制MP4
的基础上进行,运用AudioRecord
将音频数据组成到MP4
中,Android
音视频同系列文章如下:
- 音视频开发基础知识
- 音频帧、视频帧及其同步
- Camera2、MediaCodec录制mp4
- Android原生编解码接口MediaCodec详解
- 音频基础知识
本文的首要内容如下:
- AudioRecord介绍
- AudioRecord生命周期
- AudioRecord音频数据读取
- 直接缓冲区和字节序(选)
- AudioRecord运用
AudioRecord介绍
AudioRecord
是 Android 中用来录制硬件设备的音频工具,经过 pulling
的方法获取音频数据,一般用来取得原始音频 PCM
格式的数据,能够完成边录边播,多用于音频数据的实时处理。
创立AudioRecord
的参数及阐明如下:
// 创立AudioRecord
public AudioRecord (int audioSource,
int sampleRateInHz,
int channelConfig,
int audioFormat,
int bufferSizeInBytes)
- audioSource:表明音频源,音频源界说在
MediaRecorder.AudioSource
中,如常见的音频源主麦克风MediaRecorder.AudioSource.MIC
等。 - sampleRateInHz:表明以赫兹为单位的采样率,其意义是每个通道每秒的采样数,常见采样率中只要 44100Hz 的采样率能够确保在所有设备上正常运用,能够经过
getSampleRate
获取实际采样率,这个采样率不是音频内容播映的采样率,比方能够在采样率为 48000Hz 的设备上播映采样率为 8000Hz 的声响,对应平台会自动处理采样率转换,因此不会以 6 倍的速度播映。 - channelConfig:表明声道数,声道界说在
AudioFormat
中,常见的声道中只要单声道AudioFormat.CHANNEL_IN_MONO
能确保在所有设备上正常运用,其他的比方AudioFormat.CHANNEL_IN_STEREO
表明双声道,也便是立体声。 - audioFormat:表明
AudioRecord
回来的音频数据的格式,关于线性PCM
来说,反响每个样本巨细(8、16、32位)及表现形式(整型、浮点型),音频格式界说在AudioFormat
中,常见的音频数据格式中只要AudioFormat.ENCODING_PCM_16BIT
能够确保在所有的设备上正常运用,像AudioFormat.ENCODING_PCM_8BIT
不能确保在所有设备上正常运用。 - bufferSizeInBytes:表明写入音频数据的缓冲区的巨细,该值不能小于
getMinBufferSize
的巨细,即不能小于AudioRecord
所需的最小缓冲区的巨细,否则将导致AudioRecord
初始化失利,该缓冲区巨细并不能确保在负载状况下顺利录制,必要时可选择更大值。
AudioRecord生命周期
AudioRecord
的生命周期状况包括 STATE_UNINITIALIZED
、STATE_INITIALIZED
、RECORDSTATE_RECORDING
和RECORDSTATE_STOPPED
,分别对应未初始化、已初始化、录制中、停止录制,如下图所示:
简略阐明一下:
- 未创立之前或者
release
之后AudioRecord
都进入STATE_UNINITIALIZED
状况。 - 创立
AudioRecord
时进入STATE_INITIALIZED
状况。 - 调用
startRecording
进入RECORDSTATE_RECORDING
状况。 - 调用
stop
进入RECORDSTATE_STOPPED
状况。
那么怎么获取AudioRecord
的状况呢,能够经过getState
和getRecordingState
获取其状况,为确保正确运用可在运用AudioRecord
目标操作之前进行其状况的判别。
AudioRecord音频数据读取
AudioRecord
提供的三种读取音频数据的方法,如下:
// 1. 读取音频数据,音频格式为AudioFormat#ENCODING_PCM_8BIT
int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)
// 2. 读取音频数据,音频格式为AudioFormat#ENCODING_PCM_16BIT
int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts)
// 3. 读取音频数据,见后边章节
int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes)
读取音频数据的回来值大于等于 0,读取音频数据常见异常如下:
- ERROR_INVALID_OPERATION:表明
AudioRecord
未初始化。 - ERROR_BAD_VALUE:表明参数无效。
- ERROR_DEAD_OBJECT:表明现已传输了一些音频数据的状况下不回来错误码,将在下次
read
回来处回来错误码。
上面三个 read
函数都是从硬件音频设备读取音频数据,前两个首要的差异便是音频格式不同,分别是 8 位、16 位,对应的量化等级则是 2^8 和 2^16 量化等级。
第三个read
函数在读取音频数据时,会将其记录在直接缓冲区(DirectBuffer
)中,假如此缓冲区不是 DirectBuffer
则一直回来 0,也便是运用第三个read
函数时传入的参数audioBuffer
有必要是一个 DirectBuffer
,否则不能正确读取到音频数据,此时,该Buffer
的position
将坚持不变,缓冲区中的数据的音频格式则取决于AudioRecord
中指定的格式,且字节寄存的方法为本机字节序。
直接缓冲区和字节序
上面提到了两个概念直接缓冲区和自己许,这儿简略阐明一下:
直接缓冲区
DirectBuffer
是 NIO 里面的东西,这儿简略看下一般缓冲区和直接缓冲区的一些差异。
- 一般缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
可知一般缓冲区从堆上分配一个字节缓冲区,该缓冲区受 JVM 的办理,意味着在合适的时分是能够被 GC 回收的,GC 回收伴随着内存的收拾,某种程度上对功能是有影响的。
- 直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
public static ByteBuffer allocateDirect(int capacity) {
// Android-changed: Android's DirectByteBuffers carry a MemoryRef.
// return new DirectByteBuffer(capacity);
DirectByteBuffer.MemoryRef memoryRef = new DirectByteBuffer.MemoryRef(capacity);
return new DirectByteBuffer(capacity, memoryRef);
}
上面是 Android 中的DirectBuffer
的完成,可见是从内存中分配的,这种方法取得的缓冲区的获取成本是释放成本都是巨大的,可是能够驻留在废物回收堆的外部,一般分配给大型、寿命长的缓冲区,最后分配此缓冲区能够带来显著的功能提高才进行分配,是否是DirectBuffer
能够经过 isDirect
来确定。
字节序
字节序指的是字节在内存中的寄存方法,字节序首要分为两类:BIG-ENDIAN和LITTLE-ENDIAN,通俗的称之为网络字节序和本机字节序,具体如下:
- 本机字节序,即 LITTLE-ENDIAN(小字节序、低字节序),即低位字节排放在内存的低地址端,高位字节排放在内存的高地址端,与之对应的还有网络字节序。
- 网络字节序,一般指的是 TCP/IP 协议中运用的字节序,因为 TCP/IP 各层协议将字节序界说为 BIG-ENDIAN,所以网络字节序一般指的是 BIG-ENDIAN。
AudioRecord的运用
记得在前面的文章 Camera2、MediaCodec录制mp4 中仅仅录制了视频,侧重于MediaCodec
的运用,这儿将在视频录制的基础上运用AudioRecord
增加音频的录制,并将其组成到MP4
文件中,其关键步骤如下:
- 开启一个线程运用
AudioRecord
读取硬件的音频数据,开线程能够防止卡顿,文末案例中也有代码示例,见AudioEncode2
,参阅如下:
/**
* 音频读取Runnable
*/
class RecordRunnable : Runnable{
override fun run() {
val byteArray = ByteArray(bufferSize)
// 录制状况 -1表明默认状况,1表述录制状况,0表明停止录制
while (recording == 1){
val result = mAudioRecord.read(byteArray, 0, bufferSize)
if (result > 0){
val resultArray = ByteArray(result)
System.arraycopy(byteArray, 0, resultArray, 0, result)
quene.offer(resultArray)
}
}
// 自界说流完毕的数据
if (recording == 0){
val stopArray = byteArrayOf((-100).toByte())
quene.offer(stopArray)
}
}
}
这儿提一下,假如仅仅运用AudioRecord
录制音频数据,当读取到音频数据可将音频数据写入文件即可。
- 读取到音频数据要想组成到
MP4
中需要先进行音频数据的编码,音频数据编码器配置如下:
// 音频数据编码器配置
private fun initAudioCodec() {
L.i(TAG, "init Codec start")
try {
val mediaFormat =
MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_AAC,
RecordConfig.SAMPLE_RATE,
2
)
mAudioCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000)
mediaFormat.setInteger(
MediaFormat.KEY_AAC_PROFILE,
MediaCodecInfo.CodecProfileLevel.AACObjectLC
)
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 8192)
mAudioCodec.setCallback(this)
mAudioCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
} catch (e: Exception) {
L.i(TAG, "init error:${e.message}")
}
L.i(TAG, "init Codec end")
}
关于编码也便是MediaCodec
的运用能够参阅前面下面两篇文章:
- MediaCodec详解
- Camera2、MediaCodec录制mp4
这儿运用MediaCodec
的异步处理模式进行音频数据的编码,这儿将不贴代码了,留意一点便是填充和释放Buffer
的时分一定要判别条件,假如InputBuffer
一直不释放则会导致无可用的InputBuffer
运用导致音频编码失利,还有便是流完毕的处理。
- 文件的组成运用
MediaMuxer
,MediaMuxer
在发动之前有必要确保增加好视轨和音轨
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
L.i(TAG, "onOutputFormatChanged format:${format}")
// 增加音轨
addAudioTrack(format)
// 假如音轨和视轨都增加的状况下才发动MediaMuxer
if (RecordConfig.videoTrackIndex != -1) {
mAudioMuxer.start()
RecordConfig.isMuxerStart = true
L.i(TAG, "onOutputFormatChanged isMuxerStart:${RecordConfig.isMuxerStart}")
}
}
// 增加音轨
private fun addAudioTrack(format: MediaFormat) {
L.i(TAG, "addAudioTrack format:${format}")
RecordConfig.audioTrackIndex = mAudioMuxer.addTrack(format)
RecordConfig.isAddAudioTrack = true
}
// ...
AudioRecord
的运用根本如上,AudioRecord
录制音频并组成到 MP4
中的相关代码能够在大众号【躬行之】后台回复关键字关键字【record】获取。