25、屏幕录制实现:MediaProjection API、虚拟显示、音频内录、权限处理

屏幕录制,说白了就是把手机屏幕上显示的内容抓取下来,存成视频文件。这在直播、录课、游戏录制场景里太常见了。Android 从 5.0 开始提供了 MediaProjection API,算是正式把屏幕录制能力开放给了开发者。

我个人习惯把屏幕录制拆成三个核心环节:权限获取、虚拟显示创建、音视频编码。今天咱们就一个一个掰开揉碎讲清楚。

核心流程概览

用户授权 → 获取 MediaProjection → 创建 VirtualDisplay → 绑定 Surface → 编码器写入 → 生成 MP4

用户授权弹窗 MediaProjection 实例 创建 VirtualDisplay 绑定 Surface MediaCodec 编码 MP4 音频内录 AudioRecord 音频编码 AAC 音视频混合器 (Muxer)

权限处理:别让用户一脸懵

屏幕录制需要动态申请权限。跟相机、麦克风不一样,它用的是 MediaProjectionManager 的 createScreenCaptureIntent() 方法。嗯,这里要注意:这不是普通权限,而是一个 Intent。

我记得第一次做屏幕录制时,直接调了 requestPermissions,结果发现根本弹不出授权窗口。后来才明白,屏幕录制走的是 startActivityForResult 路线。

// 获取 MediaProjectionManager
MediaProjectionManager projectionManager = 
    (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);

// 发起授权请求
Intent intent = projectionManager.createScreenCaptureIntent();
startActivityForResult(intent, REQUEST_CODE_SCREEN_CAPTURE);

// 在 onActivityResult 中处理结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_SCREEN_CAPTURE) {
        if (resultCode == RESULT_OK) {
            // 授权成功,创建 MediaProjection 实例
            mediaProjection = projectionManager.getMediaProjection(resultCode, data);
        } else {
            // 用户拒绝了,给个提示
            Toast.makeText(this, "屏幕录制需要授权才能使用", Toast.LENGTH_LONG).show();
        }
    }
}

避坑指南

我曾经遇到过一个问题:在 Android 10 以上,如果应用没有 FOREGROUND_SERVICE 权限,屏幕录制会在后台被系统杀掉。解决方案是启动一个前台服务,并显示通知。

虚拟显示:把屏幕内容"映射"到 Surface 上

拿到 MediaProjection 之后,下一步就是创建 VirtualDisplay。你可以把它理解成一个"虚拟的屏幕",这个屏幕的内容会直接渲染到你指定的 Surface 上。

说白了,就是系统把真实屏幕的内容,复制了一份到你的 Surface 上。这个 Surface 通常来自 MediaCodec 的输入。

// 创建 VirtualDisplay
virtualDisplay = mediaProjection.createVirtualDisplay(
    "ScreenRecorder",           // 名称,随便起
    width,                      // 屏幕宽度
    height,                     // 屏幕高度
    densityDpi,                 // 屏幕密度
    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,  // 镜像模式
    surface,                    // MediaCodec 提供的 Surface
    null,                       // 回调
    null                        // Handler
);

这里有个细节:VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR 表示虚拟显示的内容和真实屏幕保持一致。如果你不加这个 flag,虚拟显示可能是空白的。我在项目中踩过这个坑,折腾了半天才发现是 flag 没传对。

音频内录:系统声音也能抓

屏幕录制光有画面不行,还得有声音。音频内录分两种情况:

  • 麦克风录音:用 AudioRecord 从麦克风采集,适合录解说
  • 系统内录音:从 AudioRecord 的 REMOTE_SUBMIX 设备采集,适合录游戏声音

我个人建议:如果是录游戏或直播,优先用 REMOTE_SUBMIX。但要注意,这个设备在普通模式下只能录系统声音,麦克风的声音需要另外混音。

// 音频内录配置
AudioRecord audioRecord = new AudioRecord(
    MediaRecorder.AudioSource.REMOTE_SUBMIX,  // 系统内录音源
    sampleRate,           // 采样率,推荐 44100
    AudioFormat.CHANNEL_IN_STEREO,  // 立体声
    AudioFormat.ENCODING_PCM_16BIT, // 16位 PCM
    bufferSize
);

// 读取音频数据
byte[] audioData = new byte[bufferSize];
while (isRecording) {
    int readSize = audioRecord.read(audioData, 0, bufferSize);
    if (readSize > 0) {
        // 将音频数据送入 MediaCodec 编码
        audioEncoder.feedData(audioData, readSize);
    }
}

小技巧

你想想看,如果同时录麦克风和系统声音,就需要做混音处理。我一般用 AudioMixer 或者自己写一个简单的 PCM 叠加算法。注意音量不要溢出,否则会有爆音。

音视频编码与混合

视频用 MediaCodec 编码成 H.264,音频编码成 AAC。最后用 MediaMuxer 把两路流混合成一个 MP4 文件。

// 创建 MediaMuxer
MediaMuxer muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

// 添加视频轨道
int videoTrackIndex = muxer.addTrack(videoFormat);
// 添加音频轨道
int audioTrackIndex = muxer.addTrack(audioFormat);

// 开始混合
muxer.start();

// 在编码回调中写入数据
// videoBufferInfo 和 audioBufferInfo 分别来自视频和音频编码器
muxer.writeSampleData(videoTrackIndex, videoBuffer, videoBufferInfo);
muxer.writeSampleData(audioTrackIndex, audioBuffer, audioBufferInfo);

// 录制结束
muxer.stop();
muxer.release();

这里有个关键点:时间戳必须同步。视频和音频的 presentationTimeUs 要基于同一个时钟源。我习惯用 System.nanoTime() 来生成时间戳,这样两路流能对齐。

性能优化与注意事项

问题 原因 解决方案
录制卡顿 编码器负载过高 降低分辨率或帧率,使用硬件编码器
音画不同步 时间戳不一致 统一使用 System.nanoTime() 生成时间戳
内存泄漏 VirtualDisplay 未释放 在 onDestroy 中调用 virtualDisplay.release()
权限被回收 MediaProjection 被系统回收 使用前台服务保持进程活跃

重要提醒

Android 10 以上,屏幕录制必须在前台服务中运行。否则系统会在几分钟后杀掉你的录制进程。我曾经在测试时发现录到一半就停了,查了半天才发现是后台限制导致的。

嗯,屏幕录制这块内容其实不算复杂,但细节特别多。尤其是权限和虚拟显示这块,稍不注意就会出问题。我个人建议:先把核心流程跑通,再逐步优化性能和兼容性。别一上来就想搞个完美的录制工具,先让画面能录下来再说。


公众号:蓝海资料掘金营,微信deep3321