如何使用libavcodec将.h264码流文件解码为.yuv图像序列?

2023-10-22 13:47:53 浏览数 (1)

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

代码语言:javascript复制
//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是码流解析器的句柄,其作用是从一串二进制数据流中解析出

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

代码语言:javascript复制
//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."<<endl;
        return -1;
    }
    parser= av_parser_init(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.输出解码获取的图像帧到输出文件

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

代码语言:javascript复制
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

代码语言:javascript复制
//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."<<endl;
            return -1;
        }
        data=inbuf;
        while(data_size>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."<<endl;
                return -1;
            }
            data =result;
            data_size-=result;
            if(pkt->size){
                cout<<"Parsed packet size:"<<pkt->size<<endl;
                decode_packet(false);
            }
        }
    }
    decode_packet(true);
    return 0;
}
static int32_t decode_packet(bool flushing){
    int32_t result=0;
    result= avcodec_send_packet(codec_ctx,flushing? nullptr:pkt);
    if(result<0){
        cerr<<"Error:failed to send packet,result:"<<result<<endl;
        return -1;
    }
    while(result>=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:"<<result<<endl;
            return -1;
        }
        if(flushing){
            cout<<"Flushing:";
        }
        cout<<"Write frame pic_num:"<<frame->coded_picture_number<<endl;
        write_frame_to_yuv(frame);
    }
    return 0;
}

  输出解码图像数据:

代码语言:javascript复制
//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;
}

  关闭解码器:

代码语言:javascript复制
//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函数的实现如下:

代码语言:javascript复制
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

0 人点赞