FFmpegHandler 功能解析,C語言程序化設計與C++面向對象設計的核心差異

FFmpegHandler 功能解析

本文件記錄了關于 FFmpegHandler 類中核心函數工作流程的詳細解釋。


Q: FFmpeg逐幀解碼,FFmpegHandler::openVideoFFmpegHandler::readAVFrame 這兩個函數都分別做了什么?

A:

可以把整個過程想象成“準備播放一部電影”:

FFmpegHandler::openVideo() —— 準備工作

這個函數只在開始時調用一次,負責所有繁瑣但必要的初始化和設置。它就像是你在電影播放前,把影碟放進播放器,然后播放器讀取影碟信息、準備好解碼芯片的過程。

它具體做了以下幾件大事:

  1. 打開文件 (avformat_open_input)

    • 作用: 這是第一步,告訴FFmpeg“我要處理這個視頻文件”。
    • 細節: FFmpeg會讀取文件的頭部信息,識別出它是什么容器格式(比如.mp4, .mkv, .avi等)。所有這些文件級別的信息會被存放在一個叫做 AVFormatContext 的結構體里。
    • 類比: 把影碟放進播放器。
  2. 探測流信息 (avformat_find_stream_info)

    • 作用: 真正去讀取文件的一小部分數據,來弄清楚這個文件里到底有什么內容。
    • 細節: 一個視頻文件里通常包含多個“流”(Stream),比如一個視頻流、一個或多個音頻流、甚至還有字幕流。這個函數會分析出每個流的詳細信息(編碼格式、分辨率、幀率等)。
    • 類比: 播放器讀取影碟目錄,發現里面有一條視頻軌道(H.264編碼)、一條英語音軌和一條中文字幕。
  3. 找到視頻流并獲取解碼器 (av_find_best_stream)

    • 作用: 從所有流中,找到我們最關心的那一個——視頻流。
    • 細節: 遍歷所有流,找到類型為 AVMEDIA_TYPE_VIDEO 的那一個,并確定解碼這個視頻流需要哪種解碼器(比如H.264解碼器)。
    • 類比: 你在播放器菜單上選擇了“播放視頻”。
  4. 準備解碼器上下文 (avcodec_alloc_context3, avcodec_parameters_to_context)

    • 作用: 創建并配置一個解碼器實例。
    • 細節: 我們需要一個 AVCodecContext 結構體來管理解碼過程。這個函數會為它分配內存,并把從視頻流中讀到的參數(如視頻寬、高、像素格式等)拷貝到這個上下文中。
    • 類比: 播放器為H.264視頻流,激活并配置了專門的H.264解碼芯片。
  5. 打開解碼器 (avcodec_open2)

    • 作用: 正式啟動解碼器,讓它進入準備好接收數據進行解碼的狀態。
    • 類比: 解碼芯片通電,準備開始工作。
  6. 分配內存 (av_frame_alloc, av_packet_alloc)

    • 作用: 預先分配好之后會重復使用的內存空間。
    • 細節:
      • AVPacket: 用來存放從文件中讀出來的、未經解碼的壓縮數據(一小包一小包的)。
      • AVFrame: 用來存放解碼器輸出的、已經解碼的原始圖像數據(一幀一幀的)。
    • 類比: 準備好一個“籃子”(AVPacket)去裝壓縮數據,再準備一個“畫框”(AVFrame)去承載解壓后的圖像。

openVideo 執行完畢后,萬事俱備,只欠“讀取”。


FFmpegHandler::readAVFrame() —— 循環工作

這個函數在 while 循環中被反復調用,負責持續地解碼出新的一幀圖像。它就像是播放器在持續地播放電影。

它的工作流程是一個精密的“生產者-消費者”模型:

  1. 從文件讀取一個數據包 (av_read_frame)

    • 作用: 從視頻文件中讀取一小塊壓縮數據,存放到我們之前準備好的 AVPacket(籃子)里。
    • 細節: 這個數據包可能屬于視頻流,也可能屬于音頻流。所以我們需要檢查它的 stream_index。如果不是視頻流,我們就丟棄它,繼續讀下一個。
    • 類比: 從影碟上讀取一小段數據流。
  2. 將數據包發送給解碼器 (avcodec_send_packet)

    • 作用: 把裝有壓縮數據的 AVPacket(籃子)遞給已經準備好的解碼器。
    • 細節: 解碼器可能會需要好幾個數據包才能解出一幀,也可能一個數據包就能解出好幾幀。這是一個異步的過程。
    • 類比: 把數據流送進解碼芯片。
  3. 從解碼器接收一幀圖像 (avcodec_receive_frame)

    • 作用: 向解碼器“索要”已經解碼完成的圖像,并存放到我們準備好的 AVFrame(畫框)里。
    • 細節:
      • 如果解碼器說“成功了”(返回0),我們就得到了新的一幀,可以立刻返回這個 AVFrame
      • 如果解碼器說“我還需要更多數據”(返回 EAGAIN),說明當前的壓縮數據還不夠解出一幀。我們就需要回到第1步,繼續讀取下一個數據包。
      • 如果解碼器說“我已經到頭了”(返回 AVERROR_EOF),說明視頻流結束了。
    • 類比: 解碼芯片處理完數據后,輸出一幅完整的圖像。

readAVFrame 不斷重復這個“讀包 -> 送包 -> 收幀”的循環,直到 av_read_frame 再也讀不到數據包(文件末尾),此時它返回 nullptr,通知調用者視頻已經播放完畢。

Q: 如果要仿照FILE *fopen(const char filename, const char mode);與size_t fread(void ptr, size_t size, size_t nmemb, FILE stream);模式的話。這里的bool FFmpegHandler::openVideo(const std::string& input_path)不考慮char mode,fopen的返回值類型為FILE,請問openVideo是否也要返回值為一個類似FILE指針的東西呢?

A:
? 這是個非常深刻的問題,直擊了C語言程序化設計與C++面向對象設計的核心差異。您的類比非常恰當,而這兩種模式的不同選擇,正是兩種編程思想的體現。

簡單來說,答案是:不,openVideo不應該返回指針,因為它是在一個C++類(對象)的上下文中工作的。在這個模式中,對象本身就是那個“FILE指針”。

讓我們來深入剖析一下。

模式一:C語言的程序化、無狀態函數 (fopen/fread)

在C語言中,函數通常是“無狀態”的。fread 函數本身并不知道要從哪個文件讀取。它需要你告訴它。

  1. fopen 的角色: 它是一個工廠函數。你給它一個路徑,它在內存中創建并初始化一個包含所有文件狀態(如文件描述符、當前讀取位置、緩沖區等)的 FILE 結構體。然后,它返回一個指向這個結構體的句柄(Handle),也就是FILE*。這個句柄是后續所有操作的唯一憑證。

  2. fread 的角色: 它接收這個 FILE*句柄,通過它找到對應的文件狀態信息,然后執行讀取操作。

  3. 調用者責任: 調用者必須負責存儲這個 FILE*句柄,并手動將它傳遞給每一個需要它的函數 (fread, fseek, fclose等)。最后,調用者必須記得調用 fclose(f) 來銷毀那個 FILE結構體,否則就會內存泄漏。

流程: fopen() -> 返回句柄 -> 將句柄傳遞給 fread() -> 將句柄傳遞給 fclose()

模式二:C++的面向對象、有狀態對象 (FFmpegHandler)

在C++中,我們將數據和操作這些數據的函數捆綁在一起,形成一個對象。這個對象自身就維護著自己的狀態。

  1. FFmpegHandler 對象的角色: 當你創建一個 FFmpegHandler handler; 實例時,你就已經創建了一個準備好承載狀態的“容器”。這個 handler 對象本身,就扮演了那個 FILE* 句柄的角色。它就是憑證。

  2. openVideo() 的角色: 它是一個成員函數(Method),它的工作不是創建一個新的憑證,而是填充和改變它所屬對象的狀態。當你調用 handler.openVideo(…)時,它會去填充 handler 內部的私有成員變量,如 format_ctx_, codec_ctx_ 等。

  3. readAVFrame() 的角色: 當你調用 handler.readAVFrame()時,它不需要你再給它任何憑證。因為它作為同一個對象的成員,天生就知道要去訪問handler 內部的 format_ctx_ 和 codec_ctx_ 來完成工作。

  4. 析構函數的角色: 當 handler對象生命周期結束時(例如離開作用域),它的析構函數 ~FFmpegHandler()會被自動調用。我們把 cleanup() 放在這里,就實現了資源的自動釋放,調用者完全無需操心,極大地避免了內存泄漏。這就是著名的 RAII (Resource Acquisition Is Initialization) 原則。

流程: 創建對象 -> openVideo() 改變對象內部狀態并返回bool狀態碼 -> readAVFrame() 自動使用對象內部狀態 -> 對象銷毀時自動清理

對比總結
│ 特性 │ C-style (fopen/fread) │ C++ Class (FFmpegHandler) │
│ *憑證… │ fopen 返回的 FILE 指針 │ FFmpegHandler 對象本身 │
│ **狀態… │ 隱藏在 FILE 結構體內部 │ 類的私有成員變量 (format_ctx_ 等) │
│ **初始… │ fopen() 創建并返回一… │ openVideo() 修改一個已存在對… │
│ **返回… │ fopen 返回句柄,用 N… │ openVideo 返回 bool,直接表… │
│ **資源… │ 調用者必須手動調用 f… │ 對象析構時通過RAII自動完成 │

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917289.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917289.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917289.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Codeforces Round 1039 (Div. 2) A-C

A. Recycling Center題目大意 給你n個垃圾袋,每個垃圾袋有一個重量 在每秒鐘,你可以選擇一個垃圾袋,如果他的重量小于等于c,那么你可以不花費硬幣丟掉它 當你丟掉一個垃圾袋后,其他垃圾袋在這一秒重量會翻倍 問最少花費…

【設計模式】 原則

單一職責原則 對于一個類而言,有且僅有一個引起他變化的原因或者說,一個類只負責一個職責 如果一個類承擔的職責過多,那么這些職責放在一起耦合度太高了,一個職責的變化可能會影響這個類其他職責的能力。 所以我們在做軟件設計的時…

windows11右鍵菜單新增項增加drawio文件,使用draw.io

目錄1.新建空白模板2.建立注冊表文件1.新建空白模板 這里我們的模板文件路徑為 D:\Software\drawio\template.drawio 2.建立注冊表文件 首先新建一個.txt文件,我這里取名為menulize.txt,然后將下面的內容復制到.txt文件中 Windows Registry Editor Ver…

解鎖網頁魔法:零基礎HTML通關秘籍

文章目錄**解鎖網頁魔法:零基礎HTML通關秘籍**HTML 基礎目標HTML 結構認識 HTML 標簽HTML 文件基本結構標簽層次結構快速生成代碼框架HTML 常見標簽注釋標簽注釋的原則標題標簽: h1-h6段落標簽: p換行標簽:br綜合案例: 展示博客超鏈接標簽: a表格標簽**基…

類似 Pixso 但更側重「網頁 / 軟件界面設計」「前后端可視化開發」的工具

從 GoView 的 Demo 功能來看,它主要聚焦于數據可視化大屏的低代碼搭建,更側重數據圖表配置和頁面布局,沒有類似 Pixso 的在線 UI 設計(如矢量繪圖、組件樣式精細化設計)功能,其核心是通過預設組件快速構建數…

MySQL--組從復制的詳解及功能演練

2.MySQL的組從復制 2.1 配置mastesr [rootmysqlaa ~]# vim /etc/my.cnf [mysqld] server-id10 datadir/data/mysql socket/data/mysql/mysql.sock default_authentication_pluginmysql_native_password log-binmysql-bin[rootmysqlaa ~]# /etc/init.d/mysqld restart# 進入數據…

JavaScript將String轉為base64 筆記250802

JavaScript將String轉為base64 筆記250802 在 JavaScript 中將字符串轉換為 Base64 編碼有多種方法,每種方法都有其適用場景。下面我將全面介紹這些方法,包括處理 ASCII 字符、Unicode 字符以及性能優化方案。 基礎方法:btoa() 基本用法&a…

Unity3D數學第四篇:射線與碰撞檢測(交互基礎篇)

Unity3D數學第一篇:向量與點、線、面(基礎篇) Unity3D數學第二篇:旋轉與歐拉角、四元數(核心變換篇) Unity3D數學第三篇:坐標系與變換矩陣(空間轉換篇) Unity3D數學第…

數據處理和統計分析——09 數據分組

1 聚合 1.1 簡介 在SQL中我們經常使用GROUP BY將某個字段,按不同的取值進行分組,在Pandas中也有groupby()函數;分組之后,每組都會有至少1條數據,將這些數據進一步處理返回單個值的過程就是聚合,比如分組之后…

【數據結構與算法】數據結構初階:排序內容加餐(一)——快速排序:三路劃分、自省排序

🔥個人主頁:艾莉絲努力練劍 ?專欄傳送門:《C語言》、《數據結構與算法》、C語言刷題12天IO強訓、LeetCode代碼強化刷題 🍉學習方向:C/C方向 ??人生格言:為天地立心,為生民立命,為…

MySqL(加餐)

范式第一范式數據庫表的每一列都是不可分割的原子數據項,而不能是集合,數組,對象等非原子數據。在關系型數據庫的設計中,滿足第一范式是對關系模式的基本要求。不滿足第一范式的數據庫就不能被稱為關系數據庫。第一范式實際上只要…

【redis】基于工業界技術分享的內容總結

Redis 實踐指南與核心概念 一、Java 中常用的 Redis 使用場景與實踐 緩存(Caching) 場景:熱點數據、頻繁訪問的數據,如商品詳情、用戶信息。通過緩存減少數據庫壓力,提高系統響應速度。 工業界實踐: 淘寶…

服務端之nestJS常用異常類及封裝自定義響應模塊

MENU前言常用異常類(由nestjs/common提供)示例自定義異常(可選)自定義響應模塊前言 在NestJS中,nestjs/common提供了大量的內置異常類,主要用于在控制器、服務等層拋出特定的HTTP錯誤響應。 常用異常類&…

數據鏈路層、NAT、代理服務、內網穿透

目錄 一. 以太網 以太網幀格式 二. MAC地址 三. MTU 四. ARP協議 五. NAT NAPT 六. 代理服務器 正向代理 反向代理 七. 內網穿透 八. 內網打洞 一. 以太網 ? "以太網" 不是一種具體的網絡, 而是一種技術標準; 既包含了數據鏈路層的內 容, 也包含了一些物理層…

Rust在CentOS 6上的移植

Rust已不支持Cent OS 6 rhel是Redhat 發布的Red Hat Enterprise Linux的簡稱,使用rhel源代碼編譯的CentOS,最新的版本是CentOS 7,于2024年停止支持。而更古老的CentOS 6,則在2020年就已經結束了。 而面對如此老舊的系統&#xf…

C++音視頻開發:基礎面試題

音視頻領域技術門檻高,學習資料稀缺,體系化書籍和開發工具有限,新手入門困難。音視頻開發涉及眾多任務:音頻(采集、編解碼、降噪等)、視頻(采集、編解碼、圖像處理)、實時傳輸&#…

C++刷題 - 7.27

貪心算法的詳細邏輯這個問題的最優解可以用 貪心算法 在 O(N) 時間 內解決。它的核心思想是:每次操作盡可能覆蓋最長的連續非零區間,并通過數學分析發現:最小操作次數等于所有“上升臺階”的高度差之和。1. 直觀理解假設 steps [1, 2, 3, 2,…

音頻3A處理簡介之AGC(自動增益控制)

在音頻通話和視頻會議中,音頻自動增益控制AGC模塊的主要作用:? 穩定音頻信號的輸出電平。無論麥克風采集信號的強弱(如用戶離麥克風遠近程度不同),盡可能保證音頻采集模塊的輸出音量保持相對一致,不會偏大…

web前端打包apk包

我用的是HBuilder工具,可視化更便捷,目前我這操作的apk包是不需要上架的,所以跟實際需要上架的可能還有些出入 首先先新建個項目,選擇5App模式 把目前需要打包的內容上傳到服務器,我們以嵌套的形式進行打包,找到index.…

Ansible提權sudo后執行報錯

1.問題 配置了sudo提權信息后,執行ansible-play報錯,報錯信息如下:2.原因 sudo沒有執行**/bin/sh的權限,而ansible腳本中依賴/bin/sh**,所以報錯了: 查看日志sudo tail -f /var/log/secure3.解決方式 修改*…