热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Golang 实现 RTP音视频传输示例详解

Golang 实现 RTP音视频传输示例详解-目录引言RTP数据包头部字段Golang的相关实现结尾引言在Coding之前我们先来简单介绍一下RTP(Real-timeTransp

引言

在 Coding 之前我们先来简单介绍一下 RTP(Real-time Transport Protocol), 正如它的名字所说,用于互联网的实时传输协议,通过 IP 网络传输音频和视频的网络协议。

由音视频传输工作小组开发,1996 年首次发布,并提出了以下使用设想。

  • 简单的多播音频会议

使用 IP 的多播服务进行语音通信。通过某种分配机制,获取多播组地址和端口对。一个端口用于音频数据的,另一个用于控制(RTCP)包,地址和端口信息被分发给预期的参与者。如果需要加密,可通过特定格式进行加密。

  • 音视频会议

如果在会议中同时使用音视频媒体,那么它们是作为单独的 RTP 会话传输。音频,视频两个媒体分别使用不同的 UDP 端口对传输单独的 RTP 和 RTCP 数组包,多播地址可能相同,可能不同。进行这种分离的动机是如果参与者只想接受一种媒体,可以进行选择。

  • Mixer 和 Translator

我们需要考虑这样一种情况,在某个会议中,大多数人处于高速网络链路中,而某个地方的一小部分人只能低速率连接。为了防止所有人使用低带宽,可以在低带宽区域放置一个 RTP 级的中继器 Mixer。Mixer 将接收的音频报文重新同步为发送方 20 ms 恒定间隔,重建音频为单一流,音频编码为低速带宽的音频,然后转发给低速链路上的带宽数据包流。

  • 分层编码

多媒体应用程序应该能调节传输速率以匹配接收者容量或适应网络拥塞。可以将调节速率的任务通过将分层编码和分层传输系统相结合来实现接收器。在基于 IP 多播的 RTP 上下文中,每个 RTP 会话均承载在自己的多播组上。然后,接收者可以只通过加入组播组合适的子集来调整接收带宽。

RTP 数据包头部字段

只有当 Mixer 存在时,才会存在 CSRC 标识符列表。这些字段具有以下含义。前 12 个 8 位组在每一个包中都有。

  • version (V): 2 bits

RTP 版本。

  • 填充 (P): 1 bit

如果设置了填充位,包中包含至少一个填充 8 位组,其他填充位不属于 Payload。

  • 扩展 (X): 1 bit

如果设置了扩展位则存在。

  • CSRC 数量(CC): 4 bits

CSRC 数量包含在固定头中,CSRC 标识符数量。

  • 标记 (M): 1 bit

标记由配置文件定义。用于标记数据包流中例如帧边界之类的重要事件。

  • payload 类型(PT): 7 bits

该字段指出 RTP 有效载荷格式,由应用程序进行解释。接收者必须忽略无法理解的有效载荷类型的数据包。

  • 序列号: 16 bits

每次 RTP 数据包发送时增加,可能用于接收者检测包丢失并且恢复包序列。

  • 时间戳: 32 bits

该字段反映了 RTP 数据包中第一个 8 位组的采样时刻。

  • SSRC: 32 bits

标识同步源,这个标识符应该选择随机,在同一个 RTP 对话的两个同步源应该有不同的同步标识。

  • CSRC 列表:0 到 15 项, 其中每项 32 bits

该字段表示对该 payload 数据做出贡献所有 SSRC。

Golang 的相关实现

RTP 的实现有一些,不过通过 Go 实现有一些好处。

  • 易于测试

这里的易于测试不仅仅是体现在容易书写,能够快速通过源码,函数直接生成相应测试函数。而且更重要的是能够提供相应的基准测试,提供计时,并行执行,内存统计等参数供开发者进行相应调整。

  • 语言层面强大的 Web 开发能力

能够基于语言层面快速的对例 JSON 解析,字段封装 。无需引入三方库。

  • 性能较为优异

相比于 Python,Ruby 等解释型语言快,比 node, erlang 等语言更易书写。如果服务中需要用并发,内置关键字 go 就可以快速起多个 goroutine。

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 自带的 Unmarshal 快速将 byte 切片转换为相应结构体。减少了相关封包,解包等代码的工作量。在网络传输中,也能够在语言层面直接完成大端,小端编码的转换,减少编码的烦恼。

h.SequenceNumber = binary.BigEndian.Uint16(rawPacket[seqNumOffset : seqNumOffset+seqNumLength])
h.Timestamp = binary.BigEndian.Uint32(rawPacket[timestampOffset : timestampOffset+timestampLength])
h.SSRC = 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 去进行相关的业务时间,部分公司甚至已经将核心业务交由处理,可见其稳定性。个人去测试了一下他们的云课堂相关服务,回放,在线演示等功能十分便捷,可以节约大量开发时间。


推荐阅读
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • 开发笔记:Python之路第一篇:初识Python
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Python之路第一篇:初识Python相关的知识,希望对你有一定的参考价值。Python简介& ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • 本文介绍了如何在Azure应用服务实例上获取.NetCore 3.0+的支持。作者分享了自己在将代码升级为使用.NET Core 3.0时遇到的问题,并提供了解决方法。文章还介绍了在部署过程中使用Kudu构建的方法,并指出了可能出现的错误。此外,还介绍了开发者应用服务计划和免费产品应用服务计划在不同地区的运行情况。最后,文章指出了当前的.NET SDK不支持目标为.NET Core 3.0的问题,并提供了解决方案。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
author-avatar
BELLICOSE牛仔
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有