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

//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."<<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.输出解码获取的图像帧到输出文件

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

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."<<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;
}

  输出解码图像数据:

//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

  

  

      

    

如何使用libavcodec将.h264码流文件解码为.yuv图像序列?的更多相关文章

  1. H264码流打包分析(精华)

    H264码流打包分析 SODB 数据比特串-->最原始的编码数据 RBSP 原始字节序列载荷-->在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若 ...

  2. H264码流解析及NALU

    ffmpeg 从mp4上提取H264的nalu http://blog.csdn.net/gavinr/article/details/7183499 639     /* bitstream fil ...

  3. H264码流中SPS PPS详解<转>

    转载地址:https://zhuanlan.zhihu.com/p/27896239 1 SPS和PPS从何处而来? 2 SPS和PPS中的每个参数起什么作用? 3 如何解析SDP中包含的H.264的 ...

  4. 从H264码流中获取视频宽高 (SPS帧) 升级篇

    之前写过 <从H264码流中获取视频宽高 (SPS帧)> . 但发现很多局限性,而且有时解出来是错误的. 所以重新去研究了. 用了 官方提供的代码库来解析. 花了点时间,从代码库里单独把解 ...

  5. 从H264码流中获取视频宽高 (SPS帧)

    获取.h264视频宽高的方法 花了2个通宵终于搞定.(后面附上完整代码) http://write.blog.csdn.net/postedit/7852406 图像的高和宽在H264的SPS帧中.在 ...

  6. h264码流分析

    ---------------------------------------------------------------------------------------------------- ...

  7. hisi出的H264码流结构

    hisi出的H264码流结构: IDR帧结构如下: 开始码 + nalu + I帧    +    开始码 + nalu + SPS    +     开始码 + nalu + PPS    +    ...

  8. RTP协议全解析(H264码流和PS流)(转)

    源: RTP协议全解析(H264码流和PS流)

  9. RTP协议全解析(H264码流和PS流)

    转自:http://blog.csdn.net/chen495810242/article/details/39207305 写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个 ...

  10. (转)RTP协议全解(H264码流和PS流)

    写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希 ...

随机推荐

  1. @property、关联关系、继承、多继承

    1.@property使用来修饰属性的,将属性私有化改为公有化,但是可以设置属性条件 格式: class Person: def __init__(self,name,age): self.name ...

  2. 多进程和多线程,Thread模块 GIL全局解释锁, 进程池与线程池,协程

    1.多进程实现TCP服务端并发: import socket from multiprocessing import Process def get_server(): server = socket ...

  3. Web界面元素的测试

    文本框 测试点 输入正常的字母或数字 输入超长字符.例如在"名称"框中输入超过允许边界个数的字符,假设最多255个字符,尝试输入256个字符,检查程序能否正确处理 输入默认值,空白 ...

  4. 【原型设计模式详解】C/Java/JS/Go/Python/TS不同语言实现

    简介 原型模式(Prototype Pattern)是一种创建型设计模式,使你能够复制已有对象,而无需使代码依赖它们所属的类,同时又能保证性能. 这种模式是实现了一个原型接口,该接口用于创建当前对象的 ...

  5. Linux修改远程命令端口

    Linux修改远程命令端口 执行以下命令,修改sshd服务的配置文件 vi /etc/ssh/sshd_config 找到port,默认是注释了的 22 打开注释,将22改成想要的端口号 重启服务 s ...

  6. ts中接口

    前言:ts定义接口的任意一个属性 interface IPerson { name: string age: number family?: any[] // Error,因为不是任意类型的子集 [p ...

  7. Jenkins-Email Extension Plugin邮件配置

    前言:此文为 Jenkins 2.346 版本 一.配置准备 1.安装 Email Extension Plugin 插件 二.配置邮件 Email Extension Plugin 部分 1.进入系 ...

  8. day09-达人探店

    功能04-达人探店 5.功能04-达人探店 5.1发布&查看探店笔记 5.1.1发布探店笔记 探店笔记类似点评网站的评价,往往是图文结合.对应的表有两个: tb_blog:探店笔记表,包含笔记 ...

  9. golang中一种不常见的switch语句写法

    最近翻开源代码的时候看到了一种很有意思的switch用法,分享一下. 注意这里讨论的不是typed switch,也就是case语句后面是类型的那种. 直接看代码: func (s *systemd) ...

  10. PaddlePaddle 飞桨复现 VGG16

    import paddle.nn as nn class VGG16(nn.Layer): def __init__(self, num_classes=1000): super(VGG16, sel ...