乐于分享
好东西不私藏

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

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

    此前我已分享过基于 SRS 流媒体服务 + WebRTC 实现的多端屏幕共享方案。在实际项目落地中,我们往往还需要支持 Android、iOS 等移动终端的屏幕共享能力。本文将通过 Android 原生插件,实现 UniApp 应用的屏幕共享功能。如果你对往期的多端屏幕共享方案感兴趣,可查看下方链接:

别被需求吓退!小项目用 WebRTC+SRS 轻松实现浏览器直播

引言
    我们知道在 UniApp 开发过程中,WebView 是极为常用的核心组件,它的主要作用是加载 H5 页面、复用 Web 端能力、快速集成 Web 生态的业务功能。如果直接照搬 Web 端屏幕共享的实现方案,尝试在 WebView 中通过 WebRTC 标准 API getDisplayMedia 实现屏幕共享,看似是最便捷的方案,但在实际安卓真机调试中,会直接遇到两个底层级、无法通过常规手段修复的致命问题:

1、Android WebView 内核天生缺陷,H5 方案彻底失效

    安卓系统的 WebView 内核底层并未暴露 getDisplayMedia 屏幕采集接口,这是系统级硬性限制,与 WebView 版本、权限配置、运行环境均无关系,直接导致基于 H5 的屏幕共享方案在安卓 WebView 中完全无法实现。

2、原生录屏 + WebRTC 方案,技术瓶颈无法绕过

    即便改用安卓原生 MediaProjection 进行屏幕录制,也会面临核心技术障碍:原生采集的屏幕视频帧(基于 ImageReader/Surface)无法直接注入 WebRTC 的 MediaStream 流,根本原因是视频帧格式、渲染管线、编码入口三者完全不匹配,这并非代码写法问题,常规改造无法突破该底层限。

解决方案
    本方案在 DCloud 官方 UniPlugin-Hello-AS 工程基础上,开发了一个名为 SrsScreenSharePlugin 的 UniApp Android 原生插件。通过该插件,UniApp 前端只需调用一个 JS 方法,即可实现将 Android 设备的屏幕通过 WebRTC 协议实时推送到 SRS 5.0 流媒体服务器,远端浏览器可即时拉流观看。方案核心特点:
  •     纯原生实现:无第三方付费 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(128072030); // 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.IceServericeServers = 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 的端口分配:

用途
端口
协议
推流 API
1990
HTTPS
流地址标识
1985
webrtc://
媒体传输
8000
UDP

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, 200, bitrateAdjuster,    null               // ← 关键:不传EGL上下文);

YUV 模式的数据流:

plaintext

录屏纹理(GPU) → toI420()转换(CPU) → 喂给MediaCodec → H264编码输出

虽然多了一步 GPU→CPU 拷贝,但保证了编码器拿到真实像素数据。

实现效果

    本文采用华为平板进行测试,将测试应用安装到平板后,点击“开始屏幕共享推流”,在PC端通过测试应用进行观看。

总结

本方案针对 UniApp 开发中 Android WebView 不支持getDisplayMedia API、原生录屏帧无法适配 WebRTC 流的两大核心问题,构建了一套Android 原生采集推流 —SRS 5.0 中转分发 — 多端实时观看的三层技术架构。Android 端通过MediaProjection+MediaCodec+PeerConnection实现同进程的屏幕采集、硬编码与 WebRTC 推流,彻底绕开 WebView 的系统限制;SRS 5.0 负责 WebRTC 流的接收与低延迟分发,支持多终端同时拉流;浏览器等终端可直接通过 WebRTC 协议观看,最终实现了低延迟、稳定的跨端屏幕共享能力。