网上有很多用各种显示屏、字符界面、甚至是激光、示波器等去播放动画的视频。而在视频的选择上,很多视频播放的是Bad Apple影绘。这个视频全程黑白,所以在各种设备上播放效果都特别良好,经常被用来做各种实验,所以也有句名言“有屏幕的地方就有Bad Apple”。
我买到树莓派之后一直在想树莓派+GPIO这个组合能做些什么有意思的事情。正好最近找到了一个一直都没有玩的3.3V小尺寸12864液晶模块,就决定用这个也放一放Bad Apple,见证一下树莓派远超过单片机的实力。
视频
视频地址:http://player.youku.com/player.php/sid/XNDU4MzQwMTUy/v.swf
概要
设备与材料:
- 树莓派
- MzL05-12864液晶模块 (控制器: ST7565)
- “Bad Apple影绘”等视频资源
功能:12864液晶模块的单色视频播放
开发周期:几个小时
程序发布程序的下载及用法,请见项目首页:
树莓派12864视频播放器 http://forum.eepw.com.cn/thread/258780/1
—————————————————————————————-
注:本日志为开发过程记录。对编程不感兴趣就请读到这里为止 :)
—————————————————————————————-
视频是以前从niconico上下载下来的原画质视频。(512×384, 3’39″=6566帧@30fps/容器mp4/视频AVC/音频AAC)
128×64单色无压缩视频的数据量是128*64*6566/8 = 6.41MiB,不大。所以为了最高效率起见,还是把视频的每一帧先处理成符合液晶屏幕的,可以直接写入液晶模块的格式。
格式是由液晶模块主控ST7565所规定的:
这个格式是纵向的,和更常见的大12864里用的ST7920(横向)不一样。个人感觉纵向的有点烦人。
处理步骤:视频 → 每一帧截图为BMP文件 → 目标文件。
中间需要在某个位置,将图片尺寸从512×384缩小为128×64。
我选择的是在“视频→BMP”这一步直接缩小。不过先转出BMP来,再把所有的BMP批处理了也可以。
一处理视频就比较麻烦,工具繁多、方法复杂。
将视频转换成图片序列(当然不能一帧一帧去截图了@_@),使用VirtualDub。但是VirtualDub不能读.mp4容器的文件,并且支持的解码器极其有限(AVC和H264都不行)……。
我是不希望用视频转换工具重新压一遍(损画质)。所以找了个解决办法:AviSynth。这个软件可以通过写脚本,将任何视频“伪装”成AVI文件,供其他软件无障碍读取。并且可以做一些简单的视频处理。
但是AviSynth也不能直接读.mp4 AVC的文件,还需要给它加插件FFmpegSource。
步骤:(1)安装AviSynth和FFmpegSource。
下载FFmpegSource时选择32位稳定发布版ffms-2.17.7z;
(2)写伪装用的.avs脚本,和badapple.mp4放在同一目录下:
A = FFAudioSource(“badapple.mp4″)
V = FFVideoSource(“badapple.mp4″).BicubicResize(128,64)
AudioDub(V, A)
# 注意:这个脚本里直接顺手把视频尺寸缩小到了128×64。
(3)开VirtualDub,把.avs脚本当做视频打开。然后File→Export→Image Sequence输出图像序列,输出格式选BMP。
由于这么麻烦,所以我把我转换出来的BMP文件序列,放出来提供下载 :) 不用管这么多步骤,用就好了:
http://dl.vmall.com/c00sg8rfgk
(Bad.Apple.开发大礼包.[逐帧BMP截图+原画质视频].z01
& Bad.Apple.开发大礼包.[逐帧BMP截图+原画质视频].zip)
注:网盘早晚是会失效的。如果不能下载,请留言告诉我。
BMP→液晶屏数据虽然点阵液晶取模软件不少,但想做批量的,现成的程序肯定没有,自己写是跑不了的。
程序流程:读BMP文件→判断每个像素(黑或白)→转换成液晶屏规定的数据字节→写二进制文件。
读BMP我拉了一个BMP识别库EasyBMP来用——BMP格式再简单也不值得做重复发明轮子的事情 :) 这个库可以方便的读取每个像素的RGB颜色值,功能够用了。
尽管BadApple是黑白视频,但灰度部分也是存在的——比如1’29″妹红的火焰,和1’54″幽灵乐团的影子。所以使用RGB→YUV公式中亮度分量Y的部分,先将每个像素点转换成灰度值:Y = 0.299R + 0.587G + 0.114B
我的判断方法,是简单的50%阈值:亮度过一半为白,不到一半为黑。保险,但不完美。至少因为图片太小,在0’55″就丢失掉了二小姐的“奸笑”这个细节。
总之写出的取模程序,最后输出output.bin即为动画数据。尺寸应该是一字节不差的:1024B/帧 × 6566帧 = 6566KiB。
使用GPIO/SPI驱动12864我手里这个12864是串行驱动的,时序符合SPI——尽管只能单向写入,MISO没有用到。
树莓派的GPIO里直接有SPI。树莓派需要设置的SPI参数检查一下:
- 字节顺序(MSB First)
- 时钟极性(CPOL=1, CPHA=1)
- 时钟分频(即SPI总线频率,64分频=4MHz。检查液晶的时序图,确保总线频率不超过液晶模块支持的最高频率)
- 片选信号(随意,我用的CS0)
- 片选极性(低有效)
操作树莓派的GPIO和SPI,我用了BCM2835函数库。用法很简单,看Example就够了。
12864的上电初始化时序,我偷懒参考了北京集粹的资料《SO12864-13C(COG)说明书》——这家公司的资料很准确。注:对于液晶模块,只要控制器一致,资料一般可以通用。
动画播放动画的流程:读取一帧的数据→写入液晶模块→等待1/30s→下一帧。
但是“等待1/30s”,肯定不是字面意思的“延时1/30s”。因为写SPI总线这个操作本身就使用时间,并且这个时间无法确定、不可忽略。忽略就会造成明显的时间不准。
其实这个“等待1/30s”,准确来说,应该表达为“等待下一个1/30s时刻的到来”。也就是说,在程序中要维持一个ms级的时标,每显示一帧就标注一下当前的时刻,然后等待到时标相比上一个标记点超过了33ms,就该显示下一帧了。
Linux环境下ms级的时标,这个问题正经找了一阵……。有不少人认为该使用gettimeofday()函数,但是用这个,在跨越两天的时间不就出问题了……
最后找到提供的ftime()函数可以解决。ftime()函数可以得到当前的毫秒级的UNIX时间戳。不过也不是没有隐患:程序运行中,不能更改系统时间。这个问题暂时放着了。
总结其实这个项目相比单片机播放视频,是相当轻松的:
- 性能够用,海量内存和存储随便使,无需自己操心SD卡操作、FAT32等等
- 开发资源丰富,甚至无需交叉编译和烧录下载
- SPI使用很方便。
果然用树莓派做点短开发周期的玩具再合适不过了。