这几天 Bad Apple 又逆袭了,比如这个和这个。虽然周末时候自己也做了一个,但介于身心憔悴和一个遗留问题,暂时先不和上次一样出视频了……
首先,看到那两个演示视频就可以联想到《编程之美》里面的一道面试题,但显然完全不是一回事,因为图像的 x → y
中 y
并不唯一。既然如此,那么就可以推定这是某种程度的「造假」,因为图像已经不是 CPU 占用率曲线了。
既然造假,我们就要造得有良心!那种类似直接在任务管理器上新建/覆盖一个视频窗口的做法略无节操了一些。作为一个死程,我们还是慢慢用程序解决吧……
视频预处理
这个就不用写程序了,看过我之前视频的童鞋一定知道我要用 ffmpeg 来把视频转成位图序列,没错,这次还是它!
而 ffmpeg 同时还提供了非常好用的滤镜支持,要把视频变成 CPU 占用率曲线的样子,我们需要边缘检测(edgedetect)和颜色通道混合器(colorchannelmixer)。
ffmpeg -i <文件名> -r <FPS> -vcodec bmp -vf edgedetect,colorchannelmixer=0:0:0:0:1:1:1:0:0:0:0:0 <输出文件名>
这样就能直接输出黑底绿线的位图序列,以备使用了。
屏蔽掉 CPU 占用率曲线的绘制
这里只考虑 Win 7 及之前的任务管理器,因为这货很好改。首先,这曲线一看就是 LineTo()
函数画出来的,那么我们用 OllyDbg 直接查看任务管理器中所有对于 LineTo()
的调用就可以找到对应代码。
我是 Win 7 的系统,用 OllyDbg 直接打开后,对 LineTo()
的调用只有:
- 绘制性能标签页中的背景网格
- 绘制 CPU 占用率曲线
- 绘制内存占用曲线
- 绘制联网标签页中的背景网格
- 绘制网络活动曲线
这几处,熟悉汇编的都可以很快定位整个语句的范围。至此,我们得到了一个地址及一个长度,把这个范围内的指令全部变成 Nop 指令即可。
当然 OllyDbg 中反汇编的地址是不能直接用的,还需要减去当前模块的基地址,得到偏移量以备使用……
那么来到 C++,FindWindow
→ GetWindowThreadProcessId
→ OpenProcess
即可获得任务管理器的进程句柄。
得到进程句柄后,我们首先要获得 taskmgr.exe 模块当前的基地址:EnumProcessModules
→ GetModuleInformation
。而后,就可以用上之前得到的偏移量和长度,使用 WriteProcessMemory
把绘制 CPU 曲线的代码覆写为一串 Nop 指令。
世界清静了。
绘制动画
这里就没有什么技术含量了。无非就是自己 SetTimer
开一个定时器,按照一定的帧率往窗口上 TransparentBlt
以前处理好的图片。
需要注意的是,背景网格,这货最麻烦了。我目前没有用 Hook,所以暂时的做法是:自己维护一份干净的背景网格。这样做的缺点是,网格更新时有一定几率察觉到曲线的缺失(用 Hook 后应该会好:Hook 后替换掉 Window Proc,然后正确的 WM_DRAWITEM
/WM_PAINT
之后立即把当前的图片绘制上去)。
以上。
字符画版本的 Bad Apple 的流行应该是好多年以前的事情了吧……虽然当时也想过自己做一个,但一直觉得图片转字符画是一个很神秘的过程(据说 mplayer 可以直接把视频按字符画输出)。前两天做完 ssoaag 发现,貌似可以用这个东西的原理输出字符动画,于是就有了下面这个视频 = =
【似乎是教程】如何制作字符动画
看到最后的效果你会发现最终产出的字符动画最右一列字符是略有问题的,在视频里也注明了,是那个 GetColor
函数里有个失误 = = 会导致某些情况下取到下一行的颜色,需要 continue
掉,并且把最后除以的 w * h
变成真正有效的像素数。
今天逛 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
)防止视频文件生成。
至于其它用法就留待以后要用上的时候再去研究了(懒……
以上。