# 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-08-17 ## 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 / libyuv),摆脱对第三方商业 SDK 的依赖。 - **SIP 信令**:基于 eXosip2 实现设备注册、心跳保活、实时视频协商、语音对讲协商 - **视频链路**:Camera1 采集(NV21)→ libyuv 旋转 → 硬件 H.264 编码 → MPEG-2 PS 封装 → RTP 分片发送 - **音频链路**:AudioRecord 采集(PCM)→ G.711 μ/A 律编码 → PS 封装 → RTP 发送 - **对讲链路**:接收平台 G.711 数据 → 解码为 PCM → AudioTrack 播放 ## 功能特性 - 设备注册 / 注销(支持 Digest 认证) - 心跳保活 - 实时视频推流(H.264 + MPEG-2 PS + RTP,支持 TCP / UDP 传输) - 双向语音对讲(G.711 μ-law / A-law) - 视频画面旋转(libyuv) - 水印叠加(公司名称 / 时间 / 位置) - 下行音频落盘(PCM / RAW 文件保存) ## 工程结构 ``` GB28181Pusher/ ├── app/ # 示例应用(应用层) │ └── src/main/ │ ├── java/com/pengxh/pusher/ │ │ ├── PusherApplication.kt # 应用入口 │ │ ├── ui/ │ │ │ ├── PermissionActivity.kt # 权限申请 │ │ │ └── MainActivity.kt # 采集/编码/推流/对讲调度 │ │ ├── extensions/ # 预览尺寸选择、列表扩展 │ │ └── utils/NetworkHelper.kt # 本机 IP 获取 │ └── AndroidManifest.xml ├── library/ # 媒体核心库(SDK 模块) │ └── src/main/ │ ├── cpp/ # C++ 原生层 │ │ ├── eXosip2/ # SIP 信令库 │ │ ├── osip2/ # SIP 协议栈 │ │ ├── osipparser2/ # SIP 消息解析 │ │ ├── src/ │ │ │ ├── sip/ # 注册/心跳/SDP/流管理 │ │ │ ├── stream/ # PS 封装/头构建/RTP 发送 │ │ │ ├── audio/ # G.711 编解码/环形缓冲 │ │ │ └── video/ # H.264 分片 │ │ ├── GB28181Pusher.cpp # JNI 入口 │ │ └── CMakeLists.txt │ ├── java/com/pengxh/media/ # Kotlin API 层 │ │ ├── GB28181Pusher.kt # 对外 API │ │ └── utils/ # 编码器/水印/参数/回调 │ └── jniLibs/ # 预编译 .so(4 架构) ├── share/ # 技术文档与架构图 ├── build_*.sh # 依赖库交叉编译脚本 └── sip_command.md # SIP 信令示例 ``` ## 环境要求 | 项 | 要求 | |------------------------|----------------------------------------| | Android Gradle Plugin | 8.11.1 | | Kotlin | 2.3.20 | | Gradle | 8.13 | | NDK | 21.4.7075529 | | CMake | 3.22.1 | | minSdk | 26(Android 8.0) | | targetSdk / compileSdk | 36 | | 支持架构 | armeabi-v7a / arm64-v8a / x86 / x86_64 | ## 快速开始 ### 1. 克隆并构建 ```bash git clone <仓库地址> cd GB28181Pusher ./gradlew assembleDebug ``` ### 2. 安装运行 构建产物位于 `app/build/outputs/apk/`,安装到 Android 8.0 及以上设备后: 1. 首次启动会申请相机、麦克风权限 2. 点击「流媒体配置」,填写 SIP 平台参数: - 服务器 IP / 端口 - 服务器账号(SIP 服务器 ID) - 域 ID(服务器域) - 设备 ID(国标设备编码) - 设备名称、密码、经纬度 3. 点击「流媒体注册」完成设备注册 4. 平台侧下发拉流 / 对讲信令后,自动开始推流 / 对讲 ### 3. SIP 参数说明 注册参数由 `SipParameter` 描述,字段含义如下: | 字段 | 说明 | |--------------------------|----------------------| | `localHost` | 本地 IP(程序自动获取,无需手动填写) | | `serverHost` | SIP 服务器 IP | | `serverPort` | SIP 服务器端口 | | `serverCode` | SIP 服务器账号 | | `serverDomain` | SIP 服务器域 | | `deviceCode` | 设备国标编码(20 位) | | `serialNumber` | 设备序列号(自动生成) | | `deviceName` | 设备名称 | | `password` | 认证密码 | | `longitude` / `latitude` | 经纬度 | ## 接口说明 核心 API 封装在 `com.pengxh.media.GB28181Pusher` 单例对象中: | 方法 | 说明 | |---------------------------------|-----------------------| | `register(parameter, callback)` | 注册到国标平台 | | `unregister()` | 注销 | | `rotateYuv(...)` | 旋转 YUV(NV21) | | `addOverlay(...)` | 叠加水印 | | `pushVideoFrame(h264, ptsUs)` | 推送 H.264 视频帧(Annex B) | | `pushAudioFrame(pcm, ptsUs)` | 推送 PCM 音频帧 | | `releaseMuxer()` | 释放 PS 封装器 | | `shutdown()` | 销毁并回收所有资源 | 事件通过 `SipEventCallback` 回调(含 SIP 事件码、下行 PCM / G.711 音频数据)。 --- # Android 平台(Java/Kotlin/C++)GB/T 28181-2016 推流与语音对讲技术详解 ## 推流流程 ### 1. 初始化 SIP 参数并注册到国标平台 - **Java 层**:初始化 SIP 核心参数(设备 ID、服务器地址、端口等),通过 JNI 传递至 Native 层 - **Native 层**:基于 eXosip2 库向 GB/T 28181 国标平台发起注册请求 - **时间戳基准**:注册成功后记录程序启动的微秒级时间戳(`System.nanoTime() / 1000`),作为后续音视频帧时间戳的全局基准 ### 2. 视频采集 - 采用 Camera1 API 采集视频画面 - 输出 YUV420 (NV21) 格式的原始视频数据 ### 3. 音频采集 - 使用 `AudioRecord` 采集原始 PCM 裸流(标准配置:16-bit、单声道、8kHz) - 记录每帧音频数据的时间戳(微秒级),相对于步骤 1 中的起始时间基准 ### 4. 拉流信令触发视频编码 **默认状态**:客户端仅进行本地预览,不执行编码和推流操作 **触发机制**:当国标平台下发拉流信令(INVITE 请求)后,启动编码推流流程: 1. **格式转换**:将 NV21 转换为 NV12(适配硬件编码器输入要求) 2. **H.264 编码**:通过硬件编码器生成完整的 H.264 帧数据 - ⚠️ **关键要求**:必须包含起始码(Start Code: `00 00 00 01` 或 `00 00 01`) - ⚠️ 若 H.264 帧缺少起始码,PS 封装将失败或平台无法解析,导致推流无画面! 3. **时间戳记录**:记录每帧编码完成时的时间戳(微秒级),相对于全局起始时间 ### 5. 视频帧封装为 MPEG-2 PS 流(高复杂度环节) #### 5.1 时间戳转换 通过 JNI 将 H.264 帧数据传递至 Native 层,执行时间戳基准转换: ```objectivec uint64_t pts_90kHz = static_cast(pts_us / 1000) * 90 ``` > ⚠️ **强制要求**:GB/T 28181-2016 规定必须使用 90KHz 时间基准,否则收流端无法解码导致无画面! #### 5.2 NALU 拆分与关键帧处理 - 将整帧 H.264 数据按起始码拆分为 NALU(网络应用层单元,是 H.264 数据的基本单元) - 提取 SPS/PPS 帧数据并缓存 - 筛选 IDR 帧(关键帧) #### 5.3 IDR 帧封装流程 组帧顺序:`[起始码] + [SPS] + [PPS] + [IDR 帧]` → 封装为 PES 包 > ⚠️ **传输顺序强制要求**:IDR 帧必须先于任何其他帧发送到平台,因为 SPS/PPS > 携带视频帧的基本解码信息,平台缺少这些信息将无法解析画面。 #### 5.4 非 IDR 帧封装流程 组帧顺序:`[起始码] + [P 帧]` → 封装为 PES 包 #### 5.5 MPEG-2 PS 流最终封装 以 PES 包为载荷,按照以下规则封装为 MPEG-2 PS 流: - **IDR 帧**:添加系统头(System Header)和 PSM(Program Stream Map) - **非 IDR 帧**:直接封装 > ⚠️ **字节级精度要求**:系统头和 PSM 的封装涉及复杂的字节和 Bit 位操作,每个字节的含义必须严格符合 > MPEG-2 PS 规范,任何偏差都会导致封装失败! ### 6. 音频帧编码与 MPEG-2 PS 流封装 #### 6.1 时间戳转换 通过 JNI 将 PCM 裸流传递至 Native 层: ```objectivec uint64_t pts_90kHz = static_cast(pts_us / 1000) * 90 ``` > ⚠️ **同步关键**:90KHz 时间基准是保证音画同步的核心要求! #### 6.2 音频编码 在 Native 层将 PCM 编码为: - G.711 μ-law(μ律) - G.711 A-law(A 律) #### 6.3 PES 封装 将整帧 G.711 数据直接封装为 PES 包(音频封装相对简单,无需复杂处理) #### 6.4 PS 流封装 以 PES 包为载荷,封装为 MPEG-2 PS 流 > ⚠️ **重要区分**:音频帧均为非 IDR 帧,**无需添加**系统头、PSM 和起始码! ### 7. PES 包 RTP 分片处理 #### 7.1 MTU 限制适配 由于 RTP MTU 限制(约 1400 字节),需对较大的 PES 包进行分片处理 #### 7.2 分片策略 判断每个 PES 包大小并执行相应策略: - **≤ 1300 字节**:直接作为单个 RTP 包发送 - **> 1300 字节**:按 RFC 3984 或 GB/T 28181-2016 规范进行 RTP 分片封装 #### 7.3 封装传输 将分片后的 PES 包封装为 RTP 包,发送至国标平台 > ⚠️ **协议一致性要求**:必须严格按照 SDP 消息中协商的传输方式发送,否则平台会丢弃接收到的数据包! ### 8. 网络传输 #### 8.1 传输模式 将封装好的 RTP 包(单包或分片)通过以下方式发送至国标平台: - **TCP 模式** - 优势:连接可靠、抗丢包 - 适用场景:网络环境不稳定 - **UDP 模式** - 优势:延迟更低 - 适用场景:实时性要求高的场景 #### 8.2 协议协商 传输协议类型由平台信令协商确定,客户端动态适配 --- ## 语音对讲流程 ### 1. 接收平台 Notify 语音对讲信令 - **信令监听**:监听平台下发的 Notify 语音对讲信令 - **参数解析**:根据 `CmdType` 为 `Broadcast` 类型的信令解析出 `SourceID`、`TargetID` - **确认响应**:回复平台 ACK 200 确认信令 ### 2. 初始化本地 TCP 客户端 - **TCP 初始化**:初始化 TCP 客户端,获取本地 TCP 端口 - **INVITE 发送**:发送 INVITE 请求到平台 > ⚠️ **TCP 选择原因**:设备和平台通常不在同一网段(设备位于局域网/内网,平台位于公网)。TCP > 可建立双向连接,平台可通过该连接链路将对讲数据回传;UDP 模式需自行实现 NAT 穿透或打洞,复杂度较高。 ### 3. 等待平台回复 SDP 并建立连接 #### 3.1 SDP 解析 - 解析平台回复的 SDP 消息,获取: - 平台 TCP IP 地址和端口 - 音频解码类型(G.711 μ/A 律) - 回复 ACK 200 确认 #### 3.2 TCP 连接建立 连接平台指定的 TCP 地址和端口,开始接收平台数据 > ⚠️ **高性能接收要求**:由于数据接收频率较高,必须采用【独立线程 + 环形缓冲区】架构,否则性能将成为系统瓶颈! #### 3.3 数据解码与播放 接收到的数据为 G.711 μ/A 音频流,Android 端无法直接播放,需解码为 PCM 裸流: **实现方案选择**: - **方案 A**:通过 JNI 将 G.711 μ/A 数据回传至 Java/Kotlin 层,在 Java 层解码后播放 - **方案 B**:在 C++ 层解码为 PCM,将 PCM 音频数据回传至 Java/Kotlin 层播放 > ⚠️ **数据类型说明**: - G.711 μ/A 音频数据回调类型:ByteArray - PCM 裸流回调类型:ShortArray