ffmpeg

  • av_register_all

av_register_all

FFmpeg中av_register_all()函数用于注册所有muxers、demuxers与protocols。FFmpeg4.0以前是用链表存储muxer/demuxer,FFmpeg4.0以后改为数组存储,并且av_register_all方法已被标记为过时,av_register_input_format和av_register_output_format也被标记为过时。

av_register_all()的声明位于libavformat/avformat.h头文件中,在新版本4.0以后,不需要调用该方法,可以直接使用所有模块。

文档提到avfilter模块:avfilter_register()、avfilter_register_all()和avfilter_next()已过时,添加av_filter_iterate()来迭代遍历。avformat模块:av_register_input_format()、av_register_output_format()、av_register_all()等已过时,添加av_demuxer_iterate()和av_muxer_iterate()来迭代遍历。avcodec模块:avcodec_register()、avcodec_register_all()、av_register_codec_parser()等已过时,添加av_codec_iterae()和av_parser_iterate()来迭代遍历。

avformat模块的注册函数改动,链表存储改为数组存储,在运行期添加muxer与demuxer到链表,改为编译期自动生成保存于数组。

一般我们使用FFMpeg做编解码都会先调用av_register_all()这个函数开头,完成基本的初始化工作。而至于它具体初始化了哪些东西呢,我们直接从代码里面来看吧。

    void av_register_all(void){static int initialized; //标志位指示是否已经初始化过if (initialized)return;initialized = 1;avcodec_register_all(); // 注册编解码器/* (de)muxers */  //注册复用器与解复用器REGISTER_MUXER   (A64,              a64);REGISTER_DEMUXER (AA,               aa);REGISTER_DEMUXER (AAC,              aac);REGISTER_MUXDEMUX(AC3,              ac3);REGISTER_DEMUXER (ACM,              acm);REGISTER_DEMUXER (ACT,              act);..........}

说到这里,应该先说明一个FFMpeg里面的设计思路。向我们在这里注册这些编解码器和(解)复用器在FFMpeg的源代码里面是个什么概念呢。
在FFMepg里,像编解码器,它们都是被一个全局的链表所维护这。所谓注册其实就是往这些全局的链表里面添加节点,而每一个节点维护的是一个编码器或者解码器的相关信息以及相互操作的函数指针。
理解了这样一个注册的概念。我们就可以来看接下来的代码了。先看avcodec_register_all()这个函数。

void avcodec_register_all(void)
{static int initialized;if (initialized)return;initialized = 1;/* hardware accelerators */REGISTER_HWACCEL(H263_VAAPI,        h263_vaapi);REGISTER_HWACCEL(H263_VIDEOTOOLBOX, h263_videotoolbox);REGISTER_HWACCEL(H264_D3D11VA,      h264_d3d11va);REGISTER_HWACCEL(H264_DXVA2,        h264_dxva2);REGISTER_HWACCEL(H264_MMAL,         h264_mmal);REGISTER_HWACCEL(H264_QSV,          h264_qsv);REGISTER_HWACCEL(H264_VAAPI,        h264_vaapi);REGISTER_HWACCEL(H264_VDA,          h264_vda);REGISTER_HWACCEL(H264_VDA_OLD,      h264_vda_old);............/* video codecs */REGISTER_ENCODER(A64MULTI,          a64multi);REGISTER_ENCODER(A64MULTI5,         a64multi5);REGISTER_DECODER(AASC,              aasc);REGISTER_DECODER(AIC,               aic);REGISTER_ENCDEC (ALIAS_PIX,         alias_pix);REGISTER_ENCDEC (AMV,               amv);REGISTER_DECODER(ANM,               anm);REGISTER_DECODER(ANSI,              ansi);REGISTER_ENCDEC (APNG,              apng);REGISTER_ENCDEC (ASV1,              asv1);.............}

然后我们来看一个 REGISTER_HWACCEL(H263_VIDEOTOOLBOX, h263_videotoolbox);这样一句代码做了什么事情吧。

#define REGISTER_HWACCEL(X, x)                                          \
{                                                                   \extern AVHWAccel ff_##x##_hwaccel;                              \if (CONFIG_##X##_HWACCEL)                                       \av_register_hwaccel(&ff_##x##_hwaccel);                     \
}

很明显这是一个宏。如果按照上句调用把它展开还原应该就是:

extern AVHWAccel ff_h263_videotoolbox_hwaccel;
if (CONFIG_H263_VIDEOTOOLBOX_HWACCEL)av_register_hwaccel(&ff_videotoolbox_hwaccel);

第一句表明在别的地方应该有定义过这样一个变量 ff_h263_videotoolbox_hwaccel 然后我们在源代码里面搜索这个变量会发现,它别定义在了 libavcodec/videotoolbox.c 里面。

AVHWAccel ff_h263_videotoolbox_hwaccel = {.name           = "h263_videotoolbox",.type           = AVMEDIA_TYPE_VIDEO,.id             = AV_CODEC_ID_H263,.pix_fmt        = AV_PIX_FMT_VIDEOTOOLBOX,.alloc_frame    = ff_videotoolbox_alloc_frame,.start_frame    = videotoolbox_mpeg_start_frame,.decode_slice   = videotoolbox_mpeg_decode_slice,.end_frame      = videotoolbox_mpeg_end_frame,.uninit         = ff_videotoolbox_uninit,.priv_data_size = sizeof(VTContext),
};

完整代码如上,我们先来看一下 AVHWAccel这个类型的定义吧。

/*** @defgroup lavc_hwaccel AVHWAccel* @{*/
typedef struct AVHWAccel {/*** Name of the hardware accelerated codec.* The name is globally unique among encoders and among decoders (but an* encoder and a decoder can share the same name).*/const char *name;/*** Type of codec implemented by the hardware accelerator.** See AVMEDIA_TYPE_xxx*/enum AVMediaType type;/*** Codec implemented by the hardware accelerator.** See AV_CODEC_ID_xxx*/enum AVCodecID id;/*** Supported pixel format.** Only hardware accelerated formats are supported here.*/enum AVPixelFormat pix_fmt;/*** Hardware accelerated codec capabilities.* see HWACCEL_CODEC_CAP_**/int capabilities;/****************************************************************** No fields below this line are part of the public API. They* may not be used outside of libavcodec and can be changed and* removed at will.* New public fields should be added right above.******************************************************************/struct AVHWAccel *next;/*** Allocate a custom buffer*/int (*alloc_frame)(AVCodecContext *avctx, AVFrame *frame);/*** Called at the beginning of each frame or field picture.** Meaningful frame information (codec specific) is guaranteed to* be parsed at this point. This function is mandatory.** Note that buf can be NULL along with buf_size set to 0.* Otherwise, this means the whole frame is available at this point.** @param avctx the codec context* @param buf the frame data buffer base* @param buf_size the size of the frame in bytes* @return zero if successful, a negative value otherwise*/int (*start_frame)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);/*** Callback for each slice.** Meaningful slice information (codec specific) is guaranteed to* be parsed at this point. This function is mandatory.* The only exception is XvMC, that works on MB level.** @param avctx the codec context* @param buf the slice data buffer base* @param buf_size the size of the slice in bytes* @return zero if successful, a negative value otherwise*/int (*decode_slice)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size);/*** Called at the end of each frame or field picture.** The whole picture is parsed at this point and can now be sent* to the hardware accelerator. This function is mandatory.** @param avctx the codec context* @return zero if successful, a negative value otherwise*/int (*end_frame)(AVCodecContext *avctx);/*** Size of per-frame hardware accelerator private data.** Private data is allocated with av_mallocz() before* AVCodecContext.get_buffer() and deallocated after* AVCodecContext.release_buffer().*/int frame_priv_data_size;/*** Called for every Macroblock in a slice.** XvMC uses it to replace the ff_mpv_decode_mb().* Instead of decoding to raw picture, MB parameters are* stored in an array provided by the video driver.** @param s the mpeg context*/void (*decode_mb)(struct MpegEncContext *s);/*** Initialize the hwaccel private data.** This will be called from ff_get_format(), after hwaccel and* hwaccel_context are set and the hwaccel private data in AVCodecInternal* is allocated.*/int (*init)(AVCodecContext *avctx);/*** Uninitialize the hwaccel private data.** This will be called from get_format() or avcodec_close(), after hwaccel* and hwaccel_context are already uninitialized.*/int (*uninit)(AVCodecContext *avctx);/*** Size of the private data to allocate in* AVCodecInternal.hwaccel_priv_data.*/int priv_data_size;
} AVHWAccel;

可以发现 保护的操作函数有:alloc_frame , start_frame, decode_slice, end_frame, decode_mb, init, uninit 。从ff_h263_videotoolbox_hwaccel 的定义中我们可以发现,值实现了 alloc_frame, start_frame, decode_slice, end_frame, uninit 这几个方法。

那我们具体来看一下av_register_hwaccel(&ff_videotoolbox_hwaccel) 这个函数做了什么。

void av_register_hwaccel(AVHWAccel *hwaccel)
{AVHWAccel **p = last_hwaccel;hwaccel->next = NULL;while(*p || avpriv_atomic_ptr_cas((void * volatile *)p, NULL, hwaccel))p = &(*p)->next;last_hwaccel = &hwaccel->next;
}

这个很直白,是在做一个链表追加的操作。而它的链表定义在什么样呢?如下:

static AVHWAccel *first_hwaccel = NULL;
static AVHWAccel **last_hwaccel = &first_hwaccel;

这样,整个硬件加速器的注册过程就完成了。以 first_hwaccel 为头节点。

同样的,我们可以发现,编解码器的注册也是这个套路,只不过编解码器是用AVCodec 这个结构体来表示的。同样的,定义一个全局的 ff_x_encoder 或者 ff_x_decoder,然后在各自的实现文件中,实现AVCodec 中定义的方法。函数指针赋予 ff_x_encode 或者 ff_x_decoder。对应的 Parser 也是这样注册。
这样下来,所有的注册工作就完成了,avcodec_register_all 也就可以功成身退了。得到的结果就是几个初始化过的全局链表。

注:如果我们我们想往FFMpeg里面添加一个编解码器。那我们只要自己实现好相应的方法,然后再在avcodec_register_all 完成注册操作,这样这个自己添加的编解码就能被FFMpeg所使用到了。

好了,分析忘了avcodec_register_all() 这句代码,那么接下来的就是注册一些复用器与解复用器了。这块主要看三个宏:

#define REGISTER_MUXER(X, x)                                            \
{                                                                   \extern AVOutputFormat ff_##x##_muxer;                           \if (CONFIG_##X##_MUXER)                                         \av_register_output_format(&ff_##x##_muxer);                 \
}#define REGISTER_DEMUXER(X, x)                                          \
{                                                                   \extern AVInputFormat ff_##x##_demuxer;                          \if (CONFIG_##X##_DEMUXER)                                       \av_register_input_format(&ff_##x##_demuxer);                \
}#define REGISTER_MUXDEMUX(X, x) REGISTER_MUXER(X, x); REGISTER_DEMUXER(X, x)

其中AVOutputFormat 结构体表示复合器。AVInputFormat 表示解复合器。注册完成后将会产生两个分别以 first_iformat 和 first_oformat 为头节点的全局的链表。

至此,整个av_register_all 函数的工作就算做完了。

av_register_all()函数是在libavformat/allformates.c中定义,用于初始化libavformat并且注册所有的封装器,解封装器以及协议。在旧版(3.x早期)的ffmpeg中该函数几乎是所有应用的开始。使用版本4.x的FFMPEG很多时候其实并不会直接调用这个函数对封装和解封装器进行注册,该函数也被声明为deprecated了。但这并不妨碍学龄前去阅读学习这个函数。因为确实有很多值得研究的地方,也有很多其他ffmpeg函数中会用到的接口和思路。
多提一嘴,何为解封装:
如果想要将常见的视频源数据转化到电视屏幕或者电脑上可以让肉眼看见的图片、视频帧,需要经过以下的步骤
视频源(RTSP,RTMP,HTTP,普通文件等) -> [经过解协议] ->视频文件或视频流(FLV,MKV,AVI等)-> [经过解封装] ->视频数据(H264、HEVC视频数据)和音频数据(MP3,WMA等)-> [视频数据经过解码] ->YUV视频帧,最后YUV视频帧就可以进行展示和播放并被人眼所捕捉到了。

回到av_register_all(),函数的代码内容很简单,只有如下的几行

void av_register_all(void) {ff_thread_once(&av_format_next_init, av_format_init_next);
}

很短的函数包含三个内容,ff_thread_once(一个函数),av_format_next_init(一个宏定义的变量) 以及 av_format_init_next(一个函数)。
这三行代码的实际含义是使用了函数ff_thread_once函数来执行函数av_format_init_next()函数,且第一个参数为宏定义变量av_format_next_init。
值得注意的是,在该函数下面有两个函数:

void av_register_input_format(AVInputFormat *format)
{ff_thread_once(&av_format_next_init, av_format_init_next);
}void av_register_output_format(AVOutputFormat *format)
{ff_thread_once(&av_format_next_init, av_format_init_next);
}

函数内容,也就是他们所做的事情是一模一样的,只不过名字不同,也就是注册inputformat和outputformat函数跟register_all所做的事情是完全一致的。

ff_thread_once()
ff_thread_once()是定义在libavutil/thread.h的用于线程安全的函数。在thread.h文件里,在拥有os2threads.h库时,会通过以下define指向pthread_once函数:

#define ff_thread_once(control, routine) pthread_once(control, routine)

在所有情况以外,它创建了一个自己的函数:

static inline int ff_thread_once(char *control, void (*routine)(void)) {if (!*control) {routine();*control = 1;}return 0;
} //这个函数也很简单就能看出是通过一个control来保证routine函数只会执行一次的。

所以实际上的pthread_once():thread.h中根据平台不同引入不同的线程库,删减掉不必要的代码,引入库的顺序如下代码所示:发现优先使用pthread.h库->其次os2threads.h库->再次w32pthreads.h。pthread_once在不同的平台下映射到不同线程库中的pthread_once函数。

所以总体来说,这个函数的主要功能就是保证传入的函数在多线程的环境下也只会被执行一次。根据平台的不同使用了不同的进程函数。

av_format_next_init
该变量的定义在allformat.c文件内部:

static AVOnce av_format_next_init = AV_ONCE_INIT;

AVOnce也是定义在thread.h内的

#define AVOnce pthread_once_t

在thread.h内,不同的环境下,AVOnce也拥有着不同的内容

#define AVOnce pthread_once_t
#define AVOnce char

该变量也是用于保证传入的函数是在多线程情况下只会执行一次的,对于不同的环境内,AVOnce采取了不同的变量来实现这个功能。

av_format_init_next()
对ff_thread_once()传入的函数为av_formate_init_next(),该函数内容如下:

static void av_format_init_next(void)
{AVOutputFormat *prevout = NULL, *out;AVInputFormat *previn = NULL, *in;ff_mutex_lock(&avpriv_register_devices_mutex);for (int i = 0; (out = (AVOutputFormat*)muxer_list[i]); i++) {if (prevout)prevout->next = out;prevout = out;}if (outdev_list) {for (int i = 0; (out = (AVOutputFormat*)outdev_list[i]); i++) {if (prevout)prevout->next = out;prevout = out;}}for (int i = 0; (in = (AVInputFormat*)demuxer_list[i]); i++) {if (previn)previn->next = in;previn = in;}if (indev_list) {for (int i = 0; (in = (AVInputFormat*)indev_list[i]); i++) {if (previn)previn->next = in;previn = in;}}ff_mutex_unlock(&avpriv_register_devices_mutex);
}

该函数的作用是将muxer_list中的输出封装结构AVOutputFormat串起来形成单向链表,当outdev_list不为空的时候,将outdev_list中的AVOutputFormat也一并串在后头。同理将demuxer_list中的输入封装结构体AVInputFormat挨个穿起来形成单向链表,同时在indev_list不为空的时候,将indev_list中的AVInputFormat也一并串起来挂在后头。

在代码进行之前使用了ff_mutex_lock()函数,是为了保证函数并发安全,上并发锁。传入参数avpriv_register_devices_mutex的定义是

static AVMutex avpriv_register_devices_mutex = AV_MUTEX_INITIALIZER;

可以追查其定义为 #define AVMutex pthread_mutex_t,定义在libavutil/thread.h中,与ptread_once函数一样,对于不同的平台使用不同的线程库,映射到相应的互斥锁变量中。

关于muxer_list.c和demuxer_list.c
函数中使用的两个list,即mutex_list和demutex_list两个静态数组的定义在libavformat/muxer_list.c源文件和libavformat/demuxer_list.c源文件中。具体内容如下所示:

static const AVOutputFormat *muxer_list[] = {
&ff_a64_muxer,
......
&ff_yuv4mpegpipe_muxer,
NULL };static const AVInputFormat *demuxer_list[] = {
&ff_aa_demuxer,
......
&ff_libmodplug_demuxer,
NULL };

值得注意的是,这里的两个list源文件,libavformat/muxer_list.c和libavformat/demuxer_list.c是在下载下来的源代码中看不到的,只有执行了FFMPEG根目录的configure文件才能够生成这两个list文件。这样做的目的是为了在用户自行往ffmpeg中加入了封装和解封装器的时候,这样做可以让用户的封装/解封装器一并被写入到list中

outdev_list 和 indev_list以及跟avdevice_register_all()的关系
outdev_list && indev_list的定义同样在allformat.c文件当中,如下所示:

static const AVInputFormat * const *indev_list = NULL;
static const AVOutputFormat * const *outdev_list = NULL;

这两个指针初始都为NULL,怎么能够挂载其他的list在上面的呢?
答案其实在allformat.c文件中。该代码中有一个函数avpriv_register_devices(),是供ffmpeg的其他注册函数进行调用的。查找其声明的头文件,为libavformat/internal.h

void avpriv_register_devices(const AVOutputFormat * const o[], const AVInputFormat * const i[]) {ff_mutex_lock(&avpriv_register_devices_mutex);outdev_list = o;indev_list = i;ff_mutex_unlock(&avpriv_register_devices_mutex);
#if FF_API_NEXTav_format_init_next();
#endif
}

所以这两个表实际上是由其他的函数来调用进行初始化的,调用该方法的函数为libavdevice/alldevice.c的avdevice_register_all()函数,也是经常会使用到的初始注册函数。

void avdevice_register_all(void)
{avpriv_register_devices(outdev_list, indev_list);
}

传入的两个list参数,outdev_list定义在libavdevice/outdev_list.c,indev_list定义在libavdevice/indev_list.c中,list中也都是AVInputFormat和AVOutputFormat对象

static const AVInputFormat * const indev_list[] = {
&ff_alsa_demuxer,
&ff_fbdev_demuxer,
&ff_lavfi_demuxer,
&ff_oss_demuxer,
&ff_sndio_demuxer,
&ff_v4l2_demuxer,
&ff_xcbgrab_demuxer,
NULL };

static const AVOutputFormat * const outdev_list[] = {
&ff_alsa_muxer,
&ff_fbdev_muxer,
&ff_oss_muxer,
&ff_sdl2_muxer,
&ff_sndio_muxer,
&ff_v4l2_muxer,
&ff_xv_muxer,
NULL };

至此实际上av_register_all()函数的初步入口我们也得到了了解,很神奇的是,在阅读源码追根溯源的过程中,连avdevice_register_all()所做的一些事情也顺带着被学龄前吸收了。

av_register_all

首先检测是不是调用过了,如果调用了,那么直接返回。 初始化所有的封装和解封装器(或称为复用器和解复用器),比如flv mp4 mp3
mov,但是在这个函数源码开始的时候会先调用avcodec_register_all(…)来注册所有的编码和解码器。那么什么是注册呢,就是把av_register_all中代码提到的也就是ffmpeg支持的所有封装格式加入到链表中去,等后面需要的时候再去这个链表中查找使用。

avcodec_register_all

首先检测是否已经注册,如果注册,则返回。
初始化所有的编解码器,如h264,h265等,解码和编码器也维护一个链表用于存储所有支持的编解码器。

avformat_network_init

初始化所有的网络库,比如rtmp,rtsp等。

FFMpeg (一) av_register_all()相关推荐

  1. cygwin环境下ffmpeg中av_register_all()函数,警告过时问题。

    开发环境介绍 这一次代码环境比较复杂,我使用的分别是VS2017与CygWin编译C文件 项目遇见的问题 1.VS2017中使用av_register_all(),显示4996错误. 2.cygwin ...

  2. FFMPEG系列课程(一)打开视频解码器

    测试环境:windows10 开发工具:VS2013 从今天开始准备些FFmpeg的系列教程,今天是第一课我们研究下打开视频文件和视频×××.演示环境在windows上,在linux上代码也是一样. ...

  3. 从零开始学习音视频编程技术(四) FFMPEG的使用

    零开始学习音视频编程技术(四) FFMPEG的使用 原文地址:http://blog.yundiantech.com/?log=blog&id=7 音视频开发中最常做的就是编解码的操作了,以H ...

  4. [imx6 VPU]硬解码+示例[ffmpeg获取海康rtsp h264流 QT显示]

    0.说明: 1,代码基于imx6q.imx6dl已验证. 2,网上关于imx6 VPU的资料很少,遂从官方例程mxc_vpu_test里面活生生抽出来.主要是dec_test()里面提取,因为我只要解 ...

  5. 通过ffmpeg实时读取宇视摄像头的高清帧流数据,并保存4张图片进行4合一照片的生成。

    通过ffmpeg实时读取宇视摄像头的高清帧流数据,并保存4张图片进行4合一照片的生成. FFmpeg视频解码过程 通常来说,FFmpeg的视频解码过程有以下几个步骤: 注册所支持的所有的文件(容器)格 ...

  6. 深度探索:使用FFmpeg实现视频Logo的添加与移除

    深度探索:使用FFmpeg实现视频Logo的添加与移除 前言 一.FFmpeg简介(Introduction to FFmpeg) 1.1 FFmpeg的定义(Definition of FFmpeg ...

  7. 【FFMPEG】vs2019调用FFmpeg动态库教程

    测试环境: windows10 64位 visual studio 2019 ffmpeg-4.2.2 如何自己编译FFmpeg点击此链接: [FFMPEG]win10上编译FFmpeg-64位动态库 ...

  8. Android直播开发之旅(17):使用FFmpeg提取MP4中的H264和AAC

    最近在开发中遇到了一个问题,即无法提取到MP4中H264流的关键帧进行处理,且保存到本地的AAC音频也无法正常播放.经过调试分析发现,这是由于解封装MP4得到的H264和AAC是ES流,它们缺失解码时 ...

  9. ffmpeg打开网络摄像头编解码详细流程.

    下载项目地址:https://download.csdn.net/download/signal___/21559335 videoplayer.h #include <QThread> ...

  10. FFmpeg 开启QSV硬解加速

    简介 QSV 全称:Quick Sync Video Acceleratio ,是Intel媒体开发库(The Intel® Media Software Development Kit)提供了一个对 ...

最新文章

  1. 对于索引(a,b,c),下列哪些说法是正确的
  2. LSB最低有效位和MSB最高有效位
  3. noi.ac NA535 【生成树】
  4. 养成一个SQL好习惯带来一笔大财富
  5. python 变量引用_Python 知识要点:变量及引用
  6. 人工智能技术将重塑银行业
  7. python编程 迷你世界_迷你编程下载-迷你世界迷你编程下载 v1.0官方版--pc6下载站...
  8. 12月江苏计算机考试报名入口,江苏2019年12月计算机等级报名时间丨报名系统
  9. ubuntu下sudo:add-apt-repository:command not found 问题
  10. 拉普拉斯变换与拉普拉斯逆变换的常用结论与经典公式
  11. selenium之浏览器弹出新窗口并在新窗口操作
  12. python开发项目经历_Python开发工程师岗位项目经历怎么写
  13. 当初的愿望实现了吗?
  14. KLOOK客路旅行获2亿美元D轮融资,将投入全球拓展
  15. TIOBE 2 月编程语言榜:C++ 势不可挡
  16. poi导出自定义文字水印excel文件
  17. 机械设计matlab,基于MATLAB的机械设计方法分析
  18. 赵纪锋首谈2018淘宝双十一全新玩儿法!恭喜你在双十一前看见了!
  19. 一年读100本书---HHR,NZJ---19年最后4个月
  20. MySQL实现成绩表单视图

热门文章

  1. 开发工具篇 程序员计算器
  2. 集体智慧编程--优化
  3. flowjo汉化版_流式细胞分析软件FlowJo
  4. 韩信点兵php,说说大脑的“同时性信息加工功能”
  5. 计算机丢失lua51dll怎么修复,lua51.dll
  6. 轻量级过程改进之项目启动
  7. ICON艾肯live声卡系列驱动安装设置方法
  8. 【行业专题报告】家电、白电、黑电-专题资料
  9. 解析rtcm32报文工具_RTCM数据格式实时处理方法应用
  10. 高等数学 · 希腊符号