Linux-视频监控系统(5)-TCP传输子系统实现

news/2024/7/9 16:08:33 标签: Epoll, mini2440, linux, 摄像头, 监控

传输子系统的协议打算采用TCP来完成,开发板充当服务器,PC机充当客户机。传输视频其实也就是传输一幅幅图片,因此接下来的任务就时在服务器和客户机之间传输图片。这里面又涉及到了传输协议,我们采用申请式的协议,客户机发送一个图片请求,服务器就传送一副图片,如果没有任何请求,服务器将什么也不做。

 

1、协议设计

为了把Epoll用起来我们定义2个事件,分别是对可以对socket读和写的事件。当socket创建之后,显然是立马就可以写的(发送数据),因此我们初始化之后先添加读事件,等待客户机的请求,当收到请求之后,开始处理请求,这个时候把读事件关闭(挂起接收请求的任何),把写事件打开(开始传输数据),当传输结束后再把读事件打开、写事件关闭,这样就可以实现有序的传输图片。

 

对于请求包的头部包含3个字节,可以是请求一帧图片、获取图像格式、设置图像格式等,这3个字节又分为请求包长度、命令1(请求类型)、命令2(命令ID)。接下来是数据部分,数据部分的前4个字节又是数据包的长度,随后是数据。把这些构思好之后,就根据这个协议来传输图片,为了方便,可以把一些宏定义,比如说构造头部,获取请求ID。写出的头文件如下:

 

#ifndef __PROTOCOL_H__
#define __PROTOCOL_H__

#define VID_FRAME_MAX_SZ    (0xFFFFF - FRAME_MAX_SZ)

#define FRAME_MAX_SZ    253
#define FRAME_DAT_MAX   253
#define FRAME_HDR_SZ    3

#define FRAME_ERR_SZ    3

#define TYPE_MASK       0xE0
#define TYPE_BIT_POS    5
#define SUBS_MASK       0x1F

#define LEN_POS         0
#define CMD0_POS        1
#define CMD1_POS        2
#define DAT_POS         3

/* Error codes */
#define ERR_SUCCESS     0   /* success */
#define ERR_SUBS        1   /* invalid subsystem */
#define ERR_CMD_ID      2   /* invalid command ID */
#define ERR_PARAM       3   /* invalid parameter */
#define ERR_LEN         4   /* invalid length */

#define TYPE_SREQ    	0x1
#define TYPE_SRSP     	0x2

	
#define SUBS_ERR  		0x0
#define SUBS_SYS  		0x1
#define SUBS_VID  		0x3
#define SUBS_MAX  		0x4
	
	
#define REQUEST(len, type, subs, id)	(((len) << (8*LEN_POS)) | \
	(((type) << TYPE_BIT_POS | (subs)) << (8*CMD0_POS)) | ((id) << (8*CMD1_POS)))
	

enum request {

	SYS_VERSION		=	REQUEST(0x0, TYPE_SREQ, SUBS_SYS, 0x0),

	/**
	 * VID SubSystem
	 */
	VID_GET_UCTLS	=	REQUEST(0x0, TYPE_SREQ, SUBS_VID, 0x0), 
	VID_GET_UCTL	=	REQUEST(0x4, TYPE_SREQ, SUBS_VID, 0x1), 
	VID_SET_UCTL	=	REQUEST(0x8, TYPE_SREQ, SUBS_VID, 0x2), 
	VID_SET_UCS2DEF	=	REQUEST(0x0, TYPE_SREQ, SUBS_VID, 0x3), 

	VID_GET_FRMSIZ	=	REQUEST(0x0, TYPE_SREQ, SUBS_VID, 0x10), 
	VID_GET_FMT	    =	REQUEST(0x0, TYPE_SREQ, SUBS_VID, 0x11), 

	VID_REQ_FRAME	=	REQUEST(0x0, TYPE_SREQ, SUBS_VID, 0x20),
};

#define REQUEST_ID(req)     (((req) >> (8*CMD1_POS)) & 0xFF)
#define REQUEST_TYPE(req)   (((req) >> (8*CMD0_POS + TYPE_BIT_POS)) & TYPE_MASK)
#define REQUEST_SUBS(req)   (((req) >> (8*CMD0_POS)) & SUBS_MASK)
#define REQUEST_LEN(req)    (((req) >> (8*LEN_POS)) & 0xFF)


#endif

 

 

2、结构设计

为了方便代码框架的设计,定义一些结构:

传输子系统的结构,成员包括:服务器的socke文件,Epoll的fd,以及Epoll需要使用到的参数。

 

struct tcp_srv {
    int                     sock;           
    int                     epfd;
    void                    *arg;           

};

在和客户机建立连接之后还需要保存一些客户机的信息,因此定义一个结构:

 

 

struct tcp_cli
{
	int sock;//客户机的sockfd
	struct sockaddr_in addr;//客户机的地址

	struct tcp_srv *srv;//保存服务器的相关信息
	struct event_ext *ev_tx;//发送数据的epoll事件
	struct event_ext *ev_rx;//接收数据的epoll事件

	char *buf;
	int len;

	unsigned char req[FRAME_MAX_SZ];//存放请求数据包
	unsigned char rsp[FRAME_MAX_SZ + VID_FRAME_MAX_SZ];//存放发送数据包
};

 

 

 

 

 

3、代码设计

首先是传输子系统的初始化,包括建立TCP的socket,以及添加socket读事件到Epoll,函数如下:

int net_sys_init()
{
	struct sockaddr_in addr;
	struct sockaddr_in sin;
	struct tcp_srv *s = calloc(1, sizeof(struct tcp_srv));
	struct tcp_cli *c = calloc(1, sizeof(struct tcp_cli));

	int new_sock;
	int len;

	//初始化传输子系统
	s->epfd = srv_main->epfd;

	//socket
	s->sock = socket(AF_INET, SOCK_STREAM, 0);

	//bind
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(DEF_TCP_SRV_PORT);
	bind(s->sock, (struct sockaddr*)&addr, sizeof(struct sockaddr));

	//listen
	listen(s->sock, 5);

	//accept
	new_sock = accept(s->sock, (struct sockaddr*)&sin, &len);
	c->sock = new_sock;
	memcpy(&c->addr, &sin, len);
	c->srv = s;

	//将传输子系统的事件加入Epoll池,tcp_cli作为事件的参数
	c->ev_rx = epoll_event_create(c->sock, EPOLLIN, rx_app_handler, c);
	c->ev_tx = epoll_event_create(c->sock, EPOLLOUT, tx_app_handler, c);

	//先加入rx的事件
	epoll_add_event(c->srv->epfd, c->ev_rx);

	//保存数据到srv_main中
	srv_main->srv = s;

	//return s;
	return 0;
}


接下来就阻塞起来了,等到请求包的到了,当有了一个请求之后触发rx_app_handler函数,这个函数设计如下:

static void rx_app_handler(int sock, void *arg)
{
	struct tcp_cli *c = arg;
	int res = 0;
	unsigned char *pbuf;

	pbuf = &c->req[0];
	res = read(c->sock, pbuf, FRAME_HDR_SZ);//读取头部数据
	process_incoming(c);//根据请求ID进行处理
}

 

然后调用process_incoming进行处理,这个函数定义如下

/*请求包处理函数
形参:
c:连接到的客户机的结构*/
int process_incoming(struct tcp_cli *c)
{
	struct cam *v 	= srv_main->cam;
	__u8	*req 	= c->req;
	__u8	*rsp	= c->rsp; 
	__u8	id 		= req[CMD1_POS];
	__u8	fmt_data[FRAME_DAT_MAX];
	__u8	status	= ERR_SUCCESS;
	__u32	pos,len,size;

	switch(id){
		//获取图像格式
		case REQUEST_ID(VID_GET_FMT):
			//获取图像格式
			cam_get_fmt(v, fmt_data);
			//构造返回数据
			build_ack(rsp, (TYPE_SRSP << TYPE_BIT_POS) | SUBS_VID, id, 4, fmt_data);
			//发送返回数据
			net_send(c, rsp, 4 + FRAME_HDR_SZ);
			
			break;

		//获取一帧图像
		case REQUEST_ID(VID_REQ_FRAME):
			pos = FRAME_HDR_SZ + 4;
			//获取一帧图像
			size = cam_get_trans_frame(v, &rsp[pos]);
			//构造返回数据
			build_ack(rsp, (TYPE_SRSP << TYPE_BIT_POS) | SUBS_VID, id, 4, (__u8*)&size);
			//发送返回数据
			net_send(c, rsp, pos + size);
			break;

		default:
			status = ERR_CMD_ID;
			break;
	}

	return status;
}


这里对获取一帧图像的流程做简要介绍,在摄像头子系统的设计中,如果获取到了一帧图像,会把图像数据保存在缓冲里面,而这个缓冲出队后,它的首地址和长度又保存到一个buf结构中,因此获取一帧图片,主要就是根据地址和长度把图片数据拷贝到返回数据包中,然后调用net_send发送数据。net_send函数如下:

void net_send(struct tcp_cli *tc, void *buf, int len)
{
	struct tcp_cli *c = tc;
	struct tcp_srv *s = c->srv;

	epoll_del_event(s->epfd, c->ev_rx);
	c->buf = buf;
	c->len = len;
	epoll_add_event(s->epfd, c->ev_tx);
}

这个函数非常简单,他只是把socket读事件删除(挂起接收请求包),然后保存发送数据包的信息,再添加socket写事件,什么时候可以写socke交给Epoll,让系统来判断,当可以写socket的时候会唤醒tx_app_handler,这个函数如下:

 

static void tx_app_handler(int sock, void *arg)
{
	struct tcp_cli *c = arg;
	struct tcp_srv *s = c->srv;
	int res = 0;

	res = send(sock, c->buf, c->len, 0);
	if(res > 0)
	{
		c->len -= res;
		if(c->len == 0)
		{
			epoll_del_event(s->epfd, c->ev_tx);
			epoll_add_event(s->epfd, c->ev_rx);
		}
	}
}


这里面除了发送数据之外,还要判断是否发送成功,如果发送成功,可以添加读事件而关闭写事件,从而开始写一个轮回。如果写失败了可以返回相应的错误编码,这里没有设计。

 

到这里整个框架和代码就设计完成,至于客户机播放器方面,我们先提供一个编写好的程序用于测试,具体代码的设计后面再学。

 

代码和可执行程序在https://github.com/dayL-W/Video-capture-system.git

下载后,分别在开发板和PC机上运行wcamsrv和wcamclient,在运行PC机的客户端是会提示需要输入开发板的IP地址。

更多Linux资料及视频教程点击这里

 

 


http://www.niftyadmin.cn/n/890806.html

相关文章

递归及尾递归优化

1、递归介绍 递归简而言之就是自己调用自己。使用递归解决问题的核心就是分析出递归的模型&#xff0c;看这个问题能拆分出和自己类似的问题并且有一个递归出口。比如最简单的就5的阶乘&#xff0c;可以把它拆分成5*4!&#xff0c;然后求4!又可以调用自己&#xff0c;这种问题…

Windows下修改MySQL编码为utf8

转自&#xff1a;http://www.jianshu.com/p/f4a564179107 Problem Windows下安装MySQL&#xff08;mysql-installer-community-5.7.17.0.msi&#xff09;后&#xff0c;默认编码不全是gbk。 mysql> show variables like %char%; ------------------------------------------…

Python下安装MySQL驱动出错

Python中安装MySQL时&#xff1a; pip install mysql-connector 出现下面的报错 Unable to find Protobuf include directory.---------------------------------------- Command "d:\python\python.exe -u -c "import setuptools, tokenize;__file__C:\\W indows\\…

SQLAlchemy 和其他的 ORM 框架

Python ORM 概览 作为一个美妙的语言&#xff0c;Python 除了 SQLAlchemy 外还有很多ORM库。在这篇文章里&#xff0c;我们将来看看几个流行的可选 ORM 库&#xff0c;以此更好地窥探到Python ORM 境况。通过写一段脚本来读写2个表 &#xff0c;person 和 address 到一个简单的…

GTK学习(1)-常用控件之窗口( GtkWindow )

转自http://blog.csdn.net/lianghe_work/article/details/47087109 项目中需要用到GTK&#xff0c;在这里整理几个经常用到的知识点。 窗口的创建&#xff1a; GtkWidget *gtk_window_new(GtkWindowType type); GtkWindowType是一个枚举&#xff0c;有两种情况&#xff1a; G…

GTK学习(2)-布局管理

GTK中用于管理各种构件布局的是一类特殊的构件&#xff0c;称作布局容器&#xff08;Layout Containers&#xff09;&#xff0c;这类构件不可见&#xff0c;并且有很多种&#xff0c;常用的有以下几种&#xff1a; GtkHBox&#xff1a;水平组装盒 GtkVBox&#xff1a;垂直组…

GTK学习(3)-Lable、pixbuf、single、GtkEntry

添加标签 标签主要是显示文本信息&#xff0c;用作标志。 标签的创建&#xff1a; GtkWidget *gtk_label_new(const gchar *str); str&#xff1a;文本内容 返回值&#xff1a;标签指针 设置标签的内容&#xff1a; void gtk_label_set_text (GtkLabel *label, const gch…

GTK学习(4)-主循环(main loop)的工作原理

GTK主循环(main loop)的工作原理 我们知道GUI应用程序都是事件驱动的。这些事件大部分都来自于用户&#xff0c;比如键盘事件、鼠标事件或笔点事件。还有一些事件来自于系统内部&#xff0c;比如定时事件、socket事件和其它文件事件等等。在没有任何事件的情况下&#xff0c;应…