MediaCodec使用之编码为MP4(四)
MediaCodec使用之编码为MP4(四)
将上面一节mp4生成的yuv编码为h264,逆过程
为什么要编码,原因就是02:30s的mp4视频才12.02MB, yuv格式竟然要我11.60GB!!!
上一节解码的视频可以使用以下命令播放
ffplay -f rawvideo -pixel_format nv12 -video_size 672x1280 -framerate 60 output.yuv
注意
MediaCode编码h264的时候,喂的数据必须是nv21,记得转换一下
// ffplay -x 1280 -y 720 -f rawvideo -pixel_format yuv420p -video_size 3840x2176 -framerate 60 output1.yuv
internal suspend fun yuvToh264(context: Context, yuvUri: Uri, h264Uri: Uri): Unit = suspendCancellableCoroutine{ continuation ->
val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)
// 视频解码器材
val mkvDecoders: List<MediaCodecInfo> = mediaCodecList.codecInfos
.filter { !it.isEncoder && MediaFormat.MIMETYPE_VIDEO_HEVC in it.supportedTypes }
if (mkvDecoders.isEmpty()){
Log.i(TAG, "videoToYuvPcm -> 不支持mkv解码")
if (continuation.isActive){
continuation.resume(Unit)
}
return@suspendCancellableCoroutine
}
// 拿解码器
val mkvDecoder: MediaCodecInfo = mkvDecoders.firstOrNull {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
it.isHardwareAccelerated
} else {
true
}
} ?: mkvDecoders.first()
// 重试解码器 android 8 不支持 hevc (Main 10)
// val mkvDecoder: MediaCodecInfo = mkvDecoders[1]
// MediaCodec.createByCodecName(h264Decoder.name)
Log.i(TAG, "videoToYuvPcm1 -> mkvDecoderName: ${mkvDecoder.name}")
mkvDecoders.forEach { mediaCodecInfo ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, canonicalName: ${mediaCodecInfo.canonicalName}, isAlias: ${mediaCodecInfo.isAlias}, isVendor: ${mediaCodecInfo.isVendor}, isHardwareAccelerated: ${mediaCodecInfo.isHardwareAccelerated}, isEncoder: ${mediaCodecInfo.isEncoder}, isSoftwareOnly: ${mediaCodecInfo.isSoftwareOnly}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
} else {
Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
}
mediaCodecInfo.supportedTypes.forEach { mimeType: String ->
if (mimeType.lowercase() == MediaFormat.MIMETYPE_VIDEO_HEVC){
val mediaCodecInfoCodecCapabilities: MediaCodecInfo.CodecCapabilities = mediaCodecInfo.getCapabilitiesForType(mimeType)
mediaCodecInfoCodecCapabilities.profileLevels.forEach { codecProfileLevel ->
if (codecProfileLevel.profile == MediaCodecInfo.CodecProfileLevel.HEVCProfileMain){
if (codecProfileLevel.level >= MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel62){
Log.i(TAG, "h265ToYuvPcm -> 支持 8k h265 name: ${mediaCodecInfo.name}")
} else {
Log.i(TAG, "h265ToYuvPcm -> 不支持 8k h265 name: ${mediaCodecInfo.name}")
}
}
}
}
}
}
val width = 672 /* 662 */
val height = 1280
val frameRate = 60
val frameSize = width * height * 3 / 2
val mediaFormat: MediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height).apply {
setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000) // 可根据分辨率调整
setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
}
val mediaCodec: MediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
val yuvInputStream: InputStream = context.contentResolver.openInputStream(yuvUri)!!
val h264OutputStream: OutputStream = context.contentResolver.openOutputStream(h264Uri)!!
val bytes = ByteArray(frameSize)
mediaCodec.setCallback(object : MediaCodec.Callback() {
override fun onError(
codec: MediaCodec,
e: MediaCodec.CodecException
) {
Log.e(
TAG,
"onError name: ${codec.name}, thread: ${Thread.currentThread()}, error: ${e.message}",
e
)
}
override fun onInputBufferAvailable(
codec: MediaCodec,
index: Int
) {
Log.i(
TAG,
"onInputBufferAvailable -> name: ${mediaCodec.name}, index: $index, thread: ${Thread.currentThread()}"
)
val inputBuffer: ByteBuffer = codec.getInputBuffer(index) ?: return
val size: Int = yuvInputStream.read(bytes, 0, frameSize)
if (size == frameSize) {
inputBuffer.put(bytes, 0, size)
codec.queueInputBuffer(index, 0, size, System.nanoTime() / 1000, 0)
} else {
codec.queueInputBuffer(
index,
0,
0,
System.nanoTime() / 1000,
MediaCodec.BUFFER_FLAG_END_OF_STREAM
)
}
}
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {
Log.i(
TAG,
"onOutputBufferAvailable -> name: ${codec.name}, index: $index, info: ${info.size}, thread: ${Thread.currentThread()}"
)
val outputBuffer: ByteBuffer = codec.getOutputBuffer(index) ?: return
outputBuffer.get(bytes, 0, info.size)
h264OutputStream.write(bytes, 0, info.size)
codec.releaseOutputBuffer(index, false)
if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
Log.i(TAG, "onOutputBufferAvailable -> == 编码结束...") // todo
yuvInputStream.close()
h264OutputStream.close()
if (continuation.isActive) {
Log.i(TAG, "pcmToAac -> 解码完成 resume before...")
continuation.resume(Unit)
Log.i(TAG, "pcmToAac -> 解码完成 resume after...")
}
}
}
override fun onOutputFormatChanged(
codec: MediaCodec,
format: MediaFormat
) {
Log.i(
TAG,
"onOutputFormatChanged -> name: ${codec.name}, format: $format"
)
}
})
Log.i(TAG, "pcmToAac -> before start...")
mediaCodec.start()
Log.i(TAG, "pcmToAac -> after start...")
}
/**
* yuv420p 转 nv21
*/
fun yuv420pToNv21(yuv420p: ByteArray, nv12: ByteArray, width: Int, height: Int) {
val frameSize: Int = width * height
val qFrameSize: Int = frameSize / 4
// Y 拷贝
System.arraycopy(yuv420p, 0, nv12, 0, frameSize)
val uStart: Int = frameSize
val vStart: Int = frameSize + qFrameSize
var uvIndex: Int = frameSize
for (i in 0 until qFrameSize) {
nv12[uvIndex++] = yuv420p[vStart + i] // V
nv12[uvIndex++] = yuv420p[uStart + i] // U
}
}

MediaCodec使用之编码为MP4(四)
浙公网安备 33010602011771号