1. FFMPEG
FFMPEG: fast forward moving picture experts group,音视频编解码工具及开发套件。由法国Fabrice Bellard在2000年开发出出版。
1.1基本组成:
- AVFormat: 封装模块,媒体封装/解封装格式,RTMP/RTSP/MMS/HLS等网络封装协议。
- AVCodec: 常用多媒体编解码格式,支持自带的MPEG4,AAC,MJPEG等自带格式和AVC等第三方库。
- AVFilter: 通用音频、视频、字幕等滤镜处理框架。
- AVDevice: 输入输出设备库。
- AVUtil: 核心工具库。
- swreasmple: 音频转换计算模块。
- swscale: 视频图像转换计算模块。
ffplay
ffpaly video_name #播放视频文件
ffplay -video_size 1920X1080 test.yuv #播放yuv等原始视频格式
ffplay -ar 16000 -channels 1 -f s16le -i xxx.pcm/wav #播放音频
FFMPEG基本命令
#帮助命令
ffmpeg --help
ffmpeg --help long #高级
ffmpeg --help full #全部
#支持的视频文件封装格式
ffmpeg -formats
DE h264 raw H.264 video #封装、解封装 媒体文件格式 文件格式详细说明
#支持的解码格式
ffmpeg -decoders
VFS..D h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 #第一列6个字段:视频/音频/字幕,帧级别多线程,分片级别多线程,编码为实验版本,draw horiz band模式支持,直接渲染模式支持。 第二列:编码格式 第三列:编码格式详细说明
#支持的编码格式
ffmpeg -encoders
V..... libx264 libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (codec h264) #同decoders
#muxer demuxer同上
#支持的滤波器
ffmpeg -filters
TS. dilation V->V Apply dilation effect.#3字段:时间轴支持/分片线程支持/命令支持,滤镜名,转换方式,滤镜作用说明
#查看单独
ffmpeg -h muxer=flv
ffprobe
多媒体文件信息查看工具
usage: ffprobe [OPTIONS] [INPUT_FILE]
-L # license
-show_packets #数据包信息
-show_data #包中数据
-show_format #封装格式
-show_frames #视频帧信息
AVFormatContext: 封装的上下文
AVStream : 存放的是音频流或视频流的参数信息
AVPacket: 针对于具体的解封装完后的一个一个的数据包。它保存了解复用之后,解码之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等。对于视频(Video)来说,AVPacket通常包含一个压缩的Frame,而音频(Audio)则有可能包含多个压缩的Frame。并且,一个Packet有可能是空的,不包含任何压缩数据,只含有side data(side data,容器提供的关于Packet的一些附加信息。例如,在编码结束的时候更新一些流的参数)。[详细]
av_read_frame() 用于读取一个AVPacket,AVPacket里面包含了这个包的pts,dts,还有这个包的stream index(它是音频还是视频),是否是关键帧,AVPacket把h264数据的间隔符去掉了。
avformat.h
av_register_all()
to register all compiled muxers, demuxers and protocols,included in "avformat.h".注册所有的编解码器、复用/解复用组件等
avformat_network_init()
Undo the initialization done by avformat_network_init. Call it only once for each time you called avformat_network_init.included in "avformat.h",用于初始化网络。FFmpeg本身也支持解封装RTSP的数据,如果要解封装网络数据格式,则可调用该函数。
avformat_alloc_context()
In some cases you might want to preallocate an AVFormatContext yourself with avformat_alloc_context() and do some tweaking on it before passing it to avformat_open_input()
avformat_open_input() 打开文件或网络流
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
//AVFormatContext **ps, 格式化的上下文。要注意,如果传入的是一个AVFormatContext*的指针,则该空间须自己手动清理,若传入的指针为空,则FFmpeg会内部自己创建。
//const char *url, 传入的地址。支持http,RTSP,以及普通的本地文件。地址最终会存入到AVFormatContext结构体当中。
//AVInputFormat *fmt, 指定输入的封装格式。一般传NULL,由FFmpeg自行探测。
//AVDictionary **options, 其它参数设置。它是一个字典,用于参数传递,不传则写NULL。参见:libavformat/options_table.h,其中包含了它支持的参数设置。
=0:打开成功
function for opening a file, av_read_frame() for reading a single packet and finally avformat_close_input(), which does the cleanup.可解析的内容包括:视频流、音频流、视频流参数、音频流参数、视频帧索引.
在调用该函数之前,须确保av_register_all()和avformat_network_init()已调用。
查找视频流和音频流
当视频被解封装出来后,需要分开处理音频和视频,需要找到对应的音频流和视频流
for(i;i<s.pFormatCtx->nb_streams;i++)
{
if(s.pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
{
s.video_index=i;
}
if(audio==1 & s.pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
{
s.audio_index=i;
}
}
avformat_find_stream_info()
查找格式和索引。有些早期格式它的索引并没有放到头当中,需要你到后面探测,就会用到此函数。
avcodec_find_decoder() ,avcodec_open2()
查找AV相应的解码器;打开相应解码器
s.vCodecCtx=s.pFormatCtx->streams[s.video_index]->codecpar;
s.vCodec=avcodec_find_decoder(s.vCodecCtx->codec_id);
if(s.vCodec==NULL)
{
printf("error: cannot find video codec.\n");
}
if(avcodec_open2(s.vCodecCtx,s.vCodec,NULL)<0)
{
printf("error: cannot open video codec.\n");
}
av_q2d()
av_q2d(AVRational);该函数负责把AVRational结构转换成double,通过这个函数可以计算出某一帧在视频中的时间位置
timestamp(秒) = pts * av_q2d(st->time_base);
计算视频长度的方法:
time(秒) = st->duration * av_q2d(st->time_base);
av_rescale_q()
av_rescale_q(int64_t a, AVRational bq, AVRational cq)函数,这个函数的作用是计算a*bq / cq来把时间戳从一个时间基调整到另外一个时间基。在进行时间基转换的时候,应该首先这个函数,因为它可以避免溢出的情况发生
ffmpeg内部的时间与标准的时间转换方法:
timestamp(ffmpeg内部的时间戳) = AV_TIME_BASE * time(秒)
time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部的时间戳)
ts格式文件中3600间隔是什么意思
它是25fps帧率的ts媒体文件,每个视频帧的间隔时间。
ts文件的封装时基是90kHz为单位,timebase是AVRational{1,90000},简单的理解就是把1秒分成了90000等分,拿25帧率ts文件来分析
按标准时间来计算每帧的间隔:
公式为:1 / 25 = 0.04(秒) = 40毫秒
按ffmpeg中的1秒(即90000)来计算每帧的间隔(单位好像没有明确的定义,暂且使用ffmpeg吧):
90000 / 25 = 3600(ffmpeg)
用时间转换公式可能会更清楚一些:
1(s) = 90000(ffmpeg)
40(ms) = 3600(ffmpeg)
不同的时间基
现实中不同的封装格式,timebase是不一样的。另外,整个转码过程,不同的数据状态对应的时间基也不一致。还是拿mpegts封装格式25fps来
说(只说视频,音频大致一样,但也略有不同)。非压缩时候的数据(即YUV或者其它),在ffmpeg中对应的结构体为AVFrame,它的时间基为AVRational{1,25}。
压缩后的数据(对应的结构体为AVPacket)对应的时间基为AVRational{1,90000}
sws_getContext()
sws_scale()
sws_freeContect()
sws_getContext() 初始化SwsContext()
sws_scale()
1.图像色彩空间转换;2.分辨率缩放;3.前后图像滤波处理。
truct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,int dstW, int dstH, enum AVPixelFormat dstFormat,int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);
av_packet_ref()
int av_packet_ref(AVPacket *dst, const AVPacket *src)
创建一个src->data的新的引用计数。如果src已经设置了引用计数发(src->buffer不为空),则直接将其引用计数+1;如果src没有设置引用计数(src->buffer为空),则为dst创建一个新的引用计数buf,并复制src->data到buf->buffer中。最后,复制src的其他字段到dst中。
av_packet_unref()
void av_packet_unref(AVPacket *pkt)
将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间。
所以,有两个Packet共享同一个数据缓存空间的时候可用这么做
AVPacket
- av_read_frame() 这个是比较常见的了,从媒体流中读取帧填充到填充到Packet的数据缓存空间。如果Packet->buf为空,则Packet的数据缓存空间会在下次调用av_read_frame的时候失效。这也就是为何在FFmpeg3:播放音频中,从流中读取到Packet的时,在将该Packet插入队列时,要调用av_dup_avpacket重新复制一份缓存数据
- av_packet_alloc 创建一个AVPacket,将其字段设为默认值(data为空,没有数据缓存空间)
- av_packet_free 释放使用av_packet_alloc创建的AVPacket,如果该Packet有引用计数(packet->buf不为空),则先调用av_packet_unref(&packet)
- av_packet_clone 其功能是 av_packet_alloc + av_packet_ref
- av_init_packet 初始化packet的值为默认值,该函数不会影响data引用的数据缓存空间和size,需要单独处理。
- av_new_packet av_init_packet的增强版,不但会初始化字段,还为data分配了存储空间。
- av_copy_packet 复制一个新的packet,包括数据缓存。
- av_packet_from_data 初始化一个引用计数的packet,并指定了其数据缓存。
- av_grow_packet和 av_shrink_packet 增大或者减小Packet->data指向的数据缓存。
av_write_trailer()
(1)循环调用interleave_packet()以及write_packet(),将还未输出的AVPacket输出出来。
(2)调用AVOutputFormat的write_trailer(),输出文件尾。
AvFrame AVPicture AVFrame
AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。
AVPicture是图片数据存储结构体。最多可以存储4个组件,最后一个是α,AVPicturek可以在栈上创建。
从定义上可知,AVPicture是AVFrame的一个子集,他们都是数据流在编解过程中用来保存数据缓存的对像,从int av_read_frame(AVFormatContext *s, AVPacket *pkt)函数看,从数据流读出的数据首先是保存在AVPacket里,也可以理解为一个AVPacket最多只包含一个AVFrame,而一个 AVFrame可能包含好几个AVPacket,AVPacket是种数据流分包的概念。记录一些音视频相关的属性值,如pts,dts等
avcodec_decode_video2() 3.x已弃用
解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。
avcodec_send_packet(),avcodec_receive_frame()
代替avcodec_decode_video2()
对于解码,调用avcodec_send_packet()以在AVPacket中给出解码器原始的压缩数据。
对于编码,请调用avcodec_send_frame()为编码器提供包含未压缩音频或视频的AVFrame。 在这两种情况下,建议对AVPackets和AVFrames进行重新计数,否则libavcodec可能必须复制输入数据。 (libavformat总是返回引用计数的AVPackets,av_frame_get_buffer()分配引用计数的AVFrames)
在循环中接收输出。 定期调用avcodec_receive _ *()函数并处理其输出
对于解码,请调用avcodec_receive_frame()。 成功后,它将返回一个包含未压缩音频或视频数据的 AVFrame
对于编码,请调用avcodec_receive_packet()。 一旦成功,它将返回带有压缩帧的AVPacket。 重复此呼叫,直到它返回AVERROR(EAGAIN)或错误。 AVERROR(EAGAIN)返回值意味着需要新的输入数据才能返回新的输出。 在这种情况下,继续发送输入。 对于每个输入帧/包,编解码器通常将返回1个输出帧/包,但也可以是0或大于1。
AVstream::codec弃用改动
s.vCodec_pars=s.pFormatCtx->streams[s.video_index]->codecpar;
s.vCodec=avcodec_find_decoder(s.vCodec_pars->codec_id);
if(s.vCodec == NULL)
{
printf("error: didn't found video decoder.\n");
return;
}
s.vCodecCtx=avcodec_alloc_context3(s.vCodec);
avcodec_parameters_to_context(s.vCodecCtx,s.vCodec_pars);
if(avcodec_open2(s.vCodecCtx,s.vCodec,NULL)<0)
{
printf("error: cannot open video decoder.\n");
return;
}
图像验证
YUV
fwrite(s.pFrame->data[0],s.vCodecCtx->width*s.vCodecCtx->height,1,fp); //Y
fwrite(s.pFrame->data[1],s.vCodecCtx->width*s.vCodecCtx->height/4,1,fp); //U
fwrite(s.pFrame->data[2],s.vCodecCtx->width*s.vCodecCtx->height/4,1,fp); //V
BGR
fwrite(s.pFrame->data[0],s.vCodecCtx->width*s.vCodecCtx->height*3,1,fp);
UYUY
fwrite(s.pFrame->data[0],s.vCodecCtx->width*s.vCodecCtx->height,2,fp)
图像测试
ffplay
播放图像YUV,RGB
ffplay -video_size 1920X1080 test.yuv
ffplay -video_size 1920X1080 test.rgb
引用
https://www.cnblogs.com/yongdaimi/p/9796349.html
https://blog.csdn.net/TopsLuo/article/details/76239136