UniApp Android 原生插件实现 SRS WebRTC 屏幕推流




1、Android WebView 内核天生缺陷,H5 方案彻底失效
安卓系统的 WebView 内核底层并未暴露 getDisplayMedia 屏幕采集接口,这是系统级硬性限制,与 WebView 版本、权限配置、运行环境均无关系,直接导致基于 H5 的屏幕共享方案在安卓 WebView 中完全无法实现。
2、原生录屏 + WebRTC 方案,技术瓶颈无法绕过
即便改用安卓原生 MediaProjection 进行屏幕录制,也会面临核心技术障碍:原生采集的屏幕视频帧(基于 ImageReader/Surface)无法直接注入 WebRTC 的 MediaStream 流,根本原因是视频帧格式、渲染管线、编码入口三者完全不匹配,这并非代码写法问题,常规改造无法突破该底层限。



-
纯原生实现:无第三方付费 SDK,全部基于开源技术栈
-
轻量化:仅需 2 个 Java 文件 + 1 个 Vue 页面即可完整落地
-
广兼容:通过反射机制解决了华为海思芯片的 H264 编码器限制问题
-
端到端闭环:从录屏采集 → H264 编码 → WebRTC 信令 → SRS 推流全链路打通。
解决方案的技术栈如下:
|
层次 |
技术选型 |
版本 |
|
前端框架 |
UniApp(Vue) |
|
|
原生插件机制 |
DCloud UniModule |
uniapp-v8-release.aar |
|
屏幕采集 |
Android MediaProjection API |
API 21+ |
|
推流协议 |
WebRTC(H264 over SRTP) |
google-webrtc:1.0.32006 |
|
信令协议 |
SRS REST API(HTTP POST) |
okhttp:4.12.0 |
|
流媒体服务器 |
SRS 5.0.213 |
Docker 部署 |
|
JSON 序列化 |
Fastjson |
1.2.83 |
整体技术架构如下:

技术架构采用Android原生采集推流 + SRS 5.0 WebRTC 流媒体服务 + 多端实时分发的三层架构,端到端基于WebRTC协议实现低延迟跨端屏幕共享,彻底绕过UniApp WebView的系统级限制,同时解决原生录屏帧与WebRTC流格式不兼容的问题。
1. Android终端采集推流层(核心能力层)
该层通过纯原生技术栈完成屏幕采集、编码与推流,全程在Android应用进程内完成,无跨进程数据拷贝:
屏幕录制采集:通过MediaProjection系统API实现原生屏幕录制,直接获取系统级原始屏幕画面,从根源上绕开了Android WebView对getDisplayMedia API的限制,支持后台保活录屏与息屏录制。
H.264硬编码:通过MediaCodec硬件编码器,将原始屏幕帧高效压缩为标准H.264格式视频流,兼顾低功耗与高画质,同时适配WebRTC的编码规范。
WebRTC推流通道:通过PeerConnection构建原生WebRTC推流链路,将编码后的视频帧直接封装为RTP/SRTP流,推送到SRS服务器。整个流程在同进程内完成,解决了原生录屏帧无法直接注入WebRTC MediaStream的格式与渲染管线不匹配问题。
2. SRS 5.0流媒体服务层(中转分发层)
作为核心枢纽,SRS 5.0接收Android端推送的WebRTC流,完成协议解析与低延迟分发:
支持WebRTC协议的接入与转发,同时兼容RTMP/HTTP-FLV等多协议转换,可对接更多类型的播放终端;
提供高并发拉流能力,支持多终端同时连接,保证大规模场景下的稳定性;
基于RTP/SRTP协议的低延迟传输,确保屏幕画面从采集到播放的端到端延迟控制在毫秒级。
3. 多端分发观看层(消费层)
SRS将流分发后,支持浏览器、移动端等多终端通过WebRTC协议拉流,实现跨平台实时屏幕观看:
浏览器端可直接通过标准WebRTC API拉流播放,无需额外插件;
其他终端也可通过适配WebRTC协议的播放器接入,实现多端互通。



基于 DCloud 官方的 UniPlugin-Hello-AS 工程模板,新建 SrsScreenSharePlugin 模块:

Step 1:前台服务
Android 10 开始,系统强制要求:录屏必须在一个声明了 mediaProjection 类型的前台服务运行期间进行。 没有这个服务,MediaProjection 会被系统直接终止。
java
public class ScreenShareForegroundService extends Service { @Override public void onCreate() { super.onCreate(); // 创建通知 Notification notification = buildNotification(); // 关键:声明 mediaProjection 类型 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION); } else { startForeground(NOTIFICATION_ID, notification); } }}AndroidManifest.xml 中注册:
xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" /><service android:name=".ScreenShareForegroundService" android:foregroundServiceType="mediaProjection" android:exported="false" />Step 2:录屏采集 —— ScreenCapturerAndroid
用户授权后,通过 WebRTC 提供的
ScreenCapturerAndroid封装MediaProjection:java
// 用户授权后创建mScreenCapturer = new ScreenCapturerAndroid(data, callback);// 初始化并启动采集mScreenCapturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver());mScreenCapturer.startCapture(1280, 720, 30); // 720p@30fps踩坑点:
startForegroundService()是异步的!必须延迟等待前台服务就绪后再创建ScreenCapturerAndroid,否则MediaProjection会被系统立即回收:java
// 延迟800ms,确保前台服务的 startForeground() 已执行new Handler(Looper.getMainLooper()).postDelayed(() -> { mScreenCapturer = new ScreenCapturerAndroid(captureData, ...); initWebRTC(); startScreenCapture(); publishStreamToSRS();}, 800);Step 3:WebRTC 初始化 —— PeerConnection
java
// 创建 EGL 上下文(视频编解码需要)mEglBase = EglBase.create();// 创建 PeerConnectionFactory(含编解码器工厂)mPeerConnectionFactory = PeerConnectionFactory.builder() .setVideoEncoderFactory(encoderFactory) .setVideoDecoderFactory(decoderFactory) .createPeerConnectionFactory();// 配置 ICE(NAT穿透)List<PeerConnection.IceServer> iceServers = new ArrayList<>();iceServers.add(PeerConnection.IceServer.builder( "stun:stun.l.google.com:19302").createIceServer());PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;// 创建 PeerConnectionmPeerConnection = mPeerConnectionFactory .createPeerConnection(rtcConfig, observer);// 添加视频轨道(仅发送模式)VideoTrack videoTrack = mPeerConnectionFactory .createVideoTrack("screen_video_track", videoSource);mPeerConnection.addTransceiver(videoTrack, new RtpTransceiverInit(RtpTransceiverDirection.SEND_ONLY));Step 4:SRS 信令交互
WebRTC 推流到 SRS 的信令非常简洁,只需要一次 HTTP POST:
java
// 构造请求(只需3个字段)JSONObject request = new JSONObject();request.put("api", "https://服务器IP:1990/rtc/v1/publish/");request.put("streamurl", "webrtc://服务器IP:1985/live/livestream");request.put("sdp", offerSdp);// POST 到 SRSResponse response = httpClient.newCall( new Request.Builder().url(apiUrl).post(body).build()).execute();// 解析 Answer SDPJSONObject result = JSON.parseObject(response.body().string());if (result.getIntValue("code") == 0) { String answerSdp = result.getString("sdp"); mPeerConnection.setRemoteDescription(observer, new SessionDescription(Type.ANSWER, answerSdp)); // 推流开始!}SRS 的端口分配:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Step 5:前端调用
在 UniApp 中使用极其简单:
vue
<template> <view> <button @click="startShare">开始屏幕共享</button> <button @click="stopShare">停止推流</button> </view></template><script>const plugin = uni.requireNativePlugin('SrsScreenSharePlugin');export default { methods: { startShare() { plugin.startScreenShare({}, (res) => { if (res.code === 0) { uni.showToast({ title: '推流成功!' }); } }); }, stopShare() { plugin.stopScreenShare({}, (res) => { uni.showToast({ title: '已停止' }); }); } }}</script>踩坑实录:华为设备的 H264 编码器之战 这是整个项目最硬核的部分。SRS 服务器要求 H264 编码,但在华为平板上遭遇了连环坑。
坑1:SDP 中没有 H264
现象:SRS 返回
{"code": 400},拒绝推流。排查:打印 Offer SDP,发现只有 VP8 和 VP9,没有 H264。
根因:WebRTC 的
HardwareVideoEncoderFactory内部有一个硬编码的芯片白名单:plaintext
✅ OMX.qcom.* → 高通(Snapdragon)✅ OMX.Exynos.* → 三星(Exynos)❌ OMX.hisi.* → 华为(麒麟)← 被过滤了!华为设备明明有
OMX.hisi.video.encoder.avc这个 H264 硬件编码器,但被 WebRTC 认为”不支持”。解决:自定义
FullCodecVideoEncoderFactory,直接查询MediaCodecList绕过白名单:java
// 直接问系统:你有没有H264编码器?MediaCodecList codecList = new MediaCodecList(REGULAR_CODECS);for (MediaCodecInfo info : codecList.getCodecInfos()) { if (info.isEncoder() && supports("video/avc")) { // 找到了!手动添加到编码器列表 codecs.add(new VideoCodecInfo("H264", params)); }}坑2:编码器创建失败
现象:SDP 中有了 H264,但
createEncoder()返回 null。根因:白名单不仅影响
getSupportedCodecs(),也影响createEncoder()。DefaultVideoEncoderFactory内部还是会检查白名单。解决:通过 Java 反射,直接构造
HardwareVideoEncoder实例,跳过所有白名单检查:java
// 反射创建 WebRTC 内部类Class<?> hwEncoderClass = Class.forName( "org.webrtc.HardwareVideoEncoder");Constructor<?> ctor = hwEncoderClass.getDeclaredConstructors()[0];ctor.setAccessible(true);VideoEncoder encoder = (VideoEncoder) ctor.newInstance( wrapperFactory, "OMX.hisi.video.encoder.avc", h264MimeType, ...);坑3:编码输出全是空帧
现象:编码器创建成功,
encode()返回 OK,但 SRS 显示frames: 0。排查:用
DiagnosticEncoderWrapper包装编码器,监控回调输出:plaintext
编码输出#1 size=7386bytes ← 关键帧,正常编码输出#100 size=52bytes ← 52字节!全是空帧!编码输出#200 size=52bytes ← 持续空帧...根因:
HardwareVideoEncoder默认使用 Surface 模式(GPU 纹理直接送给编码器)。但华为设备的 EGL 跨上下文纹理共享不兼容,编码器从 GPU 读到的是一片空白。解决:强制切换为 YUV 模式,走 CPU 路径:
java
// surfaceColorFormat=null, eglContext=null → 强制 YUV 模式VideoEncoder encoder = ctor.newInstance( wrapperFactory, codecName, h264MimeType, null, // ← 关键:不用Surface模式 yuvColorFormat, params, 20, 0, bitrateAdjuster, null // ← 关键:不传EGL上下文);YUV 模式的数据流:
plaintext
录屏纹理(GPU) → toI420()转换(CPU) → 喂给MediaCodec → H264编码输出虽然多了一步 GPU→CPU 拷贝,但保证了编码器拿到真实像素数据。
实现效果 本文采用华为平板进行测试,将测试应用安装到平板后,点击“开始屏幕共享推流”,在PC端通过测试应用进行观看。
总结 本方案针对 UniApp 开发中 Android WebView 不支持
getDisplayMediaAPI、原生录屏帧无法适配 WebRTC 流的两大核心问题,构建了一套Android 原生采集推流 —SRS 5.0 中转分发 — 多端实时观看的三层技术架构。Android 端通过MediaProjection+MediaCodec+PeerConnection实现同进程的屏幕采集、硬编码与 WebRTC 推流,彻底绕开 WebView 的系统限制;SRS 5.0 负责 WebRTC 流的接收与低延迟分发,支持多终端同时拉流;浏览器等终端可直接通过 WebRTC 协议观看,最终实现了低延迟、稳定的跨端屏幕共享能力。
夜雨聆风








