02-05-04 MediaCodec编解码
·
02-05-04 MediaCodec编解码
1. 概述
MediaCodec是Android提供的底层多媒体编解码API,支持硬件加速,可处理音频和视频的编码(压缩)与解码(解压缩)。相比MediaPlayer/MediaRecorder的高级API,MediaCodec提供了对编解码过程的精细控制,适合视频剪辑、直播推流、实时通信等需要自定义处理的场景。本文将深入探讨MediaCodec的工作原理、使用方法、性能优化及常见问题。
1.1 MediaCodec架构
┌─────────────────────────────────────────────────────────┐
│ MediaCodec Architecture │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Application Layer (Java/Kotlin) │ │
│ │ MediaCodec | MediaFormat | MediaExtractor │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ JNI │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Native Layer (C++) │ │
│ │ AMediaCodec | AMediaFormat │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Media Framework │ │
│ │ stagefright | OMX | Codec2 │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Codec Implementation │ │
│ │ Hardware Codec (GPU/DSP) | Software Codec │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 编解码器类型
| 类型 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 硬件编解码器 | 使用GPU/DSP | 低功耗、高性能 | 设备兼容性差 |
| 软件编解码器 | 使用CPU | 兼容性好 | 功耗高、性能低 |
常见编解码器名称:
| 格式 | 硬件编码器 | 软件编码器 | 硬件解码器 | 软件解码器 |
|---|---|---|---|---|
| H.264 | OMX.qcom.video.encoder.avc | c2.android.avc.encoder | OMX.qcom.video.decoder.avc | c2.android.avc.decoder |
| H.265 | OMX.qcom.video.encoder.hevc | c2.android.hevc.encoder | OMX.qcom.video.decoder.hevc | c2.android.hevc.decoder |
| AAC | OMX.google.aac.encoder | c2.android.aac.encoder | OMX.google.aac.decoder | c2.android.aac.decoder |
2. MediaCodec工作流程
2.1 编解码生命周期
Stopped
↓ configure()
Configured
↓ start()
Executing ←─────────────┐
│ │
├→ Input Buffers │
│ ↓ │
│ queueInputBuffer │
│ ↓ │
│ Encode/Decode │
│ ↓ │
│ dequeueOutputBuffer
│ ↓ │
├→ Output Buffers │
│ ↓ │
│ releaseOutputBuffer
│ │ │
└─────┘ │
↓ stop() │
Released ←──────────────┘
↓ release()
End
2.2 基础使用流程
// MediaCodecBasics.kt - MediaCodec基础使用
class MediaCodecBasics {
fun encodeH264Example() {
// 1. 创建编码器
val codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
// 2. 配置参数
val format = MediaFormat().apply {
setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
setInteger(MediaFormat.KEY_WIDTH, 1280)
setInteger(MediaFormat.KEY_HEIGHT, 720)
setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000)
setInteger(MediaFormat.KEY_FRAME_RATE, 30)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
setInteger(
MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
)
}
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
// 3. 启动编码器
codec.start()
// 4. 编码循环
var inputEOS = false
var outputEOS = false
while (!outputEOS) {
// 4.1 喂入输入数据
if (!inputEOS) {
val inputBufferId = codec.dequeueInputBuffer(10_000)
if (inputBufferId >= 0) {
val inputBuffer = codec.getInputBuffer(inputBufferId)
inputBuffer?.let {
// 填充YUV数据
val yuvData = generateYUVFrame() // 生成一帧YUV数据
it.put(yuvData)
codec.queueInputBuffer(
inputBufferId,
0,
yuvData.size,
computePresentationTimeUs(), // 计算时间戳
0
)
}
}
}
// 4.2 取出输出数据
val bufferInfo = MediaCodec.BufferInfo()
val outputBufferId = codec.dequeueOutputBuffer(bufferInfo, 10_000)
when (outputBufferId) {
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
val outputFormat = codec.outputFormat
Log.d(TAG, "输出格式变化: $outputFormat")
}
MediaCodec.INFO_TRY_AGAIN_LATER -> {
// 暂时没有输出,稍后重试
}
else -> {
if (outputBufferId >= 0) {
val outputBuffer = codec.getOutputBuffer(outputBufferId)
outputBuffer?.let {
// 处理编码后的H.264数据
val h264Data = ByteArray(bufferInfo.size)
it.get(h264Data)
// 写入文件或发送到网络
saveToFile(h264Data)
// 释放输出buffer
codec.releaseOutputBuffer(outputBufferId, false)
}
// 检查EOS
if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
outputEOS = true
}
}
}
}
}
// 5. 停止并释放
codec.stop()
codec.release()
}
private fun generateYUVFrame(): ByteArray {
// 生成YUV数据(示例)
return ByteArray(1280 * 720 * 3 / 2)
}
private fun computePresentationTimeUs(): Long {
// 计算当前帧的时间戳
return System.nanoTime() / 1000
}
private fun saveToFile(data: ByteArray) {
// 保存到文件
}
companion object {
private const val TAG = "MediaCodecBasics"
}
}
3. 视频编码
3.1 H.264视频编码器
// H264Encoder.kt - H.264视频编码器封装
class H264Encoder(
private val width: Int,
private val height: Int,
private val bitrate: Int = 2_000_000,
private val frameRate: Int = 30,
private val iFrameInterval: Int = 1
) {
private var mediaCodec: MediaCodec? = null
private var isEncoding = false
private var frameIndex = 0L
interface EncoderCallback {
fun onEncodedFrame(data: ByteArray, isKeyFrame: Boolean, presentationTimeUs: Long)
fun onError(error: String)
}
private var callback: EncoderCallback? = null
fun prepare() {
try {
// 1. 创建编码器
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
// 2. 配置参数
val format = MediaFormat().apply {
setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
setInteger(MediaFormat.KEY_WIDTH, width)
setInteger(MediaFormat.KEY_HEIGHT, height)
setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval)
// 色彩格式
setInteger(
MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
)
// Profile设置(Baseline/Main/High)
setInteger(
MediaFormat.KEY_PROFILE,
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh
)
// 码率控制模式
setInteger(
MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR // 可变码率
)
}
mediaCodec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
mediaCodec?.start()
isEncoding = true
frameIndex = 0
Log.d(TAG, "H.264编码器初始化完成:${width}x${height} @ ${bitrate}bps")
} catch (e: Exception) {
Log.e(TAG, "编码器初始化失败: ${e.message}")
callback?.onError("编码器初始化失败: ${e.message}")
}
}
fun encodeFrame(yuvData: ByteArray) {
if (!isEncoding) return
try {
// 1. 获取输入buffer
val inputBufferId = mediaCodec?.dequeueInputBuffer(10_000) ?: return
if (inputBufferId >= 0) {
val inputBuffer = mediaCodec?.getInputBuffer(inputBufferId)
inputBuffer?.let {
it.clear()
it.put(yuvData)
// 2. 提交输入buffer
val presentationTimeUs = computePresentationTimeUs(frameIndex)
mediaCodec?.queueInputBuffer(
inputBufferId,
0,
yuvData.size,
presentationTimeUs,
0
)
frameIndex++
}
}
// 3. 获取输出buffer
drainEncoder(false)
} catch (e: Exception) {
Log.e(TAG, "编码帧失败: ${e.message}")
callback?.onError("编码帧失败: ${e.message}")
}
}
private fun drainEncoder(endOfStream: Boolean) {
val bufferInfo = MediaCodec.BufferInfo()
while (true) {
val outputBufferId = mediaCodec?.dequeueOutputBuffer(bufferInfo, 0) ?: break
when (outputBufferId) {
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
val outputFormat = mediaCodec?.outputFormat
Log.d(TAG, "输出格式变化: $outputFormat")
// 提取SPS/PPS
outputFormat?.let { extractSPSandPPS(it) }
}
MediaCodec.INFO_TRY_AGAIN_LATER -> {
if (!endOfStream) break
}
else -> {
if (outputBufferId >= 0) {
val outputBuffer = mediaCodec?.getOutputBuffer(outputBufferId)
outputBuffer?.let {
// 检查是否为关键帧
val isKeyFrame = (bufferInfo.flags and MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0
// 提取H.264数据
val h264Data = ByteArray(bufferInfo.size)
it.position(bufferInfo.offset)
it.limit(bufferInfo.offset + bufferInfo.size)
it.get(h264Data)
// 回调编码数据
callback?.onEncodedFrame(h264Data, isKeyFrame, bufferInfo.presentationTimeUs)
// 释放buffer
mediaCodec?.releaseOutputBuffer(outputBufferId, false)
}
// 检查结束标志
if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break
}
}
}
}
}
}
private fun extractSPSandPPS(format: MediaFormat) {
// 提取SPS(Sequence Parameter Set)
val sps = format.getByteBuffer("csd-0")
sps?.let {
val spsData = ByteArray(it.remaining())
it.get(spsData)
Log.d(TAG, "SPS: ${spsData.toHexString()}")
}
// 提取PPS(Picture Parameter Set)
val pps = format.getByteBuffer("csd-1")
pps?.let {
val ppsData = ByteArray(it.remaining())
it.get(ppsData)
Log.d(TAG, "PPS: ${ppsData.toHexString()}")
}
}
fun stop() {
isEncoding = false
try {
// 发送EOS信号
val inputBufferId = mediaCodec?.dequeueInputBuffer(10_000) ?: -1
if (inputBufferId >= 0) {
mediaCodec?.queueInputBuffer(
inputBufferId,
0,
0,
0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM
)
}
// 排空编码器
drainEncoder(true)
// 停止并释放
mediaCodec?.stop()
mediaCodec?.release()
mediaCodec = null
Log.d(TAG, "H.264编码器已停止")
} catch (e: Exception) {
Log.e(TAG, "停止编码器失败: ${e.message}")
}
}
private fun computePresentationTimeUs(frameIndex: Long): Long {
return frameIndex * 1_000_000L / frameRate
}
fun setCallback(callback: EncoderCallback) {
this.callback = callback
}
companion object {
private const val TAG = "H264Encoder"
}
}
// ByteArray扩展函数:转十六进制字符串
fun ByteArray.toHexString(): String {
return joinToString("") { "%02x".format(it) }
}
3.2 动态码率调整
// DynamicBitrateAdjuster.kt - 动态码率调整器
class DynamicBitrateAdjuster(private val mediaCodec: MediaCodec) {
/**
* 调整编码器码率(Android Kitkat+)
*/
fun adjustBitrate(newBitrate: Int) {
try {
val bundle = Bundle().apply {
putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate)
}
mediaCodec.setParameters(bundle)
Log.d(TAG, "码率已调整为: $newBitrate bps")
} catch (e: Exception) {
Log.e(TAG, "调整码率失败: ${e.message}")
}
}
/**
* 请求关键帧(Android Kitkat+)
*/
fun requestKeyFrame() {
try {
val bundle = Bundle().apply {
putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0)
}
mediaCodec.setParameters(bundle)
Log.d(TAG, "已请求关键帧")
} catch (e: Exception) {
Log.e(TAG, "请求关键帧失败: ${e.message}")
}
}
/**
* 根据网络状况自动调整码率
*/
fun autoAdjustBitrate(networkSpeedKbps: Int, currentBitrate: Int): Int {
val targetBitrate = when {
networkSpeedKbps > 5000 -> 3_000_000 // 高速网络:3 Mbps
networkSpeedKbps > 2000 -> 1_500_000 // 中速网络:1.5 Mbps
networkSpeedKbps > 1000 -> 800_000 // 低速网络:800 kbps
else -> 400_000 // 极低速:400 kbps
}
// 平滑过渡:每次最多调整20%
val delta = (targetBitrate - currentBitrate) * 0.2
val newBitrate = (currentBitrate + delta).toInt()
adjustBitrate(newBitrate)
return newBitrate
}
companion object {
private const val TAG = "DynamicBitrateAdjuster"
}
}
4. 视频解码
4.1 H.264视频解码器
// H264Decoder.kt - H.264视频解码器封装
class H264Decoder(
private val width: Int,
private val height: Int,
private val surface: Surface? = null // 可选:用于渲染
) {
private var mediaCodec: MediaCodec? = null
private var isDecoding = false
interface DecoderCallback {
fun onDecodedFrame(data: ByteArray, width: Int, height: Int, presentationTimeUs: Long)
fun onError(error: String)
}
private var callback: DecoderCallback? = null
fun prepare(sps: ByteArray, pps: ByteArray) {
try {
// 1. 创建解码器
mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
// 2. 配置参数
val format = MediaFormat().apply {
setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
setInteger(MediaFormat.KEY_WIDTH, width)
setInteger(MediaFormat.KEY_HEIGHT, height)
// 设置SPS/PPS
setByteBuffer("csd-0", ByteBuffer.wrap(sps))
setByteBuffer("csd-1", ByteBuffer.wrap(pps))
}
// 如果提供了Surface,直接渲染到Surface
mediaCodec?.configure(format, surface, null, 0)
mediaCodec?.start()
isDecoding = true
Log.d(TAG, "H.264解码器初始化完成:${width}x${height}")
} catch (e: Exception) {
Log.e(TAG, "解码器初始化失败: ${e.message}")
callback?.onError("解码器初始化失败: ${e.message}")
}
}
fun decodeFrame(h264Data: ByteArray, presentationTimeUs: Long, isKeyFrame: Boolean) {
if (!isDecoding) return
try {
// 1. 获取输入buffer
val inputBufferId = mediaCodec?.dequeueInputBuffer(10_000) ?: return
if (inputBufferId >= 0) {
val inputBuffer = mediaCodec?.getInputBuffer(inputBufferId)
inputBuffer?.let {
it.clear()
it.put(h264Data)
// 2. 提交输入buffer
val flags = if (isKeyFrame) MediaCodec.BUFFER_FLAG_KEY_FRAME else 0
mediaCodec?.queueInputBuffer(
inputBufferId,
0,
h264Data.size,
presentationTimeUs,
flags
)
}
}
// 3. 获取输出buffer
drainDecoder()
} catch (e: Exception) {
Log.e(TAG, "解码帧失败: ${e.message}")
callback?.onError("解码帧失败: ${e.message}")
}
}
private fun drainDecoder() {
val bufferInfo = MediaCodec.BufferInfo()
while (true) {
val outputBufferId = mediaCodec?.dequeueOutputBuffer(bufferInfo, 0) ?: break
when (outputBufferId) {
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
val outputFormat = mediaCodec?.outputFormat
Log.d(TAG, "输出格式变化: $outputFormat")
}
MediaCodec.INFO_TRY_AGAIN_LATER -> {
break
}
else -> {
if (outputBufferId >= 0) {
if (surface == null) {
// 没有Surface,手动获取YUV数据
val outputBuffer = mediaCodec?.getOutputBuffer(outputBufferId)
outputBuffer?.let {
val yuvData = ByteArray(bufferInfo.size)
it.position(bufferInfo.offset)
it.limit(bufferInfo.offset + bufferInfo.size)
it.get(yuvData)
callback?.onDecodedFrame(
yuvData,
width,
height,
bufferInfo.presentationTimeUs
)
}
// 释放buffer
mediaCodec?.releaseOutputBuffer(outputBufferId, false)
} else {
// 有Surface,直接渲染
mediaCodec?.releaseOutputBuffer(outputBufferId, true)
}
}
}
}
}
}
fun stop() {
isDecoding = false
try {
mediaCodec?.stop()
mediaCodec?.release()
mediaCodec = null
Log.d(TAG, "H.264解码器已停止")
} catch (e: Exception) {
Log.e(TAG, "停止解码器失败: ${e.message}")
}
}
fun setCallback(callback: DecoderCallback) {
this.callback = callback
}
companion object {
private const val TAG = "H264Decoder"
}
}
4.2 视频渲染到Surface
// VideoRenderer.kt - 视频渲染器
class VideoRenderer(private val surfaceView: SurfaceView) {
private lateinit var decoder: H264Decoder
init {
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
// Surface创建后初始化解码器
val surface = holder.surface
decoder = H264Decoder(1280, 720, surface)
// 准备解码器(需要SPS/PPS)
val sps = getSPS() // 获取SPS数据
val pps = getPPS() // 获取PPS数据
decoder.prepare(sps, pps)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
// Surface尺寸变化
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
// Surface销毁时停止解码器
decoder.stop()
}
})
}
fun renderFrame(h264Data: ByteArray, pts: Long, isKeyFrame: Boolean) {
decoder.decodeFrame(h264Data, pts, isKeyFrame)
}
private fun getSPS(): ByteArray {
// 返回SPS数据
return byteArrayOf()
}
private fun getPPS(): ByteArray {
// 返回PPS数据
return byteArrayOf()
}
}
5. 音频编解码
5.1 AAC音频编码器
// AACEncoder.kt - AAC音频编码器
class AACEncoder(
private val sampleRate: Int = 44100,
private val channelCount: Int = 2,
private val bitrate: Int = 128_000
) {
private var mediaCodec: MediaCodec? = null
private var isEncoding = false
interface EncoderCallback {
fun onEncodedFrame(data: ByteArray, presentationTimeUs: Long)
fun onError(error: String)
}
private var callback: EncoderCallback? = null
fun prepare() {
try {
// 1. 创建编码器
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
// 2. 配置参数
val format = MediaFormat().apply {
setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC)
setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate)
setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelCount)
setInteger(MediaFormat.KEY_BIT_RATE, bitrate)
setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)
setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384)
}
mediaCodec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
mediaCodec?.start()
isEncoding = true
Log.d(TAG, "AAC编码器初始化完成:${sampleRate}Hz, ${channelCount}ch, ${bitrate}bps")
} catch (e: Exception) {
Log.e(TAG, "编码器初始化失败: ${e.message}")
callback?.onError("编码器初始化失败: ${e.message}")
}
}
fun encodePCM(pcmData: ByteArray, presentationTimeUs: Long) {
if (!isEncoding) return
try {
// 1. 获取输入buffer
val inputBufferId = mediaCodec?.dequeueInputBuffer(10_000) ?: return
if (inputBufferId >= 0) {
val inputBuffer = mediaCodec?.getInputBuffer(inputBufferId)
inputBuffer?.let {
it.clear()
it.put(pcmData)
// 2. 提交输入buffer
mediaCodec?.queueInputBuffer(
inputBufferId,
0,
pcmData.size,
presentationTimeUs,
0
)
}
}
// 3. 获取输出buffer
drainEncoder()
} catch (e: Exception) {
Log.e(TAG, "编码PCM失败: ${e.message}")
callback?.onError("编码PCM失败: ${e.message}")
}
}
private fun drainEncoder() {
val bufferInfo = MediaCodec.BufferInfo()
while (true) {
val outputBufferId = mediaCodec?.dequeueOutputBuffer(bufferInfo, 0) ?: break
when (outputBufferId) {
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
val outputFormat = mediaCodec?.outputFormat
Log.d(TAG, "输出格式变化: $outputFormat")
}
MediaCodec.INFO_TRY_AGAIN_LATER -> {
break
}
else -> {
if (outputBufferId >= 0) {
val outputBuffer = mediaCodec?.getOutputBuffer(outputBufferId)
outputBuffer?.let {
// 提取AAC数据
val aacData = ByteArray(bufferInfo.size)
it.position(bufferInfo.offset)
it.limit(bufferInfo.offset + bufferInfo.size)
it.get(aacData)
// 回调编码数据
callback?.onEncodedFrame(aacData, bufferInfo.presentationTimeUs)
// 释放buffer
mediaCodec?.releaseOutputBuffer(outputBufferId, false)
}
}
}
}
}
}
fun stop() {
isEncoding = false
try {
mediaCodec?.stop()
mediaCodec?.release()
mediaCodec = null
Log.d(TAG, "AAC编码器已停止")
} catch (e: Exception) {
Log.e(TAG, "停止编码器失败: ${e.message}")
}
}
fun setCallback(callback: EncoderCallback) {
this.callback = callback
}
companion object {
private const val TAG = "AACEncoder"
}
}
6. 性能优化
6.1 硬件编解码器选择
// CodecSelector.kt - 编解码器选择器
object CodecSelector {
/**
* 列出所有支持的编解码器
*/
fun listAllCodecs(): List<CodecInfo> {
val codecList = MediaCodecList(MediaCodecList.ALL_CODECS)
val codecInfos = mutableListOf<CodecInfo>()
for (codecInfo in codecList.codecInfos) {
val types = codecInfo.supportedTypes
val isEncoder = codecInfo.isEncoder
for (type in types) {
codecInfos.add(
CodecInfo(
name = codecInfo.name,
type = type,
isEncoder = isEncoder,
isHardwareAccelerated = isHardwareAccelerated(codecInfo)
)
)
}
}
return codecInfos
}
/**
* 选择最佳编码器(优先硬件编码器)
*/
fun selectBestEncoder(mimeType: String): String? {
val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
// 1. 优先选择硬件编码器
for (codecInfo in codecList.codecInfos) {
if (codecInfo.isEncoder && isHardwareAccelerated(codecInfo)) {
for (type in codecInfo.supportedTypes) {
if (type.equals(mimeType, ignoreCase = true)) {
Log.d(TAG, "选择硬件编码器: ${codecInfo.name}")
return codecInfo.name
}
}
}
}
// 2. 退化到软件编码器
for (codecInfo in codecList.codecInfos) {
if (codecInfo.isEncoder) {
for (type in codecInfo.supportedTypes) {
if (type.equals(mimeType, ignoreCase = true)) {
Log.d(TAG, "选择软件编码器: ${codecInfo.name}")
return codecInfo.name
}
}
}
}
return null
}
/**
* 判断是否为硬件编解码器
*/
private fun isHardwareAccelerated(codecInfo: MediaCodecInfo): Boolean {
// Android Q+: 使用官方API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return codecInfo.isHardwareAccelerated
}
// 旧版本:根据名称判断
val name = codecInfo.name.toLowerCase(Locale.ROOT)
return !name.startsWith("omx.google.") && !name.startsWith("c2.android.")
}
/**
* 检查编解码器能力
*/
fun checkCodecCapabilities(mimeType: String, width: Int, height: Int): CodecCapabilityInfo {
val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
for (codecInfo in codecList.codecInfos) {
if (codecInfo.isEncoder) {
for (type in codecInfo.supportedTypes) {
if (type.equals(mimeType, ignoreCase = true)) {
val capabilities = codecInfo.getCapabilitiesForType(type)
val videoCapabilities = capabilities.videoCapabilities
return CodecCapabilityInfo(
codecName = codecInfo.name,
supportedWidthRange = videoCapabilities.supportedWidths,
supportedHeightRange = videoCapabilities.supportedHeights,
supportedFrameRateRange = videoCapabilities.supportedFrameRates,
supportedBitrateRange = videoCapabilities.bitrateRange,
supportsResolution = videoCapabilities.isSizeSupported(width, height)
)
}
}
}
}
return CodecCapabilityInfo(codecName = "Unknown")
}
data class CodecInfo(
val name: String,
val type: String,
val isEncoder: Boolean,
val isHardwareAccelerated: Boolean
)
data class CodecCapabilityInfo(
val codecName: String,
val supportedWidthRange: Range<Int>? = null,
val supportedHeightRange: Range<Int>? = null,
val supportedFrameRateRange: Range<Double>? = null,
val supportedBitrateRange: Range<Int>? = null,
val supportsResolution: Boolean = false
)
private const val TAG = "CodecSelector"
}
6.2 异步模式
// AsyncMediaCodec.kt - 异步模式MediaCodec
class AsyncMediaCodec {
fun createAsyncEncoder(): MediaCodec {
val codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
// 设置异步回调
codec.setCallback(object : MediaCodec.Callback() {
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
// 输入buffer可用
val inputBuffer = codec.getInputBuffer(index)
inputBuffer?.let {
// 填充数据
val yuvData = generateYUVFrame()
it.put(yuvData)
codec.queueInputBuffer(index, 0, yuvData.size, computePTS(), 0)
}
}
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {
// 输出buffer可用
val outputBuffer = codec.getOutputBuffer(index)
outputBuffer?.let {
// 处理编码数据
val h264Data = ByteArray(info.size)
it.get(h264Data)
processEncodedFrame(h264Data)
codec.releaseOutputBuffer(index, false)
}
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
Log.e(TAG, "编解码错误: ${e.message}")
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
Log.d(TAG, "输出格式变化: $format")
}
})
// 配置并启动
val format = createVideoFormat()
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
codec.start()
return codec
}
private fun generateYUVFrame(): ByteArray {
return ByteArray(1280 * 720 * 3 / 2)
}
private fun computePTS(): Long {
return System.nanoTime() / 1000
}
private fun processEncodedFrame(data: ByteArray) {
// 处理编码后的数据
}
private fun createVideoFormat(): MediaFormat {
return MediaFormat().apply {
setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC)
setInteger(MediaFormat.KEY_WIDTH, 1280)
setInteger(MediaFormat.KEY_HEIGHT, 720)
setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000)
setInteger(MediaFormat.KEY_FRAME_RATE, 30)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
setInteger(
MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
)
}
}
companion object {
private const val TAG = "AsyncMediaCodec"
}
}
7. 案例研究
案例1:屏幕录制
// ScreenRecorder.kt - 屏幕录制器
class ScreenRecorder(
private val context: Context,
private val width: Int,
private val height: Int
) {
private lateinit var mediaProjection: MediaProjection
private lateinit var virtualDisplay: VirtualDisplay
private lateinit var encoder: H264Encoder
fun start(resultCode: Int, data: Intent, outputFile: File) {
// 1. 创建MediaProjection
val mediaProjectionManager =
context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data)
// 2. 创建编码器
encoder = H264Encoder(width, height)
encoder.prepare()
// 3. 创建VirtualDisplay
virtualDisplay = mediaProjection.createVirtualDisplay(
"ScreenCapture",
width,
height,
context.resources.displayMetrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
encoder.getInputSurface(), // 直接编码到Surface
null,
null
)
// 4. 保存编码数据到文件
encoder.setCallback(object : H264Encoder.EncoderCallback {
override fun onEncodedFrame(data: ByteArray, isKeyFrame: Boolean, presentationTimeUs: Long) {
// 写入文件
outputFile.appendBytes(data)
}
override fun onError(error: String) {
Log.e(TAG, "编码错误: $error")
}
})
}
fun stop() {
virtualDisplay.release()
mediaProjection.stop()
encoder.stop()
}
companion object {
private const val TAG = "ScreenRecorder"
}
}
总结
MediaCodec是Android音视频编解码的核心API,本文深入探讨了:
- MediaCodec架构:生命周期、工作流程
- 视频编码:H.264编码器、动态码率调整
- 视频解码:H.264解码器、Surface渲染
- 音频编解码:AAC编码器
- 性能优化:硬件编解码器选择、异步模式
- 案例研究:屏幕录制实现
关键要点:
- 硬件编解码器优先选择,性能更好
- 异步模式降低延迟,提高吞吐量
- 动态码率适应网络状况
- Surface渲染减少内存拷贝
下一篇预告:《Camera2相机开发》- 深入讲解Camera2 API、预览、拍照、录像、手动对焦曝光。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)