您好,登錄后才能下訂單哦!
今天小編給大家分享一下Golang如何實現RTP音視頻傳輸的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
在 Coding 之前我們先來簡單介紹一下 RTP(Real-time Transport Protocol), 正如它的名字所說,用于互聯網的實時傳輸協議,通過 IP 網絡傳輸音頻和視頻的網絡協議。
由音視頻傳輸工作小組開發,1996 年首次發布,并提出了以下使用設想。
簡單的多播音頻會議
使用 IP 的多播服務進行語音通信。通過某種分配機制,獲取多播組地址和端口對。一個端口用于音頻數據的,另一個用于控制(RTCP)包,地址和端口信息被分發給預期的參與者。如果需要加密,可通過特定格式進行加密。
音視頻會議
如果在會議中同時使用音視頻媒體,那么它們是作為單獨的 RTP 會話傳輸。音頻,視頻兩個媒體分別使用不同的 UDP 端口對傳輸單獨的 RTP 和 RTCP 數組包,多播地址可能相同,可能不同。進行這種分離的動機是如果參與者只想接受一種媒體,可以進行選擇。
Mixer 和 Translator
我們需要考慮這樣一種情況,在某個會議中,大多數人處于高速網絡鏈路中,而某個地方的一小部分人只能低速率連接。為了防止所有人使用低帶寬,可以在低帶寬區域放置一個 RTP 級的中繼器 Mixer。Mixer 將接收的音頻報文重新同步為發送方 20 ms 恒定間隔,重建音頻為單一流,音頻編碼為低速帶寬的音頻,然后轉發給低速鏈路上的帶寬數據包流。
分層編碼
多媒體應用程序應該能調節傳輸速率以匹配接收者容量或適應網絡擁塞。可以將調節速率的任務通過將分層編碼和分層傳輸系統相結合來實現接收器。在基于 IP 多播的 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。
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") } }) }
以上就是“Golang如何實現RTP音視頻傳輸”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。