libav是ffmpeg的一个分支,我纯粹是出于喜欢avconv和avprobe这样清晰的命名才对它有好感。
因为官方网站有一个编译指南(https://libav.org/platform.html#Microsoft-Visual-C_002b_002b-or-Intel-C_002b_002b-Compiler-for-Windows),以下只是本人在实际编译过程中的一些问题点的记录。
编译环境准备
因为libav使用configure脚本来编译,所以必须要安装mingw的msys,这样可以有一个bash来运行configure脚本,以及保证一个可用的gnu make来执行编译。现在安装Mingw可以使用mingw-get,类似于一个包管理器,我们只需要msys的包和coreutils的ext包就可以了。
其次我使用VS2013 Express版本的msvc来编译。在之前版本的msvc,只需要安装Windows SDK那个光盘镜像就可以有一个命令行的编译环境了,但是VS2013之后,必须安装VS2013的光盘镜像才可以(Express版本足矣)。
另外libav中有汇编代码,语法是yasm,所以得下载一份windows版本的yasm。
依赖的第三方库准备
我只准备了zlib和libfdk_aac两个库,前者其实我不知道libav哪里用了,只是看到指南中有提及,就下载了,libfdk_aac据说是目前开源界最好的aac编解码库,只是因为用了一个custom的license,所以用它编译出来的二进制文件是禁止发布的,所以想用支持这个库的libav只能自己编译用。
按照指南中的说明来修改编译zlib即可,非常straight forward,只是要编译一个x64的zlib.lib有需要注意的地方;cl.exe编译object file的时候是没有选项来指定要生成32位还是64位的,相反的,它用不同的cl.exe用来实现这一点,使用vs2013的两个命令行快捷方式开启编译环境命令行即可(也就是给vcvarsall.bat传递不同的参数来打开相应的编译环境命令行,例如我安装的32位的VS2013 Express,就可以用vcvarsall.bat x86_amd64在PATH中准备好一个本身是32位的但生成的目标文件是64bit的cl.exe)。
libfdk_aac的代码中没有为msvc的toolchain来准备任何配置,所以我采用的最笨的方法:在VC中新建一个static library类型的工程,然后把需要的文件全部添加进来(除了aac decoder的部分),并且配置各种细节,来生成最后的64位的fdk-aac.lib文件。
configure libav
然后把zlib的头文件和libfdk_aac的encoder的头文件放到3rd/headers,把刚才生成的lib放到3rd/libs。正如指南中说的,msvc的toolchain会使用INCLUDE和LIB两个环境变量来确定头文件和库文件的位置;在打开vcvarsall.bat之后,这两个变量已经带了系统库的设定;而我们需要在./configure和make执行的时候,都要在这两个变量中加上我们的3rd环境;因为用msys的bash来运行,只需要在这两个命令前面如此设置即可:
INCLUDE=3rd/headers/;$INCLUDE LIB=3rd/libs/;$LIB ./configure
还有需要删除mingw的link.exe,避免与msvc中的link.exe冲突,指南中也提到了。
另外附上我的configure参数,因为我的fdk-aac.lib中没有解码器,我专门声明了不使用它的解码器:
./configure --disable-debug \ --enable-gpl --enable-nonfree \ --enable-runtime-cpudetect \ --disable-avplay --disable-avserver \ --disable-encoder=aac \ --enable-libfdk-aac --disable-decoder=libfdk_aac \ --toolchain=msvc
libav本身的修改和make
libav在windows下,会把通过GetCommandLineW()来获取UTF-16LE版本的命令行参数,并转换成UTF-8之后才进行后续的所有操作(libav内部都是用UTF-8来统一对待字符串编码的);但是在往控制台打log的时候,直接用fputs接口向stderr打印UTF-8的multibyte字符串,在CP_ACP(ANSI codepage)为gbk的中文windows上,字符串中的非ascii字符(例如刚才命令行传入的中文文件名)就会打印为乱码(因为系统用GBK来解码UTF-8的字符串)。
解决方案是:
先用MultiByteToWideChar()把待打印的UTF-8 multibyte字符串转换回UTF-16LE的wchar_t字符串;
这时候如果用fwputs来打印转换后的字符串,会发现一遇到非ascii字符就会调用失败(打印停止),这是因为默认的locale中的LC_TYPE(这个locale category专门控制字符相关的操作的)是"C",用setlocale(LC_TYPE, "")设置为系统默认(中文系统为GBK),或者显式调用setlocale(LC_TYPE, ".936")设置为GBK,就能成功打印出UTF-16LE中的GBK能表示的字符;
但是这个locale+fwputs的方案只能显示出一种ANSI方案支持的字符,想显示出所有UTF-16LE支持的字符,应该直接使用windows的API调用,WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), wcMsg, wcMsgLen, &wcMsgLen, NULL)。
以下是我对libavutil/log.c的patch:
--- log.c 2014-08-04 10:02:34 +0800 +++ libav-64bit/libav-10.3/libavutil/log.c 2014-09-10 06:42:02 +0800 @@ -59,6 +59,10 @@ static void colored_fputs(int level, const char *str) { +#if HAVE_COMMANDLINETOARGVW && defined(_WIN32) + int wcBufLen = 0; + wchar_t* wcBuf = NULL; +#endif if (use_color < 0) { #if HAVE_SETCONSOLETEXTATTRIBUTE CONSOLE_SCREEN_BUFFER_INFO con_info; @@ -83,7 +87,19 @@ if (use_color) { set_color(level); } +#if HAVE_COMMANDLINETOARGVW && defined(_WIN32) + wcBufLen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + if (wcBufLen == 0) { + fputs("Failed to convert multibyte log string to widechar.\n", stderr); + exit(1); + } + wcBuf = (wchar_t*)malloc(wcBufLen *2); + wcBufLen = MultiByteToWideChar(CP_UTF8, 0, str, -1, wcBuf, wcBufLen); + WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), wcBuf, wcBufLen, &wcBufLen, NULL); + free((void*)wcBuf); +#else fputs(str, stderr); +#endif if (use_color) { reset_color(); }
使用avconv的简易教程
可以参考avprobe input.mkv来理解。
在libav的术语里,容器的每一轨叫做一个stream;每个stream都可用stream specifier来代称,它常见的有两种形式:
stream_index 单独一个数字,也就是上面avprobe输出的Stream 0.0:的第二个数字序号;
steam_type[:stream_index] 例如a和a:1,分别表示所有音轨与音轨中的第二个。
下面的转换通常就需要用这种方式指明某个stream的转换方案。
avconv的默认工作方式,就是把所有-i的输入文件,其中的每种轨(例如所有视频轨)挑选出码率最好的一个,解码并编码,混流到输出文件中。
-c指明解码(在-i之前,很少用)或编码(在输出文件之前)的格式;-c后面就可选地可以用stream specifier来指明输入文件或者输出文件的某个stream的方案。