前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >为什么OpenCV计算的帧率是错误的?

为什么OpenCV计算的帧率是错误的?

作者头像
LiveVideoStack
发布于 2022-06-27 05:42:26
发布于 2022-06-27 05:42:26
1.2K00
代码可运行
举报
文章被收录于专栏:音视频技术音视频技术
运行总次数:0
代码可运行

 点击上方“LiveVideoStack”关注我们

▲扫描图中二维码或点击阅读原文▲

了解音视频技术大会更多信息


作者:王伟 编辑:Alex

  引 言  

我们有一个平台来周期性地对线上的直播流数据进行某些检测,例如黑/白屏检测、静态画面检测……在检测中,我们会根据提取到的直播流的帧率来预估要计算的帧数量,例如,如果要检测5s的直播流,而该直播流的帧率为20fps,需要计算的帧数量则为100。忽然有一天,我们发现,平台开始大面积的超时,之前只需要2s就能完成的计算,现在却需要30+分钟。查了之后,我们发现,之所以计算超时是因为OpenCV计算的帧率为2000,从而导致需要计算的帧数量从之前的100变为了10000,进而引起了计算超时。

1

OpenCV 如何计算帧率

这个问题的具体描述可以参见 OpenCV Issues 21006[1]。该问题的模拟直播流片段test.ts可以点击链接下载:

https://pan.baidu.com/share/init?surl=RY0Zk5C_DOEwTXYe2SLFEg,下载提取码为x87m。

如果用如下的代码获取test.ts的fps,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const double FPS = cap.get(cv::CAP_PROP_FPS);std::cout << "fps: " << FPS << std::endl;

可以得到:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ fps: 2000

用ffprobe对视频进行分析,可以得到:

代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
codec_name=h264r_frame_rate=30/1avg_frame_rate=0/0……

从 opencv/modules/videoio/src/cap_ffmpeg_impl.hpp[2]中,我们发现fps由CvCapture_FFMPEG::get计算而来,其计算逻辑如下:

代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
double fps = r2d(ic->streams[video_stream]->avg_frame_rate);if (fps < eps_zero) {    fps = 1.0 / r2d(ic->streams[video_stream]->codec->time_base);}

2

为什么OpenCV得到的帧率是错的

利用test_time_base.cpp[3],我们可以得到:

代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
time_base: 1/2000framerate: 0/0avg_framerate: 0/0r2d(ic->streams[video_stream]->avg_frame_rate) = 0

所以OpenCV采用了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1.0 / r2d(ic->streams[video_stream]->codec->time_base)
代码语言:javascript
代码运行次数:0
运行
复制

来计算该视频的fps。而此处的time_base = 1/2000,因此,最终得到的fps是2000。

也就是说,AVStream->codec->time_base的值导致了OpenCV得到一个看起来是错误的fps。那么,AVStream->codec->time_base为什么是这个值呢?FFmpeg是怎么计算这个字段的呢?

3

FFmpeg 如何计算

AVCodecContext.time_base

AVStream->codec->time_baseAVCodecContext中定义的 time_base字段,根据libavcodec/avcodec.h[4] 中的定义可知,对于解码而言,time_base已经被废弃,需要使用framerate来替换 time_base。并且,对于固定帧率而言,time_base = 1/framerate,但并非总是如此。

利用H264Naked[5]对test.ts对应的H.264码流进行分析,我们得到SPS.Vui信息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
timing_info_present_flag :1num_units_in_tick :1time_scale :2000fixed_frame_rate_flag :0

从中可以看到,test.ts是非固定帧率视频。从test_time_base.cpp[3]的结果看,test.ts视频中,framerate = 0/0,而time_base = 1/2000

难道,对于非固定帧率视频而言,time_baseframerate之间没有关联?如果存在关联,那又是怎样的运算才能产生这种结果?这个 time_base究竟是怎么计算的呢?究竟和framerate有没有关系呢?一连串的问题随之而来……

源码面前,了无秘密。接下来,带着这个问题,我们来一起分析一下FFmpeg究竟是如何处理time_base的。

3.1  avformat_find_stream_info

在 FFmpeg中,avformat_find_stream_info() 对ic->streams[video_stream]->codec进行初始化,因此我们可以从 avformat_find_stream_info() 开始分析。

从 libavformat/avformat.h[6]中,可以得知avformat_open_input()会打开视频流,从中读取相关的信息,然后存储在AVFormatContext中,但是有时候,此处获取的信息并不完整,因此需要调用avformat_find_stream_info()来获取更多的信息。

需要注意的是:

avformat_find_stream_info()会尝试通过解码部分视频帧来获取需要的信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** * Read packets of a media file to get stream information. This * is useful for file formats with no headers such as MPEG. This * function also computes the real framerate in case of MPEG-2 repeat * frame mode. * The logical file position is not changed by this function; * examined packets may be buffered for later processing. * * @param ic media file handle * @param options  If non-NULL, an ic.nb_streams long array of pointers to*                 dictionaries, where i-th member contains options for *                 codec corresponding to i-th stream. *                 On return each dictionary will be filled with options that*                 were not found. * @return >=0 if OK, AVERROR_xxx on error * * @note this function isn't guaranteed to open all the codecs, so *       options being non-empty at return is a perfectly normal behavior. * * @todo Let the user decide somehow what information is needed so that *       we do not waste time getting stuff the user does not need. */int avformat_find_stream_info(AVFormatContext*ic, AVDictionary **options);

avformat_find_stream_info()的整体逻辑大致如下图所示,其中特别需要关注图中所示的 7 个步骤:

3.2  avformat_find_stream_info()的重要步骤说明

 STEP 1  设置线程数,避免H.264多线程解码时没有把SPS/PPS信息提取到extradata

 STEP 2  设置AVStream *stst会在后续的函数调用中一直透传到 try_decode_frame()

 STEP 3  比较简单,这里不再赘述。

 STEP 4  设置AVCodecContext *avctx为透传的st->internal->avctx,在后续的解码函数调用中,一直透传的就是这个avctx,因此,从这里开始的执行流程,FFmpeg使用的全部都是st->internal->avctx,而不是st->codec,这里要特别的注意。此处同时会设置解码的线程数,其目的和STEP 1是一致的。

 STEP 5  因为之前设置了解码线程数为1,所以此处会调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ret = avctx->codec->decode(avctx, frame, &got_frame, pkt)

来解码并计算avctx->framerate。注意,此处的avctx实际上是透传而来的st->internal->avctx。计算 framerate的逻辑会在如何计算framerate部分介绍。

 STEP 6  根据解码器得到的framerate信息来计算 avctx->time_base,注意此处实际上是st->internal->avctx->time_base。根据 如何计算framerate可知,此处framerate = {1000, 1}。根据 AVCodecContext.ticks_per_frame的介绍可知,ticks_per_frame = 2。因此,此处avctx->time_base = {1, 2000}

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
avctx->time_base = av_inv_q(av_mul_q({1000, 1}, {2, 1})) = {1, 2000}

 STEP 7  这一步可谓是“瞒天过海,明修栈道暗度陈仓”。这一步为了解决API的前向兼容,做了一个替换,把st->internal->avctx->time_base 赋值给了st->codec->time_base,而把st->avg_frame_rate 赋值给了 st->codec->framerate。因此:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
st->codec->time_base = {1, 2000}st->codec->framerate = {0, 0}

st->codec->time_base 的计算和 st->codec->framerate 之间没有任何关系,而是和 st->internal->avctx->framerate 有关。究其本质,是和sps.time_scale,sps.num_units_in_tick有关。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
st->internal->avctx->time_base.num =sps->num_units_in_tick * st->internal->avctx->ticks_per_frame
st->internal->avctx->time_base.den = sps->time_scale *    st->internal->avctx->ticks_per_frame;
st->internal->avctx->time_base = {sps->num_units_in_tick,sps->time_scale}
3.3  internal->avctx->time_base & internal->framerate

所以实际上,internal->avctx->time_base为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
avctx->time_base = sps->num_units_in_tick /sps->time_scale

internal->avctx->framerate则是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
avctx->framerate = sps->time_scale /(sps->num_units_in_tick * avctx->ticks_per_frame)

因此,对于 H.264 码流而言,time_base = 1 / (2 * framerate),而不是1 / framerate

这也就是为什么

libavcodec/avcodec.h[4] 中说:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
* This often, but not always is the inverse of the frame rate or field rate* for video.

从如上的分析可以知道:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
avctx->framerate = 1 / (avctx->time_base * avctx->ticks_per_frame)

因此,当st->avg_frame_rate = 0 时,OpenCV计算fps的逻辑是错误的。

在H.265中,ticks_per_frame = 1,因此对于H.265的编码,OpenCV是没有这个问题的。可以使用Zond 265 [7]工具来分析一个 H.265的视频码流,然后对照OpenCV以及FFmpeg的结果来验证。

同时,正是如上所示的STEP 7中的移花接木导致了 test_time_base.cpp[3] 的结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
st->codec->framerate: 0/0st->codec->time_base: 1/2000
3.4 ff_h264_decoder

libavcodec/decode.c [8]中的

decode_simple_internal()会调用对应的解码器来进行解码(STEP 5)。而正如前所示,test.ts为H.264 编码的视频流,因此此处会调用 H.264 解码器来进行解码。在FFmpeg中,H.264解码器位于 libavcodec/h264dec.c[9] 中定义的 

const AVCodec ff_h264_decoder

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const AVCodec ff_h264_decoder = {    .name                  = "h264",    .type                  = AVMEDIA_TYPE_VIDEO,    .id                    = AV_CODEC_ID_H264,    .priv_data_size        = sizeof(H264Context),    .init                  = h264_decode_init,    .close                 = h264_decode_end,    .decode                = h264_decode_frame,    ......};

在上文图中的STEP 5中,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ret = avctx->codec->decode(avctx, frame, &got_frame, pkt);

实际调用的就是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ff_h264_decoder->h264_decode_frame(avctx, frame, &got_frame, pkt);

而此处的avctx也就是

try_decode_frame()中透传下来的st->internal->avctx,即上文图中的STEP 4。

3.5  h264_decode_frame

h264_decode_frame()的整体逻辑如下图所示:

3.6  AVCodecContext.ticks_per_frame

后面会用到ticks_per_frame来计算framerate。在STEP 6中计算 time_base的时候也用到了该值。因此,有必要做一下特殊说明。在H.264解码器中,ticks_per_frame=2,其具体的取值可以从如下几处得知:

  • libavcodec/avcodec.h [4]中的字段说明:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*** For some codecs, the time base is closer to the field rate than the frame rate. * Most notably, H.264 and MPEG-2 specify time_base as half of frame duration * if no telecine is used ... * * Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2. */ int ticks_per_frame;
  • libavcodec/h264dec.c [9]中的 h264_decode_init()
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
avctx->ticks_per_frame = 2;

4

如何计算framerate

 STEP 1  根据整体的计算流程可知,此处的h实际上就是

avformat_find_stream_info() 中的 

st->internal->avctx->priv_datah会一直透传到之后的所有流程,这个务必要注意。

 STEP 2  此处会首先获取到sps的相关信息,以备后续的计算使用,我们可以再次看一下test.ts sps[10] 的相关信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
timing_info_present_flag :1num_units_in_tick :1time_scale :2000fixed_frame_rate_flag :0
代码语言:javascript
代码运行次数:0
运行
复制

 STEP 3  根据sps的相关信息计算framerate,在上文的STEP 6中计算 time_base用到的framerate就是在此处计算的。因为 timing_info_present_flag = 1,因此会执行计算framerate的逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
avctx->framerate.den = sps->num_units_in_tick * h->avctx->ticks_per_frame = 1 * 2 = 2avctx->framerate.num = sps->time_scale = 2000avctx->framerate = (AVRational){1000, 1}
代码语言:javascript
代码运行次数:0
运行
复制

因此,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
st->internal->avctx->framerate = {1000, 1}
代码语言:javascript
代码运行次数:0
运行
复制

但是,因为avctx->time_base={1,2000},所以OpenCV计算出来的帧率结果为2000。导致这种不一致的原因在于,OpenCV在使用codec->time_base计算帧率的时候没有考虑ticks_per_frame。因此,对于OpenCV而言,正确的计算帧率的方式应该为:

代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
double fps = r2d(ic->streams[video_stream]->avg_frame_rate);if (fps < eps_zero) {fps = 1.0 / r2d(ic->streams[video_stream]->codec->time_base * ic->streams[video_stream]->codec->ticks_per_frame);}
代码语言:javascript
代码运行次数:0
运行
复制

  结 论  

通过上面的分析我们可以知道:

  • FFmpeg在计算 AVCodecContex 中的frameratetime_base的时候,会用到: o sps.time_scale o sps.num_units_in_tick o AVCodecContex.ticks_per_frame
  • 在 FFmpeg 中,frameratetime_base的关系为: o framerate = 1 / (time_base * ticks_per_frame) o time_base = 1 / (framerate * ticks_per_frame)
  • 对于非 H.264/MPEG-2, ticks_per_frame=1,因此frameratetime_base是互为倒数的关系。而对于H.264/MPEG-2 而言,ticks_per_frame=2,因此,此时二者并非是互为倒数的关系。因而,FFmpeg 中才说,frameratetime_base通常是互为倒数的关系,但并非总是如此。
  • 在OpenCV中,对于H.264/MPEG-2视频而言,当 AVStream.avg_frame_rate=0时,其计算fps的逻辑存在BUG。
  • 因为在解码时, AVCodecContex.time_base已经废弃,同时 AVStream.avctx也已经废弃,而 avformat_find_stream_info() 中为了兼容老的API,因此会利用 AVStream.internal.avctx和其他的信息来设置AVStream.avctx。而AVStream.avctx.time_base取自AVStream.internal.avctxAVStream.avctx.framerate 则取自 AVStream.framerate

注释:

[1] https://github.com/opencv/opencv/issues/21006

[2] https://github.com/opencv/opencv/blob/4.x/modules/videoio/src/cap_FFmpeg_impl.hpp

[3] https://github.com/wangwei1237/wangwei1237.github.io_src/blob/master/source/_posts/Why-OpenCV-Get-the-Wrong-FPS/test_time_base.cpp

[4]https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/avcodec.h

[5] https://github.com/shi-yan/H264Naked

[6] https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/avformat.h

[7] https://www.dektec.com/products/applications/Zond/

[8]https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/decode.c

[9]https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/h264dec.c

[10] https://wangwei1237.github.io/2021/11/26/Why-OpenCV-Get-the-Wrong-FPS/#sps

作者简介:

王伟,17哥,百度资深测试工程师。百度视频质量评测技术负责人,在解决视频质量评测的标准化、置信度方面有丰富的实践经验,搭建了百度首个体系化的视频质量评测服务平台,并服务于多个视频业务。


喜欢我们的内容就点个“在看”吧!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 LiveVideoStack 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Python操作xml
Xml XML指可扩展标记语言(Extensible Markup Language) XML被设计用于结构化、存储和传输数据 XML是一种标记语言,很类似于HTML XML没有像HTML那样具有预定义标签,需要程序员自定义标签。 XML被设计为具有自我描述性,并且是W3C的标准 XML元素 XML的元素是指从开始标签直到结束标签的部分(均包括开始结束)。 一个元素可以包含: 1、其他元素 2、文本 3、属性 4、或混合以上所有 XML语法规则 所有的XML元素都必须有一个开始标签和结束标签,
py3study
2020/01/10
2.4K0
Python:Dom生成XML文件(写X
在python中解析XML文件也有Dom和Sax两种方式,这里先介绍如何是使用Dom解析XML,这一篇文章是Dom生成XML文件,下一篇文章再继续介绍Dom解析XML文件。
py3study
2020/01/09
1.2K0
4-SIII-Android数据固化之Xml的Dom解析和存储
零、前言 Xml是一种应用广泛的标记语言,我们常见的html便是它的子集。形如:<XXX>xxx</XXX>组成的闭合标签 安卓的res目录下的xml想必大家都是否熟悉,它们最终都是在代码中被读取并解析发挥效果的 安卓的SharedPreferences是以Xml的形式存储数据的,就连AndroidManifest注意一看也是Xml 可以说xml涉及到了安卓的很多方面,我们并不陌生,Xml最大的好处就是解耦,容易复用和更换 安卓的Xml解析有Pull解析、Sax解析和Dom解析,这篇说Dom解析与
张风捷特烈
2018/09/29
7280
Qt配置文件之Xml
XML(可扩展标记语言)文件本身不能对自己“做出”任何操作。它们是存储数据的一种简单方式,而存储的数据可以被其它软件轻松读取。很多程序都会使用可扩展标记语言(XML)来存储数据。XML可用来存储数据、携带数据和交换数据,不是为了显示数据而设计的。
用户5908113
2019/11/21
1.4K0
Python格式化文件存储---XML
结构化文件存储 xml, json 为了解决不同设备之间信息交换 xml jsonXML文件 参考资料 https://docs.python.org/3/library/xml.etree.elementtree.html http://www.runoob.com/python/python-xml.html https://blog.csdn.net/seetheworld518/article/details/49535285 XML(eXtensibleMarkupLanguage), 可扩展标记
ruochen
2021/05/11
6.7K0
Python格式化文件存储---XML
【Java 进阶篇】Java XML快速入门:理解、解析和生成XML
XML(可扩展标记语言)是一种常用于存储和交换数据的标记语言,而Java是一种强大的编程语言,它具有处理XML的能力。在本篇博客中,我们将探讨XML的基础知识,学习如何在Java中解析和生成XML文档,以及实际应用中如何处理XML数据。
繁依Fanyi
2023/10/25
3.1K0
【Java 进阶篇】Java XML快速入门:理解、解析和生成XML
Python处理XML文件
XML全称是Extensible Markup Language,中文名为可扩展标记语言。对xml的介绍可以看一下w3c的介绍。网络中数据传输的常见格式有json、xml、txt等。json很简单,xml稍微复杂,但是在python面前都不是事。 python有三种方式解析xml文档:SAX,DOM,以及 ElementTree(引自 菜鸟教程-Python XML解析),sax有些复杂,dom简单但是解析速度上不如sax。但是咱就是说,都选择Python了,肯定是想“更简单”。所以本文主要介绍python通过DOM方式对xml文件的解析读取、创建、修改等操作。 python有内置的模块:xml.dom(xml.dom官方文档)和xml.dom.minidom(xml.dom.minidom官方文档),本文主要使用xml.dom.minidom这个内置模块。
Crayon鑫
2023/10/10
3020
C# 中的 XML 与 JSON 数据处理
在现代软件开发中,数据交换和存储的需求日益增长,而 XML 和 JSON 成为了两种最常用的数据格式。它们各有特点,在不同的场景下有着各自的优势。本文将从 C# 的角度出发,探讨如何处理这两种数据格式,并分享一些常见的问题及解决方法。
Jimaks
2024/09/25
3590
用 jdom 解析 xml 文件时如何解决中文问题?如何解析?
<span style="font-size:18px;">package test; import java.io.*; public class DOMTest{ private String inFile = "c:\people.xml"; private String outFile = "c:\people.xml"; public static void main(String args[]){ new DOMTest(); } public DOMTest(){ try{ javax.xm
MickyInvQ
2020/09/27
7110
Java Document生成和解析XML(DOMHelper.createDocument()方法过期了)
参考:https://blog.csdn.net/p812438109/article/details/81807440
别先生
2020/12/01
6530
Java Document生成和解析XML(DOMHelper.createDocument()方法过期了)
C# XML操作
1.XML帮助类 using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Data.SqlClient; using System.Data; using System.IO; using System.Xml.Serialization; namespace AutoO2O.Common { public class XmlHelper : XmlDocum
用户1055830
2018/01/18
1K0
C# XML操作
JAVA生成XML文件
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/116340.html原文链接:https://javaforall.cn
全栈程序员站长
2022/07/07
9280
XML——对XML文档的创建与增删改查
一、创建的第一种方式  //1、创建一个XML文档 XmlDocument doc = new XmlDocument(); //2、创建第一行描述信息 XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", "utf-8", null); //3、将创建的第一行描述信息添加到文档中 doc.AppendChild(
指尖改变世界
2018/08/31
1K0
XML——对XML文档的创建与增删改查
4--安卓网络编程之XML总汇篇
零、前言 Xml是一种应用广泛的标记语言,我们常见的html便是它的子集。形如:<XXX>xxx</XXX>组成的闭合标签 安卓的res目录下的xml想必大家都是否熟悉,它们最终都是在代码中被读取
张风捷特烈
2018/10/25
7090
4--安卓网络编程之XML总汇篇
C#XmlHelper帮助类操作Xml文档的通用方法汇总
该篇文章主要总结的是自己平时工作中使用频率比较高的Xml文档操作的一些常用方法和收集网上写的比较好的一些通用Xml文档操作的方法(主要包括Xml序列化和反序列化,Xml文件读取,Xml文档节点内容增删改的一些通过方法)。当然可能还有很多方法会漏了,假如各位同学好的方法可以在文末留言,我会统一收集起来。
追逐时光者
2022/04/16
1.9K0
XML基础
随着Internet的飞速发展,HTML因扩展困难、交互性差和语义模糊等缺点在日益增长的网络设计需求面前呈现出弱势。标准、简洁、结构严谨、可扩展性高的XML应运而生。在推出之初,XML提供通用数据交换、改变Web发布、改变分布式计算的功能。如今,XML不仅对软件开发的各个方面都产生了巨大影响,而且在各行各业都得到了充分应用。
张哥编程
2024/12/17
1850
C# XML基础入门(XML文件内容增删改查清)
最近对接了一个第三方的项目,该项目的数据传输格式是XML。由于工作多年只有之前在医疗行业的时候有接触过少量数据格式是XML的接口,之后就几乎没有接触过了。因此对于XML这块自己感觉还是有很多盲点和不足的,所以自己通过一些网上的资料总结了一下XML相关知识点。
追逐时光者
2022/04/16
2.1K0
C#操作XML方法集合
先来了解下操作XML所涉及到的几个类及之间的关系 如果大家发现少写了一些常用的方法,麻烦在评论中指出,我一定会补上的!谢谢大家
全栈程序员站长
2022/09/07
2.6K0
C#操作XML方法集合
C#中常用的几种读取XML文件的方法
本文转载:http://www.cnblogs.com/xiaoxiangfeizi/archive/2011/07/29/2120807.html
跟着阿笨一起玩NET
2018/09/18
6.2K0
C#中常用的几种读取XML文件的方法
C#在WINForm程序中创建XML文件
string path = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
zls365
2021/02/26
2.6K0
相关推荐
Python操作xml
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 3.1  avformat_find_stream_info
    • 3.2  avformat_find_stream_info()的重要步骤说明
    • 3.3  internal->avctx->time_base & internal->framerate
    • 3.4 ff_h264_decoder
    • 3.5  h264_decode_frame
    • 3.6  AVCodecContext.ticks_per_frame
加入讨论
的问答专区 >
1技术总监架构部总经理擅长3个领域
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档