原文 : https://juejin.cn/post/69601302052266311754
本文主要使用 MediaCodec
硬编码器对 Android
设备采集的音视频编码
示例链接
ICodec
:interface ICodec {
//入队
fun putBuf(data: ByteArray, offset: Int, size: Int)
//处理数据
fun dealWith(data: ByteArray)
//停止线程
fun stopWorld()
}
BaseCodec
:音视频逐帧编码,编码是一项耗时任务,所以需要定义一队列来存储需要编码的数据帧.
BaseCodec
为抽象类并且实现了ICodec接口,在任务启动时不停的从队列中取出数据,dealWith方法进行处理
abstract class BaseCodec : Thread(), ICodec {
val inBlockingQueue = ArrayBlockingQueue(30)
override fun putBuf(data: ByteArray, offset: Int, size: Int) {
val byteArray = ByteArray(size)
System.arraycopy(data, offset, byteArray, 0, size)
inBlockingQueue.put(byteArray)
}
var threadRunning = true;
override fun run() {
try {
BaseCodecLoop1@ while (threadRunning) {
val item = inBlockingQueue.take()
dealWith(item)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun dealWith(data: ByteArray) {}
override fun stopWorld() {
inBlockingQueue.clear()
threadRunning = false;
interrupt()
join(1000)
}
}
const val SAMPLE_RATE_IN_HZ = 44100 //采样率44.1KHz
const val CHANNEL = AudioFormat.CHANNEL_IN_MONO //单声道,立体声:CHANNEL_IN_STEREO
const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT //每个采样点16bit
const val DEST_BIT_RATE = 128000 //编码码率
MediaCodec
音视频编码基础类 BaseMediaCodec
:该类包含
/**
* mime类型对应的格式
* video/avc: h.264
* video/hevc: h.265
* audio/mp4a-latm: aac
*/
CodecListener
audio/mp4a-latm
编码器createCodec("audio/mp4a-latm")
val format = MediaFormat.createAudioFormat(mime, SAMPLE_RATE_IN_HZ, CHANNEL)
format.setInteger(MediaFormat.KEY_BIT_RATE, DEST_BIT_RATE)
//buffer 最大值
val bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ, CHANNEL, AUDIO_FORMAT)
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize)
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
configEncoderBitrateMode(format)
codec.start()
override fun bufferUpdate(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) {
buffer.position(bufferInfo.offset)
buffer.limit(bufferInfo.offset + bufferInfo.size)
val data = ByteArray(bufferInfo.size + 7)
addADTStoPacket(data, data.size)
buffer.get(data, 7, bufferInfo.size)
buffer.position(bufferInfo.offset)
listener?.bufferUpdate(data)
}
/**
* 添加ADTS头部的7个字节
*/
private fun addADTStoPacket(packet: ByteArray, packetLen: Int) {
val profile = 2 // AAC LC
val freqIdx: Int = 4// 44.1kHz
val chanCfg = 2 // CPE
packet[0] = 0xFF.toByte()
packet[1] = 0xF9.toByte()
packet[2] = ((profile - 1 shl 6) + (freqIdx shl 2) + (chanCfg shr 2)).toByte()
packet[3] = ((chanCfg and 3 shl 6) + (packetLen shr 11)).toByte()
packet[4] = ((packetLen and 0x7FF) shr 3).toByte()
packet[5] = ((packetLen and 7 shl 5) + 0x1F).toByte()
packet[6] = 0xFC.toByte()
}
init {
createCodec("video/avc")
}
fun setUpVideoCodec(width: Int, height: Int) {
val format = MediaFormat.createVideoFormat(mime, width, height)
format.setInteger(
MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
)
//width*height*frameRate*[0.1-0.2]码率控制清晰度
format.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 3)
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
//每秒出一个关键帧,设置0为每帧都是关键帧
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
// format.setInteger(
// MediaFormat.KEY_BITRATE_MODE,
// MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR//遵守用户设置的码率
// )
configEncoderBitrateMode(format)
// format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
// codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
codec.start()
}
override fun bufferUpdate(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) {
listener?.bufferUpdate(buffer, bufferInfo)
}
接下来改造第二篇文章的yuv编码mp4代码:
步骤如下: 示例代码链接
CodecListener
fun convertYuv2Mp4_2(context: Context) {
val yuvPath = "${context.filesDir}/test.yuv"
val saveMp4Path = "${context.filesDir}/test.mp4"
File(saveMp4Path).deleteOnExit()
//定义混合器:输出并保存h.264码流为mp4
val mediaMuxer =
MediaMuxer(
saveMp4Path,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
);
var muxerTrackIndex = -1
val videoEncoder = VideoEncoder()
videoEncoder.setUpVideoCodec(1920, 1080)
videoEncoder.start()
videoEncoder.setCodecListener(object : CodecListener {
override fun formatUpdate(format: MediaFormat) {
//step3.1 标记新的解码数据到来,在此添加视频轨道到混合器
muxerTrackIndex = mediaMuxer.addTrack(format)
mediaMuxer.start()
}
override fun bufferUpdate(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) {
mediaMuxer.writeSampleData(muxerTrackIndex, buffer, bufferInfo)
}
override fun bufferOutputEnd() {
mediaMuxer.release()
videoEncoder.stopWorld()
}
})
val byteArray = ByteArray(1920 * 1080 * 3 / 2)
var read = 0
FileInputStream(yuvPath).use { fis ->
while (true) {
read = fis.read(byteArray)
if (read == byteArray.size) {
Thread.sleep(30)
videoEncoder.putBuf(byteArray, 0, byteArray.size)
} else {
videoEncoder.putBufEnd()
break
}
}
}
}
流程与前一示例基本一致,只是获取yuv数据从文件修改到camera实时流
VideoEncoder
,混合器 MediaMuxer
videoEncoder.putBufEnd()
方法通知编码器结束示例代码如下: 链接
var capture = false;
//视频编码为mp4
val videoEncoder = VideoEncoder()
override fun initView() {
videoEncoder.setUpVideoCodec(640, 480)
videoEncoder.start()
binding.cameraview0.apply {
cameraParams.facing = 1
cameraParams.isScaleWidth = false
cameraParams.oritationDisplay = 90
cameraParams.previewSize.previewWidth = 640
cameraParams.previewSize.previewHeight = 480
cameraParams.isFilp = false
addPreviewFrameCallback(object : CameraView.PreviewFrameCallback {
override fun analyseData(data: ByteArray?): Any {
if (capture) {
videoEncoder.putBuf(data!!, 0, data.size)
}
return 0
}
override fun analyseDataEnd(p0: Any?) {}
})
}
addLifecycleObserver(binding.cameraview0)
val saveMp4Path = "${filesDir}/test.mp4"
File(saveMp4Path).deleteOnExit()
//定义混合器:输出并保存h.264码流为mp4
val mediaMuxer =
MediaMuxer(
saveMp4Path,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
)
var muxerTrackIndex = -1
videoEncoder.setCodecListener(object : CodecListener {
override fun formatUpdate(format: MediaFormat) {
muxerTrackIndex = mediaMuxer.addTrack(format)
mediaMuxer.start()
}
override fun bufferUpdate(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) {
mediaMuxer.writeSampleData(muxerTrackIndex, buffer, bufferInfo)
}
override fun bufferOutputEnd() {
mediaMuxer.release()
videoEncoder.stopWorld()
}
})
binding.cameraview0.setOnClickListener {
DLog.d("录制:$capture")
capture = !capture
}
}
override fun onDestroy() {
videoEncoder.putBufEnd()
super.onDestroy()
}