网站开发综合实训总结,建设银行德阳分行网站,用户注册,10天搞定网站开发目录 1.AVFormatContext和FFFormatContext类。1.1 概述1.2 构造函数1.3 oopc的继承实现 2. AVInputFormat 类。2.1 多态的实现 3.所用设计模式3.1模板模式3.2 工厂模式#xff1f; 3.3 rtsp拉流建链 4.this指针5.小结6.rtsp拉流流程 1.AVFormatContext和FFFormatContext类。
… 目录 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是thispython是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.小结
发散下思维想下面向对象语言的拉流代码怎么写 伪代码:
snew 拉流对象;
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