关于现有的宽带网络上的视频传输
作者:bomb(木头青蛙)
若发现本文的不足之处欢迎发EMAIL到bomb_hero@163.com指正。
==================================================================================
一、视频流的结构
MPEG2中视频流中视频数据分帧传输,帧分三种类型I,P,B。
I帧:描述当前图像的所有信息,可以单独解码。但由于信息比较多,所以比较大。
P帧:描述当前图像和上一帧图像不同的地方或某块区域移动信息,需要对照上帧解码后的图像进行解码。这样就减小了该帧的数据量。一般大小为I帧的1/5~1/10。当然也有例外,比如当镜头突然切换的时候,由于当前图像信息和上一帧图像信息几乎没有相同的地方,所以,P帧也会很大。
B帧:描述当前图像和上一帧以及下一帧图像不同的地方或某些区域的移动信息,这样当镜头切换的时候,可以参考下一帧的信息进行编码,从而减小B帧的大小。
MPEG4的Sample Profile中沿用了这种编码策略。目前普通的网络视频流编码器编码出来的视频流就是I帧+P帧的结构。具体的压缩算法可以写一本书不是本文讨论的范围。
现在来看一下视频流结构
IPPPPPPPPPPPPPPPIPPPPPPPPPPPPPPPIPPPPPPPPPPPPPPP............
这是16帧一个I帧的视频流结构。如果在传输过程中,丢失一个P帧,那么在下一个I帧来之前,所有的P帧解码将出现错误,这就是马赛克的来由。
二、传输方式
TCP的传输在服务端和客户端都有缓冲和校验机制,而且传输过程中服务端不需要关心客户端是否经过网关。连接建立后直接传输就可以了。传输过程使用流方式传输,一般不会出现后发送的包先到达的情况。客户端只要按流的方式接收,然后将每一帧区分开进行解码就可以了。但由于TCP有校验重传机制,当一个数据包在传输出错后,将请求重新传输该数据包。从而造成时延,短时间内不会有什么大问题。但时间久了将出现视频严重的滞后现象,而且如果客户端解码速度不够将出现缓冲区溢出的现象。
UDP是使用数据包的方式传输,而且没有内置的重发机制,在数据丢失后不会请求重新传输。而且UDP在数据包大于2KB的时候,会将数据包分割成2KB进行发送。也就是说,服务端发送一个大于2kb的数据包,客户端将收到数个小于等于2kb的数据包,而且前后顺序很可能颠倒。基于上述原因,在使用UDP包传输视频数据时要将每一帧数据分割成小于2kb的数据包然后加上头信息再发送出去。头信息一般包括
帧号:用于说明那些包是属于同一帧。
包号:用于同一帧的的数据组合排序。
总包号:用于说明本帧被分为了多少个包。
本包大小:记录本包的大小。
关于UDP丢包的问题和客户端解码速度问题。一般当UDP丢包时,就舍弃该帧,然后继续解码使屏幕上出现马赛克,或者舍弃该帧及以后的P帧,直到收到一个I帧再开始解码,这样屏幕上将出现画面突变现象。不重传的原因是因为如果要重传将造成画面暂时停顿。如果用在实时视频传输的应用,重传的意义也不大。
现在还有使用RTCP传输的方式,我没有这方面的研究,不知道那位网友能给这部分打个patch。
三、UDP方式客户端如何通过网关得到视频数据
当客户端在网关内,而服务端在网关外。服务端是无法主动用UDP向客户端传送视频数据的。为解决这个问题,我们必须得在客户端和服务端的握手过程中寻找解决方案。
服务端有公开的TCP监听端口和公开的UDP监听端口。当客户端有数据请求,使用TCP方式连接服务端,然后服务端给客户端分配一个唯一的序列号,序列号用于身份识别。客户端收到这个序列号后,新开一个socket,用UDP方式将此序列号发送给服务端的UDP监听端口,然后用这个socket进行UDP接收。服务端的UDP监听端口在收到UDP包的序列号后,首先进行身份识别,用于判断是哪个TCP连接的客户端发送。并从给UDP包中得到客户端的IP和端口号,这个端口号就是客户端在网关上用的端口号,而IP则是网关的对外IP。向这个IP和端口使用UDP发送视频数据,客户端就可以收到。
四、流量问题
我们讨论的流量问题是服务端是LINUX系统,客户端是Windows系统。
我们来看看视频数据流的格式。
IPPPPPPPPPPPPPPPIPPPPPPPPPPPPPPPIPPPPPPPPPPPPPPP............
我们知道,视频数据是25帧/秒。匀速发送,则每帧间隔是40ms。I帧的数据量是P帧的5~10倍,这样在发送I帧的40ms中,网络的瞬间流量将是平时的5~10倍。在客户端使用ADSL的上网方式下,可能会造成断线的情况。在局域网中这样容易造成客户端丢包,致使客户端无法得到完整的I帧。(但如果客户端使用LINUX系统,在局域网中将不会出现丢包的情况。)
在LINUX系统中如果需要在应用层中平滑流量,只能使用发送一定的数据包后调用usleep(1)。又由于LINUX的时间片是10ms的大小,所以调用usleep(1)其实使进程睡眠了10ms。由此,只要在发送一定数据包后插入usleep系统调用即刻改善I帧的流量冲击问题。
bomb
2004-04-10