win64版本的libav编译和简易使用指南

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的方案。

avconv -i xxx.mkv -c copy -c:1 libdfk_aac out.mkv

以上把输出文件的1号轨(一般也就是xxx.mkv的1号轨)按照aac重新编码,别的种类的轨全部择优对拷
avconv -i xxx.mkv -c copy -c:a libdfk_aac out.mkv
以上把择优选中的音轨按照aac重新编码,别种类的轨全部择优对拷
avconv -i xxx.mkv -i xxx.srt -c copy -c:a libdfk_aac out.mkv
以上演示另外附加一个srt

注:某些种类的输入文件(例如avi)在remux到mkv的时候会报错,给avconv加上-fflags +genpts一般能解决。

注:不过libav的matroska muxer的坑比较多,不太建议把字幕轨也放进去,甚至我觉得大部分时候可以把音频和视频轨mux到MP4容器里面,字幕外挂。

 

默认的择优输出有时候是个坑,特别是常见的多音轨(原语种和翻译配音)不一定能选择到原语种那一轨;这种时候,应该-map参数登场:

avconv -i xxx.mkv -c copy -c:a libdfk_aac -map 0 out.mkv
map 0是说把第0号输入文件中的所有轨都输入到out.mkv
avconv -i xxx.mkv -c copy -c:a libdfk_aac -map 0:v out.mkv
以上语句将只输出视频轨
avconv -i xxx.mkv -c copy -c:a libdfk_aac -map 0:v -map 0:1 -map 0:s out.mkv
如果输入文件中0为视频,1和2均为音频轨,3和4为字幕轨,以上语句可以只输出0 1 3轨,不输出2号音频轨
avconv -i xxx.mkv -c copy -c:a libdfk_aac -map 0:0 -map 0:1 -map 0:3 -map 0:4 out.mkv
以上功能相同,只是完全用轨的数字序号来表示
avconv -i xxx.mkv -c copy -c:a libdfk_aac -map 0 -map -0:2 out.mkv
以上功能相同,只是用“负”来表示从之前形成的map中去掉指定的轨

使用/dev/null或者windows的nul作为输出文件做benchmark的时候,需要指明输出的容器,例如-f avi。

很多AC3音轨是5.1的,大部分单身屌丝没有这个设备,而且5.1全用libfdk转换成AAC实在也是太慢,可以用avconv的-ac 2选项(-ac:[stream specifier] count,这个参数中的stream specifier任何时候都是针对输出文件的)来混音成立体声,转码速度快很多。

你可能感兴趣的:(lib)