FFmpeg 中对于 av 开头 API 的约束实现

当我们封装一个库给用户使用时,为了库的安全,通常会选择一定的命名规则提供给调用者使用,而有心的调用者通常也会做一定的猜测然后使用库里面隐藏的接口。FFmpeg 也是如此,有些API 也会有用户尝试 hacking 操作,所以 FFmpeg 做了一定的 API 命名规则约束,主要是为了确保外部用户可以使用 API 命名规则约定的 API,命名规则之外的 API 则不希望用户可以调用。

首先我们可以看一下 FFmpeg 的 API 命名规则:


2.4 Naming conventions

函数、变量和结构成员的名称必须小写,使用下划线 (_) 分隔单词。 例如,“avfilter_get_video_buffer”是可接受的函数名称,而“AVFilterGetVideo”则不是。

Names of functions, variables, and struct members must be lowercase, using underscores (_) to separate words. For example, ‘avfilter_get_video_buffer’ is an acceptable function name and ‘AVFilterGetVideo’ is not.

结构体、联合、枚举和类型定义的类型名称必须使用 CamelCase。 所有结构体和联合体的类型定义应与结构体/联合体标签的名称相同,例如 typedef struct AVFoo { ... } AVFoo;。 枚举通常不进行类型定义。

Struct, union, enum, and typedeffed type names must use CamelCase. All structs and unions should be typedeffed to the same name as the struct/union tag, e.g. typedef struct AVFoo { ... } AVFoo;. Enums are typically not typedeffed.

枚举常量和宏必须大写,伪装成函数的宏除外,它应该使用函数命名约定。

Enumeration constants and macros must be UPPERCASE, except for macros masquerading as functions, which should use the function naming convention.

库中的所有标识符都应按如下方式命名:

All identifiers in the libraries should be namespaced as follows:

具有文件和较低范围的标识符(例如局部变量、静态函数)以及结构和联合成员没有命名空间,

ff_ 前缀必须用于在文件范围之外可见的变量和函数,但只能在单个库内部使用,例如 ‘ff_w64_demuxer’。 这可以防止 FFmpeg 静态链接时发生名称冲突。

No namespacing for identifiers with file and lower scope (e.g. local variables, static functions), and struct and union members,

The ff_ prefix must be used for variables and functions visible outside of file scope, but only used internally within a single library, e.g. ‘ff_w64_demuxer’. This prevents name collisions when FFmpeg is statically linked.

对于在文件范围之外可见、跨多个库内部使用的变量和函数,请使用 avpriv_ 作为前缀,例如“avpriv_report_missing_feature”。

For variables and functions visible outside of file scope, used internally across multiple libraries, use avpriv_ as prefix, for example, ‘avpriv_report_missing_feature’.

所有其他内部标识符(例如私有类型或宏名称)都应该命名空间以避免可能的内部冲突。 例如。 H264_NAL_SPS 与 HEVC_NAL_SPS。

All other internal identifiers, like private type or macro names, should be namespaced only to avoid possible internal conflicts. E.g. H264_NAL_SPS vs. HEVC_NAL_SPS.

除了常用的 av_ 之外,每个库都有自己的公共符号前缀(avformat_ 表示 libavformat、avcodec_ 表示 libavcodec、swr_ 表示 libswresample 等)。 检查现有代码并相应地选择名称。

其他公共标识符(结构、联合、枚举、宏、类型名称)必须使用其库的公共前缀(AV、Sws 或 Swr)。

Each library has its own prefix for public symbols, in addition to the commonly used av_ (avformat_ for libavformat, avcodec_ for libavcodec, swr_ for libswresample, etc). Check the existing code and choose names accordingly.

Other public identifiers (struct, union, enum, macro, type names) must use their library’s public prefix (AV, Sws, or Swr).

此外,为系统保留的名称空间不应被入侵。 以 _t 结尾的标识符由 POSIX 保留。 还要避免名称以 __ 或 _ 开头,后跟大写字母,因为它们是 C 标准保留的。 以 _ 开头的名称在文件级别保留,不能用于外部可见的符号。 如果有疑问,请完全避免以 _ 开头的名称。

Furthermore, name space reserved for the system should not be invaded. Identifiers ending in _t are reserved by POSIX. Also avoid names starting with __ or _ followed by an uppercase letter as they are reserved by the C standard. Names starting with _ are reserved at the file level and may not be used for externally visible symbols. If in doubt, just avoid names starting with _ altogether.

从这个命名规则中可以看到,av 开头的 API 接口我们都可以使用,但是有时候可能我们想用 ffurl 开头的API,这其实是不允许的,因为 ff 开头的 API 是给 ffmpeg 内部的哥哥模块使用的,并不是给外部 API 用户使用的。而 FFmpeg 为了确保这个规则是有效的,就在自身的构建流程里面加入了对应的处理。

首先看一下 configure 文件中动态生成ffbuild/config.mak

cat > ffbuild/config.mak <<EOF

append SHFLAGS '-Wl,${version_script},\$(SUBDIR)lib\$(NAME).ver'

ffmpeg 生成库对应的 Makefile 规则可以在 library.mak里面看到:


$(SUBDIR)lib$(NAME).ver: $(SUBDIR)lib$(NAME).v $(OBJS)
        $$(M)sed 's/MAJOR/$(lib$(NAME)_VERSION_MAJOR)/' $$< | $(VERSION_SCRIPT_POSTPROCESS_CMD) > $$@

在做库的 make 之前,会生成libavformat/libavformat.ver文件,会配合链接器的version_script参数来设定动态库的符号表。
version_script在链接器里面有详细的描述:


19.3 LD Version Scripts
The lib-symbol-versions module can be used to add shared library versioning support. Currently, only GNU LD and the Solaris linker supports this.

Version scripts provides information that can be used by GNU/Linux distribution packaging tools. For example, Debian has a tool dpkg-shlibdeps that can determine the minimal required version of each dependency (by looking at the symbol list) and stuff the information into the Debian specific packaging files.

For more information and other uses of version scripts, see Ulrich Drepper’s paper https://www.akkadia.org/drepper/dsohowto.pdf

You use the module by importing it to your library, and then add the following lines to the Makefile.am that builds the library:

if HAVE_LD_VERSION_SCRIPT
libfoo_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libfoo.map
endif
The version script file format is documented in the GNU LD manual, but a small example would be:

LIBFOO_1.0 {
  global:
    libfoo_init; libfoo_doit; libfoo_done;

  local:
    *;
};
If you target platforms that do not support linker scripts (i.e., all platforms that doesn’t use GNU LD) you may want to consider a more portable but less powerful alternative: libtool -export-symbols. It will hide internal symbols from your library, but will not add ELF versioning symbols. Your usage would then be something like:

if HAVE_LD_VERSION_SCRIPT
libfoo_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libfoo.map
else
libfoo_la_LDFLAGS += -export-symbols $(srcdir)/libfoo.sym
endif
See the Libtool manual for the file syntax, but a small example would be:

libfoo_init
libfoo_doit
libfoo_done
To avoid the need for a *.sym file if your symbols are easily expressed using a regular expression, you may use -export-symbols-regex:

if HAVE_LD_VERSION_SCRIPT
libfoo_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libfoo.map
else
libfoo_la_LDFLAGS += -export-symbols-regex '^libfoo_.*'
endif
For more discussions about symbol visibility, rather than shared library versioning, see the visibility module (see Controlling the Exported Symbols of Shared Libraries).

看一下 libavformat/libavformat.ver 里面的内容:

LIBAVFORMAT_60 {
    global:
        av*;
    local:
        *;
};

从文件中的规则来看,除了 av 开头的符号是 global外,别的都是 local。

为了验证是否真的如此,我们自己来简单的实现一下:

Makefile


TOPDIR  = $(shell pwd)
CFLAGS  = \
    -fPIC \
    -O0 \
    -Werror \
    -g \
    -I.

OBJS  += $(TOPDIR)/api.o

MAIN  += $(TOPDIR)/main.o

TARGET  = $(TOPDIR)/example

LIBNAME  = liblocaltestapi.so
SHAREDFLAGS  = -shared -Wl,-soname,$(LIBNAME)
LIBSHARED1  += -Wl,-Bsymbolic \
       -Wl,-O0 \
       -Wl,--sort-common \
       -Wl,--as-needed \
       -Wl,-z,relro \
       -Wl,-z,now \
       -Wl,--disable-new-dtags \
       -Wl,--gc-sections \
       -Wl,--version-script,$(TOPDIR)/apilist1.map

LIBSHARED2  += -Wl,-Bsymbolic \
       -Wl,-O0 \
       -Wl,--sort-common \
       -Wl,--as-needed \
       -Wl,-z,relro \
       -Wl,-z,now \
       -Wl,--disable-new-dtags \
       -Wl,--gc-sections \
       -Wl,--version-script,$(TOPDIR)/apilist2.map

CC  = gcc

all: $(OBJS)
  $(CC) $(SHAREDFLAGS) $(LIBSHARED) -o $(TOPDIR)/$(LIBNAME) $(OBJS)

test1: $(MAIN)
  $(CC) $(SHAREDFLAGS) $(LIBSHARED1) -o $(TOPDIR)/$(LIBNAME) $(OBJS)
  $(CC) $(CFLAGS) -o $(TARGET) $(MAIN) -L$(TOPDIR) -llocaltestapi

test2: $(MAIN)
  $(CC) $(SHAREDFLAGS) $(LIBSHARED2) -o $(TOPDIR)/$(LIBNAME) $(OBJS)
  $(CC) $(CFLAGS) -o $(TARGET) $(MAIN) -L$(TOPDIR) -llocaltestapi

clean:
  rm -rf $(TARGET) $(LIBNAME) $(OBJS)

apilist1的内容如下:

[lq@chinaffmpeg so]$ cat apilist1.map
localtestapi {
    global:
        apitest*;
    local:
        *;
};

apilist2的内容如下:


[lq@chinaffmpeg so]$ cat apilist2.map
localtestapi {
    global:
        *;
};

api.c 中的实现如下:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void apitest_first(int num)
{
  fprintf(stdout, "output apitest_first %d\n", num);
}

void apitest_second(int num)
{
  fprintf(stdout, "output apitest_second %d\n", num);
}

void apitest_third(int num)
{
  fprintf(stdout, "output apitest_third %d\n", num);
}

void for_apitest(int num)
{
  fprintf(stdout, "output for_apitest %d\n", num);
}

main用来做链接实验:


#include "api.h"

int main(int argc, char *argv[])
{
  apitest_first(1);
  apitest_second(2);
  apitest_third(3);
  for_apitest(4);

  return 0;
}

然后我们可以根据 Makefile 里面的 test1 与 test2 来做链接实验。


[lq@chinaffmpeg so]$ make test1
gcc -shared -Wl,-soname,liblocaltestapi.so -Wl,-Bsymbolic -Wl,-O0 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags -Wl,--gc-sections -Wl,--version-script,/home/lq/so/apilist1.map -o /home/lq/so/liblocaltestapi.so /home/lq/so/api.o
gcc -fPIC -O0 -Werror -g -I. -o /home/lq/so/example /home/lq/so/main.o -L/home/lq/so -llocaltestapi
/home/lq/so/main.o: In function `main':
/home/lq/so/main.c:8: undefined reference to `for_apitest'
collect2: error: ld returned 1 exit status
make: *** [Makefile:44: test1] Error 1
[lq@chinaffmpeg so]$ make test2
gcc -shared -Wl,-soname,liblocaltestapi.so -Wl,-Bsymbolic -Wl,-O0 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now -Wl,--disable-new-dtags -Wl,--gc-sections -Wl,--version-script,/home/lq/so/apilist2.map -o /home/lq/so/liblocaltestapi.so /home/lq/so/api.o
gcc -fPIC -O0 -Werror -g -I. -o /home/lq/so/example /home/lq/so/main.o -L/home/lq/so -llocaltestapi
[lq@chinaffmpeg so]$

从实验中中输出的内容可以看出,test1 在链接时会有错误提示,找不到 for_apitest 符号,而在 test2 的时候没有约束导出的符号,所以链接也自然没有问题。

作者:大师兄悟空
来源:流媒体技术
原文:https://mp.weixin.qq.com/s/UPDljCLlSNHBCG7M3Kw8CQ

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论