# GB28181Pusher **Repository Path**: AndroidCoderPeng/GB28181Pusher ## Basic Information - **Project Name**: GB28181Pusher - **Description**: 一个专注于实现GB28181标准的视频流推送功能的SDK项目,支持多种设备和平台,适用于视频监控和传输场景。 - **Primary Language**: Kotlin - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-24 - **Last Updated**: 2026-09-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GB28181Pusher 基于 Android(Java / Kotlin / C++)的 GB/T 28181-2016 国标推流与语音对讲 SDK, 支持设备注册、目录/状态查询响应、实时视频推流(H.264 + PS + RTP)与双向语音对讲(G.711 A/μ-law)。 ## 目录 - [项目简介](#项目简介) - [功能特性](#功能特性) - [工程结构](#工程结构) - [环境要求](#环境要求) - [快速开始](#快速开始) - [接口说明](#接口说明) - [事件码说明](#事件码说明) - [推流流程](#推流流程) - [语音对讲流程](#语音对讲流程) - [第三方依赖编译](#第三方依赖编译) ## 项目简介 本项目实现了一套自主可控的 GB/T 28181 推流与语音对讲方案,核心链路(协议栈 + 媒体封装)自研, 仅依赖开源基础库(osip2 / eXosip2 / pugixml),摆脱对第三方商业 SDK 的依赖。 工程由三个 Gradle 模块组成,职责清晰、可独立复用: | 模块 | 语言 | 职责 | |-----------|--------------|----------------------------------------------------------------------------| | `app` | Kotlin | 示例应用:权限申请、参数配置、采集/编码/推流/对讲的业务调度 | | `encoder` | Java | 采集与编码库:Camera2 + OpenGL ES 渲染 + MediaCodec 硬编 H.264;AudioRecord + G.711 编码 | | `pusher` | Kotlin + C++ | 国标协议库:SIP 信令(eXosip2)、PS 封装、RTP 收发、G.711 编解码 | 核心链路: - **SIP 信令**:基于 eXosip2 实现设备注册(Digest 认证)、心跳保活、实时视频协商、语音对讲协商、目录/状态查询响应 - **视频链路**:Camera2 采集 → OpenGL ES 渲染(旋转 / 水印)→ MediaCodec 硬件 H.264 编码 → MPEG-2 PS 封装 → RTP 分片发送 - **音频链路**:AudioRecord 采集 PCM → G.711 μ/A 律编码 → PS 封装 → RTP 发送 - **对讲链路**:接收平台 G.711 数据 → 环形缓冲 → 解码为 PCM → AudioTrack 播放 ## 功能特性 - 设备注册 / 注销(支持 Digest 认证,注册有效期 7200s,心跳间隔 30s) - 心跳保活(`MANSCDP+xml` 心跳报文) - 实时视频推流(H.264 + MPEG-2 PS + RTP,支持 TCP / UDP,按 SDP 协商结果动态适配) - 双向语音对讲(G.711 μ-law / A-law) - H.264 格式自动适配:自动探测 AnnexB / AVCC,AVCC 自动转换为 AnnexB - 零拷贝视频帧投递(`ByteBuffer` 直传 MediaCodec 输出缓冲) - 目录查询(Catalog)、设备信息(DeviceInfo)、设备状态(DeviceStatus)查询响应 - OpenGL ES 水印叠加(公司名称 / 位置 / 时间,支持运行时开关) - 下行音频落盘(`.pcm` 解码后数据 / `.raw` 原始 G.711 裸流) ## 工程结构 ``` GB28181Pusher/ ├── app/ # 示例应用(Kotlin) │ └── src/main/ │ ├── java/com/pengxh/pusher/ │ │ ├── PusherApplication.kt # 应用入口,初始化 KV 存储 │ │ ├── ui/ │ │ │ ├── PermissionActivity.kt # 启动页 + 动态权限申请 │ │ │ ├── MainActivity.kt # 相机/编码/推流/对讲调度 │ │ │ └── ParameterConfigDialog.kt # SIP 参数配置弹窗(自动填充设备序列号) │ │ ├── extensions/ # Channel 扩展 │ │ └── utils/NetworkHelper.kt # 本机 IPv4 地址获取 │ └── AndroidManifest.xml # 权限声明、入口 Activity │ ├── encoder/ # 采集与编码库(Java) │ └── src/main/java/com/pengxh/encoder/ │ ├── video/ │ │ ├── VideoEncoder.java # Camera2 + GL + MediaCodec 硬编 │ │ ├── VideoEncoderConfig.java # 编码参数(Builder,默认 720x1280@30/2Mbps/1s) │ │ ├── SizeSelector.java # 预览与编码尺寸选择 │ │ ├── H264DataCallback.java # H.264 零拷贝回调 │ │ ├── EncoderState.java # 编码器状态枚举 │ │ └── gl/ # EGL 渲染管线 │ │ ├── EglCore.java / EglRenderLayer.java / FrameEglRenderer.java │ │ ├── ShaderProgram.java / PassThroughFilter.java │ │ └── WatermarkFilter.java / WatermarkConfig.java / WatermarkController.java │ └── audio/ │ ├── AudioEncoder.java # AudioRecord 采集 + G.711 编码 │ ├── AudioEncoderConfig.java # 默认 8kHz / 单声道 / 16bit / 20ms 一帧 │ ├── AudioCodecType.java # G711A / G711U │ └── AudioDataCallback.java │ ├── pusher/ # 国标信令与媒体封装库(Kotlin + C++) │ └── src/main/ │ ├── java/com/pengxh/media/ │ │ ├── GB28181Pusher.kt # 对外 JNI API(单例 object) │ │ ├── SipParameter.kt # 注册参数 │ │ └── SipEventCallback.kt # 事件回调接口 │ ├── cpp/ │ │ ├── CMakeLists.txt # 唯一构建脚本,产物 libGB28181Pusher.so │ │ ├── GB28181Pusher.cpp # JNI 入口 │ │ ├── java_callback.cpp / .hpp # Java 回调桥 │ │ ├── eXosip2/ osip2/ osipparser2/ # 第三方头文件 │ │ └── src/ │ │ ├── global_definition.hpp # 全局常量与结构体 │ │ ├── logger.cpp / .hpp # 日志工具 │ │ ├── pugixml.cpp / .hpp # XML 解析(MANSCDP 消息) │ │ ├── rtp_sender.cpp / .hpp # RTP 封包与发送 │ │ ├── video/h264_splitter.* # 格式探测 / AVCC→AnnexB / NALU 拆分 │ │ ├── audio/ # G.711 编解码 / 音频接收 / 环形缓冲 │ │ ├── muxer/ # PS 封装 / 系统头·PSM 构建 │ │ └── sip/ │ │ ├── sip_manager.cpp # 总控 │ │ ├── register_manager.cpp # 注册状态机 │ │ ├── heartbeat_manager.cpp # 心跳线程 │ │ ├── stream_manager.cpp # 推流与对讲会话管理 │ │ ├── event_dispatcher.cpp # eXosip 事件分发 │ │ ├── sip_context.cpp # eXosip 上下文(TCP 5060 监听) │ │ ├── utils/ # 响应发送 / SDP 解析 / 状态码 / 参数解析 / XML 构建 / 工作线程 │ │ └── handlers/ # 注册 / 呼叫 / 消息 / 默认 事件处理器 │ └── jniLibs// # 预编译 so(osip2 / eXosip2 / osipparser2 / libc++_shared) │ ├── share/ # 架构图与技术详解文档 ├── build_osip2.sh / build_exosip2.sh # 第三方依赖交叉编译脚本 ├── build_libyuv.sh / build_x264.sh └── sip_command.md # SIP 信令交互示例 ``` ## 环境要求 | 项 | 要求 | |------------------------|----------------------------------------| | Gradle | 8.13 | | Android Gradle Plugin | 8.11.1 | | Kotlin | 2.3.20 | | JDK | 11 | | NDK | 21.4.7075529 | | CMake | 3.22.1 | | C++ 标准 | C++14 | | minSdk | 26(Android 8.0) | | targetSdk / compileSdk | 36 | | 支持架构 | armeabi-v7a / arm64-v8a / x86 / x86_64 | 应用模块主要依赖: | 依赖 | 版本 | 用途 | |-----------------------------------------------|--------|--------------------------| | `com.github.AndroidCoderPeng:Kotlin-lite-lib` | 2.0.0 | 基础 Activity / KV 存储 / 扩展 | | `androidx.core:core-ktx` | 1.17.0 | — | | `androidx.appcompat:appcompat` | 1.7.1 | — | | `com.google.android.material:material` | 1.13.0 | Material 组件 | | `pub.devrel:easypermissions` | 3.0.0 | 动态权限申请 | | `com.google.code.gson:gson` | 2.14.0 | SIP 参数本地持久化 | ## 快速开始 ### 1. 克隆并构建 ```bash git clone <仓库地址> cd GB28181Pusher ./gradlew assembleDebug ``` Release 包输出名为 `GB28181Pusher__.apk`,位于 `app/build/outputs/apk/release/`。 ### 2. 安装运行 安装到 Android 8.0 及以上设备后: 1. 首次启动 `PermissionActivity` 会申请相机、麦克风权限(Android 10 以下额外申请存储权限) 2. 进入主界面后即可看到相机预览;点击「国标参数配置」,填写平台参数: - 服务器 IP / 端口 - 服务器账号(SIP 服务器 ID) - 域 ID(服务器域) - 设备 ID(国标设备编码) - 设备名称、密码、经度、纬度 3. 点击「国标注册」完成设备注册(未配置参数时会先弹出配置框,配置完成后自动注册) 4. 平台侧下发拉流 / 对讲信令后,自动开始推流 / 对讲 5. 「添加水印」开关可实时启停 OpenGL 水印 > 注意:已注册状态下不允许修改配置,需先点击「注销流媒体」。 ### 3. SIP 参数说明 注册参数由 `SipParameter` 描述(`com.pengxh.media.SipParameter`): | 字段 | 说明 | |--------------------------|------------------------------------| | `localHost` | 本地 IP(由 `NetworkHelper` 自动获取,无需填写) | | `serverHost` | SIP 服务器 IP | | `serverPort` | SIP 服务器端口 | | `serverCode` | SIP 服务器账号 | | `serverDomain` | SIP 服务器域 | | `deviceCode` | 设备国标编码(20 位) | | `serialNumber` | 设备序列号(由硬件信息 MD5 自动生成) | | `deviceName` | 设备名称 | | `password` | 认证密码 | | `longitude` / `latitude` | 经度 / 纬度 | 参数经 Gson 序列化后保存在本地 KV(key = `sip_parameter`),下次启动自动回显。 ## 接口说明 ### 国标库(`com.pengxh.media.GB28181Pusher`) | 方法 | 说明 | |---------------------------------------------|-------------------------------------------------------| | `register(parameter, callback)` | 注册到国标平台(若已注册,内部先注销再注册) | | `unregister()` | 注销 | | `pushVideoFrameBuffer(buffer, size, ptsUs)` | 推送 H.264 帧,**推荐**:直传 MediaCodec 输出 `ByteBuffer`,少一次拷贝 | | `pushVideoFrameBytes(bytes, ptsUs)` | 推送 H.264 帧(`ByteArray`,**已废弃**,JNI 需整体拷贝) | | `pushAudioOriginalFrame(pcm, ptsUs)` | 推送原始 PCM(`ShortArray`),Native 侧编码为 G.711μ | | `pushAudioEncodedFrame(data, ptsUs)` | 推送已编码的 G.711 数据(`ByteArray`),直接封装 | | `releaseMuxer()` | 释放 PS 封装器(停止推流时调用) | | `shutdown()` | 销毁并回收所有资源(App 销毁时调用) | > `.so` 加载顺序不可调整:`osipparser2` → `osip2` → `eXosip2` → `GB28181Pusher`。 ### 事件回调(`com.pengxh.media.SipEventCallback`) ```kotlin interface SipEventCallback { fun onEventReceived(code: Int, content: String?) // SIP / 媒体事件 fun onAudioReceived(pcm: ShortArray) // 下行音频(已解码为 PCM,可直接 AudioTrack 播放) fun onRawAudioReceived(g711: ByteArray, type: Int) // 下行音频原始裸流,type: 0=PCMU, 8=PCMA } ``` > 两个音频回调均在 Native 接收线程触发,**必须先拷贝数据再投递到业务线程**,否则后续帧会覆盖缓冲区。 ### 视频编码(`com.pengxh.encoder.video`) ```kotlin val config = VideoEncoderConfig.Builder() .setCameraFacing(CameraCharacteristics.LENS_FACING_BACK) .setWidth(720).setHeight(1280) .setFrameRate(25).setBitrate(2_000_000).setIFrameInterval(1) .build() val watermark = WatermarkFilter().apply { setCompany("公司名称"); setLocation("位置信息"); setShowTime(true) } val encoder = VideoEncoder(activity, config, textureView, watermark, object : H264DataCallback { override fun onFrameEncoded(buffer: ByteBuffer, size: Int, ptsUs: Long, isKeyFrame: Boolean) { GB28181Pusher.pushVideoFrameBuffer(buffer, size, ptsUs) } override fun onEncoderError(e: Exception) = e.printStackTrace() }) encoder.start() ``` 主要方法: | 方法 | 说明 | |-----------------------------|--------------------------| | `start()` | 打开相机、建立预览会话(默认只预览不编码) | | `setEncodingEnable(enable)` | 开关编码推流(收到 2100/2101 时调用) | | `getWatermarkController()` | 获取水印控制器,用于运行时开关水印 | | `getEncoderState()` | 查询当前编码器状态 | | `release()` | 释放相机、编码器与 GL 资源 | ### 音频编码(`com.pengxh.encoder.audio`) ```kotlin val config = AudioEncoderConfig.Builder().setCodecType(AudioCodecType.G711U).build() val encoder = AudioEncoder(activity, config, object : AudioDataCallback { override fun onFrameEncoded(g711: ByteArray, ptsUs: Long) { GB28181Pusher.pushAudioEncodedFrame(g711, ptsUs) } override fun onError(e: Exception) = e.printStackTrace() }) encoder.start() // 收到 2100 时启动;收到 2101 时 stop() ``` 默认参数:8kHz / 单声道 / 16bit / 每帧 160 样本(20ms),与 Native 侧 `G711_FRAME_SIZE = 160` 对齐。 ## 事件码说明 `onEventReceived` 回传的事件码(完整码表见 `pusher/src/main/cpp/src/sip/utils/state_code.cpp`): | 码值 | 含义 | 示例应用处理 | |--------|-------------|-----------------------------------| | `1000` | 注册成功 | 按钮切换为「注销流媒体」 | | `201` | 注销成功 | 停止编码/录音/对讲,`releaseMuxer()` | | `2100` | 开始推流 | `setEncodingEnable(true)` + 启动录音 | | `2101` | 停止推流 | `setEncodingEnable(false)` + 停止录音 | | `2107` | 收到 BYE,停止推流 | 同 `2101` | | `2200` | 开始接收音频 | 初始化 AudioTrack 并启动播放协程 | | `2201` | 停止接收音频 | 停止播放并落盘 | | `408` | 请求超时 | 视为注册失败,复位 UI | | `2001` | 发送注册请求失败 | 同上 | | `2003` | 发送认证注册失败 | 同上 | | `2007` | 注册已过期 | 同上 | 其余区间: - `1xx`–`6xx`:SIP 标准响应码 - `1000`–`1099`:eXosip2 事件类型 - `1100`–`1199`:osip2 返回码 - `2000`–`2099`:注册相关错误 - `2100`–`2199`:媒体流相关 - `2200`–`2299`:语音对讲相关 - `2300`–`2399`:消息处理相关 - `2400`–`2499`:设备控制相关 - `3000`–`3099`:系统 / 网络错误 --- # Android 平台 GB/T 28181-2016 推流与语音对讲技术详解 ## 推流流程 ### 1. 初始化 SIP 参数并注册到国标平台 - **Kotlin 层**:组装 `SipParameter`(设备 ID、服务器地址、端口、经纬度等),通过 JNI 传递至 Native 层 - **Native 层**:`SipContext` 初始化 eXosip 上下文并以 **TCP 5060** 监听;`RegisterManager` 基于状态机( `IDLE → SENT_INITIAL → SENT_AUTH`)发起注册,支持 Digest 认证 - **注册成功**:启动 `HeartbeatManager`(30s 间隔)并回调 `1000` - **时间戳基准**:音视频帧的时间戳(微秒)由采集侧产生,Native 侧统一转换为 90kHz ### 2. 视频采集与预处理 - 采用 **Camera2** API 采集,输出送入 OpenGL ES 渲染管线(`EglCore` / `EglRenderLayer` / `FrameEglRenderer`) - 渲染管线完成**画面旋转、缩放与水印叠加**,再通过 `MediaCodec` 输入 Surface 实现硬件编码 - 编码输出经 `H264DataCallback` 以 `ByteBuffer` + `size` 零拷贝回调给业务层 > ⚠️ 回调返回的 `ByteBuffer` 与 MediaCodec 输出缓冲区共享内存,**必须在回调返回前完成数据投递**。 ### 3. 音频采集与编码 - 使用 `AudioRecord` 采集原始 PCM(8kHz / 单声道 / 16bit) - 每帧 160 样本(20ms),由 `AudioEncoder` 编码为 G.711 μ-law 或 A-law - 记录每帧时间戳(微秒),与视频帧共用同一时间基准 ### 4. 拉流信令触发视频编码 **默认状态**:客户端仅进行本地预览,不执行编码和推流操作 **触发机制**:当国标平台下发拉流信令(INVITE + SDP)后,`StreamManager` 完成 SDP 协商并回调 `2100` ,此时启动编码推流: 1. **SDP 协商**:解析平台下发的 SDP,获取收流 IP / 端口、传输协议(TCP / UDP)、媒体类型 2. **H.264 编码**:`MediaCodec` 输出完整帧数据,携带 `ptsUs` 与关键帧标记 3. **时间戳记录**:记录每帧编码完成时的时间戳(微秒) ### 5. 视频帧封装为 MPEG-2 PS 流(高复杂度环节) #### 5.1 格式探测与归一化 Native 侧先探测 H.264 数据格式: - **AnnexB**(起始码 `00 00 00 01` / `00 00 01`):直接使用 - **AVCC**(长度前缀):调用 `h264_splitter::avcc_to_annex_b()` 转为 AnnexB - 其他:丢弃该帧 > ⚠️ 因此业务层**无需关心**编码器输出的是 AnnexB 还是 AVCC,Native 侧已统一处理。 #### 5.2 时间戳转换 ```cpp uint64_t pts_90kHz = static_cast(pts_us / 1000) * 90; ``` > ⚠️ **强制要求**:GB/T 28181-2016 规定必须使用 90kHz 时间基准,否则收流端无法解码导致无画面。 > 先除法再乘法,避免大时间戳溢出。 #### 5.3 NALU 拆分与关键帧处理 - 按起始码将整帧拆分为 NALU - 提取并缓存 SPS / PPS - 识别 IDR 帧(NALU type = 5) #### 5.4 IDR 帧封装流程 组帧顺序:`[起始码] + [SPS] + [PPS] + [IDR 帧]` → 封装为 PES 包 > ⚠️ **传输顺序强制要求**:IDR 帧必须先于任何其他帧发送,SPS/PPS 缺失会导致平台无法解析画面。 #### 5.5 非 IDR 帧封装流程 组帧顺序:`[起始码] + [P 帧]` → 封装为 PES 包 #### 5.6 MPEG-2 PS 流最终封装 以 PES 包为载荷封装为 MPEG-2 PS 流: - **IDR 帧**:额外添加系统头(System Header)和 PSM(Program Stream Map) - **非 IDR 帧**:直接封装 视频 `Stream Type` 为 `0x1B`(H.264)。 > ⚠️ **字节级精度要求**:系统头与 PSM 的封装涉及复杂的字节与 Bit 位操作,每个字节的含义必须严格符合 > MPEG-2 PS 规范。 ### 6. 音频帧编码与 MPEG-2 PS 流封装 #### 6.1 两种投递方式 | 方式 | 调用 | 编码位置 | |--------|--------------------------------------|-------------------------------------------| | 已编码 | `pushAudioEncodedFrame(g711, ptsUs)` | Java 层 `AudioEncoder` | | 原始 PCM | `pushAudioOriginalFrame(pcm, ptsUs)` | Native 层 `audio_processor::pcm_to_ulaw()` | #### 6.2 时间戳转换 ```cpp uint64_t pts_90kHz = static_cast(pts_us / 1000) * 90; ``` > ⚠️ **同步关键**:90kHz 时间基准是保证音画同步的核心要求。 #### 6.3 PES 与 PS 封装 整帧 G.711 数据直接封装为 PES 包,再封装为 PS 流。 > ⚠️ **重要区分**:音频帧均为非 IDR 帧,**无需添加**系统头、PSM 和起始码。 ### 7. PES 包 RTP 分片处理 #### 7.1 MTU 限制适配 受 RTP MTU 限制(约 1400 字节),需对较大的 PES 包进行分片。 #### 7.2 分片策略 - **≤ 1300 字节**:直接作为单个 RTP 包发送 - **> 1300 字节**:按 RFC 3984 / GB/T 28181-2016 规范分片封装 #### 7.3 封装传输 > ⚠️ **协议一致性要求**:必须严格按照 SDP 协商的传输方式与端口发送,否则平台会丢弃数据包。 ### 8. 网络传输 #### 8.1 传输模式 - **TCP 模式**:连接可靠、抗丢包,适合网络环境不稳定场景 - **UDP 模式**:延迟更低,适合实时性要求高的场景 #### 8.2 协议协商 传输协议由平台 SDP 协商确定,客户端通过 `StreamManager` 动态适配,无需业务层干预。 --- ## 语音对讲流程 ### 1. 接收平台 Notify 语音对讲信令 - **信令监听**:`MessageHandler` 监听平台下发的 Notify 消息 - **参数解析**:识别 `CmdType` 为 `Broadcast` 的信令,解析出 `SourceID`、`TargetID` - **确认响应**:回复平台 200 OK ### 2. 初始化本地 TCP 客户端并发送 INVITE - **TCP 初始化**:`AudioReceiver` 初始化本地 TCP 监听,获取本地端口 - **SDP 构造**:按编码类型构造下行 SDP(`PCMA/8000` 或 `PCMU/8000`,`setup:active`) - **INVITE 发送**:携带 SDP 向平台发起 INVITE > ⚠️ **TCP 选择原因**:设备通常位于内网、平台位于公网,TCP 可建立双向连接,平台可通过该链路回传对讲数据; > UDP 模式需自行实现 NAT 穿透或打洞,复杂度较高。 ### 3. 等待平台回复 SDP 并建立连接 - 解析平台回复的 SDP,获取平台 TCP IP 与端口,回复 ACK - 连接平台指定地址,开始接收音频数据 > ⚠️ **高性能接收要求**:采用【独立接收线程 + 256KB 环形缓冲区】架构,避免高频数据接收成为系统瓶颈。 ### 4. 数据解码与播放 接收到的 G.711 数据 Android 端无法直接播放,Native 侧解码为 PCM 后回调: | 回调 | 数据类型 | 说明 | |----------------------------------|--------------|-------------------------------------| | `onAudioReceived(pcm)` | `ShortArray` | 已解码 PCM,可直接 `AudioTrack.write()` 播放 | | `onRawAudioReceived(g711, type)` | `ByteArray` | 原始裸流,`type`:0 = PCMU,8 = PCMA,需自行解码 | 示例应用的播放链路: ``` onAudioReceived → Channel(50) → IO 协程 → AudioTrack(8kHz/MONO/16bit, MODE_STREAM) ↘ 追加写入 AUD_<时间戳>.pcm onRawAudioReceived → Channel(50) → IO 协程 → 追加写入 AUD_<时间戳>.raw ``` 落盘文件位于 `getExternalFilesDir(Environment.DIRECTORY_MUSIC)` 目录,便于抓包排查音频问题。 > ⚠️ **回调线程注意**:音频回调在 Native 接收线程触发,业务层必须**先 `copyOf()` 再投递**,否则数据会被后续帧覆盖。 --- ## 第三方依赖编译 `pusher` 模块通过 `jniLibs` 引入预编译的 `libosipparser2.so`、`libosip2.so`、`libeXosip2.so`(三者均基于 Android 8 / API 26 编译)。 如需重新编译,可使用仓库根目录的交叉编译脚本: ```bash ./build_osip2.sh # osip2 + osipparser2 ./build_exosip2.sh # eXosip2(依赖 osip2) ./build_libyuv.sh # libyuv ./build_x264.sh # x264 ``` 产物需按 ABI 放入 `pusher/src/main/jniLibs//`。 > 说明:当前视频链路已改为 Camera2 + OpenGL ES + MediaCodec 硬编, > `libyuv` 与 `x264` 不再是主链路的必需依赖,脚本保留仅供扩展使用。 ## 参考文档 - `sip_command.md`:SIP 信令交互示例(注册、INVITE、Notify、BYE 等) - `share/Android 端GB28181推流与语音对讲技术详解.md`:完整技术详解 - `share/`:整体架构图、流程图、通信模型图、流数据结构图、流程时序图