Amy Blog

推流协议开发记

推流协议开发记

最近的工作,都与rtsp推流有关.随着对live555了解的深入,超发觉得有必要做一个更新的推流协议.

RTSP的不方便之处

也许是对live555研究不够深入,感觉live555有几个问题,一直有如下问题困扰着我.

代码繁杂

live555的实现已经经过多年迭代,里面的功能是非常完备的.

但是,正因为”多年”这一个词,表示这个项目是从很早就已经开始发展,当时的C++还停留在C++01, 或者C++98上,所以里面的实现,在现代的C++语法标准上,其实已经不用再自己实现,使用标准库即可.

这就造成了本来一二行代码就可以完成的事,变成了需要把标准库实现一次的问题.

以MediaSession这个类为例, 在mediasession这个类里,因为要管理subsession,在这个类里,增加二个指针,一个指向subsession链表头,一个指向subsession链表尾.

  // Linkage fields:
  MediaSubsession* fSubsessionsHead;
  MediaSubsession* fSubsessionsTail;

同时,为了做这个链表的遍历,需要另外写一个类.

class MediaSubsessionIterator {
public:
  MediaSubsessionIterator(MediaSession const& session);
  virtual ~MediaSubsessionIterator();

  MediaSubsession* next(); // NULL if none
  void reset();

private:
  MediaSession const& fOurSession;
  MediaSubsession* fNextPtr;
};

这些功能,在现代的C++里,都不需要自己实现.在STL里,有一个容器list即可完成.

std::list<mediasubsession> lst_subsession;

这个容器里,封装了链表的所有操作.操作不会出错,而且不用维护.对代码阅读者来说,可以极大减少心智负担.

task驱动问题

整个框架,不使用多线程技术, 使用大任务切分为多个小任务, 使用任务机按顺序处理小任务的方式.

当然,这是为了跨平台不得不做的选择,整份代码,可以无缝移植到多个嵌入式平台.

当年的嵌入式平台对多线程我支持不好.

C++高级用法没有稳定,很多嵌入式的编译系统,无法支持高级语法.

这样的选择是正确的. 但是放到现在,就不是一个好的方案了.

丢包花屏问题

使用rtsp时,有二种选择,一种是使用tcp传输,一种是使用udp传输.

使用tcp传输时,可以保证图像质量,但是延时会高;

使用udp传输时,延时会低,但是如果发生丢包,图像就会花屏.

实现选型

基于以上几个问题,我觉得很有必要开发一个自己的协议.

当然,对于小人物来说,不可能从零开始写一个协议,而是应该站在巨人的肩膀上,在现有的技术上,架设自己的协议.

对比一番后,终于选定了,基于quic协议,实现一套多媒体传输协议.

通道设计

quic在一个连接当中,可以创建多个通道,每个通道传输一条数据流,这样可以解决队头阻塞的问题.

比如,如果视频发送一帧大数量的图像帧时,发生丢包,再发生重传,就会导致同时到达的音频需要排队,这就是队头阻塞.

使用分通道设计后,二条流各不影响,分别统计数据丢帧.

在这里,把stream0 设计为控制原语通道;

stream5, stream7 作为视频,音频的通道.

后续就基于这几个通道分别设计对应的传输方式.

stream5, stream7 这几个是临时选择. 因为quic 支持从服务器创建的通道,和从客户端创建的通道. 有双向通道, 有单向通道. 后面再做细微的调整.

原语设计

协议的原语,也不用自创一套,就用rtsp里的原语: option –> descript –> setup –> play –> stop.

这些原语的交互,都放在stream0上传输.

与rtsp的对比

这个设计,与rtsp协议非常类似,所以实现起来并不复杂.因为是依葫芦画瓢.

stream0传的协议,即为控制协议,与rtsp对应.

而stream5/stream7, 与rtsp over udp里的rtp协议对应.

不过数据与rtp有一点差异,因为quic传输的数据是流式数据,因此可以把整帧数据传到quic协议,由quic协议分包.

而rtp协议,需要自己分包,每一个数据包前都需要增加媒体帧头.

如果后续要使用quic datagram方式传输,这个udp包头还是要设计的.但是对于新开发的协议,当然是能省则省了.

这也符合我的初始设计,实现一个简洁,现代的媒体传输协议.

做出来后呢?给谁用?

经过二个多月的奋战,协议终于设计出来了.

然后,我发现一个问题,这个协议给谁用? 因为传输完数据,还需要有一个播放器端,才能让这个协议工作起来.

对于一个名不见经传的小公司来说,有谁来响应你,用你的产品,这是一个大问题.

「一流企业做标准,二流企业做品牌,三流企业做产品」.

我现在是深刻理解到这句话的正确性了.

另外一个问题,你怎么确保这个协议的工作是正确的?或者说,怎么证明这个协议的正确性,稳定性.

以前我总是不太喜欢写测试用例, 因为写用例,总是没有实现功能更让人有快感(看到一个项目,按照自己的设想呈现,是一件很快乐的事).

听说sqlite,测试用例的代码量比整个程序的实现都要多几倍.现在我觉得,是不是这种开发方式才是正确的?