# GB28181 **Repository Path**: AndroidCoderPeng/GB28181 ## Basic Information - **Project Name**: GB28181 - **Description**: Android平台(Java/Kotlin/C++)GB/T 28181-2016推流以及语音对讲 - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 2 - **Created**: 2025-08-26 - **Last Updated**: 2026-03-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 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