15、屏幕共享:屏幕采集API、MediaProjection使用、屏幕流编码与传输
屏幕共享,说白了就是把手机屏幕上的内容实时分享给远端的人。这在视频会议里是个刚需——你想想看,演示PPT、展示代码、远程协助,哪个离得开它?
我在做第一个WebRTC会议APP时,就踩过屏幕共享的坑。当时觉得不就是录个屏嘛,结果发现权限、编码、传输,每一步都有讲究。今天咱们就把这块彻底捋清楚。
15.1 屏幕采集的核心API
Android平台做屏幕采集,绕不开两个关键类:MediaProjectionManager 和 MediaProjection。前者负责申请权限,后者负责拿到屏幕数据。
嗯,这里要注意:屏幕采集需要用户明确授权。你不能偷偷录屏,这是Android安全机制的红线。
核心流程:
- 通过系统服务获取
MediaProjectionManager - 发起权限请求Intent
- 在
onActivityResult中获取授权结果 - 创建
MediaProjection实例 - 创建
VirtualDisplay将屏幕内容输出到Surface
// 获取MediaProjectionManager
MediaProjectionManager projectionManager =
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
// 发起权限请求
Intent intent = projectionManager.createScreenCaptureIntent();
startActivityForResult(intent, REQUEST_CODE_SCREEN_CAPTURE);
// 处理授权结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_SCREEN_CAPTURE) {
if (resultCode == RESULT_OK) {
// 创建MediaProjection实例
MediaProjection projection =
projectionManager.getMediaProjection(resultCode, data);
// 接下来就可以创建VirtualDisplay了
}
}
}
我个人习惯把这段逻辑封装成一个工具类。因为每次写重复的权限请求代码,真的很烦。
15.2 MediaProjection的实战用法
拿到 MediaProjection 对象后,下一步就是创建 VirtualDisplay。这个VirtualDisplay就像一块虚拟屏幕,你把内容投上去,它就会通过Surface输出。
我记得第一次用的时候,搞混了 VirtualDisplay 的宽高和屏幕实际分辨率。结果录出来的画面比例不对,远端看到的画面被拉伸了。后来我学乖了,直接用 DisplayMetrics 获取真实尺寸。
// 获取屏幕尺寸
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
int width = metrics.widthPixels;
int height = metrics.heightPixels;
int density = metrics.densityDpi;
// 创建VirtualDisplay
VirtualDisplay virtualDisplay = projection.createVirtualDisplay(
"ScreenShare",
width, height, density,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
surface, // 这个surface来自MediaCodec或SurfaceTexture
null,
null
);
小技巧: 如果你只需要分享部分区域(比如某个应用窗口),可以调整VirtualDisplay的宽高参数。但要注意,Android没有原生API支持窗口级采集,需要配合辅助功能服务来实现。
15.3 屏幕流的编码与传输
屏幕数据拿到手了,怎么传给远端?答案是用 MediaCodec 编码成视频流,然后通过WebRTC的 VideoTrack 发送出去。
这里有个关键点:屏幕共享的编码参数和摄像头不一样。摄像头追求低延迟,屏幕共享追求高清晰度。你想想看,PPT上的小字如果糊了,远端根本看不清。
| 参数 | 摄像头推荐 | 屏幕共享推荐 |
|---|---|---|
| 码率 | 1-2 Mbps | 3-8 Mbps |
| 帧率 | 30 fps | 15-24 fps |
| 分辨率 | 640x480 / 1280x720 | 1920x1080(原分辨率) |
| 编码器 | H.264 / VP8 | H.264 / H.265(硬件编码) |
我在项目中遇到过一个问题:屏幕共享时CPU占用飙升。后来发现是用了软件编码器。换成硬件编码器后,CPU占用从80%降到了15%。所以,能用硬件编码就别用软件。
// 配置MediaCodec用于屏幕编码
MediaFormat format = MediaFormat.createVideoFormat(
MediaFormat.MIME_TYPE_AVC, width, height);
format.setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000); // 4 Mbps
format.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2); // 关键帧间隔2秒
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIME_TYPE_AVC);
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface inputSurface = encoder.createInputSurface();
encoder.start();
// 将inputSurface传给VirtualDisplay
virtualDisplay = projection.createVirtualDisplay(
"ScreenShare", width, height, density,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
inputSurface, null, null
);
避坑指南: 我曾经在华为手机上遇到编码器崩溃的问题。原因是某些厂商的硬件编码器对 COLOR_FormatSurface 支持不完整。解决方案是降级到 COLOR_FormatYUV420Flexible,但需要手动做颜色空间转换。嗯,这个坑我踩了整整两天。
15.4 与WebRTC VideoTrack的对接
编码后的数据怎么喂给WebRTC?最直接的方式是自定义 VideoSource。WebRTC提供了 VideoSource 的基类,你只需要实现 onFrameCaptured 方法。
// 创建自定义VideoSource
VideoSource videoSource = peerConnectionFactory.createVideoSource(false);
// 创建VideoTrack
VideoTrack videoTrack = peerConnectionFactory.createVideoTrack(
"screen_track_id", videoSource);
// 将编码后的帧送入VideoSource
// 注意:这里需要将MediaCodec输出的ByteBuffer转为VideoFrame
VideoFrame videoFrame = convertToVideoFrame(encodedData, width, height);
videoSource.onFrameCaptured(videoFrame);
// 将VideoTrack添加到PeerConnection
peerConnection.addTrack(videoTrack);
不过说实话,我更推荐用 SurfaceTextureHelper 来做这个对接。它内部处理了纹理转换和线程同步,比自己手动转 VideoFrame 省心得多。
15.5 屏幕共享的完整架构
来,我画了一张图,把整个流程串起来。你看完就明白了。
从图里你能看到,整个链路其实不复杂。但每个环节都有坑:权限申请、编码参数、颜色格式、线程安全……任何一个地方出问题,屏幕共享就挂了。
15.6 性能优化与注意事项
最后,我总结几条实战经验,你直接拿去用:
- 分辨率降级: 如果设备性能不足,可以降低分辨率。比如从1080p降到720p,码率从4M降到2M。我在低端机上试过,效果还行。
- 动态码率: 根据网络状况动态调整码率。画面静止时降低码率,画面变化剧烈时提高码率。WebRTC的
BitrateSettings可以帮你做这个。 - 关键帧间隔: 屏幕共享建议设置2-3秒一个关键帧。太短浪费带宽,太长远端加入时等待太久。
- 内存管理: 屏幕采集会产生大量帧数据。记得及时释放
ByteBuffer,不然内存会暴涨。我见过一个案例,内存从200M涨到1.2G,然后APP被系统杀了。
个人建议: 如果你做的是企业级会议APP,建议加上屏幕共享的预览功能。让用户看到自己正在分享什么,避免尴尬。这个预览可以用 SurfaceView 绑定到VirtualDisplay的另一个Surface上实现。
好了,屏幕共享这块就讲到这里。核心就是三件事:拿到权限、编码数据、传给WebRTC。你动手写一遍,遇到问题再回来看这篇文章,应该就能搞定了。
公众号:蓝海资料掘金营,微信deep3321