Android 硬件编码器参数完全指南:MediaCodec 深度解析

主题 :Android 硬编码 · MediaCodec · MediaFormat · 码率控制 · H.264/H.265/HEVC · AAC
前置知识:Android 多媒体基础(Surface/Canvas/Bitmap)、音视频编码基本概念(I/P/B帧、AAC Profile)


一、引言:为什么 MediaCodec 编码参数如此关键

Android 的 MediaCodec API 封装了对硬件编码器的访问接口。编码参数配置的正确与否,直接决定了:

  • 视频质量:码率、色彩格式、GOP 结构
  • 编码性能:编码速度、CPU/GPU 占用、功耗
  • 输出兼容性:Profile/Level 决定可播放范围
  • 延迟控制:实时通信 vs 离线录制的参数差异
  • 硬件适配:不同设备编码器能力差异巨大

一个配置不当的编码器,轻则产生马赛克/花屏,重则编码器初始化失败直接崩溃。本文系统梳理所有编码参数,提供可直接运行的 Demo,覆盖视频编码器、音频编码器的完整参数体系。


二、MediaCodec 编码器全景架构

2.1 编码器的数据流

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    MediaCodec Encoder                        │
│                                                             │
│  输入(生产者)                                              │
│  ┌──────────────┐    ┌──────────────────────────────────┐  │
│  │ YUV/RGB 数据 │───→│  InputBuffer 队列                │  │
│  │ ByteBuffer   │    │  queueInputBuffer()              │  │
│  │ 或 Surface   │    │  createInputSurface()            │  │
│  └──────────────┘    └────────────────┬─────────────────┘  │
│                                       │                      │
│  编码器核心(硬件 ASIC / DSP / GPU)   │                      │
│  ┌────────────────────────────────────▼──────────────────┐  │
│  │  ┌────────────┐   ┌──────────────┐   ┌────────────┐ │  │
│  │  │ H.264/HEVC │   │   AAC/Opus   │   │  VP8/VP9   │ │  │
│  │  │  编码核     │   │   编码核     │   │   编码核   │ │  │
│  │  └────────────┘   └──────────────┘   └────────────┘ │  │
│  └────────────────────────────────────────────────────┘  │
│                                       │                      │
│  输出(消费者)                                              │
│  ┌────────────────┬───────────────────────────────┐       │
│  │  H.264 NALU   │   OutputBuffer 队列            │       │
│  │  AAC ES 流    │   dequeueOutputBuffer()        │       │
│  └────────────────┴──────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

2.2 三种编码模式

模式 API 输入方式 适用场景 性能
ByteBuffer getInputBuffer() 手动填充 YUV/RGB 数据 软硬件混合、需要预处理 中等
Surface createInputSurface() Surface 直接接收 Camera/Canvas 绘制 Camera 实时编码、屏幕录制 最优
Image getInputImage() 读写字节缓冲 特殊格式处理 较低

最佳实践 :优先使用 Surface 模式------数据无需在 APP 层拷贝,直接由硬件写入,解码侧同理。CameraX → Surface → MediaCodec → Surface 是目前最低延迟的 pipeline。

2.3 编码器创建方法

kotlin 复制代码
// ===== 方法一:按 MIME 类型创建(最常用)=====
val encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)

// ===== 方法二:按编码器名称创建(需要特定编码器时)=====
val encoderList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
for (info in encoderList.codecInfos) {
    if (!info.isEncoder) continue
    if (info.name == "OMX.qcom.video.encoder.avc") {
        val encoder = MediaCodec.createByCodecName(info.name)
    }
}

// ===== 方法三:查询某 MIME 类型是否有编码器 =====
val hasEncoder = MediaCodecList(MediaCodecList.REGULAR_CODECS)
    .codecInfos.any { !it.isEncoder && it.supportedTypes.contains("video/avc") }

三、MediaFormat:编码参数的载体

MediaFormat 是 Android 多媒体参数的核心载体,所有编码参数都通过它传递给编码器:

kotlin 复制代码
// ===== 创建视频格式 =====
val videoFormat = MediaFormat.createVideoFormat(
    MediaFormat.MIMETYPE_VIDEO_AVC,  // MIME 类型
    1920,                             // 宽度
    1080                              // 高度
)

// ===== 设置参数 =====
videoFormat.apply {
    setInteger(MediaFormat.KEY_BIT_RATE, 8_000_000)       // 8 Mbps
    setInteger(MediaFormat.KEY_FRAME_RATE, 30)             // 30 fps
    setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)        // 每秒 1 个 I 帧
    setInteger(MediaFormat.KEY_COLOR_FORMAT,
        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
    setInteger(MediaFormat.KEY_BITRATE_MODE,
        MediaCodecInfo.VideoEncoderCapabilities.BITRATE_MODE_CBR)
}

// ===== 配置编码器 =====
encoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
encoder.start()

四、视频编码参数详解

4.1 必需参数(缺一不可)

4.1.1 KEY_MIME:编码格式
MIME 常量 标准名称 容器封装 备注
video/avc H.264 / AVC MP4 / MKV / TS 最广泛兼容
video/hevc H.265 / HEVC MP4 / MKV Android 5.0+
video/vp8 VP8 WebM / MKV Android 4.0+
video/vp9 VP9 WebM / MKV Android 4.4+
video/av01 AV1 MP4 / MKV Android 10+
kotlin 复制代码
// 各格式编码器创建
val h264Encoder = MediaCodec.createEncoderByType("video/avc")
val h265Encoder = MediaCodec.createEncoderByType("video/hevc")
val vp8Encoder  = MediaCodec.createEncoderByType("video/vp8")
val vp9Encoder  = MediaCodec.createEncoderByType("video/vp9")
val av1Encoder  = MediaCodec.createEncoderByType("video/av01")
4.1.2 KEY_WIDTH / KEY_HEIGHT:分辨率
kotlin 复制代码
videoFormat.setInteger(MediaFormat.KEY_WIDTH, 1920)
videoFormat.setInteger(MediaFormat.KEY_HEIGHT, 1080)

常见分辨率对照表

分辨率 标识 典型码率(1080p@30fps) 典型码率(720p@30fps)
7680×4320 8K UHD 80--120 Mbps ---
3840×2160 4K UHD 35--60 Mbps ---
2560×1440 2K QHD 15--25 Mbps ---
1920×1080 FHD 8--15 Mbps 5--8 Mbps
1280×720 HD 4--8 Mbps 2--4 Mbps
854×480 SD 1--3 Mbps 1--2 Mbps
640×360 LD 0.5--1.5 Mbps 0.5--1 Mbps
426×240 最小 0.3--0.8 Mbps 0.3--0.5 Mbps

分辨率必须是 2 的倍数(编码器内部以 16×16 宏块为单位):宽度/高度必须为 2 的倍数,推荐为 16 的倍数以获得最佳性能。

4.1.3 KEY_BIT_RATE:码率(最核心参数)
kotlin 复制代码
// 设置码率(单位:bits per second)
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 8_000_000)  // 8 Mbps

// 音频码率
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128_000)    // 128 kbps

常见码率推荐表

场景 分辨率 帧率 推荐码率 编码器模式
直播(运动多) 1920×1080 30 6--10 Mbps CBR
直播(静止为主) 1920×1080 30 3--6 Mbps VBR
点播录制 1920×1080 30 5--12 Mbps VBR
屏幕录制 屏幕分辨率 屏幕帧率 8--20 Mbps CQ
视频通话 1280×720 30 1.5--4 Mbps CBR
视频通话(弱网) 640×480 15 0.5--1.5 Mbps CBR
监控存储 1920×1080 15 2--5 Mbps VBR
慢动作(120fps) 1280×720 120 10--20 Mbps CBR
4.1.4 KEY_FRAME_RATE:帧率
kotlin 复制代码
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
帧率 适用场景 运动容限 带宽消耗
120 fps 慢动作回放 极高 4× 30fps
60 fps 游戏录制、高清直播 2× 30fps
30 fps 标准视频、会议 基准
24 fps 电影风格 0.8× 30fps
15 fps 弱网通话、屏幕共享 很低 0.5× 30fps
10 fps 极低带宽监控 极低 0.33× 30fps

⚠️ API 约束KEY_FRAME_RATE建议值 ,编码器可能忽略此参数而使用设备支持的帧率。部分编码器会在 configure() 后通过 getOutputFormat() 返回实际使用的帧率。

4.1.5 KEY_I_FRAME_INTERVAL:I 帧间隔(GOP 长度)
kotlin 复制代码
// 每 1 秒一个 I 帧(即 GOP = 1 秒)
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)

// 每 2 秒一个 I 帧
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2)

// 每 5 秒一个 I 帧
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5)

// 全部 I 帧(无压缩,仅适用于屏幕录制等特殊场景)
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0)

// Android 11+(API 31):I 帧后跟 P 帧,无 B 帧
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, -1)

GOP(Group of Pictures)结构说明

复制代码
GOP = 1 秒,帧率 = 30 fps 时:

I[0]  P[1]  P[2]  P[3]  P[4]  P[5]  P[6]  ... P[29]  |  I[30]  P[31] ...
I帧    ↓      ↓      ↓      ↓      ↓      ↓       ↓          I帧
       P帧参考前一帧(P[1]参考I[0],P[2]参考P[1]...)

I 帧大小:约 100--500 KB
P 帧大小:约 10--50 KB
总 GOP 大小:约 300--800 KB
码率 = GOP大小 / GOP时长 = ~400KB / 1s ≈ 3.2 Mbps

I 帧间隔对系统的影响

I 帧间隔 优势 劣势
0(全部 I 帧) 最快 seek,无预测误差 码率极高(5--10×),存储大
1s 适中,seek 较快 码率略高
2--5s 码率节省,较好质量 seek 需等待 I 帧
10s+ 最低码率 seek 卡顿明显

实时通信推荐 :1--2 秒(兼顾质量和 seek 体验);离线录制推荐:5--10 秒(质量优先)。

4.2 色彩格式参数

色彩格式决定了 YUV 数据的内存布局,直接影响编码效率和兼容性。

4.2.1 色彩格式全解析
常量 别名 YUV 布局 Y 平面 U 平面 V 平面 兼容性
COLOR_FormatSurface --- Surface GPU 内部 GPU 内部 GPU 内部 最优(零拷贝)
COLOR_FormatYUV420Flexible --- 灵活 线性 线性 线性 最广(API 21+)
COLOR_FormatYUV420Planar I420 / YV12 平面 W×H W/2×H/2 W/2×H/2 最广
COLOR_FormatYUV420SemiPlanar NV12 半平面 W×H W/2×H/2 W×H/2 (交错)
COLOR_FormatYUV420PackedSemiPlanar NV21 --- --- --- ---
COLOR_FormatRGBA --- --- 4 通道 RGBA --- --- 用于 Canvas

内存布局图解

复制代码
COLOR_FormatYUV420Planar (I420):
┌─────────────────┬─────────────────┐
│      Y Plane    │   U Plane       │
│   (W × H)      │   (W/2 × H/2)   │
└─────────────────┴─────────────────┤
                   │   V Plane       │
                   │   (W/2 × H/2)   │
                   └─────────────────┘
内存总大小 = W×H + W×H/4 + W×H/4 = 1.5 × W × H 字节

COLOR_FormatYUV420SemiPlanar (NV12):
┌─────────────────┬─────────────────┐
│      Y Plane    │   UV Interleaved │
│   (W × H)      │   (W × H/2)      │
└─────────────────┴─────────────────┘
内存总大小 = W×H + W×H/2 = 1.5 × W × H 字节

COLOR_FormatSurface(Surface 模式):
┌─────────────────────────┐
│     GPU 内部格式          │
│   (编码器直接访问)       │
│   无 APP 层 YUV 拷贝      │
└─────────────────────────┘
4.2.2 如何查询可用色彩格式
kotlin 复制代码
fun printAvailableColorFormats(mime: String) {
    val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
    for (info in codecList.codecInfos) {
        if (info.isEncoder && info.supportedTypes.any { it == mime }) {
            val caps = info.getCapabilitiesForType(mime)
            Log.d("Codec", "编码器: ${info.name}, Mime: $mime")
            for (cf in caps.colorFormats) {
                val name = when (cf) {
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface -> "Surface(零拷贝)"
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible -> "YUV420Flexible"
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar -> "YUV420Planar (I420)"
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar -> "YUV420SemiPlanar (NV12)"
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar -> "YUV420PackedSemiPlanar (NV21)"
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatRGBA -> "RGBA"
                    else -> "未知 (0x${cf.toString(16)})"
                }
                Log.d("Codec", "  色彩格式: $name (0x${cf.toString(16)})")
            }
        }
    }
}

// 调用
printAvailableColorFormats(MediaFormat.MIMETYPE_VIDEO_AVC)

典型输出示例(某高通设备):

复制代码
编码器: OMX.qcom.video.encoder.avc, Mime: video/avc
  色彩格式: Surface(零拷贝)(0x4)
  色彩格式: YUV420Flexible (0x23)
  色彩格式: YUV420SemiPlanar (NV12) (0x15)
4.2.3 ByteBuffer 模式下的 YUV 填充
kotlin 复制代码
// 将 Bitmap 转换为 NV12/NV21 并填入 InputBuffer
fun fillInputBuffer(
    encoder: MediaCodec,
    inputBufIndex: Int,
    bitmap: Bitmap,
    isNV21: Boolean = false  // NV21 vs NV12
) {
    val inputBuf = encoder.getInputBuffer(inputBufIndex) ?: return
    inputBuf.clear()

    val w = bitmap.width
    val h = bitmap.height

    // 1. 将 Bitmap 转换为 YUV
    val yuvData = ByteArray(w * h * 3 / 2)
    val argb = IntArray(w * h)
    bitmap.getPixels(argb, 0, w, 0, 0, w, h)
    rgbToNv21(argb, yuvData, w, h, isNV21)

    // 2. 写入 InputBuffer
    inputBuf.put(yuvData)
}

// RGB → NV21 转换
private fun rgbToNv21(rgb: IntArray, yuv: ByteArray, w: Int, h: Int, nv21: Boolean) {
    val frameSize = w * h
    var yIndex = 0
    var uvIndex = frameSize

    for (j in 0 until h) {
        for (i in 0 until w) {
            val pixel = rgb[j * w + i]
            val r = (pixel shr 16) and 0xFF
            val g = (pixel shr 8) and 0xFF
            val b = pixel and 0xFF

            // Y 分量
            val y = ((66 * r + 129 * g + 25 * b + 128) shr 8) + 16
            yuv[yIndex++] = y.coerceIn(0, 255).toByte()

            // UV 分量(每 2×2 像素块共享一个 UV)
            if (j % 2 == 0 && i % 2 == 0 && uvIndex < yuv.size - 1) {
                val u = ((-38 * r - 74 * g + 112 * b + 128) shr 8) + 128
                val v = ((112 * r - 94 * g - 18 * b + 128) shr 8) + 128
                if (nv21) {
                    yuv[uvIndex++] = u.coerceIn(0, 255).toByte()
                    yuv[uvIndex++] = v.coerceIn(0, 255).toByte()
                } else {
                    yuv[uvIndex++] = v.coerceIn(0, 255).toByte()
                    yuv[uvIndex++] = u.coerceIn(0, 255).toByte()
                }
            }
        }
    }
}

4.3 码率控制模式(BITRATE_MODE)

这是最容易被忽视但影响最大的参数组。

4.3.1 四种码率控制模式
模式 常量 码率特征 质量特征 适用场景 API 版本
CQ BITRATE_MODE_CQ 波动 质量恒定 高质量本地录制、电影级 API 21+
VBR BITRATE_MODE_VBR 波动 质量优先 点播、VOD、存储 API 21+
CBR BITRATE_MODE_CBR 恒定 稳定码率 直播、实时通信、CDN API 21+
CBR_FD BITRATE_MODE_CBR_FD 恒定 允许丢帧 极低带宽限制 API 29+
kotlin 复制代码
// API 29+:通过 VideoEncoderCapabilities 设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    val caps = encoder.getVideoCapabilities()
    val supportedModes = caps.supportedBitrateModes
    if (supportedModes.contains(VideoEncoderCapabilities.BITRATE_MODE_CBR)) {
        videoFormat.setInteger(
            MediaFormat.KEY_BITRATE_MODE,
            MediaFormat.BITRATE_MODE_CBR
        )
    }
}

// API 21-28:某些设备通过 KEY_PROFILE 设置隐式模式
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 8_000_000)
4.3.2 码率控制模式详细对比
复制代码
┌──────────────────────────────────────────────────────────────┐
│                    CQ(Constant Quality)                    │
│  码率图:    ╱╲    ╱╲                                        │
│            ╱  ╲  ╱  ╲     质量恒定(目标 QP)                  │
│          ╱    ╲╱    ╲    码率波动(场景复杂↑,码率↑)          │
│        ╱                适用:高质量本地录制                    │
├──────────────────────────────────────────────────────────────┤
│                    VBR(Variable Bitrate)                   │
│  码率图:  ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁▂▃▄▅▆▇█▇▆▅▄▃▂▁                    │
│           场景复杂度高    场景复杂度低                         │
│          质量下限保障(min bitrate)                          │
│          质量上限限制(max bitrate)                          │
│          适用:点播、存储、省流量                              │
├──────────────────────────────────────────────────────────────┤
│                    CBR(Constant Bitrate)                   │
│  码率图:  ━━━━━━━━━━━━━━━━━━━━━━                             │
│          码率恒定                                              │
│          质量随场景波动(复杂→量化参数↑→质量↓)               │
│          适用:直播、实时通信、带宽稳定需求                     │
├──────────────────────────────────────────────────────────────┤
│                    CBR_FD(CBR with Frame Drop)             │
│  码率图:  ━━━━  ━━  ━━━━  ░░░(丢帧)                        │
│          超出码率能力时,主动丢帧而非降低质量                    │
│          适用:极低带宽 + 必须保持实时性                        │
└──────────────────────────────────────────────────────────────┘

各模式推荐配置

kotlin 复制代码
// 场景 1:高质量本地录像(推荐 CQ 或 VBR)
val recordFormat = MediaFormat.createVideoFormat("video/avc", 1920, 1080).apply {
    setInteger(KEY_BIT_RATE, 15_000_000)      // 15 Mbps(1080p 高质量)
    setInteger(KEY_FRAME_RATE, 30)
    setInteger(KEY_I_FRAME_INTERVAL, 2)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        setInteger(KEY_BITRATE_MODE, MediaFormat.BITRATE_MODE_CQ)
    }
}

// 场景 2:实时通信(推荐 CBR)
val rtcFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720).apply {
    setInteger(KEY_BIT_RATE, 2_000_000)        // 2 Mbps(720p 通话)
    setInteger(KEY_FRAME_RATE, 30)
    setInteger(KEY_I_FRAME_INTERVAL, 1)
    setInteger(KEY_BITRATE_MODE, MediaFormat.BITRATE_MODE_CBR)
    // 可选:降低 I 帧大小(通话中 I 帧会明显卡顿)
    setInteger(KEY_INTRA_REFRESH_PERIOD, 30)  // API 23+:周期帧内刷新
}

// 场景 3:直播推流(推荐 CBR,兼容 CDN)
val liveFormat = MediaFormat.createVideoFormat("video/avc", 1920, 1080).apply {
    setInteger(KEY_BIT_RATE, 6_000_000)        // 6 Mbps(1080p 直播)
    setInteger(KEY_FRAME_RATE, 30)
    setInteger(KEY_I_FRAME_INTERVAL, 2)
    setInteger(KEY_BITRATE_MODE, MediaFormat.BITRATE_MODE_CBR)
}

4.4 Profile 与 Level(H.264 / HEVC)

4.4.1 H.264 Profile 对比
Profile 编码特性 压缩效率 兼容性 适用场景
Baseline 无 B 帧、无 CABAC 最低 最广(所有设备) 实时通信、老设备
Main B 帧 + CABAC 中等 大部分设备 通用场景
High 所有特性 最高 中高端设备 高质量点播
Constrained High High - 模拟参考帧 较广 实时通信+高质量
kotlin 复制代码
// H.264 Profile 设置
videoFormat.setInteger(
    MediaFormat.KEY_PROFILE,
    MediaCodecInfo.CodecProfileLevel.AVCProfileHigh  // 推荐 High
)
// 或约束 Baseline(兼容性最佳)
videoFormat.setInteger(
    MediaFormat.KEY_PROFILE,
    MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline
)
// 或 Constrained Baseline(RTC 推荐)
videoFormat.setInteger(
    MediaFormat.KEY_PROFILE,
    MediaCodecInfo.CodecProfileLevel.AVCProfileConstrainedBaseline
)

Profile 对照表

复制代码
Baseline Profile:
  ├── I/P 帧,无 B 帧
  ├── CABAC:❌(仅 CAVLC)
  ├── 宏块:16×16 / 8×8 / 4×4
  └── 隔行扫描:❌

Main Profile:
  ├── I/P/B 帧
  ├── CABAC:✅
  ├── 宏块:16×16 / 8×8 / 4×4
  └── 隔行扫描:✅(MBAFF)
  └── 权重预测:❌

High Profile:
  ├── I/P/B 帧
  ├── CABAC:✅
  ├── 宏块:16×16 / 8×8 / 4×4
  ├── 自适应宏块划分:✅(4×4→16×16)
  ├── 独立分区:✅
  ├── 加权预测:✅(FMO + ASO)
  └── 8×8  DCT 变换:✅(比 4×4 更平滑)
4.4.2 H.264 Level 与设备能力

Level 决定了分辨率、帧率、码率的上限:

Level 最大分辨率 最大帧率 最大码率 Baseline 最大码率 High
1 176×144 15 64 kbps 80 kbps
1b 176×144 15 128 kbps 160 kbps
1.1 352×288 30 192 kbps 240 kbps
1.2 352×288 30 384 kbps 480 kbps
1.3 352×480 30 768 kbps 960 kbps
2 352×480 30 2 Mbps 2.5 Mbps
2.1 640×360 30 4 Mbps 5 Mbps
2.2 720×576 30 4 Mbps 5 Mbps
3 720×480 30 10 Mbps 12.5 Mbps
3.1 1280×720 30 14 Mbps 17.5 Mbps
3.2 1280×720 60 14 Mbps 17.5 Mbps
4 1920×1080 30 20 Mbps 25 Mbps
4.1 1920×1080 30 20 Mbps 25 Mbps
4.2 1920×1080 60 50 Mbps 62.5 Mbps
5 2048×1024 72 40 Mbps 50 Mbps
5.1 4096×2048 72 135 Mbps 168 Mbps
5.2 4096×2048 120 240 Mbps 300 Mbps
kotlin 复制代码
// 设置 Level(自动降级,超出设备能力则自动调整)
videoFormat.setInteger(
    MediaFormat.KEY_LEVEL,
    MediaCodecInfo.CodecProfileLevel.AVCLevel4
)

// ===== 智能设置 Profile + Level =====
fun selectBestProfileLevel(
    encoder: MediaCodec,
    mime: String,
    width: Int,
    height: Int,
    bitrate: Int,
    frameRate: Int
): Pair<Int, Int> {
    val caps = encoder.codecCapabilities
    val supportedProfiles = caps.profileLevels

    // 选择最高质量且支持的 Profile
    val profile = when {
        supportedProfiles.any { it.profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh } ->
            MediaCodecInfo.CodecProfileLevel.AVCProfileHigh
        supportedProfiles.any { it.profile == MediaCodecInfo.CodecProfileLevel.AVCProfileMain } ->
            MediaCodecInfo.CodecProfileLevel.AVCProfileMain
        else -> MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline
    }

    // 选择满足当前分辨率的最高 Level
    val level = supportedProfiles
        .filter { it.profile == profile }
        .maxByOrNull {
            // 选择 MaxLumaSamples 最大的 Level
            val maxSamples = getMaxSamplesForLevel(it.level)
            maxSamples
        }?.level ?: MediaCodecInfo.CodecProfileLevel.AVCLevel31

    return Pair(profile, level)
}

private fun getMaxSamplesForLevel(level: Int): Int {
    return when (level) {
        MediaCodecInfo.CodecProfileLevel.AVCLevel1 -> 25344
        MediaCodecInfo.CodecProfileLevel.AVCLevel1b -> 25344
        MediaCodecInfo.CodecProfileLevel.AVCLevel11 -> 101376
        MediaCodecInfo.CodecProfileLevel.AVCLevel12 -> 101376
        MediaCodecInfo.CodecProfileLevel.AVCLevel13 -> 101376
        MediaCodecInfo.CodecProfileLevel.AVCLevel2 -> 101376
        MediaCodecInfo.CodecProfileLevel.AVCLevel21 -> 202752
        MediaCodecInfo.CodecProfileLevel.AVCLevel22 -> 405504
        MediaCodecInfo.CodecProfileLevel.AVCLevel3 -> 405504
        MediaCodecInfo.CodecProfileLevel.AVCLevel31 -> 921600
        MediaCodecInfo.CodecProfileLevel.AVCLevel32 -> 1310720
        MediaCodecInfo.CodecProfileLevel.AVCLevel4 -> 2097152
        MediaCodecInfo.CodecProfileLevel.AVCLevel41 -> 2228224
        MediaCodecInfo.CodecProfileLevel.AVCLevel42 -> 2516582
        MediaCodecInfo.CodecProfileLevel.AVCLevel5 -> 5652480
        MediaCodecInfo.CodecProfileLevel.AVCLevel51 -> 10485760
        MediaCodecInfo.CodecProfileLevel.AVCLevel52 -> 16777216
        else -> 0
    }
}
4.4.3 HEVC(H.265)Profile 与 Level
kotlin 复制代码
// HEVC Profile
videoFormat.setInteger(
    MediaFormat.KEY_PROFILE,
    MediaCodecInfo.CodecProfileLevel.HEVCProfileMain  // Main / Main10 / Main10HDR10
)
// HEVC Level
videoFormat.setInteger(
    MediaFormat.KEY_LEVEL,
    MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel31
)
HEVC Profile 色深 HDR 用途
Main 8 bit 通用
Main10 10 bit HDR10
Main10 HDR10 10 bit ✅ + Static HDR10
Main10 HDR10Plus 10 bit ✅ + Dynamic HDR10+

4.5 高级可选参数

4.5.1 KEY_MAX_INPUT_SIZE:最大输入缓冲
kotlin 复制代码
// 建议值:分辨率 × 1.5(I420 格式大小)
videoFormat.setLong(
    MediaFormat.KEY_MAX_INPUT_SIZE,
    (width * height * 3 / 2).toLong()
)

// 或设置为 0(编码器自动分配)
videoFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, 0)

// 最小值约束
videoFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, 50_000)

⚠️ 若输入数据超过此大小,queueInputBuffer() 会抛出 MediaCodec.CODE_ERROR_MAX_INPUT_SIZE_EXCEEDED。设为 0 可让编码器自动选择,但可能导致初始延迟。

4.5.2 KEY_COMPLEXITY:编码复杂度(速度/质量权衡)
kotlin 复制代码
// 0(最快/最低质量)←→ 10 或更高(最慢/最高质量)
videoFormat.setInteger(MediaFormat.KEY_COMPLEXITY, 2)  // 默认中等

// 编码器能力查询
val encoderCaps = encoder.codecCapabilities
val minComplexity = encoderCaps encoderCapabilities.minimumSupportedInstances
val maxComplexity = encoderCaps.getEncoderCapabilities().complexityRange.high
Log.d("Complexity", "支持范围: ${minComplexity}--$maxComplexity")
复杂度值 编码速度 质量 功耗 适用场景
0--1 极快 实时通信、预览
2--4 移动端录制
5--7 中等 标准录制
8--10 最高 最高 离线高质量压缩
4.5.3 KEY_QUALITY:质量级别(CQ 模式下的 QP 控制)
kotlin 复制代码
// 仅在 BITRATE_MODE_CQ 下有效
// 值范围通常为 0--100(值越高质量越好)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    videoFormat.setInteger(MediaFormat.KEY_QUALITY, 80)  // 80/100
}
4.5.4 KEY_PREPEND_HEADER_TO_SYNC_FRAMES:关键帧头部注入
kotlin 复制代码
// API 23+:在每个同步帧(I 帧)前自动添加 SPS/PPS NAL 单元
// 推荐开启,确保每个 I 帧独立可解码
videoFormat.setInteger(
    MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES,
    1  // 1 = 开启,0 = 关闭
)
4.5.5 KEY_REPEAT_PREVIOUS_FRAME_AFTER:重复帧(慢动作)
kotlin 复制代码
// 用于慢动作编码(120fps 拍摄 → 30fps 输出)
// 指定帧显示时长(微秒)
// 120fps 拍摄 → 每帧 8333us → 设为 3×8333 = 25000us → 输出 30fps
videoFormat.setLong(
    MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER,
    25_000  // 25 ms = 帧率变为 40fps
)
4.5.6 KEY_INTRA_REFRESH_PERIOD:帧内刷新(API 23+)

帧内刷新是替代周期性 I 帧的低开销方案,在 RTC 场景中极为重要:

kotlin 复制代码
// 设置帧内刷新周期(毫秒)
// 设备内部会在每个周期内逐步刷新一行宏块,而非发送完整 I 帧
videoFormat.setLong(
    MediaFormat.KEY_INTRA_REFRESH_PERIOD,
    1_000  // 每秒刷新一次(等于 GOP 长度)
)

帧内刷新原理

复制代码
传统 I 帧(每 30 帧一个 I 帧):
  I[0] P[1] P[2] ... P[29] I[30] ...
  ↑ 大小 ~300KB                ↑ 再次 ~300KB(网络抖动!)

帧内刷新(每帧刷新一行宏块,替代 I 帧):
  I_row[0] P[1] ... P[14] I_row[15] P[16] ... I_row[29] I_row[30] ...
  ↑ ~20KB    逐步恢复         ↑ 每行 ~10KB(无大 I 帧!)
  抗丢包能力大幅提升
4.5.7 KEY_TEMPORAL_LAYERING:时域分层(API 31+)
kotlin 复制代码
// Android 12+(API 31):分层编码(Temporal Scalability)
// 将帧分为多个时域层:T0(关键帧)→ T1 → T2 → ...
// 低层丢失时,高层可选择性丢弃而不影响基础层

// 设置时域分层方案
videoFormat.setInteger(MediaFormat.KEY_TEMPORAL_LAYERING,
    VideoEncoderFeatures.TEMPORAL_LAYERING_BTBL_HEVC)  // HEVC
// 或
videoFormat.setInteger(MediaFormat.KEY_TEMPORAL_LAYERING,
    VideoEncoderFeatures.TEMPORAL_LAYERING_BTBL_H264)   // H.264

时域分层效果

复制代码
T0 (基础层):    I   I   I   I   I   I   I   I    ← 最重要,始终保留
T1 (增强层):      P   P   P   P   P   P   P       ← 丢包首选丢弃
T2 (增强层):        B   B   B   B   B   B           ← 进一步丢弃

总帧数:I P B P B P B P B P B P B P B P ...
       ↑ I (T0)
           ↑ T1 P
               ↑ T2 B
                   ↑ T1 P ...
4.5.8 HDR 元数据(API 31+)
kotlin 复制代码
// Android 12+:HDR10 编码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    // HDRStaticInfo(CTA-861.3)
    val hdrStaticInfo = android.media.HdrStaticInfo().apply {
        // Primaries(色域原色)
        primaries_x = intArrayOf(34000, 16000, 8500, 0, 0, 0)
        primaries_y = intArrayToIntArray(intArrayOf(32000, 30000, 39850, 0, 0, 0))
        // 白点
        white_point_x = 15635
        white_point_y = 16450
        // 最大/最小亮度
        max_luminance = 1_000_000  // 1000 nit
        min_luminance = 1_000       // 0.1 nit
    }
    videoFormat.setFeature(
        MediaFormat.FEATURE_HdrEncoding,
        true
    )
    // 注意:HEVC Main10 Profile 需单独设置
}

4.6 码率范围控制(Android 12+,API 31)

kotlin 复制代码
// Android 12+:设置码率范围(VBR 模式)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    videoFormat.setInteger(
        "max-bitrate",
        12_000_000  // VBR 模式下的最大码率
    )
    videoFormat.setInteger(
        "typical-bitrate",
        8_000_000   // 典型码率
    )
}

// 从 VideoCapabilities 读取支持的码率范围
val videoCaps = encoder.videoCapabilities
val bitrateRange = videoCaps.bitrateRange
Log.d("BitrateRange", "支持码率: ${bitrateRange.lower} -- ${bitrateRange.upper} bps")
val frameRateRange = videoCaps.supportedFrameRates
Log.d("FrameRateRange", "支持帧率: ${frameRateRange.lower} -- ${frameRateRange.upper}")
val widthRange = videoCaps.supportedWidths
Log.d("WidthRange", "支持宽度: ${widthRange.lower} -- ${widthRange.upper}")
val heightRange = videoCaps.supportedHeights
Log.d("HeightRange", "支持高度: ${heightRange.lower} -- ${heightRange.upper}")

五、音频编码参数详解

5.1 音频格式全景

MIME 类型 标准名称 minSdk 码率范围 声道 典型用途
audio/mp4a-latm AAC-LC API 21+ 64--320 kbps 单/双 通用音频
audio/he-aac HE-AAC v1 API 21+ 32--128 kbps 单/双 音乐流媒体
audio/he-aac-v2 HE-AAC v2 API 21+ 16--64 kbps 超低码率
audio/opus Opus API 21+ 6--510 kbps 单/双/多 实时通信
audio/amr-wb AMR-WB API 21+ 6.6--23.85 kbps 语音
audio/amr-nb AMR-NB API 21+ 4.75--12.2 kbps 语音(已淘汰)
audio/ac3 AC3 API 24+ 192--640 kbps 杜比环绕
audio/eac3 E-AC3(Dolby Digital+) API 24+ 256--1510 kbps 杜比全景声

5.2 AAC 编码参数

kotlin 复制代码
// ===== AAC-LC 标准配置 =====
val aacFormat = MediaFormat.createAudioFormat(
    MediaFormat.MIMETYPE_AUDIO_AAC,
    48_000,    // 采样率 48 kHz
    2          // 双声道
).apply {
    setInteger(MediaFormat.KEY_BIT_RATE, 128_000)    // 128 kbps(AAC-LC 推荐)
    setInteger(MediaFormat.KEY_AAC_PROFILE,
        MediaCodecInfo.CodecProfileLevel.AACObjectLC)
    setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 4096)  // 最大输入缓冲
}

// ===== HE-AAC v1(高效率,用于流媒体)=====
val heAacFormat = MediaFormat.createAudioFormat(
    MediaFormat.MIMETYPE_AUDIO_AAC,
    48_000,
    2
).apply {
    setInteger(MediaFormat.KEY_BIT_RATE, 64_000)     // 64 kbps(HE-AAC 推荐)
    setInteger(MediaFormat.KEY_AAC_PROFILE,
        MediaCodecInfo.CodecProfileLevel.AACObjectHE)
}

// ===== HE-AAC v2(超低码率)=====
val heAacV2Format = MediaFormat.createAudioFormat(
    MediaFormat.MIMETYPE_AUDIO_AAC,
    48_000,
    2
).apply {
    setInteger(MediaFormat.KEY_BIT_RATE, 32_000)     // 32 kbps
    setInteger(MediaFormat.KEY_AAC_PROFILE,
        MediaCodecInfo.CodecProfileLevel.AACObjectHEv2)
    // ⚠️ HE-AACv2 仅支持单声道输出
}

5.3 AAC Profile 详细对比

AAC Profile Object Type ID 效率 音质 最低码率 适用场景
AAC-LC (Low Complexity) 2 128 kbps (44kHz 立体声) 通用(最常用
HE-AAC v1 5 (SBR) 中高 64 kbps (44kHz 立体声) 流媒体音乐
HE-AAC v2 29 (PS+SBR) 极高 32 kbps (44kHz 立体声) 超低码率语音
AAC-LD (Low Delay) 29 --- --- --- 实时通信
AAC-ELD (Enhanced LD) 39 --- --- --- VoIP

SBR(Spectral Band Replication):将音频分为低频(基础)+ 高频(参数),低频全编码,高频用少量参数重建,节省约 50% 码率。

PS(Parametric Stereo):将立体声编码为单声道 + 空间参数,进一步节省约 50% 码率。

5.4 Opus 编码参数

kotlin 复制代码
// ===== Opus 音频编码(Android 21+,实时通信最佳选择)=====
val opusFormat = MediaFormat.createAudioFormat(
    MediaFormat.MIMETYPE_AUDIO_OPUS,
    48_000,     // Opus 必须 48 kHz
    2           // 声道数(Opus 支持 1--8 声道)
).apply {
    // Opus 无需额外参数,码率由编码器内部控制
    setInteger(MediaFormat.KEY_BIT_RATE, 64_000)   // 推荐 64 kbps(高质量语音)
    setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 5760)  // 120ms × 48kHz × 2ch × 2B
}

// Opus 推荐码率表:
//   6 kbps  :窄带语音(电话质量)
//   16 kbps :宽带语音(会议质量)
//   32 kbps :高质量语音
//   48 kbps :音乐质量(立体声)
//   64 kbps :高质量音乐
//   128 kbps:接近 CD 质量

5.5 PCM 编码参数

kotlin 复制代码
// PCM 16bit 线性编码(最通用)
val pcmFormat = MediaFormat.createAudioFormat(
    "audio/raw",  // 注意:非标准 MIME
    44_100,
    2
).apply {
    setInteger(MediaFormat.KEY_PCM_ENCODING,
        AudioFormat.ENCODING_PCM_16BIT)
}

5.6 音频参数速查表

参数 视频 KEY 音频 KEY 类型 典型值
采样率 --- KEY_SAMPLE_RATE Int 44100 / 48000
声道数 --- KEY_CHANNEL_COUNT Int 1 / 2 / 6
码率 KEY_BIT_RATE KEY_BIT_RATE Int 视频: 1--20 Mbps; 音频: 64--320 kbps
Profile KEY_PROFILE KEY_AAC_PROFILE Int AVCProfileHigh / AACObjectLC
最大输入 KEY_MAX_INPUT_SIZE KEY_MAX_INPUT_SIZE Long 视频: W×H×1.5; 音频: 4096
PCM 编码 --- KEY_PCM_ENCODING Int ENCODING_PCM_16BIT

六、编码器能力查询:设备适配的核心

6.1 完整能力查询工具类

kotlin 复制代码
/**
 * MediaCodecCapability:编码器能力查询工具
 * 在配置编码器前,先用此工具验证设备是否支持目标参数
 */
object MediaCodecCapability {

    data class EncoderInfo(
        val name: String,
        val mime: String,
        val profileLevels: List<Pair<Int, Int>>,
        val colorFormats: List<Int>,
        val maxInstances: Int,
        val bitrateRange: IntRange,
        val widthRange: IntRange,
        val heightRange: IntRange,
        val frameRateRange: IntRange
    )

    /**
     * 查询指定 MIME 类型的最佳编码器
     */
    fun findBestEncoder(mime: String): EncoderInfo? {
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)

        for (info in codecList.codecInfos) {
            if (info.isEncoder && info.supportedTypes.contains(mime)) {
                return getEncoderInfo(info, mime)
            }
        }
        return null
    }

    /**
     * 查询所有可用编码器
     */
    fun findAllEncoders(): List<EncoderInfo> {
        val result = mutableListOf<EncoderInfo>()
        val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)

        for (info in codecList.codecInfos) {
            if (info.isEncoder) {
                for (mime in info.supportedTypes) {
                    result.add(getEncoderInfo(info, mime))
                }
            }
        }
        return result
    }

    private fun getEncoderInfo(info: MediaCodecInfo, mime: String): EncoderInfo {
        val caps = info.getCapabilitiesForType(mime)

        val profileLevels = caps.profileLevels.map {
            Pair(it.profile, it.level)
        }

        val (bitrateRange, widthRange, heightRange, frameRateRange) = when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
                mime.startsWith("video/") -> {
                val vc = caps.videoCapabilities
                IntRange(vc.bitrateRange.lower, vc.bitrateRange.upper) to
                IntRange(vc.supportedWidths.lower, vc.supportedWidths.upper) to
                IntRange(vc.supportedHeights.lower, vc.supportedHeights.upper) to
                IntRange(
                    (vc.supportedFrameRates.lower * 1000).toInt(),
                    (vc.supportedFrameRates.upper * 1000).toInt()
                )
            }
            else -> {
                IntRange(0, Int.MAX_VALUE) to
                IntRange(0, Int.MAX_VALUE) to
                IntRange(0, Int.MAX_VALUE) to
                IntRange(0, Int.MAX_VALUE)
            }
        }

        return EncoderInfo(
            name = info.name,
            mime = mime,
            profileLevels = profileLevels,
            colorFormats = caps.colorFormats.toList(),
            maxInstances = caps.maxSupportedInstances,
            bitrateRange = bitrateRange,
            widthRange = widthRange,
            heightRange = heightRange,
            frameRateRange = frameRateRange
        )
    }

    /**
     * 检查目标配置是否在编码器能力范围内
     */
    fun isConfigurationSupported(
        encoder: EncoderInfo,
        width: Int,
        height: Int,
        bitrate: Int,
        frameRate: Int
    ): Boolean {
        return width in encoder.widthRange &&
                height in encoder.heightRange &&
                bitrate in encoder.bitrateRange &&
                frameRate * 1000 in encoder.frameRateRange
    }

    /**
     * 自动调整到支持的配置
     */
    fun autoAdjust(
        targetWidth: Int,
        targetHeight: Int,
        targetBitrate: Int,
        targetFrameRate: Int,
        encoder: EncoderInfo
    ): AdjustedConfig {
        val adjustedWidth  = adjustToRange(targetWidth, encoder.widthRange, 16)
        val adjustedHeight = adjustToRange(targetHeight, encoder.heightRange, 16)
        val adjustedBitrate = adjustToRange(targetBitrate, encoder.bitrateRange, 1000)
        val adjustedFrameRate = adjustToRange(
            targetFrameRate * 1000,
            encoder.frameRateRange,
            1000
        ) / 1000

        return AdjustedConfig(
            adjustedWidth, adjustedHeight,
            adjustedBitrate, adjustedFrameRate
        )
    }

    private fun adjustToRange(value: Int, range: IntRange, alignment: Int): Int {
        val aligned = (value / alignment) * alignment
        return aligned.coerceIn(range)
    }

    data class AdjustedConfig(
        val width: Int,
        val height: Int,
        val bitrate: Int,
        val frameRate: Int
    )
}

6.2 使用示例

kotlin 复制代码
// 查询 AVC 编码器能力
val avcEncoder = MediaCodecCapability.findBestEncoder("video/avc")
avcEncoder?.let {
    Log.d("Encoder", "编码器: ${it.name}")
    Log.d("Encoder", "分辨率范围: ${it.widthRange} × ${it.heightRange}")
    Log.d("Encoder", "码率范围: ${it.bitrateRange}")
    Log.d("Encoder", "帧率范围: ${it.frameRateRange}")

    // 尝试配置 4K@30fps 编码
    val targetConfig = MediaCodecCapability.AdjustedConfig(
        3840, 2160, 30_000_000, 30
    )

    if (!MediaCodecCapability.isConfigurationSupported(
            it, 3840, 2160, 30_000_000, 30)) {
        // 自动调整
        val adjusted = MediaCodecCapability.autoAdjust(
            3840, 2160, 30_000_000, 30, it
        )
        Log.w("Encoder", "4K 不支持,自动降级为: "
            + "${adjusted.width}×${adjusted.height} @ ${adjusted.frameRate}fps, "
            + "${adjusted.bitrate / 1_000_000}Mbps")
    }
}

七、完整编码流程与 Demo

7.1 Surface 模式(推荐,性能最优)

kotlin 复制代码
/**
 * SurfaceVideoEncoder:Surface 模式的视频编码器
 * 特点:
 *   1. 无需手动处理 YUV 数据
 *   2. 数据直接从 Surface 传入编码器(零拷贝)
 *   3. 适合 Camera 实时编码、屏幕录制
 *   4. 性能最优,功耗最低
 */
class SurfaceVideoEncoder(
    private val context: Context,
    private val width: Int,
    private val height: Int,
    private val bitrate: Int,
    private val frameRate: Int,
    private val outputPath: String
) {
    private var encoder: MediaCodec? = null
    private var muxer: MediaMuxer? = null
    private var videoTrackIndex = -1
    private var isMuxerStarted = false
    private var isRunning = false
    private var encoderThread: Thread? = null

    private var inputSurface: Surface? = null

    // ===== 步骤 1:创建编码器 =====
    fun prepare(): Boolean {
        val mime = MediaFormat.MIMETYPE_VIDEO_AVC

        // 创建编码器
        encoder = MediaCodec.createEncoderByType(mime)

        // ===== 步骤 2:构建 MediaFormat =====
        val format = MediaFormat.createVideoFormat(mime, width, height).apply {
            setInteger(KEY_BIT_RATE, bitrate)
            setInteger(KEY_FRAME_RATE, frameRate)
            setInteger(KEY_I_FRAME_INTERVAL, 1)  // 每秒 1 个 I 帧
            setInteger(KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
            setInteger(KEY_BITRATE_MODE, MediaFormat.BITRATE_MODE_CBR)

            // Profile / Level(自动选择)
            setInteger(KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh)
            setInteger(KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel41)

            // 最大输入缓冲(自动)
            setLong(KEY_MAX_INPUT_SIZE, 0)

            // I 帧前添加 SPS/PPS
            setInteger(KEY_PREPEND_HEADER_TO_SYNC_FRAMES, 1)
        }

        // ===== 步骤 3:配置编码器 =====
        encoder!!.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

        // ===== 步骤 4:获取输入 Surface =====
        // Surface 创建后,可以直接向其绘制(Canvas/Camera/EglCore)
        inputSurface = encoder!!.createInputSurface()
        encoder!!.start()

        // ===== 步骤 5:创建 MediaMuxer =====
        muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
        isRunning = true

        // ===== 步骤 6:启动编码线程 =====
        encoderThread = Thread { encodeLoop() }
        encoderThread?.start()

        return true
    }

    /**
     * 获取输入 Surface,供 CameraX / Canvas / OpenGL ES 使用
     */
    fun getInputSurface(): Surface? = inputSurface

    // ===== 编码输出循环 =====
    private fun encodeLoop() {
        val bufferInfo = MediaCodec.BufferInfo()
        val frameIntervalUs = 1_000_000L / frameRate

        while (isRunning) {
            // 取输出缓冲
            val outputBufIndex = encoder!!.dequeueOutputBuffer(bufferInfo, 10_000)

            when {
                outputBufIndex == MediaCodec.INFO_TRY_AGAIN_LATER -> {
                    // 无可用缓冲,继续等待
                }
                outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                    // 编码器格式改变(第一个 buffer 时触发)
                    val newFormat = encoder!!.outputFormat
                    Log.d("Encoder", "输出格式: $newFormat")

                    // 添加视频轨道
                    videoTrackIndex = muxer!!.addTrack(newFormat)
                    muxer!!.start()
                    isMuxerStarted = true
                }
                outputBufIndex >= 0 -> {
                    val encodedData = encoder!!.getOutputBuffer(outputBufIndex)!!

                    if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG == 0) {
                        // 普通编码帧
                        if (isMuxerStarted && bufferInfo.size > 0) {
                            synchronized(muxer!!) {
                                muxer!!.writeSampleData(videoTrackIndex, encodedData, bufferInfo)
                            }
                        }
                    }

                    encoder!!.releaseOutputBuffer(outputBufIndex, false)
                }
            }
        }
    }

    /**
     * 发送帧到 Surface(Camera 实时编码时调用)
     * 时间戳单位:微秒(microseconds)
     */
    fun frameAvailable(timestampUs: Long) {
        // Surface 由 Camera 或 Canvas 绘制,无需手动处理
        // Camera 自动处理 buffer 交换
    }

    /**
     * 停止编码
     */
    fun stop() {
        isRunning = false

        // 发送 EOS 信号
        encoderThread?.join(2000)
        encoderThread = null

        try {
            encoder?.stop()
            encoder?.release()
            encoder = null

            muxer?.stop()
            muxer?.release()
            muxer = null

            inputSurface?.release()
            inputSurface = null
        } catch (e: Exception) {
            Log.e("Encoder", "释放失败", e)
        }
    }
}

7.2 ByteBuffer 模式(手动 YUV 填充)

kotlin 复制代码
/**
 * ByteBufferVideoEncoder:ByteBuffer 模式的视频编码器
 * 适用于:非 Camera 源(自定义图像生成、测试)
 */
class ByteBufferVideoEncoder(
    private val context: Context,
    private val width: Int,
    private val height: Int,
    private val bitrate: Int,
    private val frameRate: Int,
    private val outputPath: String
) {
    private var encoder: MediaCodec? = null
    private var muxer: MediaMuxer? = null
    private var videoTrackIndex = -1
    private var isMuxerStarted = false
    private var isRunning = false
    private var encodeThread: Thread? = null
    private var presentationTimeUs = 0L

    private val frameIntervalUs = 1_000_000L / 30
    private val yuvBuffer = ByteArray(width * height * 3 / 2)

    fun prepare(): Boolean {
        val mime = MediaFormat.MIMETYPE_VIDEO_AVC

        encoder = MediaCodec.createEncoderByType(mime)

        // ===== 查询可用色彩格式 =====
        val caps = encoder!!.getCapabilitiesForType(mime)
        val colorFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // API 30+:优先使用 YUV420Flexible
            if (caps.isFormatSupported(MediaFormat.createVideoFormat(mime, width, height)
                    .also { it.setInteger(KEY_COLOR_FORMAT,
                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible) }
                )) {
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
            } else if (caps.isFormatSupported(MediaFormat.createVideoFormat(mime, width, height)
                    .also { it.setInteger(KEY_COLOR_FORMAT,
                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar) }
                )) {
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar
            } else {
                caps.colorFormats.firstOrNull { isYuvFormat(it) }
                    ?: MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
            }
        } else {
            // API 21-29:手动选择
            if (caps.colorFormats.contains(
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar)) {
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar
            } else {
                caps.colorFormats.firstOrNull { isYuvFormat(it) }
                    ?: MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
            }
        }

        Log.d("Encoder", "选择的色彩格式: 0x${colorFormat.toString(16)}")

        val format = MediaFormat.createVideoFormat(mime, width, height).apply {
            setInteger(KEY_BIT_RATE, bitrate)
            setInteger(KEY_FRAME_RATE, frameRate)
            setInteger(KEY_I_FRAME_INTERVAL, 1)
            setInteger(KEY_COLOR_FORMAT, colorFormat)
            setInteger(KEY_BITRATE_MODE, MediaFormat.BITRATE_MODE_CBR)
            setInteger(KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh)
            setInteger(KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel41)
            setLong(KEY_MAX_INPUT_SIZE, (width * height * 3 / 2).toLong())
            setInteger(KEY_PREPEND_HEADER_TO_SYNC_FRAMES, 1)
        }

        encoder!!.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
        encoder!!.start()

        muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
        isRunning = true

        encodeThread = Thread { encodeLoop() }
        encodeThread?.start()

        return true
    }

    private fun isYuvFormat(cf: Int): Boolean {
        return cf in listOf(
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar,
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
        )
    }

    // ===== ByteBuffer 模式编码循环 =====
    private fun encodeLoop() {
        val bufferInfo = MediaCodec.BufferInfo()
        var inputDone = false

        while (isRunning) {
            // A. 填充输入缓冲
            if (!inputDone) {
                val inputBufIndex = encoder!!.dequeueInputBuffer(10_000)
                if (inputBufIndex >= 0) {
                    // ===== 步骤:获取 YUV 数据(这里用测试图案代替)=====
                    val inputBuf = encoder!!.getInputBuffer(inputBufIndex)!!
                    inputBuf.clear()

                    // 填充 YUV 数据(NV12 或 I420)
                    fillTestPattern(inputBuf, width, height, presentationTimeUs)

                    encoder!!.queueInputBuffer(
                        inputBufIndex,
                        0,               // offset
                        yuvBuffer.size,  // size
                        presentationTimeUs,
                        0                // flags
                    )

                    presentationTimeUs += frameIntervalUs
                }
            }

            // B. 取输出缓冲
            val outputBufIndex = encoder!!.dequeueOutputBuffer(bufferInfo, 10_000)
            when {
                outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                    val newFormat = encoder!!.outputFormat
                    videoTrackIndex = muxer!!.addTrack(newFormat)
                    muxer!!.start()
                    isMuxerStarted = true
                }
                outputBufIndex >= 0 -> {
                    val outputBuf = encoder!!.getOutputBuffer(outputBufIndex)!!
                    if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG == 0) {
                        if (isMuxerStarted && bufferInfo.size > 0) {
                            synchronized(muxer!!) {
                                muxer!!.writeSampleData(videoTrackIndex, outputBuf, bufferInfo)
                            }
                        }
                    }
                    encoder!!.releaseOutputBuffer(outputBufIndex, false)
                }
            }
        }

        // 处理剩余输出
        drainEncoder()
    }

    /**
     * 填充测试图案(彩色条纹)
     */
    private fun fillTestPattern(buf: ByteBuffer, w: Int, h: Int, timeUs: Long) {
        // NV12 格式:Y + UV 交错
        // Y: w × h 字节
        // UV: w × h/2 字节(U 和 V 交错)
        Arrays.fill(yuvBuffer, 0.toByte())

        // Y 分量:随帧变化(条纹动画)
        val stripeIndex = ((timeUs / 100_000) % 6).toInt()
        for (y in 0 until h) {
            for (x in 0 until w) {
                val value = if ((x / 64) % 2 == stripeIndex) 200 else 50
                yuvBuffer[y * w + x] = value.toByte()
            }
        }

        // UV 分量:固定灰色
        for (y in 0 until h / 2) {
            for (x in 0 until w / 2) {
                yuvBuffer[w * h + y * w + x * 2] = 128     // U
                yuvBuffer[w * h + y * w + x * 2 + 1] = 128 // V
            }
        }

        buf.put(yuvBuffer)
    }

    private fun drainEncoder() {
        val bufferInfo = MediaCodec.BufferInfo()
        while (true) {
            val outputBufIndex = encoder!!.dequeueOutputBuffer(bufferInfo, 10_000)
            if (outputBufIndex >= 0) {
                val outputBuf = encoder!!.getOutputBuffer(outputBufIndex)!!
                if (bufferInfo.size > 0 && isMuxerStarted) {
                    synchronized(muxer!!) {
                        muxer!!.writeSampleData(videoTrackIndex, outputBuf, bufferInfo)
                    }
                }
                encoder!!.releaseOutputBuffer(outputBufIndex, false)
                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
                    break
                }
            } else if (outputBufIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                break
            }
        }
    }

    /**
     * 编码一帧图像(供外部调用)
     */
    fun encodeFrame(argbData: IntArray) {
        // RGB → NV12
        rgbToNv12(argbData, yuvBuffer, width, height)

        // 获取输入缓冲并写入
        val inputBufIndex = encoder!!.dequeueInputBuffer(0)
        if (inputBufIndex >= 0) {
            val inputBuf = encoder!!.getInputBuffer(inputBufIndex)!!
            inputBuf.clear()
            inputBuf.put(yuvBuffer)
            encoder!!.queueInputBuffer(
                inputBufIndex, 0, yuvBuffer.size,
                presentationTimeUs, 0
            )
            presentationTimeUs += frameIntervalUs
        }
    }

    private fun rgbToNv12(rgb: IntArray, yuv: ByteArray, w: Int, h: Int) {
        val frameSize = w * h
        var yi = 0
        var uvi = frameSize

        for (j in 0 until h) {
            for (i in 0 until w) {
                val px = rgb[j * w + i]
                val r = (px shr 16) and 0xFF
                val g = (px shr 8) and 0xFF
                val b = px and 0xFF
                val y = ((66 * r + 129 * g + 25 * b + 128) shr 8) + 16
                yuv[yi++] = y.coerceIn(0, 255).toByte()
                if (j % 2 == 0 && i % 2 == 0) {
                    val u = ((-38 * r - 74 * g + 112 * b + 128) shr 8) + 128
                    val v = ((112 * r - 94 * g - 18 * b + 128) shr 8) + 128
                    yuv[uvi++] = u.coerceIn(0, 255).toByte()
                    yuv[uvi++] = v.coerceIn(0, 255).toByte()
                }
            }
        }
    }

    fun stop() {
        isRunning = false
        encodeThread?.join(2000)
        drainEncoder()
        encoder?.stop()
        encoder?.release()
        encoder = null
        muxer?.stop()
        muxer?.release()
        muxer = null
    }
}

7.3 带动态码率调整的完整 Activity

kotlin 复制代码
/**
 * AdaptiveEncoderActivity:带动态码率调整的视频编码 Activity
 * 演示:
 *   1. 根据网络带宽自适应码率
 *   2. 根据编码器负载自适应码率
 *   3. 参数动态修改(码率/帧率/分辨率)
 */
class AdaptiveEncoderActivity : AppCompatActivity() {

    private lateinit var encoder: SurfaceVideoEncoder
    private lateinit var surfaceView: SurfaceView
    private var canvas: Canvas? = null

    // 编码参数
    private var currentBitrate = 4_000_000   // 4 Mbps
    private var currentFrameRate = 30
    private var currentWidth = 1280
    private var currentHeight = 720

    // 帧计数器(用于 FPS 统计)
    private var frameCount = 0
    private var lastFpsTime = System.currentTimeMillis()
    private var currentFps = 0f

    private val handler = Handler(Looper.getMainLooper())
    private val renderRunnable = object : Runnable {
        override fun run() {
            renderFrame()
            handler.postDelayed(this, (1000 / currentFrameRate).toLong())
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        surfaceView = SurfaceView(this)
        setContentView(surfaceView)

        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                initializeEncoder()
            }
            override fun surfaceChanged(holder: SurfaceHolder, f: Int, w: Int, h: Int) {}
            override fun surfaceDestroyed(holder: SurfaceHolder) {
                encoder.stop()
            }
        })
    }

    private fun initializeEncoder() {
        val outputPath = "${cacheDir}/encoded_${System.currentTimeMillis()}.mp4"
        encoder = SurfaceVideoEncoder(
            this, currentWidth, currentHeight,
            currentBitrate, currentFrameRate, outputPath
        )

        if (encoder.prepare()) {
            // 将编码器输入 Surface 配置到 SurfaceView
            encoder.getInputSurface()?.let { surface ->
                // Canvas 绘制模式(也可对接 CameraX)
                val sf = surface.lockCanvas(null)
                sf.drawColor(Color.BLACK)
                surface.unlockCanvasAndPost(sf)
            }

            handler.post(renderRunnable)
        }
    }

    private fun renderFrame() {
        val surface = encoder.getInputSurface() ?: return
        val sf = surface.lockCanvas(null) ?: return

        try {
            // 绘制内容
            sf.drawColor(Color.HSBtoRGB(
                (System.currentTimeMillis() % 10000 / 10000f), 0.8f, 0.8f))
            val paint = Paint().apply {
                color = Color.WHITE
                textSize = 40f
            }
            sf.drawText(
                "${currentWidth}×${currentHeight} @ ${currentBitrate / 1_000_000}Mbps FPS:${currentFps.toInt()}",
                20f, 60f, paint)

            // 帧计数
            frameCount++
            val now = System.currentTimeMillis()
            if (now - lastFpsTime >= 1000) {
                currentFps = frameCount * 1000f / (now - lastFpsTime)
                frameCount = 0
                lastFpsTime = now

                // 每秒评估是否需要调整码率
                evaluateBitrateAdaptation()
            }
        } finally {
            surface.unlockCanvasAndPost(sf)
        }
    }

    /**
     * 动态码率调整策略
     * 实际项目中可接入网络带宽探测(SpeedTest)结果
     */
    private fun evaluateBitrateAdaptation() {
        // 简单策略:FPS 低于目标 80% → 降低码率
        val targetFps = currentFrameRate
        val fpsRatio = currentFps / targetFps

        when {
            fpsRatio < 0.7 && currentBitrate > 1_000_000 -> {
                // 编码器跟不上,降低码率和分辨率
                adjustEncoding(bitrate = (currentBitrate * 0.7).toInt(),
                               width = (currentWidth * 0.75).toInt() and 0xFFFFFE,
                               height = (currentHeight * 0.75).toInt() and 0xFFFFFE)
                Log.d("Encoder", "码率下降: ${currentBitrate / 1_000_000}Mbps "
                        + "→ ${(currentBitrate * 0.7 / 1_000_000).toInt()}Mbps")
            }
            fpsRatio > 0.95 && currentFps > targetFps * 0.95 -> {
                // 编码器负载低,可提高质量
                if (currentBitrate < 12_000_000) {
                    adjustEncoding(bitrate = (currentBitrate * 1.2).toInt())
                    Log.d("Encoder", "码率上升: ${currentBitrate / 1_000_000}Mbps")
                }
            }
        }
    }

    private fun adjustEncoding(
        bitrate: Int = currentBitrate,
        frameRate: Int = currentFrameRate,
        width: Int = currentWidth,
        height: Int = currentHeight
    ) {
        currentBitrate = bitrate
        currentFrameRate = frameRate
        currentWidth = width
        currentHeight = height

        // 重新配置编码器(需要在 Surface 模式下重建)
        encoder.stop()
        val outputPath = "${cacheDir}/encoded_${System.currentTimeMillis()}.mp4"
        encoder = SurfaceVideoEncoder(this, width, height, bitrate, frameRate, outputPath)
        encoder.prepare()
    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacks(renderRunnable)
        encoder.stop()
    }
}

八、MediaMuxer:编码输出封装

8.1 MediaMuxer 参数与轨道添加

kotlin 复制代码
// ===== 创建 MediaMuxer =====
val muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
// 输出格式:
//   MUXER_OUTPUT_MPEG_4:MP4(默认,最通用)
//   MUXER_OUTPUT_WEBM:WebM(VP8/VP9/Opus)
//   MUXER_OUTPUT_3GPP:3GP(低码率手机视频)

// ===== 添加视频轨道 =====
val videoFormat = encoder.outputFormat  // 从编码器获取实际格式
val videoTrackIndex = muxer.addTrack(videoFormat)

// ===== 添加音频轨道 =====
val audioFormat = audioEncoder.outputFormat
val audioTrackIndex = muxer.addTrack(audioFormat)

// ===== 启动 =====
muxer.start()

// ===== 写入数据(音视频分开写)=====
muxer.writeSampleData(videoTrackIndex, videoBuf, videoBufferInfo)
muxer.writeSampleData(audioTrackIndex, audioBuf, audioBufferInfo)

// ===== 停止 =====
muxer.stop()
muxer.release()

8.2 音视频同步写入

kotlin 复制代码
// 音视频同步的核心:确保 DTS/PTS 顺序正确
data class MuxerFrame(
    val trackIndex: Int,
    val buffer: ByteBuffer,
    val info: MediaCodec.BufferInfo
)

class SyncMuxer(outputPath: String) {
    private val muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
    private var isStarted = false
    private var videoTrack = -1
    private var audioTrack = -1

    fun addTrack(mime: String, format: MediaFormat): Int {
        val trackIdx = muxer.addTrack(format)
        if (mime.startsWith("video/")) videoTrack = trackIdx
        if (mime.startsWith("audio/")) audioTrack = trackIdx
        return trackIdx
    }

    fun start() {
        muxer.start()
        isStarted = true
    }

    fun writeFrame(frame: MuxerFrame) {
        if (!isStarted) return
        synchronized(muxer) {
            muxer.writeSampleData(frame.trackIndex, frame.buffer, frame.info)
        }
    }

    // 按 PTS 排序后写入(确保解码顺序正确)
    private val pendingFrames = PriorityQueue(compareBy<MuxerFrame> {
        it.info.presentationTimeUs
    })

    fun writeFrameSorted(frame: MuxerFrame) {
        pendingFrames.add(frame)
        while (pendingFrames.isNotEmpty() &&
               pendingFrames.peek().info.presentationTimeUs <= getMinPts()) {
            val oldest = pendingFrames.poll()
            writeFrame(oldest)
        }
    }

    private var minVideoPts = Long.MAX_VALUE
    private var minAudioPts = Long.MAX_VALUE

    private fun getMinPts(): Long {
        return minOf(minVideoPts, minAudioPts)
    }
}

九、常见问题与解决方案

9.1 问题诊断树

复制代码
编码器初始化失败(createEncoderByType 返回 null)
│
├─ 原因 1:设备不支持该 MIME 类型
│  └─ 解决:使用 MediaCodecCapability.findAllEncoders() 查询可用编码器
│
├─ 原因 2:设备被其他应用占用(编码器独占)
│  └─ 解决:关闭其他相机/录制应用,重试
│
└─ 原因 3:编码器实例数超限
    └─ 解决:检查 caps.maxSupportedInstances,等待资源释放

9.2 编码质量差

症状 可能原因 解决方案
块效应明显 码率过低 提高 BIT_RATE 或切换 CQ 模式
马赛克/花屏 码率模式不匹配 运动场景用 VBR/CBR
I 帧卡顿 I 帧间隔太长或 I 帧过大 减小 I_FRAME_INTERVAL,增加码率
色彩失真 色彩格式不匹配 确认编码器支持的 COLOR_FORMAT
画面模糊 分辨率与码率不匹配 4K 需要 ≥ 25 Mbps

9.3 编码器参数冲突

kotlin 复制代码
// 常见错误 1:码率超出编码器能力
val caps = encoder.videoCapabilities
val clampedBitrate = caps.bitrateRange.clamp(bitrate)
videoFormat.setInteger(KEY_BIT_RATE, clampedBitrate)

// 常见错误 2:分辨率不对齐(宏块要求 16 对齐)
val alignedW = (width / 16) * 16
val alignedH = (height / 16) * 16
videoFormat.setInteger(KEY_WIDTH, alignedW)
videoFormat.setInteger(KEY_HEIGHT, alignedH)

// 常见错误 3:Surface 已销毁后仍尝试操作
inputSurface?.let { surf ->
    if (surf.isValid) {
        // 安全操作
    }
}

9.4 内存溢出(OOM)

kotlin 复制代码
// InputBuffer 大小过大 → OOM
// 解决:设置合理的 KEY_MAX_INPUT_SIZE
val safeInputSize = minOf(
    width * height * 3 / 2,  // I420 大小
    2 * 1024 * 1024         // 最大 2MB
)
videoFormat.setLong(KEY_MAX_INPUT_SIZE, safeInputSize.toLong())

// ByteBuffer 模式下避免重复分配
private val reusableBuffer = ByteArrayBuffer(width * height * 3 / 2)

十、总结:参数速查总表

10.1 视频编码参数速查

参数 KEY 常量 类型 必需 典型值 备注
MIME --- String video/avc 决定编解码格式
宽度 KEY_WIDTH Int 1920 需 16 的倍数
高度 KEY_HEIGHT Int 1080 需 16 的倍数
码率 KEY_BIT_RATE Int 5--15 Mbps 场景决定
帧率 KEY_FRAME_RATE Int 30 建议值,非强制
I 帧间隔 KEY_I_FRAME_INTERVAL Int 1--5 秒为单位
色彩格式 KEY_COLOR_FORMAT Int Surface 模式✅ COLOR_FormatSurface 零拷贝优先
Profile KEY_PROFILE Int 推荐 AVCProfileHigh 兼容性/质量
Level KEY_LEVEL Int 自动 AVCLevel4 设备自动降级
码率模式 KEY_BITRATE_MODE Int 推荐 BITRATE_MODE_CBR API 29+
最大输入 KEY_MAX_INPUT_SIZE Long 推荐 W×H×1.5 防止 OOM
预处理头部 KEY_PREPEND_HEADER_TO_SYNC_FRAMES Int 推荐 1 API 23+
复杂度 KEY_COMPLEXITY Int 可选 2--4 速度/质量权衡
帧内刷新 KEY_INTRA_REFRESH_PERIOD Long RTC 推荐 帧率×2 API 23+
时域分层 KEY_TEMPORAL_LAYERING Int 流媒体推荐 BTBL_* API 31+
重复帧 KEY_REPEAT_PREVIOUS_FRAME_AFTER Long 慢动作 N×帧长 120fps 拍摄
HDR 编码 FEATURE_HdrEncoding Boolean HDR 录制 true API 31+

10.2 音频编码参数速查

参数 KEY 常量 类型 必需 典型值
MIME --- String audio/mp4a-latm
采样率 KEY_SAMPLE_RATE Int 44100 / 48000
声道数 KEY_CHANNEL_COUNT Int 1 / 2
码率 KEY_BIT_RATE Int 64--320 kbps
AAC Profile KEY_AAC_PROFILE Int AAC 必需 AACObjectLC
最大输入 KEY_MAX_INPUT_SIZE Int 推荐 4096
PCM 编码 KEY_PCM_ENCODING Int PCM 必需 PCM_16BIT

10.3 推荐配置场景指南

场景 编码器 分辨率 码率 帧率 码率模式 I 帧
4K 电影录制 H.264 High 3840×2160 40--60 Mbps 30 CQ 2s
1080p 日常录制 H.264 High 1920×1080 12--20 Mbps 30 VBR 2s
720p 直播推流 H.264 High 1280×720 4--8 Mbps 30 CBR 1s
1080p 视频通话 H.264 Baseline 1920×1080 2--4 Mbps 30 CBR 1s
720p 视频通话 H.264 Baseline 1280×720 1.5--3 Mbps 30 CBR 0.5s
屏幕录制 H.264 High 屏幕分辨率 8--20 Mbps 屏幕帧率 CQ 5s
HEVC 高效录制 HEVC Main 1920×1080 4--8 Mbps 30 VBR 2s
语音通话 Opus 48kHz 32 kbps --- --- ---
高品质音频 AAC-LC 48kHz 256 kbps --- --- ---

十一、参考文献

# 文档 出处
1 **MediaCodec Android Developers**
2 **MediaFormat Android Developers**
3 VideoEncoderFeatures Android Developers
4 H.264/AVC标准 ITU-T Rec. H.264
5 H.265/HEVC标准 ITU-T Rec. H.265
6 MediaCodecInfo.CodecCapabilities Android Developers
7 Android 媒体性能优化指南 Android Developers
8 MediaMuxer Android Developers

相关推荐
JohnnyDeng942 小时前
Android 自定义 View:Canvas 绘图与事件分发深度解析
android
音视频牛哥3 小时前
大牛直播SDK(SmartMediaKit)Windows平台RTSP/RTMP直播播放SDK集成说明(C#版)
音视频·低延迟rtsp播放器·windows rtsp播放器·windows rtmp播放器·低延迟rtmp播放器·c# rtsp播放器·c# rtmp播放器
薛定猫AI5 小时前
【深度解析】Gemini Omni 多模态生成与 Agent 化创作工作流:从视频编辑到 UI 生成的技术演进
人工智能·ui·音视频
Android小码家5 小时前
Framework之Launcher小窗开发
android·framework·虚拟屏·小窗
赏金术士6 小时前
第七章:状态管理实战与架构总结
android·ui·kotlin·compose
颂love7 小时前
MySQL的执行流程
android·数据库·mysql
云起SAAS11 小时前
抖音小游戏源码 - 消消乐 | 含激励广告+成就系统 | 开箱即用商业级消除游戏模板
android·游戏·广告联盟·看激励广告联盟流量主·抖音小游戏源码 - 消消乐
大貔貅喝啤酒13 小时前
基于Windows下载安装Android Studio 3.3.2版本教程(2026详细图文版)
android·java·windows·android studio
程序员码歌13 小时前
OpenSpec 到 Superpowers:AI 编码从说清到做对
android·前端·人工智能