Golang 实现 RTP
在 Coding 之前我们先来简单介绍一下 RTP(Real-time Transport Protocol), 正如它的名字所说,用于互联网的实时传输协议,通过 IP 网络传输音频和视频的网络协议。
由音视频传输工作小组开发,1996 年首次发布,并提出了以下使用设想。
- 简单的多播音频会议
- 音视频会议
- Mixer 和 Translator
- 分层编码
RTP 数据包头部字段 只有当 Mixer 存在时,才会存在 CSRC 标识符列表。这些字段具有以下含义。前 12 个 8 位组在每一个包中都有。
- version (V): 2 bits
- 填充 (P): 1 bit
- 扩展 (X): 1 bit
- CSRC 数量(CC): 4 bits
- 标记 (M): 1 bit
- payload 类型(PT): 7 bits
- 序列号: 16 bits
- 时间戳: 32 bits
- SSRC: 32 bits
- CSRC 列表:0 到 15 项, 其中每项 32 bits
Golang 的相关实现 RTP 的实现有一些,不过通过 Go 实现有一些好处。
- 易于测试
- 语言层面强大的 Web 开发能力
- 性能较为优异
Go 社区的 RTP 有 RTP 相关实现,对应的测试也比较全面,简单介绍一下。
package_test.go (基础测试)
func TestBasic(t *testing.T) {
p := &Packet{}if err := p.Unmarshal([]byte{});
err == nil {
t.Fatal("Unmarshal did not error on zero length packet")
}rawPkt := []byte{
0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64,
0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e,
}
parsedPacket := &Packet{
// 固定头部
Header: Header{
Marker:true,
Extension:true,
ExtensionProfile: 1,
Extensions: []Extension{
{0, []byte{
0xFF, 0xFF, 0xFF, 0xFF,
}},
},
Version:2,
PayloadOffset:20,
PayloadType:96,
SequenceNumber: 27023,
Timestamp:3653407706,
SSRC:476325762,
CSRC:[]uint32{},
},
// 有效负载
Payload: rawPkt[20:],
Raw:rawPkt,
}// Unmarshal to the used Packet should work as well.
for i := 0;
i < 2;
i++ {
t.Run(fmt.Sprintf("Run%d", i+1), func(t *testing.T) {
if err := p.Unmarshal(rawPkt);
err != nil {
t.Error(err)
} else if !reflect.DeepEqual(p, parsedPacket) {
t.Errorf("TestBasic unmarshal: got %#v, want %#v", p, parsedPacket)
}if parsedPacket.Header.MarshalSize() != 20 {
t.Errorf("wrong computed header marshal size")
} else if parsedPacket.MarshalSize() != len(rawPkt) {
t.Errorf("wrong computed marshal size")
}if p.PayloadOffset != 20 {
t.Errorf("wrong payload offset: %d != %d", p.PayloadOffset, 20)
}raw, err := p.Marshal()
if err != nil {
t.Error(err)
} else if !reflect.DeepEqual(raw, rawPkt) {
t.Errorf("TestBasic marshal: got %#v, want %#v", raw, rawPkt)
}if p.PayloadOffset != 20 {
t.Errorf("wrong payload offset: %d != %d", p.PayloadOffset, 20)
}
})
}
}
【Golang 实现 RTP】基本测试中,利用 Golang 自带的 Unmarshal 快速将 byte 切片转换为相应结构体。减少了相关封包,解包等代码的工作量。在网络传输中,也能够在语言层面直接完成大端,小端编码的转换,减少编码的烦恼。
h.SequenceNumber = binary.BigEndian.Uint16(rawPacket[seqNumOffset : seqNumOffset+seqNumLength])
h.Timestamp = binary.BigEndian.Uint32(rawPacket[timestampOffset : timestampOffset+timestampLength])
h.SSRC = https://www.it610.com/article/binary.BigEndian.Uint32(rawPacket[ssrcOffset : ssrcOffset+ssrcLength])
其中关于切片的相关操作十分便捷,可以获取数组中的某一段数据,操作比较灵活,在协议数据的传输过程中,通过切片,获取某段数据进行相应处理。
m := copy(buf[n:], p.Payload)
p.Raw = buf[:n+m]
在实现完成后,Golang 的子测试能够进行嵌套测试。对执行特定测试用例特别有用,只有子测试完成后,父测试才会返回。
func TestVP8PartitionHeadChecker_IsPartitionHead(t *testing.T) {
checker := &VP8PartitionHeadChecker{}
t.Run("SmallPacket", func(t *testing.T) {
if checker.IsPartitionHead([]byte{0x00}) {
t.Fatal("Small packet should not be the head of a new partition")
}
})
t.Run("SFlagON", func(t *testing.T) {
if !checker.IsPartitionHead([]byte{0x10, 0x00, 0x00, 0x00}) {
t.Fatal("Packet with S flag should be the head of a new partition")
}
})
t.Run("SFlagOFF", func(t *testing.T) {
if checker.IsPartitionHead([]byte{0x00, 0x00, 0x00, 0x00}) {
t.Fatal("Packet without S flag should not be the head of a new partition")
}
})
}
更多的相关实现可以去 GitHub(https://github.com/pion/rtp) 上看一下实现源码。
结尾 如果人为去关注相关的传输细节,可能在底层耗费大量时间,目前市面上有很多相关的实现方案,有开源的,和一些公司提供的一些方案。目前经过业界实践,陌陌,小米众多公司都采用了声网的 SDK 去进行相关的业务时间,部分公司甚至已经将核心业务交由处理,可见其稳定性。个人去测试了一下他们的云课堂相关服务,回放,在线演示等功能十分便捷,可以节约大量开发时间。
推荐阅读
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- MybatisPlus使用queryWrapper如何实现复杂查询
- python学习之|python学习之 实现QQ自动发送消息
- 孩子不是实现父母欲望的工具——林哈夫
- opencv|opencv C++模板匹配的简单实现
- Node.js中readline模块实现终端输入
- java中如何实现重建二叉树
- 人脸识别|【人脸识别系列】| 实现自动化妆
- paddle|动手从头实现LSTM
- pytorch|使用pytorch从头实现多层LSTM