基于Linux的视频传输系统(上大学时參加的一个大赛的论文)

文件夹

1原创性声明----------------------------------------------------3

2 摘要----------------------------------------------------------4

3系统方案------------------------------------------------------4

3.1功能与指标----------------------------------------------4

3.2方案选择与论证------------------------------------------4

系统组成框图--------------------------------------------4

硬件平台介绍------------------------------------------------------------------4

视频採集方案的选择---------------------------------------------------------4

视频编码方案的选择---------------------------------------------------------5

视频传输方案的选择---------------------------------------------------------6

显示方案选择------------------------------------------------------------------6

3.4系统软件实现--------------------------------------------6

3.4.1server--------------------------------------------6

(1)视频採集模块-------------------------------------6

(2)视频压缩模块------------------------------------10

(3)网络传输发送模块---------------------------------13

3.4.2client--------------------------------------------19

(1)网络传输接收模块--------------------------------19

(2)视频解码模块------------------------------------19

(3)视频显示模块------------------------------------22

四 系统測试----------------------------------------------------25

附录:源码

參考书目

 

 

 

 

 

 

 

 

 

2006年英特尔杯大学生电子设计竞赛嵌入式系统专题邀请赛

 

參赛作品原创性声明

 

本人郑重声明:所呈交的參赛作品报告,是本人和队友独立进行研究工作所取得的成果。除文中已经注明引用的内容外,本论文不包括不论什么其它个人或集体已经发表或撰写过的作品成果,不侵犯不论什么第三方的知识产权或其它权利。本人全然意识到本声明的法律结果由本人承担。

參赛队员签名:

日期:     年   月   日

 

2 摘要

本系统在LINUX平台下实现了视频的採集、压缩、传输及组播,图象清晰,实时性较好。本设计採用USB摄像头结合LINUX下自带的驱动模块VIDEO4LINUX实现视频採集。在XVID视频编解码平台下实现视频的压缩和解压。视频传输採用专门为流媒体传输设计的RTP协议,达到了较高的实时性。

ABSTRACT 

On the basis of Linux platform, this system realizes the videodata‘s collection, compression and network transmission. The videodata‘s collection is realized through USB camera and Video4Linux. The videodata‘s coding and decoding is realized under the Xvid platform. And the network transmission is realized by Rtp protocal which is designed for streammedia. All of these make hign real-time performance.

关键词:视频  RTP   XVID   SDL

3 系统方案

3.1实现功能与指标

本系统可用于足球赛场向场内或场外观众提供更逼真的更精彩的比赛画面,使场内观众能够零距离的观看射门等精彩画面。用户可用笔记本电脑由局域网连接server,执行client软件就可以赞赏近距离的比赛画面。server由USB摄像头採集数字视频信息,经过MPEG4视频编码,然后通过JRTP网络传输协议向连接到server的client传输视频信息,实现视频的实时组播。採集到的YUV图像大约为100KB压缩后每帧图像大小平均为5KB,在局域网环境下延迟小于0.5秒,视频清晰无失真。鉴于server的主频限制,组播最大连接数为5,可同一时候向5个用户提供视频信息。

3.2方案选择与论证

系统组成框图:




USB摄像头





基于GENE8310的server

10





网络





远程登录主机

硬件平台介绍

GENE-8310是AAEON提供的第三代无风扇解决方式,在低功耗情况下能够获取更高的性能表现,主要表如今:卓越性能与可控的功耗,多种显示模式,可扩展性,GENE8310主频为500M 能够做视频採集处理与传输的server。

视频採集方案的选择:

LINUX有自带的摄像头驱动模块Video4Linux. Video4Linux为针对视频设备的应用程序编程提供一系列的接口函数,对于USB摄像头,其驱动程序中须要提供主要的I/O操作接口函数 如open close的实现 以及内存影射功能和对I/O操作的控制接口函数ioctl等。LINUX下视频採集例如以下所看到的




视频应用程序





Video4Linux





设备驱动程序





视频採集设备

视频编解码方案的选择

Xvid作为第二代MPEG-4编码具有多方面的长处,XVID是DIVX开发小组因不满DIVX被封闭而在其基础上开发的源代码开放的视频编码解码平台。对于第二代的MPEG4视频编码内核来说。XVID的各种特点都有代表性和先进意义。1.它支持多种编码模式:除了最原始的单重估定码流压缩(1-pass CBR)之外,XVID提供了包含:单重质量模式动态码流压缩单重量化(Quantization)模式动态码流压缩、和包含外部控制和内部控制的两种双重(2-pass)动态码流压缩模式。2.在量化方式上Xvid不仅提供了标准的MPEG量化方式,还特地提供了更适合低码流压缩的.h 263量化方式。3.除了量化方式迭择,Xvid还提供了强大的对压缩过程中的量化幅度的范围控制。用户能够选定压缩时同意使用的量化幅度范围。比如设定一个量化的上限,就能够避免可能出现的画质大幅下降的情况。4.在运动侦測(Motion Search)和曲线平衡分配(Curve)方面,XVID对画面帧进行运动侦測以及对全片段的运动侦測结果进行分析后,又一次以曲线平衡分配每一帧的量化幅度,以做到:须要高码流的运动画面能够分配很多其它空间、更高的码流、更低的量化幅度来保持画面的细节;而对于不包含太多运动信息的静态画面,则消减分配预算。这样的把好钢用在刀刃上的做法,是Xvid作为第二代MPEG-4编码的核心内容。5.Xvid提供了多极运动侦測精度,包含半像素插值的技术以16x16像素的微区块为单元标示上运动矢量:以及4分运动矢量(inter4v motionvectors)的方式,以8x8的像素区块为单元更仔细的纪录运动向量以供二重分析。6.动态关键帧距是还有一个Xvid所具有的,在空间和画面之间获得最大平衡的技术。我们知道在视频压缩中不是每一帧都记录着所有的画面信息,其实唯独关键帧记录着完整的画面信息,而兴许的P帧(P-Frame)不过纪录下与之前一帧的差值。假设关键帧之间的画面变化非常大,则会浪费宝贵的空间在P-Frame上;而增加把变化非常大的那一帧记录在关键帧里,那么因为兴许的帧不再有更大的变化,就能够节省P帧所需的空间。因此,依据画面镜头切换和运动幅度来变换关键帧的位置,对于视频压缩下的画面质量提高,就有着事半功倍的效果。

鉴于XVID以上种种长处,我们採用XVID实现视频的编解码。

视频传输方案的选择

视频传输能够选择TCP与UDP,TCP是一个面向连接协议,传输信息前须要建立连接,系统资源开销大,但可靠性较高。UDP是一个无连接协议,数据传输之前源端和终端不须要建立连接,资源开销小,实时性较高。

实时传输协议(Real-time Transport Protocol,RTP)是在Internet上处理多媒体数据流的一种网络协议,利用它可以在一对一(Unicast,单播)或者一对多(Multicast,多播)的网络环境中实现传流媒体数据的实时传输。RTP通常使用UDP来进行多媒体数据的传输,具有UDP传输的长处。

鉴于可靠性考虑,在本系统中,信息传输之前server和client用TCP建立连接。然后server通过RTP向client发送视频信息,这样就达到了可靠性和实时性的平衡。

显示方案选择

SDL(Simple DirectMedia Layer)是一个跨平台的多媒体游戏支持库。当中包括了对图形、声音、线程等等的支持,眼下能够执行在很多平台上,当中包括 X Window、X Window with DGA、Linux FrameBuffer 控制台等等。

由于 SDL 专门为游戏和多媒体应用而设计开发,所以它对图形的支持很优秀,尤其是高级图形能力,比方 Alpha 混和、透明处理、YUV 覆盖、Gamma 校正等等。并且在 SDL 环境中可以很方便地载入支持 OpenGL 的 Mesa 库,从而提供对二维和三维图形的支持。

本系统client接受到的视频解压后为YUV格式,考虑到SDL在YUV覆盖方面的优势,我们选择SDL实现视频信息接收接压后的显示。

3.3系统软件实现

3.3.1server

server实现了採集数据然后压缩后进行实时传输,用了三个线程分别实现了视频的採集压缩(线程1),通过TCP协议建立连接(线程2),压缩后视频流的传输(线程3)。server应用程序执行后,server即创建线程1进行视频採集,线程2处于堵塞状态。一旦有client建立连接,则线程2获得clientIP信息。以此IP信息为參数建立线程3,线程3通过JRTP协议向client传递视频流。此后client继续处于堵塞状态,直到有新的client连接。server端的重要的模块包含视频採集模块,视频压缩模块,和网络传输发送模块。

(1)视频採集模块

Linux内核公开支持的OV511等摄像头芯片,但因为较陈旧在市面不easy找到。我们选用LOGITECH的QUICKCAM  COOL摄像头并从网上下载摄像头驱动程序qc-usb-0.6.3.tar.gz然后进行解压、编译、安装。

假定已经搭建好嵌入式Linux的开发环境,以下第一步工作就是USB摄像头的安装与驱动。

确定USB摄像头被正常驱动后,下一步就是使用Video4Linux提供的API函数集来编写视频採集程序。

在Linux下,全部外设都被看成是一种特殊的文件,称为设备文件。系统调用是内核和应用程序之间的接口,而设备驱动程序则是内核和外设之间的接口。他完毕设备的初始化和释放、对设备文件的各种操作和中断处理等功能,为应用程序屏蔽了外设硬件的细节,使得应用程序能够像普通文件一样对外设进行操作。

Linux系统中的视频子系统Video4Linux为视频应用程序提供了一套统一的API,视频应用程序通过标准的系统调用就可以操作各种不同的视频捕获设备。Video4Linux向虚拟文件系统注冊视频设备文件,应用程序通过操作视频设备文件实现对视频设备的訪问。

Linux下视频採集流程如图1所看到的




开启视频设备()





获取设备信息及图像信息()





初始化窗,颜色模式,帧状态()





捕捉视频帧数据()





关闭视频设备()





送压缩模块





是否中止採集





终止





開始





N





Y

图2  LINUX下视频採集流程图

Video4Linux视频设备数据结构的定义

struct vdIn {

int fd;                 //文件描写叙述符

char *videodevice ;     //视频捕捉接口文件

struct video_mmap vmmap;

struct video_capability videocap;// 包括设备的基本信息(设备名称、支持的最大最小分辨率、信号源信息等)

int mmapsize;

struct video_mbuf videombuf;映射的帧信息,实际是映射到摄像头存储缓冲区的帧信息,包含帧的大小(size),最多支持的帧数(frames)每帧相对基址的偏移(offset)

struct video_picture videopict;//採集图像的各种属性

struct video_window videowin;

struct video_channel videochan;

int cameratype ; //能否capture,彩色还是黑白,是否  能裁剪等等。                                       值如VID_TYPE_CAPTURE等

char *cameraname; //设备名称

char bridge[9];

int palette; // available palette

int channel ; //信号源个数

int grabMethod ;

unsigned char *pFramebuffer;//指向内存映射的指针

unsigned char *ptframe[4];//指向压缩后的帧的指针数组

int framelock[4];//

pthread_mutex_t grabmutex;// 视频採集线程和传输线程的相互排斥信号

int framesizeIn ;// 视频帧的大小

volatile int frame_cour;// 指向压缩后的帧的指针数组下标

int bppIn;// 採集的视频帧的BPP

int  hdrwidth;// 採集的视频帧的宽度

int  hdrheight;// 採集的视频帧的高度

int  formatIn;//採集的视频帧的格式

int signalquit;//停止视频採集的信号

};

在视频採集之前,先要对Video4Linux进行初始化

初始化阶段用ioctl(int fd, ind cmd, …) 函数和设备进行“对话”。Fd是设备的文件描写叙述符,cmd是用户程序对设备的控制命令 ,省略号通常是一个表示类型长度的參数,也能够没有。初始化过程例如以下:

1.打开视频:

open (vd->videodevice, O_RDWR))

2. 读video_capability 中信息包含设备名称,支持最大最小分辨率,信号源信息等。调用函数ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap))成功后可读取vd->capability各分量

3.对採集图象的各种属性进行设置,分为两步 首先获取摄象头缓冲区中video_picture中信息调用函数ioctl(vd->fd, VIDIOCGPICT, &(vd->picture));然后改变video_picture中分量的值,为vd->videopict分量赋新值,调用ioctl (vd->fd, VIDIOCSPICT, &vd->videopict)就可以实现

4.对图象截取有两种方式:第一种是用read()直接读取数据,另外一种是用mmap是把设备文件映射到内存,用内存映射法一个显而易见的优点是效率高,由于进程能够直接读写内存,而不须要不论什么数据的拷贝,所以我们选择这样的方法。详细做法是

1        获取摄象头存储缓冲区的帧信息调用ioctl (vd->fd, VIDIOCGMBUF, &(vd->videombuf))

2把摄象头相应的设备文件映射到内存区。调用函数vd->pFramebuffer =    (unsigned char *) mmap (0, vd->videombuf.size, PROT_READ | PROT_WRITE,  MAP_SHARED, vd->fd, 0),成功调用后设备文件内容映射到内存区,

返回的映象内存区指针给vd->pFramebuffer,失败时返回-1。

3改动vd->vmmap中的设置,比如设置图象帧的垂直水平分辨率,彩色显示格式:   vd->vmmap.height = vd->hdrheight;

vd->vmmap.width = vd->hdrwidth;

vd->vmmap.format = vd->formatIn;

图象採集可分为单帧採集和连续帧採集,在本系统中採用连续帧採集的方法採集。将vd->videombuf.framese的值赋为2确定採集完成摄像头帧缓冲区帧数据进行循环的次数。在循环语句中,採集当中的vd->pFramebuffer + vd->videombuf.offsets[vd->vmmap.frame],使用ioctl (vd->fd, VIDIOCMCAPTURE, &(vd->vmmap)函数,若调用成功,则激活设备真正開始一帧图像的截取,是非堵塞的。接着使用ioctl (vd->fd, VIDIOCSYNC,&vd->vmmap.frame) 函数推断该帧图像是否截取完成,成功返回表示截取完成,之后就可将採集到的帧进行压缩,然后将压缩后的文件保存到发送缓冲区中。最后改动  vd->vmmap.frame ,vd->frame_cour的值进行下一次採集。

(2)视频压缩模块

对图像帧的编码是通过调用xvidcore-1.1.0函数库的函数实现的,在使用XVID之前要对XVID进行初始化,在初始化过程中,首先对编码器的各项參数即结构体xvid_enc_create中的各成员进行设定,然后调用xvid_encore(NULL, XVID_ENC_CREATE, &xvid_enc_create, NULL)建立编码器,初始化函数例如以下:

Int  enc_init(int use_assembler)

{

int xerr;

xvid_plugin_single_t single;         //运算參数

xvid_plugin_2pass1_t rc2pass1;

xvid_plugin_2pass2_t rc2pass2;

xvid_enc_plugin_t plugins[7];

xvid_gbl_init_t xvid_gbl_init;          //xvid初始化參数

xvid_enc_create_t xvid_enc_create;     //xvid编码參数

/* Set version -- version checking will done by xvidcore */

memset(&xvid_gbl_init, 0, sizeof(xvid_gbl_init));

xvid_gbl_init.version = XVID_VERSION;

xvid_gbl_init.debug = 0;                   //设置版本

/* Do we have to enable ASM optimizations ? */

if (use_assembler) {

xvid_gbl_init.cpu_flags = 0;

}

xvid_global(NULL, XVID_GBL_INIT, &xvid_gbl_init, NULL);  //初始化

/*------------------------------------------------------------------------

* XviD 编码器初始化

*----------------------------------------------------------------------*/

memset(&xvid_enc_create, 0, sizeof(xvid_enc_create));

xvid_enc_create.version = XVID_VERSION;   //设置版本

xvid_enc_create.width = XDIM;     //编码器输入宽度

xvid_enc_create.height = YDIM;    //编码器输入高度

xvid_enc_create.profile = XVID_PROFILE_S_L3;   //编码的框架级别

/* init plugins  */

xvid_enc_create.zones = NULL;

xvid_enc_create.num_zones = 0;

xvid_enc_create.plugins = NULL;

xvid_enc_create.num_plugins = 0;

/* No fancy thread tests */

xvid_enc_create.num_threads = 0;

/* Frame rate - Do some quick float fps = fincr/fbase hack */

if ((ARG_FRAMERATE - (int) ARG_FRAMERATE) < SMALL_EPS) {

xvid_enc_create.fincr = 1;

xvid_enc_create.fbase = (int) ARG_FRAMERATE;

} else {

xvid_enc_create.fincr = FRAMERATE_INCR;

xvid_enc_create.fbase = (int) (FRAMERATE_INCR * ARG_FRAMERATE);

}

if (ARG_MAXKEYINTERVAL > 0) {

xvid_enc_create.max_key_interval = ARG_MAXKEYINTERVAL;

}else {

xvid_enc_create.max_key_interval = (int) ARG_FRAMERATE *10;

}

//关键帧之间的间距

xvid_enc_create.max_bframes = 0;            //B帧设置

xvid_enc_create.bquant_ratio = 150;

xvid_enc_create.bquant_offset = 100;

xvid_enc_create.frame_drop_ratio = 0;   //编码弃帧率   从0到100

/* Global encoder options */

xvid_enc_create.global = 0;

/* I use a small value here, since will not encode whole movies, but short clips */

xerr = xvid_encore(NULL, XVID_ENC_CREATE, &xvid_enc_create, NULL);/*创建编码器,但创建编码器后编码器并不立即工作,编码器真正工作时是在enc_main函数中调用ret = xvid_encore(enc_handle, XVID_ENC_ENCODE, &xvid_enc_frame, &xvid_enc_stats)时*/

enc_handle = xvid_enc_create.handle;

return (xerr);

}

在编码过程中通常是让编码器自行认为什么时候产生I帧,但为了提高容错性或者减小网络传输量,会增大或减小I帧的产生频率。I帧的控制由參数通过xvid_enc_create.max_key_interval来绝定,当它设置成-1时,Xvid系统自己主动选择当前编码是否为I帧或P帧。当网络状况比較良好时(丢包数较少),能够适当降低I帧数量,这样能够提高服务质量。当网络丢包率上升时,就要考虑添加I帧数量,这样能够更快更好地修正、掩盖错误。

XVID编码的主函数为例如以下:

Int  enc_main(unsigned char *image,

unsigned char *bitstream,

int *key,

int *stats_type,

int *stats_quant,

int *stats_length,

int sse[3])

{

int ret;

xvid_enc_frame_t xvid_enc_frame;

xvid_enc_stats_t xvid_enc_stats;

memset(&xvid_enc_frame, 0, sizeof(xvid_enc_frame));

xvid_enc_frame.version = XVID_VERSION;        //帧版本

memset(&xvid_enc_stats, 0, sizeof(xvid_enc_stats));

xvid_enc_stats.version = XVID_VERSION;    //编码状态版本

/* Bind output buffer */

xvid_enc_frame.bitstream = bitstream;

xvid_enc_frame.length = -1;

/* Initialize input image fields */

if (image) {

xvid_enc_frame.input.plane[0] = image;

xvid_enc_frame.input.csp = XVID_CSP_I420;    //视频输入格式

xvid_enc_frame.input.stride[0] = XDIM;

} else {

xvid_enc_frame.input.csp = XVID_CSP_NULL;

}

xvid_enc_frame.vol_flags = 0;

xvid_enc_frame.vop_flags = vop_presets[ARG_QUALITY];

/* Frame type -- let core decide for us */

xvid_enc_frame.type = XVID_TYPE_AUTO;//自己主动决定帧格式

/* Force the right quantizer -- It is internally managed by RC plugins */

xvid_enc_frame.quant = 3;

xvid_enc_frame.motion = motion_presets[ARG_QUALITY];

/* We don‘t use special matrices */

xvid_enc_frame.quant_intra_matrix = NULL;

xvid_enc_frame.quant_inter_matrix = NULL;

ret = xvid_encore(enc_handle, XVID_ENC_ENCODE, &xvid_enc_frame,

&xvid_enc_stats);//编码并把编码状态存入xvid_enc_stats

*key = (xvid_enc_frame.out_flags & XVID_KEYFRAME);

*stats_type = xvid_enc_stats.type;

*stats_quant = xvid_enc_stats.quant;

*stats_length = xvid_enc_stats.length;

sse[0] = xvid_enc_stats.sse_y;

sse[1] = xvid_enc_stats.sse_u;

sse[2] = xvid_enc_stats.sse_v;

return (ret);

}

(3)网络传输发送模块

 

流媒体协议分析:

实时传输协议(Real-time Transport Protocol,RTP)是在Internet上处理多媒体数据流的一种网络协议,利用它可以在一对一(Unicast,单播)或者一对多(Multicast,多播)的网络环境中实现传流媒体数据的实时传输。RTP通常使用UDP来进行多媒体数据的传输,整个RTP协议由两个密切相关的部分组成:RTP数据协议和RTP控制协议。RTP是眼下解决流媒体实时传输问题的最好办法,在Linux平台上进行实时流媒体编程,我们採用了开放源码的RTP库JRTPLIB 3.5.2。JRTPLIB是一个面向对象的高度封装后的RTP库,它全然遵循RFC 3550设计,是一个非常成熟的RTP库,并且眼下仍在维护中。JRTPLIB提供了简单易用的API供程序开发人员使用,它使得我们仅仅需关注发送与接收数据,控制部分(RTCP) 由jrtplib内部实现。

RTP数据协议

RTP数据协议负责对流媒体数据进行封包并实现媒体流的实时传输,每个RTP数据报都由头部(Header)和负载(Payload)两个部分组成,当中头部前12个字节的含义是固定的,而负载则能够是音频或者视频数据。RTP数据报的头部格式如图3.1所看到的:

















V=2

P

X

CC

M

PT

序列号

时间戳

同步源标识(SSRC)

提供源标识(CSRC)

图3.1 RTP头部格式

当中几个域及其意义例如以下:

版本 (V): 标明RTP协议版本。

补充位(P):假设该位被设置,则在该packet末尾包括了额外的附加信息。

扩展位(X):假设该位被设置,则在固定的头部后存在一个扩展头部。

标记位 (M):: 该位的功能依详细应用的而定。我们将其作为一帧结束的标志。当M=1时,表示一帧的结束,下一个发送或接收的RTP数据包为新的一帧。

CSRC记数(CC):表示CSRC标识的数目。CSRC标识紧跟在RTP固定头部之后,用来表示RTP数据报的来源。


















   PT值

编码标准

採样速率(HZ)

  26

JPEG

90000

  31

H.261

90000

   34

h.263

90000

负载类型(PT):标明RTP负载的格式,包含所採用的编码算法、採样频率等。经常使用的PT值如图3.2所看到的:

图3.2 经常使用的负载类型及PT值

序列号:用来为接收方提供探測数据丢失的方法,但怎样处理丢失的数据则由应用程序负责,RTP协议本身并不负责数据的重传。

时间戳:记录了负载中第一个字节的採样时间,接收方根据时间戳可以确定数据的到达是否受到了延迟抖动的影响,但详细怎样来补偿延迟抖动则由应用程序本身负责。

RTP数据报包括了传输媒体的类型、格式、序列号、时间戳以及是否有附加数据等信息,这些都为实时的流媒体传输提供了对应的基础。RTP中没有连接的概念,它能够建立在底层的面向连接或面向非连接的传输协议之上;RTP也不依赖于特别的网络地址格式,而只只须要底层传输协议支持组帧(Framing)和分段(Segmentation)就足够了;RTP本身不提供不论什么可靠性机制,这些须要由传输协议或者应用程序本身来保证。RTP通常是在传输协议之上作为详细的应用程序的一部分加以实现的,如图3.3所看到的:













详细的应用程序

RTP/RTCP

UDP

TCP

IPv4/IPv6

局域网/广域网

图3.3 RTP与其他协议的关系

RTCP控制协议

RTCP控制协议与RTP数据协议一起配合使用,当应用程序启动一个RTP会话时将同一时候占用两个port,分别供RTP和RTCP使用。RTP本身并不能为按序数据传输包提供可靠的保证,也不提供流量控制和拥塞控制,这些都由RTCP来负责完毕。通常RTCP会採用与RTP同样的分发机制,向会话中的全部成员周期性地发送控制信息,应用程序通过接收这些数据,从中获取会话參与者的相关资料,以及网络状况、分组丢失概率等反馈信息,从而可以对服务质量进行控制或者对网络状况进行诊断。RTCP协议的功能是通过不同的RTCP数据报来实现的,主要有例如以下几种类型:

发送端报告(SR):发送端是指发出RTP数据报的应用程序或者终端,发送端同一时候也能够是接收端。

接收端报告(RR):接收端是指仅接收但不发送RTP数据报的应用程序或者终端。

源描写叙述(SDES):主要功能是作为会话成员有关标识信息的载体,如username、邮件地址等,此外还具有向会话成员传达会话控制信息的功能。

通知离开(BYE):主要功能是指示某一个或者几个源不再有效,即通知会话中的其它成员自己将退出会话。

RTCP数据报携带有服务质量监控的必要信息,可以对服务质量进行动态的调整,并可以对网络拥塞进行有效的控制。因为RTCP数据报採用的是多播方式,因此会话中的全部成员都可以通过RTCP数据报返回的控制信息,来了解其它參与者的当前情况。

一般应用场合下,发送媒体流的应用程序将周期性地产生发送端报告SR,该RTCP数据报含有不同媒体流间的同步信息,以及已经发送的数据报和字节的计数,接收端依据这些信息能够预计出实际的传输数据速率。还有一方面,接收端会向全部已知的发送端发送接收端报告RR,该RTCP数据报含有已接收数据报的最大序列号、丢失的数据报数目、延时抖动和时间戳等重要信息,发送端应用依据这些信息能够预计出往返时延,而且能够依据数据报丢失概率和时延抖动情况动态调整发送速率,以改善网络拥塞状况,或者依据网络状况平滑地调整应用程序的服务质量。

环境搭建 :JRTPLIB是一个用C++语言实现的RTP库。为Linux 系统安装JRTPLIB,从JRTPLIB的站点http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.html下载最新的源代码包jrtplib-3.5.2.tar.bz2。同一时候为了增加对线程的支持,须要单独下载jthread1.2.0.tar.bz2.。将下载后的源代码包保存在/usr/local/src文件夹下,运行以下的命令对其进行解压缩

bzip2 -dc jrtplib-3.5.2b.tar.bz2 | tar xvf –

接下去对JRTPLIB进行配置和编译:

cd jrtplib-3.5.2

./configure

make

再运行例如以下命令完毕JRTPLIB的安装:

make install

依照此步骤再安装jthread1.2.0。

流媒体编程

用JRTPLIB进行实时流媒体传输数据之前,首先应该生成RTPSession类的一个实例来表示此次RTP会话;
RTPSession session;
RTPSession 类的构造函数中须要一个表明UDP协议类型的參数,是基于Ipv4
还是基于Ipv6,构造函数默认的协议为Ipv4。
在真正创建会话之前还需设置两个參数 :
第一个參数为设置恰当的时戳单元
RTPSessionParams sessionparams;
sessionparams.SetOwnstampUnit(1.0/90000.0);
函数SetOwnstampUnit(1.0/90000.0)的參数1.0/90000.0表示的是以秒为单元的时戳单元。当使用RTP会话传输90000Hz採样的视频数据时,因为时戳每秒钟将递增90000.0,所以时戳单元应该被设置成1/90000.0。假设是音频数据,则设置为1.0/8000.0。
第二个參数为一个指向RTPTransmissionParams实例的指针。当採用IPv4协议时,应使用类RTPUDPv4TransmissionParams,当中要设置的參数为传输数据所用的端口号。
RTPUDPv4TransmissionParams transparams;
transparams.SetPortbase(localportbase);
然后就能够调用RTPSession 的create()函数真正创建会话:
int status=session.Create(sessionparams,&transparams);

假设RTP会话创建过程失败,Create()方法将返回一个负数,通过它尽管能够非常easy地推断出函数调用究竟是成功的还是失败,却非常难明确出错的原因究竟什么。JRTPLIB採用了统一的错误处理机制,它提供的全部函数假设返回负数就表明出现了某种形式的错误,而详细的出错信息则能够通过调用RTPGetErrorString()函数得到。该函数将错误代码作为參数传入,然后返回该错误代码所相应的错误信息。

下一步就是设置发送数据的目标地址和目标端口号:
RTPIPv4ADDRESS addr(desIP,desportbase);
session.AddDestination(addr);
其他须要设置的參数有默认负载类型,是否设标志位,时间戳增量。
session.SetDefaultPayloadType();
session.SetDefaultMark();
session.SetDefaultTimestampIncrement();
真正发送数据是通过调用SendPacket()函数实现的。 
int SendPacket(const void *data,size_t len,uint8_t pt,
bool mark,uint32_t timestampinc);
參数data指针指向要发送的数据,数据的长度为len,负载类型为pt, mark为标志位,取值为0或1,能够使用此标志位推断一帧的開始或结束。
时间戳增量timestampinc 用于表示是否是同一帧数据,对于同一帧数据设置同一时间戳。接收端也能够根据时间戳来推断一帧数据的開始或结束。
int SendPacket(const void *data,size_t len,uint8_t pt,
bool mark,uint32_t timestampinc,uint16_t hdrextID,
const void *hdrextdata,size_t numhdrextwords);
此函数用于发送带附加报头的数据帧。hdrextID用于对不同的报头进行编号,
hdrextdata为指向头数据的指针,报头长度为 numhdrextwords。     

程序流程框图:




開始发送





该帧大于1400字节?





通过RTP发送数据





将该拆成几个不大于1400字节的数据包





组帧并发送给解码线程





接收到RTP包时间戳与上一个同样?





网络





循环接收直到不同一时候间戳的RTP包





通过RTP接收数据

















             图3 网络发送接收程序流程图

对程序流程图的说明:

(1)发送端拆帧的算法例如以下:

If (该数据帧小于1400个字节){

直接用RTPSessio::SendPacket()发送出去;

}

else

{

把该帧拆成1400个字节一个包再发送。对于同一帧数据,採用同样的时间戳来标记,以利于接收端对数据的接收。

}

(2)接收端组帧算法例如以下:

while(该RTP包的时间戳和上一个RTP包的时间戳同样)

{

说明该RTP包和上一个RTP包属于同一个视频帧的数据。

把接收到的数据保存在缓存中。}

然后把属于同一视频帧的数据组装好,发送给解码线程。採用拆帧方法传输视频数据比直接发送丢包率更低,且实时性更强,传输质量明显提高。

3.3.2client

(1)网络传输接收模块

我们在使用Jrtp 库的同一时候增加了Jthread 的支持,使得详细的数据接收在后台执行,仅仅需改写类RTPSession的成员函数OnPollThreadStep()和ProcessRTPPacket(const RTPSourceData &srcdat,const RTPPacket &rtppack)。因为同一个RTP会话中同意有多个參与者,通过调用RTPSession类的GotoFirstSourceWithData()和GotoNextSourceWithData()方法来遍历那些携带有数据的源。在函数OnPollThreadStep()中,为了正确接收同一数据源的数据据,必须先对数据源表加锁。通过调用BeginDataAccess()实现,当正确接收一个数据报后,调用EndDataAccess()实现对数据源表的解锁。

在函数ProcessRTPPacket (const RTPSourceData & srcdat,const RTPPacket & rtppack);

中对接收到的数据包进行处理。

首先调用

char * payloadpointer = (char *)rtppack.GetPayloadData ()得到源数据包的指针,并通过rtppack.HasMarker ()来推断是否是一帧的结束。因为一帧数据要分成多个数据包进行传送,须要将收到的包临时保存到内存缓冲区中,等到有足够的帧数据后再调用解码线程进行解码。memcpy(receivepointer,payloadpointer,rtppack.GetPayloadLength ())将数据临时保存在receivepointer指向的内存缓冲区中。假设一帧数据结束则设置标志位并作对应处理。

JRTPLIB为RTP数据报定义了三种接收模式,当中每种接收模式都详细规定了哪些到达的RTP数据报将会被接受,而哪些到达的RTP数据报将会被拒绝。通过调用RTPSession类的SetReceiveMode()方法能够设置下列这些接收模式:RECEIVEMODE_ALL:为缺省的接收模式,全部到达的RTP数据报都将被接收;

RECEIVEMODE_IGNORESOME:除了某些特定的发送者之外,全部到达的RTP数据报都将被接收,而被拒绝的发送者列表能够通过调AddToIgnoreList()、DeleteFromIgnoreList()和ClearIgnoreList()方法来进行设置; RECEIVEMODE_ACCEPTSOME:除了某些特定的发送者之外,全部到达的RTP数据报都将被拒绝,而被接受的发送者列表能够通过调用AddToAcceptList ()、DeleteFromAcceptList和ClearAcceptList ()方法来进行设置。

(2)视频解码模块

client解码的流程图例如以下所看到的:





设置解码帧缓冲区





停止显示





解码一帧





是VOL头





Y





N





Y





N





SDL显示





释放解码缓冲区





终止





開始





Dec_init()解码初始化

当client接收到发送来的压缩后的视频信息后,调用视频解码模块将码流解码成能够播放的YUV格式。在调用解码模块之前要正确的初始化解码器。初始化解码器的程序例如以下所看到的:

static int

dec_init(int use_assembler, int debug_level)

{

int ret;

xvid_gbl_init_t   xvid_gbl_init;

xvid_dec_create_t xvid_dec_create;

memset(&xvid_gbl_init, 0, sizeof(xvid_gbl_init_t));

memset(&xvid_dec_create, 0, sizeof(xvid_dec_create_t));

/*------------------------------------------------------------------------

* XviD 核心初始化

*----------------------------------------------------------------------*/

/* Version */

xvid_gbl_init.version = XVID_VERSION;//版本

/* Assembly setting */

if(use_assembler)

xvid_gbl_init.cpu_flags = 0;

else

xvid_gbl_init.cpu_flags = XVID_CPU_FORCE;

xvid_gbl_init.debug = debug_level;

xvid_global(NULL, 0, &xvid_gbl_init, NULL);

/*------------------------------------------------------------------------

* XviD 解码器初始化

*----------------------------------------------------------------------*/

xvid_dec_create.version = XVID_VERSION;//解码器版本

xvid_dec_create.width = 0;// 帧的宽 自己主动适应

xvid_dec_create.height = 0;// 帧的高 自己主动适应

ret = xvid_decore(NULL, XVID_DEC_CREATE, &xvid_dec_create, NULL);

//创建解码实例

dec_handle = (int *)xvid_dec_create.handle;  //传递句柄

return(ret);

}

解码过程调用xvid_decore(dec_handle, XVID_DEC_DECODE, &xvid_dec_frame, xvid_dec_stats)将得到的解码状态放入xvid_dec_stats中,依据xvid_dec_stats再对解码后的帧进行处理,解码的主程序例如以下:

static int  dec_main(unsigned char *istream,unsigned char *ostream, int istream_size,xvid_dec_stats_t *xvid_dec_stats)

{

int ret;

xvid_dec_frame_t xvid_dec_frame;

/* Reset all structures */

memset(&xvid_dec_frame, 0, sizeof(xvid_dec_frame_t));

memset(xvid_dec_stats, 0, sizeof(xvid_dec_stats_t));

/* Set version */

xvid_dec_frame.version = XVID_VERSION;  // 帧版本

xvid_dec_stats->version = XVID_VERSION;  //解码状态版本

/* No general flags to set */

xvid_dec_frame.general          = 0;

/* Input stream */

xvid_dec_frame.bitstream        = istream;  //指向输入的视频流的指针

xvid_dec_frame.length           = istream_size;//输入视频流的大小

/* Output frame structure */

xvid_dec_frame.output.plane[0]  = ostream; //指向输出的视频流的指针

xvid_dec_frame.output.stride[0] = XDIM*BPP;

xvid_dec_frame.output.csp = CSP;//视频输出格式

ret = xvid_decore(dec_handle, XVID_DEC_DECODE, &xvid_dec_frame, xvid_dec_stats);

return(ret);

}

(3)视频显示模块

在本系统中解码得到的YUV格式的视频流用SDL显示,显示的流程图例如以下所看到的:




推断帧长宽变化





重建YUVOverlay





Y





N





将帧信息复制到显示缓冲区





锁定SDL





解锁SDL





接收一帧并解压





显示





检測键盘信息QUIT?





N





Y





初始化SDL





结束,调用SDLQUIT()

SDL的初始化步骤例如以下:

首先调用SDL_Init (SDL_INIT_VIDEO) 初始化SDL的视频显示系统,然后调用函数SDL_SetVideoMode (320, 240, 0, SDL_HWSURFACE | SDL_DOUBLEBUF| SDL_ANYFORMAT| SDL_RESIZABLE)设置视频模式,包含长,宽,和 BPP,參数SDL_HWSURFACE建立一个带视频存储的SURFACE,SDL_DOUBLEBUF使能双缓冲区,消除Bpp对SURFACE的影响,SDL_RESIZABLE使窗体大小可调整。

最后调用函数SDL_CreateYUVOverlay(XDIM, YDIM, SDL_YV12_OVERLAY, screen)建立一个长为XDIM宽为YDIM,格式为SDL_YV12_OVERLAY的YUV平面,返回一个指向新建SDL_overlay的指针。SDL_overlay结构体例如以下:

typedef struct SDL_Overlay {

Uint32 format;                          /* Read-only */

int w, h;                               /* Read-only */

int planes;                             /* Read-only */

Uint16 *pitches;                        /* Read-only */

Uint8 **pixels;                         /* Read-write */

/* Hardware-specific surface info */
        struct private_yuvhwfuncs *hwfuncs;
        struct private_yuvhwdata *hwdata;

/* Special flags */
        Uint32 hw_overlay :1;   /* Flag: This overlay hardware accelerated? */

Uint32 UnusedBits :31;

} SDL_Overlay;

SDL_DisplayYUVOverlay(overlay, &rect) 这就是要显示YUV的详细函数,第一个參数是已经创建的YUV平面,第二个參数是一个矩形宽,设置在显示平面的哪个区域内显示。在使用前,须要将要显示的数据指针送给pixels

outy = out_buffer;

outu = out_buffer+XDIM*YDIM;

outv = out_buffer+XDIM*YDIM*5/4;

for(y=0;y<screen->h && y<overlay->h;y++)

{

op[0]=overlay->pixels[0]+overlay->pitches[0]*y;

op[1]=overlay->pixels[1]+overlay->pitches[1]*(y/2);

op[2]=overlay->pixels[2]+overlay->pitches[2]*(y/2);

memcpy(op[0],outy+y*XDIM,XDIM);

if(y%2 == 0)

{

memcpy(op[1],outu+XDIM/2*y/2,XDIM/2);

memcpy(op[2],outv+XDIM/2*y/2,XDIM/2);

}

}
pitches是指YUV存储数据相应的stride(步长)

系统測试

測试环境 局域网

延迟

測试系统延时採用网络时间server(NTP),在视频传输之前从网络时间server上获得时间,在client接收到视频播放之前再一次获得时间,计算两次差值从而得到延时,多次測试得到,在局域网的条件下,延时小于0.1秒。

图象质量:在局域网的条件下,图象无失真。

时间: 2024-09-30 16:41:11

基于Linux的视频传输系统(上大学时參加的一个大赛的论文)的相关文章

基于Linux的v4l2视频架构驱动编写(转载)

转自:http://www.linuxidc.com/Linux/2011-03/33022.htm 其实,我刚开始一直都不知道怎么写驱动,什么都不懂的,只知道我需要在做项目的过程中学习,所以,我就自己找了一个关于编写Linux下的视频采集监控项 目做,然后上学期刚开学的时候听师兄说,跟院长做项目,没做出来也没关系,所以直接退出博士的团队,投靠了院长的门下,呵呵,说到这里其实并不是我太见风 使驼了,而是····老是让我做单片机的东东,我嫌没意思,他也知道我一开始就要学嵌入式,所以,最后,我想了一

基于H.264协议的视频传输系统中遇到的问题以及解决办法

问题1.视频压缩解码模块在运用的时候出现错误:解码器再解码第二帧视频图片的时候出现异常 client: ../../decoder/T264dec.c:594:T264dec_decode_nal: Assertion `0' failed. Aborted 对于该问题的分析及解决过程为: 1.  修改数据类型,所有缓存区改为unsigned char类型(原来统一为char 类型),但是还是遇到一样的异常错误,问题没有解决. 2.  查看缓存区具体内容是否与服务器端压缩的数据是一致的的,对照数

清华大学视频课件:基于Linux的C++(自主模式)

基于Linux的C++(自主模式) 课程简介 Linux操作系统开源的特性使得其获得越来越重要的地位,而Linux系统编程也向C++程序设计者提出了更高的要求.本课程由C/C++语言的共性与特性出发,在深入学习程序设计语言的基础上,进一步强调程序设计语言的适用性,并与Linux系统编程紧密结合,通过大力培养学习者的抽象思维能力和计算思维能力,将学习者对语言作为工具的基本认知转化为动手实践能力,完成从具象到抽象再到具象的思维能力转变.课程内容主要涵盖C++程序设计基本概念.数据组织与算法设计.程序

视频怎么下载大电脑上

可以下的...以下是本人用的方法,当然会python等的朋友可以尝试自己抓包,会点HTML差不多就可以做了~ 进入视频页面: 1.F12(审查网页源代码) 2.点击第二项Network(网络),第一项是Elements(元素) 3.F5(刷新网页,以重新获取资源) 4.查看浏览器客户端下载的资源里是否含有一些特征的资源,如后缀名为mp4,如url包含flv等(这里有时media过滤是过滤不出来的,当然视频一般较大,我用的chrome视频名称会出现红色,比如imooc视频名称基本是L.flv) 5

【转】实现RTP协议的H.264视频传输系统

1.  引言       随着信息产业的发展,人们对信息资源的要求已经逐渐由文字和图片过渡到音频和视频,并越来越强调获取资源的实时性和互动性.但人们又面临着另外一种不可避免的尴尬,就是在网络上看到生动清晰的媒体演示的同时,不得不为等待传输文件而花费大量时间.为了解决这个矛盾,一种新的媒体技术应运而生,这就是流媒体技术.流媒体由于具有启动时延小.节省客户端存储空间等优势,逐渐成为人们的首选,流媒体网络应用也在全球范围内得到不断的发展.其中实时流传输协议 RTP 详细说明了在互联网上传递音频和视频的

碉堡了!基于 Linux 的可自动瞄准的智能步枪

此文并不是 TrackingPoint 产品的最新报道,而是基于 arstechnica 2013 年的一篇报道. CES (此处指 2013 年的 CES)真是包罗科技万象;当我们忙于报道摄影器材.电视和 CPU 时,却忽略了大量存在于我们视线之外的科技产品.总部位于 Austin 的初创公司 TrackingPoint 并不属于我们传统的关注范围,但就凭它运用科技做到完美一击这一点,足以使我驻足把玩它的产品. TrackingPoint 制造的“精确制导枪支”,也可称为“PGF”,这一系列有

基于Linux应用层的6LOWPAN物联网网关及实现方法

本发明涉及一种基于Linux应用层的6LOWPAN物联网网关及实现方法,所述物联网网关包括开发平台以及无线射频模块,其实现方法是:所述6LOWPAN物联网网关的以太网网口收到访问6LOWPAN无线传感器网络中节点的数据包,Linux应用层将以太网数据包格式转化成6LOWPAN物联网的格式,然后通过无线射频模块发送出去:同理,Linux应用层同时监听无线射频模块,收到6LOWPAN无线传感器网络中的数据包,所述Linux应用层将数据包转化成以太网数据包格式,再通过以太网网口把该数据包发送出去.本发

项目三. 基于图像压缩的视频监控系统

项目三. 基于图像压缩的视频监控系统 Sprint0-产品设计与规划 基于图像压缩的视频监控系统 1. 产品功能演示 在linux系统上运行程序,弹出登录界面,输入地址.端口信息,弹出视频监控界面,实时传出视频信息. 2. 功能模块分析 采集端: 图像采集子系统 图像编码子系统 传输子系统 主程序 监控端: 传输子系统 图像编码子系统 传输子系统 主程序 Sprint1-基于Epoll架构的采集端程序框架设计 第1课-Epoll机制精通 大纲: v  为什么用Epoll? 阻塞型IO与多路复用

基于Linux的智能家居的设计(1)

写在前面:做了半年的毕业设计,找到的工作与这个完全无关,发现自己现在有写不甘心,但是我还是在关注这方面的发展,自己的摸索和前人的帮助我完成了智能家居的一部分,希望这个能够给一些初学者 能够一些便利,毕竟技术是一个开放的,不属于某一个人的. 摘要 本课题主要目的是设计和实现一个基于Linux开发平台的智能家居系统.本系统主要使用PVC板做成的家居模型.本系统硬件使用基于ARM架构的samsung S3C6410芯片做成的OK6410开发板为手持终端,利用Zigbee实现网络通讯,并结合各种电子元器