static void add_client(struct server *s, int sock, struct sockaddr_storage *addr, struct frame_buffers *fbs) { struct client *c; char cbuf[INET6_ADDRSTRLEN]; // general purpose buffer for various string conversions in this function c = malloc(sizeof(struct client)); memset(c, 0, sizeof(struct client)); c->sock = sock; memcpy(&c->addr, addr, sizeof(struct sockaddr_storage)); c->last_communication = gettime(); c->current_frame = 0; c->current_frame_pos = 0; c->request_header_size = 0; c->request = REQUEST_INCOMPLETE; c->resp = NULL; c->resp_pos = 0; c->resp_len = 0; c->fb = NULL; c->static_file = NULL; if (s->ssl_ctx != NULL) { c->ssl = SSL_new(s->ssl_ctx); SSL_set_fd(c->ssl, c->sock); if (SSL_accept(c->ssl) < 1) { log_itf(LOG_ERROR, "Error occured doing the SSL handshake: %s", ERR_error_string(ERR_get_error(), NULL)); free(c); return; } } s->clients[c->sock] = c; log_itf(LOG_INFO, "Client connected from %s.", ntop(&c->addr, cbuf, sizeof(cbuf))); }
size_t capture_frame(struct video_device *vd) { memset(&vd->buf, 0, sizeof(struct v4l2_buffer)); vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vd->buf.memory = V4L2_MEMORY_MMAP; if (xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf) < 0) { log_itf(LOG_ERROR, "Unable to dequeue buffer on device %s.", vd->device_filename); return -1; } switch(vd->format_in) { case V4L2_PIX_FMT_MJPEG: if (vd->buf.bytesused <= MIN_BYTES_USED) { // Prevent crash on empty image log_itf(LOG_WARNING, "Ignoring empty buffer on device %s.", vd->device_filename); return 0; } memcpy(vd->framebuffer, vd->mem[vd->buf.index], vd->buf.bytesused); break; case V4L2_PIX_FMT_YUYV: if (vd->buf.bytesused > vd->framebuffer_size) memcpy(vd->framebuffer, vd->mem[vd->buf.index], vd->framebuffer_size); else memcpy(vd->framebuffer, vd->mem[vd->buf.index], vd->buf.bytesused); break; default: return -1; break; } return vd->buf.bytesused; }
static int open_sock(const char *hostname, short port, int family, int socktype) { struct addrinfo hints, *res, *ressave; int n, sock; int sockoptval = 1; char cbuf[INET6_ADDRSTRLEN]; // general purpose buffer for various string conversions in this function memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = family; hints.ai_socktype = socktype; snprintf(cbuf, sizeof(cbuf), "%d", port); n = getaddrinfo(strlen(hostname) ? hostname : NULL, cbuf, &hints, &res); if (n < 0) { log_itf(LOG_DEBUG, "getaddrinfo error for hostname '%s': %s.", hostname, gai_strerror(n)); return -1; } ressave = res; // Try open socket with each address getaddrinfo returned, // until getting a valid listening socket. sock = -1; while (res) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sockoptval, sizeof(int)); if (sock >= 0) { if (bind(sock, res->ai_addr, res->ai_addrlen) == 0) { inet_ntop(res->ai_family, (void *) &((struct sockaddr_in6*) res->ai_addr)->sin6_addr, cbuf, sizeof(cbuf)); log_itf(LOG_INFO, "Listening on %s port %d.", cbuf, port); break; } close(sock); sock = -1; } res = res->ai_next; } if (sock >= 0) { if (listen(sock, MAX_SERVER_SOCKET_BACKLOG) < 0) { panic("listen() failed."); } } freeaddrinfo(ressave); return sock; }
static int video_enable(struct video_device *vd) { int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret; log_itf(LOG_INFO, "Starting capture on device %s.", vd->device_filename); ret = xioctl(vd->fd, VIDIOC_STREAMON, &type); if (ret < 0) { log_itf(LOG_ERROR, "Unable to start capture on device %s", vd->device_filename); return ret; } vd->streaming_state = STREAMING_ON; return 0; }
static int video_disable(struct video_device *vd, streaming_state disabledState) { int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret; log_itf(LOG_INFO, "Stopping capture on device %s.", vd->device_filename); ret = xioctl(vd->fd, VIDIOC_STREAMOFF, &type); if (ret != 0) { log_itf(LOG_ERROR, "Unable to stop capture on device %s.", vd->device_filename); return ret; } log_itf(LOG_INFO, "Stopping capture done on device %s.", vd->device_filename); vd->streaming_state = disabledState; return 0; }
void destroy_video_device(struct video_device *vd) { if (vd->streaming_state == STREAMING_ON) { video_disable(vd, STREAMING_OFF); } if (CLOSE_VIDEO(vd->fd) != 0) { log_itf(LOG_ERROR, "Failed to close device %s.", vd->device_filename); } free(vd->framebuffer); vd->framebuffer = NULL; free(vd->device_filename); vd->device_filename = NULL; free(vd->formats); vd->formats = NULL; vd->format_count = 0; free(vd->resolutions); vd->resolutions = NULL; vd->resolution_count = 0; free(vd); }
int requeue_device_buffer(struct video_device *vd) { if (xioctl(vd->fd, VIDIOC_QBUF, &vd->buf) < 0) { log_itf(LOG_ERROR, "Unable to requeue buffer on device %s.", vd->device_filename); return -1; } return 0; }
void* __wrap_realloc(void *ptr, size_t size) { void *tmp = __real_realloc(ptr, size); char error[512]; if (tmp == NULL) { strerror_r(errno, (char *) &error, sizeof(error)); log_itf(LOG_ERROR, "realloc() failed: (%d) %s", errno, error); exit(EXIT_FAILURE); } return tmp; }
char* __wrap_strdup(const char *s) { char *ptr = __real_strdup(s); char error[512]; if (ptr == NULL) { strerror_r(errno, (char *) &error, sizeof(error)); log_itf(LOG_ERROR, "strdup() failed: (%d) %s", errno, error); exit(EXIT_FAILURE); } return ptr; }
static int xioctl(int fd, int IOCTL_X, void *arg) { int ret = 0; int tries = IOCTL_RETRY; do { ret = IOCTL_VIDEO(fd, IOCTL_X, arg); } while(ret && tries-- && ((errno == EINTR) || (errno == EAGAIN) || (errno == ETIMEDOUT))); if (ret && tries <= 0) { log_itf(LOG_ERROR, "ioctl (%x) retried %i times - giving up: %s.", IOCTL_X, IOCTL_RETRY, strerror(errno)); user_panic("ioctl error."); } return (ret); }
struct server *create_server(char *host, unsigned short port, struct frame_buffers *fbs, char *static_root, char *auth, char *ssl_cert_file, char *ssl_key_file) { struct server *s = malloc(sizeof(struct server)); size_t stream_info_buf_size; memset(s->clients, 0, sizeof(s->clients)); s->sock6 = open_sock(host, port, AF_INET6, SOCK_STREAM); s->sock4 = open_sock(host, port, AF_INET, SOCK_STREAM); if (s->sock4 < 0 && s->sock6 < 0) { panic("Could not bind to socket."); } stream_info_buf_size = sizeof(HTTP_STREAM_INFO_TEMPLATE) + 64; // Should be enough room for the data s->stream_info = malloc(stream_info_buf_size); snprintf(s->stream_info, stream_info_buf_size, HTTP_STREAM_INFO_TEMPLATE, (int) fbs->count, fbs->buffers[0].vd->width, fbs->buffers[0].vd->height ); s->auth = NULL; if (strlen(auth)) { s->auth = base64_encode((unsigned char *) auth); } s->static_root = strdup(static_root); s->ssl_ctx = NULL; if (strlen(ssl_cert_file) && strlen(ssl_key_file)) { s->ssl_ctx = ssl_create_ctx(); ssl_load_certs(s->ssl_ctx, ssl_cert_file, ssl_key_file); } else if (strlen(auth)) { log_itf(LOG_WARNING, "You are password protecting your streams, but not using encryption.\nAnyone can see your password!"); } return s; }
static void remove_client(struct server *s, struct client *c) { char cbuf[INET6_ADDRSTRLEN]; log_itf(LOG_INFO, "Disconneting client from %s.", ntop(&c->addr, cbuf, sizeof(cbuf))); s->clients[c->sock] = NULL; close(c->sock); if (c->static_file != NULL) { fclose(c->static_file); } if (c->resp != NULL) { free(c->resp); } if (c->ssl) { SSL_free(c->ssl); } free(c); }
static void handle_request(struct server *s, struct client *c, struct frame_buffers *fbs) { int index; char cbuf[INET6_ADDRSTRLEN]; // general purpose buffer for various string conversions in this function char tmp_filename[PATH_MAX + 1], filename[PATH_MAX + 1]; // Need 2 of these for realpath() char resp_head[sizeof(HTTP_STATIC_FILE_HEADERS_TMPL) + 256]; struct http_request req; parse_request(c->request_headers, &req); if (req.protocol_version == NULL) { set_client_response(c, REQUEST_BAD, HTTP_BAD_REQUEST); return; } log_itf(LOG_INFO, "Client at %s requested %s %s%s%s.", ntop(&c->addr, cbuf, sizeof(cbuf)), req.method, req.path, *req.query_string != '\0' ? "?" : "", req.query_string ); if (strcmp(req.method, "GET") != 0 || strcmp(req.protocol, "HTTP") != 0) { return set_client_response(c, REQUEST_BAD, HTTP_BAD_REQUEST); } if (strncmp(req.path, "/img/", 5) != 0) { if (!check_http_auth(req.authorization, s->auth)) { return set_client_response(c, REQUEST_AUTH_REQUIRED, HTTP_AUTH_REQUIRED); } } // /stream/ if (strncmp(req.path, "/stream/", strlen("/stream/")) == 0) { if (strcmp(req.path, "/stream/info") == 0) { set_client_response(c, REQUEST_STREAM_INFO, s->stream_info); } else { // /stream/0, /stream/1, etc index = atoi(&req.path[strlen("/stream/")]); if (index < 0 || index >= fbs->count) { set_client_response(c, REQUEST_NOT_FOUND, HTTP_NOT_FOUND); } else { set_client_response(c, REQUEST_STREAM, STREAM_HEADER); c->fb = &fbs->buffers[index]; c->current_frame = c->fb->current_frame; c->current_frame_pos = 0; } } } else { if (!strlen(s->static_root)) { return set_client_response(c, REQUEST_NOT_FOUND, HTTP_NOT_FOUND); } // Serving static file strncpy(tmp_filename, s->static_root, PATH_MAX); strncat(tmp_filename, req.path, PATH_MAX); if (tmp_filename[strlen(tmp_filename) - 1] == '/') { strncat(tmp_filename, INDEX_FILE_NAME, PATH_MAX); } // This also checks if the file exists if (NULL == realpath(tmp_filename, filename) || strncmp(filename, s->static_root, strlen(s->static_root)) != 0) { return set_client_response(c, REQUEST_NOT_FOUND, HTTP_NOT_FOUND); } // Fill in response template snprintf(resp_head, sizeof(resp_head), HTTP_STATIC_FILE_HEADERS_TMPL, (long) file_size(filename), get_mime_type(filename) ); set_client_response(c, REQUEST_STATIC_FILE, resp_head); c->static_file = fopen(filename, "rb"); } }
int init_v4l2(struct video_device *vd) { int i; struct v4l2_streamparm setfps; if ((vd->fd = OPEN_VIDEO(vd->device_filename, O_RDWR)) == -1) { log_itf(LOG_ERROR, "Error opening V4L2 interface on %s. errno %d", vd->device_filename, errno); } memset(&vd->cap, 0, sizeof(struct v4l2_capability)); if (xioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap) < 0) { log_itf(LOG_ERROR, "Error opening device %s: unable to query device.", vd->device_filename); return -1; } if ((vd->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) { log_itf(LOG_ERROR, "Error opening device %s: video capture not supported.", vd->device_filename); return -1; } if (vd->use_streaming) { if (!(vd->cap.capabilities & V4L2_CAP_STREAMING)) { log_itf(LOG_ERROR, "%s does not support streaming I/O", vd->device_filename); return -1; } } else { if (!(vd->cap.capabilities & V4L2_CAP_READWRITE)) { log_itf(LOG_ERROR, "%s does not support read I/O", vd->device_filename); return -1; } } vd->streaming_state = STREAMING_OFF; // set format in memset(&vd->fmt, 0, sizeof(struct v4l2_format)); vd->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vd->fmt.fmt.pix.width = vd->width; vd->fmt.fmt.pix.height = vd->height; vd->fmt.fmt.pix.pixelformat = vd->format_in; vd->fmt.fmt.pix.field = V4L2_FIELD_ANY; if (xioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt) < 0) { log_itf(LOG_WARNING, "Unable to set format %d, res %dx%d, device %s. Trying fallback.", vd->format_in, vd->width, vd->height, vd->device_filename); // Try the fallback format vd->format_in = UVC_FALLBACK_FORMAT; vd->fmt.fmt.pix.pixelformat = vd->format_in; if (xioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt) < 0) { log_itf(LOG_ERROR, "Unable to set fallback format %d, res %dx%d, device %s.", vd->format_in, vd->width, vd->height, vd->device_filename); return -1; } } if ((vd->fmt.fmt.pix.width != vd->width) || (vd->fmt.fmt.pix.height != vd->height)) { log_itf(LOG_WARNING, "The format asked unavailable, so the width %d height %d on device %s.", vd->fmt.fmt.pix.width, vd->fmt.fmt.pix.height, vd->device_filename); vd->width = vd->fmt.fmt.pix.width; vd->height = vd->fmt.fmt.pix.height; // look the format is not part of the deal ??? if (vd->format_in != vd->fmt.fmt.pix.pixelformat) { if (vd->format_in == V4L2_PIX_FMT_MJPEG) { log_itf(LOG_ERROR, "The input device %s does not supports MJPEG mode.\nYou may also try the YUV mode, but it requires a much more CPU power.", vd->device_filename); return -1; } else if (vd->format_in == V4L2_PIX_FMT_YUYV) { log_itf(LOG_ERROR, "The input device %s does not supports YUV mode.", vd->device_filename); return -1; } } else { vd->format_in = vd->fmt.fmt.pix.pixelformat; } } // set framerate memset(&setfps, 0, sizeof(struct v4l2_streamparm)); setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; setfps.parm.capture.timeperframe.numerator = 1; setfps.parm.capture.timeperframe.denominator = vd->fps; if (xioctl(vd->fd, VIDIOC_S_PARM, &setfps) < 0) { log_itf(LOG_ERROR, "Unable to set FPS to %d on device %s.", vd->fps, vd->device_filename); return -1; } // request buffers memset(&vd->rb, 0, sizeof(struct v4l2_requestbuffers)); vd->rb.count = NB_BUFFER; vd->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vd->rb.memory = V4L2_MEMORY_MMAP; if (xioctl(vd->fd, VIDIOC_REQBUFS, &vd->rb) < 0) { log_itf(LOG_ERROR, "Unable to allocate buffers for device %s.", vd->device_filename); return -1; } // map the buffers for(i = 0; i < NB_BUFFER; i++) { memset(&vd->buf, 0, sizeof(struct v4l2_buffer)); vd->buf.index = i; vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vd->buf.memory = V4L2_MEMORY_MMAP; if (xioctl(vd->fd, VIDIOC_QUERYBUF, &vd->buf)) { log_itf(LOG_ERROR, "Unable to query buffer on device %s.", vd->device_filename); return -1; } vd->mem[i] = mmap(0 /* start anywhere */ , vd->buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, vd->fd, vd->buf.m.offset); if (vd->mem[i] == MAP_FAILED) { log_itf(LOG_ERROR, "Unable to map buffer on device %s.", vd->device_filename); return -1; } DBG("Buffer mapped at address %p for device %s.", vd->mem[i], vd->device_filename); } // Queue the buffers. for(i = 0; i < NB_BUFFER; ++i) { memset(&vd->buf, 0, sizeof(struct v4l2_buffer)); vd->buf.index = i; vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vd->buf.memory = V4L2_MEMORY_MMAP; if (xioctl(vd->fd, VIDIOC_QBUF, &vd->buf) < 0) { log_itf(LOG_ERROR, "Unable to query buffer on device %s.", vd->device_filename); return -1; } } if (video_enable(vd)) { log_itf(LOG_ERROR, "Unable to enable video for device %s.", vd->device_filename); return -1; } return 0; }