ffmpeg面向对象-rtsp拉流相关对象
目录
- 1.AVFormatContext和FFFormatContext类。
- 1.1 概述
- 1.2 构造函数
- 1.3 oopc的继承实现
- 2. AVInputFormat 类。
- 2.1 多态的实现
- 3.所用设计模式
- 3.1模板模式
- 3.2 工厂模式?
- 3.3 rtsp拉流建链
- 4.this指针
- 5.小结
- 6.rtsp拉流流程
1.AVFormatContext和FFFormatContext类。
1.1 概述
ffmpeg5.x以后,把AVFormatContext类进行了拆分,把用户需要看到的共性部分抽取为父类AVFormatContext类,把用户用不到/不需要知道的剩余部分变成了子类FFFormatContext类——隐藏用户不用关心的内容。
5.x以后,实例化流对象FFFormatContext,然后返回给用户可见的基类AVFormatContext指针。
其他的函数调用就是以FFFormatContext这个类为中心,实质是实例化这个类(初始化,构造函数)、调用这个类的方法,最后销毁这个类对象。
比如拉流的必经流程:
avformat_open_input
av_read_frame
avformat_close_input
很明显的对应构造,调用方法和析构函数。
1.2 构造函数
rtsp拉流第一步都是avformat_open_input,同时也是流对象FFFormatContext的构造函数。
AVFormatContext *fmt_ctx = NULL;
result = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL);
其中fmt_ctx 如何分配内存的?如下
int avformat_open_input(AVFormatContext **ps, const char *filename,
const AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
FFFormatContext *si;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
int ret = 0;
if (!s && !(s = avformat_alloc_context()))
……
}
avformat_alloc_context来给fmt_ctx 分配内存的,看它实质分配的内存有多大:
AVFormatContext *avformat_alloc_context(void)
{
FFFormatContext *const si = av_mallocz(sizeof(*si));
AVFormatContext *s;
if (!si)
return NULL;
s = &si->pub;
s->av_class = &av_format_context_class;
s->io_open = io_open_default;
s->io_close = ff_format_io_close_default;
s->io_close2= io_close2_default;
av_opt_set_defaults(s);
si->pkt = av_packet_alloc();
si->parse_pkt = av_packet_alloc();
if (!si->pkt || !si->parse_pkt) {
avformat_free_context(s);
return NULL;
}
si->shortest_end = AV_NOPTS_VALUE;
return s;
}
1.3 oopc的继承实现
可以看到它实际分配的内存是FFFormatContext这么大的,但是返回的地址是AVFormatContext *类型的,缩小了。这就是典型的oopc的接口继承。
FFFormatContext类如下实现:
typedef struct FFFormatContext {
/**
* The public context.
*/
AVFormatContext pub;
/**
* Number of streams relevant for interleaving.
* Muxing only.
*/
int nb_interleaved_streams;
……
}
oopc的继承实现是结构体套结构体。如上结构体,FFFormatContext继承自AVFormatContext。
同时可以看到它的特点,一般父类都是作为子类第一个成员,这样方便强转更改访问权限。
FFFormatContext第1个成员就是父类AVFormatContext的成员,把它命名为pub——注释中说是public公共上下文——公共的——这就是面向对象中常用的抽象出的基类的基本方法。
所谓接口继承,就是创建子类返父类的地址,这是多态实现的基础。
oopc中,这种继承是个老套路,也是经典套路,linux中,还有rtthread RTOS的内核实现中常用这种套路,这个应该是oopc的经典。
对应的对象图简略示意图如下:
2. AVInputFormat 类。
就像linux一切统一于文件,ffmpeg的一切拉流输入格式统一于AVInputFormat类——对所有拉流输入格式进行了抽象——体现了面向对象的抽象与多态——不同子类的方法不同,在oopc中就是采用函数指针实现。
什么意思?在代码上,说的“子类”具体代码体现在demuxer_list指针数组——定义在libavformat/demuxer_list.c中——该文件是编译FFMPEG configure的时候生成的(也就是说下载的源码中是没有的,config时才生成),举例生成的数组如下:
static const AVInputFormat * const demuxer_list[] = {
&ff_aa_demuxer,
&ff_aac_demuxer,
&ff_ac3_demuxer,
&ff_acm_demuxer,
&ff_act_demuxer,
&ff_adf_demuxer,
&ff_adp_demuxer,
&ff_ads_demuxer,
&ff_adx_demuxer,
&ff_aea_demuxer,
&ff_afc_demuxer,
&ff_aiff_demuxer,
&ff_aix_demuxer,
&ff_amr_demuxer,
&ff_amrnb_demuxer,
&ff_amrwb_demuxer,
&ff_anm_demuxer,
&ff_apc_demuxer,
&ff_ape_demuxer,
&ff_apng_demuxer,
&ff_aptx_demuxer,
&ff_aptx_hd_demuxer,
&ff_aqtitle_demuxer,
&ff_asf_demuxer,
&ff_asf_o_demuxer,
&ff_ass_demuxer,
&ff_ast_demuxer,
&ff_au_demuxer,
&ff_avi_demuxer,
&ff_avr_demuxer,
&ff_avs_demuxer,
&ff_bethsoftvid_demuxer,
&ff_bfi_demuxer,
&ff_bintext_demuxer,
&ff_bink_demuxer,
&ff_bit_demuxer,
&ff_bmv_demuxer,
&ff_bfstm_demuxer,
&ff_brstm_demuxer,
&ff_boa_demuxer,
&ff_c93_demuxer,
&ff_caf_demuxer,
&ff_cavsvideo_demuxer,
&ff_cdg_demuxer,
&ff_cdxl_demuxer,
&ff_cine_demuxer,
&ff_codec2_demuxer,
&ff_codec2raw_demuxer,
&ff_concat_demuxer,
&ff_data_demuxer,
&ff_daud_demuxer,
&ff_dcstr_demuxer,
&ff_dfa_demuxer,
&ff_dirac_demuxer,
&ff_dnxhd_demuxer,
&ff_dsf_demuxer,
&ff_dsicin_demuxer,
&ff_dss_demuxer,
&ff_dts_demuxer,
&ff_dtshd_demuxer,
&ff_dv_demuxer,
&ff_dvbsub_demuxer,
&ff_dvbtxt_demuxer,
&ff_dxa_demuxer,
&ff_ea_demuxer,
&ff_ea_cdata_demuxer,
&ff_eac3_demuxer,
&ff_epaf_demuxer,
&ff_ffmetadata_demuxer,
&ff_filmstrip_demuxer,
&ff_fits_demuxer,
&ff_flac_demuxer,
&ff_flic_demuxer,
&ff_flv_demuxer,
&ff_live_flv_demuxer,
&ff_fourxm_demuxer,
&ff_frm_demuxer,
&ff_fsb_demuxer,
&ff_g722_demuxer,
&ff_g723_1_demuxer,
&ff_g726_demuxer,
&ff_g726le_demuxer,
&ff_g729_demuxer,
&ff_gdv_demuxer,
&ff_genh_demuxer,
&ff_gif_demuxer,
&ff_gsm_demuxer,
&ff_gxf_demuxer,
&ff_h261_demuxer,
&ff_h263_demuxer,
&ff_h264_demuxer,
&ff_hevc_demuxer,
&ff_hls_demuxer,
&ff_hnm_demuxer,
&ff_ico_demuxer,
&ff_idcin_demuxer,
&ff_idf_demuxer,
&ff_iff_demuxer,
&ff_ilbc_demuxer,
&ff_image2_demuxer,
&ff_image2pipe_demuxer,
&ff_image2_alias_pix_demuxer,
&ff_image2_brender_pix_demuxer,
&ff_ingenient_demuxer,
&ff_ipmovie_demuxer,
&ff_ircam_demuxer,
&ff_iss_demuxer,
&ff_iv8_demuxer,
&ff_ivf_demuxer,
&ff_ivr_demuxer,
&ff_jacosub_demuxer,
&ff_jv_demuxer,
&ff_lmlm4_demuxer,
&ff_loas_demuxer,
&ff_lrc_demuxer,
&ff_lvf_demuxer,
&ff_lxf_demuxer,
&ff_m4v_demuxer,
&ff_matroska_demuxer,
&ff_mgsts_demuxer,
&ff_microdvd_demuxer,
&ff_mjpeg_demuxer,
&ff_mjpeg_2000_demuxer,
&ff_mlp_demuxer,
&ff_mlv_demuxer,
&ff_mm_demuxer,
&ff_mmf_demuxer,
&ff_mov_demuxer,
&ff_mp3_demuxer,
&ff_mpc_demuxer,
&ff_mpc8_demuxer,
&ff_mpegps_demuxer,
&ff_mpegts_demuxer,
&ff_mpegtsraw_demuxer,
&ff_mpegvideo_demuxer,
&ff_mpjpeg_demuxer,
&ff_mpl2_demuxer,
&ff_mpsub_demuxer,
&ff_msf_demuxer,
&ff_msnwc_tcp_demuxer,
&ff_mtaf_demuxer,
&ff_mtv_demuxer,
&ff_musx_demuxer,
&ff_mv_demuxer,
&ff_mvi_demuxer,
&ff_mxf_demuxer,
&ff_mxg_demuxer,
&ff_nc_demuxer,
&ff_nistsphere_demuxer,
&ff_nsp_demuxer,
&ff_nsv_demuxer,
&ff_nut_demuxer,
&ff_nuv_demuxer,
&ff_ogg_demuxer,
&ff_oma_demuxer,
&ff_paf_demuxer,
&ff_pcm_alaw_demuxer,
&ff_pcm_mulaw_demuxer,
&ff_pcm_f64be_demuxer,
&ff_pcm_f64le_demuxer,
&ff_pcm_f32be_demuxer,
&ff_pcm_f32le_demuxer,
&ff_pcm_s32be_demuxer,
&ff_pcm_s32le_demuxer,
&ff_pcm_s24be_demuxer,
&ff_pcm_s24le_demuxer,
&ff_pcm_s16be_demuxer,
&ff_pcm_s16le_demuxer,
&ff_pcm_s8_demuxer,
&ff_pcm_u32be_demuxer,
&ff_pcm_u32le_demuxer,
&ff_pcm_u24be_demuxer,
&ff_pcm_u24le_demuxer,
&ff_pcm_u16be_demuxer,
&ff_pcm_u16le_demuxer,
&ff_pcm_u8_demuxer,
&ff_pjs_demuxer,
&ff_pmp_demuxer,
&ff_pva_demuxer,
&ff_pvf_demuxer,
&ff_qcp_demuxer,
&ff_r3d_demuxer,
&ff_rawvideo_demuxer,
&ff_realtext_demuxer,
&ff_redspark_demuxer,
&ff_rl2_demuxer,
&ff_rm_demuxer,
&ff_roq_demuxer,
&ff_rpl_demuxer,
&ff_rsd_demuxer,
&ff_rso_demuxer,
&ff_rtp_demuxer,
&ff_rtsp_demuxer,
&ff_s337m_demuxer,
&ff_sami_demuxer,
&ff_sap_demuxer,
&ff_sbc_demuxer,
&ff_sbg_demuxer,
&ff_scc_demuxer,
&ff_sdp_demuxer,
&ff_sdr2_demuxer,
&ff_sds_demuxer,
&ff_sdx_demuxer,
&ff_segafilm_demuxer,
&ff_shorten_demuxer,
&ff_siff_demuxer,
&ff_sln_demuxer,
&ff_smacker_demuxer,
&ff_smjpeg_demuxer,
&ff_smush_demuxer,
&ff_sol_demuxer,
&ff_sox_demuxer,
&ff_spdif_demuxer,
&ff_srt_demuxer,
&ff_str_demuxer,
&ff_stl_demuxer,
&ff_subviewer1_demuxer,
&ff_subviewer_demuxer,
&ff_sup_demuxer,
&ff_svag_demuxer,
&ff_swf_demuxer,
&ff_tak_demuxer,
&ff_tedcaptions_demuxer,
&ff_thp_demuxer,
&ff_threedostr_demuxer,
&ff_tiertexseq_demuxer,
&ff_tmv_demuxer,
&ff_truehd_demuxer,
&ff_tta_demuxer,
&ff_txd_demuxer,
&ff_tty_demuxer,
&ff_ty_demuxer,
&ff_v210_demuxer,
&ff_v210x_demuxer,
&ff_vag_demuxer,
&ff_vc1_demuxer,
&ff_vc1t_demuxer,
&ff_vivo_demuxer,
&ff_vmd_demuxer,
&ff_vobsub_demuxer,
&ff_voc_demuxer,
&ff_vpk_demuxer,
&ff_vplayer_demuxer,
&ff_vqf_demuxer,
&ff_w64_demuxer,
&ff_wav_demuxer,
&ff_wc3_demuxer,
&ff_webm_dash_manifest_demuxer,
&ff_webvtt_demuxer,
&ff_wsaud_demuxer,
&ff_wsd_demuxer,
&ff_wsvqa_demuxer,
&ff_wtv_demuxer,
&ff_wve_demuxer,
&ff_wv_demuxer,
&ff_xa_demuxer,
&ff_xbin_demuxer,
&ff_xmv_demuxer,
&ff_xvag_demuxer,
&ff_xwma_demuxer,
&ff_yop_demuxer,
&ff_yuv4mpegpipe_demuxer,
&ff_image_bmp_pipe_demuxer,
&ff_image_dds_pipe_demuxer,
&ff_image_dpx_pipe_demuxer,
&ff_image_exr_pipe_demuxer,
&ff_image_j2k_pipe_demuxer,
&ff_image_jpeg_pipe_demuxer,
&ff_image_jpegls_pipe_demuxer,
&ff_image_pam_pipe_demuxer,
&ff_image_pbm_pipe_demuxer,
&ff_image_pcx_pipe_demuxer,
&ff_image_pgmyuv_pipe_demuxer,
&ff_image_pgm_pipe_demuxer,
&ff_image_pictor_pipe_demuxer,
&ff_image_png_pipe_demuxer,
&ff_image_ppm_pipe_demuxer,
&ff_image_psd_pipe_demuxer,
&ff_image_qdraw_pipe_demuxer,
&ff_image_sgi_pipe_demuxer,
&ff_image_svg_pipe_demuxer,
&ff_image_sunrast_pipe_demuxer,
&ff_image_tiff_pipe_demuxer,
&ff_image_webp_pipe_demuxer,
&ff_image_xpm_pipe_demuxer,
&ff_image_xwd_pipe_demuxer,
NULL };
ffmpeg支持的每一个输入格式都统一抽象为AVInputFormat类,支持的每个输入格式都实例化一个AVInputFormat对象出来(oopc就是变量或全局变量),然后放到这个指针数组中——聚合到一起了。
而AVFormatContext的成员iformat就是AVInputFormat类的指针,它就是匹配到上面指针数组里的成员的呢!那么如何匹配的呢?通过av_demuxer_iterate循环遍历指针数组demuxer_list拿到的。
具体调用链如下:
avformat_open_input => init_input => av_probe_input_format2 => av_probe_input_format3 => av_demuxer_iterate。
const AVInputFormat *av_demuxer_iterate(void **opaque)
{
static const uintptr_t size = sizeof(demuxer_list)/sizeof(demuxer_list[0]) - 1;
uintptr_t i = (uintptr_t)*opaque;
const AVInputFormat *f = NULL;
uintptr_t tmp;
if (i < size)
{
f = demuxer_list[i];
}
else if (tmp = atomic_load_explicit(&indev_list_intptr, memory_order_relaxed))
{
const AVInputFormat *const *indev_list = (const AVInputFormat *const *)tmp;
f = indev_list[i - size];
}
if (f)
*opaque = (void*)(i + 1);
return f;
}
2.1 多态的实现
这样,av_demuxer_iterate循环遍历libavformat/demuxer_list.c中的demuxer_list数组,AVFormatContext的成员iformat就匹配到了指针数组demuxer_list中的成员。
ffmpeg在各个格式的c文件中定义了AVInputFormat类的对象(全局变量),最后被加入到demuxer_list数组中。这样实现多态。
这里比如rtsp拉流,输入是“rtsp:”的形式,它会匹配到ff_rtsp_demuxer这个全局变量(通过调用read_probe)——定义在libavformat/rtspdec.c中:
const AVInputFormat ff_rtsp_demuxer = {
.name = "rtsp",
.long_name = NULL_IF_CONFIG_SMALL("RTSP input"),
.priv_data_size = sizeof(RTSPState),
.read_probe = rtsp_probe,
.read_header = rtsp_read_header,
.read_packet = rtsp_read_packet,
.read_close = rtsp_read_close,
.read_seek = rtsp_read_seek,
.flags = AVFMT_NOFILE,
.read_play = rtsp_read_play,
.read_pause = rtsp_read_pause,
.priv_class = &rtsp_demuxer_class,
};
AVInputFormat类抽象的这些方法包括:
探测或者称之为辨识是不是本格式的方法probe,
读头信息,读取包数据,等等。把各种格式都抽象统一于此,这样同样的子类但是方法不同,实现了多态。
3.所用设计模式
3.1模板模式
接着统一的操作如下:
当然不仅仅这一种统一操作——但是这种统一操作,不管啥格式的,都走这个流程,这种设计模式是为模版模式。
avformat_open_input中的流程,可以说是对所有输入格式抽象出来的共性流程,这种提取出的共性流程,基本万古不变,变得只是函数指针指向或者参数配置,那么这种流程就像一个“模板”一样,这就是模板模式。
这种模式是常见的模式,因为行业固定,行业对应的业务流程基本能抽出共性的流程,那么随着代码的迭代,共性代码会自然出现,而不是因为“设计模式”才出现模板模式,而是因为这样的代码出现了,对它总结,起个名字叫“模板模式”。设计模式来源于现成代码的概念抽象和总结。
3.2 工厂模式?
从上面AVFormatContext的成员iformat匹配对应的AVInputFormat这种行为,有点类似策略模式(但是策略模式的特点是运行中可以更改算法,但这里不是,初始化完毕就不能动了),又有点像工厂模式,工厂模式会根据不同格式创建不同类,和这个有点异曲同工,也有解析的部分,解析到哪类就实例化哪类。我更倾向于它这种解析匹配子类对象地址的方式是工厂模式。
不管了,反正记住一点,设计模式是对现有代码的抽象,先有代码,再有提出对应设计模式的概念。
3.3 rtsp拉流建链
AVFormatContext的成员iformat匹配到合适的全局变量后,
有个统一操作
s->iformat->read_header(s)。
在rtsp拉流对象对应的就是ff_rtsp_demuxer中的rtsp_read_header,如下实现
static int rtsp_read_header(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
int ret;
if (rt->initial_timeout > 0)
rt->rtsp_flags |= RTSP_FLAG_LISTEN;
if (rt->rtsp_flags & RTSP_FLAG_LISTEN) {
ret = rtsp_listen(s);
if (ret)
return ret;
} else {
ret = ff_rtsp_connect(s);
if (ret)
return ret;
rt->real_setup_cache = !s->nb_streams ? NULL :
av_calloc(s->nb_streams, 2 * sizeof(*rt->real_setup_cache));
if (!rt->real_setup_cache && s->nb_streams) {
ret = AVERROR(ENOMEM);
goto fail;
}
rt->real_setup = rt->real_setup_cache + s->nb_streams;
if (rt->initial_pause) {
/* do not start immediately */
} else {
ret = rtsp_read_play(s);
if (ret < 0)
goto fail;
}
}
return 0;
fail:
rtsp_read_close(s);
return ret;
}
可以看到很关键,包含了rtsp协议链接到完成。
4.this指针
看下oopc是如何模拟this指针的。
再次突出强调下,rtsp拉流对象是FFFormatContext——对用户看到的是其基类AVFormatContext指针——this指针。
FFFormatContext这个就是流对象。this指针实质指向的就是这个流对象的地址。
看下前面的调用
avformat_open_input
av_read_frame
avformat_close_input
它们几个的第一个形参都是流对象的基类指针。
还有其内部调用方法形式:
s->iformat->read_header(s),
它模拟的就是面向对象的如下形式:
对象指针->对象方法(形参1,形参2,…)
因为面向对象的this指针是默认参数(c++是this,python是self),代码中不需要显式调用,但是oopc中this指针只能如上形式进行显式调用。
而且,还能发现AVInputFormat对所有格式抽象的方法第一个形参都是AVFormatContext指针,这就是this指针,这里this指针指的就是FFFormatContext的基类对象指针。
再看下av_read_frame这个调用,它第一个形参也是这个this指针,内部怎么调用的呢?截取如下
int av_read_frame(AVFormatContext *s, AVPacket *pkt)
{
FFFormatContext *const si = ffformatcontext(s);
……
ret = read_frame_internal(s, pkt);
……
}
static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{
FFFormatContext *const si = ffformatcontext(s);
...
while (!got_packet && !si->parse_queue.head) {
AVStream *st;
FFStream *sti;
/* read next packet */
ret = ff_read_packet(s, pkt);
……
}
int ff_read_packet(AVFormatContext *s, AVPacket *pkt)
{
FFFormatContext *const si = ffformatcontext(s);
for (;;) {
……
err = s->iformat->read_packet(s, pkt);
……
}
调用链一路到了ff_read_packet,最终到了如下调用:
s->iformat->read_packet(s, pkt);
和之前的形式一样,模拟了面向对象的方法调用形式,但是this指针需要显式调用。
另外AVFormatContext 基类指针怎么取到子类FFFormatContext 指针的呢?ffformatcontext(s)调用,如下
static av_always_inline FFFormatContext *ffformatcontext(AVFormatContext *s)
{
return (FFFormatContext*)s;
}
很显然,就是强转改变访问权限,不像c++需要维护虚表,运行时选择。因为c没有这个机制,只能这样子,当然linux内核没有做的这么方便,用的是内核第一宏container_of()根据成员地址推算结构体指针,这样父类可以不放到子类结构体的第一个成员。
5.小结
发散下思维,想下面向对象语言的拉流代码怎么写?
伪代码:
s=new 拉流对象;
while(1)
{
s->读数据方法(packet);
}
delete s;
oopc模拟的话,类使用结构体表示的,继承一般采用结构体套结构体的形式,多态采用函数指针形式。
拉流结构体 s;
s.方法(s指针即this指针,形参1,形参2,...)
ffmpeg拉流呢,就是模拟的这样,比如创建FFFormatContext类对象,然后返回其父类指针,然后调用AVFormatContext的拉流方法:
s->iformat->read_packet(s, pkt);
其他的方法都是以这个流对象为中心,实质是对流对象进行操作——构造、析构、方法调用。这个方法包含了各种格式,编码、解码、转换,这个流对象就是中心操作对象——流对象内部存放了百宝箱——有数据有方法——这就是面向对象的类对象。这样化繁为简,统一操作。
如果你学过一门面向对象语言,只要掌握基础的类定义,继承,封装,接口类等基础语法,还有了解下智能指针,再看这个ffmpeg代码或者rtthreadrtos内核源码,那么就会感觉无缝衔接,如果了解过oopc的套路,更加无缝衔接。
如果再学下设计模式,那就能看到设计模式。
设计模式是现有代码再由人总结出来的概念。
6.rtsp拉流流程
很多博客有,不再赘述。
如下
https://blog.csdn.net/baidu_41388533/article/details/112728029
https://www.cnblogs.com/caiyingyong/p/16947075.html
https://blog.csdn.net/u013692429/article/details/101698740