如何使用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的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希 ...
随机推荐
- .NET中使用Redis总结 —— 1.Redis搭建
注:关于如何在windows,linux下配置redis,详见这篇文章:) 下载地址:http://redis.io/download Redis官方是不支持windows的,只是 Microsoft ...
- 25-tree shaking(树摇)
const { resolve } = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin') ...
- flex:1的情况下,overflow:auto没有生效的问题
flex:1的元素的父元素必须保证高度或者宽度有具体的数值:如果父元素的高度或者宽度也是flex:1自适应的,最好在父元素上也设置overflow:auto,这样子元素的overflow:auto生效 ...
- [C++提高编程] 3.2 vector容器
文章目录 3.2 vector容器 3.2.1 vector基本概念 3.2.2 vector构造函数 3.2.3 vector赋值操作 3.2.4 vector容量和大小 3.2.5 vector插 ...
- fiddler简单使用
fiddler简单使用 下载 网上找资源下载 安装 一路同意就可以了 使用 1.配置https证书 这些项全选,然后信任证书,就可以抓到ssl的包 2.改变网络端口 3.改写网页代码 以爬虫网为例,先 ...
- 大米cms爆破后台及支付逻辑漏洞
又找到个网站挖洞,我来康康. 大米手机是个什么鬼手机??看一下吧 这个支付页面好熟悉,可能存在支付逻辑漏洞,咱们用burp改个包看看. 先支付一个看看 把包里那个=1改成0试试~ 证实确实存在支付逻辑 ...
- 2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写。
2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写. 答案2023-03-05: 使用 github.com/moonfdd/ffmpeg-go 库 ...
- 2022-09-11:arr是一个可能包含重复元素的整数数组,我们将这个数组分割成几个“块”, 并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。 我们最多能将数组分成
2022-09-11:arr是一个可能包含重复元素的整数数组,我们将这个数组分割成几个"块", 并将这些块分别进行排序.之后再连接起来,使得连接的结果和按升序排序后的原数组相同. ...
- vue全家桶进阶之路12:监听器 watch
在Vue2中,监听器(watch)用于监测数据的变化,并在数据变化时执行一些操作.监听器可以用来响应用户输入.观察数据变化.执行异步操作等. 监听器的使用方法如下: 在组件的watch选项中定义一个或 ...
- 【故障公告】博客站点一台阿里云负载均衡被DDoS攻击
13:06 收到阿里云的电话与邮件通知,博客站点的一台阿里云负载均衡因被 DDoS 攻击被关进黑洞(所有访问被屏蔽),部分用户的访问受影响,由此给您带来麻烦,请您谅解. 您的IP:x.x.x.x 实例 ...