溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Golang如何實現(xiàn)RTP音視頻傳輸

發(fā)布時間:2022-07-13 11:03:48 來源:億速云 閱讀:229 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下Golang如何實現(xiàn)RTP音視頻傳輸?shù)南嚓P(guān)知識點,內(nèi)容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

引言

在 Coding 之前我們先來簡單介紹一下 RTP(Real-time Transport Protocol), 正如它的名字所說,用于互聯(lián)網(wǎng)的實時傳輸協(xié)議,通過 IP 網(wǎng)絡(luò)傳輸音頻和視頻的網(wǎng)絡(luò)協(xié)議。

由音視頻傳輸工作小組開發(fā),1996 年首次發(fā)布,并提出了以下使用設(shè)想。

  • 簡單的多播音頻會議

使用 IP 的多播服務(wù)進行語音通信。通過某種分配機制,獲取多播組地址和端口對。一個端口用于音頻數(shù)據(jù)的,另一個用于控制(RTCP)包,地址和端口信息被分發(fā)給預(yù)期的參與者。如果需要加密,可通過特定格式進行加密。

  • 音視頻會議

如果在會議中同時使用音視頻媒體,那么它們是作為單獨的 RTP 會話傳輸。音頻,視頻兩個媒體分別使用不同的 UDP 端口對傳輸單獨的 RTP 和 RTCP 數(shù)組包,多播地址可能相同,可能不同。進行這種分離的動機是如果參與者只想接受一種媒體,可以進行選擇。

  • Mixer 和 Translator

我們需要考慮這樣一種情況,在某個會議中,大多數(shù)人處于高速網(wǎng)絡(luò)鏈路中,而某個地方的一小部分人只能低速率連接。為了防止所有人使用低帶寬,可以在低帶寬區(qū)域放置一個 RTP 級的中繼器 Mixer。Mixer 將接收的音頻報文重新同步為發(fā)送方 20 ms 恒定間隔,重建音頻為單一流,音頻編碼為低速帶寬的音頻,然后轉(zhuǎn)發(fā)給低速鏈路上的帶寬數(shù)據(jù)包流。

  • 分層編碼

多媒體應(yīng)用程序應(yīng)該能調(diào)節(jié)傳輸速率以匹配接收者容量或適應(yīng)網(wǎng)絡(luò)擁塞??梢詫⒄{(diào)節(jié)速率的任務(wù)通過將分層編碼和分層傳輸系統(tǒng)相結(jié)合來實現(xiàn)接收器。在基于 IP 多播的 RTP 上下文中,每個 RTP 會話均承載在自己的多播組上。然后,接收者可以只通過加入組播組合適的子集來調(diào)整接收帶寬。

RTP 數(shù)據(jù)包頭部字段

只有當 Mixer 存在時,才會存在 CSRC 標識符列表。這些字段具有以下含義。前 12 個 8 位組在每一個包中都有。

  • version (V): 2 bits

RTP 版本。

  • 填充 (P): 1 bit

如果設(shè)置了填充位,包中包含至少一個填充 8 位組,其他填充位不屬于 Payload。

  • 擴展 (X): 1 bit

如果設(shè)置了擴展位則存在。

  • CSRC 數(shù)量(CC): 4 bits

CSRC 數(shù)量包含在固定頭中,CSRC 標識符數(shù)量。

  • 標記 (M): 1 bit

標記由配置文件定義。用于標記數(shù)據(jù)包流中例如幀邊界之類的重要事件。

  • payload 類型(PT): 7 bits

該字段指出 RTP 有效載荷格式,由應(yīng)用程序進行解釋。接收者必須忽略無法理解的有效載荷類型的數(shù)據(jù)包。

  • 序列號: 16 bits

每次 RTP 數(shù)據(jù)包發(fā)送時增加,可能用于接收者檢測包丟失并且恢復(fù)包序列。

  • 時間戳: 32 bits

該字段反映了 RTP 數(shù)據(jù)包中第一個 8 位組的采樣時刻。

  • SSRC: 32 bits

標識同步源,這個標識符應(yīng)該選擇隨機,在同一個 RTP 對話的兩個同步源應(yīng)該有不同的同步標識。

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

該字段表示對該 payload 數(shù)據(jù)做出貢獻所有 SSRC。

Golang 的相關(guān)實現(xiàn)

RTP 的實現(xiàn)有一些,不過通過 Go 實現(xiàn)有一些好處。

  • 易于測試

這里的易于測試不僅僅是體現(xiàn)在容易書寫,能夠快速通過源碼,函數(shù)直接生成相應(yīng)測試函數(shù)。而且更重要的是能夠提供相應(yīng)的基準測試,提供計時,并行執(zhí)行,內(nèi)存統(tǒng)計等參數(shù)供開發(fā)者進行相應(yīng)調(diào)整。

  • 語言層面強大的 Web 開發(fā)能力

能夠基于語言層面快速的對例 JSON 解析,字段封裝 。無需引入三方庫。

  • 性能較為優(yōu)異

相比于 Python,Ruby 等解釋型語言快,比 node, erlang 等語言更易書寫。如果服務(wù)中需要用并發(fā),內(nèi)置關(guān)鍵字 go 就可以快速起多個 goroutine。

Go 社區(qū)的RTP有 RTP 相關(guān)實現(xiàn),對應(yīng)的測試也比較全面,簡單介紹一下。

package_test.go (基礎(chǔ)測試)

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 切片轉(zhuǎn)換為相應(yīng)結(jié)構(gòu)體。減少了相關(guān)封包,解包等代碼的工作量。在網(wǎng)絡(luò)傳輸中,也能夠在語言層面直接完成大端,小端編碼的轉(zhuǎn)換,減少編碼的煩惱。

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])

其中關(guān)于切片的相關(guān)操作十分便捷,可以獲取數(shù)組中的某一段數(shù)據(jù),操作比較靈活,在協(xié)議數(shù)據(jù)的傳輸過程中,通過切片,獲取某段數(shù)據(jù)進行相應(yīng)處理。

m := copy(buf[n:], p.Payload)
p.Raw = buf[:n+m]

在實現(xiàn)完成后,Golang 的子測試能夠進行嵌套測試。對執(zhí)行特定測試用例特別有用,只有子測試完成后,父測試才會返回。

func TestVP8PartitionHeadChecker_IsPartitionHead(t *testing.T) {
    checker := &amp;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")
        }
    })
}

以上就是“Golang如何實現(xiàn)RTP音視頻傳輸”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI