限时福利领取


在开发需要录屏功能的Android应用时,我们常常会遇到音频视频不同步、录制卡顿、兼容性差等问题。今天就来分享一套经过实战检验的解决方案,使用MediaProjection+MediaCodec+MediaMuxer技术栈实现高效录屏。

录屏技术栈示意图

为什么选择MediaCodec方案

Android提供了两种主要的录屏方案:

  • MediaRecorder方案
  • 优点:API简单,集成快速
  • 缺点:灵活性差,无法精细控制编码参数,难以处理特殊需求

  • MediaCodec+MediaMuxer方案

  • 优点:支持硬件编码,性能更好
  • 可精确控制每一帧数据
  • 支持自定义处理流程
  • 能实现更低的延迟

对于需要高质量录屏的场景,我们显然应该选择后者。

核心实现步骤

1. 初始化MediaProjection

首先需要获取录屏权限,这是整个流程的起点:

// 在Activity中请求录屏权限
val mediaProjectionManager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startActivityForResult(
    mediaProjectionManager.createScreenCaptureIntent(),
    REQUEST_CODE_SCREEN_CAPTURE
)

// 处理授权结果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_CODE_SCREEN_CAPTURE) {
        val mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data!!)
        // 保存mediaProjection实例后续使用
    }
}

2. 配置AudioRecord采集音频

音频采集需要特别注意参数配置和时间戳处理:

val audioSource = MediaRecorder.AudioSource.MIC
val sampleRate = 44100 // 采样率
val channelConfig = AudioFormat.CHANNEL_IN_MONO
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
val bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)

val audioRecord = AudioRecord(
    audioSource,
    sampleRate,
    channelConfig,
    audioFormat,
    bufferSize * 2 // 适当放大缓冲区
)

3. 配置MediaCodec进行编码

视频编码建议使用硬件加速的H.264编码器:

val mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
val format = MediaFormat.createVideoFormat(
    MediaFormat.MIMETYPE_VIDEO_AVC, 
    width, 
    height
).apply {
    setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
    setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
    setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
    setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval)
}

mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
val inputSurface = mediaCodec.createInputSurface()
mediaCodec.start()

时间戳同步处理

音视频同步是录屏的核心难点,这里展示关键同步代码:

// 视频帧时间戳处理
fun onFrameAvailable(surfaceTexture: SurfaceTexture) {
    surfaceTexture.updateTexImage()
    val pts = System.nanoTime() / 1000 // 微秒单位
    // 将pts传递给编码器...
}

// 音频时间戳处理
val audioBuffer = ByteArray(bufferSize)
var audioPts = 0L
val readSize = audioRecord.read(audioBuffer, 0, bufferSize)
if (readSize > 0) {
    audioPts += 1000000L * readSize / (2 * sampleRate)
    // 将音频数据和pts传递给编码器...
}

时间戳同步示意图

性能优化技巧

  1. 使用双缓冲队列:避免直接在回调线程处理数据
  2. 调整关键编码参数
  3. 适当降低帧率(如24fps)
  4. 使用可变比特率(VBR)
  5. 调整关键帧间隔
  6. 及时释放资源:在Activity生命周期结束时确保释放所有资源

常见问题解决

  1. Android版本兼容性问题
  2. 不同版本对MediaCodec的支持有差异
  3. 解决方案:做好版本判断和fallback处理

  4. 虚拟屏适配问题

  5. 部分设备使用虚拟屏技术
  6. 解决方案:检查display.isValid()并做相应处理

  7. 内存泄漏问题

  8. 忘记释放MediaCodec和MediaProjection
  9. 解决方案:实现完整的生命周期管理

进阶方向

掌握了基础录屏功能后,可以考虑:

  1. 使用MediaCodec的异步模式提升性能
  2. 集成OpenGL ES实现实时滤镜
  3. 支持多路音视频流混合

这套方案已经在我们多个产品中稳定运行,希望对你有所帮助。如果有任何问题,欢迎在评论区交流讨论。

Logo

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

更多推荐