流媒體服務-傳輸延時(SEI插幀)

什么是延時

很多小伙伴認為,當推流端和拉流端顯示的時間不一致,即為延時。

其實這種看法是比較片面的,不同的播放器,對同一路流進行測試,可能會得到不同的結果。

一般來說,延時為以下幾個部分的累加組成

  • 采集延時

在采集攝像頭或顯卡畫面時,由于fps的限制和cpu性能、內存拷貝速度等客觀限制,采集畫面成YUV/RGB等數據時會有一定的延時,一般延時為毫秒級別。由于一般編碼器對輸入數據格式存在限制,譬如要求統一輸入YUV420P,這樣在做RGB->YUV420P轉換時,也會有轉換計算延時(這個可以通過libyuv庫來降低)。總而言之,采集延時大概為毫秒級別,如果fps為30,那么一般采集延時會有30毫秒以上的延時,在內存拷貝和顏色轉換時,又可能增加若干毫秒的延時。

  • 編碼延時

在把原始畫面輸入到編碼器時,并不會立即輸出編碼后的數據,特別是在開啟B幀時,由于需要參考后面的P幀,那么延時會更大,所以延時敏感的情況下一般不開啟B幀,這種情況下編碼延時應該是毫秒級別,不是很大。

  • 網絡上行傳輸延時

編碼后的數據,要經過一定的協議打包才能寫入socket,然后傳輸給推流服務器或拉流代理服務器,協議打包會有一定的內存拷貝和計算量,那么會增加延時,不過這個延時很小,基本忽略不計。數據在上傳到服務器時,這個延時可大可小,取決于網絡質量。

  • 服務器轉協議延時

服務器在收到數據后,要讀socket緩存、協議解析、解復用、重新打包等操作,不過總體而言,這個延時比較小,基本沒什么影響。有時,服務器為了提高性能,會采取合并寫的機制,也就是收到一定量的數據后才會一并轉發,這個延時一般為幾百毫秒。不過一般服務器會默認不會打開此機制

  • 網絡下行延時

流媒體在把視頻數據轉發給播放器時,會存在網絡發送,這個延時大小取決于網絡質量。

  • 播放器延時

播放器延時主要有網絡接收延時、協議解析解復用延時、解碼延時、緩存延時、渲染延時組成,這些延時中緩存延時最大,因為一般的播放器為了保證在網絡抖動情況下視頻播放的流暢性,會以增加延時為代價,增加播放緩存,這樣在網絡變差時,不至于播放緩沖卡頓。而且為了音視頻同步,也必須確保一定的緩存量。這種延時一般都是秒級別,一般5秒左右。

  • 播放器GOP緩存延時

流媒體服務器為了能讓播放器立即出畫面,往往會緩存最近的一個I幀,這個I幀往后的所有音視頻數據被稱作為GOP緩存。如果不緩存GOP,那么播放器要等下一個I幀才能解碼成功或不花屏,顯然為了提高播放體驗,這個GOP緩存是不能去掉的。而一般GOP短則1~3秒,長則10幾秒,這個跟采集端編碼器設置有關,服務器改變不了。但是由于一般的播放器收到緩存后,并不會丟棄過多的畫面來確保低延時。況且播放器還希望有一定的緩存來確保播放的流暢性,所以這個GOP緩存將會增大播放器的延時。

  • 綜合延時

以上所有的延時累加,就是你觀看到的直觀延時。通常大部分延時可能是由播放器造成,如果對播放器緩沖區感興趣的同學可以參考這篇文章:https://zhuanlan.zhihu.com/p/51582357

如何計算延時

本文所討論的延時為網絡傳輸延時,也就是經過采集編碼后的數據,經由推流端通過網絡發送到到流媒體服務器流媒體服務器將數據通過網絡推送到到拉流端的延時。

本文推薦使用在碼流中混入SEI幀的方式來計算傳輸延時,具體步驟如下

  • 推流端在I幀之前插入SEI幀,內容為推流端時間戳
  • 拉流端在接收到SEI幀之后,解碼出推流端時間戳,與拉流端時間戳對比,計算出延時

在這里插入圖片描述

SEI 幀

先復習一下H264碼流結構

  • H.264 原始碼流組成結構

H.264 原始碼流(裸流)是由一個接一個 NALU 組成。它的功能分為兩層,VCL(視頻編碼層)和 NAL(網絡提取層)。

為了方便從字節流中提取出 NALU,協議規定,在每個 NALU 的前面加上起始碼(StartCode): 0x000001 或 0x00000001。

  • NALU 組成結構

NALU(NAL Unit)= 一組對應于視頻編碼的 NALU 頭部信息(NAL header)+ 一個 RBSP(Raw Byte Sequence Payload,原始字節序列負荷)

NAL Unit Type 常?類型如下:

NAL Unit TypeNAL Unit Content
1非 IDR 圖像,且不采用數據劃分的片段。
5IDR 圖像。
6補充增強信息(SEI)。
7序列參數集(SPS)。
8圖像參數集(PPS)。
11流結束符。

那么NAL Unit Type6時,即為SEI幀。

SEI payload type 計算方式

當開始解析類型為 SEI 的 NAL 時,在 RBSP 中持續讀取 8 bit,直到非 0xff 為止,然后把讀取的數值累加,累加值即為 SEI payload type。

SEI RBSP 結構圖如下:

SEI payload size 計算方式

讀取 SEI payload size 的邏輯與 SEI payload type 類似,即讀取到非 0xff 為止,這樣可以支持任意?度的 SEI payload 添加。假設 SEI payload type 后面的字符序列是 FF FF AA BB …,則 FF FF AA 將會解析成 SEI payload size,為 255 + 255 + 170 = 680。

實例代碼

// @brief: 將時間戳寫入sei frame,將sei frame寫入文件
#include <iostream>
#include <vector>
#include <cassert>
#include <fstream>
#include <string>
#include <chrono>std::vector<uint8_t> MakeSei(const std::string& data)
{// 使用1個字節存儲payloadassert(data.size() + 16 < 255);std::vector<uint8_t> seiFrame;std::vector<uint8_t> uuid(16, 0x41);uint8_t              payloadSize = 16 + data.size();seiFrame.insert(seiFrame.end(), {0x00, 0x00, 0x00, 0x01}); // start codeseiFrame.insert(seiFrame.end(), {0x06});                   // nalu typeseiFrame.insert(seiFrame.end(), {0x05});                   // sei unregister data typeseiFrame.push_back(payloadSize);                           // sei payload sizeseiFrame.insert(seiFrame.end(), uuid.begin(), uuid.end()); // uuid,這里可以替換成你自己的for (auto& ch : data)seiFrame.push_back(ch); // custom messageseiFrame.push_back(0x80);   // rbsp trailing bitsreturn seiFrame;
}int main()
{std::ofstream file("sei.h264", std::ios::binary);std::string   data ="ts:" + std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());auto sei = MakeSei(data);file.write(reinterpret_cast<char*>(sei.data()), sei.size());file.close();return 0;
}

這里在SEI中寫入的數據格式為ts:{timestamp},你也可以定義為你希望的數據格式,如json,注意不要超過255 - 16個字節。

生成的幀信息如下:

推流端和拉流端如何進行時鐘對齊

在拉流端拿到SEI frame之后,解碼出推流端時間戳,計算delay = {拉流端時間戳} - {推流端時間戳}

這里存在一個問題是,拉流端系統時鐘可能與推流端系統時鐘不一致(如人為調整過系統時間),導致延時計算不準確,甚至是拉流端時間戳早于推流端時間戳。那么這時候就需要將推流端和拉流端的時間戳進行對齊

一般選擇流媒體服務器時間戳進行對齊。

計算方法如下:

在這里插入圖片描述

  • 參考文章

https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E6%80%8E%E4%B9%88%E6%B5%8B%E8%AF%95ZLMediaKit%E7%9A%84%E5%BB%B6%E6%97%B6%EF%BC%9F

https://doc-zh.zego.im/faq/sei?product=ExpressAudio&platform=macos

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/39879.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/39879.shtml
英文地址,請注明出處:http://en.pswp.cn/news/39879.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Android】解決Lint found fatal errors while assembling a release target

報錯信息&#xff1a; Android在debug模式下打包沒有問題&#xff0c;但是在打包release版本時出現一下問題&#xff1a; 結果圖 原因 我項目的原因是因為把正式、測試地址放到代碼里了&#xff0c;忘記選中正式環境的地址&#xff0c;導致打正式包有問題&#xff1b;大家如果…

Shell編程學習之變量的使用

查看當前系統使用的命令解釋器&#xff1a; linuxubuntu:~$ echo $SHELL /bin/bashshell命令&#xff1a;在終端上使用的命令&#xff0c;例如 vi a.cgcc a.c./a.outshell腳本&#xff1a;其是一個.sh文件&#xff0c;里面都是命令的集合&#xff0c;以及一些復雜的邏輯&#…

RuntimeException詳解

當我們談論Java編程中的異常處理時&#xff0c;RuntimeException是一個關鍵的概念&#xff0c;它在代碼開發和維護中扮演著重要的角色。本文將深入探討RuntimeException&#xff0c;了解它的特點、使用場景以及如何在代碼中處理它。 什么是RuntimeException&#xff1f; 在Ja…

復合 類型

字符串和切片 切片 切片的作用是允許你引用集合中部分連續的元素序列&#xff0c;而不是引用整個集合。 例如&#xff1a; let s String::from("hello world");let hello &s[0..5]; // 切片 [0,5) 等效于&s[..5] let world &s[6..11]; // 切片…

線性動態規劃入門之挖地雷

P2196 [NOIP1996 提高組] 挖地雷 - 洛谷 | 計算機科學教育新生態 (luogu.com.cn) 這個題有點坑&#xff0c;就是說你只能往下挖&#xff0c;可以理解成單項路徑。比如1與3之間是1代表1可以到3而3不可以到1。所以我們來思考dp把。怎么寫&#xff1f;我們這么想假設1與2&#xf…

gitee上傳一個本地項目到一個空倉庫

gitee上傳一個本地項目到一個空倉庫 引入 比如&#xff0c;你現在本地下載了一個半成品的框架&#xff0c;現在想要把這個本地項目放到gitee的倉庫上&#xff0c;這時就需要我們來做到把這個本地項目上傳到gitee上了。 具體步驟 1. 登錄碼云 地址&#xff1a;https://gite…

【Pytroch】基于支持向量機算法的數據分類預測(Excel可直接替換數據)

【Pytroch】基于支持向量機算法的數據分類預測(Excel可直接替換數據) 1.模型原理2.數學公式3.文件結構4.Excel數據5.下載地址6.完整代碼7.運行結果1.模型原理 支持向量機(Support Vector Machine,SVM)是一種強大的監督學習算法,用于二分類和多分類問題。它的主要思想是找…

【數據結構】樹和二叉樹的概念及結構

1.樹概念及結構 1.1樹的概念 樹是一種非線性的數據結構&#xff0c;它是由n&#xff08;n>0&#xff09;個有限結點組成一個具有層次關系的集合。把它叫做樹是因為它看起來像一棵倒掛的樹&#xff0c;也就是說它是根朝上&#xff0c;而葉朝下的。 有一個特殊的結點&#…

Spring Boot 中的 AOP,到底是 JDK 動態代理還是 Cglib 動態代理

大家都知道&#xff0c;AOP 底層是動態代理&#xff0c;而 Java 中的動態代理有兩種實現方式&#xff1a; 基于 JDK 的動態代理 基于 Cglib 的動態代理 這兩者最大的區別在于基于 JDK 的動態代理需要被代理的對象有接口&#xff0c;而基于 Cglib 的動態代理并不需要被代理對…

list

目錄 迭代器 介紹 種類 本質 介紹 模擬實現 注意點 代碼 迭代器 介紹 在C中&#xff0c;迭代器&#xff08;Iterators&#xff09;是一種用于遍歷容器&#xff08;如數組、vector、list等&#xff09;中元素的工具 無論容器的具體實現細節如何,訪問容器中的元素的方…

在ubuntu中將dict.txt導入到數據庫sqlite3

將dict.txt導入到數據庫 #include <head.h> #include <sqlite3.h> int do_insert(int i,char *str,sqlite3 *db); int main(int argc, const char *argv[]) {//創建泵打開一個數據庫sqlite3 *db NULL;if(sqlite3_open("./my.db",&db) ! SQLITE_OK){…

【TI-CCS筆記】工程編譯配置 bin文件的編譯和生成 各種架構的Post-build配置匯總

【TI-CCS筆記】工程編譯配置 bin文件的編譯和生成 各種架構的Post-build配置匯總 TI編譯器分類 在CCS按照目錄下 有個名為${CG_TOOL_ROOT}的目錄 其下就是當前工程的編譯器 存放目錄為&#xff1a; C:\ti\ccs1240\ccs\tools\compiler按類型分為五種&#xff1a; ti-cgt-arm…

2023年排行前五的大規模語言模型(LLM)

2023年排行前五的大規模語言模型(LLM) 截至2023年&#xff0c;人工智能正在風靡全球。它已經成為熱門的討論話題&#xff0c;吸引了數百萬人的關注&#xff0c;不僅限于技術專家和研究人員&#xff0c;還包括來自不同背景的個人。人們對人工智能熱情高漲的原因之一是其在人類多…

CS5263替代停產IT6561連接DP轉HDMI音視頻轉換器ASL 集睿致遠CS5263設計電路原理圖

ASL集睿致遠CS5263是一款DP1.4到HDMI2.0b轉換器芯片&#xff0c;設計用于將DP1.4源連接到HDMI2.0b接收器。 CS5263功能特性&#xff1a; DP接口包括4條主通道、輔助通道和HPD信號。接收器支持每通道5.4Gbps&#xff08;HBR2&#xff09;數據速率。DP接收機結合了HDCP1.4和HDCP…

NVIDIA Omniverse與GPT-4結合生成3D內容

全球各行業對 3D 世界和虛擬環境的需求呈指數級增長。3D 工作流程是工業數字化的核心&#xff0c;開發實時模擬來測試和驗證自動駕駛車輛和機器人&#xff0c;操作數字孿生來優化工業制造&#xff0c;并為科學發現鋪平新的道路。 如今&#xff0c;3D 設計和世界構建仍然是高度…

C#的 Settings.Settings配置文件的使用方法

1、定義 在Settings.settings文件中定義配置字段。把作用范圍定義為&#xff1a;User則運行時可更改(用戶范圍的字段數據更改存儲在用戶信息中&#xff0c;不在該程序文件中)&#xff0c;Applicatiion則運行時不可更改。可以使用數據網格視圖(VS軟件的Properties 下面的Setting…

常見的Redux問題

在React中使用Redux的面試題目通常涵蓋了Redux的基本概念、工作原理、如何在React應用中集成Redux等方面。以下是一些常見的Redux問題&#xff1a; Redux的核心概念&#xff1a; 1、什么是Redux&#xff1f;它解決了什么問題&#xff1f; 它是一個狀態管理庫&#xff0c;解決…

2023國賽數學建模思路 - 復盤:校園消費行為分析

文章目錄 0 賽題思路1 賽題背景2 分析目標3 數據說明4 數據預處理5 數據分析5.1 食堂就餐行為分析5.2 學生消費行為分析 建模資料 0 賽題思路 &#xff08;賽題出來以后第一時間在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 賽題背景 校園一卡通是集…

個保新標 | 《信息安全技術 敏感個人信息處理安全要求》(征求意見稿)發布

8 月 9 日&#xff0c;全國信息安全標準化技術委員會公開發布關于國家標準《信息安全技術 敏感個人信息處理安全要求》&#xff08;征求意見稿&#xff09;&#xff08;以下簡稱《標準》&#xff09;的通知&#xff0c;面向社會廣泛征求意見。 《標準》的制定背景是為支撐《個人…

《Go 語言第一課》課程學習筆記(一)

配好環境&#xff1a;選擇一種最適合你的 Go 安裝方法 選擇 Go 版本 一般情況下&#xff0c;建議采用最新版本。因為 Go 團隊發布的 Go 語言穩定版本的平均質量一直是很高的&#xff0c;少有影響使用的重大 bug。可以根據不同實際項目需要或開源社區的情況使用不同的版本。 有…