ffmpeg4,ffmpeg33
在引言中,我们介绍了FFmpeg的解封装,实现了从视频文件中提取音频流和视频流并分别输出,使用ffplay进行播放和验证。
今天我们用FFmpeg解码视频流,把视频解码成YUV输出到一个文件,然后用ffplay播放YUV图像。
关于YUV的知识,笔者之前也做过一些笔记,但是写的比较简单。你可以在网上寻找更详细的信息:
音视频基础知识-YUV图像
关于使用FFmpeg进行视频解码的文章,我之前写过类似的文章《Android使用ffmpeg解码视频为YUV》。
但是这篇文章有一个错误,就是写出来的YUV方法不具有通用性。对于一些从视频解码出来的YUV,按照文章中的方法写,可能会出现花屏播放甚至无法播放的情况。如果对此有任何误解,
我深感抱歉。在这里我想说明一下,作者发布的这些博客仅供笔记或交流,不具有权威性。意见汇总仅限本人理解,不保证全部准确。
AVFrame简介与解封装相比,我们在解码视频时需要使用一种新的结构AVFrame。
AVFrame可以说是对应AVPacket的结构。由于AVPacket表示的是音视频包解码或编码前的数据,所以AVFrame是音视频包解码或编码后的原始数据包。
AVFrame包含视频帧或音频帧的持续时间和定时等时间信息,以及采样率、采样格式、图片格式和帧类型等相关信息。
在FFmpeg中,我们使用av_frame_alloc()分配一个AVFrame,使用av_frame_free释放一个AVFrame,使用函数av_frame_get_buffer在AVFrame内部分配一个数据缓冲区。
视频回顾前解封的一张图:
视频解码阶段发生在函数av_read_frame之后。如果读取的资源包是视频类型的,则将其发送到解码器进行解码。
再来看另一张图。这张图片主要介绍了视频解码过程中用到的一些结构的作用:
以下是视频解码的两个重要步骤:
1.为jie码配置ma解码器。
配置马杰解码器的这一步骤可以分为四个小步骤:
a、找到解码器
b .分配解码器上下文
c .根据视频流信息为解码器上下文配置解码参数。
d、打开解码器。
用于这四个小步骤的FFmpeg的API是:
//找到解码器
Avcodec_find_decoder或avcodec_find_decoder
//分配解码器上下文
av编解码器_分配_上下文3
//根据视频流信息为解码器上下文配置解码参数。
avcodec _参数_至_上下文
//打开解码器
Avcodec_open22,发送解码后的数据包,获取解码后的YUV数据帧
解码阶段使用的两个关键API是avcodec_send_packet和avcodec_receive_frame,其中avcodec_send_packet表示向解码器发送一个视频数据包,然后用avcodec_receive_frame接收它。
解码数据帧(即YUV数据)。Avcodec_send_packet和avcodec_receive_frame不是一对一的调用,而是avcodec_send_packet的一次调用,可能对应n个avcodec_receive_frame函数。
打电话。因为解码器内部有缓冲区和参考帧,所以不可能在每次发送数据包时都解码一帧数据。可能存在这样的情况,有几个包被发送进来,但是暂时没有数据帧被解码输出,或者可能存在这样的情况,在某个时间点有一个包被发送进来,然后输出N个数据帧。
主要代码如下:
视频解码器. h
#包含字符串
类别视频解码器{
公共:
video decode();
~ video decoder();
void decode _ video(STD:string media _ path,STD:string YUV _ path);
};以下是实现文件:
视频解码器. cpp
#包含 VideoDecoder.h
#包括iostream
外部 C{
#包含libavformat/avformat.h
#包含libavcodec/avcodec.h
#包含libavutil/avutil.h
#包含libavutil/log.h
}
视频解码器:视频解码器(){
}
视频解码器:~视频解码器()
}
请参阅视讯解码器* decode _ video(STD:string media _ path、std:string yuv_path)>
avformat上下文* avformat上下文=零tr:
avodecccontext * avodecccontext=零tr;
av format context=av format _ alloc _ context();
av format _ open _ input(av format context,media_path.c_str()、nulltr、nulltr);
av_dump_format(avFormatContext,0,media_path.c_str(),0);
int VIDEO _ index=av _ find _ best _ stream(av format context,AVMEDIA_TYPE_VIDEO,-1,-1,zero tr,0);
if(video _ index 0)}
标准:cout没有找到视频STD:end;
}
const AVC odec * AVC odec=AVC odec _ find _ decoder(av format context-streams[video _ index]-code cpar编解码器_ id);
avodec context=avodec _ alloc _ context 3(avodec);
AVO dec _ parameters _ to _ context(AVO dec context,av format context-streams[video _ index]-code cpar);
int ret=avodec _ open 2(avodec上下文、avodec、nulltr);
S7-1200可编程控制器
标准:cout解码器打开失败STD:end;
}
档案*yuv_file=fopen(yuv_path.c_str(), WB );
av packet * av packet=av _ packet _ alloc();
av frame * av frame=av _ frame _ alloc();
while(true)}
ret=av _ read _ frame(avformat上下文,avPacket):
S7-1200可编程控制器
标准:cout文件读取完毕STD:end;
打断;打断;
} else if(video _ index==av packet-stream _ index)}
ret=avodec _ send _ packet(avodec上下文,avPacket):
S7-1200可编程控制器
标准:cout视频发送解码失败* av _ err 2 str(ret)STD:end;
}
while(true)}
ret=avodec _ receive _ frame(avodec上下文,avFrame):
if(ret==averror(eagain) ret==averror _ eof)>
STD:cout AVO dec _ receive _ frame: av _ err 2 str(ret)STD:end;
打断;打断;
} else if(ret 0)}
标准:cout视频解码失败* STD:end;
返回;
}否则
标准:cout写入尤夫文件av frame-line size[0]: av frame-line size[0] av frame-width: av frame-width STD:end;
std:cout avFrame格式: avFrame格式STD:end;
//播放播放-我的YUV文件路径-pixel_format yuv420p帧速率25-视频_尺寸640x480
//frame- linesize[1]对齐的问题
//正确写法直线化[]代表每行的字节数量,所以每行的偏移是直线化[]
//成员日期是个指针数组,每个成员所指向的就是尤夫三个分量的实体数据了,成员直线化是指对应于每一行的大小,为什么需要这个变量,是因为在尤夫格式和RGB(三原色)格式时,每行的大小不一定等于图像的宽度
//
for(int j=0);j avFrame高度;(j)
fwrite(av frame-data[0]j * av frame-line size[0],1,avFrame- width,YUV _ file);
for(int j=0);j avFrame-高度/2;(j)
fwrite(av frame-data[1]j * av frame-line size[1],1,avFrame- width/2,YUV _ file);
for(int j=0);j avFrame-高度/2;(j)
fwrite(av frame-data[2]j * av frame-line size[2],1,avFrame- width/2,YUV _ file);
//错误写法用资料来源:200 kbps .766 x 322 _ 10s。h 264测试时可以看出该种方法是错误的
//如果框架。width==avframe-line size[0]则可以用这种方式写入
//写入然后呢分量
//fwrite(avFrame- data[0],1,avFrame- width * avFrame- height,YUV _ file);//Y
////写入-你好分量
//fwrite(avFrame- data[1],1,(av frame-width)*(av frame-height)/4,YUV _ file);//U:宽高均是然后呢的一半
////写入五、导言分量
//fwrite(avFrame- data[2],1,(av frame-width)*(av frame-height)/4,YUV _ file);//V:宽高均是然后呢的一半
}
}
}
av_packet_unref(avPacket):
}
ffle ush(YUV _ file):
av_packet_free( avPacket):
av_frame_free( avFrame):
如果(零精度!=YUV _ file .]
fclose(yuv_file):
yuv_file=空值:
}
}对于解码出来的尤夫输出文件,我们可以使用玩游戏命令来进行播放:
//其中640x480需要替换成自己解码的视频的真实宽高
播放-我的YUV文件路径-pixel_format yuv420p帧速率25-视频_尺寸640x480针对导读中提到的尤夫非通用写法,笔者在代码中做了简单的注释。更多的资料可以查询关于ffmpeg(ffmpeg)内存对齐的问题,例如针对图像来说并不是像素对齐而是字节对齐的。
注意,在上面的例子中,视频文件被读取后,编码器内部的数据没有被刷新,这可能会导致视频的最后几帧丢失。比较规范的写入方式应该是在文件读取后再次调用函数avcodec_send_packet,但是需要传入一个空的视频包。
然后循环调用avcodec_receive_frame得到解码器中缓冲的所有数据帧。
别忘了释放资源。
推荐FFmpeg系列1-开发环境构建
FFmpeg串行2-分离视频和音频
关注我,共同进步,生活不止编码!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。