FFmpeg视频解码器
本博客是摘自雷霄驊大神的課程《基于 FFmpeg + SDL 的視頻播放器的制作》課程 里的內容,非常適合音視頻小白入門,在這里感謝雷神的指導!
目錄
- 視頻解碼知識
- VC下FFmpeg開發環境的搭建
- 示例程序運行
- FFmpeg解碼的函數
- FFmpeg解碼的數據結構
視頻解碼知識
? 純凈的視頻解碼流程
壓縮編碼數據->像素數據。
例如解碼H.264,就是“H.264碼流->YUV”。
? 一般的視頻解碼流程
視頻碼流一般存儲在一定的封裝格式(例如MP4、AVI等)中。封裝格式中通常還包含音頻碼流等內容。
對于封裝格式中的視頻,需要先從封裝格式中提取中視頻碼流,然后再進行解碼。
例如解碼MKV格式的視頻文件,就是“MKV->H.264碼流->YUV”。
VC下FFmpeg開發環境的搭建
? 新建控制臺工程
打開VC++
文件->新建->項目->控制臺應用程序
? 拷貝FFmpeg開發文件
頭文件(*.h)拷貝至項目文件夾的include子文件夾下
導入庫文件(*.lib)拷貝至項目文件夾的lib子文件夾下
動態庫文件(*.dll)拷貝至項目文件夾下
? 配置開發文件
? 打開屬性面板
解決方案資源管理器->右鍵單擊項目->屬性
(平臺選擇x64)
? 頭文件配置
配置屬性->C/C+±>常規->附加包含目錄,輸入“include”(剛才拷貝頭文件的目錄)
? 導入庫配置
配置屬性->鏈接器->常規->附加庫目錄,輸入“lib” (剛才拷貝庫文
件的目錄)
配置屬性->鏈接器->輸入->附加依賴項,輸入“avcodec.lib; avformat.lib; avutil.lib; avdevice.lib; avfilter.lib; postproc.lib; swresample.lib; swscale.lib”(導入庫的文件名)
? 動態庫不用配置
測試
? 創建源代碼文件
在工程中創建一個包含main()函數的C/C++文件(如果已經有了可以跳過這一步)。
? 包含頭文件
如果是C語言中使用FFmpeg,則直接使用下面代碼
如果是C++語言中使用FFmpeg,則使用下面代碼
#define __STDC_CONSTANT_MACROS
main()中調用一個FFmpeg的接口函數
例如下面代碼打印出了FFmpeg的配置信息
如果運行無誤,則代表FFmpeg已經配置完成。
? FFmpeg庫簡介
FFmpeg一共包含8個庫:
? avcodec:編解碼(最重要的庫)。
? avformat:封裝格式處理。
? avfilter:濾鏡特效處理。
? avdevice:各種設備的輸入輸出。
? avutil:工具庫(大部分庫都需要這個庫的支持)。
? postproc:后加工。
? swresample:音頻采樣數據格式轉換。
? swscale:視頻像素數據格式轉換。
示例程序運行
因為雷神代碼比較久遠,在最新環境下很多都不能運行了,但是大體思路還是不變的(在雷神給的vs文件中還是可以運行的),我也將很多廢棄的代碼進行整改
/*** 最簡單的基于FFmpeg的解碼器* Simplest FFmpeg Decoder** 雷霄驊 Lei Xiaohua* leixiaohua1020@126.com* 中國傳媒大學/數字電視技術* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020** 本程序實現了視頻文件的解碼(支持HEVC,H.264,MPEG2等)。* 是最簡單的FFmpeg視頻解碼方面的教程。* 通過學習本例子可以了解FFmpeg的解碼流程。* This software is a simplest video decoder based on FFmpeg.* Suitable for beginner of FFmpeg.**/#include <stdio.h>#define __STDC_CONSTANT_MACROSextern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libavutil/imgutils.h" };int main(int argc, char* argv[]) {AVFormatContext* pFormatCtx;int i, videoindex;AVCodecContext* pCodecCtx;AVCodec* pCodec;AVFrame* pFrame, * pFrameYUV;uint8_t* out_buffer;AVPacket* packet;int y_size;int ret, got_picture;struct SwsContext* img_convert_ctx;//輸入文件路徑char filepath[] = "Titanic.ts";int frame_cnt;//av_register_all();avformat_network_init();pFormatCtx = avformat_alloc_context();if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {printf("Couldn't open input stream.\n");return -1;}if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {printf("Couldn't find stream information.\n");return -1;}videoindex = -1;for (i = 0; i < pFormatCtx->nb_streams; i++)if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {videoindex = i;break;}if (videoindex == -1) {printf("Didn't find a video stream.\n");return -1;}AVCodecParameters* codecParameters = pFormatCtx->streams[videoindex]->codecpar;pCodecCtx = avcodec_alloc_context3(nullptr);avcodec_parameters_to_context(pCodecCtx, codecParameters);//pCodecCtx=pFormatCtx->streams[videoindex]->codec;pCodec = (AVCodec*)avcodec_find_decoder(pCodecCtx->codec_id);if (pCodec == NULL) {printf("Codec not found.\n");return -1;}if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {printf("Could not open codec.\n");return -1;}FILE* fp, * fp_264, * fp_yuv;fopen_s(&fp, "info.txt", "wb+");fopen_s(&fp_264, "output264.h264", "wb+");fopen_s(&fp_yuv, "outputyuv.yuv", "wb+");/** 在此處添加輸出視頻信息的代碼* 取自于pFormatCtx,使用fprintf()*/fprintf(fp, "shichang: %d\n", pFormatCtx->duration);fprintf(fp, "fengzhuanggeshi:%s\n", pFormatCtx->iformat->long_name);fprintf(fp, "kuangao: %d*%d\n", pCodecCtx->width, pCodecCtx->height);packet = av_packet_alloc();pFrame = av_frame_alloc();pFrameYUV = av_frame_alloc();out_buffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);//Output Info-----------------------------printf("--------------- File Information ----------------\n");av_dump_format(pFormatCtx, 0, filepath, 0);printf("-------------------------------------------------\n");img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);frame_cnt = 0;while (av_read_frame(pFormatCtx, packet) >= 0) {if (packet->stream_index == videoindex) {/** 在此處添加輸出H264碼流的代碼* 取自于packet,使用fwrite()*/fwrite(packet->data, 1, packet->size, fp_264);if (avcodec_send_packet(pCodecCtx, packet) < 0) {printf("avcodec_send_packet failed!.\n");continue;}while (1){ret = avcodec_receive_frame(pCodecCtx, pFrame);if (ret != 0)break;sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);printf("Decoded frame index: %d\n", frame_cnt);/** 在此處添加輸出YUV的代碼* 取自于pFrameYUV,使用fwrite()*/fwrite(pFrameYUV->data[0], 1, pCodecCtx->width * pCodecCtx->height, fp_yuv);fwrite(pFrameYUV->data[1], 1, pCodecCtx->width * pCodecCtx->height / 4, fp_yuv);fwrite(pFrameYUV->data[2], 1, pCodecCtx->width * pCodecCtx->height / 4, fp_yuv);frame_cnt++;}}av_packet_unref(packet);}sws_freeContext(img_convert_ctx);fclose(fp);fclose(fp_264);fclose(fp_yuv);av_frame_free(&pFrameYUV);av_frame_free(&pFrame);avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0; }FFmpeg解碼的函數
FFmpeg解碼的流程圖如下所示:
FFmpeg解碼函數簡介:
? av_register_all():注冊所有組件 (目前廢棄)。
? avformat_open_input():打開輸入視頻文件。
? avformat_find_stream_info():獲取視頻文件信息。
? avcodec_find_decoder():查找解碼器。
? avcodec_open2():打開解碼器。
? av_read_frame():從輸入文件讀取一幀壓縮數據。
? avcodec_decode_video2():解碼一幀壓縮數據 (目前廢棄)。
取代:(這兩個函數要特別注意,有很多注意點)
avcodec_send_packet():發送編碼數據包
avcodec_receive_frame():接收解碼后數據
? avcodec_close():關閉解碼器。
? avformat_close_input():關閉輸入視頻文件。
PS:初次學習,一定要將這些函數名稱熟記于心
FFmpeg解碼的數據結構
FFmpeg解碼的數據結構如下所示:
注意AVCodecContext已經被AVCodecParameters取代,但是編解碼仍然離不開AVCodecContext,所以有avcodec_parameters_to_context函數可以將AVCodecParameters的信息填充到AVCodecContext中。
FFmpeg數據結構簡介
? AVFormatContext
封裝格式上下文結構體,也是統領全局的結構體,保存了視頻文件封裝格式相關信息。
? AVInputFormat
每種封裝格式(例如FLV, MKV, MP4, AVI)對應一個該結構體。
? AVStream
視頻文件中每個視頻(音頻)流對應一個該結構體。
? AVCodecParameters
此結構描述編碼流的屬性。
? AVCodecContext
編碼器上下文結構體,保存了視頻(音頻)編解碼相關信息。
? AVCodec
每種視頻(音頻)編解碼器(例如H.264解碼器)對應一個該結構體。
? AVPacket
存儲一幀壓縮編碼數據。
? AVFrame
存儲一幀解碼后像素(采樣)數據。
FFmpeg數據結構分析
? AVFormatContext
iformat:輸入視頻的AVInputFormat
nb_streams :輸入視頻的AVStream 個數
streams :輸入視頻的AVStream []數組
duration :輸入視頻的時長(以微秒為單位)
bit_rate :輸入視頻的碼率
? AVInputFormat
name:封裝格式名稱
long_name:封裝格式的長名稱
extensions:封裝格式的擴展名
id:封裝格式ID
一些封裝格式處理的接口函數
? AVStream
id:序號
codec:該流對應的AVCodecContext
time_base:該流的時基
r_frame_rate:該流的幀率
? AVCodecContext
codec:編解碼器的AVCodec
width, height:圖像的寬高(只針對視頻)
pix_fmt:像素格式(只針對視頻)
sample_rate:采樣率(只針對音頻)
channels:聲道數(只針對音頻)
sample_fmt:采樣格式(只針對音頻)
? AVCodec
name:編解碼器名稱
long_name:編解碼器長名稱
type:編解碼器類型
id:編解碼器ID
一些編解碼的接口函數
? AVPacket
pts:顯示時間戳
dts :解碼時間戳
data :壓縮編碼數據
size :壓縮編碼數據大小
stream_index :所屬的AVStream
? AVFrame
data:解碼后的圖像像素數據(音頻采樣數據)。
linesize:對視頻來說是圖像中一行像素的大小;對音頻來說是整個音頻幀的大小。
width, height:圖像的寬高(只針對視頻)。
key_frame:是否為關鍵幀(只針對視頻) 。
pict_type:幀類型(只針對視頻) 。例如I,P,B。
補充小知識
? 解碼后的數據為什么要經過sws_scale()函數處理?
? 解碼后YUV格式的視頻像素數據保存在AVFrame的data[0]、data[1]、data[2]中。但是這些像素值并不是連續存儲的,每行有效像素之后存儲了一些無效像素 。 以亮度 Y 數據為例 , data[0] 中一共包含了linesize[0]*height個數據。但是出于優化等方面的考慮,linesize[0]實際上并不等于寬度width,而是一個比寬度大一些的值。因此需要使用sws_scale()進行轉換。轉換后去除了無效數據,width和linesize[0]取值相等。
總結
以上是生活随笔為你收集整理的FFmpeg视频解码器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pci总线定时协议_PCI总线标准协议(
- 下一篇: html+css制作圣诞树