如果使用如下指令启动的mjpg_streamer

./mjpg_streamer -o "output_http.so -w ./www" -i "input_s3c2410.so -d /dev/camera"  

则在mjpg_streamer.c中的两条指令

  for (i=0; i<global.outcnt; i++) {//只指定了一个-o,global.outcnt = 1global.out[i].init(&global.out[i].param);global.out[i].run(global.out[i].param.id);}

分别是执行output_http.c中的

output_init(output_parameter *param)// param.parameter_string="-w ./www"
output_run(int id) //id=0

搜索"见下面"取得线索。
***********************************************************init***************************************************************************
在output_http.c里,output_init源码如下

int output_init(output_parameter *param) {char *argv[MAX_ARGUMENTS]={NULL};int  argc=1, i;int  port;char *credentials, *www_folder;char nocommands;DBG("output #%02d\n", param->id);port = htons(8080);credentials = NULL;www_folder = NULL;nocommands = 0;/* convert the single parameter-string to an array of strings */argv[0] = OUTPUT_PLUGIN_NAME;if ( param->parameter_string != NULL && strlen(param->parameter_string) != 0 ) {char *arg=NULL, *saveptr=NULL, *token=NULL;arg=(char *)strdup(param->parameter_string);if ( strchr(arg, ' ') != NULL ) {token=strtok_r(arg, " ", &saveptr);if ( token != NULL ) {argv[argc] = strdup(token);argc++;while ( (token=strtok_r(NULL, " ", &saveptr)) != NULL ) {argv[argc] = strdup(token);argc++;if (argc >= MAX_ARGUMENTS) {OPRINT("ERROR: too many arguments to output plugin\n");return 1;}}}}}/* show all parameters for DBG purposes */for (i=0; i<argc; i++) {DBG("argv[%d]=%s\n", i, argv[i]);}reset_getopt();while(1) {int option_index = 0, c=0;static struct option long_options[] = \{{"h", no_argument, 0, 0},{"help", no_argument, 0, 0},{"p", required_argument, 0, 0},{"port", required_argument, 0, 0},{"c", required_argument, 0, 0},{"credentials", required_argument, 0, 0},{"w", required_argument, 0, 0},{"www", required_argument, 0, 0},{"n", no_argument, 0, 0},{"nocommands", no_argument, 0, 0},{0, 0, 0, 0}};c = getopt_long_only(argc, argv, "", long_options, &option_index);/* no more options to parse */if (c == -1) break;/* unrecognized option */if (c == '?'){help();return 1;}switch (option_index) {/* h, help */case 0:case 1:DBG("case 0,1\n");help();return 1;break;/* p, port */case 2:case 3:DBG("case 2,3\n");port = htons(atoi(optarg));break;/* c, credentials */case 4:case 5:DBG("case 4,5\n");credentials = strdup(optarg);break;/* w, www */case 6:case 7:DBG("case 6,7\n");www_folder = malloc(strlen(optarg)+2);strcpy(www_folder, optarg);if ( optarg[strlen(optarg)-1] != '/' )strcat(www_folder, "/");break;/* n, nocommands */case 8:case 9:DBG("case 8,9\n");nocommands = 1;break;}}

从此也可看出-o可以接受什么参数,一般要指定-p 8080(默认),-w /www

***********************************************************run***************************************************************************
在output_http.c里,output_run源码如下

int output_run(int id) {DBG("launching server thread #%02d\n", id);/* create thread and pass context to thread function */pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));//见下面pthread_detach(servers[id].threadID);return 0;
}

由于在mjpg_streamer.c中是根据-o的数量使用for循环调用的output_run(),所以有几个-o就会创建几个服务线程,每个服务线程对应一个线程上下文servers[id],id是线程的序号(即-o的序号)
pthread_create的
参数1.servers[id].threadID.第id个线程对应的线程号
参数4.servers[id] 第id个线程的上下文参数,成员如下

/* context of each server thread */
typedef struct {int sd[MAX_SD_LEN];int sd_len;int id;globals *pglobal;pthread_t threadID;config conf;
} context;//httpd.h
#define MAX_OUTPUT_PLUGINS 10//mjpg-streamer.h 可知最多支持10个 -o
context servers[MAX_OUTPUT_PLUGINS];//output_http.c

然后进入线程函数

/******************************************************************************
Description.: Open a TCP socket and wait for clients to connect. If clientsconnect, start a new thread for each accepted connection.
Input Value.: arg is a pointer to the globals struct
Return Value: always NULL, will only return on exit
******************************************************************************/
void *server_thread( void *arg ) {int on;pthread_t client;struct addrinfo *aip, *aip2;struct addrinfo hints;struct sockaddr_storage client_addr;socklen_t addr_len = sizeof(struct sockaddr_storage);fd_set selectfds;int max_fds = 0;char name[NI_MAXHOST];int err;int i;context *pcontext = arg;pglobal = pcontext->pglobal;/* set cleanup handler to cleanup ressources */pthread_cleanup_push(server_cleanup, pcontext);bzero(&hints, sizeof(hints));hints.ai_family = PF_UNSPEC;hints.ai_flags = AI_PASSIVE;hints.ai_socktype = SOCK_STREAM;//tcp
//为调用getaddrinfo()准备hintssnprintf(name, sizeof(name), "%d", ntohs(pcontext->conf.port));
//端口号 8080if((err = getaddrinfo(NULL, name, &hints, &aip)) != 0) {
//取得指定类型的socket address(addrinfo),以便后面的函数使用
//参数1 主机名或ip
//参数2 服务名或端口号
//参数3 指定需要返回的地址类型
//参数4 返回的第一个addrinfo结构体(通过遍历addrinfo结构体的链表得到所有符合条件的addrinfo)
//hints.ai_flags = AI_PASSIVE;和主机名设为NULL,则此函数会返回本机所有ip的addrinfo,包括回环地址127.0.0.1和本地地址如192.168.1.230
//refer to man getaddrinfo , http://blog.csdn.net/lgtnt/article/details/3745194perror(gai_strerror(err));exit(EXIT_FAILURE);}for(i = 0; i < MAX_SD_LEN; i++)pcontext->sd[i] = -1;
//httpd.c #define MAX_SD_LEN 50
//初始化所有的套接字描述符为-1/* open sockets for server (1 socket / address family) */i = 0;for(aip2 = aip; aip2 != NULL; aip2 = aip2->ai_next){
//遍历所有的套接字地址,为每一个地址创建一个套接字(服务器套接字)。最多可以建立MAX_SD_LEN个(50)--即最多支持本机的50个ip。但
//通过上面的getaddrinfo()返回的是两个socket地址(ip),一个是回环ip 127.0.0.1一个是本地ip比如192.168.1.230
//所以执行2次if((pcontext->sd[i] = socket(aip2->ai_family, aip2->ai_socktype, 0)) < 0) {
//创建socket,返回套接字描述符 pcontext->sd[i]
//每个-o 会创建2个(也是所有了)socket,即会监视本机的所有ip的8080端口continue;}/* ignore "socket already in use" errors */on = 1;if(setsockopt(pcontext->sd[i], SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {perror("setsockopt(SO_REUSEADDR) failed");}/* IPv6 socket should listen to IPv6 only, otherwise we will get "socket already in use" */on = 1;if(aip2->ai_family == AF_INET6 && setsockopt(pcontext->sd[i], IPPROTO_IPV6, IPV6_V6ONLY,(const void *)&on , sizeof(on)) < 0) {perror("setsockopt(IPV6_V6ONLY) failed");}/* perhaps we will use this keep-alive feature oneday *//* setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); */if(bind(pcontext->sd[i], aip2->ai_addr, aip2->ai_addrlen) < 0) {
//为上面创建的socket绑定地址perror("bind");pcontext->sd[i] = -1;continue;}if(listen(pcontext->sd[i], 10) < 0) {
//创建一个可以容纳2个请求者的监听队列perror("listen");pcontext->sd[i] = -1;} else {i++;if(i >= MAX_SD_LEN) {OPRINT("%s(): maximum number of server sockets exceeded", __FUNCTION__);i--;break;}}}pcontext->sd_len = i;if(pcontext->sd_len < 1) {OPRINT("%s(): bind(%d) failed", __FUNCTION__, htons(pcontext->conf.port));closelog();exit(EXIT_FAILURE);}/* create a child for every client that connects */while ( !pglobal->stop ) {//int *pfd = (int *)malloc(sizeof(int));cfd *pcfd = malloc(sizeof(cfd));if (pcfd == NULL) {fprintf(stderr, "failed to allocate (a very small amount of) memory\n");exit(EXIT_FAILURE);}DBG("waiting for clients to connect\n");do {FD_ZERO(&selectfds);for(i = 0; i < MAX_SD_LEN; i++) {if(pcontext->sd[i] != -1) {FD_SET(pcontext->sd[i], &selectfds);
//将上面创建的socket加入selectfds描述符集合if(pcontext->sd[i] > max_fds)max_fds = pcontext->sd[i];}}err = select(max_fds + 1, &selectfds, NULL, NULL, NULL);
//使用select监听文件文件描述符集合,没有动静就阻塞在这里。有动静继续执行。if (err < 0 && errno != EINTR) {perror("select");exit(EXIT_FAILURE);}} while(err <= 0);for(i = 0; i < max_fds + 1; i++) {
//遍历所有的服务器套接字描述符,以便确认是哪个套接字上有链接请求if(pcontext->sd[i] != -1 && FD_ISSET(pcontext->sd[i], &selectfds)) {pcfd->fd = accept(pcontext->sd[i], (struct sockaddr *)&client_addr, &addr_len);
//accept函数会自动创建一个新的套接字于这个客户端套接字通信,并且返回新套接字的文件描述符。原有的套接字继续执行监听。pcfd->pc = pcontext;
/*httpd.c
typedef struct {context *pc;int fd;
} cfd;
*//* start new thread that will handle this TCP connected client */DBG("create thread to handle client that just established a connection\n");if(getnameinfo((struct sockaddr *)&client_addr, addr_len, name, sizeof(name), NULL, 0, NI_NUMERICHOST) == 0) {syslog(LOG_INFO, "serving client: %s\n", name);}if( pthread_create(&client, NULL, &client_thread, pcfd) != 0 ) {//见下面
//为客户端创建服务线程
//参数4 pcfd
//pcfd->fd 套接字的文件描述符
//pcfd->pc 套接字上下文DBG("could not launch another client thread\n");close(pcfd->fd);free(pcfd);continue;}pthread_detach(client);}}}DBG("leaving server thread, calling cleanup function now\n");pthread_cleanup_pop(1);return NULL;
}

可以看出线程函数(对应一个-o的)server_thread里面是为每个addrinfo(最多50个)创建一个套接字,然后去监听。每当一个套接字上有客户端链接请求,就会再创建一个线程去传输数据。这里的套接字地址与beginning linux programming上讲的不太一样,在ipv6新加的吧。。。
./mjpg_streamer  -i "input_s3c2410.so -d /dev/camera"  -o "output_http.so -p 8080"  -o "output_http.so -p 8081"
这样就会创建2个线程,
一个线程里面会创建2个套接字,一个在侦听127.0.0.1:8080,一个在侦听192.168.1.230:8080
另一个线程也会创建2个套接字,一个在侦听127.0.0.1:8081,一个在侦听192.168.1.230:8081
然后在客户端的浏览器中同时访问如下两个网址
http://192.168.1.230:8080/?action=stream
http://192.168.1.230:8081/?action=stream
则服务器上监听192.168.1.230:8080和监听192.168.1.230:8081的socket就会accept()---
函数会自动创建一个新的套接字(和一个线程)与这个客户端套接字通信,并且返回新套接字的文件描述符。原有的套接字继续执行监听。所以之后再开多个浏览器去访问比如http://192.168.1.230:8080/?action=stream也可以访问得到数据。
以上是个人理解仅供参考
线程函数如下

/******************************************************************************
Description.: Serve a connected TCP-client. This thread function is calledfor each connect of a HTTP client like a webbrowser. It determinesif it is a valid HTTP request and dispatches between the differentresponse options.
Input Value.: arg is the filedescriptor and server-context of the connected TCPsocket. It must have been allocated so it is freeable by thisthread function.
Return Value: always NULL
******************************************************************************/
/* thread for clients that connected to this server */
void *client_thread( void *arg ) {int cnt;char buffer[BUFFER_SIZE]={0}, *pb=buffer;iobuffer iobuf;request req;cfd lcfd; /* local-connected-file-descriptor *//* we really need the fildescriptor and it must be freeable by us */if (arg != NULL) {memcpy(&lcfd, arg, sizeof(cfd));free(arg);}elsereturn NULL;/* initializes the structures */init_iobuffer(&iobuf);init_request(&req);/* What does the client want to receive? Read the request. */memset(buffer, 0, sizeof(buffer));if ( (cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer)-1, 5)) == -1 ) {
//从描述符(客户端)读取一行数据到bufferclose(lcfd.fd);return NULL;}/* determine what to deliver */if ( strstr(buffer, "GET /?action=snapshot") != NULL ) {req.type = A_SNAPSHOT;}else if ( strstr(buffer, "GET /?action=stream") != NULL ) {req.type = A_STREAM;
//比如浏览器中输入 http://192.168.1.230:8080/?action=stream}else if ( strstr(buffer, "GET /?action=command") != NULL ) {int len;req.type = A_COMMAND;/* advance by the length of known string */if ( (pb = strstr(buffer, "GET /?action=command")) == NULL ) {DBG("HTTP request seems to be malformed\n");send_error(lcfd.fd, 400, "Malformed HTTP request");close(lcfd.fd);return NULL;}pb += strlen("GET /?action=command");/* only accept certain characters */len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-=&1234567890%./"), 0), 100);req.parameter = malloc(len+1);if ( req.parameter == NULL ) {exit(EXIT_FAILURE);}memset(req.parameter, 0, len+1);strncpy(req.parameter, pb, len);if ( unescape(req.parameter) == -1 ) {free(req.parameter);send_error(lcfd.fd, 500, "could not properly unescape command parameter string");LOG("could not properly unescape command parameter string\n");close(lcfd.fd);return NULL;}DBG("command parameter (len: %d): \"%s\"\n", len, req.parameter);}else {int len;DBG("try to serve a file\n");req.type = A_FILE;if ( (pb = strstr(buffer, "GET /")) == NULL ) {DBG("HTTP request seems to be malformed\n");send_error(lcfd.fd, 400, "Malformed HTTP request");close(lcfd.fd);return NULL;}pb += strlen("GET /");len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-1234567890"), 0), 100);req.parameter = malloc(len+1);if ( req.parameter == NULL ) {exit(EXIT_FAILURE);}memset(req.parameter, 0, len+1);strncpy(req.parameter, pb, len);DBG("parameter (len: %d): \"%s\"\n", len, req.parameter);}/** parse the rest of the HTTP-request* the end of the request-header is marked by a single, empty line with "\r\n"*/do {memset(buffer, 0, sizeof(buffer));if ( (cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer)-1, 5)) == -1 ) {free_request(&req);close(lcfd.fd);return NULL;}if ( strstr(buffer, "User-Agent: ") != NULL ) {req.client = strdup(buffer+strlen("User-Agent: "));}else if ( strstr(buffer, "Authorization: Basic ") != NULL ) {req.credentials = strdup(buffer+strlen("Authorization: Basic "));decodeBase64(req.credentials);DBG("username:password: %s\n", req.credentials);}} while( cnt > 2 && !(buffer[0] == '\r' && buffer[1] == '\n') );/* check for username and password if parameter -c was given */if ( lcfd.pc->conf.credentials != NULL ) {if ( req.credentials == NULL || strcmp(lcfd.pc->conf.credentials, req.credentials) != 0 ) {DBG("access denied\n");send_error(lcfd.fd, 401, "username and password do not match to configuration");close(lcfd.fd);if ( req.parameter != NULL ) free(req.parameter);if ( req.client != NULL ) free(req.client);if ( req.credentials != NULL ) free(req.credentials);return NULL;}DBG("access granted\n");}/* now it's time to answer */switch ( req.type ) {case A_SNAPSHOT:DBG("Request for snapshot\n");send_snapshot(lcfd.fd);break;case A_STREAM:DBG("Request for stream\n");send_stream(lcfd.fd);//见下面break;case A_COMMAND:if ( lcfd.pc->conf.nocommands ) {send_error(lcfd.fd, 501, "this server is configured to not accept commands");break;}command(lcfd.pc->id, lcfd.fd, req.parameter);break;case A_FILE:if ( lcfd.pc->conf.www_folder == NULL )send_error(lcfd.fd, 501, "no www-folder configured");elsesend_file(lcfd.pc->id, lcfd.fd, req.parameter);break;default:DBG("unknown request\n");}close(lcfd.fd);free_request(&req);DBG("leaving HTTP client thread\n");return NULL;
}

下面是服务器响应客户端的?action=stream请求所发送的全部数据-----一个web服务器发送数据的实现
比如 http://192.168.1.230:8081/?action=stream
从这个函数可以看出,在运行程序时即使不使能www 路径也可以观看图像。因为它发送了完整的http标记。

/******************************************************************************
Description.: Send a complete HTTP response and a stream of JPG-frames.
Input Value.: fildescriptor fd to send the answer to
Return Value: -
******************************************************************************/
void send_stream(int fd) {unsigned char *frame=NULL, *tmp=NULL;int frame_size=0, max_frame_size=0;char buffer[BUFFER_SIZE] = {0};DBG("preparing header\n");sprintf(buffer, "HTTP/1.0 200 OK\r\n" \STD_HEADER \"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \"\r\n" \"--" BOUNDARY "\r\n");if ( write(fd, buffer, strlen(buffer)) < 0 ) {
//发送http头free(frame);return;}DBG("Headers send, sending stream now\n");while ( !pglobal->stop ) {
//只要没停止就一直发送图像,所以在浏览器中看到的是 视频/* wait for fresh frames */pthread_cond_wait(&pglobal->db_update, &pglobal->db);/* read buffer */frame_size = pglobal->size;/* check if framebuffer is large enough, increase it if necessary */if ( frame_size > max_frame_size ) {DBG("increasing buffer size to %d\n", frame_size);max_frame_size = frame_size+TEN_K;if ( (tmp = realloc(frame, max_frame_size)) == NULL ) {free(frame);pthread_mutex_unlock( &pglobal->db );send_error(fd, 500, "not enough memory");return;}frame = tmp;}memcpy(frame, pglobal->buf, frame_size);DBG("got frame (size: %d kB)\n", frame_size/1024);pthread_mutex_unlock( &pglobal->db );/** print the individual mimetype and the length* sending the content-length fixes random stream disruption observed* with firefox*/sprintf(buffer, "Content-Type: image/jpeg\r\n" \"Content-Length: %d\r\n" \"\r\n", frame_size);DBG("sending intemdiate header\n");if ( write(fd, buffer, strlen(buffer)) < 0 ) break;
//发送内容类型,内容大小DBG("sending frame\n");if( write(fd, frame, frame_size) < 0 ) break;
//发送内容--图像数据DBG("sending boundary\n");sprintf(buffer, "\r\n--" BOUNDARY "\r\n");if ( write(fd, buffer, strlen(buffer)) < 0 ) break;
//发送http尾}free(frame);
}

而如果客户端想要访问www下的文件,则在服务器端启动mjpg-streamer时需要指定www路径,发送文件的函数是send_file()
一个简单的web服务器的实现

/******************************************************************************
Description.: Send HTTP header and copy the content of a file. To keep thingssimple, just a single folder gets searched for the file. Justfiles with known extension and supported mimetype get served.If no parameter was given, the file "index.html" will be copied.
Input Value.: * fd.......: filedescriptor to send data to* parameter: string that consists of the filename* id.......: specifies which server-context is the right one
Return Value: -
******************************************************************************/
void send_file(int id, int fd, char *parameter) {char buffer[BUFFER_SIZE] = {0};char *extension, *mimetype=NULL;int i, lfd;config conf = servers[id].conf;/* in case no parameter was given */if ( parameter == NULL || strlen(parameter) == 0 )parameter = "index.html";/* find file-extension */if ( (extension = strstr(parameter, ".")) == NULL ) {send_error(fd, 400, "No file extension found");return;}/* determine mime-type */for ( i=0; i < LENGTH_OF(mimetypes); i++ ) {if ( strcmp(mimetypes[i].dot_extension, extension) == 0 ) {mimetype = (char *)mimetypes[i].mimetype;break;}}/* in case of unknown mimetype or extension leave */if ( mimetype == NULL ) {send_error(fd, 404, "MIME-TYPE not known");return;}/* now filename, mimetype and extension are known */DBG("trying to serve file \"%s\", extension: \"%s\" mime: \"%s\"\n", parameter, extension, mimetype);/* build the absolute path to the file */strncat(buffer, conf.www_folder, sizeof(buffer)-1);strncat(buffer, parameter, sizeof(buffer)-strlen(buffer)-1);/* try to open that file */if ( (lfd = open(buffer, O_RDONLY)) < 0 ) {DBG("file %s not accessible\n", buffer);send_error(fd, 404, "Could not open file");return;}DBG("opened file: %s\n", buffer);/* prepare HTTP header */sprintf(buffer, "HTTP/1.0 200 OK\r\n" \"Content-type: %s\r\n" \STD_HEADER \"\r\n", mimetype);
//发送的这些数据在浏览器中看不到的,是给浏览器一个提供的一个版本识别信息
//浏览器中可以观察到的后面真正的数据(比如index.html的内容)
//上面send_stream()发送图像流也是一样,浏览器中只呈现出图像i = strlen(buffer);/* first transmit HTTP-header, afterwards transmit content of file */do {if ( write(fd, buffer, i) < 0 ) {close(lfd);return;}} while ( (i=read(lfd, buffer, sizeof(buffer))) > 0 );/* close file, job done */close(lfd);
}

可以看到此函数会按照浏览器地址指定的文件在www目录寻找这个文件,然后发送出去。所以可以按照项目要求自己加一些网页进去就可以扩增功能啦
比如在板子上

[root@FriendlyARM www]# touch a.html
[root@FriendlyARM www]# echo hhheh > a.html

然后客户端访问
http://192.168.1.230:8080/a.html

同样可以想到,如果在www目录下放一个cgi文件,是否也可以访问呢?
不支持。看上面line 36 --line39,有识别的。如果注释掉那个return,则浏览到的是乱码。
boa是支持的,可以参考一下boa的源码,修改一下send_file()估计就可以了。
http详细部分见下文。

转载于:https://www.cnblogs.com/-song/archive/2011/11/27/3331922.html

---WebCam网络摄像头10 socket相关推荐

  1. ---WebCam网络摄像头7 cmos--yuv rgb , Format............:V4L2_PIX_FMT_YUYV

    颜色系统基本 refer to http://bbs.chinavideo.org/viewthread.php?tid=4143 常见的RGB格式有RGB1.RGB4.RGB8.RGB565.RGB ...

  2. ---WebCam网络摄像头6 编译WebCam

    直接使用天嵌提供的交叉编译器编译WebCam而生成的input_uvc.so output_http.souvc_stream放在micro2440下面可以直接使用--两个开发板几乎没什么不同... ...

  3. ---WebCam网络摄像头9 usb dirver

    Device Drivers  ---><*> Multimedia support  --->[*]   Video capture adapters  --->[*] ...

  4. ---WebCam网络摄像头11 http协议

    看一下httpd.c中关于http协议的部分 关于http协议的知识 refer to http://www.cnblogs.com/li0803/archive/2008/11/03/1324746 ...

  5. ---WebCam网络摄像头12 ---图像编码解码,视频编码解码

    1.图像格式与图像编码,图像显示 图像被拍摄后,一般都会按照某种编码方式被压缩,使得占用更少的空间来存放(或传输).然后再播放的时候又会使用想用的解码方式将图像还原成源图像(指显示器认可的图像格式,一 ...

  6. 网络协议 11 - Socket 编程(下):眼见为实耳听为虚

    网络协议 11 - Socket 编程(下):眼见为实耳听为虚 原文:网络协议 11 - Socket 编程(下):眼见为实耳听为虚 系列文章传送门: 网络协议 1 - 概述 网络协议 2 - IP ...

  7. html与摄像头怎么链接,HTML5将DSLR连接为网络摄像头(HTML5 connect DSLR as webcam)

    HTML5将DSLR连接为网络摄像头(HTML5 connect DSLR as webcam) 最近我在html5中创建了一个应用程序来连接网络摄像头并拍照. 是否有可能使用dslr / slr作为 ...

  8. 佳能清零软件万能版_Mac版Fujifilm X Webcam发布:用无反相机做网络摄像头

    >>>2020 苹果教育优惠来袭,购买就送 AirPods!点击进入苹果教育商店.(注意:若 App 端无法直接点开链接,请尝试长按) 富士(Fujifilm)今天发布了适用于 ma ...

  9. linux 命令行 webcamera,如何在Linux上运行网络摄像头(Run a Webcam on Linux)?

    在Linux上安装.配置和运行网络摄像头可以是一个相当简单的过程,也可以是一个相当复杂的过程.有许多步骤可以帮助顺利安装,每个网络摄像头和计算机设置都会有自己的怪癖和潜在的问题.用最简单的形式,要在L ...

最新文章

  1. spring源码解读之 JdbcTemplate源码
  2. ASP.NET Core 之 Identity 入门(二)
  3. java项目加减乘除验证码_课堂Java小程序(加减乘除与验证码)
  4. NB-IoT(9)---云平台对接及使用
  5. hostingEnvironment与宿主环境
  6. Gallery 3D+倒影 滑动切换图片示例(转)
  7. 怎样在photoshop中快速批量,修改图片
  8. pano2vr.exe下载
  9. cc.Layout代码设置裁剪
  10. 数据挖掘导论实验报告01
  11. 在VMware上安装Android虚拟机
  12. 如何利用seo技术霸屏你的行业关键词排名
  13. 哔哩哔哩(B站)暑期实习面经(已OC)
  14. JS EXCEL表的操作
  15. Android - 批量发送短信的实现方式
  16. 解决nexus 6p 无限重启的问题。nexus 6p 刷入twrp,magisk
  17. word中鼠标拖动文字突然无法突出显示
  18. Unknown database ‘ ‘
  19. Python 身体质量指数BMI
  20. 尘埃落定!清华才子王垠​加入华为职级22,前阿里P10赵海平加入字节跳动,职级或为4+...

热门文章

  1. 怒卸python3.4.1
  2. Zend Studio 高亮显示dwt和lbi
  3. 计算机常用英语(2)
  4. mysqldumper 与 Innobackupex的备份和恢复操作实验过程
  5. android AtomicBoolean类的使用
  6. Understand Tasks and Back Stack--Defining launch modes
  7. 分布式服务常见问题—分布式事务
  8. AOP—JVM SandBox—底层原理解析
  9. pandas之loc iloc ix
  10. Echart---多项柱状图-2D/H5