今天逛 Nico 的时候忽然起了搬运的念头,于是把原视频下载下来,开始进行传说中的「战渣浪」运动。在地图上到处逛搜集到如下信息:
- 非 FLV 格式一定会被新浪二压
- 整个文件的平均码率大于 500Kbps 会被新浪二压(也有说 1000Kbps 的)
发现不是很麻烦,懒得再去找某个版本的 MediaCoder,就直接上 FFmpeg 了……
查看文件信息
为了确定码率,首先用这样的方法查看文件信息:
ffmpeg -i <输入文件>
我从 Nico 下载视频的时候不是混杂时段,下载到的是高清 MP4 文件,于是 FFmpeg 就会输出类似这样的信息:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'filename.mp4':
Metadata:
Duration: 00:02:59.77, start: 0.000000, bitrate: 430 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 854x480, 315 kb/s, 30 fps, 30 tbr, 30 tbn, 60 tbc
Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 44100 Hz, stereo, s16, 111 kb/s
可以看出一共有 1 个输入文件,其中包含 1 条视频流和 1 条音频流。各自的码率已经可以看得很清楚了,这里该文件的平均码率没有超过 500Kbps,于是安心地进行下一步。
将 MP4 封装为 FLV
因为下载下来的是 MP4 格式文件,需要将文件(容器)换为 FLV 才能够不被新浪二压:
ffmpeg -i <输入文件> -vcodec copy -acodec copy -f flv <输出文件>
FFmpeg 的 -vcodec
和 -acodec
选项指定了输出文件的编码器,而填入 copy
则表示直接复制,不编码。
而 -f
选项是用来强制指定输出格式的,虽然 FFmpeg 自己会根据输出文件的扩展名猜,但还是写一下更保险。
其它未尽事宜
于是收工上传,不久发现直接成功了 = = 呃……我记得很久很久以前我用 MediaCoder 还失败了两次呢,囧。
这次很幸运的是下载到的文件码率没有超标,如果超标的话,据说这样可以指定码率:
ffmpeg -i input.mp4 -vcodec h264 -acodec aac -b:v <视频码率> -b:a <音频码率> output.mp4
至于传说中的 h264 2-pass 压制法(用来在更好的视频质量下控制码率),据说是这样的:
ffmpeg -i input.mp4 -pass 1 -vcodec h264 -an -b:v <视频码率> -f rawvideo -y NUL
ffmpeg -i input.mp4 -pass 2 -vcodec h264 -acodec aac -b:v <视频码率> -b:a <音频码率> output.mp4
这里的第一 Pass 因为目的只是取得一个包含视频信息的 log 文件,所以用 -an
禁用音频、用 NUL
(或者 /dev/null
)防止视频文件生成。
至于其它用法就留待以后要用上的时候再去研究了(懒……
以上。
还记得大明湖畔的 mbstowcs
和 wcstombs
吗?个人觉得这哥俩的存在感真的是比 setjmp
和 longjmp
还要低啊。
size_t mbstowcs (wchar_t *dest, char const *src, size_t max);
size_t wcstombs (char *dest, wchar_t const *src, size_t max);
解释一下:「mbs」对应「Multibyte String」而「wcs」对应「Wide-character String」。于是顾名思义,这两位的功能就是把多字节字符串和宽字符字符串互相转换。
如果把 Wide-character 看作是 Unicode 的 code point,那么 Multibyte 就是对该 code point 的具体编码。至于这两个函数是如何得知 Multibyte 究竟使用什么编码,答案是他们根据当前 locale 中所指定的字符编码决定。下面的代码是在 Windows 上将 Big5 编码的字符串转换为 GBK 编码:
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE (128)
int main()
{
char const *source = "Hello 世界!"; // 将文件保存为 Big5 编码
wchar_t wc_out[BUFFER_SIZE];
char mb_out[BUFFER_SIZE];
printf("%s\n", source); // 输出不正常
setlocale(LC_CTYPE, "chinese-traditional"); // 认为输入的 MBS是 Big5 编码
mbstowcs(wc_out, source, BUFFER_SIZE);
setlocale(LC_CTYPE, "chinese-simplified"); // 设置输出的 MBS 为 GBK 编码
wcstombs(mb_out, wc_out, BUFFER_SIZE);
printf("%s\n", mb_out); // 正常输出
}
也可以试试日文 Shift-JIS 编码和 GBK 的转换:将文件存为 Shift-JIS 编码,然后把第一个 setlocale
的目标修改为 "japanese"
(反正「世界」在简繁日里写法都一样)。甚至还可以跑到 Linux 上在「zh_CN.gbk」和 「zh_CN.utf-8」互转。
于是第一次知道这对函数时,我的第一想法是「哇,原来标准库里也有这样的函数啊!那我岂不是可以用很 portable 的方法来转换编码了?」无奈正常人都会说:你太甜了,这两个函数完全不可靠,还是用 Windows API 吧~还是用 libiconv 吧~
为什么呢?因为这两个函数所能进行的转换取决于 locale 的支持。例如 Windows 的中文 locale 就只能设置为 GBK 和 Big5 编码两种,Unix 的可用 locale 也和系统本身有关(?)。所以,想要通用还是老老实实用 libiconv 吧少年们~
参考:Code Pages Supported by Windows
背景知识
Lua 给我的感觉是:各种内置函数和标准库的存在感都是比较强的。如果执行这句:
for name in pairs(_G) do print(_G) end
就会把各种环境中已存在名称的打印出来:
- 全局变量:比如字符串
_VERSION
。
- 内置函数:比如
print
、tonumber
、dofile
之类。
- 模块名称:比如
string
、io
、coroutine
之类。
这里的全局变量 _G
就是存放环境的表(于是会有 _G
中存在着 _G._G
的递归)。
于是,平时对于全局变量的访问就可以等同于对 _G
表进行索引:
value = _G[varname] --> value = varname
_G[varname] = value --> varname = value
改变函数的环境
函数的上下文环境可以通过 setfenv(f, table)
函数改变,其中 table
是新的环境表,f
表示需要被改变环境的函数。如果 f
是数字,则将其视为堆栈层级(Stack Level),从而指明函数(1 为当前函数,2 为上一级函数):
a = 3 -- 全局变量 a
setfenv(1, {}) -- 将当前函数的环境表改为空表
print(a) -- 出错,因为当前环境表中 print 已经不存在了
没错,不仅是 a
不存在,连 print
都一块儿不存在了。如果需要引用以前的 print
则需要在新的环境表中放入线索:
a = 3
setfenv(1, { g = _G })
g.print(a) -- 输出 nil
g.print(g.a) -- 输出 3
沙盒
于是,出于安全或者改变一些内置函数行为的目的,需要在执行 Lua 代码时改变其环境时便可以使用 setfenv
函数。仅将你认为安全的函数或者新的实现加入新环境表中:
local env = {} -- 沙盒环境表,按需要添入允许的函数
function run_sandbox(code)
local func, message = loadstring(code)
if not func then return nil, message end -- 传入代码本身错误
setfenv(func, env)
return pcall(func)
end
Lua 5.2 的 _ENV 变量
Lua 5.2 中所有对全局变量 var
的访问都会在语法上翻译为 _ENV.var
。而 _ENV
本身被认为是处于当前块外的一个局部变量。(于是只要你自己定义一个名为 _ENV
的变量,就自动成为了其后代码所处的「环境」(enviroment)。另有一个「全局环境」(global enviroment)的概念,指初始的 _G
表。)
Lua 的作者之一 Roberto Ierusalimschy 同志在介绍 Lua 5.2 时说:
the new scheme, with _ENV, allows the main benefit of
setfenv with a little more than syntactic sugar.
就我的理解来说,优点就是原先虚无缥缈只能通过 setfenv
、getfenv
访问的所谓「环境」终于实体化为一个始终存在的变量 _ENV
了。
于是以下两个函数内容大致是一样的:
-- Lua 5.1
function foobar()
setfenv(1, {})
-- code here
end
-- Lua 5.2
function foobar()
local _ENV = {}
-- code here
end
而更进一步的是,5.2 中对 load
函数作出了修改。(包括但不限于 :))合并了 loadstring
功能,并可以在参数中指定所使用的环境表:
local func, message = load(code, nil, "t", env)
参考
没错,Lua 中只存在表(Table)这么唯一一种数据结构,但依旧可以玩出面向对象的概念。
添加成员函数
好吧,如果熟悉 C++ 还是很好理解类似的演化过程的:如果说 struct
里可以添加函数是从 C 过渡到 C++ 的第一认识的话,为 Table 添加函数也可以算是认识 Lua 是如何面向对象的第一步吧。
player = { health = 200 } --> 一个普通的 player 表,这里看作是一个对象
function takeDamage(self, amount)
self.health = self.health - amount
end
takeDamage(player, 20) --> 调用
如何将独立的 takeDamage
塞进 player
中咧?答案是直接定义进去:
player = { health = 200 }
function player.takeDamage(self, amount)
self.health = self.health - amount
end
player.takeDamage(player, 20) --> 调用
这样就相当于在 player
表中添加了一个叫做 takeDamage
的字段,和下面的代码是一样的:
player = {
health = 200,
takeDamage = function(self, amount) --> Lua 中的函数是 first-class value
self.health = self.health - amount
end
}
player.takeDamage(player, 20) --> 调用
调用时的 player.takeDamage(player, 20)
稍显不和谐(据说用术语叫做 DRY),于是就要出动「冒号操作符」这个专门为此而生的语法糖了:
player:takeDamage(20) --> 等同于 player.takeDamage(player, 20)
function player:takeDamage(amount) --> 等同于 function player.takeDamage(self, amount)
从对象到类
类的意义在于提取一类对象的共同点从而实现量产(我瞎扯的 >_<)。同样木有 Class 概念的 Javascript 使用 prototype
实现面向对象,Lua 则通过 Metatable 实现与 prototype
类似的功能。
Player = {}
function Player:create(o) --> 参数 o 可以暂时不管
o = o or { health = 200 } --> Lua 的 or 与一般的 || 不同,如果非 nil 则返回该非 nil 值
setmetatable(o, self)
self.__index = self
return o
end
function Player:takeDamage(amount)
self.health = self.health - amount
end
playerA = Player:create() --> 参数 o 为 nil
playerB = Player:create()
playerA:takeDamage(20)
playerB:takeDamage(40)
顾名思义 Metatable 也是一个 Table,可以通过在其中存放一些函数(称作 metamethod)从而修改一些默认的求值行为(如何显示为字符串、如何相加、如何连接、如何进行索引)。Metatable 的 __index
域设置了「如何进行索引」的方法。例如调用 foo.bar
时,如果在 foo
中没有找到名为 bar
的域时,则会调用 Metatable:__index(foo, bar)
。于是:
playerA:takeDamage(20)
因为在 playerA
中并不存在 takeDamge
函数,于是求助于 Metatable:
getmetatable(playerA).__index.takeDamage(playerA, 20)
带入 Metatable 后:
Player.__index.takeDamage(playerA, 20)
因为 Player
的 __index
在 create
时被指定为 self
,所以最终变为:
Player.takeDamage(playerA, 20)
于是 takeDamage
的 self
得到了正确的对象 playerA
。
继承
继承是面向对象的一大特性,明白了如何创建「类」,那么继承也就比较明了了,还记得大明湖畔的参数 o
么?
RMBPlayer = Player:create()
function RMBPlayer:broadcast(message) --> 为子类添加新的方法
print(message)
end
function RMBPlayer:takeDamage(amount) --> 子类重载父类方法
self.health = self.health - amount / (self.money / 100)
end
vip = RMBPlayer:create { money = 200 } --> 子类添加新成员(单个 Table 作为参数可以省略括号)
vip:takeDamage(20)
vip:broadcast("F*ck")
以上便是 Lua 中实现面向对象的基本方法。
libcurl 是鼎鼎大名的开源客户端 URL 传输库,支持 FTP、HTTP 以及其它很多乱七八糟的协议。在各种语言上的实现也很多:C、C++、Lua、Java、Pascal、Perl、PHP、Python、Ruby、Visual Basic……。这里说最常用的 C 语言实现,环境是 Windows 7 + MinGW32。
下载 libcurl 源代码
libcurl 可以在官网 http://curl.haxx.se/ 获得。Download 页有源代码和为各平台预编译的二进制文件(curl 程序)和开发包(include + lib + doc)下载。不过我想要的 MinGW32 开发包的链接失效了 = = 就下载个源代码包自己编译吧。
在 Download 页的 Source Archives 栏里有最新的 curl 7.27.0 版本源代码压缩包,下载 curl-7.27.0.zip 文件。
编译 libcurl 库
- 解压 curl-7.27.0.zip 文件
- 通过命令提示符进入 curl-7.27.0 文件夹
- 输入
mingw32-make mingw32
进行生成(这里我只需要普通的功能,于是没有加附加的选项)
编译完成后,在 lib 文件夹中会有我们需要的三个文件。
libcurl.a
静态链接库
libcurldll.a
动态链接库的导入库
libcurl.dll
动态链接库
接下来,可以进入 docs/examples 文件夹,测试编译一些示例程序。可以直接用 Makefile.m32 文件,也可以手动一个个用 gcc 编译。
动态链接:
gcc -I../../include -L../../lib simple.c -lcurldll
静态链接:
gcc -I../../include -L../../lib simple.c -DCURL_STATICLIB -lcurl -lws2_32 -lwldap32
动态链接编译出的程序运行时依赖 libcurl.dll。静态链接参数中的 ws2_32
是 Windows Socket 2 库,wldap32
是微软的 Lightweight Directory Access Protocol API 库。
一般使用流程
基本的 curl 程序主要分四步:
curl_easy_init
创建 CURL 对象
curl_easy_setopt
设置操作选项
curl_easy_perform
进行操作
curl_easy_cleanup
销毁 CURL 对象
最简单的示例可以看 doc/examples 下的 simple.c 文件,演示如何将 curl 主页内容输出到 stdout。
- «
- 1
- ...
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- ...
- 15
- »