FFmpeg解碼過程詳解
解碼流程
API文檔
avcodec_find_decoder
AVCodec *avcodec_find_decoder(enum AVCodecID id);
- 功能: 根據編解碼器ID查找解碼器
- 參數:
id
: 編解碼器ID
- 返回值: 成功返回AVCodec指針,失敗返回NULL
- 示例:
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {fprintf(stderr, "未找到H264解碼器\n");return -1;
}
avcodec_alloc_context3
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
- 功能: 分配并初始化AVCodecContext
- 參數:
codec
: 關聯的編解碼器,可以為NULL
- 返回值: 成功返回AVCodecContext指針,失敗返回NULL
- 示例:
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {fprintf(stderr, "無法分配編解碼器上下文\n");return -1;
}
avcodec_parameters_to_context
int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par);
- 功能: 將AVCodecParameters中的參數填充到AVCodecContext
- 參數:
codec
: 目標AVCodecContextpar
: 源AVCodecParameters
- 返回值: 成功返回0,失敗返回負值錯誤碼
- 示例:
if (avcodec_parameters_to_context(codec_ctx, codecpar) < 0) {fprintf(stderr, "無法填充編解碼器參數\n");return -1;
}
avcodec_open2
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
- 功能: 使用指定解碼器初始化AVCodecContext
- 參數:
avctx
: 要初始化的AVCodecContextcodec
: 要使用的解碼器options
: 額外的選項字典
- 返回值: 成功返回0,失敗返回負值錯誤碼
- 示例:
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "無法打開解碼器\n");return -1;
}
avcodec_send_packet
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
- 功能: 將數據包發送給解碼器
- 參數:
avctx
: AVCodecContext指針avpkt
: 要解碼的數據包
- 返回值: 成功返回0,失敗返回負值錯誤碼
- 示例:
if (avcodec_send_packet(codec_ctx, &pkt) < 0) {fprintf(stderr, "發送數據包失敗\n");continue;
}
avcodec_receive_frame
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
- 功能: 從解碼器獲取解碼后的幀
- 參數:
avctx
: AVCodecContext指針frame
: 用于存儲解碼后的幀
- 返回值: 成功返回0,失敗返回負值錯誤碼
- 示例:
AVFrame *frame = av_frame_alloc();
while (avcodec_receive_frame(codec_ctx, frame) >= 0) {// 處理解碼幀
}
Audio編碼格式介紹
1. 音頻編碼格式概述
音頻編碼格式是將音頻數據轉換為可存儲或傳輸的格式的過程。在音頻領域,常見的編碼格式包括PCM(脈沖編碼調制)、MP3、AAC等。每種格式都有其特定的特點和應用場景。
2.常見音頻編碼格式
2.1 PCM(脈沖編碼調制)
PCM(Pulse Code Modulation,脈沖編碼調制)是一種將模擬信號轉換為數字信號的標準方法。其核心過程包括采樣、量化和編碼,最終生成未經壓縮的音頻數據。PCM數據由如下六個參數來描述:
- 采樣率:每秒鐘采集的樣本數,例如44.1kHz表示每秒采集44100個樣本。
位深度:每個樣本的量化位數,常見的有8位、16位、24位等。位深度越高,表示的動態范圍越大,音質越好。 - 聲道數:單聲道或雙聲道等。雙聲道音頻通常采用LRLR或RLRL的方式存儲。
- 符號性:有符號或無符號。有符號數據可以表示負值,適用于更廣泛的音頻范圍。
- 字節序:大端模式或小端模式。字節序決定了數據在存儲或傳輸時的排列順序。
- 數據類型:整型或浮點型。整型數據通常用于存儲離散的量化值,而浮點型數據可能用于某些特殊應用。
PCM數據的存儲方式:
- 單聲道PCM:采樣數據按時間順序存儲,例如16位單聲道音頻,每個樣本占用2個字節。
- 雙聲道PCM:采樣數據交錯存儲(Packed)或分開存儲(Planar)。交錯存儲將左右聲道的數據混合在一起,而分開存儲則將左右聲道的數據分別存儲。
2.1.2 PCM數據的操作
在dump pcm數據的時候要關注當前數據是Packed還是Planar。通常pcm文件格式為Packed,那么也就是說如果按照Planar的方式來dump數據,那么dump出來的文件是不可以播放的。下面分別提供兩種格式dump代碼。
void FileDump::WritePcmPlanarData(uint8_t *data[], int size_per_sample) {/**P表示Planar(平面),其數據格式排列方式為 :LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每個LLLLLLRRRRRR為一個音頻幀)注意planar格式通常是ffmpeg內部使用格式而不帶P的數據格式(即交錯排列)排列方式為:LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每個LR為一個音頻樣本)播放范例: `*/
// 解析出來的是32bit float planar 小端數據for (int i = 0; i < audio_info_.samples; i++) {for (int ch = 0; ch < audio_info_.channels;ch++) // 交錯的方式寫入, 大部分float的格式輸出fwrite(data[ch] + size_per_sample * i, 1, size_per_sample, file_);fflush(file_);}
}
void FileDump::WritePcmData(uint8_t *data[], int pcm_size) {fwrite(data[0], 1, pcm_size, file_);fflush(file_);
}
注意使用avcodec_receive_frame
獲取到的數據是planar
的,因此解碼后的數據在dump的時候應該注意。
在使用sox
添加音效時也要注意,輸入給API的數據需要是int32_t
類型的planar
數據。
2.1.2 PCM文件大小與播放時長的關系
假如一個PCM文件大小為100MB,采樣率為44.1kHz,采樣深度為16位,雙聲道。那么這個PCM文件的時長為:
時長 = 文件大小 / (采樣率 * 采樣深度 * 聲道數 / 8)
時長 = 100MB / (44.1kHz * 16位 * 2聲道 / 8)
2.1.3 PCM sample與播放時長的關系
如果當前解析出來的一段PCM數據sample個數是1024,并且采樣率為44.1kHz,采樣深度為16位,雙聲道。那么這個PCM文件的時長為:
時長 = sample個數 / 采樣率
時長 = 1024 / 44.1kHz 約等于23ms左右
2.2 AAC
AAC(高級音頻編碼,Advanced Audio Coding)是一種高壓縮比的音頻編碼技術,最初基于MPEG-2標準開發,并在2000年隨著MPEG-4標準的推出進一步發展為MPEG-4 AAC。AAC的設計目標是取代MP3格式,提供更高的音質和更低的比特率。
AAC編碼封裝格式分為兩種:ADTS(Audio Data Transport Stream)和ADIF(Audio Data Interchange Format)。ADTS是一種流式封裝格式,適用于實時傳輸和存儲;ADIF是一種文件封裝格式,適用于存儲。
2.2.1 ADTS格式
上圖可以看出以ADTS格式的AAC文件包含了多個adts_frame。
解析 ADTS 幀頭:
- adts_fixed_header():解析 ADTS 幀的固定頭部,包含同步字、幀長度、采樣率等信息。
- adts_variable_header():解析 ADTS 幀的可變頭部,包含聲道配置、幀類型等信息。
處理音頻數據塊: - adts_error_check():檢查幀頭部的錯誤。
- raw_data_block():提取并處理音頻數據塊。
如果 number_of_raw_data_blocks_in_frame == 0,表示幀中只有一個音頻數據塊。
注意一個adts_frame中包含了1到16個raw_data_block,一個raw_data_block包含了1024個采樣點。
2.2.2 ADIF格式
從圖可以看出ADIF格式類型的AAC文件只有一個adif_header。
- aif_header()
作用: 定義位于 adif_sequence 開頭的 ADIF 頭。
描述: 包含音頻數據交換格式的基本信息,如版權標識、比特率類型、比特率等。具體字段包括 adif_id, copyright_id_present, copyright_id, original_copy, home, bitstream_type, bitrate, num_program_config_elements, 和 adif_buffer_fullness。 - byte_alignment()
作用: 對齊到頭部的第一個比特。
描述: 確保數據流在內存中的對齊,以便后續的數據處理更加高效。 - raw_data_stream()
作用: 包含實際的音頻數據流。
描述: 這是音頻數據的核心部分,包含了經過編碼的音頻數據。
好了,aac我們就介紹這些,畢竟較為復雜,在日常的開發中,解析aac head一般使用開源庫,如果需要自己解析,也是邊看spec邊code.具體的aac格式請參照ISO/IEC
13818-7 Part 7: Advanced Audio Coding (AAC)
Video 編碼格式介紹
視頻編碼格式是將視頻數據壓縮成更小的數據塊,以便于存儲和傳輸。常見的視頻編碼格式包括H.264、H.265、VP8、VP9等。這些格式在壓縮效率和視頻質量之間取得了平衡。
1.H.264 (AVC)
H.264是一種廣泛使用的視頻編碼格式,由ITU-T和ISO/IEC聯合開發。H.264采用了基于幀的編碼方式,將視頻數據分割成多個幀,并對每個幀進行壓縮。H.264的壓縮算法包括幀內編碼和幀間編碼。
1.H264數據組織形式
1.幀的分類
1.1 I幀
- I幀(Intra Frame)
定義:I幀采用幀內壓縮技術,不依賴于其他幀進行解碼。I幀通過預測當前幀內的像素差異來減少數據量,通常用于場景切換或圖像變化較大的時刻。
特點:
解碼時不需要參考其他幀。
壓縮率較低(約7%),但提供高質量的畫面。
在GOP(Group of Picture)中作為起始幀,用于重同步解碼過程。
應用場景:I幀通常用于關鍵幀的插入,例如在檢測到丟包或需要重新同步時立即插入一個I幀。
1.2 P幀
- P幀(Predictive Frame)
定義:P幀是前向參考幀,采用幀間壓縮技術,僅參考前一幀(P幀或I幀)進行解碼。
特點:
解碼時需要參考前一幀。
壓縮率較高(約20%),適合變化較小的場景。
可以進一步分為P1幀和P2幀,分別依賴前一幀和前兩幀。
應用場景:P幀常用于連續變化的場景,例如緩慢移動的物體或背景。
1.3 B幀
- B幀(Bi-Directional Frame)
定義:B幀是雙向參考幀,采用幀間壓縮技術,同時參考前后兩幀(P幀或I幀)進行解碼。
特點:
解碼時需要參考前后兩幀。
壓縮率最高(約50%),適合快速變化的場景。
提高了壓縮效率,但增加了解碼復雜度。
應用場景:B幀更適合運動較平緩的場景、靜態北背景變化
幀大小對比
從圖中可以看出I>P>B,而且大小相差很多。
參考鏈接
由于本人對視頻編碼格式不是很了解,因此這里給出一個參考鏈接,供大家學習。
https://www.nxrte.com/jishu/16413.html
注意
特性 | I 幀 | IDR 幀 |
---|---|---|
定義 | 獨立編碼的幀,不依賴其他幀 | 特殊的 I 幀,用于重置參考幀列表 |
參考關系 | 可能被后續 P/B 幀參考 | 之后的所有幀都不能參考之前的幀考 |
作用 | 提供隨機訪問點 | 提供更強的隨機訪問能力 |
2.YUV編碼介紹
YUV編碼是一種廣泛應用于數字視頻和圖像處理中的顏色編碼方式,其核心思想是將圖像的顏色信息分解為亮度(Y)和色度(U和V)兩個部分。這種編碼方式基于人類視覺對亮度和色度敏感度的不同,通過減少色度信息的采樣率來降低數據傳輸和存儲的帶寬需求,從而實現高效的視頻壓縮和傳輸。
2.YUV原理
2.1亮度與色度分離:
Y(亮度)表示圖像的明暗程度,是灰度值。
- U和V(色度)分別表示藍色差分信號和紅色差分信號,用于描述顏色的飽和度和色調。
- 在YUV編碼中,亮度信息占據主要帶寬,而色度信息則通過采樣率降低來減少存儲空間。
2.2 人眼特性:
- 人類視覺對亮度變化比對顏色變化更敏感,因此在YUV編碼中,亮度信息被賦予更高的帶寬,而色度信息則通過采樣率降低來減少數據量。
- 這種設計使得YUV編碼在保持圖像質量的同時,能夠有效減少數據傳輸和存儲的帶寬需求。
數學表達:
2.3 YUV可以通過以下公式從RGB轉換而來:
2.3.1 YUV 分量定義
- Y:亮度(Luma)
- U:藍色色差(Cb)
- V:紅色色差(Cr)
2.3.2. 轉換公式
- Y = 0.299 * R + 0.587 * G + 0.114 * B
- U = -0.14713 * R - 0.28886 * G + 0.436 * B
- V = 0.615 * R - 0.51499 * G - 0.10001 * B
2.3.3. 公式說明
- R, G, B 的取值范圍為 [0, 255]
- Y 的取值范圍為 [0, 255]
- U 和 V 的取值范圍為 [-128, 127]
2.3.4. 示例
假設 R = 100, G = 150, B = 200:
- Y = 0.299 * 100 + 0.587 * 150 + 0.114 * 200 ≈ 146.45
- U = -0.14713 * 100 - 0.28886 * 150 + 0.436 * 200 ≈ 33.565
- V = 0.615 * 100 - 0.51499 * 150 - 0.10001 * 200 ≈ -10.7485
其中,R、G、B分別表示紅色、綠色和藍色分量。
2.4 YUV編碼的采樣格式
根據YUV的原理主要是改變UV的個數因此有如下格式
舉個例子YUV4:4:4 代表什么意思:
- 第一個"4"代表4列像素信息
- 第二個"4"代表第一行像素信息
- 第三個"4"代表第二行像素信息
先記住下面這段話,以后提取每個像素的YUV分量會用到。 - YUV 4:4:4采樣,每一個Y對應一組UV分量。
- YUV 4:2:2采樣,每兩個Y共用一組UV分量。
- YUV 4:2:0采樣,每四個Y共用一組UV分量。
注意YUV存在Packed與Planar的概念,下面給出YUV420P如何dump code.
void FileDump::WriteVideoYUV420PData(AVFrame* videoFrame) {for (int j = 0; j < videoFrame->height; j++)fwrite(videoFrame->data[0] + j * videoFrame->linesize[0], 1, videoFrame->width, file_);for (int j = 0; j < videoFrame->height / 2; j++)fwrite(videoFrame->data[1] + j * videoFrame->linesize[1], 1, videoFrame->width / 2, file_);for (int j = 0; j < videoFrame->height / 2; j++)fwrite(videoFrame->data[2] + j * videoFrame->linesize[2], 1, videoFrame->width / 2, file_);
}
大多數播放器支持PCM packeted 與YUVPlanar ,因此我們在開發的時候也要注意,設置給驅動的format和輸入給驅動的data 要符合驅動的要求。
2.4 YUV編碼的采樣格式
yuv420p原圖
Y圖
U圖
V圖
從上面三幅圖片可以看出來,人眼灰度圖片(Y圖)敏感度最高,感官上可以感覺出來Y圖反饋的信息最多
總結
可以看出不論是Audio還是Video,編碼的最終目的都是在盡可能保證原始數據質量的同時,減少冗余信息,提高傳輸效率。
源碼地址
https://github.com/OrangeKitten/VideoPlayer