基於FFmpeg和Qt實現簡易影片播放器

KanHai1024發表於2023-11-25

VideoPlay001

記得一鍵三連哦

  • 使用qt+ffmpeg開發簡單的影片播放器,無聲音
  • 影片解碼使用的是軟解碼即只用CPU進行QPainter繪製每一幀影像,CPU佔用過高
  • image
  • 簡單易學,適合小白入門學習FFMpeg影片解析的基本API

遺留問題

  • 影片播放時間的處理,基勻速播放的實現原理

專案程式碼

videoPlay001分支

專案警告

  • 注:博主本人學習過程的分享,引用他人的文章皆會標註原作者
  • 注:點名某初生DN,未來是屬於開源的
  • 注:本人文章非盈利性質,若有侵權請聯絡我刪除
  • 注:聯絡方式Q:2950319782
  • 注:博主本人很菜,文章基本是二次創作,大佬請忽略我的隨筆
  • 注:我會一步步分享實現的細節,若仍有問題請留言,還可以問ChatGPT

專案引用

問題解決

開發環境

  • 系統:Win10
  • Qt:5.14.2
  • 編譯器:qtcreator 4.11.1, minGW64
  • ffmpeg: 5.12

實現功能

  • 使用ffmpeg音影片庫軟解碼實現影片播放器
  • 支援開啟多種本地影片檔案(如mp4,mov,avi等)
  • 支援解析多種網路影片流(如rtsp,rtmp,http等)
  • 支援影片勻速播放
  • 採用QPainter進行影像顯示,支援自適應視窗縮放
  • 影片播放支援實時開始,暫停,繼續播放
  • 採用模組化程式設計,影片解碼,執行緒控制,影像顯示各功能分離,低耦合
  • 多執行緒程式設計

實現邏輯

  • 程式主邏輯
  • image
  • ffmpeg軟解碼流程
  • image

專案實現

專案結構

image

專案思路

頁面搭建

  • 需要實現影片的播放,那麼要先要有個頁面,使用qtcreator設計介面
  • 不是很複雜的頁面我們直接使用自帶的ui介面即可
  • image
  • 博主使用的是minGW64編譯
  • 我們要有一個combo box來獲取網路影片流的URL或者本地影片流的地址
  • 還需要一個開啟檔案的按鈕,來打卡檔案對話方塊,選擇影片檔案
  • 還需要一個開始播放的按鈕和一個暫停播放的按鈕
  • 至於QPainter繪製解碼出來的每一幀圖片我們自定義一個PlayImage控制元件,來顯示影片
  • PlayImage繼承自Widget,記得把ui檔案中的PlayImage控制元件提升為自定義的控制元件
  • 最終的ui圖
  • image

自定義控制元件

  • 一步步來,我們先實現自定義的控制元件來解決影片的功能
  • 先建立一個CPP類PlayImage繼承自QWidget
  • 為什麼不用QLabel顯示圖片呢,因為顯示靜態的圖片還可以,但是如果像影片這樣頻繁的更新圖片,會使程式變得異常卡頓,因此使用QPainter重繪每一幀影像來實現影片播放的功能
  • 這個Demo圖片資料量比較小,完全可以實現,基本的邏輯就是不斷更新影像路徑,一直重繪,直到沒有影像傳進來
  • 其實這裡自定義控制元件應該是單獨封裝的,作為外部檔案引入,方便複用,但這裡為了簡單,還是直接寫入程式碼吧
  • 在這裡封裝了兩個主要的方法updateImage和updatePixmap,解碼出的每一幀圖片只需要呼叫對應的影像更新方法就可以實現影片顯示

影片解碼實現

  • 到這裡,已經有影片播放的功能了,現在需要把影片的解碼完成出來
  • 到這裡呢,其實建議把影片解碼封裝成一個單獨的功能,使用pri引入即可
  • 這裡呢,直接建立一個不帶ui檔案的pri就可以,我是直接在原始檔目錄下新建了一個play檔案了,新建一個videoplay.pri檔案,再修改一下主工程的pro檔案,引入即可
  • 在這個子工程中,新增一個純cpp類,videoDocode,實現影片解碼主要功能
  • 影片解碼按照上面的流程圖實現即可,當然需要先引入FFMpeg的l相關檔案,這裡建議以外部檔案引入,我是放在videoplay.pri目錄下,新建一個ffmpeg資料夾,再更改一下pri檔案,引入即可
影片解碼
  • 引入avformat解封裝模組,先來把封裝的格式剝去
  • 這裡呢因為使用了FFmpeg,建議還是寫c風格的程式碼,省的報錯
  • 先定義一個通用的處理錯誤的函式errHandle
  • 然後在open函式中實現解析影片流,剝去封裝格式,然後讀取影片流獲取資訊
  • 然後我們發現需要手動釋放一些資源,自己定義一個free函式,先釋放解封裝上下文,後續還需要釋放什麼資源自己新增到free函式里就可以了
  • 下面的小邏輯都在程式碼裡以註釋的形式表示了,不寫了,太累了
  • 這裡完了之後,其實就是把影片解碼的功能封裝好,供下面的讀取執行緒呼叫,因為都寫在子執行緒的run函式里太複雜了,所以這裡單獨處理影片解碼
影片解碼執行緒
  • 這裡為什麼需要有一個執行緒類呢,因為在qt設計中,視窗的控制和各種功能的後臺實現應該是不同執行緒處理的,否則都給視窗執行緒處理,這個程式會變得異常卡頓
  • 直接建立一個readThread類繼承自QThread類,這裡基本是按照流程圖呼叫影片解碼的函式即可,並返回給視窗執行緒相應的值,下面要定義幾個public的介面供視窗執行緒呼叫
  • 主要是處理重寫run函式,在cpp檔案中引入videoDocode,並例項化物件,呼叫影片解碼功能
  • 先呼叫videoDocode的open函式,
  • 需要一個自定義訊號playState來與視窗執行緒傳遞資訊,處理播放狀態
  • open成功後開始呼叫videoDocode的read函式
  • 還需要處理影片暫停pause的功能,這裡需要先實現sleep延時操作
  • 影片解碼是一個相對耗時的操作,不能影響視窗執行緒,因此解碼執行緒應該是非阻塞延時
  • 成功open和read後,需要關閉執行緒close了
  • 下面我們來處理視窗執行緒

視窗執行緒

  • 在ui檔案中繫結控制元件的槽函式
  • 透過將獲取的檔案路徑顯示在combox上,其他控制元件透過文字值呼叫,其實是不安全的
  • 頁面的具體狀態透過自定義的訊號與槽與影片解碼執行緒互動