mjpg-streamer客户端

MJPG-streamer是一款免费基于IP地址的视频流服务器,它的输入插件从摄像头读取视频数据,这个输入插件产生视频数据并将视频数据复制到内存中,它有多个输出插件将这些视频数据经过处理,其中最重要的输出插件是网站服务器插件,它将视频数据传送到用户浏览器中,MJPG-streamer的工作就是将其中的一个输入插件和多个输出插件绑定在一起,所有的工作都是通过它的各个插件完成的。

移植mjpg-streamer

(1).发送一个请求字符串
“GET /?action=snapshot\n”
"GET /?action=stream\n"
“GET /?action=command\n”
(2).再发送一次字符串
如果我们不使用密码功能!则只需发送任意长度为小于2字节的字符串,比如:“f\n”

如果发送的请求是:“GET /?action=snapshot\n”
(3).需要接收一次字符串(是服务器发过来的报文)
(4).接收一帧图片

如果发送的请求是:"GET /?action=stream\n"
(3).需要接收一次字符串(是服务器发过来的报文)
while(1)
{
  (4).再接收一次报文,解析它,得到一帧图片的大小(size)
  (5).接收size个字节的数据
}

服务器报文:
sprintf(buffer,“Content-Type: image/jjpeg\r\n”
“Content-Length: %d\r\n”
“\r\n”,frame_size);

电脑或安卓端的客户端

video_recv_manager.c

static PT_VideoRecv g_ptVideoRecvHead = NULL;	//链表头

//注册
int RegisterVideoRecv(PT_VideoRecv ptVideoRecv){
	PT_VideoRecv ptTmp;
	if (!g_ptVideoRecvHead){
		g_ptVideoRecvHead   = ptVideoRecv;
		ptVideoRecv->ptNext = NULL;
	}
	else{
		ptTmp = g_ptVideoRecvHead;
		while (ptTmp->ptNext)
			ptTmp = ptTmp->ptNext;
		ptTmp->ptNext     = ptVideoRecv;
		ptVideoRecv->ptNext = NULL;
	}
	return 0;
}

//根据名字取出指定的"获取视频模块"
PT_VideoRecv GetVideoRecv(char *pcName){
	PT_VideoRecv ptTmp = g_ptVideoRecvHead;	
	while (ptTmp){
		if (strcmp(ptTmp->name, pcName) == 0)
			return ptTmp;
		ptTmp = ptTmp->ptNext;
	}
	return NULL;
}

//调用各模块的初始化函数
int VideoRecvInit(void){
	return Video_Recv_Init();
}

video_recv.c

typedef struct VideoBuf {
    T_PixelDatas tPixelDatas;
    int iPixelFormat;
    /* signal fresh frames */
  	pthread_mutex_t db;				//自旋锁
  	pthread_cond_t  db_update;		//条件变量
}T_VideoBuf, *PT_VideoBuf;

typedef struct VideoRecv {
    char *name;
    int (*Connect_To_Server)(int *SocketClient, const char *ip);	//连接到服务器
    int (*DisConnect_To_Server)(int *SocketClient);					//断开服务器
    int (*Init)(int *SocketClient);									//客户端初始化
    int (*GetFormat)(void);											//获取视频格式
    int (*Get_Video)(int *SocketClient, PT_VideoBuf ptVideoBuf);	//获取视频
    struct VideoRecv *ptNext;
}T_VideoRecv, *PT_VideoRecv;

分配设置注册VideoRecv结构体

/* 构造 */
static T_VideoRecv g_tVideoRecv = {
    .name        			= "http",
    .Connect_To_Server  	= connect_to_server,		//连接到服务器
    .DisConnect_To_Server   = disconnect_to_server,		//断开服务器
    .Init 					= init,						//初始化
    .GetFormat				= getformat,				//获取视频格式
    .Get_Video				= get_video,				//获取视频
};

/* 注册 */
int Video_Recv_Init(void){
    return RegisterVideoRecv(&g_tVideoRecv);
}

连接到服务器
断开服务器
获得视频的格式

static int connect_to_server(int *SocketClient, const char *ip){
	struct sockaddr_in tSocketServerAddr;
	
	*SocketClient = socket(AF_INET, SOCK_STREAM, 0);

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);   /* host to net, short */
 	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
 	inet_aton(ip, &tSocketServerAddr.sin_addr);
	memset(tSocketServerAddr.sin_zero, 0, 8);

	connect(*SocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
	return 0;
}

static int disconnect_to_server(int *SocketClient){
	close(*SocketClient);
	return 0;
}

static int getformat(void){
	return V4L2_PIX_FMT_MJPEG;	/* 直接返回视频的格式 */
}

初始化函数
1.发送一个请求字符串:“GET /?action=stream\n”
2.再发送一次字符串(不使用密码)
3.需要接收一次字符串(是服务器发过来的报文)

static int init(int *SocketClient){
	char ucSendBuf[100];	//发送缓冲区
	int iSendLen;
	int iRecvLen;
	unsigned char ucRecvBuf[1000];	//接收缓冲区

	/* 发请求类型字符串 */
	memset(ucSendBuf, 0x0, 100);
	strcpy(ucSendBuf, "GET /?action=stream\n");
	iSendLen = send(*SocketClient, ucSendBuf, strlen(ucSendBuf), 0);
	if (iSendLen <= 0){
		close(*SocketClient);
		return -1;
	}

	/* 如果我们不使用密码功能!则只需发送任意长度为小于2字节的字符串 */
	memset(ucSendBuf, 0x0, 100);
	strcpy(ucSendBuf, "f\n");
	iSendLen = send(*SocketClient, ucSendBuf, strlen(ucSendBuf), 0);
	if (iSendLen <= 0){
		close(*SocketClient);
		return -1;
	}

	/* 将从服务器端接收一次报文 */
	/* 接收客户端发来的数据并显示出来 */
	iRecvLen = recv(*SocketClient, ucRecvBuf, 999, 0);
	if (iRecvLen <= 0){
		close(*SocketClient);
		return -1;
	}
	else{
		ucRecvBuf[iRecvLen] = '\0';
		printf("http header: %s\n", ucRecvBuf);
	}
	return 0;
}

获取视频get_video
3.需要接收一次字符串(是服务器发过来的报文)
while(1)
{
4.再接收一次报文,解析它,得到一帧图片的大小(size)
5.接收size个字节的数据
}

获取一帧数据要上锁,原子操作。

static int get_video(int *SocketClient, PT_VideoBuf ptVideoBuf){
	long int video_len, iRecvLen;	//一帧视频长度,接收视频长度
	int FirstLen = 0;				//第1次获取视频大小长度
	char tmpbuf[1024];
	char *FreeBuffer = NULL;		//剩余视频缓冲区

	while(1){
		video_len = getFileLen(SocketClient, tmpbuf, &FirstLen);	//获取一帧视频大小
		iRecvLen = http_recv(SocketClient, &FreeBuffer, video_len - FirstLen);	//获取剩余视频数据
		
		pthread_mutex_lock(&ptVideoBuf->db);
		/* 将两次接收到的视频数据组装成一帧数据 */
		memcpy(ptVideoBuf->tPixelDatas.aucPixelDatas, tmpbuf, FirstLen);				//第一次获得视频
		memcpy(ptVideoBuf->tPixelDatas.aucPixelDatas+FirstLen, FreeBuffer, iRecvLen);	//第二次获得视频
		ptVideoBuf->tPixelDatas.iTotalBytes = video_len;
		pthread_cond_broadcast(&ptVideoBuf->db_update);	// 发出一个数据更新的信号,通知发送通道来取数据
		pthread_mutex_unlock( &ptVideoBuf->db );	// 原子操作结束
	}
	return 0;
}

获得一帧视频大小,参数:客户端,视频数据大小和长度(输出参数)
通过字符串判断是否为报文,/r/n/r/n后面就是一帧视频的数据,要保存在缓冲区。
服务器报文:
sprintf(buffer,“Content-Type: image/jjpeg\r\n”
“Content-Length: %d\r\n”
“\r\n”,frame_size);

static long int getFileLen(int *SocketClient, char *FreeBuf, int *FreeLen){
	int iRecvLen;
	long int videolen;		//视频长度
	char ucRecvBuf[1024];	//接收缓冲区
	char *plen, *buffp;

	while(1){
		iRecvLen = recv(*SocketClient, ucRecvBuf, 1024, 0);		//接收1k数据(一次最多1024)
		/* 解析ucRecvBuf,判断接收到的数据是否是报文 */
		plen = strstr(ucRecvBuf, "Length:");	//字符串查找报文
		if(NULL != plen){
			plen = strchr(plen, ':');		//字符查找
			plen++;
			videolen = atol(plen);			//字符串转整数,获得视频长度
			printf("the Video Len %ld\n", videolen);
		}
		buffp = strstr(ucRecvBuf, "\r\n\r\n");
		if(buffp != NULL)
			break;
	}
	buffp += 4;	//指向视频数据
	*FreeLen = 1024 - (buffp - ucRecvBuf);	//视频数据长度
	memcpy(FreeBuf, buffp, *FreeLen);		//保存视频数据
	return videolen;
}

视频接收函数,参数:socket句柄,剩余视频缓冲区地址,获取大小

static long int http_recv(int *SocketClient, char **lpbuff, long int size){
	int iRecvLen = 0, RecvLen = 0;	//单次接收大小,总接收大小
	char ucRecvBuf[BUFFER_SIZE];	//接收缓冲区
	
	while(size > 0){ //1次最大1024,多次接收,size可能很大
		iRecvLen = recv(*SocketClient, ucRecvBuf, (size > BUFFER_SIZE)? BUFFER_SIZE: size, 0);	
		if (iRecvLen <= 0)
			break;

		RecvLen += iRecvLen;
		size -= iRecvLen;

		if(*lpbuff == NULL){	//如果缓冲区为空,分配空间
			*lpbuff = (char *)malloc(RecvLen);
			if(*lpbuff == NULL)
				return -1;
		}
		else{	//如果缓冲区不为空,之前已分配,重新分配
			*lpbuff = (char *)realloc(*lpbuff, RecvLen);
			if(*lpbuff == NULL)
				return -1;
		}
		memcpy(*lpbuff+RecvLen-iRecvLen, ucRecvBuf, iRecvLen);	//视频数据拷贝进缓存
	}
	return RecvLen;	//返回接收总长度
}

main函数

PT_VideoRecv g_ptVideoRecvOpr;
int iSocketClient;

//获取摄像头数据的线程:获取一帧视频
void *RecvVideoThread(void *data){	
	g_ptVideoRecvOpr->Get_Video(&iSocketClient, (PT_VideoBuf)data);
	return data;
}

/* video2lcd </dev/video0,1,...> */
int main(int argc, char **argv){	
	if (argc != 2){
		printf("Usage:\n");
		printf("%s <ip>\n", argv[0]);
		return -1;
	}
	
	/* 一系列的初始化 */	
	DisplayInit();	/* 注册显示设备 */
	/* 可能可支持多个显示设备: 选择和初始化指定的显示设备 */
	SelectAndInitDefaultDispDev("crt");
	GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);
	GetVideoBufForDisplay(&tFrameBuf);
	iPixelFormatOfDisp = tFrameBuf.iPixelFormat;

	VideoRecvInit();	//视频接收模块初始化
	g_ptVideoRecvOpr = GetVideoRecv("http");
	iPixelFormatOfVideo = g_ptVideoRecvOpr->GetFormat();

	VideoConvertInit();
	ptVideoConvert = GetVideoConvertForFormats(iPixelFormatOfVideo, iPixelFormatOfDisp);

	/* 启动摄像头设备 */
	g_ptVideoRecvOpr->Connect_To_Server(&iSocketClient, argv[1]);	//连接到服务器
	g_ptVideoRecvOpr->Init(&iSocketClient);	//视频接收模块初始化
	
	memset(&tVideoBuf, 0, sizeof(tVideoBuf));
	tVideoBuf.tPixelDatas.aucPixelDatas = malloc(30000);
	memset(&tConvertBuf, 0, sizeof(tConvertBuf));
	tConvertBuf.iPixelFormat     = iPixelFormatOfDisp;
	tConvertBuf.tPixelDatas.iBpp = iLcdBpp;
	
	pthread_mutex_init(&tVideoBuf.db, NULL);         /* 初始化 global.db(自旋锁) 成员 */
	pthread_cond_init(&tVideoBuf.db_update, NULL);   /* 初始化 global.db_update(条件变量) 成员 */
	/* 创建获取摄像头数据的线程 */
	pthread_create(&RecvVideo_Id, NULL, &RecvVideoThread, &tVideoBuf);

	while (1){
		pthread_cond_wait(&tVideoBuf.db_update, &tVideoBuf.db);	 //等待数据更新
		ptVideoBufCur = &tVideoBuf;
		if (iPixelFormatOfVideo != iPixelFormatOfDisp){	  //格式转换
			/* 转换为RGB */
			ptVideoConvert->Convert(&tVideoBuf, &tConvertBuf);
			DBG_PRINTF("Convert %s, ret = %d\n", ptVideoConvert->name, iError);      
			ptVideoBufCur = &tConvertBuf;
		}

		/* 合并进framebuffer */
		/* 接着算出居中显示时左上角坐标 */
		iTopLeftX = (iLcdWidth - ptVideoBufCur->tPixelDatas.iWidth) / 2;
		iTopLeftY = (iLcdHeigt - ptVideoBufCur->tPixelDatas.iHeight) / 2;
		PicMerge(iTopLeftX, iTopLeftY, &ptVideoBufCur->tPixelDatas, &tFrameBuf.tPixelDatas);
		FlushPixelDatasToDev(&tFrameBuf.tPixelDatas); /* 把framebuffer的数据刷到LCD上, 显示 */	
	}

	pthread_detach(RecvVideo_Id);	// 等待线程结束,以便回收它的资源	
	return 0;
}

互斥锁和条件变量

在主函数中创建接收视频线程RecvVideoThread,调用Get_Video函数获取一帧数据。

get_video函数中:
3.需要接收一次字符串(是服务器发过来的报文)
while(1){

 4.再接收一次报文,解析它,得到一帧图片的大小(size)
 5.接收size个字节的数据

 pthread_mutex_lock(&ptVideoBuf->db);    上锁
 memcpy(ptVideoBuf->tPixelDatas.aucPixelDatas, tmpbuf, FirstLen);    //第一次获得视频
 memcpy(ptVideoBuf->tPixelDatas.aucPixelDatas+FirstLen, FreeBuffer, iRecvLen);  //第二次获得视频
 pthread_cond_broadcast(&ptVideoBuf->db_update);  // 发出一个数据更新的信号,通知发送通道来取数据
 pthread_mutex_unlock( &ptVideoBuf->db );   解锁
}

主函数:
while (1){
  pthread_cond_wait(&tVideoBuf.db_update, &tVideoBuf.db); //等待数据更新
  ptVideoBufCur = &tVideoBuf
  转换为RGB
  合并进framebuffer
  PicMerge(iTopLeftX, iTopLeftY, &ptVideoBufCur->tPixelDatas, &tFrameBuf.tPixelDatas);
  FlushPixelDatasToDev(&tFrameBuf.tPixelDatas); /* 把framebuffer的数据刷到LCD上, 显示 */
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值