音视频编解码
问题
MediaCodec 的工作原理是什么?硬编硬解和软编软解有什么区别?
答案
硬编硬解 vs 软编软解
| 维度 | 硬编硬解(MediaCodec) | 软编软解(FFmpeg 等) |
|---|---|---|
| 执行单元 | GPU / DSP 硬件 | CPU |
| 性能 | 高,功耗低 | 低,功耗高 |
| 兼容性 | 依赖设备硬件 | 跨平台一致 |
| 格式支持 | H.264/H.265/VP9 | 几乎所有格式 |
| 灵活性 | 受限于硬件能力 | 可自定义算法 |
MediaCodec 异步模式
// 视频编码示例(H.264)
val format = MediaFormat.createVideoFormat(
MediaFormat.MIMETYPE_VIDEO_AVC, // H.264
1920, 1080
).apply {
setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000) // 4Mbps
setInteger(MediaFormat.KEY_FRAME_RATE, 30)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2) // 关键帧间隔 2 秒
setInteger(
MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
)
}
val encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
// 异步回调模式(推荐)
encoder.setCallback(object : MediaCodec.Callback() {
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
// 填充原始数据到输入 Buffer
val inputBuffer = codec.getInputBuffer(index) ?: return
val data = readYUVFrame()
if (data != null) {
inputBuffer.put(data)
codec.queueInputBuffer(index, 0, data.size, presentationTimeUs, 0)
} else {
// 输入结束标志
codec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
}
}
override fun onOutputBufferAvailable(
codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo
) {
// 取出编码后的数据
val outputBuffer = codec.getOutputBuffer(index) ?: return
if (info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG == 0) {
// 写入到 MediaMuxer 或发送到网络
muxer.writeSampleData(videoTrack, outputBuffer, info)
}
codec.releaseOutputBuffer(index, false)
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
// 编码格式确定,初始化 MediaMuxer
videoTrack = muxer.addTrack(format)
muxer.start()
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
// 编码错误
}
})
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
val inputSurface = encoder.createInputSurface() // Surface 输入模式
encoder.start()
MediaMuxer 封装
MediaCodec 编码后输出的是裸流(H.264 NALU),需要 MediaMuxer 封装为 MP4 等容器格式:
val muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
// 添加视频轨道和音频轨道
val videoTrack = muxer.addTrack(videoFormat)
val audioTrack = muxer.addTrack(audioFormat)
muxer.start()
// 写入编码数据
muxer.writeSampleData(videoTrack, buffer, bufferInfo)
// 完成时
muxer.stop()
muxer.release()
常见面试问题
Q1: MediaCodec 的同步模式和异步模式有什么区别?
答案:
- 同步模式:在循环中调用
dequeueInputBuffer()/dequeueOutputBuffer(),阻塞等待可用 Buffer。代码简单但容易阻塞线程。 - 异步模式(推荐):通过
setCallback()注册回调,MediaCodec 在 Buffer 可用时主动通知。非阻塞,性能更好,但需注意回调在 MediaCodec 内部线程执行。
Android 5.0+ 推荐异步模式。
Q2: H.264 和 H.265 的区别?
答案:
| 维度 | H.264 (AVC) | H.265 (HEVC) |
|---|---|---|
| 压缩效率 | 基准 | 比 H.264 高约 50% |
| 同等画质码率 | 4 Mbps | 2 Mbps |
| 编解码复杂度 | 低 | 高 |
| 设备兼容性 | 几乎所有设备 | Android 5.0+(硬件支持可能有限) |
| 专利费用 | 较低 | 复杂 |
短视频、直播常用 H.264(兼容性好);存储和离线视频可选 H.265(体积小)。