2019年終總結&小半年流媒體服務器開發經驗總結
文章目錄
時間轉瞬即逝,轉眼間19年已經過去了,卻沒見自己心智有多大提升,期望2020年能有一個嶄新的自己。
19年因爲發展原因,從嵌入式轉行做互聯網了,幹了一年突然轉行,自己也很糾結這麼做對不對,但我想得很清楚我只是想做自己想做的事情,從事自己想做的工作。
雖然意想不到的的是後期的工作並不算真正意義上的後端工作,用到的後端技術並不多,主要時間掙扎在了流媒體的開發,音視頻的編解碼,濾波、硬件加速、rtp、音視頻格式(aac、pcm、h264)、另外主管選擇了開源的ffmpeg庫、都偏向於流媒體開發,於是本着邊學邊工作的心理過了今年。。
19年開發小結
流媒體服務框架設計
基於公司項目需求設計流媒體服務器,考慮到擴展性,分離了業務與音視頻處理,便於後期音視頻處理服務能做水平擴展,同時還能更替音視頻處理方案。
登錄服開發
基於公有協議棧,管理用戶登錄、緩存、出入房間動態等等、迎合docker特點、單獨用戶數據庫緩存。
ffmpeg音視頻合成服務開發
主要包括音視頻相關內容的開發,也是開發投入時間最長的服務,主要經歷了以下過程。
1)、最初基於ffmpeg的h264軟解實現服務demo.
2)、然後嘗試基於英特爾vaapi硬件加速驅動做h264硬解,對解碼視頻幀做軟件overlay濾波進行音視頻合成,再做vaapi硬編碼。
優點是此流程軟件實現上會更爲簡潔快速,也比較穩定。
但是後面發現系統會時不時crash。從系統日誌上沒有找到相關日誌,於是進行了長期的軟件模塊排除法檢查問題原因,做了解碼、解碼+濾波、濾波+編碼等單元模塊組掛機測試,仍然無法找到系統crash問題原因。
同時也在微軟的github倉庫提交了bug/issue,但是回覆較慢。
3)、起初懷疑是vaapi驅動問題,於是嘗試使用英特爾qsv硬件加速驅動硬解,對解碼視頻幀做硬件overlay_qsv濾波進行音視頻合成,再做qsv硬編碼。
優點是音視頻處理全交付於gpu處理,省下大片cpu時間。缺點是硬件幀上下文關係密切,做視頻自動切換上,需要做更多軟件處理,編碼上略微複雜。
可惜系統crash問題依然存在。
4)、ffmpeg原生工具命令行測試系統crash問題,發現確實有這個問題,而且更換很多個ffmpeg版本都會出現,只是概率可能會有浮動:快的幾分鐘到幾小時crash、慢的一星期可能不會出現,但是不改任何參數再次嘗試依然可能crash.
5)、移植音視頻服務從linux到windows系統下、經過長期測試windows下運行intel加速方案確實沒有系統宕機問題了、同時因爲登錄服基於muduo庫實現,移植複雜,改將登錄服打包進docker容器運行。
6)、完善及優化音視頻服務框架及功能。包括:添加適當rtp緩存解決公網環境udp包波動問題、添加音視頻同步機制、增加相應業務功能接口。
7)、移植到微服務框架,進一步增強程序擴展性。
流媒體服務開發小結
音視頻合成服務簡要組成模塊如下
rtp處理
rtp手解,沒有用ffmpeg的avformat庫,這樣我更便於管理網絡處理部分。
需要注意的是udp在公網上可能存在網絡抖動問題,開一塊rtp緩存消抖,按seq做最小堆。我直接用的golang的heap包實現。
然後將音頻包和視頻包按相對時間戳,做堆排序放進一塊rtp緩存裏(av_rtp_handler),這樣是爲了方便做音視頻同步,記錄第一個到達的rtp包時間戳、後續rtp的相對時間加上timestamp的增量換算來的時鐘,然後用來標誌這個包的相對時間。
例如 h264 90000的時鐘 、 30的幀率 、 : 假如第一包爲3000時間戳增量 那麼本包相對時間假定爲 3000 / 90000 = 33.33333ms, 假如下一包增量也是3000,第二包相對時間就是33.3+33.3 = 66.6ms依此類推。
rtp時間的同步 : av_rtp_handler裏所有rtp包都帶了我一個換算出來的相對時間戳的、 只需要將音視頻的包做一次最小堆插入、每次取堆頂時間戳最小的rtp包即可、 是音頻包就丟進音頻解碼器、視頻包就丟進視頻解碼器。
視頻的合成&音視頻緩衝區
我想了很久音視頻進行合成結構後發現有一個很重要的東西、那就是音視頻幀的緩衝區、而且這個緩衝區真的很重要、它能做到以下效果:
1、控制幀率
2、解決多路流的音視頻幀抖動問題
1、 通過一個定時器、你能很方便的控制幀率、例如隔33ms往合成器發送一組音視頻幀進行合成即可。修改幀率你只需要更改定時器的種子值。
2、消抖、每路流到達的時間肯定是不穩定的、可能通道3一下子除了了5包數據、一下子來了10幀數據、而其他路還只有1到2幀或者沒有、但是你要保持實時性肯定不能把所有幀全部保存下來、所以你必須控制每路的緩衝大小得把擠出來的非I幀刪掉、注意是非I幀
不然可能會花屏。
然後就是音視頻合成、音頻用amix、視頻用overlay、
視頻幀合成麻煩在qsv有一個硬件幀上下文、qsvframecontext
每次做屏幕的自動切換
、或者屏幕位置交換
、需要重新生成filter、而你就需要費工夫去更新這個qsvframecontext
極爲麻煩、後面想到的方法是設計一張ffmpeg filter輸入的映射
就是在不該ffmpeg濾波器描述符的情況下、而是直接交換filter的輸入位置。
一個草圖將就下: 相當於 就是打亂正當的輸入順序、做一張映射、這樣子不用更改filter的描述符即可做濾波器的切換,要便利極多!
音頻合成沒什麼好說的、就是每路的輸出可能不能包含自己的通道聲音、不然可能存在迴音、可以弄個集合記錄需要合成的流輸入id的集合、合成的時候把自己的id去掉再合成就行了,還有不同編碼合成可以做重採樣來保證filter輸入的一致性。
音視頻編碼
編碼好像沒什麼重點東西、打包的時候打上個合適的時間戳即可。
音視頻部分功能演示
因爲csdn限制了gif的大小爲5m,博客園好像是10m,所以用ffmpeg壓縮了大小和幀率,比較模糊。
首先5個推流客戶端
用了兩個demo視頻
測試用推流工具: https://github.com/ailumiyana/streaming
5條命令行一直往新建音視頻服務的音視頻端口發rtp數據
5對流的登錄
配置登錄流的地址,流標識依次爲 “ai“, “lu”, “mi”, “ya”, “na“
同時自動合成一條流,並用ffplay播放出來。
流的位置交換
演示中
流0和流1交換了幾次位置
流0和流4交換了幾次位置
流刪除
依次刪除 用戶 “ai“, “lu”, “mi”, “ya”, 四條流
流登錄
再次將 “ai“, “lu”, “mi”, “ya”, 四條流登錄回去
這裏演示了上文流媒體服務的幾個基本功能,其他還包括設置固定展示流合成數量(例如只展示2個畫面)、單條流音頻開關、加水印等等。 其實都只需要切換下濾波器即可,對ffmpeg而言就是一個 filter 描述符。