鄧正良
(廣東粵電陽江海上風電有限公司,陽江529500)
在當今的互聯網時代,隨著網絡視頻的興起與流媒體通信技術的不斷提高、完善,多媒體數據傳輸成為了日常生活中不可或缺的一部分。網絡視頻傳輸依靠其快捷、即時和分享性等優勢,已經成為當今環境下的主宰,越來越多的平臺需要憑借網絡視頻傳輸實現其功能,例如視頻直播、在線觀影和周界安防監控等[1]。不同的視頻服務提供商使用不同類型的傳輸協議、媒體容器格式以及音視頻編解碼標準。傳輸方式包括HTTP、RTMP、RTSP 等多種媒體傳輸協議[2];媒體容器格式包括FLV、MP4、AVI、TS、RMVB 等;視頻編碼標準[3]包括H.264、H.265、MPEG4、MPEG2 等[4]。為了能夠支持不同的傳輸協議、媒體容器格式以及音視頻編解碼標準,市場上一般常用FFmpeg 和SDL 作為主要的工具庫實現視頻播放的功能。FFmpeg[5]作為一個開放的多媒體框架,支持多種傳輸協議、媒體容器格式以及音視頻編解碼標準,能夠通過其封裝的數據結構來存儲從多媒體數據中提取的信息,有效解決視頻數據的編解碼;SDL[6]全稱為Simple DirectMedia Layer,它作為一套開源應用工具開發庫,依靠其簡潔的接口能夠支持音視頻、事件、繪圖等功能,應用于游戲研發、多媒體影像、圖像處理等領域。基于FFmpeg 和SDL 工具庫,能夠實現高效地實現視頻流播放存儲。
FFmpeg 結構體功能概述[7]如下:
(1)AVFormatContext:作為輸入、輸出相關信息的一個容器,封裝了格式上下文結構體,對相關視頻文件的封裝格式信息進行存儲。
(2)AVPacket:在讀媒體源文件和寫輸出文件時都需要用于存儲每一幀壓縮編碼后的視頻數據。
(3)AVFrame:存儲每一幀解碼后的采樣數據。
(4)AVStream:音視頻流與對該結構體對應。
(5)AVInputForma:封裝格式與該結構體對應。
(6)AVCodecContext:它作為編碼器關聯結構,對視音視頻編解碼相關信息進行了存儲。
(7)AVCodec:音視頻編解碼器與該結構體對應。
上述結構體[8]之間的關系如圖1 所示。

圖1 FFmpeg結構體間的關系
在利用FFmpeg 進行解碼的過程中,主要會用到以下相關函數:
(1)av_register_all():全注冊函數,該函數的作用是初始化工具庫。
(2)avformat_open_input():輸入源打開函數,該函數的作用是打開輸入視頻文件。
(3)avformat_find_stream_info():流找尋函數,該函數的作用是獲取視頻信息,視頻數據包含了媒體數據中的流信息。
(4)av_find_decoder():編解碼器找尋函數,該函數的作用是查找解碼器,包含了id 和encoder 這兩個參數。
(5)avcodec_open2():解碼器打開函數,該函數的作用是打開解碼器,初始化AVCodecContext 結構。
(6)av_read_frame():幀數讀取函數,該函數的作用是將編碼數據從輸入數據中讀取出來。
(7)avcodec_decode_video2():視頻解碼函數,該函數的功能是解碼壓縮數據。解碼視頻流數據包結構體,調用上述提及的幀數讀取函數獲取流,并判斷其是否為視頻流,若為視頻流則執行解碼操作。
FFmpeg 解碼包含了主要的三個解碼模塊[9],分別為解碼協議模塊、解碼格式模塊和解碼視頻和音頻模塊。
(1)解碼協議模塊
媒體流輸入協議數據的協議類型和狀態(例如HTTP、RTMP、RTSP 等類型的傳輸協議)均被存儲于AVIOContext、URLProtocol 和URLContext 中,但由于上述協議中存在對媒體流進行控制的信令數據,該數據會對輸入協議數據產生影響,所以要將其抹去。解碼協議模塊的功能是對輸入數據進行過濾,剔除協議信令數據,將包含協議信息的輸入數據轉換為媒體容器格式數據。
(2)解碼格式模塊
剔除協議信令數據并完成協議解碼的媒體容器格式數據包含了音視頻兩部分數據,解碼格式模塊的作用就是對媒體容器格式數據進行拆分,獨立出音頻編碼數據和視頻編碼數據這兩部分,并將解碼后的兩部分數據分別放入特定格式的文件中。
該模塊主要與AVFormatContext 和AVInputFormat結構體相關,AVFormatContext 存儲包含在媒體容器格式中的信息;AVInputFormat 存儲輸入媒體流媒體容器格式的類型。
(3)解碼音視頻模塊
經由格式解碼拆分獨立的音視頻兩部分數據,仍需要進行進一步操作才能得到未壓縮的數據。解碼視頻和音頻模塊的作用就是將視頻編碼數據和音頻編碼數據分別解碼為未經壓縮的視頻和音頻原始數據。經過解碼處理后的視頻數據被轉換為未壓縮的像素數據,而音頻數據被轉換為未壓縮的音頻采樣數據。
該模塊主要與AVStream、AVCodecContext 和AVCodec 結構體相關,AVStream 存儲了音頻和視頻流信息;AVStream 存儲了AVCodecContext,用于保存音視頻流解碼信息;AVCodecContext 存儲了AVCodec,用于保存音視頻頻解碼器信息。
(1)SDL_Window:該結構表示“窗口”。
(2)SDL_Renderer:該結構表示“渲染器”。
(3)SDL_Texture:該結構表示“紋理”。
(4)SDL_Rect:該結構表示“矩形結構”。
基于SDL 數據結構,視頻顯示原理如圖2 所示。

圖2 視頻顯示原理
SDL 主要通過以下八個函數實現顯示操作:
(1)SDL_Init():系統初始化函數,該函數對整個系統進行初始化操作。
(2)SDL_CreateWindow():窗口創建函數,該函數創建了窗口SDL_Window 結構體變量。它的主要作用是分別是為SDL_Window 結構體分配內存、設置窗口的寬高與坐標位置以及創建窗口。
(3)SDL_CreateRenderer():渲染器創建函數,該函數創建了渲染器結構體變量。
(4)SDL_CreateTexture():紋理創建函數,該函數創建了紋理SDL_Texture 結構體變量。它的主要作用分別是檢查輸入參數合理性、為SDL_Texture 紋理結構分配內存以及創建紋理。
(5)SDL_UpdateTexture():紋理更新函數,該函數主要提供四部分功能,分別是設置相關紋理數據、檢查輸入參數合理性、處理特殊格式以及更新紋理。
(6)SDL_RendererCopy():渲染拷貝函數,該函數的功能是把紋理數據復制給渲染器,主要完成檢查輸入參數合理性以及復制紋理給渲染對象這兩部分操作。
(7)SDL_RendererPresent():渲染顯示函數,該函數的功能是顯示已復制給渲染對象的圖像。
(8)SDL_Quit():系統退出函數,該函數的作用是退出SDL 系統,釋放相關占用資源。
基于FFmpeg 和SDL 的視頻流播放存儲的實現方式如圖3 所示[10]。
視頻流播放的實現步驟主要分為取流、解碼和顯示三部分組成。
(1)視頻流播放
①讀取視頻流地址的配置文件信息,根據配置信息從流地址中讀取實時視頻的數據。
②通過FFmpeg 初始化,獲取媒體源后,尋找視頻流的相關信息,尋找解碼器,并打開解碼器,讀取視頻幀,判斷是否獲取到數據包,存儲壓縮編碼的數據,解碼每一幀視頻數據后,再存儲每一幀解碼后的數據,回到解碼器進行下一幀的處理,直到無法獲取到數據包為止。
③通過SDL 創建窗口,渲染器和紋理,將一幀幀解碼后的視頻貼圖顯示,實現視頻的播放。
(2)視頻流存儲
視頻流存儲的實現步驟主要分為創建、取流、讀幀和存儲四部分組成。
①指定擴展名,輸入存儲文件的名稱,創建文件進行保存,若視頻文件名稱已存在,提示是否覆蓋。
②讀取視頻流地址的配置文件信息,根據配置信息從流地址中讀取實時視頻的數據。
③通過FFmpeg 初始化用于輸出結構體,為視頻流創建Stream 通道。獲取媒體源后,尋找視頻流的相關信息,尋找并打開解碼器。地址的打開方式選擇讀寫狀態,調用頭函數創建函數完成視頻文件頭的寫入。循環讀取并存儲每一幀數據,判斷是否獲取到數據包。
④通過寫視頻文件尾,關閉上下文結構體,釋放輸出結構體,完成視頻存儲。

圖3 視頻流播放存儲過程
本文介紹了基于FFmpeg 和SDL 工具庫,實現視頻流播放的方式,闡述FFmpeg 與SDL 的常用函數及各結構體間的關系,并給出視頻流播放存儲的實現過程。在實際應用中,可以根據支持的不同種傳輸協議、媒體容器格式和音視頻編解碼標準,調整碼率、幀數,實現不同需求的實時視頻流播放存儲,提高在不同情況下的媒體流播放效果。