Skip to content

AndroidCoderPeng/GB28181

Repository files navigation

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 0100 00 01
    • ⚠️ 若 H.264 帧缺少起始码,PS 封装将失败或平台无法解析,导致推流无画面!
  3. 时间戳记录:记录每帧编码完成时的时间戳(微秒级),相对于全局起始时间

5. 视频帧封装为 MPEG-2 PS 流(高复杂度环节)

5.1 时间戳转换

通过 JNI 将 H.264 帧数据传递至 Native 层,执行时间戳基准转换:

uint64_t pts_90kHz = static_cast<uint64_t>(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 层:

uint64_t pts_90kHz = static_cast<uint64_t>(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 语音对讲信令
  • 参数解析:根据 CmdTypeBroadcast 类型的信令解析出 SourceIDTargetID
  • 确认响应:回复平台 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

About

Android平台(Java/Kotlin/C++)GB/T 28181-2016推流以及语音对讲

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors