Golang 實(shí)現(xiàn) RTP音視頻傳輸示例詳解
引言
在 Coding 之前我們先來簡(jiǎn)單介紹一下 RTP(Real-time Transport Protocol), 正如它的名字所說,用于互聯(lián)網(wǎng)的實(shí)時(shí)傳輸協(xié)議,通過 IP 網(wǎng)絡(luò)傳輸音頻和視頻的網(wǎng)絡(luò)協(xié)議。
由音視頻傳輸工作小組開發(fā),1996 年首次發(fā)布,并提出了以下使用設(shè)想。
- 簡(jiǎn)單的多播音頻會(huì)議
使用 IP 的多播服務(wù)進(jìn)行語音通信。通過某種分配機(jī)制,獲取多播組地址和端口對(duì)。一個(gè)端口用于音頻數(shù)據(jù)的,另一個(gè)用于控制(RTCP)包,地址和端口信息被分發(fā)給預(yù)期的參與者。如果需要加密,可通過特定格式進(jìn)行加密。
- 音視頻會(huì)議
如果在會(huì)議中同時(shí)使用音視頻媒體,那么它們是作為單獨(dú)的 RTP 會(huì)話傳輸。音頻,視頻兩個(gè)媒體分別使用不同的 UDP 端口對(duì)傳輸單獨(dú)的 RTP 和 RTCP 數(shù)組包,多播地址可能相同,可能不同。進(jìn)行這種分離的動(dòng)機(jī)是如果參與者只想接受一種媒體,可以進(jìn)行選擇。
- Mixer 和 Translator
我們需要考慮這樣一種情況,在某個(gè)會(huì)議中,大多數(shù)人處于高速網(wǎng)絡(luò)鏈路中,而某個(gè)地方的一小部分人只能低速率連接。為了防止所有人使用低帶寬,可以在低帶寬區(qū)域放置一個(gè) RTP 級(jí)的中繼器 Mixer。Mixer 將接收的音頻報(bào)文重新同步為發(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é)合來實(shí)現(xiàn)接收器。在基于 IP 多播的 RTP 上下文中,每個(gè) RTP 會(huì)話均承載在自己的多播組上。然后,接收者可以只通過加入組播組合適的子集來調(diào)整接收帶寬。
RTP 數(shù)據(jù)包頭部字段
只有當(dāng) Mixer 存在時(shí),才會(huì)存在 CSRC 標(biāo)識(shí)符列表。這些字段具有以下含義。前 12 個(gè) 8 位組在每一個(gè)包中都有。
- version (V): 2 bits
RTP 版本。
- 填充 (P): 1 bit
如果設(shè)置了填充位,包中包含至少一個(gè)填充 8 位組,其他填充位不屬于 Payload。
- 擴(kuò)展 (X): 1 bit
如果設(shè)置了擴(kuò)展位則存在。
- CSRC 數(shù)量(CC): 4 bits
CSRC 數(shù)量包含在固定頭中,CSRC 標(biāo)識(shí)符數(shù)量。
- 標(biāo)記 (M): 1 bit
標(biāo)記由配置文件定義。用于標(biāo)記數(shù)據(jù)包流中例如幀邊界之類的重要事件。
- payload 類型(PT): 7 bits
該字段指出 RTP 有效載荷格式,由應(yīng)用程序進(jìn)行解釋。接收者必須忽略無法理解的有效載荷類型的數(shù)據(jù)包。
- 序列號(hào): 16 bits
每次 RTP 數(shù)據(jù)包發(fā)送時(shí)增加,可能用于接收者檢測(cè)包丟失并且恢復(fù)包序列。
- 時(shí)間戳: 32 bits
該字段反映了 RTP 數(shù)據(jù)包中第一個(gè) 8 位組的采樣時(shí)刻。
- SSRC: 32 bits
標(biāo)識(shí)同步源,這個(gè)標(biāo)識(shí)符應(yīng)該選擇隨機(jī),在同一個(gè) RTP 對(duì)話的兩個(gè)同步源應(yīng)該有不同的同步標(biāo)識(shí)。
- CSRC 列表:0 到 15 項(xiàng), 其中每項(xiàng) 32 bits
該字段表示對(duì)該 payload 數(shù)據(jù)做出貢獻(xiàn)所有 SSRC。
Golang 的相關(guān)實(shí)現(xiàn)
RTP 的實(shí)現(xiàn)有一些,不過通過 Go 實(shí)現(xiàn)有一些好處。
- 易于測(cè)試
這里的易于測(cè)試不僅僅是體現(xiàn)在容易書寫,能夠快速通過源碼,函數(shù)直接生成相應(yīng)測(cè)試函數(shù)。而且更重要的是能夠提供相應(yīng)的基準(zhǔn)測(cè)試,提供計(jì)時(shí),并行執(zhí)行,內(nèi)存統(tǒng)計(jì)等參數(shù)供開發(fā)者進(jìn)行相應(yīng)調(diào)整。
- 語言層面強(qiáng)大的 Web 開發(fā)能力
能夠基于語言層面快速的對(duì)例 JSON 解析,字段封裝 。無需引入三方庫。
- 性能較為優(yōu)異
相比于 Python,Ruby 等解釋型語言快,比 node, erlang 等語言更易書寫。如果服務(wù)中需要用并發(fā),內(nèi)置關(guān)鍵字 go 就可以快速起多個(gè) goroutine。
Go 社區(qū)的RTP有 RTP 相關(guān)實(shí)現(xiàn),對(duì)應(yīng)的測(cè)試也比較全面,簡(jiǎn)單介紹一下。
package_test.go (基礎(chǔ)測(cè)試)
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{}, }, // 有效負(fù)載 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) } }) } }
基本測(cè)試中,利用 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ù)進(jìn)行相應(yīng)處理。
m := copy(buf[n:], p.Payload) p.Raw = buf[:n+m]
在實(shí)現(xiàn)完成后,Golang 的子測(cè)試能夠進(jìn)行嵌套測(cè)試。對(duì)執(zhí)行特定測(cè)試用例特別有用,只有子測(cè)試完成后,父測(cè)試才會(huì)返回。
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") } }) }
更多的相關(guān)實(shí)現(xiàn)可以去 GitHub(https://github.com/pion/rtp) 上看一下實(shí)現(xiàn)源碼。
結(jié)尾
如果人為去關(guān)注相關(guān)的傳輸細(xì)節(jié),可能在底層耗費(fèi)大量時(shí)間,目前市面上有很多相關(guān)的實(shí)現(xiàn)方案,有開源的,和一些公司提供的一些方案。目前經(jīng)過業(yè)界實(shí)踐,陌陌,小米眾多公司都采用了聲網(wǎng)的 SDK 去進(jìn)行相關(guān)的業(yè)務(wù)時(shí)間,部分公司甚至已經(jīng)將核心業(yè)務(wù)交由處理,可見其穩(wěn)定性。個(gè)人去測(cè)試了一下他們的云課堂相關(guān)服務(wù),回放,在線演示等功能十分便捷,可以節(jié)約大量開發(fā)時(shí)間。
以上就是Golang 實(shí)現(xiàn) RTP音視頻傳輸示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang RTP音視頻傳輸?shù)馁Y料請(qǐng)關(guān)注本站其它相關(guān)文章!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。