Month: 11月 2013
网络五子棋 一 关于务器端的设计
首先说明,此小程序是写完后才更新本博客的,所以程序中涉及的内容都是完整了的,与客户端的联系还得等待接下来的更新才会联系得上。
第一点:程序只属于个人测试程序,带有调试信息,同时也还有很多的不完善,觉得不好的,请各位尽管拍砖。~~
下面说正题:
开发环境和工具:Ubuntu 12.04、 Qt4.7 、vi
使用语言:C/C++
详细设计过程:
第一步,要设计一个服务器端,首先要考虑的就是我们如何初始化我们自己的服务器,然后设置好本地的服务器之后,等待客户端的连接。
这里我采用的是linux下的socket编程,按照之前在博客中提到的七步,初始化和设置本地服务器。之后便是开始监听,然后等待客户端的链接。
第二步:当有客户端连接到服务器端之后,我们需要做的是把客户端的连接fd存储起来,然后开辟一个线程用于处理客户端的请求。
在这里我使用的是C++中的容器vector,我在程序中设置了一个全局的vector变量,用于存储客户端的连接到服务器分配给客户端的fd。以便我接下俩用于发送消息回客户端。
第三步:当客户端连接上后,我们需要做的便是开始处理线程,我们为没一个客户端的连接开辟了一个线程处理函数,处理客户端的连接。
第四步:我们开始线程后,就是处理客户端的信息了,这使用了简单的客户端和服务器的数据封装,以便我方便解析到底客户端发送过来的是命令信息还是聊天信息。这样我就可以知道客户端到底想干嘛,然后分别做不同的处理。
关于协议封装这一块,其实还有很多可以做,这里我只是简单的定义了一个头,区分命令和聊天信息而已,有兴趣的可以继续深入协议封装。想现在的zigbee这类的技术。很多都支持二次封装开发。
以下我会给出整个实现的代码,并且写有很多注释。具体要联系接下来的客户端代码一起看,才好。尽量在未来3天内更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <netdb.h> #include <time.h> #include <signal.h> #include <pthread.h> #include <vector> #include <arpa/inet.h> #include <QDebug> using namespace std; int socketfd; //定义一个表示本服务器的socketfd vector<int> v; //定义一个全局容器,存储链接的上的客户端的socketfd int roleFlag1 = 0; //标记下棋的客户端 int roleFlag2 = 0; int flag; //标记转换下棋的角色 int winOrFailure[30][30]={-1}; //记录下棋的位置,以及是谁下了棋子在哪里,用于判断输赢 //输出客户端信息 void out_addr(struct sockaddr *addr) { struct sockaddr_in *a = (struct sockaddr_in *)addr; char ip[16]; memset(ip,0,16); if(a->sin_family == AF_INET) { printf("IPv4 "); printf("%s (%d) connected!\n",(char *)inet_ntop(AF_INET,&a->sin_addr.s_addr,ip,sizeof(ip)),ntohs(a->sin_port)); } else { printf("OTHR"); } } //发送聊天信息 void senMsg(char *msg) { int i=0; for(i=0;i<v.size();i++) //容器循环的遍历所有客户端的socketfd,这里也可以使用容器迭代遍历容器 { write(v.at(i),msg,strlen(msg)); //发送聊天信息给所有的客户端 } } //再次开局,从置所有的标记和下棋记录 void resetGame(void) { roleFlag1 = roleFlag2 = flag = 0; memset(winOrFailure,-1,sizeof(winOrFailure)); } //发送输赢消息 void sendWin(int fd) { int i=0; char buf[30] = {'\0'}; sprintf(buf,"#Command:fd=%d",fd); //发送的消息是赢的客户端的socketfd for(i=0;i<v.size();i++) //发送给所有的客户端 { write(v.at(i),buf,strlen(buf)); } } //判断输赢 int jugeWinOrfailure(char *str,int fd) { int i,j; int x_bak; //记录点击的坐标,用户恢复 int y_bak; int count = 1; //判断输赢的时候统计有多少个连成直线 QString buf = QString(str); buf.remove(0,buf.indexOf("s")+1); int x = buf.left(buf.indexOf(",")).toInt(); int y = buf.mid(buf.indexOf(",")+1).toInt(); //获取当前这一步下棋的位置 winOrFailure[x][y] = fd; //存储到相应的输赢记录数组,记录是谁的棋子 //以下是判断输赢模块 x_bak = x;y_bak = y;count=0; while(winOrFailure[x][y] == winOrFailure[x][y-1]) { y = y-1; } for(i=0;i<5;i++) { if(y>=0 && y<29) { if(winOrFailure[x][y] == winOrFailure[x][y+i]) count=count+1; } if(count >= 5) { return fd;} } x = x_bak; y = y_bak;count=0; while(winOrFailure[x][y] == winOrFailure[x-1][y]) { x = x-1; } for(i=0;i<5;i++) { if(x>=0 && x<29) { if(winOrFailure[x][y] == winOrFailure[x+i][y]) count=count+1; } if(count >= 5) {return fd;} } x = x_bak; y = y_bak;count=0; while(winOrFailure[x][y] == winOrFailure[x+1][y+1]) { x = x+1; y = y+1; } for(i=0;i<5;i++) { if(x>=0 && x<29 && y<29 && y>=0) { if(winOrFailure[x][y] == winOrFailure[x-i][y-i]) count=count+1; } if(count >= 5) {return fd;} } x = x_bak; y = y_bak;count=0; while(winOrFailure[x][y] == winOrFailure[x-1][y-1]) { x = x-1; y = y-1; } for(i=0;i<5;i++) { if(x>=0 && x<29 && y<29 && y>=0) { if(winOrFailure[x][y] == winOrFailure[x+i][y+i]) count=count+1; } if(count >= 5) {return fd;} } return 0; } //发送按钮点击的位置回客户端 void sendButtonPos(char *message) { int i=0; for(i=0;i<v.size();i++) { write(v.at(i),message,strlen(message)); } } //点击开始按钮后,发回信息设置棋盘可用 void sendGridEnableTrue(char *message) { int i=0; for(i=0;i<v.size();i++) { write(v.at(i),message,strlen(message)); } } //发送请求开始按钮信息 void sendStartGameRequest(int fd,char *message) { if(fd == v.at(0)) write(v.at(1),message,strlen(message)); else if(fd == v.at(1)) write(v.at(0),message,strlen(message)); } //线程处理函数 void *thread_fun(void *arg) { int fd = (int)arg; char buf[1024] = {0}; printf("connected! to do service thread_fun\n"); while(1) { memset(buf,0,sizeof(buf)); int ret = read(fd,buf,sizeof(buf)); //qDebug()<<buf; //printf("Data from client:\n"); if(ret < 0) { exit(1); }else if(ret == 0){ ;} else if(strstr(buf,"#Command:") != NULL) //包含子串 { if(strcmp(buf,"#Command:AreYouReady?") == 0) //相等时是0 { //链接到服务器的客户端数量为0 if(v.max_size() <= 1) continue; roleFlag1 = fd; //给客户端1赋值 sendStartGameRequest(fd,buf); }else if(strcmp(buf,"#Command:StartGame!") == 0) { qDebug()<<"#Command:StartGame!\n"; sendGridEnableTrue(buf); roleFlag2 = fd; //给客户端2赋值 flag = roleFlag1; }else if((strstr(buf,"#Command:pos") != NULL) && (flag == fd)) { qDebug()<<"#Command is pos:"<<buf<<endl; sendButtonPos(buf); if(fd == roleFlag1) //角色1下了棋 flag = roleFlag2; else if(fd == roleFlag2) //角色2下了下棋 flag = roleFlag1; int win = jugeWinOrfailure(buf,fd); qDebug()<<win; if(win >= 5) sendWin(win); }else if(strcmp(buf,"#Command:SomeoneWin!") == 0) { qDebug()<<"#Command:SomeoneWin!"; resetGame(); } }else if(strstr(buf,"#Msg:") != NULL) { qDebug()<<"#Msg:.."; senMsg(buf); } } } int main(int argc,char *argv[]) { if(argc < 2) { fprintf(stderr,"%s error \n",strerror(errno)); exit(1); } //注册信号 //1、构建socket结构体 socketfd = socket(AF_INET,SOCK_STREAM,0); if(socketfd < 0) { fprintf(stderr,"socket:%s\n",strerror(errno)); exit(1); } //2、构建表达服务器的地址结构体 struct sockaddr_in saddr; //3、向结构体添加数据 saddr.sin_family = AF_INET; //Internet地址族IPV4 saddr.sin_port = htons(atoi(argv[1])); //设置端口号,把本地端口号转换为网络类型 saddr.sin_addr.s_addr = INADDR_ANY; //Internet地址 bzero(&saddr.sin_zero,8); //专门用于清零操作 //4、绑定地址结构体和socket描述符 if(bind(socketfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0) { fprintf(stderr,"bind:%s\n",strerror(errno)); close(socketfd); exit(1); } //5、监听客户端的链接 if(listen(socketfd,0)<0) { fprintf(stderr,"listen:%s\n",strerror(errno)); exit(1); } //记录客户端的结构体,和接收客户端的请求 struct sockaddr_in caddr; pthread_t tid; while(1) { int connfd; memset(&caddr,0,sizeof(caddr)); socklen_t len = sizeof(caddr); //准备接收客户端的信息服务 connfd = accept(socketfd,(struct sockaddr *)&caddr,&len); if(connfd < 0) { fprintf(stderr,"accept:%s\n",strerror(errno)); close(socketfd); exit(1); } out_addr((struct sockaddr *)&caddr); //连接上一个客户端后开启线程处理函数 if(pthread_create(&tid,NULL,thread_fun,(void *)connfd) != 0) { fprintf(stderr,"pthread_create:%s\n",strerror(errno)); continue; } v.push_back(connfd); pthread_detach(tid); printf("connected!\n"); } return 0; } |
采用Qt 实现简单的客户端
之前已经实现了基本的服务器端的架构,并且通过浏览器我们已经可以看见实时的视屏画面了,现在要做的是,把视屏画面显示在自己编写的一个客户端上面,而不是一直使用浏览器查看。
环境:acer 4752G ubuntu 12.04
工具:vi Qt 4.7
这里首先需要解决一个非常关键的问题:
当我们的服务器段,通过摄像头采集到了数据之后,我们把它经过编码之后形成的是一张一张的图片帧,然后发往浏览器,这时我们采用的http协议,发往浏览器,然后浏览器可以解析我们的http协议,去掉基本的数据封装的头部和尾部,但是现在我们使用的是自己编写的客户端,这样的话我们就必须得自己接写服务器端发来的数据,自己去除数据帧的帧头,以及我们在服务器端发出的一帧数据的开始和结尾除,自己删除多余的信息,然后去除相应的图片,绘制到我们的客户端截面。
当然,如果当当是为了实现一个自己写的客户端可以接收数据的话,我们完全可以自己定义协议,然后封装数据发往客户端,客户端做相应的解析即可。
这里呢,我测试的是使用http协议,
首先发送的是一个http头部 [HEADER]…..\r\n\r\n… \n\n..[data]\r\n
\n\n..[data]\r\n
\n\n..[data]\r\n
这是我们发出的每一针的数据的封装格式,
然后我们在解析的时候就是要
第一步:删除http报头
第二部:判断是否有一个数据结束的标记已经之后还附加的多余的数据 \r\n–
第三部:当等待有一帧完整的数据后,我们就从数据帧的结尾处开始取数据,区到开头,然后吧取到的数据发送给绘制函数。update()我们的屏幕。绘制完之后,删除现有的数据,准备接收下一帧的数据
解析过程是最终要的。!!
一下是完整的实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
#ifndef DIALOG_H #define DIALOG_H #include <QDialog> #include <QLineEdit> #include <QPushButton> #include <QLabel> class Dialog : public QDialog { Q_OBJECT public: Dialog(QWidget *parent = 0); ~Dialog(); //获取IP地址和端口 QString getHost(); int getPort(); private: QLabel *lbIp; QLabel *lbPort; QLineEdit *lineEditIp; QLineEdit *lineEditPort; QPushButton *btnOk; QPushButton *btnCancel; }; #endif // DIALOG_H #ifndef SCREEN_H #define SCREEN_H #include <QWidget> #include "dialog.h" #include <QImage> #include "thread.h" class Screen : public QWidget { Q_OBJECT public: Screen(QWidget *parent = 0); ~Screen(); private: QImage image; Dialog *dialog; Thread *thread; protected: void paintEvent(QPaintEvent *); signals: public slots: void showDialog(); void showImages(QImage image); void connectToServer();//链接到服务器,响应对话框发出的accept信号 }; #endif // SCREEN_H #ifndef THREAD_H #define THREAD_H #include <QThread> #include <QDebug> #include <QtNetwork/QTcpSocket> #include <QThread> #include <QImage> class Thread : public QThread { Q_OBJECT public: Thread(); void setIpPort(QString,int); private: QString ip; int port; signals: void sendCurrentImage(QImage); protected: void run(); }; #endif // THREAD_H #ifndef WIDGET_H #define WIDGET_H #include <QtGui/QWidget> #include <QPushButton> #include <QImage> #include "screen.h" class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = 0); ~Widget(); private: QPushButton *btnStart; QPushButton *btnStop; QPushButton *btnQuit; Screen *screen; }; #endif // WIDGET_H |
接下来是具体的实现每个函数和信号之间的关联
提醒一点,资源文件自己去找,然后项目文件什么的,记得加network模块,否则编译会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
#include "dialog.h" #include <QHBoxLayout> #include <QVBoxLayout> #include <QDebug> Dialog::Dialog(QWidget *parent) { lbIp = new QLabel(this); lbIp->setText("HostIP:"); lbPort = new QLabel(this); lbPort->setText(" Port:"); lineEditIp = new QLineEdit("127.0.0.1",this); lineEditPort = new QLineEdit("8000",this); btnOk = new QPushButton(this); btnOk->setText("OK"); btnCancel = new QPushButton(this); btnCancel->setText("Cancel"); QHBoxLayout *ipHBox = new QHBoxLayout; ipHBox->addWidget(lbIp); ipHBox->addWidget(lineEditIp); QHBoxLayout *portHBox = new QHBoxLayout; portHBox->addWidget(lbPort); portHBox->addWidget(lineEditPort); QHBoxLayout *btnHBox = new QHBoxLayout; btnHBox->addWidget(btnOk); btnHBox->addWidget(btnCancel); QVBoxLayout *vBox = new QVBoxLayout; vBox->addLayout(ipHBox); vBox->addLayout(portHBox); vBox->addLayout(btnHBox); setLayout(vBox); connect(btnOk,SIGNAL(clicked()),this,SLOT(accept())); connect(btnCancel,SIGNAL(clicked()),this,SLOT(reject())); } QString Dialog::getHost() { qDebug()<<"dialog"<<lineEditIp->text(); return lineEditIp->text(); } int Dialog::getPort() { qDebug()<<"dialog"<<lineEditPort->text(); return lineEditPort->text().toInt(); } Dialog::~Dialog() { } #include "screen.h" #include <QPainter> #include <QRect> #include <QDebug> #include <QDialog> Screen::Screen(QWidget *parent) :QWidget(parent) { resize(960,540); image.load(":/images/wugui.jpg"); dialog = new Dialog(this); dialog->setModal(true); //设置当对话框弹出时,背景不能点击 thread = new Thread; //注册accepted信号 connect(dialog,SIGNAL(accepted()),this,SLOT(connectToServer()));//注册在dialog里面发出的信号的响应 connect(thread,SIGNAL(sendCurrentImage(QImage)),this,SLOT(showImages(QImage))); //响应信号sendCurrentImage刷新图片 } void Screen::connectToServer() { qDebug()<<"qqqqqq"<<dialog->getHost()<<dialog->getPort(); thread->setIpPort(dialog->getHost(),dialog->getPort()); //设置链接的服务器IP和PORT thread->start(); //开启线程 } void Screen::paintEvent(QPaintEvent *) { QPainter painter(this); QRect rect(0,0,this->width(),this->height()); //设置绘制的矩形区域 painter.drawImage(rect,image); } void Screen::showImages(QImage image) { this->image =image; update(); } void Screen::showDialog() { dialog->show(); } Screen::~Screen() { } #include "thread.h" #include "screen.h" #include <QDebug> QImage image; Thread::Thread() { } void Thread::setIpPort(QString ip, int port) { this->ip = ip; this->port = port; qDebug()<<ip<<port; } void Thread::run() { QTcpSocket tcpSocket; tcpSocket.connectToHost(ip,port); //链接到服务器 qDebug()<<ip<<port; if(tcpSocket.waitForConnected()) //链接 qDebug()<<"connected..."<<endl; QByteArray streamData; // streamData.clear(); //清空数据 streamData = QByteArray("action=stream"); //定义数据头 tcpSocket.write(streamData); //发往服务器,请求数据 if(!tcpSocket.waitForBytesWritten()) { qDebug()<<"Write action failed!"<<endl; //写入头失败 } else { qDebug()<<"Write action success"<<endl; } //去除报头 //QByteArray head="\r\n\r\n"; //QByreArray start="\n\n"; //数据开始 //图片数据区 data //QByteArray end="\r\n--"; //数据结尾 while(!streamData.contains("\r\n\r\n")) //判断数据段是否包含有\r\n\r\n数据 { //数据头是\r\n\r\n tcpSocket.waitForReadyRead(); //准备读取数据 streamData.append(tcpSocket.readAll()); //读取数据到streamData } int index = streamData.indexOf("\r\n\r\n"); //找到开始位置 streamData.remove(0,index+4); //删除报头 QByteArray photoData; while(1) { photoData.clear();//清空数据 while(!streamData.contains("\r\n--")) //是否有数据尾 { tcpSocket.waitForReadyRead(); streamData.append(tcpSocket.readAll()); } index = streamData.indexOf("\n\n"); streamData.remove(0,index+2); //删除数据尾 index = streamData.indexOf("\r\n--"); photoData = streamData.left(index); //截取左边的长度,也就是目前得到的一张图片 image.loadFromData(photoData); //转换格式 emit sendCurrentImage(image); //发送信号 streamData.remove(0,index+4); //再次取出数据头 } } #include "widget.h" #include <QHBoxLayout> #include <QVBoxLayout> #include "screen.h" #include "dialog.h" Widget::Widget(QWidget *parent) : QWidget(parent) { resize(960,580); screen = new Screen(this); btnStart = new QPushButton(this); btnStart->setText("Start"); btnStop = new QPushButton(this); btnStop->setText("Stop"); btnQuit = new QPushButton(this); btnQuit->setText("Quit"); QHBoxLayout *btnHBox =new QHBoxLayout; btnHBox->addWidget(btnStart); btnHBox->addWidget(btnStop); btnHBox->addWidget(btnQuit); QVBoxLayout *vBox = new QVBoxLayout; vBox->addWidget(screen); vBox->addLayout(btnHBox); setLayout(vBox); connect(btnStart,SIGNAL(clicked()),screen,SLOT(showDialog())); connect(btnQuit,SIGNAL(clicked()),this,SLOT(close())); } Widget::~Widget() { } |
以上便是初学测试的代码。话说有点坑跌,。将就下吧。觉得不好的就请猛拍砖就好。
利用V4L2架构实现简单的视频监控
看看博客,忽然发现很久不更新文章了,最近都在忙着找工作什么的,现在又是感冒发烧,难过得很!
刚好今天实现了基本的客户端,就简单的说一下关于整个实现过程吧,废话不过多说~~
测试环境:acer 4752G ubuntu 12.04
工具:vi Qt4.7
首先要做一个视频监控,第一步就是要有一个摄像头(这是硬条件,没摄像头做个JB对吧),这里采用的是笔记本的自带摄像头。当然其它的也可以,不过要查寻一下基本的驱动架构和支持的图片格式。有的是jpeg的图片格式的,有的是yuyv的图片格式。确保摄像头是好的之后,接下来就是利用vi写一个服务器了,服务器主要工作就是等待客户端的链接,然后负责调度摄像头采集图片,不断的发往客户端就行了~~
以下是我测试用的代码,略微有点乱,注释什么的和打印信息测试什么的都没删掉,讲究用下吧。不想弄它了。。。以下是完整的代码实现。服务器采用系统编程这一块实现~~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
/*=============================================================== * Copyright (C) 2013 All rights reserved. * * #Filename : common.h * #Express : * #Created : 2013-11-8 10:57 * #Compiler : gcc/g++ * #Author : Art's * #Company : Art's * ================================================================*/ #ifndef __COMMON_H_ #define __COMMON_H_ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/time.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <netdb.h> #include <linux/videodev2.h> #include <jpeglib.h> #define CLEAR(x) memset(&x,0,sizeof(x)) typedef enum { MJPEG = V4L2_PIX_FMT_MJPEG, YUYV = V4L2_PIX_FMT_YUYV }fmt_t; #define WIDTH 640 #define HEIGHT 480 typedef struct v4l2_capability Cap; // typedef struct v4l2_format Fmt; // typedef struct v4l2_fmtdesc Fmtd; // typedef struct v4l2_streamparm Fps; // typedef struct v4l2_requestbuffers ReqBuf; // typedef struct v4l2_buffer Vbuf; // /** * @brief 摄像头配置 */ typedef struct { int cam_fd; //摄像头描述符 fmt_t fmt; //图片格式YUYV MJPEG两种 枚举类型,定义在头 int width; //宽度 int height; //高度 int fps; //针率 int n_buf; //向内存申请的缓冲数 }Cam_conf; /* * @brief 用户缓冲 */ typedef struct { char *start; //起始地址 int len; //缓冲区长度 }Buf; ReqBuf reqbuf; //内核空间 Vbuf vbuf; //队列 Buf *bufs; //用户映射空间 Buf tmp_buf; //一帧图片空间 //Buf share_buf; //线程共享变量 Cam_conf conf; //摄像头配置 #endif |
这里是图片的haffman编码之类的东西,我也不怎么懂,网上找的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
#ifndef __HUFFMAN_H__ #define __HUFFMAN_H__ #define DHT_SIZE 432 static unsigned char dht_data[DHT_SIZE] = { 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa }; #define JPG_HUFFMAN_TABLE_LENGTH 0x1A0 const unsigned char JPEGHuffmanTable[JPG_HUFFMAN_TABLE_LENGTH] = { 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA }; #endif |
接下来就是具体的关于摄像头那一块的实现,查询出基本的摄像头格式,然后相应的设置摄像头,实现数据的采集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 |
/** * @file query.h * @brief 摄像头查询模块接口文件 */ #ifndef __QUERY_H_ #define __QUERY_H_ #include "common.h" #include "webcam.h" /** * @brief query_cap * 查询摄像头参数及驱动信息:打印struct v4l2_capability * @param fd 摄像头文件描述符 * @return */ extern void query_cap(int fd); /** * @brief query_fps * 使用VIDIOC_G_PARM查询帧率信息 * @param fd */ extern void query_fps(int fd); /** * @brief query_fmt * 使用VIDIOC_ENUM_FMT查询摄像头信息 * @param fd */ extern void query_fmt(int fd); #endif /** * @file webcam.h * @brief 摄像头配置及操作接口文件 */ #ifndef __WEBCAM_H_ #define __WEBCAM_H_ #include "common.h" /** * @brief cam_open * 打开摄像头 * @param argc * @param argv * @return 文件描述符 */ extern int cam_open(int argc,char *argv[]); /** * @brief cam_close * 关闭摄像头、释放用户缓冲空间 * @param fd */ extern void cam_close(int fd); /** * @brief cam_init * 查询并设置设备属性、格式、帧率,内存映射设置 * @param fd 摄像头 */ void cam_init(Cam_conf *conf); /** * @brief cam_free 释放用户缓冲空间 */ static void cam_free(void); /** * @brief start_capturing * 开启视频流 * @param fd */ void start_capturing(int fd); /** * @brief stop_capturing * 关闭视频流 * @param fd */ void stop_capturing(int fd); /** * @brief set_fmt 设置视频的帧格式 * 根据摄像头像素参数设置Logitech C210:640*480 * @param conf */ static void set_fmt(Cam_conf *conf ); /** * @brief set_fps * 设置帧率,C210 最大帧率30fps * @param fd * @param fps */ static void set_fps(Cam_conf *conf ); /** * @brief init_mmap * 在内核空间申请帧缓冲、映射到用户空间、并入缓冲队列 * @param fd * @param num */ static void init_mmap(Cam_conf *conf ); /** * @brief set_frame_size * 设置图片大小 * @param conf * @return */ static int set_frame_size(Cam_conf *conf); /** * @brief get_frame * 出队一帧数据,拷贝到temp_buf,并入队 * @param conf * @return 成功0,失败-1 */ int get_frame(Cam_conf *conf); /** * @brief process * 保存一帧图片到本地目录 * @param conf */ void process(Cam_conf *conf); /** * @brief is_huffman * 判断图片是否为huffman编码 * @param buf * @return */ static int is_huffman(unsigned char *buf); /** * @brief print_picture * 将图片输出到fd文件中 * @param fd * @param buf * @param size * @return */ int print_picture(int fd, unsigned char *buf, int size); #endif #ifndef __PRINT_H__ #define __PRINT_H__ extern int print_picture(int fd, unsigned char *buf, int size); #endif #ifndef __YUV2JPEG_H__ #define __YUV2JPEG_H__ #include <jpeglib.h> #include "common.h" typedef struct{ struct jpeg_destination_mgr jpg_p; JOCTET * buffer; unsigned char *out_buf; int out_buf_size; unsigned char *out_buf_cur; int *written; }jpg_dst; typedef jpg_dst *jpg_ptr; /* yuyv to jpeg function */ void dst_buffer(j_compress_ptr,unsigned char*,int,int*); int yuv_to_jpeg(unsigned char*, unsigned char*,int, int); #endif 以下是上面四个头文件的函数实现 /** * @file query.h * @brief 摄像头查询模块接口文件 */ #include "query.h" /** * @brief query_cap * 查询摄像头参数及驱动信息:打印struct v4l2_capability * @param fd 摄像头文件描述符 * */ void query_cap(int fd) { Cap cap; CLEAR(cap); if(ioctl(fd,VIDIOC_QUERYCAP,&cap) < 0) //查询摄像头的功能 { perror("query_cap()"); //也是出错处理 exit(EXIT_FAILURE); } printf("===============Camera Info===============\n"); printf("Driver: %s \n",cap.driver); printf("Card: %s \n",cap.card); printf("BusInfo: %s \n",cap.bus_info); printf("Version: %d \n",cap.version); printf("Capabilities: 0x%x \n",cap.capabilities); //printf("Device Cap: 0x%x \n",cap.device_caps); //printf("Reserved: 0x%x \n",cap.reserved); } /** * @brief query_fps * 使用VIDIOC_G_PARM查询帧率信息 * @param fd */ void query_fps(int fd) { Fps fps; CLEAR(fps); fps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //设置类型 //ioctl(fd,VIDIOC_G_PARM,&fps); if(ioctl(fd,VIDIOC_G_PARM,&fps) < 0) { fprintf(stderr,"Usage:query_fps:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } printf("=======================Camera Fps==============\n"); printf("Type:%d \n",fps.type); printf("Capture->capability:%d \n",fps.parm.capture.capability); printf("Capture->capturemode:%d \n",fps.parm.capture.capturemode); //1为高清模式 printf("Capture->captureparm->timeperframe->numerator:%d \n",\ fps.parm.capture.timeperframe.numerator); //默认为1 printf("Capture->captureparm->timeperframe->denominator:%d \n",\ fps.parm.capture.timeperframe.denominator); //帧率 printf("Capture->extendedmode:%d \n",fps.parm.capture.extendedmode); printf("Capture->readbuffer:%d \n",fps.parm.capture.readbuffers); //printf("Capture->outputparm->\n",fps.parm.output.); } /** * @brief query_fmt * 使用VIDIOC_ENUM_FMT查询摄像头信息 * @param fd */ void query_fmt(int fd) { Fmtd fmtd; CLEAR(fmtd); fmtd.index = 0; fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; printf("================Fmtd Info================\n"); while(-1 != ioctl(fd,VIDIOC_ENUM_FMT,&fmtd)) { printf("query fmtd %d %s\n",fmtd.index,fmtd.description); fmtd.index++; } } /** * @file webcam.h * @brief 摄像头配置及操作接口文件 */ #include "webcam.h" //#include "print.h" #include "huffman.h" #include "yuv2jpeg.h" #define HEADERFREAM 0xaf /** * @brief cam_open * 打开摄像头 * @param argc * @param argv * @return 文件描述符 */ int cam_open(int argc,char *argv[]) { if(argc < 2) { fprintf(stderr,"Usage:webcam[/dev/videoX]:%s\error\n",strerror(errno)); exit(EXIT_FAILURE); } int fd = open(argv[1],O_RDWR,0); if(fd < 0) { fprintf(stderr,"can't open %s:%s \n",argv[1],strerror(errno)); } return fd; } /** * @brief cam_close * 关闭摄像头、释放用户缓冲空间 * @param fd */ void cam_close(int fd) { close(fd); fd = -1; cam_free(); } /** * @brief set_fmt 设置视频的帧格式 * 根据摄像头像素参数设置Logitech C210:640*480 * @param conf */ static void set_fmt(Cam_conf *conf) { Fmt cam_fmt; //视屏格式结构提 CLEAR(cam_fmt); //清零 cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //默认设置为次宏定义 cam_fmt.fmt.pix.width = conf->width; //设置宽度 cam_fmt.fmt.pix.height = conf->height; //设置高度 cam_fmt.fmt.pix.pixelformat = conf->fmt; //设置图片格式 cam_fmt.fmt.pix.field = V4L2_FIELD_ANY; //设置采集field域,默认值 if(ioctl(conf->cam_fd,VIDIOC_S_FMT,&cam_fmt) == -1) //写入 { fprintf(stderr,"Usage:set_fmt:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } } /** * @brief set_frame_size * 设置图片大小 * @param conf * @return */ static int set_frame_size(Cam_conf *conf) { if(conf == NULL) //判断是否为空 { return -1; } tmp_buf.len = WIDTH*HEIGHT<<1;//tmp_buf为全局函数 switch(conf->fmt) { case MJPEG: tmp_buf.start = (unsigned char *)calloc(1,tmp_buf.len); //return 0; break; case YUYV: tmp_buf.start = (unsigned char *)calloc(1,tmp_buf.len*3/2); //return 0; break; } return tmp_buf.len; } /** * @brief set_fps * 设置帧率,C210 最大帧率30fps * @param fd * @param fps */ static void set_fps(Cam_conf *conf ) { Fps fps; CLEAR(fps); fps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //默认设置为次宏定义 fps.parm.capture.capturemode = 1; //高清模式 fps.parm.capture.timeperframe.numerator = 1; // fps.parm.capture.timeperframe.denominator = conf->fps; //帧率 if(ioctl(conf->cam_fd,VIDIOC_S_PARM,&fps) ==-1) { fprintf(stderr,"Usage:set_fps:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } } /** * @brief init_mmap * 在内核空间申请帧缓冲、映射到用户空间、并入缓冲队列 * @param fd * @param num */ static void init_mmap(Cam_conf *conf ) { CLEAR(reqbuf); //reqbuf为全局 reqbuf.count = conf->n_buf; reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP; if(ioctl(conf->cam_fd,VIDIOC_REQBUFS,&reqbuf) == -1) //申请缓存 { fprintf(stderr,"Usage:Init_mmap--VIDIOC_DQBUF:%serror\n",strerror(errno)); exit(EXIT_FAILURE); } if(reqbuf.count < 5) { fprintf(stderr,"Usage:reqbuf.count <5:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } //申请地址 bufs = calloc(reqbuf.count,sizeof(Buf)); //申请内存 assert(bufs != NULL); //入队列 unsigned int i = 0; for(i = 0;i< reqbuf.count;i++) { CLEAR(vbuf); //vbuf为全局变量 vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vbuf.memory = V4L2_MEMORY_MMAP; vbuf.index = i; if(ioctl(conf->cam_fd,VIDIOC_QUERYBUF,&vbuf) == -1) //查询,返回内存索引号,获取物理地址 { fprintf(stderr,"Usage:VIDIOC_QUERYBUF:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } bufs[i].len = vbuf.length; bufs[i].start = mmap(NULL, //strat anywhere 内核自动分配 vbuf.length, //由vbuf查询返回 PROT_READ|PROT_WRITE, //required MAP_SHARED, //recommended conf->cam_fd, vbuf.m.offset); //vbuf查询返回,VIDIOC_QUERYBUF if(bufs[i].start == MAP_FAILED) { fprintf(stderr,"Usage:mmap:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } if(ioctl(conf->cam_fd,VIDIOC_QBUF,&vbuf) == -1) //入队 { fprintf(stderr,"Usage:Init_mmap--VIDIOC_QBUF :%s error\n",strerror(errno)); exit(EXIT_FAILURE); } } } /** * @brief cam_free 释放用户缓冲空间 */ static void cam_free(void) { unsigned int i = 0; for(i = 0;i < reqbuf.count;i++) { if(munmap(bufs[i].start,bufs[i].len) == -1) { fprintf(stderr,"Usage:munmap:%s\n",strerror(errno)); exit(EXIT_FAILURE); } } //free(bufs); } /** * @brief start_capturing * 开启视频流 * @param fd */ void start_capturing(int fd) { int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(fd,VIDIOC_STREAMON,&type) == -1) { fprintf(stderr,"Usage:VIDIOC_STREAMON:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } } /** * @brief stop_capturing * 关闭视频流 * @param fd */ void stop_capturing(int fd) { int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(fd,VIDIOC_STREAMOFF,&type) == -1) { fprintf(stderr,"Usage:VIDIOC_STREAMOFF:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } } /** * @brief get_frame * 出队一帧数据,拷贝到temp_buf,并入队 * @param conf * @return 成功0,失败-1 */ int get_frame(Cam_conf *conf) { //Vbuf vbuf; CLEAR(vbuf); //一帧图片空间 vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vbuf.memory = V4L2_MEMORY_MMAP; //vbuf.index = 0; if(ioctl(conf->cam_fd,VIDIOC_DQBUF,&vbuf) == -1) //取一帧 { fprintf(stderr,"Usage:Get_frame--VIDIOC_DQBUF:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } if(vbuf.bytesused<=HEADERFREAM) { return -1; } switch(conf->fmt) { case MJPEG: memcpy(tmp_buf.start,bufs[vbuf.index].start,vbuf.bytesused); break; case YUYV: // yuv_to_jpeg(bufs[vbuf.index].start,tmp_buf.start,vbuf.bytesused,62); break; default: printf("Unknown Format.\n"); break; } if(ioctl(conf->cam_fd,VIDIOC_QBUF,&vbuf) < 0) { fprintf(stderr,"Usage:Get_frame---VIDIOC_QBUF:%s error\n",strerror(errno)); free(tmp_buf.start); return -1; } return 0; } /** * @brief process * 保存一帧图片到本地目录 * @param conf */ void process(Cam_conf *conf) { int pic_fd = open("webcam.jpeg",O_RDWR|O_CREAT|O_TRUNC,S_IRWXU|S_IRWXG|S_IRWXO); if(pic_fd < 0) { fprintf(stderr,"Usage:pic_fd open:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } get_frame(conf); print_picture(pic_fd,tmp_buf.start,tmp_buf.len); free(tmp_buf.start); close(pic_fd); } /** * @brief cam_init * 查询并设置设备属性、格式、帧率,内存映射设置 * @param fd 摄像头 */ void cam_init(Cam_conf *conf) { set_fmt(conf); set_frame_size(conf); set_fps(conf); init_mmap(conf); } /** * @brief is_huffman * 判断图片是否为huffman编码 * @param buf * @return */ static int is_huffman(unsigned char *buf) { unsigned char *ptbuf; int i = 0; ptbuf = buf; while (((ptbuf[0] << 8) | ptbuf[1]) != 0xffda) { if (i++ > 2048) return 0; if (((ptbuf[0] << 8) | ptbuf[1]) == 0xffc4) return 1; ptbuf++; } return 0; } /** * @brief print_picture * 将图片输出到fd文件中 * @param fd * @param buf * @param size * @return */ int print_picture(int fd, unsigned char *buf, int size) { unsigned char *ptdeb, *ptcur = buf; int sizein; if (!is_huffman(buf)) { ptdeb = ptcur = buf; while (((ptcur[0] << 8) | ptcur[1]) != 0xffc0) ptcur++; sizein = ptcur - ptdeb; if( write(fd, buf, sizein) <= 0) return -1; if( write(fd, dht_data, DHT_SIZE) <= 0) return -1; if( write(fd, ptcur, size - sizein) <= 0) return -1; } else { if( write(fd, ptcur, size) <= 0) return -1; } return 0; } #include "print.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/types.h> #include <string.h> #include <fcntl.h> #include <wait.h> #include <time.h> #include <limits.h> #include "huffman.h" static int is_huffman(unsigned char *buf) { unsigned char *ptbuf; int i = 0; ptbuf = buf; while (((ptbuf[0] << 8) | ptbuf[1]) != 0xffda) { if (i++ > 2048) return 0; if (((ptbuf[0] << 8) | ptbuf[1]) == 0xffc4) return 1; ptbuf++; } return 0; } extern int print_picture(int fd, unsigned char *buf, int size) { unsigned char *ptdeb, *ptcur = buf; int sizein; if (!is_huffman(buf)) { ptdeb = ptcur = buf; while (((ptcur[0] << 8) | ptcur[1]) != 0xffc0) ptcur++; sizein = ptcur - ptdeb; if( write(fd, buf, sizein) <= 0) return -1; if( write(fd, dht_data, DHT_SIZE) <= 0) return -1; if( write(fd, ptcur, size - sizein) <= 0) return -1; } else { if( write(fd, ptcur, size) <= 0) return -1; } return 0; } /* * This file has implements changing YUYV format to * JPEG format */ #include "webcam.h" #include "yuv2jpeg.h" #include <jpeglib.h> #define OUTPUT_BUF_SIZE 4096 METHODDEF(void) init_dst(j_compress_ptr); METHODDEF(boolean) empty_out_buf(j_compress_ptr); METHODDEF(void) term_dst(j_compress_ptr); METHODDEF(void) init_dst(j_compress_ptr cinfo){ jpg_ptr dest = (jpg_ptr)cinfo->dest; dest->buffer = (JOCTET *)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_IMAGE, OUTPUT_BUF_SIZE * sizeof(JOCTET)); *(dest->written) = 0; dest->jpg_p.next_output_byte = dest->buffer; dest->jpg_p.free_in_buffer = OUTPUT_BUF_SIZE; } METHODDEF(boolean) empty_out_buf(j_compress_ptr cinfo){ jpg_ptr dest = (jpg_ptr)cinfo->dest; memcpy(dest->out_buf_cur, dest->buffer, OUTPUT_BUF_SIZE); dest->out_buf_cur += OUTPUT_BUF_SIZE; *(dest->written) += OUTPUT_BUF_SIZE; dest->jpg_p.next_output_byte = dest->buffer; dest->jpg_p.free_in_buffer = OUTPUT_BUF_SIZE; return TRUE; } METHODDEF(void) term_dst(j_compress_ptr cinfo){ jpg_ptr dest = (jpg_ptr)cinfo->dest; size_t datacount = OUTPUT_BUF_SIZE - dest->jpg_p.free_in_buffer; /* Write any data remaining in the buffer */ memcpy(dest->out_buf_cur, dest->buffer, datacount); dest->out_buf_cur += datacount; *(dest->written) += datacount; } void dst_buffer(j_compress_ptr cinfo, unsigned char *buffer, int size, int *written){ jpg_ptr dest; if (cinfo->dest == NULL) { cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr) cinfo, \ JPOOL_PERMANENT, sizeof(jpg_dst)); } dest = (jpg_ptr)cinfo->dest; dest->jpg_p.init_destination = init_dst; dest->jpg_p.empty_output_buffer = empty_out_buf; dest->jpg_p.term_destination = term_dst; dest->out_buf = buffer; dest->out_buf_size = size; dest->out_buf_cur = buffer; dest->written = written; } //摄像头采集帧图像的YUYV格式转换为JPEG格式 int yuv_to_jpeg(unsigned char *buf, unsigned char *buffer, int size, int quality){ struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; JSAMPROW row_pointer[1]; unsigned char *line_buffer, *yuyv; int z; static int written; //int count = 0; //printf("%s\n", buf); line_buffer = calloc(WIDTH * 3, 1); yuyv = buf;//将YUYV格式的图片数据赋给YUYV指针 cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); /* jpeg_stdio_dest (&cinfo, file); */ dst_buffer(&cinfo, buffer, size, &written); cinfo.image_width = WIDTH; cinfo.image_height = HEIGHT; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); jpeg_start_compress(&cinfo, TRUE); z = 0; while(cinfo.next_scanline < HEIGHT){ int x; unsigned char *ptr = line_buffer; for(x = 0; x < WIDTH; x++){ int r, g, b; int y, u, v; if(!z) y = yuyv[0] << 8; else y = yuyv[2] << 8; u = yuyv[1] - 128; v = yuyv[3] - 128; r = (y + (359 * v)) >> 8; g = (y - (88 * u) - (183 * v)) >> 8; b = (y + (454 * u)) >> 8; *(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r); *(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g); *(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b); if(z++) { z = 0; yuyv += 4; } } row_pointer[0] = line_buffer; jpeg_write_scanlines(&cinfo, row_pointer, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); free(line_buffer); return (written); } |
实现了基本的摄像头这边的硬件初始化配置之后,接下来就是网络这一块,这里需要注意的一是:在我们建立服务器的时候,大概有以下几个步骤:
1、构架socket结构体
2、构件表达服务器的地址结构体
3、像地址结构体设置数据,初始化
4、绑定地址结构体和socket描述符
5、监听客户端的链接
6、准备接收客户端的信息服务
7、启动服务线程,回应请求数据服务,分离线程属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
/** * @file sock.h * @brief webserver接口【视频传输模块及http协议处理】 * 实现将视频流发送到浏览器上,服务器一次处理一个客户端请求 * 1、先建立浏览器和本服务器的socket通信 * 2、增加http协议,实现发送视频流/图片 */ #include "common.h" typedef struct sockaddr SA; typedef struct sockaddr_in SA_in; #define LISTENQ 1024 /* second argument to listen() */ #define MAX 1024 // 存放接收客户机浏览器请求后的回应:Http head 报文头 static char HEADER[] = "HTTP/1.1 200 OK\r\nConnection: close\r\nCache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\nPragma: no-cache\r\nContent-type: multipart/x-mixed-replace;boundary=www.10086ng.com\r\n\r\n"; SA_in saddr; SA caddr; int socketfd; /** * @brief open_listenfd * 创建并返回监听socket描述付,setsockopt设置IP可重用 * @param port 端口号 * @return 监听socket描述付 */ extern int Open_listenfd(int port); /** * @brief Accept * 等待客户端的链接请求, * @param listenfd 监听socket描述付 * @param addr 客户端地址指针 * @param addrlen 客户端地址长度 * @return 链接socket描述付 */ extern int Accept(int listenfd, SA *addr, socklen_t *addrlen); /** * @brief send_picture * 响应浏览器请求,发送照片或者视频流 * 1、接受浏览器URL信息或客户端信息 * 2、向浏览器响应http 报文头信息 * 3、浏览器请求一张图片 * 4、浏览器请求视频 * @param confd 链接描述付 * @param conf 摄像头配置指针 */ extern void send_picture(int confd, Cam_conf* conf); /** * @brief Client_Info * 打印客户端的IP地址和域名 * @param client_addr */ extern void Client_Info(SA_in *client_addr); /** * @file sock.h * @brief webserver接口【视频传输模块及http协议处理】 * 实现将视频流发送到浏览器上,服务器一次处理一个客户端请求 * 1、先建立浏览器和本服务器的socket通信 * 2、增加http协议,实现发送视频流/图片 */ //typedef struct sockaddr SA; //typedef struct sockaddr_in SA_in; //#define LISTENQ 1024 /* second argument to listen() */ //#define MAX 1024 // 存放接收客户机浏览器请求后的回应:Http head 报文头 //static char HEADER[MAX] = "HTTP/1.1 200 OK\r\nConnection: close\r\nServer: Webcam V0.0\r\nCache-Control: no-store, no-cache,must-revalidate, pre-check=0, post-check=0, max-age=0\r\nPragma:no-cache\r\nContent-type: multipart/x-mixed-replace;boundary=www.10086ng.com\r\n\r\n"; #include "common.h" #include "socket.h" //int socketfd; /** * @brief open_listenfd * 创建并返回监听socket描述符,setsockopt设置IP可重用 * @param port 端口号 * @return 监听socket描述付 */ int Open_listenfd(int port) { socketfd = socket(AF_INET,SOCK_STREAM,0); if(socketfd < 0) { fprintf(stderr,"socketfd create:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } //SA_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(port); saddr.sin_addr.s_addr = INADDR_ANY; bzero(&saddr.sin_zero,8); if(bind(socketfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0) { fprintf(stderr,"Usage:socketfd bind:%s error\n",strerror(errno)); close(socketfd); exit(EXIT_FAILURE); } if(listen(socketfd,10) < 0) { fprintf(stderr,"Usage:listen:%s error\n",strerror(errno)); exit(EXIT_FAILURE); } printf("NOW SART LISTENING!:%d\n",socketfd); return socketfd; } void *do_service(void *arg) { int confd = (int)arg; print_picture(confd,tmp_buf.start,tmp_buf.len); //send_picture(confd,&conf); } /** * @brief Accept * 等待客户端的链接请求,创建线程 * @param listenfd 监听socket描述付 * @param addr 客户端地址指针 * @param addrlen 客户端地址长度 * @return 链接socket描述付 */ int Accept(int listenfd, SA *addr, socklen_t *addrlen) { int confd; printf("accept!!!\n"); confd = accept(listenfd,addr,addrlen); printf("accept!!!\n"); if(confd < 0) { fprintf(stderr,"Usage:accept:%s error\n",strerror(errno)); close(listenfd); exit(EXIT_FAILURE); } return confd; } /** * @brief send_picture * 响应浏览器请求,发送照片或者视频流 * 1、接受浏览器URL信息或客户端信息 * 2、向浏览器响应http 报文头信息 * 3、浏览器请求一张图片 * 4、浏览器请求视频 * @param confd 链接描述付 * @param conf 摄像头配置指针 */ void send_picture(int confd, Cam_conf* conf) { char buf[1024] = {'\0'}; read(confd,buf,sizeof(buf)); printf("===========\n"); write(1,buf,sizeof(buf)); printf("in client confd =%d\n",confd); write(confd,HEADER,sizeof(HEADER)-1); printf("in client confd =%d\n",confd); printf("=========================================\n"); while(1) { memset(buf,0,sizeof(buf)); sprintf(buf,"--www.10086ng.com\nContent-type: image/jpeg\nContent-Length: %d\n\n",tmp_buf.len+432); write(confd,buf,strlen(buf)); // memcpy(&tmp_buf,buf,tmp_buf.len); //printf("================%s\n",buf); //printf("confd:%d\n",confd); //printf("%s\n",tmp_buf); usleep(100*1000); print_picture(confd,tmp_buf.start,tmp_buf.len); //print_picture(confd,buf,sizeof(buf)); memset(buf,0,sizeof(buf)); sprintf(buf,"\r\n--"); int ok = (write(confd,buf,strlen(buf)) >= 0)?1:0; printf("%d\n",ok); usleep(100*1000); } printf("write picture finished!\n"); pthread_exit((void *)0); /* while(1) { print_picture(confd,tmp_buf.start,tmp_buf.len); printf("while in send picture!\n"); usleep(100*1000); } */ } /** * @brief Client_Info * 打印客户端的IP地址和域名 * @param client_addr */ void Client_Info(SA_in *client_addr) { } |
当写完以上这一块之后,便可以实现基本的服务器这边的架构了,这时我们可以通过浏览器来访问我们的服务器,通过http协议写我们的视屏流到我们的浏览器中,也就是基本的视频已经出来了。
很乱,看个大概就好,实在看不下去的就请拍砖。