如何使用libavcodec将.h264码流文件解码为.yuv图像序列?
阅读原文时间:2023年09月06日阅读:1

一.打开和关闭输入文件和输出文件

//io_data.cpp
static FILE* input_file= nullptr;
static FILE* output_file= nullptr;

int32_t open_input_output_files(const char* input_name,const char* output_name){
if(strlen(input_name)==0||strlen(output_name)==0){
cout<<"Error:empty input or output file name."<<endl;
return -1;
}
close_input_output_files();
input_file=fopen(input_name,"rb");//rb:读取一个二进制文件,该文件必须存在
if(input_file==nullptr){
cerr<<"Error:failed to open input file."<<endl;
return -1;
}
output_file=fopen(output_name,"wb");//wb:打开或新建一个二进制文件,只允许写
if(output_file== nullptr){
cout<<"Error:failed to open output file."<<endl;
return -1;
}
return 0;
}
void close_input_output_files(){
if(input_file!= nullptr){
fclose(input_file);
input_file= nullptr;
}
if(output_file!= nullptr){
fclose(output_file);
output_file= nullptr;
}
}

二.视频解码器的初始化

  解码器的初始化和编码器初始化类似,区别仅在于需要多创建一个AVCodecParserContext类型对象。AVCodecParserContext是码流解析器的句柄,其作用是从一串二进制数据流中解析出

符合某种编码标准的码流包。

//video_decoder_core.cpp
static const AVCodec* codec= nullptr;
static AVCodecContext* codec_ctx= nullptr;
static AVCodecParserContext* parser= nullptr;
static AVFrame* frame= nullptr;
static AVPacket* pkt= nullptr;
int32_t init_video_decoder(){
codec= avcodec_find_decoder(AV_CODEC_ID_H264);
if(!codec){
cerr<<"Error:could not find codec."<id);
if(!parser){
cerr<<"Error:could not init parser."<<endl;
return -1;
}
codec_ctx= avcodec_alloc_context3(codec);
if(!codec_ctx){
cerr<<"Error:could not alloc codec_ctx."<<endl;
return -1;
}
int32_t result= avcodec_open2(codec_ctx,codec, nullptr);
if(result<0){
cerr<<"Error:could not open codec."<<endl;
return -1;
}
frame=av_frame_alloc();
if(!frame){
cerr<<"Error:could not alloc frame."<<endl;
return -1;
}
pkt=av_packet_alloc();
if(!pkt){
cerr<<"Error:could not alloc packet."<<endl;
return -1;
}
return 0;
}

三.解码循环体

  解码循环体至少需要实现以下三个功能:

    1.从输入源中循环获取码流包

    2.将当前帧传入解码器,获取输出的图像帧

    3.输出解码获取的图像帧到输出文件

  从输入文件中读取数据添加到缓存,并判断输入文件是否到达结尾:

io_data.cpp
int32_t end_of_input_file(){
return feof(input_file);
}
int32_t read_data_to_buf(uint8_t* buf,int32_t size,int32_t& out_size){
int32_t read_size=fread(buf,1,size,input_file);
if(read_size==0){
cerr<<"Error:read_data_to_buf failed."<<endl;
return -1;
}
out_size=read_size;
return 0;
}

  解码循环体:在解码循环体中,有一个核心函数av_parser_parse2(),它的作用是从数据缓冲区中解析出AVPacket结构。当调用av_parser_parse2()函数时,首先通过参数指定保存

某一段码流数据的缓存区及其长度,然后通过输出poutbuf指针或poutbuf_size的值来判断是否读取了一个完整的AVPacket结构,只有当poutbuf指针为非空或

poutbuf_size值为正时,才表示解析出一个完整的AVPacket

//video_decoder_core.cpp
int32_t decoding(){
uint8_t inbuf[INBUF_SIZE+AV_INPUT_BUFFER_PADDING_SIZE]={0};
int32_t result=0;
uint8_t* data= nullptr;
int32_t data_size=0;
while(!end_of_input_file()){
result=read_data_to_buf(inbuf,INBUF_SIZE,data_size);
if(result<0){ cerr<<"Error:read_data_to_buf failed."<0){
result= av_parser_parse2(parser,codec_ctx,&pkt->data,&pkt->size,data,data_size,AV_NOPTS_VALUE,AV_NOPTS_VALUE,0);
if(result<0){ cerr<<"Error:av_parser_parse2 failed."<size){
cout<<"Parsed packet size:"<size<=0){
result= avcodec_receive_frame(codec_ctx,frame);
if(result==AVERROR(EAGAIN)||result==AVERROR_EOF){
return 1;
}
else if(result<0){ cerr<<"Error:failed to receive frame,result:"<coded_picture_number<<endl;
write_frame_to_yuv(frame);
}
return 0;
}

  输出解码图像数据:

//io_data.cpp
int32_t write_frame_to_yuv(AVFrame* frame){
uint8_t** pBuf=frame->data;
int* pStride=frame->linesize;
for(size_t i=0;i<3;i++){ int32_t width=(i==0?frame->width:frame->width/2);
int32_t height=(i==0?frame->height:frame->height/2);
for(size_t j=0;j<height;j++){
fwrite(pBuf[i],1,width,output_file);
pBuf[i]+= pStride[i];
}
}
return 0;
}

  关闭解码器:

//video_decoder_core.cpp
void destroy_video_decoder(){
av_parser_close(parser);
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
av_packet_free(&pkt);
}

  最终,main函数的实现如下:

int main(){
const char* input_file_name="../input.h264";
const char* output_file_name="../output.yuv";
int32_t result= open_input_output_files(input_file_name,output_file_name);
if(result<0){
return result;
}
result=init_video_decoder();
if(result<0){
return result;
}
result=decoding();
if(result<0){
return result;
}
destroy_video_decoder();
close_input_output_files();
return 0;
}

  解码完成后,可以使用ffplay播放输出的.yuv图像文件:

  ffplay -f rawvideo -video_size 1920x1080 -i output.yuv