在学习ffmpeg代码的时候,自始至终要记得你所写的代码面对的用户是程序员,你应该提供一种方式让他们来轻松使用你的api,并容易的做出正确的修改。为了提高自己的水平,看相当数量的优质代码是必不可少的一步。
在internal.h中可以看到为了避免后继的开发者使用printf和puts等输出,ffmpeg定义了一些宏:
#undef printf
#define printf please_use_av_log
#undef fprintf
#define fprintf please_use_av_log
#undef puts
#define puts please_use_av_log
#undef perror
#define perror please_use_av_log_instead_of_perror
同样的对于内存分配等函数,ffmpeg也通过宏定义禁止了。
#undef malloc
#define malloc please_use_av_malloc
#undef free
#define free please_use_av_free
#undef realloc
#define realloc please_use_av_realloc
#undef time
#define time time_is_forbidden_due_to_security_issues
#undef rand
#define rand rand_is_forbidden_due_to_state_trashing_use_av_random
#undef srand
#define srand srand_is_forbidden_due_to_state_trashing_use_av_init_random
#undef random
#define random random_is_forbidden_due_to_state_trashing_use_av_random
#undef sprintf
#define sprintf sprintf_is_forbidden_due_to_security_issues_use_snprintf
#undef strcat
#define strcat strcat_is_forbidden_due_to_security_issues_use_av_strlcat
#undef exit
#define exit exit_is_forbidden
当然我们也可以删掉这些宏使得可以使用诸如fprintf这样的函数,log的回调函数当中也是使用了这个函数,不过为了程序有良好的层次,最好还是使用ffmpeg的接口,此外我们看看ffmpeg对于数字的使用:
#ifndef INT16_MIN
#define INT16_MIN (-0x7fff-1)
#endif
#ifndef INT16_MAX
#define INT16_MAX 0x7fff
#endif
#ifndef INT32_MIN
#define INT32_MIN (-0x7fffffff-1)
#endif
#ifndef INT32_MAX
#define INT32_MAX 0x7fffffff
#endif
#ifndef UINT32_MAX
#define UINT32_MAX 0xffffffff
#endif
#ifndef INT64_MIN
#define INT64_MIN (-0x7fffffffffffffffLL-1)
#endif
#ifndef INT64_MAX
#define INT64_MAX INT64_C(9223372036854775807)
#endif
#ifndef UINT64_MAX
#define UINT64_MAX UINT64_C(0xFFFFFFFFFFFFFFFF)
#endif
#ifndef INT_BIT
# if INT_MAX != 2147483647
# define INT_BIT 64
# else
# define INT_BIT 32
# endif
#endif
最后一个INT_BIT的宏定义给出了int类型的位数。可以料想ffmpeg为了在64位和32位机器上正确使用,当中用了不少技巧,一些内部的定义可以在internal.h中看到。好,再回到ffmpeg的log,关于log的等级,就不必细说了,可以在log4j的文档当中仔细研究,通用的等级都差不多。我们可以看到log的函数为:
void av_log(void* avcl, int level, const char *fmt, ...)
{
va_list vl;
va_start(vl, fmt);
av_vlog(avcl, level, fmt, vl);
va_end(vl);
}
av_vlog当中是一个回调函数,这里就有一个问题了,为什么这里用回调?一般回调我们用在几种情况:
1. 事件通知。函数想把回调函数作为一个属性来处理,这样他不必知道该通知谁,相当于回调函数注册了一个地址给函数,函数在特定事件时通知。
举例来说:
A) 当一个文件处理函数在后台运行,并且不时的更改界面上显示的进度条,和其他status时,这时要把进度条更新的函数地址,和status更新的函数地址传给文件处理函数来回调。
B) 同样的,不同的模块之间的消息通信也可以采用把自己的消息添加函数作为回调函数给对方的方式。
2. 灵活性。我们的函数所调用的处理函数会更改。
举例来说:
A) 我在一个正则表达式处理函数里处理一个文本文件,这个文本文件的每一行都会出现一个解析的结果,每行的语义是既定的,这时我希望把不同行的处理函数作为回调传给正则表达式处理函数,因为我实在不想把行语义的逻辑放在底层的函数里。
B) 同样的,我有一段读取文件的代码,我同时又有一段解码器的代码,在读取文件之前,我已经通过头信息知道了文件的类型。为了我的代码可以方便的注册解码器,我希望把解码器函数作为回调函数给读取文件的函数。
3. 状态的切换。程序工作在不同的模式下,通过运行时修改程序的函数来决定这时在什么工作状态。这其实也是一种灵活性,不过稍有特别。
举例来说:
A) ffmpeg中的av_log_set_callback,我们可以看到,在show_help函数中,这个回调函数的指针被改变了,也就是说log进入了另一种工作模式,log可以是文件,也可以是控制台输出,可以是httplog。
B) 引申一下,我们开发的服务器程序一旦发布,不可避免的有现场问题,一般现场调试的时候我们都会通过telnet方式来登陆这个程序查看一些信息,通过设置回调,我们可以改变程序的一些行为,来达到调试的目的。
Ffmpeg的log就介绍到这里,以下是一些题外话:
想写好的代码,要读许多优秀的开源代码才可以。
写出ffmpeg这样的平台,不是工作经验多就可以做到的,对标准的每一个细节都要理解深刻。