限时福利领取


在短视频和在线教育爆发的时代,Android屏幕录制功能成为许多应用的刚需。但当你真正用MediaCodec实现录屏时,会发现理想很丰满,现实很骨感——帧率波动、内存泄漏、厂商兼容性问题接踵而至。今天就用我们团队趟过的坑,手把手教你打造高性能的录屏方案。

录屏性能对比

一、那些年我们踩过的MediaCodec大坑

  1. SurfaceTexture丢帧:当主线程繁忙时,SurfaceTexture的updateTexImage()会丢弃未处理的帧,导致录制视频卡顿
  2. BITRATE_MODE_CQ陷阱:质量优先模式在小米机型上会导致编码器缓冲堆积,最终视频播放速度异常
  3. 内存泄漏重灾区:未释放MediaCodec的InputSurface会导致GL上下文泄漏,应用退出后GPU内存仍在增长
  4. 厂商格式兼容:华为麒麟芯片对COLOR_FormatYUV420Flexible支持不完善,直接使用会绿屏

二、硬编码VS软编码实战对比

通过OPPO Reno6 Pro实测数据:

| 指标 | MediaCodec(H.264) | FFmpeg(x264) | |------------|------------------|-------------| | 1080P延迟 | 120ms | 350ms | | 功耗 | 2.1W/h | 4.7W/h | | 兼容性 | 需处理厂商差异 | 全版本通用 |

结论:追求低延迟和低功耗必选MediaCodec,但需要做好兼容层封装。

三、从零搭建录制流水线(Kotlin/Java双实现)

关键代码片段(省略异常处理):

// 1. 创建虚拟显示器
val surfaceTexture = SurfaceTexture(0).apply {
    setDefaultBufferSize(width, height)
}
val surface = Surface(surfaceTexture)

// 2. 配置MediaCodec(关键参数)
val format = MediaFormat.createVideoFormat(MIMETYPE, width, height).apply {
    setInteger(MediaFormat.KEY_COLOR_FORMAT, 
        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) // 必须
    setInteger(MediaFormat.KEY_BITRATE_MODE, 
        MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR) // 避免CQ
    setInteger(MediaFormat.KEY_FRAME_RATE, 30)
}

// 3. 启动编码器线程
val codec = MediaCodec.createEncoderByType(MIMETYPE).apply {
    configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    createInputSurface().also { inputSurface -> 
        start()
        virtualDisplay = mediaProjection.createVirtualDisplay(
            "RECORDER", width, height, dpi,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
            inputSurface, null, null)
    }
}

编码流程示意图

四、让性能飞起来的黑科技

  1. ByteBuffer对象池:复用ByteBuffer减少GC卡顿

    private val bufferPool = SynchronizedPool<ByteBuffer>(5);
    
    fun getInputBuffer(index: Int): ByteBuffer {
        return bufferPool.acquire()?.also { it.clear() } 
            ?: codec.getInputBuffer(index)!!
    }
  2. 动态比特率算法:根据帧复杂度调整码率

    fun calculateBitrate(frameTimeNanos: Long): Int {
        val fps = 1e9 / frameTimeNanos
        return when {
            fps < 15 -> originalBitrate * 1.5f // 降帧补偿
            fps > 45 -> originalBitrate * 0.8f // 节省带宽
            else -> originalBitrate
        }.toInt()
    }

五、血泪换来的兼容性方案

  1. Android 12受限模式
  2. 在AndroidManifest添加<uses-permission android:name="android.permission.RECORD_AUDIO"/>
  3. 启动前检查MediaProjectionManager.isProjectionEnabled()

  4. 荣耀YUV异常处理

    // 检测到荣耀机型时强制使用NV12
    if (Build.MANUFACTURER.equalsIgnoreCase("HONOR")) {
        format.setInteger(KEY_COLOR_FORMAT, 
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
    }

六、真机实测数据

| 机型 | 平均帧率 | CPU占用 | 内存增量 | |------------|---------|--------|---------| | OPPO FindX5| 29.8fps | 12% | +38MB | | 三星S22 Ultra | 28.3fps | 15% | +45MB | | 红米K50 | 26.7fps | 18% | +52MB |

思考题:4K@30fps实现密钥

提示方向: 1. 使用MediaCodec.createPersistentInputSurface() 2. 分片编码+多线程处理 3. 预加载GL Shader避免编译卡顿

优化无止境,欢迎在评论区分享你的高阶玩法。下期我们会揭秘如何用OpenGL实现实时美颜录屏,敬请期待!

Logo

音视频技术社区,一个全球开发者共同探讨、分享、学习音视频技术的平台,加入我们,与全球开发者一起创造更加优秀的音视频产品!

更多推荐