webrtc-自定義視訊流-實現篇(基於release-54)
我所使用的webrtc是release-54版本,後續我也看過release-56版本的程式碼,發現有許多的程式碼差別很大,所以如果版本不同很大可能無法直接使用程式碼,請注意。我所開發的webrtc是基於centos7進行開發的,所以如果我沒有特意標註的情況下都指的是linux下的webrtc,看官請知悉。
總記
根據上一篇所說,我所使用的方法就是“碼流偽裝法”(自稱)。本質是通過在碼流打包傳送的位置偽裝成編碼器傳送過來的視訊流,藉助後續的rtp_rtcp模組(負責打包傳送RTP的模組)將視訊傳送出去。我們的主角是一個叫做VideoSendStream的類(webrtc\video\video_send_stream.cc),Webrtc裡面有很多名字很接近的類你可能會暈乎乎的。這個類負責的工作就是接收編碼器傳送過來的資料,在將資料推送給rtp_rtcp模組。
步驟
1.整體介紹
類方法:
EncodedImageCallback::Result VideoSendStream::OnEncodedImage(
const EncodedImage& encoded_image,
const CodecSpecificInfo* codec_specific_info,
const RTPFragmentationHeader* fragmentation)
就是原本webrtc的編碼器在幀編碼完成後的回撥介面。這個方法一共需要三個引數:
1.encoded_image
視訊的各種資料,包括資料buffer,資料大小,視訊高寬和時間戳等等。
2.codec_specific_info
視訊資料型別,我用的是webrtc::kVideoCodecH264,還有比如kVideoCodecVP8等型別
3.fragmentation
視訊資料分片頭資訊,用於RTP的包內部多個幀的提取。(如果我沒記錯的話,vp8這個引數是空的。目前我沒有驗證手段。)
閱讀一下這個函式的程式碼,就會發現,他的核心其實是這句:
EncodedImageCallback::Result result = payload_router_.OnEncodedImage(
encoded_image, codec_specific_info, fragmentation);
這個 payload_router_
所以我們只要能夠將我們的視訊資料封裝成上面三個引數的形式,再直接呼叫這個 payload_router_.OnEncodedImage 方法,就可以偽裝成編碼器返回的視訊資料傳送出去了。
2.EncodedImage和CodecSpecificInfo
EncodedImage的成員比較通俗易懂。CodecSpecificInfo也就是簡單的一個引數,我們就放在一起說。
下圖中的m_image就是一個EncodedImage,一些需要處理的成員主要如下所示:
m_image_._encodedWidth = width;
m_image_._encodedHeight = height;
m_image_._size = CalcBufferSize(kI420, m_image_._encodedWidth, m_image_._encodedHeight);
m_image_._buffer = buffer;//資料區指標
m_image_._completeFrame = true;
m_image_._length = buffer_length;//資料長度
m_image_._timeStamp = time_stamp;//時間戳
//m_image_.capture_time_ms_ = time_stamp;//視訊捕獲時間,並沒有什麼用
m_codec_specific_info_.codecType = webrtc::kVideoCodecH264;
這些引數名大部分簡單粗暴,一眼就能知道他是幹什麼的。需要注意的主要是以下兩個:
timeStamp(時間戳)
時間戳對於實時流來說簡直是心跳一樣的存在。時間戳異常的話,輕則播放紊亂,重則喪失播放能力!但是其實我實測過,只要你的幀率生成且網路穩定的話,這個時間戳只要是遞增的就可以,沒有什麼要求。當你設定為簡單加一遞增時,視訊會一到達就播放,所以視訊播放會取決於網路和編碼情況。如果你要精準的控制時間的話可以用clock_->CurrentNtpInMilliseconds() * 90
。官方程式碼就是這麼計算時間戳的(雖然我已經找不到在哪。。。所以一開發還是用Windows好啊,斷點除錯,呼叫棧一看,清清楚楚)。
size(緩衝區空間大小)
這個size的值其實並沒有什麼關係,這個是buffer的最大值,所以要是設定大一點頂多是浪費空間。這裡我用到的CalcBufferSize是在webrtc\modules\video_coding\codecs\h264\h264_encoder_impl.cc裡面的一個計算方法。這個檔案裡面就是原本webrtc中EncodedImage包裝的地方,我就是直接仿造這個裡面的函式H264EncoderImpl::Encode實現的,如果有什麼不清楚的話可以直接看這個檔案。
3.RTPFragmentationHeader
上面說過RTPFragmentationHeader是要儲存H264的分幀情況的頭,那為什麼需要這個頭呢?我們先簡單介紹一下webrtc裡面預設生成的H264視訊流的格式
webrtc預設使用的H264格式如上圖所示,視訊幀只有I幀和P幀。每個I幀之前都會附帶一對SPS和PPS資訊(我猜測是由於怕網路傳輸丟失,所以需要注意補充視訊資訊)。原始webrtc會將SPS、PPS和I幀三個幀一起打包給RTP_RTCP模組。為了接收端接收到資料後可能將這三個資料快速區分開來,所以需要加上這個頭資訊。
我們知道H264是通過“001”或者”0001”的起始碼來區分幀的。而webrtc並不需要這些起始碼,所以我們需要將每幀的真實資料的開頭位置賦值給fragmentationOffset,再把每幀的長度賦值給fragmentationLength。
生成這個頭資訊的程式碼如下所示,這裡我已經將視訊資料的資訊收集到了m_fram_info裡面:
RTPFragmentationHeader frag_header;
RTPFragmentationHeader* frag_p = &frag_header;
int offset = m_fram_info->startcodeprefix_len;
if (m_fram_info->nal_unit_type == 1) {//判斷為p幀
frag_p->VerifyAndAllocateFragmentationHeader(1);//申請一個幀的頭
frag_p->fragmentationOffset[0] = offset;//設定純資料的起始位置
frag_p->fragmentationLength[0] = buffer_length - offset;//設定視訊資料長度(去除起始碼)
m_image_._frameType = kVideoFrameDelta;
}
else if (m_fram_info->nal_unit_type == 7) {//判斷為I幀
frag_p->VerifyAndAllocateFragmentationHeader(3);//申請3個幀的頭
frag_p->fragmentationOffset[0] = offset;//SPS偏移位置為起始碼長度
frag_p->fragmentationOffset[1] = m_fram_info->SPS_length + offset;//PPS偏移位置為SPS的完整長度加PPS起始碼長度
frag_p->fragmentationOffset[2] = m_fram_info->SPS_length + m_fram_info->PPS_length + offset;//I幀偏移位置為前兩幀的長度加I幀的起始碼長度
frag_p->fragmentationLength[0] = m_fram_info->SPS_length;
frag_p->fragmentationLength[1] = m_fram_info->PPS_length;
frag_p->fragmentationLength[2] = buffer_length - m_fram_info->SPS_length - m_fram_info->PPS_length;
m_image_._frameType = kVideoFrameKey;
}
當然,你也能猜到。這個頭不只是這麼用,你可以將好幾幀打包到一個包裡面一起傳送,只要你能把頭資訊給處理清楚。不過由於我們的場景對於實時性要求極高,所以不能在這邊等待多幀一起傳送。
自此我們就將視訊資料封裝完了。再下來你只要呼叫
payload_router_.OnEncodedImage(m_image_, &m_codec_specific_info_, frag_p);
將資料丟出去就大功告成了。等等,好像還沒有,那原本的攝像頭資料怎麼辦呢?
4.去除原生捕獲
Webrtc的集體功能的程式碼其實都集中在目錄 webrtc
中
這個目錄裡面的結構我就不一一解釋了,只提幾個咱們用到的的:
資料夾名 | 功能 |
---|---|
examples | demo程式 |
call | 各元件建排程核心模組 |
video | 視訊相關模組 |
進入目錄
webrtc\examples\peerconnection\client
這個目錄存放的就是peerconnection_client程式的業務層程式碼。
我們的開發在這個peerconnection_client上進行修改。
webrtc\examples\peerconnection\client\conductor.cc
整個上層業務的程式碼都在這個檔案裡面實現。我們就需要在這個檔案裡面新增音視訊的資訊。這檔案中有我們的好朋友Conductor::AddStreams
,相信看過webrtc原始碼的人都知道這個函式,且對這個函式研究了很久。
一開始我一直企圖在這個VideoTrack上面做手腳來完成自定義視訊流。最後被盤根錯節的程式碼打的服服帖帖,直接把整個函式都刪除了,那我們要如何在這裡新增視訊傳送的引數而不啟動原生的捕獲呢?答案是:
void ConductorTest::AddStreams() {
peer_connection_->CreateSender("video","1");//lyn 2016/9/28
peer_connection_->CreateSender("audio","1");
}
其實這個Sender是沒有什麼實際功能的,這裡新增這兩個的作用只是為了在生成offer的時候讓offer中帶上video和audio的資訊。peerconnection在處理offer的時候發現有視訊資訊,就會去生成VideoSendStream物件了。
下面的圖是VideoSendStream的引用持有順序,如果你要檢視他去Call.cc
。如果你要在Conductor
操作他,也可以這樣一路通下去。
結語
現在你關閉掉了原生視訊捕獲,在視訊傳送裡,你已經可以無法無天了。我推薦你可以先從檔案裡面讀入視訊檔案來測試傳送。我是llsxily,一個曾經看webrtc看到哭的人,你可以叫我橘子。
相關推薦
webrtc-自定義視訊流-實現篇(基於release-54)
我所使用的webrtc是release-54版本,後續我也看過release-56版本的程式碼,發現有許多的程式碼差別很大,所以如果版本不同很大可能無法直接使用程式碼,請注意。我所開發的webrtc是基於centos7進行開發的,所以如果我沒有特意標註的情況下都
自定義SurfaceView實現抽獎轉盤實戰篇
看到一個抽獎的效果,最近正要寫個自定義的View就用這個練一下好了 不多說先上圖,因為我這主要是實現了思路,所以UI做的不是很好看,後續我會補上,看是否能滿足你的需求: 專案git地址 思路解析 1.首先需要看仔細看一下,抽獎是什麼流程,拆分業務流程。 2.分析
[轉]自定義Drawable實現靈動的紅鯉魚動畫(上篇)
此篇中的小魚動畫是模仿國外一個大牛做的flash動畫,第一眼就愛上它了,簡約靈動又不失美學,於是抽空試著嘗試了一下,如下是我用Android實現的效果圖: 小魚兒
《SpringBoot學習篇》(5)AOP+自定義註解實現日誌管理
用到的AOP註解:@Aspect @Pointcut @After 首先看一下如何呼叫自定義註解: @MyLog(module="老師模組", method="查詢全部") @RequestMapping("/all") public List
Android 自定義ViewGroup 實戰篇 -> 實現FlowLayout
1、概述上一篇已經基本給大家介紹瞭如何自定義ViewGroup,如果你還不瞭解,請檢視:,本篇將使用上篇介紹的方法,給大家帶來一個例項:實現FlowLayout,何為FlowLayout,如果對Java的Swing比較熟悉的話一定不會陌生,就是控制元件根據ViewGroup的
自定義ActionResult實現Rss輸出 (基於ASP.NET MVC Preview 3)
前兩天才做了一個Asp.Net MVC Preview2的實踐,沒想到這就升級到了Asp.Net Preview3了,Preview3確實比2好上不少,特別有兩個地方值得注意,一是Route新增了MapRoute方法,可以更方便新增Url路由規則,二是修改了View的部分,使得Action統一返回
資料脫敏——基於Java自定義註解實現日誌欄位脫敏
上文說了資料過敏主要有兩個思路:第一個就是在序列化實體之前先把需要脫敏的欄位進行處理,之後正常序列化;第二個就是在實體序列化的時候,對要脫敏的欄位進行處理。 脫敏實現思路 這裡探討第一種方法,用基於自定義註解的方式實現日誌脫敏。 要對
手把手教你如何運用強大的谷歌自定義搜尋功能來實現你的自定義搜尋站之用谷歌自定義搜尋實現網盤搜尋引擎第一篇
今天給大家講解一下如何運用谷歌自定義搜尋功能來實現你自己的個性化搜尋網站,我們以實現網盤搜尋引擎為例進行展開說明,學會了這個,你可以做搜尋任何東西的網站,不僅僅是網盤資源搜尋引擎啦!對了,要用這個功能
J2EE專案使用自定義註解實現基於SpringMVC + Mybatis + Mysql的讀寫分離
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/
QT 自定義混合控制元件——基於View/Model/Delegate的QTableView/QTreewidget/Combobox實現
QT自定義控制元件,檢視本文章需要具備一定的View/Model基礎知識(沒有也可以看)。本文實現了在一個QTreeWidget中插入一個QTableView,然後再在QTableView的每一個item中插入一個QCombobox,當然知道了方法你可以在任意item中插入任
mininet實現自定義拓撲結構--基於兩個資料中心的網路拓撲--fattree.py
前言 基於兩個資料中心的網路拓撲是最常見的、也是最基本的一種網路拓撲結構,這裡先拿這個作為練習。 其網路拓撲結構圖如下: 一、首先編寫生成拓撲結構的python程式碼fattree.py檔案 #建立網路拓撲,程式碼可以直接使用 from minine
Android -- 自定義view實現keep歡迎頁倒計時效果
super onfinish -m use new getc awt ttr alt 1,最近打開keep的app的時候,發現它的歡迎頁面的倒計時效果還不錯,所以打算自己來寫寫,然後就有了這篇文章。 2,還是老規矩,先看一下我們今天實現的效果 相較於我們常見的倒計時
Android自定義View——實現水波紋效果類似剩余流量球
string 三個點 pre ber block span 初始化 move 理解 最近突然手癢就想搞個貝塞爾曲線做個水波紋效果玩玩,終於功夫不負有心人最後實現了想要的效果,一起來看下吧: 效果圖鎮樓 一:先一步一步來分解一下實現的過程 需要繪制一個正弦曲線(sin
Android自定義processor實現bindView功能
lis dds 定義 java代碼 cli 註冊 文章 type() mage 一、簡介 在現階段的Android開發中,註解越來越流行起來,比如ButterKnife,Retrofit,Dragger,EventBus等等都選擇使用註解來配置。按照處理時期,註解又分為兩
自定義toast實現
web javascript html5 toast ys_toast.css.ys-toast{ position:fixed; left:0; right:0; top:0; bottom:0; z-index: 999999; } .ys-toast>em{ pos
SpringVC 攔截器+自定義註解 實現權限攔截
json.js 加載 bean media tar attr esp 權限 encoding 1.springmvc配置文件中配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w
自定義ScrollView 實現上拉下拉的回彈效果--並且子控件中有Viewpager的情況
是否 AS abs pri tar utils lda animation ted onInterceptTouchEvent就是對子控件中Viewpager的處理:左右滑動應該讓viewpager消費 1 public class MyScrollView ext
[python]RobotFramework自定義庫實現UI自動化
bubuko output source 自動 封裝 9.png 全局變量 詳細 變量 1.安裝教程 環境搭建不多說,網上資料一大堆,可參考https://www.cnblogs.com/puresoul/p/3854963.html,寫的比較詳細,值得推薦。目前pyt
NPOI+反射+自定義特性實現上傳excel轉List及驗證
type set custom pre script private property xssf don 1.自定義特性 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited
Android bc信用盤搭建自定義behavior 實現上滑 隱藏底部view
退出 Y軸 log rect app sum string dsl oss 布局 <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent"