左图:没有经过编码;右图:经过LT码编码。
这篇README是继一篇发表在我的微信公众号上的文章写的,那篇文章介绍了喷泉码的背景,喷泉码的一个分支Raptor码被写入3GPP,Raptor码的一个实现方法RaptorQ也被高通公司写入RFC6330。
我的毕设实现的是最原始的喷泉码LT码,把树梅派摄像头采集的视频数据经过LT码编码后再发送。研究的结果嘛,呵呵,喷泉码这么用是不合适的。首先,主流的视频都是经过H.264压缩的,这种压缩方法的优势是压缩率高,缺点是帧之间的相关性强,关键帧的错误导致后续一连串的马赛克。喷泉码,尤其LT码的译码存在一定的译码失败概率,根据实现质量的高低这个概率很可能比信道的丢包率还要高。第二,喷泉码要求发送的数据包比原始的数据包稍微多一些,采用这种方案并不比采用TCP协议的重传产生的开销小。徒手实现LT码的编译码费了很大劲儿,编译码又带来时延,真不见得比用TCP协议实现得干净利索。第三,出于译码的需要,编码包需要携带它与原始包的邻接关系,由于视频是实时的,编码包需要携带一个ID标识它是用来译第几块视频的。这两点又带来了开销。第四,是UDP协议的问题。UDP不带反馈,它不像TCP协议或者管道那样带有同步的功能,发端不会因为收端忙得处理不过来就等它一下,白花花的编码包说丢就丢。我用的笨方法是一边两端都加上阻塞队列和专门的编译码线程,一边让发端发完一个包就usleep(100)微秒。毕设也给了我启示,如果有一个网络应用你想要用UDP做,那么十有八九你错了。
我的上一篇文章讲到这个项目最初是用C语言写的,用C语言的问题是这是一门面向过程的语言,写程序的过程中关注点一直是数组、指针、内存管理,造成进度一直比较慢,模块间耦合严重,难以调整编码器的参数,所以我用C++重写了。当时我预计需要设计度分布、编码器、补零数据包、编码包四个类,并且犹豫编解码算法是放到编码器的编解码函数里还是放到编码包、补零数据包的构造函数里。现在C++版本定型,就喷泉码模块而言,我设计了四个类:编码器、补零数据包、编码包、序列化器。对于度分布,那其实是一个离散型随机变量的累积分步,它是纯数据,用一个节点元素为pair的vector表示足以。编解码算法最后放到了编码器的编译码函数里,我觉得概念上这样更合理,而且算法需要获取编码器的配置信息。编码算法需要一个随机数和一个随机序列的发生器,问题抽象出来是:(1)如何产生服从某一分布的随机数,(2)如何从n个数中随机且均匀地选出m个数。这两个问题网上有很多答案,概率论的基础。
方才提到阻塞队列和专门的译码线程。毕设的仓库里有一个阻塞队列的简单实现——BlockQueue.h。原理其实非常简单,使用了最基础的同步原语互斥量和条件变量:生产者线程push(element)时锁住队列,push完成后通知消费者线程,然后解锁;消费者线程pop()element时锁住队列,等待push通知队列非空,然后pop,完成后通知生产者队列不满,解锁。阻塞队列就像shell中的管道,网络协议里的TCP,是居家旅行杀人越货的必备的小东东。
这套小程序原本想要放在无人机上试一下,但是昨天上午起飞前无人机侧翻,一个螺旋浆扎进了草里,电机烧了,怕是飞不成了。