当前位置:网站首页>FFMpeg (一) av_register_all()
FFMpeg (一) av_register_all()
2022-06-28 08:20:00 【qq_37705525】
ffmpeg
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_NEXT
av_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等。
边栏推荐
- Jacobian matrix J commonly used in slam
- js取整的小技巧
- Set the encoding of CMD to UTF-8
- 抗洪救灾,共克时艰,城联优品捐赠10万元爱心物资驰援英德
- Image translation /transformer:ittr: unpaired image to image translation with transformers
- 【Go ~ 0到1 】 第一天 6月24 变量,条件判断 循环语句
- [learning notes] simulation
- Super Jumping! Jumping! Jumping!
- Introduction, compilation, installation and deployment of Doris learning notes
- 解决npm ERR! Unexpected end of JSON input while parsing near问题
猜你喜欢

第六届智能家居亚洲峰会暨精品展(Smart Home Asia 2022)将于10月在沪召开

PMP从报考到拿证基本操作,了解PMP必看篇

抗洪救灾,共克时艰,城联优品捐赠10万元爱心物资驰援英德

设置网页的标题部分的图标

Kubernetes notes and the latest k3s installation introduction

Solution: selenium common. exceptions. WebDriverException: Message: ‘chromedriver‘ execu

安装nrm后,使用nrm命令报错internal/validators.js:124 throw new ERR_INVALID_ARG_TYPE(name, ‘string‘, value)

AI chief architect 8-aica-gao Xiang, in-depth understanding and practice of propeller 2.0

DB

Unity 获取当前物体正前方,一定角度、距离的坐标点
随机推荐
Redis persistence problem and final solution
Devops foundation chapter Jenkins deployment (II)
抗洪救灾,共克时艰,城联优品捐赠10万元爱心物资驰援英德
Image translation /transformer:ittr: unpaired image to image translation with transformers
[learning notes] differential constraint
你了解TCP协议吗(二)?
Kubernetes notes and the latest k3s installation introduction
Force buckle 1884 Egg drop - two eggs
Preparation for Oracle 11g RAC deployment on centos7
[learning notes] simulation
MySQL two table connection principle (understand join buf)
Is it reliable for the top ten securities companies to register and open accounts? Is it safe?
关于如何在placeholder中使用字体图标
DB
Redis uses sentinel master-slave switching. What should the program do?
Priority of JS operator
Two tips for block level elements
Modifying the SSH default port when installing Oracle RAC makes CRS unable to install
Solve NPM err! Unexpected end of JSON input while parsing near
第六届智能家居亚洲峰会暨精品展(Smart Home Asia 2022)将于10月在沪召开