如何使用libavcodec将.h264码流文件解码为.yuv图像序列?
一.打开和关闭输入文件和输出文件
//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图像序列?的更多相关文章
- H264码流打包分析(精华)
H264码流打包分析 SODB 数据比特串-->最原始的编码数据 RBSP 原始字节序列载荷-->在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若 ...
- H264码流解析及NALU
ffmpeg 从mp4上提取H264的nalu http://blog.csdn.net/gavinr/article/details/7183499 639 /* bitstream fil ...
- H264码流中SPS PPS详解<转>
转载地址:https://zhuanlan.zhihu.com/p/27896239 1 SPS和PPS从何处而来? 2 SPS和PPS中的每个参数起什么作用? 3 如何解析SDP中包含的H.264的 ...
- 从H264码流中获取视频宽高 (SPS帧) 升级篇
之前写过 <从H264码流中获取视频宽高 (SPS帧)> . 但发现很多局限性,而且有时解出来是错误的. 所以重新去研究了. 用了 官方提供的代码库来解析. 花了点时间,从代码库里单独把解 ...
- 从H264码流中获取视频宽高 (SPS帧)
获取.h264视频宽高的方法 花了2个通宵终于搞定.(后面附上完整代码) http://write.blog.csdn.net/postedit/7852406 图像的高和宽在H264的SPS帧中.在 ...
- h264码流分析
---------------------------------------------------------------------------------------------------- ...
- hisi出的H264码流结构
hisi出的H264码流结构: IDR帧结构如下: 开始码 + nalu + I帧 + 开始码 + nalu + SPS + 开始码 + nalu + PPS + ...
- RTP协议全解析(H264码流和PS流)(转)
源: RTP协议全解析(H264码流和PS流)
- RTP协议全解析(H264码流和PS流)
转自:http://blog.csdn.net/chen495810242/article/details/39207305 写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个 ...
- (转)RTP协议全解(H264码流和PS流)
写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希 ...
随机推荐
- [MySQL]set autocommit=0与start transaction的区别[转载]
set autocommit=0指事务非自动提交,自此句执行以后,每个SQL语句或者语句块所在的事务都需要显示"commit"才能提交事务. 1.不管autocommit 是1还是 ...
- [Java]排序算法>交换排序>【冒泡排序】(O(N*N)/稳定/N较小/有序/顺序+链式)
1 冒泡排序 1.1 算法思想 交换排序的基本思想:两两比较待排序记录的关键字,一旦发现2个记录不满足次序要求时,则:进行交换,直到整个序列全部满足要求为止. 1.2 算法特征 属于[交换排序] 冒泡 ...
- Java学习笔记09
1. 多态 1.1 多态 多态是指同一种行为具有多种不同的表现形式. 前提 有继承或者实现关系 有方法重写(没有重写多态就没有意义) 父类引用指向子类对象 格式 父类类型 变量名 = new 子类 ...
- Yapi及Swgger使用+注解
1.Yapi 1.1 介绍 YApi 是高效.易用.功能强大的 api 管理平台,旨在为开发.产品.测试人员提供更优雅的接口管理服务.可以帮助开发者轻松创建.发布.维护 API,YApi 还为用户提供 ...
- day03-商家查询缓存02
功能02-商铺查询缓存02 知识补充 (1)缓存穿透 https://blog.csdn.net/qq_45637260/article/details/125866738 缓存穿透(cache pe ...
- 可视化大屏的终极解决方案居然这么简单,vue-autofit一行全搞定!
可视化大屏适配/自适应现状 可视化大屏的适配是一个老生常谈的话题了,现在其实不乏一些大佬开源的自适应插件.工具但是我为什么还要重复造轮子呢?因为目前市面上适配工具每一个都无法做到完美的效果,做出来的东 ...
- 揭开神秘面纱,会stream流就会大数据
目录 准备工作 1.map类 1.1 java stream map 1.2 spark map 1.2.1 MapFunction 1.2.2 MapPartitionsFunction 2.fla ...
- Costura.Fody 使用问题
1. Costura.Fody 引用后,未能正常合并资源文件.用着用着就不行了 解决方案:在csproj所在的文件目录,找到FodyWeavers.xml,添加<Costura/> 1 & ...
- 【C#】图片上传并根据长宽大小进行正方形、长方形及等比缩放。
#region 正方型裁剪并缩放 /// <summary> /// 正方型裁剪 /// 以图片中心为轴心,截取正方型,然后等比缩放 /// 用于头像处理 /// </summary ...
- 2022-10-09:我们给出了一个(轴对齐的)二维矩形列表 rectangles 。 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐
2022-10-09:我们给出了一个(轴对齐的)二维矩形列表 rectangles . 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐标 ...