最簡單的基於FFmpeg的解碼器-純淨版(不包含libavformat)
=====================================================
最簡單的基於FFmpeg的視訊播放器系列文章列表:
=====================================================
本文記錄一個更加“純淨”的基於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()註冊所有編解碼器相關的元件。(2)新增瞭如下幾個函式。
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()。
avcodec_register_all():只註冊編解碼器有關的元件。比如說編碼器、解碼器、位元流濾鏡等,但是不註冊複用/解複用器這些和編解碼器無關的元件。(3)程式的流程發生了變化。
avcodec_alloc_context3():建立AVCodecContext結構體。
av_parser_init():初始化AVCodecParserContext結構體。
av_parser_parse2():使用AVCodecParser從輸入的資料流中分離出一幀一幀的壓縮編碼資料。
在“libavcodec+libavformat”的視訊解碼器中,使用avformat_open_input()和avformat_find_stream_info()就可以解析出輸入視訊的資訊(例如視訊的寬、高)並且賦值給相關的結構體。因此我們在初始化的時候就可以通過讀取相應的欄位獲取到這些資訊。
在“純淨”的解碼器則不能這樣,由於沒有上述的函式,所以不能在初始化的時候獲得視訊的引數。“純淨”的解碼器中,可以通過avcodec_decode_video2()獲得這些資訊。因此我們只有在成功解碼第一幀之後,才能通過讀取相應的欄位獲取到這些資訊。
原始碼
/**
* 最簡單的基於FFmpeg的視訊解碼器(純淨版)
* Simplest FFmpeg Decoder Pure
*
* 雷霄驊 Lei Xiaohua
* [email protected]
* 中國傳媒大學/數字電視技術
* 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 0
int 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_HEVC
enum AVCodecID codec_id=AV_CODEC_ID_HEVC;
char filepath_in[]="bigbuckbunny_480x272.hevc";
#elif TEST_H264
AVCodecID codec_id=AV_CODEC_ID_H264;
char filepath_in[]="bigbuckbunny_480x272.h264";
#else
AVCodecID codec_id=AV_CODEC_ID_MPEG2VIDEO;
char filepath_in[]="bigbuckbunny_480x272.m2v";
#endif
char 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 File
fp_in = fopen(filepath_in, "rb");
if (!fp_in) {
printf("Could not open input stream\n");
return -1;
}
//Output File
fp_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 AVCodecParserContext
printf("[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, V
for(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 Decoder
packet.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, V
for(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
專案主頁
本程式實現了視訊檔案的解碼和顯示(支援HEVC,H.264,MPEG2等)。是最簡單的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:NOREF
MinGW: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 -lswscale
GCC: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 -lswscale
PS:相關的編譯命令已經儲存到了工程資料夾中
SourceForge、Github等上面已經更新。
更新-2.5(2015.7.17)==============================增加了下列工程:
simplest_ffmpeg_decoder:一個包含了封裝格式處理功能的解碼器。使用了libavcodec和libavformat。simplest_video_play_sdl2:使用SDL2播放YUV的例子。simplest_ffmpeg_helloworld:輸出FFmpeg類庫的資訊。SourceForge、Github等上面已經更新。
相關推薦
最簡單的基於FFmpeg的解碼器-純淨版(不包含libavformat)
=====================================================最簡單的基於FFmpeg的視訊播放器系列文章列表:=====================================================本文記錄一個更
最簡單的基於FFmpeg的libswscale的示例(YUV轉RGB)
=====================================================最簡單的基於FFmpeg的libswscale的示例系列文章列表:====================================================
最簡單的基於FFmpeg的封裝格式處理 視音訊分離器簡化版(demuxer-simple)
=====================================================最簡單的基於FFmpeg的封裝格式處理系列文章列表:=====================================================簡介打算記錄一
最簡單的基於FFmpeg的封裝格式處理:視音訊分離器簡化版(demuxer-simple)
=====================================================最簡單的基於FFmpeg的封裝格式處理系列文章列表:=====================================================簡介打算記錄
最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭)
malloc projects == 格式 mac 跨平臺 buffer 版本 span =====================================================最簡單的基於FFmpeg的AVDevice樣例文章列表:最簡單的基於FFmp
最簡單的基於FFmpeg的AVfilter樣例(水印疊加)
中國 exiting endpoint write mod 無需 它的 fopen cap =====================================================最簡單的基於FFmpeg的AVfilter樣例系列文章:最簡單的基於FF
最簡單的視訊編碼器:基於libvpx(編碼YUV為VP8)
=====================================================最簡單的視訊編碼器系列文章列表:最簡單的視訊編碼器:編譯=====================================================本文記錄
FFmpeg的H.264解碼器原始碼簡單分析:解碼器主幹部分
=====================================================H.264原始碼分析文章列表:【編碼 - x264】【解碼 - libavcodec H.264 解碼器】================================
【轉】用Python建立最簡單的web服務器
web服務 localhost 服務器 pos 根目錄 cal body -m -- 利用Python自帶的包可以建立簡單的web服務器。在DOS裏cd到準備做服務器根目錄的路徑下,輸入命令: python -m Web服務器模塊 [端口號,默認8000]
搭建一個最簡單的node服務器
node string str console 參數 地址 param color json 搭建一個最簡單的node服務器 1、創建一個Http服務並監聽8888端口 2、使用url模塊 獲取請求的路由和請求參數 var http = require(‘
centos7純淨版(最小安裝)部署docker-ce詳情
一、安裝完純淨版centos7之後檢視本機ip #ip addr 至於為什麼用ip addr 而不用ifconfig請看我之前的文章有所介紹 https://mp.csdn.net/mdeditor/82736254 二、因為個人習慣原因,我使用xshell登入本
王權富貴基礎篇:用numpy做最簡單的單層感知器
上面就是最簡單的單層感知器,由多個輸入,一個輸出。 這個是一個完整的流程,做到右是正向傳播,右到左是更新權值。 雖然現在有TF,CAFF可以一句話寫出下面這些,不過對於努力提高的同學來說,用基礎程式碼實現基礎功能還是很有意義的。 現在開始用numpy搭建網路
一個最簡單的HTML5播放器
<!DOCTYPE HTML> <html> <head> <title>Video Player Html5</title> </he
最簡單的視訊編碼器 編譯(libx264,libx265,libvpx)
=====================================================最簡單的視訊編碼器系列文章列表:最簡單的視訊編碼器:編譯=====================================================最近研究了
一個最簡單的VLC播放器的實現—MFC
實現了一個最簡單的播放器功能,能夠進行視訊的播放和暫停。 搭建好了一個基本的“類”框架,只需要在“類”中編寫相應的功能函式就可以繼續開發其他功能。 1.環境配置: 新建一個MyPlayer專案,將VLC打包環境中的Debug、include、
最簡單的編碼解碼程式
#include <stdio.h> char encode(char ch); int main(){ char ch,ch1,ch2; scanf("%c",&
最簡單的幾何著色器(Geometry Shader)【OpenGL】【GLSL】
以繪製模型的法線的為例,效果如圖:Torus:Dragon:關鍵程式碼如下:1.頂點著色器 static const char * vs_source[] = { "#version 410 core
利用Service打造最簡單的音樂播放器
新建一個moudle,命名為MediaPlayer_ServiceDemo 1.準備素材一首歌,將音樂資源放入res/raw 目錄下,如果沒有raw目錄,就新建一個。 2.準備佈局檔案,一共5個功能,開始、停止、暫停、退出應用繼續播放、退出應用停止播放。
HTML5用audio標籤做一個最簡單的音訊播放器
在做系統的時候,要求做一個音訊播放器,就在網上查找了一些資料,發現這樣的資料還是很千篇一律的,EasyUI框架並沒有給我們一個音訊播放器的功能,在bootstrap上有,但是也是結合html5來寫的,因此,我們在這裡就用純的html5血一個音訊播放器,如何播放本地的音訊。
Netty之實現自定義簡單的編解碼器一(MessageToByteEncoder和ByteToMessageDecoder)
1、關於自定義編碼器的簡介 在這裡實現的編解碼器很簡單。編碼器的功能實現的是,int--->byets的編碼;解碼器實現的是,bytes--->int的解碼。 2、編碼器的實現 import io.netty.buffer.ByteBuf; imp