最简单的基于FFmpeg的解码器-纯净版(不包含libavformat)
=====================================================
最簡單的基于FFmpeg的視頻播放器系列文章列表:
100行代碼實現最簡單的基于FFMPEG+SDL的視頻播放器(SDL1.x)
最簡單的基于FFMPEG+SDL的視頻播放器 ver2 (采用SDL2.0)
最簡單的基于FFmpeg的解碼器-純凈版(不包含libavformat)
最簡單的基于FFMPEG+SDL的視頻播放器:拆分-解碼器和播放器
最簡單的基于FFMPEG的Helloworld程序
=====================================================
本文記錄一個更加“純凈”的基于FFmpeg的視頻解碼器。此前記錄過基于FFmpeg的視頻播放器實際上就是一個解碼器:
《最簡單的基于FFMPEG+SDL的視頻播放器 ver2 (采用SDL2.0)》這個播放器調用了FFmpeg中的libavformat和libavcodec兩個庫完成了視頻解碼工作。但是這不是一個“純凈”的解碼器。該解碼器中libavformat完成封裝格式的解析,而libavcodec完成解碼工作。一個“純凈”的解碼器,理論上說只需要使用libavcodec就足夠了,并不需要使用libavformat。本文記錄的解碼器就是這樣的一個“純凈”的解碼器,它僅僅通過調用libavcodec將H.264/HEVC等格式的壓縮視頻碼流解碼成為YUV數據。
流程圖
本文記錄的純凈版本的基于FFmpeg的解碼器的函數調用流程圖如下圖所示。需要注意的是,此解碼器的輸入必須是只包含視頻編碼數據“裸流”(例如H.264、HEVC碼流文件),而不能是包含封裝格式的媒體數據(例如AVI、MKV、MP4)。
avcodec_register_all():注冊所有的編解碼器。
avcodec_find_decoder():查找解碼器。
avcodec_alloc_context3():為AVCodecContext分配內存。
avcodec_open2():打開解碼器。
avcodec_decode_video2():解碼一幀數據。 有兩個平時“不太常見”的函數:
av_parser_init():初始化AVCodecParserContext。
av_parser_parse2():解析獲得一個Packet。
兩個存儲數據的結構體如下所列:
AVFrame:存儲一幀解碼后的像素數據
AVPacket:存儲一幀(一般情況下)壓縮編碼數據
AVCodecParser
AVCodecParser用于解析輸入的數據流并把它分成一幀一幀的壓縮編碼數據。比較形象的說法就是把長長的一段連續的數據“切割”成一段段的數據。他的核心函數是av_parser_parse2()。它的定義如下所示。
/*** Parse a packet.** @param s parser context.* @param avctx codec context.* @param poutbuf set to pointer to parsed buffer or NULL if not yet finished.* @param poutbuf_size set to size of parsed buffer or zero if not yet finished.* @param buf input buffer.* @param buf_size input length, to signal EOF, this should be 0 (so that the last frame can be output).* @param pts input presentation timestamp.* @param dts input decoding timestamp.* @param pos input byte position in stream.* @return the number of bytes of the input bitstream used.** Example:* @code* while(in_len){* len = av_parser_parse2(myparser, AVCodecContext, &data, &size,* in_data, in_len,* pts, dts, pos);* in_data += len;* in_len -= len;** if(size)* decode_frame(data, size);* }* @endcode*/ int av_parser_parse2(AVCodecParserContext *s,AVCodecContext *avctx,uint8_t **poutbuf, int *poutbuf_size,const uint8_t *buf, int buf_size,int64_t pts, int64_t dts,int64_t pos);其中poutbuf指向解析后輸出的壓縮編碼數據幀,buf指向輸入的壓縮編碼數據。如果函數執行完后輸出數據為空(poutbuf_size為0),則代表解析還沒有完成,還需要再次調用av_parser_parse2()解析一部分數據才可以得到解析后的數據幀。當函數執行完后輸出數據不為空的時候,代表解析完成,可以將poutbuf中的這幀數據取出來做后續處理。
對比
簡單記錄一下這個只使用libavcodec的“純凈版”視頻解碼器和使用libavcodec+libavformat的視頻解碼器的不同。
PS:使用libavcodec+libavformat的解碼器參考文章《 最簡單的基于FFMPEG+SDL的視頻播放器 ver2 (采用SDL2.0)》(1) 下列與libavformat相關的函數在“純凈版”視頻解碼器中都不存在。
av_register_all():注冊所有的編解碼器,復用/解復用器等等組件。其中調用了avcodec_register_all()注冊所有編解碼器相關的組件。
avformat_alloc_context():創建AVFormatContext結構體。
avformat_open_input():打開一個輸入流(文件或者網絡地址)。其中會調用avformat_new_stream()創建AVStream結構體。avformat_new_stream()中會調用avcodec_alloc_context3()創建AVCodecContext結構體。
avformat_find_stream_info():獲取媒體的信息。
av_read_frame():獲取媒體的一幀壓縮編碼數據。其中調用了av_parser_parse2()。 (2) 新增了如下幾個函數。
avcodec_register_all():只注冊編解碼器有關的組件。比如說編碼器、解碼器、比特流濾鏡等,但是不注冊復用/解復用器這些和編解碼器無關的組件。
avcodec_alloc_context3():創建AVCodecContext結構體。
av_parser_init():初始化AVCodecParserContext結構體。
av_parser_parse2():使用AVCodecParser從輸入的數據流中分離出一幀一幀的壓縮編碼數據。 (3) 程序的流程發生了變化。
在“libavcodec+libavformat”的視頻解碼器中,使用avformat_open_input()和avformat_find_stream_info()就可以解析出輸入視頻的信息(例如視頻的寬、高)并且賦值給相關的結構體。因此我們在初始化的時候就可以通過讀取相應的字段獲取到這些信息。
在“純凈”的解碼器則不能這樣,由于沒有上述的函數,所以不能在初始化的時候獲得視頻的參數。“純凈”的解碼器中,可以通過avcodec_decode_video2()獲得這些信息。因此我們只有在成功解碼第一幀之后,才能通過讀取相應的字段獲取到這些信息。
源代碼
/*** 最簡單的基于FFmpeg的視頻解碼器(純凈版)* Simplest FFmpeg Decoder Pure** 雷霄驊 Lei Xiaohua* leixiaohua1020@126.com* 中國傳媒大學/數字電視技術* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020*** 本程序實現了視頻碼流(支持HEVC,H.264,MPEG2等)解碼為YUV數據。* 它僅僅使用了libavcodec(而沒有使用libavformat)。* 是最簡單的FFmpeg視頻解碼方面的教程。* 通過學習本例子可以了解FFmpeg的解碼流程。* This software is a simplest decoder based on FFmpeg.* It decode bitstreams to YUV pixel data.* It just use libavcodec (do not contains libavformat).* Suitable for beginner of FFmpeg.*/#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32 //Windows extern "C" { #include "libavcodec/avcodec.h" }; #else //Linux... #ifdef __cplusplus extern "C" { #endif #include <libavcodec/avcodec.h> #ifdef __cplusplus }; #endif #endif//test different codec #define TEST_H264 1 #define TEST_HEVC 0int main(int argc, char* argv[]) {AVCodec *pCodec;AVCodecContext *pCodecCtx= NULL;AVCodecParserContext *pCodecParserCtx=NULL;FILE *fp_in;FILE *fp_out;AVFrame *pFrame;const int in_buffer_size=4096;uint8_t in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};uint8_t *cur_ptr;int cur_size;AVPacket packet;int ret, got_picture;int y_size;#if TEST_HEVCenum AVCodecID codec_id=AV_CODEC_ID_HEVC;char filepath_in[]="bigbuckbunny_480x272.hevc"; #elif TEST_H264AVCodecID codec_id=AV_CODEC_ID_H264;char filepath_in[]="bigbuckbunny_480x272.h264"; #elseAVCodecID codec_id=AV_CODEC_ID_MPEG2VIDEO;char filepath_in[]="bigbuckbunny_480x272.m2v"; #endifchar filepath_out[]="bigbuckbunny_480x272.yuv";int first_time=1;//av_log_set_level(AV_LOG_DEBUG);avcodec_register_all();pCodec = avcodec_find_decoder(codec_id);if (!pCodec) {printf("Codec not found\n");return -1;}pCodecCtx = avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf("Could not allocate video codec context\n");return -1;}pCodecParserCtx=av_parser_init(codec_id);if (!pCodecParserCtx){printf("Could not allocate video parser context\n");return -1;}//if(pCodec->capabilities&CODEC_CAP_TRUNCATED)// pCodecCtx->flags|= CODEC_FLAG_TRUNCATED; if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {printf("Could not open codec\n");return -1;}//Input Filefp_in = fopen(filepath_in, "rb");if (!fp_in) {printf("Could not open input stream\n");return -1;}//Output Filefp_out = fopen(filepath_out, "wb");if (!fp_out) {printf("Could not open output YUV file\n");return -1;}pFrame = av_frame_alloc();av_init_packet(&packet);while (1) {cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);if (cur_size == 0)break;cur_ptr=in_buffer;while (cur_size>0){int len = av_parser_parse2(pCodecParserCtx, pCodecCtx,&packet.data, &packet.size,cur_ptr , cur_size ,AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);cur_ptr += len;cur_size -= len;if(packet.size==0)continue;//Some Info from AVCodecParserContextprintf("[Packet]Size:%6d\t",packet.size);switch(pCodecParserCtx->pict_type){case AV_PICTURE_TYPE_I: printf("Type:I\t");break;case AV_PICTURE_TYPE_P: printf("Type:P\t");break;case AV_PICTURE_TYPE_B: printf("Type:B\t");break;default: printf("Type:Other\t");break;}printf("Number:%4d\n",pCodecParserCtx->output_picture_number);ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);if (ret < 0) {printf("Decode Error.\n");return ret;}if (got_picture) {if(first_time){printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);first_time=0;}//Y, U, Vfor(int i=0;i<pFrame->height;i++){fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);}printf("Succeed to decode 1 frame!\n");}}}//Flush Decoderpacket.data = NULL;packet.size = 0;while(1){ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);if (ret < 0) {printf("Decode Error.\n");return ret;}if (!got_picture){break;}else {//Y, U, Vfor(int i=0;i<pFrame->height;i++){fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);}printf("Flush Decoder: Succeed to decode 1 frame!\n");}}fclose(fp_in);fclose(fp_out);av_parser_close(pCodecParserCtx);av_frame_free(&pFrame);avcodec_close(pCodecCtx);av_free(pCodecCtx);return 0; }運行結果
通過設定定義在程序開始的宏,確定需要使用的解碼器。//test different codec #define TEST_H264 0 #define TEST_HEVC 1
當TEST_H264設置為1的時候,解碼H.264文件“bigbuckbunny_480x272.h264”。
當TEST_HEVC設置為1的時候,解碼HEVC文件“bigbuckbunny_480x272.hevc”。
解碼后的數據保存成YUV420P格式的文件“bigbuckbunny_480x272.yuv”。
此外,程序在運行的過程中,會打印出AVCodecParserContext中的一些信息,比如說幀類型等等,如下圖所示。
輸入H.264碼流如下所示。
輸出YUV420P像素數據如下圖所示。
下載
Simplest ffmpeg decoder pure工程被作為子工程添加到了simplest ffmpeg player 2工程中。新版的simplest ffmpeg player 2工程的信息如下。
Simplest ffmpeg player 2
項目主頁
SourceForge:https://sourceforge.net/projects/simplestffmpegplayer/
Github:https://github.com/leixiaohua1020/simplest_ffmpeg_player
開源中國:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_player
是最簡單的FFmpeg視頻解碼方面的教程。
通過學習本例子可以了解FFmpeg的解碼流程。
項目包含3個工程:
simplest_ffmpeg_player:標準版,FFmpeg學習的開始。
simplest_ffmpeg_player_su:SU(SDL Update)版,加入了簡單的SDL的Event。
simplest_ffmpeg_decoder_pure:一個純凈的解碼器。
version 2.3========================================
更新-2.4(2015.2.13)===============================
這次考慮到了跨平臺的要求,調整了源代碼。經過這次調整之后,源代碼可以在以下平臺編譯通過:VC++:打開sln文件即可編譯,無需配置。
cl.exe:打開compile_cl.bat即可命令行下使用cl.exe進行編譯,注意可能需要按照VC的安裝路徑調整腳本里面的參數。編譯命令如下。
::VS2010 Environment call "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" ::include @set INCLUDE=include;%INCLUDE% ::lib @set LIB=lib;%LIB% ::compile and link cl simplest_ffmpeg_decoder_pure.cpp /link avcodec.lib avutil.lib swscale.lib ^ /OPT:NOREFMinGW:MinGW命令行下運行compile_mingw.sh即可使用MinGW的g++進行編譯。編譯命令如下。
g++ simplest_ffmpeg_decoder_pure.cpp -g -o simplest_ffmpeg_decoder_pure.exe \ -I /usr/local/include -L /usr/local/lib -lavcodec -lavutil -lswscaleGCC:Linux或者MacOS命令行下運行compile_gcc.sh即可使用GCC進行編譯。編譯命令如下。
gcc simplest_ffmpeg_decoder_pure.cpp -g -o simplest_ffmpeg_decoder_pure.out -I /usr/local/include -L /usr/local/lib \ -lavcodec -lavutil -lswscalePS:相關的編譯命令已經保存到了工程文件夾中
CSDN項目下載地址:http://download.csdn.net/detail/leixiaohua1020/8443943
SourceForge、Github等上面已經更新。
增加了下列工程:simplest_ffmpeg_decoder:一個包含了封裝格式處理功能的解碼器。使用了libavcodec和libavformat。simplest_video_play_sdl2:使用SDL2播放YUV的例子。simplest_ffmpeg_helloworld:輸出FFmpeg類庫的信息。
CSDN項目下載地址:http://download.csdn.net/detail/leixiaohua1020/8924321
SourceForge、Github等上面已經更新。
總結
以上是生活随笔為你收集整理的最简单的基于FFmpeg的解码器-纯净版(不包含libavformat)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MP4/MP3解封装ffmpeg(十三)
- 下一篇: 2021 MCU WiFi竞争新格局,国