视频编码原理简介

要彻底理解视频编码原理,看书都是虚的,需要实际动手,实现一个简单的视频编码器:

知识准备:基本图像处理知识,信号的时域和频域问题,熟练掌握傅立叶正反变换,一维、二维傅立叶变换,以及其变种,dct 变换,快速 dct 变换。

来自知乎问题:http://www.zhihu.com/question/22567173/answer/73610451

第一步:实现有损图像压缩和解压

参考 JPEG原理,将 RGB->YUV,然后 Y/U/V 看成三张不同的图片,将其中一张图片分为 8×8 的 block 进行 dct 变换(可以直接进行二维 dct 变换,或者按一定顺序将 8×8 的二维数组整理成一个 64 字节的一维数组),还是得到一个 8×8 的整数频率数据。于是表示图像大轮廓的低频信号(人眼敏感的信号)集中在 8×8 的左上角;表示图像细节的高频信号集中在右下角。

接着将其量化,所谓量化,就是信号采样的步长,8×8 的整数频率数据块,每个数据都要除以对应位置的步长,左上角相对重要的低频信号步长是 1,也就是说 0-255,是多少就是多少。而右下角是不太重要的高频信号,比如步长取 10,那么这些位置的数据都要 /10,实际解码的时候再将他们 x10 恢复出来,这样经过编码的时候 /10 和解码的时候 x10,那么步长为 10 的信号 1, 13, 25, 37 就会变成规矩的:0, 10, 20, 30, 对小于步长 10 的部分我们直接丢弃了,因为高频不太重要。

经过量化以后,8×8 的数据块左上角的数据由于步长小,都是比较离散的,而靠近右下角的高频数据,都比较统一,或者是一串 0,因此图像大量的细节被我们丢弃了,这时候,我们用无损压缩方式,比如 lzma2 算法(jpeg 是 rle + huffman)将这 64 个 byte 压缩起来,由于后面高频数据步长大,做了除法以后,这些值都比较小,而且比较靠近,甚至右下部分都是一串 0,十分便于压缩。

JPEG 图像有个问题就是低码率时 block 边界比较严重,现代图片压缩技术往往要配合一些 de-block 算法,比如最简单的就是边界部分几个像素点和周围插值模糊一下。

做到这里我们实现了一个同 jpeg 类似的静态图片有损压缩算法。在视频里面用来保存I帧数据。

第二步:实现宏块误差计算

视频由连续的若干图像帧组成,分为 I 帧,P 帧,所谓 I 帧,就是不依赖就可以独立解码的视频图像帧,而 P 帧则需要依赖前面已解码的视频帧,配合一定数据才能生成出来。所以视频中 I 帧往往都比较大,而 P 帧比较小,如果播放器一开始收到了 P 帧那么是无法播放的,只有收到下一个I帧才能开始播放。I 帧多了视频就变大,I 帧少了,数据量是小了,但视频受到丢包或者数据错误的影响却又会更严重。

Continue reading

Loading

Posted in 图形编程, 编程技术 | Tagged | 3 Comments

多平台下录屏方式

随便记录下,想得起来的多少写多少:

Windows:
GDI 全屏:fullscreen desktop BitBlt 速度是 20ms / 帧
GDI 窗口:Win7+可以录制游戏和非游戏,XP以前只能录制普通界面,截不到游戏窗口
DirectDraw 全屏:full screen primary surface 异步到 offscreen-surface XP下闪电速度 1ms / 帧
D3D9: render target -> offscreen surface XP下 16ms Win7 下 10ms
D3D Hook: Hook Device::Present -> 保存 StateBlock -> 截屏 -> 恢复 StateBlock 极速!

Linux:
framebuffer: 打开 /dev/fb0 映射到内存,直接读里面内容,部分显卡驱动启用硬件加速绘图后,framebuffer会被跳过,直接调用 framebuffer 导致读取出来的东西为空。

Android:
framebuffer: 需要 root 权限才能访问 framebuffer
RootView: 取得 root view 然后得到 drawing cache 然后保存,兼容性好,慢。
OpenGL:直接 glReadPixel 保险,但是会卡到应用。
OpenGL FBO: 更改渲染的 FBO ,让渲染到纹理,从纹理取下来,还行,速度还好。
screenshot: SurfaceComposerClient::getBuiltInDisplay Android 4.0 以后,性能不错。
native buffer: ANativeWindowBuffer -> GraphicBuffer -> 映射读取, 性能不错

iOS:
OpenGL: 直接 glReadPixel 同 Android,同样会卡到应用
OpenGL FBO: 渲染到 FBO,读取下来,还行。

大部分方法可以用 Hook 的方式注入到目标程序,或者系统 API,在不需要目标程序改一行代码的情况下实现功能。

Loading

Posted in 图形编程 | 1 Comment

做编译器或操作系统哪个更有趣味?

同范畴类似的东西中,虚拟机开发比操作系统和编译器都有意思:

操作系统能玩的好玩的不多,做完进程管理和内存管理,其他就是脏活累活了,要得到一个成型可用的东西,没有一个团队弄不了,个人玩不转。以前调侃过这个话题:

关于LMOS自主操作系统的发展,大家有什么建议? – 韦易笑的回答

而编译器方面,优化和代码生成有llvm,用不着自己开发,其他部分基本上是一个固定套路,照着文档照着书,按固定套路来即可。

以 Lua为例,最精巧的部分就是 Lua虚拟机的实现,整个 Lua-5.3 代码中,编译部分只占2000行,剩下两万行全在实现虚拟机。大家津津乐道的各种 lua 奇技淫巧全都在它的虚拟机实现部分中。

Ruby更是如此,整个ruby代码除去库实现外,基本在实现ruby的虚拟机,编译器部分作者都懒得怎么写,直接一个的 .y文件搞定,精力都在ruby虚拟机实现上。

虚拟机难是难在开头的设计,怎样最精巧,怎样没有逻辑漏洞,别写着写着发现前后逻辑矛盾了,设计好以后就要开始架构了,先从最简单的 switch case opcode实现起来,很快你就能得到反馈看到自己的工作成果,接下来给虚拟机实现符号表,object,实现 gc,实现各种容器,优化性能,在内存中翻译成本地指令码,然后实现多线程,再给你的虚拟机实现一门汇编语言,每走一步都很有意思。且虚拟机相关的技术目前还在日新月异的发展着,光一个gc,每年都能看到很多新方法出现,大有需要继续改进迭代的地方。

实现虚拟机需要覆盖很多知识面,虚拟机设计的好,还可以嫁接很多其他语言,让这些新语言来这个虚拟机上运行。

大家夸 v8 都是说它虚拟机运行速度快,内存少,没人说 v8 的编译部分如何如何,以至于很多新语言都以v8虚拟机为运行环境。

同样,每年都有很多人尝试重新实现 lua vm,引入更灵巧的机制或者使用更新的 JIT方法。来pk官方runtime, 以及 luajit,好多人或多或少在某些方面都能并发出很多奇思妙想。

相反,从来没人碰 lua 的编译部分,为啥啊?就那样了,还碰它干嘛,没意思了啊。

Loading

Posted in 编程技术 | Tagged , | Leave a comment

程序员的工作是否就是复制粘贴?

就是复制粘贴,所谓复用,90%的时候就是打破重粘贴。所谓架构,就是知道去抄啥;所谓开发,就是老大说抄我就抄;所谓初级开发工程师,就是东看看西看看,瞧瞧别人怎么抄,自己跟着抄;所谓高级开发工程师,就是同一个模块抄过一遍的人,第二遍更熟点。

座右铭1:Github搬运工
座右铭2:有现成的用现成的,没现成的找现成的,找不到就不做了,对外宣称无法实现。
座右铭3:我们不是在抄,我们做的叫 “系统集成”

很多人做了几年觉得没有创造没有提高就转行了,所以每年才会招很多应届生补充进来,来填补空缺,比如即将毕业你们。

所以,应届生,不要阳春白雪,养着宠物喝着咖啡,敲几千行改变世界的代码,从此华丽转身。代码还是几千行,只是每天几千行,为啥那么快?抄呗!

所谓搬砖几乎不需要封装,因为大部分只用一次,今天把砖头从东搬到西明天又从西搬到东搬回来,后天又回去,既然如此任何封装都是容易过度,不比老实搬砖复制来的直接高效。就像正则表达式,每次都重写,写熟了后也并不需要想着封装复用下老的,每次重写为主,复制改写为辅。搬砖搬多了也和写正则一样,每次重写即可,简单高效,无需封装。
记住,砖是搬不完的,你搬累了,又有人来接替你搬了。不要天真谈创造,现在不是单干的时代,远离个人英雄主义,你的所有成绩来自集体,个人离开集体啥都不是,没有谁是不可替代的,少了谁地球一样转。

戒骄戒躁,努力做好社会主义的螺丝钉,凡事就怕认真二字。简单的事情重复做,你就是专家;重复的事情用心做,你就是行家!再小的地方,他也能干出成绩!

Loading

Posted in 随笔 | Tagged | Leave a comment

Android Arm 编译优化选项评测

用不同测试用例具体测试 softfp, armv7-a, cortax 等优化选项,看选项不同性能差别多大。首先设计下面几个测试用例,包含字符串处理、复杂逻辑、整数运算、浮点运算几个方面:

  • compress:进行 LZO/LZW 大规模压缩,测试搜索,字符串匹配,复杂分支等性能
  • resample:进行一系列整数 DSP 运算,包括 resample 和 fir low pass
  • int add:一亿次整数加法
  • int mul:一亿次整数乘法
  • int div:一亿次整数除法
  • float add:一亿次浮点加法
  • float mul:一亿次浮点乘法
  • float div:一亿次浮点除法
  • const div:一亿次整数除以常数255
  • matrix:若干次矩阵乘法运算,同时考察浮点数乘法加法
  • normalize:若干次矢量归一化运算,同时考察浮点数乘法,除法,加法,sqrt

其次对安卓的几个 gcc 的编译选项进行分别测试:

  • -mfloat-abi=softfp,如果有硬件浮点处理器将会使用硬件,如果没有会转移到软件模拟
  • -march=armv7-a,生成适合 armv7a 架构的代码
  • -mtune=cortex-9,代码生成按照 cortex-9 进行调优
  • –mfpu=neon,使用 neon 进行硬件浮点运算,决定 softfp 的硬件方式到底用这个
  • -mfpu=vfp,使用 vfp 进行硬件浮点运算,决定 softfp 的硬件方式用这个

测试硬件:

  • 桌面电脑:Intel® Core™ i5-2520M CPU @ 2.50GHz
  • 安卓手机:三星,双核 CPU 1.73GHz armv7-a cortex-9

结果如下:

Continue reading

Loading

Posted in 编程技术 | Tagged | 3 Comments

Android 命令行调试 C/C++ 程序

传统方式调试 NDK 开发的程序比较麻烦,先要编译成 JNI,又要导出 java接口,还要再写一个 java 工程,改一个地方又要连续改几处,这样效率是很低的。最频繁使用的关键工作路径(编译/调试环节)如果能极致简化,那么可以带来开发效率的成倍提升。其实安卓官方是提供了命令行调试方法的,将你需要调试的 C代码用 NDK直接编译成可执行,然后到设备上执行:

使用 NDK 导出独立工具链,方便以后使用,在 cygwin 下面,将 $NDK 环境变量代表的路径设置好,然后:

cd $NDK
chmod -R 755 *
build/tools/make-standalone-toolchain.sh –ndk-dir=$NDK –platform=android-9 –arch=arm –install-dir=/…../path-to-android-9

这样就导出了一套针对 API9 的独立工具链(包含 gcc, ld, ndk必要文件),以后方便使用,比如导出到 d:\android-9下面,那么以后可以跳过 cygwin,直接编译我们的 Hello World:

d:\android-9\bin\arm-linux-androideabi-gcc.exe hello.c –o hello

于是你可以在命令行下直接开发 Android 的非 GUI 应用程序了。

调试也很简单,用 adb push 上传到 /data/local/tmp 下面,并且设置可执行模式为 755:

adb push hello /data/local/tmp/hello
adb shell chmod 755 /data/local/tmp/hello

运行就是直接:

adb shell /data/local/tmp/hello

不要传到其他目录,比如 /sdcard,这些目录 mount时有 NOEXEC 权限,不能给文件增加可执行权限,而 /data/local/tmp 就是留给大家调试命令行用的,并且不需要 root 权限。

可以编写一些脚本,每次编译好自动上传,配置到你的 Editplus/Vim/Npp 中,一键编译上传,一键运行。比起以前调试下 C代码还需要写一大堆 jni 和 java 的方式,效率高极了。

Loading

Posted in 编程技术 | Tagged | Leave a comment

如何写一个软件渲染器?

实现个简单的固定渲染管线软渲染器不算复杂,差不多700行代码就可以搞定了。之所以很多人用 D3D用的很熟,写软渲染却坑坑洼洼,主要是现在大部分讲图形的书,讲到透视投影时就是分析一下透视变换矩阵如何生成,顶点如何计算就跳到其他讲模型或者光照的部分了。

因为今天基本上是直接用 D3D 或者 OGL,真正光栅化的部分不了解也不影响使用,所以大部分教材都直接跳过了一大段,摄像机坐标系如何转换?三角形如何生成?CVV边缘如何检测?四维坐标如何裁剪?边缘及步长如何计算?扫描线该如何绘制?透视纹理映射具体代码该怎么写?framebuffer zbuffer 到底该怎么用?z-test 到底是该 test z 还是 w 还是 1/z 还是 1/w ?这些都没讲。

早年培训学生时候,我花两天时间写的一个 DEMO,今天拿出来重新调整注释一下,性能和功能当然比不过高大上的软件渲染器。但一般来讲,工程类项目代码不容易阅读,太多边界情况和太多细节优化容易让初学者迷失,这个 mini3d 的项目不做任何优化,主要目的就是为了突出主干:

源代码:skywind3000/mini3d · GitHub
可执行:https://github.com/skywind3000/mini3d/releases

操作方式:左右键旋转,前后键前进后退,空格键切换模式,ESC 退出。

Continue reading

Loading

Posted in 图形编程, 编程技术 | Tagged | 16 Comments

为什么技术总是被轻视?国内做技术能不能有较大发展?

陆贾对陈平说过:“天下安,注意相;天下危,注意将!”,天下太平的时候,文官得到重视;天下大乱的时候武官得到重视。大到一个行业的兴起,小到一家公司的创业期,或者一款产品的开发期,打江山的时候自然武将重要,守江山就是另外一回事了。

曾经建议大家远离 “没有技术基因” 的公司:

从大公司离职去小公司当 CTO 是一种怎样的体验?

从小处讲,这是权力斗争,即便武将出生的宋太祖都要杯酒释兵权,各大公司上市后 CTO 出局本来就是屡见不鲜的事情。即便一个产品组,东西出来前技术比较重要,所有人求着你,东西出来后,技术比较尴尬,所有人来怪你(怎么又出问题了?怎么那么慢?怎么要那么多人?)。

从大处说,这是行业规律,任何一个行业都是按照:技术主导->产品主导->销售主导 的模式进行发展。早期的技术壁垒逐步打破后,会变成产品为重,大家产品都设计的差不多的时候又会变成销售为重。但是这是一个循环,全天下都在拼渠道和折扣的时候,偶尔一两个革命性的技术或者产品创新,就又能将行业拉到循环的起点。

参考:

这个社会中,有真才实学技术的人,是否总体不如“非常会来事”的万金油混得好? – 陈鑫的回答

可口可乐和百事可乐已经走到销售了,服装行业走到产品和销售之间。手机在功能机时代基本就是拼销售和外观,基础功能大家都基本OK,界面也差不多,但 iPhone 出来后,重新将行业拉回到重视产品的阶段。

游戏行业也是一样,早年业内出名的都是程序牛人,近十年内,游戏业听不到任何人以技术闻名了,相反涌现出一大堆策划制作人,说明游戏行业已经脱离了技术为重的阶段进入了产品为重的阶段了。天下游戏一大抄以后又变成渠道和发行的天下,而就在渠道血拼首充号和分成比例的时候今年似乎又被重度游戏拉回产品为重这个阶段。保不齐哪天 VR /AR 技术再上一个台阶后,又重新将整个行业拉回起点了。

然而所谓的循环也只是短暂的,对于大部分成熟行业,不管怎么循环,三个阶段的稳定态,差不多都是以销售为主以少量产品创新为辅的稳定态,偶尔一两个创新可以在短期内将行业回归到 “技术为重” 的阶段,然而其后将继续回归所谓的稳定态。

建议的话,除去回到学校或者科研机构做研究外,大概有三个解法:

  1. 远离没有技术基因的公司,或没技术基因的创业团队。

  2. 寻找新的行业,对技术比较倚重的领域,或者从事专门提供技术服务的工作。

  3. 自己成长并学习更多领域的内容,出将入相。

Loading

Posted in 大浪淘沙 | Tagged | Leave a comment