- Java 层:初始化 SIP 核心参数(设备 ID、服务器地址、端口等),通过 JNI 传递至 Native 层
- Native 层:基于 eXosip2 库向 GB/T 28181 国标平台发起注册请求
- 时间戳基准:注册成功后记录程序启动的微秒级时间戳(
System.nanoTime() / 1000),作为后续音视频帧时间戳的全局基准
- 采用 Camera1 API 采集视频画面
- 输出 YUV420 (NV21) 格式的原始视频数据
- 使用
AudioRecord采集原始 PCM 裸流(标准配置:16-bit、单声道、8kHz) - 记录每帧音频数据的时间戳(微秒级),相对于步骤 1 中的起始时间基准
默认状态:客户端仅进行本地预览,不执行编码和推流操作
触发机制:当国标平台下发拉流信令(INVITE 请求)后,启动编码推流流程:
- 格式转换:将 NV21 转换为 NV12(适配硬件编码器输入要求)
- H.264 编码:通过硬件编码器生成完整的 H.264 帧数据
⚠️ 关键要求:必须包含起始码(Start Code:00 00 00 01或00 00 01)⚠️ 若 H.264 帧缺少起始码,PS 封装将失败或平台无法解析,导致推流无画面!
- 时间戳记录:记录每帧编码完成时的时间戳(微秒级),相对于全局起始时间
通过 JNI 将 H.264 帧数据传递至 Native 层,执行时间戳基准转换:
uint64_t pts_90kHz = static_cast<uint64_t>(pts_us / 1000) * 90
⚠️ 强制要求:GB/T 28181-2016 规定必须使用 90KHz 时间基准,否则收流端无法解码导致无画面!
- 将整帧 H.264 数据按起始码拆分为 NALU(网络应用层单元,是 H.264 数据的基本单元)
- 提取 SPS/PPS 帧数据并缓存
- 筛选 IDR 帧(关键帧)
组帧顺序:[起始码] + [SPS] + [PPS] + [IDR 帧] → 封装为 PES 包
⚠️ 传输顺序强制要求:IDR 帧必须先于任何其他帧发送到平台,因为 SPS/PPS 携带视频帧的基本解码信息,平台缺少这些信息将无法解析画面。
组帧顺序:[起始码] + [P 帧] → 封装为 PES 包
以 PES 包为载荷,按照以下规则封装为 MPEG-2 PS 流:
- IDR 帧:添加系统头(System Header)和 PSM(Program Stream Map)
- 非 IDR 帧:直接封装
⚠️ 字节级精度要求:系统头和 PSM 的封装涉及复杂的字节和 Bit 位操作,每个字节的含义必须严格符合 MPEG-2 PS 规范,任何偏差都会导致封装失败!
通过 JNI 将 PCM 裸流传递至 Native 层:
uint64_t pts_90kHz = static_cast<uint64_t>(pts_us / 1000) * 90
⚠️ 同步关键:90KHz 时间基准是保证音画同步的核心要求!
在 Native 层将 PCM 编码为:
- G.711 μ-law(μ律)
- G.711 A-law(A 律)
将整帧 G.711 数据直接封装为 PES 包(音频封装相对简单,无需复杂处理)
以 PES 包为载荷,封装为 MPEG-2 PS 流
⚠️ 重要区分:音频帧均为非 IDR 帧,无需添加系统头、PSM 和起始码!
由于 RTP MTU 限制(约 1400 字节),需对较大的 PES 包进行分片处理
判断每个 PES 包大小并执行相应策略:
- ≤ 1300 字节:直接作为单个 RTP 包发送
- > 1300 字节:按 RFC 3984 或 GB/T 28181-2016 规范进行 RTP 分片封装
将分片后的 PES 包封装为 RTP 包,发送至国标平台
⚠️ 协议一致性要求:必须严格按照 SDP 消息中协商的传输方式发送,否则平台会丢弃接收到的数据包!
将封装好的 RTP 包(单包或分片)通过以下方式发送至国标平台:
-
TCP 模式
- 优势:连接可靠、抗丢包
- 适用场景:网络环境不稳定
-
UDP 模式
- 优势:延迟更低
- 适用场景:实时性要求高的场景
传输协议类型由平台信令协商确定,客户端动态适配
- 信令监听:监听平台下发的 Notify 语音对讲信令
- 参数解析:根据
CmdType为Broadcast类型的信令解析出SourceID、TargetID - 确认响应:回复平台 ACK 200 确认信令
- TCP 初始化:初始化 TCP 客户端,获取本地 TCP 端口
- INVITE 发送:发送 INVITE 请求到平台
⚠️ TCP 选择原因:设备和平台通常不在同一网段(设备位于局域网/内网,平台位于公网)。TCP 可建立双向连接,平台可通过该连接链路将对讲数据回传;UDP 模式需自行实现 NAT 穿透或打洞,复杂度较高。
- 解析平台回复的 SDP 消息,获取:
- 平台 TCP IP 地址和端口
- 音频解码类型(G.711 μ/A 律)
- 回复 ACK 200 确认
连接平台指定的 TCP 地址和端口,开始接收平台数据
⚠️ 高性能接收要求:由于数据接收频率较高,必须采用【独立线程 + 环形缓冲区】架构,否则性能将成为系统瓶颈!
接收到的数据为 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