音視頻推流端 OBS 框架學習和源碼分析
1.獲取混音后的音頻數據:
接口: ( *audio, , )
注釋:清空所有混音器audio->mixes中的每個混音器的buff,并逐個將混音器的每個聲道buff指針賦值給局部變量 data[],調用已綁定的回調函數 audio-> (audio->, , , &, , data)獲取新的音頻數據,通過修改局部變量data中各個指針的buff內容,來完成修改 audio->mixes 中的每個混音器的buff內容,audio->回調函數綁定的接口為 obs-audio.c 中的 ;
2.各個音頻遍歷:
接口: (void *param, , , *, , *mixes)
注釋:獲取音頻碼率 ,音頻聲道 ,遍歷當前場景中的所有源( ),加入到音頻的渲染隊列 audio-> 中 (NULL, , audio);以及 audio-> 中, ( audio-> , & );把系統自帶的音頻輸入輸出音頻源(如音響和麥克風)加入到渲染隊列 audio-> 中, = data->;
while () {
(NULL, , audio);
= ( *)->;
}
循環調用接口 der 對渲染隊列中的所有源做音頻數據渲染,每個渲染完成的音頻數據存放在 -> 中;
C++音視頻開發學習資料:點擊領取→音視頻開發(資料文檔+視頻教程+面試題)(++RTMP+RTSP+HLS+RTP)
3.各個音頻渲染:
接口: void der( *, , , , size)
注釋:如果綁定了音頻渲染器,則調用:
void ( *, , , )
將源的音頻輸出buff指針賦值給局部變量
.[mix].data[ch] = ->[mix][ch];
調用->info.回調函數,填充中各個buff的內容,調用完成后->中的音頻數據填充完成,該回調綁定接口為obs-scene.c中的(),具體是如何獲取的沒再細看,否則調用_tick:
void _tick( *, , , , size),這個就比較簡明了在線視頻制作系統源碼,將->中各個聲道的數據拷貝到->中,設置音頻輸出音量大小,,執行完成,返回,繼續下一步
4.處理混音:
接口:
注釋:處理混音:調用(),根據audio->隊列,循環處理將->中的音頻數據添加到每個與其相關的混音器中,傳入混音器集合的數組,當前源,聲道,音頻采樣率,時間戳
void ( *mixes, *, , , *ts)
for ( = 0; < ; ++) {
for ( ch = 0; ch < ; ch++) {
float *mix = mixes[].data[ch];
float *aud = ->[][ch];
float *end;
mix += ;
end = aud + ;
while (aud < end)
*(mix++) += *(aud++);
}
}
混音處理完成后,即得到所有混音器中的已處理完成的混音數據,用于接下來進行的音頻編碼以及推流,回調到步驟1中obs-audio.c 中的 ;
5.獲取混音數據輸出
接口:
注釋:執行完成audio->,并得到混音器的完整數據后,執行函數 取締混音器中的所有數據為[-1.0,1.0],
void ( *audio, bytes)
for ( plane = 0; plane < audio->; plane++) {
float * = mix->[plane];
float * = &[];
while ( < ) {
float val = *;
val = (val > 1.0f) ? 1.0f : val;
val = (val < -1.0f) ? -1.0f : val;
*(++) = val;
}
}
遍歷所有混音器,調用 接口,輸出音頻數據:
void ( *audio, , , )
將當前混音器的所有聲道的buff指針強轉成*,賦值給局部變量 data,更新data中的 和 時間戳
對數據進行重新采樣t,
mix->數據存放的是輸出個數,包括錄像和推流,兩個輸出類型綁定的輸出回調函數不同,當前是以推流為例
調用輸出回調函數input->進行音頻數據輸出,推流綁定的接口為中的
以上是獲取混音完成后的數據,接下來是音頻編碼內容。
6.接收并處理音頻數據
接口: void (void *param, , *data)
判斷音頻第一次數據是否收到,收到第一次數據后,清空->
注釋:判斷音頻第一次數據是否收到,收到第一次數據后,清空->
if (!->) {
-> = data->;
-> = true;
();
}
調用 將音頻數據添加到的音頻數據緩存
bool ( *, *data)
檢查視頻數據是否已經收到,沒有收到視頻數據,不發送音頻數據
= ->->;
/* no video yet, so don't start audio */
if (!) {
= false;
goto fail;
}
檢查音視頻時間戳是否同步,下一幀音頻數據的時間戳小于當前視頻時間戳,說明音頻數據慢了,不發送音頻數據
+= ()data-> * /
()->;
if (
(, data ,size ,)
音頻數據大小大于一幀的音頻數據大小時,調用 發送音頻數據
void ( *)
取出音頻數據,存放在局部變量 中,調用 執行推流前的音頻編碼
void ( *, *frame)
創建待推流的數據包 pkt = {0}; 初始化數據包的基礎數據,包括計算幀率的分子和分母,編碼器
調用編碼器的回調函數進行編碼->info.,當前例子中調用的時aac編碼器,回調接口為模塊obs-.dll 中的obs--audio-.c
7.
接口: bool (void *data, *frame, *, bool*)
注釋:frame中的音頻數據拷貝到 -> 中,調用 接口編碼
bool ( *enc, *, bool *)
調用接口,對音頻數據進行編碼,編碼完成后數據存放在 中,再拷貝到enc->,并將的指針,賦值給待推流發送的數據包->data,設置中的相關參數,pts時間戳,dts編碼時間戳,size編碼后數據大小,時間基的分子和分母,此時已完成了音頻數據的推流所需的編碼處理,得到的即將用于音頻數據推流
接口:
注釋:完成編碼回調,使用系統時間而不是使用相對時間修改完成編碼的pkt中的和
隨后,根據->.num循環調用,至于這個為什么會是多個,以及什么情況下會是多個,暫時猜測是包含推流和錄像的回調
void ( *, *cb, *)
這里在發送數據包之前,需要對視頻的第一幀發送進行單獨處理
如果發送的視頻第一幀,調用接口ket,在編碼數據的前段添加sei信息,然后再調用cb->回調函數,否則直接調用回調結構體中的cb->接口,該回調接口綁定至obs-.c ,并且這個回調是音頻和視頻通用的回調 就是說音頻和視頻數據的推流都是在這里執行數據包的發送。
8.獲取編碼數據并發送
接口: void (void *data, *)
注釋:獲取當前數據包對應的混音器的index并賦值給->,這個后面推流的時候會用到,
調用(&out, );
拷貝中的數據到局部變量out中,其中進行的時候,多申請了一個long類型長度的內存,這個pref是這個數據包的引用計數器
如果音視頻數據都收到時,調用,這個函數是調整時間補償或者時間修復的嗎?
否則調用接口將當前的音頻或視頻已收到標識設置為true
= -> && ->;
......
if ()
(, &out);
else
(, );
根據編碼時間戳,將當前數據包插入到輸出隊列中在線視頻制作系統源碼,并將->設置為當前數據包的編碼時間戳
acket(, &out);
(, &out);
如果當前是否第一次收到了音頻以及視頻數據包,調用ckets()對數據包中的內容進行修剪,修剪規則如下:
先找出第一幀音頻和第一幀視頻的數據包,以第一幀視頻數據包的index為基準,對比兩個數據包的時間戳的差值:
如果音頻數據包的時間戳減去視頻數據包的時間戳的數值大于每幀視頻間隔的時間差
那么需要刪除這個音頻數據包時間戳之前的所有音視頻數據包
如果沒有找到這樣的音頻數據包,那么就需要找出音視頻數據包的時間戳差距最小的那個數據包的index,如果這個index的值比第一幀視頻數據包的index小
那么需要刪除這個index之前的所有數據包,如果比第一幀視頻數據包的index大,那么需要刪除第一幀視頻數據包之前的所有數據包
通過對第一次發送的音視頻數據包的裁剪后,當前的待發送數據包中第一幀音視頻數據包的時間戳的差距最小,以達到首次發送的音視頻數據是同步的
調整修正完成后的待發送數據包相關的時間戳,再次確保發送的第一幀音視頻數據包的準確性,并且重新調整待發送數據包的index
調用發送數據包函數
如果是后續收到的音視頻數據包,則直接調用發送函數
if (-> && ->) {
if (!) {
if (ckets()) {
if (()) {
();
();
}
}
} else {
();
}
}
void ( *)
確認待發送數據包中的第一個數據包時間戳是合法的
if (!ts(, &out))
;
把第一個數據包從隊列中移除
(->, 0);
如果是視頻數據包的話,在這里統計總的發送幀數
if (out.type == ) {
->++;
調用->info.回調函數,回調到rtmp發送數據包接口中
9.推流輸出
接口: void (void *data, *)
注釋:判斷推流連接是否斷開以及是否處于活動狀態,將數據包的數據拷貝至局部變量,數據包的引用技術+1
將數據包添加到待推流數據塊中,視頻數據包: ,音頻數據包: ,其中視頻數據包在添加之前,檢查是否有需要丟棄的幀,檢查完成后也是調用, ,將數據包追加在->的尾部
添加成功后,喚醒信號量->,該信號量控制推流發送數據包,喚醒成功后在線程函數中執行發送數據包的操作,執行之前,調用將數據封包為flv格式
void *(void *data)
while ((->) == 0)
......
(, &, false, .