本章仅对部分代码进行讲解,以帮助读者更好的理解章节内容。
本系列文章涉及的项目HardwareVideoCodec已经开源到Github。目前已迭代多个稳定版本,欢迎查阅学习和使用,如有BUG或建议,欢迎Issue。
在上一章我们讲到了MediaCodec的工作流程,以及如何利用MediaCodec进行H264编码。这一章的内容同样是MediaCodec,只不过是编码音频为AAC,整个流程大同小异。
上一章我们利用MediaCodec编码视频时,使用了Surface,所以可以不直接操作输入缓冲区队列。但是编码音频的时候,由于无法使用Surface,所以需要直接操作输入缓冲区队列。
这里我们需要通过AudioRecord采集PCM数据,然后把采集到的数据送进编码器进行编码。所以首先我们要初始化一个AudioRecord对象。
要使用录音,需要申请录音权限。
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
然后初始化AudioRecorder对象,初始化完成后就可以开始录制音频了。当然,这些操作都需要在子线程中进行。最后通过循环不停的从AudioRecorder中读取PCM数据,并通过回调把PCM数据发送给MediaCodec进行编码。
/**
* 初始化AudioRecord对象
*/
private fun config(){
/**
* 计算缓存PCM数据的Buffer最小大小
* parameter.audio.sampleRateInHz = 16000
* parameter.audio.pcm = AudioFormat.ENCODING_PCM_16BIT
* parameter.audio.samplePerFrame = 1024
* parameter.video.fps = 30
*
*/
val minBufferSize = AudioRecord.getMinBufferSize(parameter.audio.sampleRateInHz,
AudioFormat.CHANNEL_IN_MONO, parameter.audio.pcm)
/**
* 计算buffer大小
*/
bufferSize = parameter.audio.samplePerFrame * parameter.video.fps
if (bufferSize < minBufferSize)
bufferSize = (minBufferSize / parameter.audio.samplePerFrame + 1) * parameter.audio.samplePerFrame * 2
debug_e("bufferSize: $bufferSize")
/**
* 新建储存PCM数据发Buffer
*/
buffer = ByteArray(parameter.audio.samplePerFrame)
/**
* 新建AudioRecord对象
*/
record = AudioRecord(MediaRecorder.AudioSource.MIC, parameter.audio.sampleRateInHz,
AudioFormat.CHANNEL_IN_MONO, parameter.audio.pcm, bufferSize)
/**
* 开始录制音频
*/
record?.startRecording()
}
private fun read() {
/**
* 读取PCM数据
*/
val bufferReadResult = record!!.read(buffer, 0, parameter.audio.samplePerFrame)
onPCMListener?.onPCMSample(buffer!!)
}
override fun run() {
while (mStart) {
read()
}
}
在正确拿到PCM数据后,就可以用MediaCodec进行编码了。我们先创建一个编码器格式对象,用来配置MediaCodec。
fun createAudioFormat(parameter: Parameter, ignoreDevice: Boolean = false): MediaFormat? {
val mediaFormat = MediaFormat()
/**
* 编码格式AAC:parameter.audio.mime = "audio/mp4a-latm"
* 声道数量:parameter.audio.channel = 1
* 频率:parameter.audio.sampleRateInHz = 16000
* 码率:parameter.audio.bitrate = 64000
* Level:parameter.audio.profile = MediaCodecInfo.CodecProfileLevel.AACObjectLC
*/
mediaFormat.setString(MediaFormat.KEY_MIME, parameter.audio.mime)
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, parameter.audio.channel)
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, parameter.audio.sampleRateInHz)
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, parameter.audio.bitrate)
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, parameter.audio.profile)
return mediaFormat
}
有了MediaFormat后,我们就可以开始创建编码器了。
private fun initCodec() {
val format = CodecHelper.createAudioFormat(parameter)
try {
codec = MediaCodec.createEncoderByType(format?.getString(MediaFormat.KEY_MIME))
codec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
codec?.start()
audioWrapper = AudioRecordWrapper(parameter)
audioWrapper?.setOnPCMListener(this)
} catch (e: Exception) {
debug_e("Can not create codec")
} finally {
if (null == codec)
debug_e("Can not create codec")
}
}
初始化之后通过OnPCMListener回调接收上文返回的PCM数据,并送入MediaCodec进行编码。最后通过循环从编码器输出缓冲区中拿出AAC数据。这里通过回调把AAC数据送进MediaMuxer进行音视频混合,最后生成mp4文件。
/**
* 把PCM数据送入编码器的输入缓存队列
*/
private fun encode(buffer: ByteArray) {
try {
pTimer.record()
/**
* 获取输入缓存队列
*/
inputBuffers = codec!!.inputBuffers
/**
* 输入输出缓存队列
*/
outputBuffers = codec!!.outputBuffers
/**
* 从编码器中获取一个缓冲区的下标
*/
val inputBufferIndex = codec!!.dequeueInputBuffer(WAIT_TIME)
if (inputBufferIndex >= 0) {
/**
* 通过下标获取缓冲区
*/
val inputBuffer = inputBuffers!![inputBufferIndex]
inputBuffer.clear()
/**
* 把PCM数据送入缓冲区
*/
inputBuffer.put(buffer)
/**
* 把带有PCM的数据缓冲区送进编码器
*/
codec!!.queueInputBuffer(inputBufferIndex, 0, buffer.size, 0, 0)
}
/**
* 从编码器中获取编码后的数据
*/
dequeue()
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 从编码器中获取编码后的数据
*/
private fun dequeue(): Boolean {
try {
/**
* 从输出缓冲区取出一个Buffer,返回一个状态
* 这是一个同步操作,所以我们需要给定最大等待时间WAIT_TIME,一般设置为10000ms
*/
val flag = codec!!.dequeueOutputBuffer(bufferInfo, WAIT_TIME)
when (flag) {
MediaCodec.INFO_TRY_AGAIN_LATER -> {//等待超时,需要再次等待,通常忽略
return false
}
/**
* 输出格式改变,很重要
* 这里必须把outputFormat设置给MediaMuxer,而不能不能用inputFormat代替,它们时不一样的,不然无法正确生成mp4文件
*/
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
debug_v("AUDIO INFO_OUTPUT_FORMAT_CHANGED")
onSampleListener?.onFormatChanged(codec!!.outputFormat)
}
else -> {
if (flag < 0) return@dequeue false//如果小于零,则跳过
val data = codec!!.outputBuffers[flag]//否则代表编码成功,可以从输出缓冲区队列取出数据
if (null != data) {
val endOfStream = bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM
if (endOfStream == 0) {//如果没有收到BUFFER_FLAG_END_OF_STREAM信号,则代表输出数据时有效的
bufferInfo.presentationTimeUs = pTimer.presentationTimeUs
//通过回调,把编码后的数据送进MediaMuxer
onSampleListener?.onSample(bufferInfo, data)
}
//缓冲区使用完后必须把它还给MediaCodec,以便再次使用,至此一个流程结束,再次循环
codec!!.releaseOutputBuffer(flag, false)
// if (endOfStream == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
// return true
// }
return true
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
以上就是本章关于MediaCodec编码PCM的全部学习内容,比较简单,关于MediaCodec的使用在第四章已经有了很详细的讲解,使用MediaCodec编码音视频的流程都是一样的。如果理解还不够透彻,欢迎查阅学习第四章。如果有疑问或者错误,欢迎在评论区留言。
本章知识点:
- 使用MediaCodec进行AAC编码。
本章相关源码·HardwareVideoCodec项目:
网友评论