25、屏幕录制实现:MediaProjection API、虚拟显示、音频内录、权限处理
屏幕录制,说白了就是把手机屏幕上显示的内容抓取下来,存成视频文件。这在直播、录课、游戏录制场景里太常见了。Android 从 5.0 开始提供了 MediaProjection API,算是正式把屏幕录制能力开放给了开发者。
我个人习惯把屏幕录制拆成三个核心环节:权限获取、虚拟显示创建、音视频编码。今天咱们就一个一个掰开揉碎讲清楚。
核心流程概览
用户授权 → 获取 MediaProjection → 创建 VirtualDisplay → 绑定 Surface → 编码器写入 → 生成 MP4
权限处理:别让用户一脸懵
屏幕录制需要动态申请权限。跟相机、麦克风不一样,它用的是 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