学习笔记
Java基础相关
JVM内存模型及线程空间
动态代理
java并发编程
Java基础知识
Java中Future
Java中9种常见的CMS GC问题分析与解决
移动端相关
杂乱整理
HarmonyOS 鸿蒙开发知识
ArkTS中如何自定义组件和复用统一样式
Android开发相关
WebView设置圆角
📄 Android 实时屏幕共享技术方案文档
Android线程与线程池全面解析:从使用到源码剖析
🔥 RecyclerView全面解析:从架构原理到性能优化与面试指南
🔥 OkHttp全面解析:从架构原理到性能优化与面试指南
🔥 Retrofit全面解析:从设计架构与核心思想到常见问题
开发工具相关
Git cmd学习整理
Markdown用法大全集
【2023年12月】工作常用
Git如何单独合并某次提交到另一个分支
Debina/Ubuntu安装Node环境
🐳 Docker 命令行与 Docker Compose 全面指南
使用Podman来替代Docker
中文注释提交git后变成"\u0087\u0079..."问题
macOS 容器化工具深度解析:Docker Desktop、OrbStack、Podman 及其他替代方案对比
前端开发相关
Node+TypeScript相关记录
TypeScript 读写 MariaDB
Node TypeScript项目 token生成、管理及拦截校验的实现
TypeScript+Express创建和实现一个服务示例
Express接口处理器抽取注册方式
Express 实现 RESTful API
创建 TypeScript Express 项目,并配置直接用 npm start 运行
TypeScript + Express 实现文件下载接口
export 和 export default的区别
TypeScript+Express 实现用户注册和登录接口
TypeScript 和 JavaScript 中,`===` 和 `==`
CSS中的尺寸大小标准
小程序px和rpx
使用Python快速处理Excel的合并拆分
读书写作相关
一些句子01
李敖语录
罗翔老师的一些经典语句
周易相关知识
周易是对自然描述还是为自然立法
40句落寞诗词,穿透柔魂弱魄
杂玩整理
黑苹果睿频问题
基于纯Linux自己部署Nas构思
Ubuntu换源
Ubuntu挂载tf卡
Ubuntu运行Docker报错
Ubuntu安装运行Docker报错处理
官方镜像安装Docker
Docker 设置root dir 切换数据到其他存储位置
systemctl stop docker 报错
NextCloud安装ffmpeg 显示视频缩略图
Docker源不生效解决方式
Docker源不生效解决方式II——搭建docker-hub镜像
搞定群晖总Docker部署gitea启用ssh协议
MacOS一键安装命令软件列表
群晖ssl证书目录
Android通过ADB命令播放视频
[完成] 群晖自动更新https证书项目
Linux设备整机限速
Linux限速2
Gitea部署Runner服务
精选网站
有声主播知识
学习笔记
有声主播入门到进阶
有声主播新手的入门练手内容推荐
DeepSeek分析喜马拉雅旗下 喜播平台 的有声主播培训
录书设备资料1
典故专辑资料整理
0B. 脚本和大纲
0A. 前置准备资料
Ai大数据模型
模型整理
使用 DeepSeek 通用公式
学会这8招,让DeepSeek变得超好用!
大数据购物分析选优
Windows和Ubuntu部署DeepSeek性能差距
本地部署 Ollam+DeepSeek 探索爬坑
模型对比测试
React Native 和 native 半屏弹窗
React Native Modal + WebView
AI模型使用心得
临时
Linux下对设备进行限速
C++ 学习
01 Android开发学习C++
其他资料分组
【面试相关】💡 面试后、Offer前,可以主动了解和确认的信息
【工作经验】会计师事务所工作中如何有效管理和规避法律风险
-
+
home page
📄 Android 实时屏幕共享技术方案文档
### 1. 需求概述 本文档旨在设计一个 Android 平台的一对多(One-to-Many)屏幕共享(同屏)系统。 - **推流端 (Broadcaster):** 1 个 Android 设备,实时采集屏幕内容,编码后推流至媒体服务器。 - **播放端 (Viewer):** 多个 Android/Web/PC 终端,从媒体服务器拉流并实时播放。 - **核心指标:** **低延迟 (Low Latency)**、**画面清晰 (High Quality)**、**实时性 (Real-time)**。 ### 2. 核心技术框架选型 对于“实时”和“低延迟”这一核心诉求,有两种主流的实现方案: #### 方案 A:WebRTC 方案 (强烈推荐) - **简介:** Web (Web Real-Time Communication) 是一个为浏览器和移动应用提供实时通信(RTC)能力的 API。它天生就是为低延迟音视频通话设计的。 - **架构:** 推流端 -> **SFU 媒体服务器** -> 播放端 - **关键组件:** - **推流端:** Android WebRTC 库 (`libwebrtc`) - **服务端:** SFU (Selective Forwarding Unit),例如 [Mediasoup](https://mediasoup.org/), [Janus](https://janus.conf.meetecho.com/), [LiveKit](https://livekit.io/) 等。SFU 负责接收推流端的流,并将其“转发”给所有订阅的播放端,效率极高。 - **播放端:** Android WebRTC 库 - **优点:** - **极低延迟:** 理想网络下可做到 **1 秒以内**(甚至 200-500ms)。 - **标准统一:** W3C 标准,Google 官方维护 `libwebrtc` for Android。 - **自带拥塞控制:** WebRTC 内置了强大的网络自适应算法 (GCC),能根据网络状况自动调整码率,优先保证低延迟。 - **缺点:** - **服务端复杂度:** SFU 的部署和运维比 RTMP 服务器稍复杂。 - **生态:** `ijkplayer` 或 `FFmpeg` 默认不直接支持 WebRTC(需要额外编译和集成)。 #### 方案 B:RTMP / RTSP + 流媒体播放器 (传统方案) - **简介:** 这是传统的直播方案。推流端使用 RTMP 协议推流,播放端使用 RTMP、HLS 或 FLV-HTTP 拉流。 - **架构:** 推流端 -> **RTMP 媒体服务器** -> 播放端 - **关键组件:** - **推流端:** 自行采集编码,使用 `librtmp` (C 库) 或 Java 封装库进行 RTMP 封包和推送。 - **服务端:** RTMP 服务器,例如 [SRS (Simple RTMP Server)](https://github.com/ossrs/srs) 或 [Nginx-RTMP](https://github.com/arut/nginx-rtmp-module)。 - **播放端:** `ijkplayer` / `FFmpeg` - **优点:** - **服务端成熟:** RTMP 服务器部署简单,资料多。 - **兼容性好:** `ijkplayer` 对 RTMP/FLV 支持良好。 - **缺点:** - **延迟较高:** RTMP 基于 TCP,且协议栈本身设计导致其延迟通常在 **1-3 秒**,很难做到 1 秒以下。 - **拥塞控制弱:** 需手动实现码率自适应,否则网络抖动时会立即造成累积延迟(卡顿)。 **结论:** - **若追求极致的“实时”和“低延迟”(如 < 1s),强烈推荐方案 A (WebRTC)。** - **若可接受 1-3s 延迟,且希望利用 `ijkplayer` 的成熟生态,可选方案 B (RTMP)。** ------ ### 3. 关键技术点及其原理 无论选择哪种方案,推流端的核心技术点是共通的: #### 3.1. 屏幕采集:`MediaProjection` API - **原理:** Android 5.0 (API 21) 之后,Google 提供了 `MediaProjection` API 作为官方的屏幕采集(录屏)方案。它允许应用获取一个 `MediaProjection` 令牌 (Token),该令牌可以用于访问屏幕内容。 - **实现:** 1. **请求权限:** ``` MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); startActivityForResult(manager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_CAPTURE); ``` 2. **获取令牌:** 在 `onActivityResult` 中获取 `MediaProjection` 实例: ``` @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == RESULT_OK) { MediaProjection mediaProjection = manager.getMediaProjection(resultCode, data); // ... 将 mediaProjection 传递给编码器 } } ``` #### 3.2. 视频编码:`MediaCodec` (硬编码) - **原理:** `MediaProjection` 捕获的是原始的屏幕图像 (Raw Frames),数据量极大(例如 1080p 60fps 约 3.6 Gbps),必须经过压缩编码(如 H.264)才能在网络上传输。 - **关键:硬编码 (Hardware Encoding)。** 必须使用 `MediaCodec` API 并指定使用硬件编码器。如果使用 FFmpeg 的 `libx264` 等软编码器,在手机上会迅速耗尽 CPU 和电量,导致严重发热和卡顿。 - **实现:`VirtualDisplay` + `MediaCodec`** 1. **配置 `MediaCodec`:** ```java MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaFormat.COLOR_FormatSurface); // 关键:指定颜色格式为 Surface format.setInteger(MediaFormat.KEY_BIT_RATE, 2 * 1024 * 1024); // 2 Mbps format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 1秒一个I帧 MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); ``` 2. **获取编码器的输入 Surface:** ``` Surface inputSurface = codec.createInputSurface(); // 这是一个神奇的 Surface codec.start(); ``` 3. **创建 `VirtualDisplay`:** - **原理:** 将 `MediaProjection` (屏幕内容) 和 `MediaCodec` (编码器) 关联起来。我们告诉 `MediaProjection`:“请把你的内容实时渲染到 `MediaCodec` 的 `inputSurface` 上”。 ``` VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay( "ScreenSharing", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, inputSurface, // 目标 Surface null, null ); ``` 4. **获取编码后的数据:** - `VirtualDisplay` 会自动将屏幕内容“绘制”到 `inputSurface`,`MediaCodec` 会自动在后台进行硬编码。 - 我们只需要在另一个线程循环从 `MediaCodec` 的输出缓冲区 (Output Buffer) 取 H.264 编码后的 NALU (Network Abstraction Layer Unit) 数据包。 ``` // 在一个单独的线程中循环 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); while (isStreaming) { int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex); // outputBuffer 中就是 H.264 码流 // 在这里将数据打包 (RTMP / WebRTC) 并发送出去 ... codec.releaseOutputBuffer(outputBufferIndex, false); } } ``` #### 3.3. 封包与推流 (Muxing & Streaming) - **方案 A (WebRTC):** `libwebrtc` 库已经封装好了 `ScreenCapturerAndroid`,它内部处理了 `MediaProjection` 和 `MediaCodec` 的集成。你只需要: 1. 创建 `PeerConnectionFactory`。 2. 使用 `ScreenCapturerAndroid` 创建 `VideoTrack`。 3. 将 `VideoTrack` 添加到 `PeerConnection`。 4. 创建 Offer SDP,并通过信令服务器发送给 SFU。 - **方案 B (RTMP):** 1. 从 `MediaCodec` 获取 H.264 NALU 数据 (如 3.2.4 所示)。 2. 你需要一个 **RTMP 封包器 (Muxer)**。H.264 裸流是不能直接发送的,必须打包成 RTMP Message (Header + Body)。 3. 使用 `librtmp` (通过 NDK) 或 Java RTMP 库(如 [rtmp-rtsp-stream-client-java](https://github.com/pedroSG94/rtmp-rtsp-stream-client-java))建立与 RTMP 服务器的连接,并将封包后的数据推送出去。 ------ ### 4. 播放器专项调优 (ijkplayer / FFmpeg) 这是你提问的重点。如果你选择了方案 B (RTMP),播放端的延迟主要来自**网络抖动**和**播放器缓冲区**。`ijkplayer` 底层就是 `FFmpeg` (`ffplay`),调优参数是相通的。 **调优的核心思想:** 牺牲一切稳定性(抗抖动能力)来换取最低延迟。 #### 4.1. 调优原理:延迟的来源 播放器的延迟 = **网络接收缓冲区 (Jitter Buffer)** + **解码缓冲区** + **渲染缓冲区**。 我们需要将所有缓冲区(Buffer)设置为**最小值**,甚至为 0。 #### 4.2. ijkplayer (IjkMediaPlayer) 调优参数 在创建 `IjkMediaPlayer` 对象后,在 `setDataSource()` 之前,使用 `setOption()` 设置参数: Java ``` IjkMediaPlayer player = new IjkMediaPlayer(); // --- 1. 关键调优:缓冲区设置 --- // 设置最大分析时长,单位是微秒 (microseconds)。 // FFmpeg 会先分析码流,设置小一点可以加快首屏速度。 player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1000000); // 1秒 // 设置探测大小,单位是字节。 // FFmpeg 会读取这么多的数据来分析码流格式。 player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024 * 10); // 10 KB // (最关键) 设置播放器最大缓存,单位是毫秒。 // 设为 0 或一个极小值,强制播放器不要缓存数据。 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 100); // 100ms // (最关键) 是否开启环路过滤 (Loop Filter)。 // 0 = 开启(默认),48 = 关闭。关闭可以节省 CPU,但可能导致画面质量下降(轻微马赛克)。 // 在低延迟场景下,解码速度优先。 player.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48); // --- 2. 协议相关调优 (以 RTMP 为例) --- // 设置为 "live" 模式 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "rtmp_live", "live"); // 设置 RTMP 缓冲区大小,单位毫秒。 // 默认 3000ms (3秒),这是 RTMP 延迟的主要来源! player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtmp_buffer_size", 100); // 100ms // --- 3. 其他调优 --- // (最关键) 设置为 0,关闭数据包缓冲。 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0); // (最关键) 关闭帧丢弃。 // 0 = 关闭。在实时场景中,我们宁愿看花屏也不要累积延迟。 // 如果 CPU 解码跟不上,这个值需要调大,但屏幕共享通常码率不高。 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 0); // 开启硬解码 (MediaCodec) player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1); player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 1); // --- 4. FFmpeg 标志 (fflags) --- // (最关键) "nobuffer" 标志 // 告诉 FFmpeg 立即处理数据,不要为了减少系统调用而缓冲数据。 player.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer"); // 设置音视频同步方式 // 0 = 同步到音频 (默认),1 = 同步到视频,255 = 同步到外部时钟 // 对于屏幕共享,视频是主体,可以尝试同步到视频。 player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "sync", "video"); // --- 真正设置数据源 (最后调用) --- player.setDataSource("rtmp://your-server.com/live/stream-key"); player.prepareAsync(); ``` #### 4.3. FFmpeg (ffplay 命令行) 调优 如果你是直接使用 `ffplay` 命令行测试,参数是等价的: Bash ``` ffplay -probesize 10240 \ -analyzeduration 1000000 \ -rtmp_buffer_size 100 \ -fflags nobuffer \ -framedrop 0 \ -sync video \ rtmp://your-server.com/live/stream-key ``` #### 4.4. 调优的代价 - **抗抖动性极差:** 上述参数将缓冲区设为了最低。这意味着网络稍微抖动(Jitter)或丢包(Packet Loss),播放器没有可回旋的余地,会**立即**出现卡顿、花屏或跳帧。 - **适用场景:** 这种极限调优**仅适用于内网(LAN)或网络质量极好(如 5G / Wi-Fi 6)** 的环境。在不稳定的 4G 或跨国公网环境下,体验会很差。 ------ ### 5. 推流端的配合调优 播放器的延迟再低,也受限于推流端。你必须同时优化推流端: 1. **GOP (Group of Pictures) 设置:** - **原理:** GOP 指的是一个 I 帧 (关键帧) 到下一个 I 帧的间隔。播放器必须在收到 I 帧后才能开始解码。 - **调优:** 将 `MediaCodec` 的 `KEY_I_FRAME_INTERVAL` 设置为一个较小的值,例如 `1` (1 秒)。 - `format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);` - **代价:** I 帧是完整的图像,压缩率低。更短的 I 帧间隔意味着**更高的带宽占用**。 2. **使用 Baseline Profile:** - **原理:** H.264 有多种 Profile (Baseline, Main, High)。Baseline Profile 功能最少,压缩率最低,但编码和解码的计算复杂度也最低,延迟最小。 - `format.setString(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline);` 3. **码率控制 (Bitrate Control):** - **原理:** 屏幕内容变化剧烈(如滚动网页、播放视频)时,码率会瞬时飙高。如果网络跟不上,数据会堆积在推流端的发送缓冲区,导致延迟。 - **调优:** - **CBR (Constant Bitrate):** 固定码率。简单,但浪费带宽或牺牲画质。 - **VBR (Variable Bitrate):** 可变码率。画质好,但码率波动大。 - **ABR (Adaptive Bitrate):** 码率自适应。**这是 WebRTC 的核心优势**。你需要实现一个拥塞控制算法,根据网络状况(RTT、丢包率)动态调整 `MediaCodec` 的编码码率。这是一个高级主题。 ### 6. 总结 1. **首选方案:** 使用 **WebRTC + SFU**。这是为实时通信设计的标准,`libwebrtc` 已为你处理了绝大部分的采集、编码、网络自适应和低延迟播放。 2. **次选方案:** 使用 **RTMP + 优化过的 ijkplayer**。可以实现 1-3 秒的延迟。 3. **调优核心:** `ijkplayer` 调优的关键是将所有缓冲区相关的参数(`analyzeduration`, `probesize`, `max_cached_duration`, `rtmp_buffer_size`, `packet-buffering`)设置为**极小值**,并使用 `fflags` 的 `nobuffer` 选项。 4. **配合:** 推流端必须配合使用**低 GOP**(如 1 秒)和 **Baseline Profile**。
嘿手大叔
Oct. 29, 2025, 2:46 p.m.
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
share
link
type
password
Update password