├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── client.c ├── client.h ├── constants.h ├── dump2file.c ├── libmjpeg2http.c ├── libmjpeg2http.h ├── list.h ├── main.c ├── makefile ├── protocol.h ├── server.c ├── server.h ├── test_mem.c ├── video.c └── video.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | mjpeg2http 3 | build/ 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # mjpeg2http 3 | # 4 | # Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | # follows: 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | 25 | cmake_minimum_required (VERSION 2.8.11) 26 | project (mjpeg2http C) 27 | 28 | add_library(libmjpeg2http STATIC 29 | libmjpeg2http.c 30 | server.c 31 | client.c 32 | video.c 33 | ) 34 | 35 | add_executable(mjpeg2http 36 | main.c 37 | ) 38 | 39 | target_link_libraries(mjpeg2http 40 | libmjpeg2http 41 | ) 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Antonino Nolano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mjpeg2http 2 | 3 | **A pure C Linux MJPEG over HTTP server** 4 |

5 | License: MIT 6 |

7 | 8 | 9 | ## Summary 10 | 11 | mjpeg2http is a minimalistic server primarily targeted to run on embedded computers, like routers, raspberry pi, with a Linux OS. 12 | 13 | It can be used to stream JPEG files over an IP-based network from a webcam to various types of viewers such as Google Chrome, Mozilla Firefox, VLC, mplayer, and other software capable of receiving MJPG streams. 14 | 15 | The implementation uses epoll on non-blocking file descriptors and is not thread-based. 16 | 17 | ## Build using make 18 | 19 | Install dependencies: 20 | ```bash 21 | $ sudo apt install build-essential libv4l-dev 22 | ``` 23 | 24 | Compile with: 25 | 26 | ```bash 27 | $ make 28 | ``` 29 | 30 | ## Build using cmake 31 | 32 | Compile with: 33 | 34 | ```bash 35 | $ mkdir build 36 | $ cmake .. 37 | $ make 38 | ``` 39 | 40 | ## Usage 41 | 42 | Run: 43 | 44 | ```bash 45 | $ ./mjpeg2http 192.168.2.1 8080 /dev/video0 my_secret_token 46 | 47 | ``` 48 | 49 | Open browser on http://192.168.2.1:8080/path?my_secret_token 50 | 51 | ## One time token 52 | 53 | Run: 54 | 55 | ```bash 56 | $ ./mjpeg2http 192.168.2.1 8080 /dev/video0 my_secret_token /tmp/mjpeg2http_token 57 | 58 | ``` 59 | Book access: 60 | 61 | ```bash 62 | $ echo "12345678901234567890" > /tmp/mjpeg2http_token 63 | 64 | ``` 65 | 66 | Open browser on http://192.168.2.1:8080/path?12345678901234567890 67 | 68 | The token will be valid exactly for one access after that it gets invalid 69 | 70 | ## Warning 71 | + mjpeg2http should be used in private network because it does not use TLS connections. If you would like to use it while on a public network it is highly recommended to use TLS, some ideas: 72 | - you can try [stunnel](https://www.stunnel.org/). 73 | - nginx or apache httpd placed in front of mjpeg2http with a reverse proxy. 74 | - for encryption between mjpeg2http and server it can be used ssh tunnels or wireguard. 75 | + token length must be exactly TOKEN_SIZE (see constants.h) 76 | 77 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "client.h" 33 | 34 | client_t *client_init(char *hostname, int port, int fd) { 35 | printf("new client %s %d fd=%d\n", hostname, port, fd); 36 | fflush(stdout); 37 | client_t *c = malloc(sizeof(client_t)); 38 | c->hostname = strdup(hostname); 39 | c->port = port; 40 | c->fd = fd; 41 | init_list_entry(&c->tx_queue); 42 | c->total_to_sent = c->start_token = c->end_token = c->rxbuf_pos = c->is_auth = 43 | c->txbuf_pos = 0; 44 | return c; 45 | } 46 | 47 | int client_parse_request(client_t *client) { 48 | if (client->start_token != 0) { 49 | // token already decoded 50 | return 1; 51 | } 52 | 53 | int r; 54 | while ((r = read(client->fd, client->rxbuf + client->rxbuf_pos, 55 | 1000 - client->rxbuf_pos)) > 0) 56 | client->rxbuf_pos += r; 57 | 58 | if (r < 0) { 59 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 60 | const char *end = strchr((const char *)client->rxbuf, '\n'); 61 | if (end != NULL) { 62 | // GET /whatever?myauthtoken HTTP/1.1 63 | // read enough bytes to get myauthtoken 64 | const char *sp = strchr((const char *)client->rxbuf, ' '); 65 | if (sp == NULL || end - sp < 2) 66 | return -1; 67 | const char *sq = strchr(sp + 1, '?'); 68 | if (sq == NULL || end - sq < 2) 69 | return -1; 70 | const char *eq = strchr(sq + 1, ' '); 71 | if (eq == NULL) 72 | return -1; 73 | client->start_token = sq + 1 - (const char *)client->rxbuf; 74 | client->end_token = eq - (const char *)client->rxbuf; 75 | return 1; 76 | } 77 | 78 | return 0; 79 | } 80 | printf("rxbuf %d error %s\n", client->fd, strerror(errno)); 81 | fflush(stdout); 82 | return -1; 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | void client_free(client_t *client) { 89 | printf("destroy client %s %d fd=%d\n", client->hostname, client->port, 90 | client->fd); 91 | fflush(stdout); 92 | struct dlist *itr, *save; 93 | message_t *msg; 94 | list_iterate_safe(itr, save, &client->tx_queue) { 95 | list_del(&list_get_entry(itr, message_t, node)->node); 96 | msg = list_get_entry(itr, message_t, node); 97 | if (--*(msg->payload + msg->size) == 0) 98 | free(msg->payload); 99 | free(msg); 100 | } 101 | 102 | close(client->fd); 103 | free(client->hostname); 104 | free(client); 105 | } 106 | 107 | int client_write_txbuf(client_t *client) { 108 | int txbuf_pos; 109 | while ((txbuf_pos = write(client->fd, client->txbuf + client->txbuf_pos, 110 | client->total_to_sent - client->txbuf_pos)) > 0) { 111 | // printf("raw: %.*s\n", txbuf_pos, client->txbuf + client->txbuf_pos); 112 | client->txbuf_pos += txbuf_pos; 113 | } 114 | 115 | if (client->txbuf_pos == client->total_to_sent) { 116 | // printf("frame size=%d sent %s:%d\n", client->total_to_sent, 117 | // client->hostname, client->port); 118 | client->total_to_sent = client->txbuf_pos = 0; 119 | } 120 | 121 | if (txbuf_pos >= 0) 122 | return 1; 123 | 124 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 125 | return 0; 126 | } 127 | 128 | printf("txbuf fd=%d error %s\n", client->fd, strerror(errno)); 129 | fflush(stdout); 130 | 131 | return -1; 132 | } 133 | 134 | int client_tx(client_t *client) { 135 | 136 | int r; 137 | 138 | do { 139 | r = 0; 140 | if (client->total_to_sent > 0) { 141 | r = client_write_txbuf(client); 142 | } else if (!list_empty(&client->tx_queue)) { 143 | // printf("move message from tx queue to tx buffer\n"); 144 | message_t *msg = 145 | list_get_entry(list_get_first(&client->tx_queue), message_t, node); 146 | list_del(&msg->node); 147 | memcpy(client->txbuf, msg->payload, msg->size); 148 | client->total_to_sent = msg->size; 149 | client->txbuf_pos = 0; 150 | if (--*(msg->payload + msg->size) == 0) 151 | free(msg->payload); 152 | free(msg); 153 | r = client_write_txbuf(client); 154 | } 155 | } while (r > 0); 156 | 157 | return r; 158 | } 159 | 160 | void client_enqueue_frame(client_t *client, uint8_t *payload, int size, 161 | uint8_t **allocated) { 162 | int tx_queue_size = 0; 163 | list_size(tx_queue_size, &client->tx_queue); 164 | // printf("enqueue message size=%d, still to sent=%d tx_queue_size=%d\n", 165 | // size, client->total_to_sent - client->txbuf_pos, tx_queue_size); 166 | 167 | if (tx_queue_size || client->total_to_sent) { 168 | if (tx_queue_size > TX_QUEUE_MAX || allocated == NULL) { 169 | printf("tx queue %s %d-> drop message because current size %d\n", 170 | client->hostname, client->port, tx_queue_size); 171 | fflush(stdout); 172 | return; 173 | } 174 | // printf("place message into queue\n"); 175 | message_t *msg = malloc(sizeof(message_t)); 176 | if (*allocated == NULL) { 177 | *allocated = malloc(size + 1); 178 | *(*allocated + size) = 0x02; 179 | memcpy(*allocated, payload, size); 180 | } else { 181 | ++*(*allocated + size); 182 | } 183 | msg->payload = *allocated; 184 | msg->size = size; 185 | init_list_entry(&msg->node); 186 | list_add_right(&msg->node, &client->tx_queue); 187 | client_tx(client); 188 | } else { 189 | int pos, sent = 0; 190 | while ((pos = write(client->fd, payload + sent, size - sent)) > 0) 191 | sent += pos; 192 | 193 | if (sent < size) { 194 | // printf("place remaining bytes %d of %d into buffer tx\n", size - 195 | // sent, size); 196 | memcpy(client->txbuf + sent, payload + sent, size - sent); 197 | client->total_to_sent = size; 198 | client->txbuf_pos = sent; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /client.h: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef CLIENT_H 27 | #define CLIENT_H 28 | 29 | #include 30 | 31 | #include "constants.h" 32 | #include "list.h" 33 | 34 | typedef struct { 35 | 36 | /* client data */ 37 | char *hostname; 38 | int port; 39 | int fd; 40 | 41 | /* tx buffer */ 42 | uint8_t txbuf[MAX_FRAME_SIZE]; 43 | uint32_t txbuf_pos; 44 | uint32_t total_to_sent; 45 | 46 | /* rx buffer */ 47 | uint8_t rxbuf[1000]; 48 | int rxbuf_pos; 49 | 50 | /* tx queue */ 51 | struct dlist tx_queue; 52 | 53 | int is_auth; 54 | int start_token, end_token; 55 | } client_t; 56 | 57 | typedef struct { 58 | struct dlist node; 59 | uint8_t *payload; 60 | int size; 61 | } message_t; 62 | 63 | client_t *client_init(char *hostname, int port, int fd); 64 | void client_free(client_t *client); 65 | int client_parse_request(client_t *client); 66 | int client_tx(client_t *client); 67 | void client_enqueue_frame(client_t *client, uint8_t *payload, int size, 68 | uint8_t **allocated); 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /constants.h: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef CONSTANTS_H 27 | #define CONSTANTS_H 28 | 29 | #define MAX_FRAME_SIZE 200000 30 | #define MAX_FILE_DESCRIPTORS 32 31 | #define WIDTH 640 32 | #define HEIGHT 480 33 | #define FRAME_PER_SECOND 30 34 | #define TOKEN_SIZE 20 35 | #define NUMBER_OF_TOKEN 20 36 | #define TX_QUEUE_MAX 5 37 | #define SERVER_LISTEN_BACKLOG 10 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /dump2file.c: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "constants.h" 39 | #include "video.h" 40 | 41 | enum type { VIDEO }; 42 | 43 | union observed_data { 44 | int fd; 45 | }; 46 | 47 | struct observed { 48 | enum type t; 49 | union observed_data data; 50 | }; 51 | 52 | void enable_video(int epfd, struct observed *video) { 53 | struct epoll_event ev; 54 | ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; 55 | ev.data.ptr = video; 56 | if (epoll_ctl(epfd, EPOLL_CTL_ADD, video->data.fd, &ev) == -1) { 57 | perror("epoll_ctl: enable video"); 58 | exit(EXIT_FAILURE); 59 | } 60 | } 61 | 62 | int g_idx = 0; 63 | char g_path[1000]; 64 | 65 | void dump_frame(uint8_t *jpeg_image, uint32_t len) { 66 | char output[8192]; 67 | snprintf(output, 8192, "%s%d.jpeg", g_path, g_idx++); 68 | int fd = open(output, O_RDWR | O_CREAT, S_IRWXU | S_IRUSR); 69 | if (fd < 0) { 70 | perror("error on opening!!"); 71 | exit(EXIT_FAILURE); 72 | } 73 | int w = write(fd, jpeg_image, len); 74 | if (w != len) { 75 | perror("error on writing!!"); 76 | exit(EXIT_FAILURE); 77 | } 78 | close(fd); 79 | } 80 | 81 | int main(int argc, char **argv) { 82 | if (argc != 3) { 83 | printf("usage example: ./dump2frame /dev/video0 /tmp/frames/test_\n"); 84 | return 1; 85 | } 86 | 87 | setvbuf(stdout, NULL, _IONBF, 0); 88 | 89 | int epfd = epoll_create1(0); 90 | if (epfd == -1) { 91 | perror("epoll_create1"); 92 | exit(EXIT_FAILURE); 93 | } 94 | 95 | strcpy(g_path, argv[2]); 96 | 97 | struct epoll_event events[MAX_FILE_DESCRIPTORS]; 98 | int video_fd = video_init(argv[1], WIDTH, HEIGHT, FRAME_PER_SECOND); 99 | struct observed video, *ov; 100 | video.data.fd = video_fd; 101 | video.t = VIDEO; 102 | 103 | int nfds, n; 104 | enable_video(epfd, &video); 105 | 106 | for (;;) { 107 | 108 | nfds = epoll_wait(epfd, events, MAX_FILE_DESCRIPTORS, -1); 109 | if (nfds == -1) { 110 | perror("epoll_wait"); 111 | exit(EXIT_FAILURE); 112 | } 113 | 114 | for (n = 0; n < nfds; ++n) { 115 | ov = (struct observed *)events[n].data.ptr; 116 | switch (ov->t) { 117 | case VIDEO: 118 | if (events[n].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) { 119 | perror("error on video"); 120 | exit(EXIT_FAILURE); 121 | } 122 | video_read_jpeg(dump_frame, MAX_FRAME_SIZE); 123 | break; 124 | } 125 | } 126 | } 127 | 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /libmjpeg2http.c: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "client.h" 41 | #include "constants.h" 42 | #include "libmjpeg2http.h" 43 | #include "list.h" 44 | #include "protocol.h" 45 | #include "server.h" 46 | #include "video.h" 47 | 48 | enum type { SERVER, CLIENT, VIDEO, TOKEN, EXITFD }; 49 | 50 | union observed_data { 51 | client_t *client; 52 | int fd; 53 | }; 54 | 55 | struct observed { 56 | struct dlist node; 57 | enum type t; 58 | union observed_data data; 59 | }; 60 | 61 | static char g_frame_complete[MAX_FRAME_SIZE]; 62 | static int g_numClients; 63 | static const int g_maxClients = MAX_FILE_DESCRIPTORS - 2; 64 | static char g_token[NUMBER_OF_TOKEN * (TOKEN_SIZE + 1)]; 65 | static int g_token_pos = -1; 66 | static int g_videoOn = 0; 67 | static int g_frame_complete_len; 68 | static int g_token_len; 69 | static int g_epfd; 70 | static int g_pipe_fd = -1; 71 | static struct observed g_pipe; 72 | static int g_runs = 0; 73 | static int g_exitfd = -1; 74 | 75 | static void cleanAll() { 76 | g_numClients = 0; 77 | g_token_pos = -1; 78 | g_videoOn = 0; 79 | g_pipe_fd = -1; 80 | g_exitfd = -1; 81 | } 82 | 83 | static int enable_video(struct observed *video) { 84 | struct epoll_event ev; 85 | ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; 86 | ev.data.ptr = video; 87 | if (epoll_ctl(g_epfd, EPOLL_CTL_ADD, video->data.fd, &ev) == -1) { 88 | perror("epoll_ctl: enable video"); 89 | return -1; 90 | } 91 | return 1; 92 | } 93 | 94 | static int disable_video(struct observed *video) { 95 | if (epoll_ctl(g_epfd, EPOLL_CTL_DEL, video->data.fd, NULL) == -1) { 96 | perror("epoll_ctl: disable video"); 97 | return -1; 98 | } 99 | return 1; 100 | } 101 | 102 | static int add_clients(int server_fd, struct dlist *clients, 103 | struct observed *video) { 104 | int ret; 105 | struct remotepeer peer; 106 | struct epoll_event ev; 107 | do { 108 | ret = server_new_peer(server_fd, &peer); 109 | if (ret == 0) 110 | break; // no more connections to accept 111 | else if (ret == -1) { 112 | perror("server_new_peer error"); 113 | return -1; 114 | } 115 | 116 | if (g_numClients <= g_maxClients - 1) { 117 | struct observed *oc = malloc(sizeof(struct observed)); 118 | oc->data.client = client_init(peer.hostname, peer.port, peer.fd); 119 | oc->t = CLIENT; 120 | list_add_right(&oc->node, clients); 121 | ev.events = 122 | EPOLLIN | EPOLLOUT | EPOLLET | EPOLLRDHUP | EPOLLERR | EPOLLHUP; 123 | ev.data.ptr = oc; 124 | if (epoll_ctl(g_epfd, EPOLL_CTL_ADD, peer.fd, &ev) == -1) { 125 | perror("epoll_ctl: add clients"); 126 | return -1; 127 | } 128 | ++g_numClients; 129 | } else { 130 | printf("reject new connection => increase MAX_FILE_DESCRIPTORS\n"); 131 | fflush(stdout); 132 | close(peer.fd); 133 | } 134 | } while (ret > 0); 135 | if (g_numClients > 0 && g_videoOn == 0) { 136 | printf("turn on video because clients=%d\n", g_numClients); 137 | fflush(stdout); 138 | g_videoOn = 1; 139 | return enable_video(video); 140 | } 141 | return 1; 142 | } 143 | 144 | static int remove_client(struct observed *oc, struct observed *video) { 145 | printf("remove client %s %d fd=%d\n", oc->data.client->hostname, 146 | oc->data.client->port, oc->data.client->fd); 147 | fflush(stdout); 148 | if (epoll_ctl(g_epfd, EPOLL_CTL_DEL, oc->data.client->fd, NULL) == -1) { 149 | perror("epoll_ctl: remove clients"); 150 | return -1; 151 | } 152 | client_free(oc->data.client); 153 | list_del(&oc->node); 154 | free(oc); 155 | if (--g_numClients == 0 && g_videoOn == 1) { 156 | printf("turn off video because clients=%d\n", g_numClients); 157 | fflush(stdout); 158 | g_videoOn = 0; 159 | return disable_video(video); 160 | } 161 | return 1; 162 | } 163 | 164 | static void prepare_frame(uint8_t *jpeg_image, uint32_t len) { 165 | struct timeval timestamp; 166 | gettimeofday(×tamp, NULL); 167 | int total = snprintf(g_frame_complete, MAX_FRAME_SIZE, frame_header, len, 168 | (int)timestamp.tv_sec, (int)timestamp.tv_usec); 169 | memcpy(g_frame_complete + total, jpeg_image, len); 170 | memcpy(g_frame_complete + total + len, end_frame, end_frame_len); 171 | g_frame_complete_len = total + len + end_frame_len; 172 | } 173 | 174 | static int handle_new_frame(struct dlist *clients) { 175 | int n = video_read_jpeg(prepare_frame, MAX_FRAME_SIZE); 176 | if (n > 0) { 177 | uint8_t *allocated = NULL; 178 | struct dlist *itr; 179 | list_iterate(itr, clients) { 180 | struct observed *oc = list_get_entry(itr, struct observed, node); 181 | if (oc->data.client->is_auth) 182 | client_enqueue_frame(oc->data.client, (uint8_t *)g_frame_complete, 183 | g_frame_complete_len, &allocated); 184 | } 185 | if (allocated != NULL) { 186 | if (--*(allocated + g_frame_complete_len) == 0) 187 | free(allocated); 188 | } 189 | } else if (n < 0) { 190 | perror("error on handle new frame"); 191 | return -1; 192 | } 193 | return 1; 194 | } 195 | 196 | static int create_pipe(char *name) { 197 | mkfifo(name, S_IRUSR | S_IWUSR); 198 | g_pipe_fd = open(name, O_RDWR | O_TRUNC); 199 | if (g_pipe_fd < 0) 200 | return -1; 201 | g_pipe.data.fd = g_pipe_fd; 202 | g_pipe.t = TOKEN; 203 | struct epoll_event ev; 204 | ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP | EPOLLERR | EPOLLHUP; 205 | ev.data.ptr = &g_pipe; 206 | fcntl(g_pipe_fd, F_SETFL, O_NONBLOCK); 207 | if (epoll_ctl(g_epfd, EPOLL_CTL_ADD, g_pipe_fd, &ev) == -1) { 208 | close(g_pipe_fd); 209 | return -1; 210 | } 211 | return 1; 212 | } 213 | 214 | static int handle_token(int fd) { 215 | int r; 216 | g_token_pos %= NUMBER_OF_TOKEN * (TOKEN_SIZE + 1); 217 | while ((r = read(fd, g_token + g_token_pos, 218 | NUMBER_OF_TOKEN * (TOKEN_SIZE + 1) - g_token_pos)) > 0) { 219 | g_token_pos %= NUMBER_OF_TOKEN * (TOKEN_SIZE + 1); 220 | g_token_pos += r; 221 | } 222 | 223 | // printf("token=%.*s\n", NUMBER_OF_TOKEN * (TOKEN_SIZE + 1), g_token); 224 | 225 | if (r < 0) { 226 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 227 | return 0; 228 | } 229 | return -1; 230 | } 231 | 232 | return 0; 233 | } 234 | 235 | static int check_token(const char *auth, uint8_t *start, int count) { 236 | if (g_token_len == count && memcmp(auth, start, count) == 0) 237 | return 1; 238 | 239 | if (g_token_pos == -1 || TOKEN_SIZE != count) 240 | return 0; 241 | 242 | for (int i = 0; i < NUMBER_OF_TOKEN * (TOKEN_SIZE + 1); i += TOKEN_SIZE + 1) { 243 | if (memcmp(g_token + i, start, TOKEN_SIZE) == 0) { 244 | memset(g_token + i, 0, TOKEN_SIZE); 245 | return 1; 246 | } 247 | } 248 | return 0; 249 | } 250 | 251 | void libmjpeg2http_endLoop() { 252 | if (g_runs > 0) { 253 | --g_runs; 254 | uint64_t beep = 1; 255 | if (write(g_exitfd, &beep, sizeof(uint64_t))) { 256 | printf("trigger exit\n"); 257 | } 258 | } 259 | } 260 | 261 | int libmjpeg2http_loop(char *ipaddress, int port, char *device, char *token, 262 | char *tokenpipe) { 263 | if (g_runs > 0) { 264 | printf("libmjpeg2http_loop: already running\n"); 265 | fflush(stdout); 266 | return -1; 267 | } 268 | 269 | cleanAll(); 270 | 271 | g_exitfd = eventfd(0, 0); 272 | if (g_exitfd == -1) { 273 | printf("libmjpeg2http_loop: cannot create g_exitfd\n"); 274 | fflush(stdout); 275 | return -1; 276 | } 277 | 278 | // reference counter uses 1 byte so 279 | // to increase this limit more bytes 280 | // must be used 281 | if (g_maxClients >= 255) { 282 | printf("g_maxClients=%d exceeds 255\n", g_maxClients); 283 | fflush(stdout); 284 | return -1; 285 | } 286 | 287 | signal(SIGPIPE, SIG_IGN); 288 | 289 | g_epfd = epoll_create1(0); 290 | if (g_epfd == -1) { 291 | perror("epoll_create1"); 292 | return -1; 293 | } 294 | 295 | ++g_runs; 296 | 297 | int server_fd = server_create(ipaddress, port); 298 | if (server_fd < 0) 299 | goto errorOnServerCreate; 300 | 301 | int video_fd = video_init(device, WIDTH, HEIGHT, FRAME_PER_SECOND); 302 | if (video_fd < 0) 303 | goto errorOnVideoInit; 304 | 305 | struct epoll_event ev, ev2, events[MAX_FILE_DESCRIPTORS]; 306 | struct observed video, server, *oev, exitfd; 307 | struct dlist *itr, *save; 308 | 309 | video.data.fd = video_fd; 310 | video.t = VIDEO; 311 | 312 | g_token_len = strlen(token); 313 | 314 | // register webserver 315 | server.data.fd = server_fd; 316 | server.t = SERVER; 317 | ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP | EPOLLERR | EPOLLHUP; 318 | ev.data.ptr = &server; 319 | if (epoll_ctl(g_epfd, EPOLL_CTL_ADD, server_fd, &ev) == -1) { 320 | perror("epoll_ctl: server socket"); 321 | goto errorOnRegisterServer; 322 | } 323 | 324 | // register eventfd 325 | exitfd.data.fd = g_exitfd; 326 | exitfd.t = EXITFD; 327 | ev2.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; 328 | ev2.data.ptr = &exitfd; 329 | if (epoll_ctl(g_epfd, EPOLL_CTL_ADD, g_exitfd, &ev2) == -1) { 330 | perror("epoll_ctl: g_exitfd"); 331 | goto errorOnRegisterServer; 332 | } 333 | 334 | if (tokenpipe != NULL) { 335 | g_token_pos = 0; 336 | if (create_pipe(tokenpipe) < 0) { 337 | goto errorOnCreatePipe; 338 | } 339 | } 340 | 341 | client_t *c; 342 | int nfds, n; 343 | declare_list(clients); 344 | 345 | printf("libmjpeg2http mainloop\n"); 346 | fflush(stdout); 347 | 348 | for (;;) { 349 | 350 | nfds = epoll_wait(g_epfd, events, MAX_FILE_DESCRIPTORS, -1); 351 | if (nfds == -1) { 352 | if (errno != EBADF) 353 | perror("epoll_wait"); 354 | goto errorOnEpollWait; 355 | } 356 | 357 | for (n = 0; n < nfds; ++n) { 358 | oev = (struct observed *)events[n].data.ptr; 359 | switch (oev->t) { 360 | 361 | case EXITFD: 362 | goto exitFromMainLoop; 363 | 364 | case TOKEN: 365 | if (handle_token(oev->data.fd) < 0) 366 | goto errorOnHandleToken; 367 | break; 368 | 369 | case VIDEO: 370 | if (events[n].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) { 371 | perror("error on video"); 372 | goto errorOnVideo; 373 | } 374 | if (handle_new_frame(&clients) < 0) 375 | goto errorOnHandleNewFrame; 376 | break; 377 | 378 | case SERVER: 379 | if (events[n].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) { 380 | perror("error on server"); 381 | goto errorOnServer; 382 | } 383 | if (events[n].events & EPOLLIN) { 384 | if (add_clients(server_fd, &clients, &video) < 0) 385 | goto errorOnAddClients; 386 | } 387 | break; 388 | 389 | case CLIENT: 390 | c = oev->data.client; 391 | if (events[n].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) { 392 | printf("generic error fd=%d\n", c->fd); 393 | fflush(stdout); 394 | if (remove_client(oev, &video) < 0) 395 | goto errorOnRemoveClient; 396 | break; 397 | } 398 | 399 | if (events[n].events & EPOLLOUT) { 400 | if (client_tx(c) < 0) { 401 | if (remove_client(oev, &video) < 0) 402 | goto errorOnRemoveClient; 403 | break; 404 | } 405 | } 406 | 407 | if (events[n].events & EPOLLIN) { 408 | if (!c->is_auth) { 409 | int done = client_parse_request(c); 410 | if (done > 0) { 411 | if (check_token(token, c->rxbuf + c->start_token, 412 | c->end_token - c->start_token)) { 413 | printf("client auth OK %s %d\n", c->hostname, c->port); 414 | fflush(stdout); 415 | c->is_auth = 1; 416 | client_enqueue_frame(c, (uint8_t *)welcome, welcome_len, NULL); 417 | } else { 418 | printf("client auth KO %s %d\n", c->hostname, c->port); 419 | fflush(stdout); 420 | client_enqueue_frame(c, (uint8_t *)welcome_ko, welcome_ko_len, 421 | NULL); 422 | if (remove_client(oev, &video) < 0) 423 | goto errorOnRemoveClient; 424 | } 425 | } else if (done < 0) { 426 | if (remove_client(oev, &video) < 0) 427 | goto errorOnRemoveClient; 428 | } 429 | } 430 | } 431 | break; 432 | } 433 | } 434 | } 435 | 436 | exitFromMainLoop: 437 | errorOnRemoveClient: 438 | errorOnAddClients: 439 | errorOnServer: 440 | errorOnVideo: 441 | errorOnHandleNewFrame: 442 | errorOnHandleToken: 443 | errorOnEpollWait: 444 | list_iterate_safe(itr, save, &clients) { 445 | list_del(&list_get_entry(itr, struct observed, node)->node); 446 | oev = list_get_entry(itr, struct observed, node); 447 | client_free(oev->data.client); 448 | free(oev); 449 | } 450 | 451 | errorOnCreatePipe: 452 | if (g_pipe_fd != -1) 453 | close(g_pipe_fd); 454 | 455 | errorOnRegisterServer: 456 | video_deinit(); 457 | 458 | errorOnVideoInit: 459 | shutdown(server_fd, SHUT_RDWR); 460 | close(server_fd); 461 | 462 | errorOnServerCreate: 463 | close(g_epfd); 464 | close(g_exitfd); 465 | g_runs = 0; 466 | 467 | printf("libmjpeg2http exit from loop\n"); 468 | fflush(stdout); 469 | 470 | return 0; 471 | } 472 | -------------------------------------------------------------------------------- /libmjpeg2http.h: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | #ifndef LIBMJPEG2HTTP_H 26 | #define LIBMJPEG2HTTP_H 27 | 28 | // ensure we can call these functions from C++. 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | // run loop (blocking call) - not thread-safe 34 | int libmjpeg2http_loop(char *ipaddress, int port, char *device, char *token, 35 | char *tokenpipe); 36 | 37 | // interrupts loop and deallocates all resources - not thread-safe 38 | void libmjpeg2http_endLoop(); 39 | 40 | #ifdef __cplusplus 41 | } // end of extern "C" 42 | #endif 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | #ifndef LIST_H 2 | #define LIST_H 3 | 4 | struct dlist { 5 | struct dlist *right, *left; 6 | }; 7 | 8 | #define init_list_entry(ptr) \ 9 | do { \ 10 | (ptr)->left = ptr; \ 11 | (ptr)->right = ptr; \ 12 | } while (0); 13 | #define declare_list(_name_) struct dlist _name_ = {&(_name_), &(_name_)} 14 | 15 | static inline void list_add_right(struct dlist *new, struct dlist *list) { 16 | list->right->left = new; 17 | new->left = list; 18 | new->right = list->right; 19 | list->right = new; 20 | } 21 | 22 | static inline void list_add_left(struct dlist *new, struct dlist *list) { 23 | list->left->right = new; 24 | new->left = list->left; 25 | new->right = list; 26 | list->left = new; 27 | } 28 | static inline void list_del(struct dlist *entry) { 29 | entry->right->left = entry->left; 30 | entry->left->right = entry->right; 31 | } 32 | 33 | static inline int list_empty(struct dlist *list) { return list->right == list; } 34 | 35 | #define list_get_first(listhead) ((listhead)->right) 36 | 37 | #define list_get_right(_entry_) ((_entry_)->right) 38 | 39 | #define list_get_entry(entry, type, structmember) \ 40 | ((type *)((char *)(entry) - (unsigned long)(&((type *)0)->structmember))) 41 | 42 | #define list_iterate(entry, listhead) \ 43 | for (entry = (listhead)->right; entry != (listhead); entry = entry->right) 44 | 45 | #define list_iterate_safe(entry, save, listhead) \ 46 | for (entry = (listhead)->right, save = entry->right; entry != (listhead); \ 47 | entry = save, save = save->right) 48 | 49 | #define list_size(n, listhead) \ 50 | for (struct dlist *entry = (listhead)->right; entry != (listhead); \ 51 | entry = entry->right, ++n) 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | #include "libmjpeg2http.h" 30 | 31 | int main(int argc, char **argv) { 32 | if (argc < 5) { 33 | printf("usage example: ./mjpeg2http 192.168.2.1 8080 /dev/video0 " 34 | "this_is_token [/tmp/mjpeg2http_onetimetoken]\n"); 35 | return 1; 36 | } 37 | 38 | char *tokenpipe = NULL; 39 | 40 | if (argc == 6) { 41 | tokenpipe = argv[5]; 42 | } 43 | 44 | libmjpeg2http_loop(argv[1], atoi(argv[2]), argv[3], argv[4], tokenpipe); 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # 2 | # mjpeg2http 3 | # 4 | # Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | # follows: 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | 25 | CC=gcc 26 | CFLAGS=-Wall -O3 27 | TIMESTAMP=$(shell date +'%Y%m%d%H%M%S') 28 | 29 | .PHONY: all clean debug run dump format test 30 | 31 | all: mjpeg2http libmjpeg2http.a 32 | 33 | mjpeg2http: main.o video.o client.o server.o libmjpeg2http.o 34 | $(CC) -o mjpeg2http video.o client.o server.o main.o libmjpeg2http.o 35 | 36 | test_mem: test_mem.o video.o client.o server.o libmjpeg2http.o 37 | $(CC) -o test_mem video.o client.o server.o test_mem.o libmjpeg2http.o -lpthread 38 | 39 | clean: 40 | rm -f test_mem mjpeg2http *.o dump2file *.a 41 | 42 | 43 | debug: mjpeg2http 44 | gdb --args ./mjpeg2http 192.168.1.2 8080 /dev/video0 mytoken 45 | 46 | run: mjpeg2http 47 | ./mjpeg2http 192.168.1.2 8080 /dev/video0 mytoken /tmp/mjpeg2http_oneshottoken 48 | 49 | test: test_mem 50 | ./test_mem 192.168.2.108 8080 /dev/video0 mytoken /tmp/mjpeg2http_oneshottoken 51 | 52 | dump: dump2file 53 | mkdir -p /tmp/mjpeg2http_dump/$(TIMESTAMP) 54 | ./dump2file /dev/video0 /tmp/mjpeg2http_dump/$(TIMESTAMP)/frame_ 55 | 56 | dump2file: dump2file.o video.o 57 | $(CC) -o dump2file video.o dump2file.o 58 | 59 | format: 60 | clang-format -i -style=LLVM *.c *.h 61 | 62 | .c.o: 63 | $(CC) $(CFLAGS) -c -o $@ $< 64 | 65 | libmjpeg2http.a: video.o client.o server.o libmjpeg2http.o 66 | ar rcs libmjpeg2http.a video.o client.o server.o libmjpeg2http.o 67 | 68 | 69 | -------------------------------------------------------------------------------- /protocol.h: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * mjpeg2http 4 | * 5 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 6 | * follows: 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | #ifndef PROTOCOL_H 28 | #define PROTOCOL_H 29 | 30 | #define BOUNDARY "hdfahelfaelfalfvcjcfjfjfj" 31 | 32 | #define FIRST_MESSAGE \ 33 | "HTTP/1.0 200 OK\r\n" \ 34 | "Access-Control-Allow-Origin: *\r\n" \ 35 | "Connection: close\r\n" \ 36 | "Server: mjpeg2http/1.0\r\n" \ 37 | "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, " \ 38 | "post-check=0, max-age=0\r\n" \ 39 | "Pragma: no-cache\r\n" \ 40 | "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n" \ 41 | "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \ 42 | "\r\n" \ 43 | "--" BOUNDARY "\r\n" 44 | 45 | #define FRAME_HEADER \ 46 | "Content-Type: image/jpeg\r\n" \ 47 | "Content-Length: %d\r\n" \ 48 | "X-Timestamp: %d.%06d\r\n" \ 49 | "\r\n" 50 | 51 | #define END_FRAME "\r\n--" BOUNDARY "\r\n" 52 | 53 | #define UNAUTHORIZED_MESSAGE \ 54 | "HTTP/1.1 401 Unauthorized\r\n" \ 55 | "Connection: close\r\n" \ 56 | "\r\n" \ 57 | "not authorized\r\n" \ 58 | "\r\n" 59 | 60 | const char *welcome = FIRST_MESSAGE; 61 | const int welcome_len = strlen(FIRST_MESSAGE); 62 | const char *welcome_ko = UNAUTHORIZED_MESSAGE; 63 | const int welcome_ko_len = strlen(UNAUTHORIZED_MESSAGE); 64 | const char *frame_header = FRAME_HEADER; 65 | const char *end_frame = END_FRAME; 66 | const int end_frame_len = strlen(END_FRAME); 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "constants.h" 39 | #include "server.h" 40 | 41 | int server_new_peer(int sfd, struct remotepeer *rpeer) { 42 | // accept 43 | struct sockaddr_storage remote; 44 | uint32_t addrlen = sizeof(struct sockaddr_storage); 45 | rpeer->fd = accept(sfd, (struct sockaddr *)&remote, &addrlen); 46 | if (rpeer->fd < 0) { 47 | if (EAGAIN == errno || EWOULDBLOCK == errno) 48 | return 0; 49 | return -1; 50 | } 51 | 52 | // set non block and tcp_nodelay 53 | fcntl(rpeer->fd, F_SETFL, O_NONBLOCK); 54 | int on = 1; 55 | if (0 != setsockopt(rpeer->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&on, 56 | sizeof(int))) { 57 | perror("error address setsock"); 58 | return -1; 59 | } 60 | 61 | // get hostname and port 62 | rpeer->port = ntohs(((struct sockaddr_in *)&remote)->sin_port); 63 | if (NULL == inet_ntop(AF_INET, &((struct sockaddr_in *)&remote)->sin_addr, 64 | rpeer->hostname, SERVER_MAX)) { 65 | perror("error address"); 66 | return -1; 67 | } 68 | return 1; 69 | } 70 | 71 | int server_create(char *hostname, int port) { 72 | // create fd 73 | int error = 0; 74 | int socket_fd = socket(PF_INET, SOCK_STREAM, 0); 75 | if (socket_fd < 0) { 76 | perror("error"); 77 | return -1; 78 | } 79 | 80 | // build address 81 | struct sockaddr_storage address; 82 | address.ss_family = AF_INET; 83 | error = inet_pton(AF_INET, hostname, 84 | &(((struct sockaddr_in *)&address)->sin_addr)); 85 | if (error != 1) { 86 | perror("error address"); 87 | close(socket_fd); 88 | return -1; 89 | } 90 | ((struct sockaddr_in *)&address)->sin_port = htons(port); 91 | 92 | // set reuseaddr 93 | int yes = 1; 94 | if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) { 95 | perror("setsockopt reuseaddr error"); 96 | close(socket_fd); 97 | return -1; 98 | } 99 | 100 | // set reuseport 101 | if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(int)) != 0) { 102 | perror("setsockopt reuseport error"); 103 | close(socket_fd); 104 | return -1; 105 | } 106 | 107 | // bind address 108 | if (bind(socket_fd, (struct sockaddr *)&address, sizeof(struct sockaddr_in)) < 109 | 0) { 110 | perror("bind error"); 111 | close(socket_fd); 112 | return -1; 113 | } 114 | 115 | // listening and backlog 116 | if (listen(socket_fd, SERVER_LISTEN_BACKLOG) < 0) { 117 | perror("listen error"); 118 | close(socket_fd); 119 | return -1; 120 | } 121 | 122 | // set nonblock 123 | fcntl(socket_fd, F_SETFL, O_NONBLOCK); 124 | 125 | return socket_fd; 126 | } 127 | -------------------------------------------------------------------------------- /server.h: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef SERVER_H 27 | #define SERVER_H 28 | 29 | #define SERVER_MAX 1024 30 | 31 | struct remotepeer { 32 | char hostname[SERVER_MAX]; 33 | int port; 34 | int fd; 35 | }; 36 | 37 | int server_create(char *hostname, int port); 38 | int server_new_peer(int fd, struct remotepeer *rpeer); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /test_mem.c: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "libmjpeg2http.h" 32 | 33 | size_t allocated = 0, freed = 0; 34 | 35 | void *my_malloc_hook(size_t, const void *); 36 | void my_free_hook(void *, const void *); 37 | void *my_realloc_hook(void *ptr, size_t size, const void *caller); 38 | 39 | void *(*old_malloc_hook)(size_t, const void *); 40 | void (*old_free_hook)(void *, const void *); 41 | void (*old_realloc_hook)(void *ptr, size_t size, const void *caller); 42 | 43 | void my_init(void) { 44 | old_malloc_hook = __malloc_hook; 45 | old_free_hook = __free_hook; 46 | old_realloc_hook = __realloc_hook; 47 | __malloc_hook = my_malloc_hook; 48 | __free_hook = my_free_hook; 49 | __realloc_hook = my_realloc_hook; 50 | } 51 | 52 | void *my_realloc_hook(void *ptr, size_t size, const void *caller) { 53 | ++allocated; 54 | void *result; 55 | /* Restore all old hooks */ 56 | __malloc_hook = old_malloc_hook; 57 | __free_hook = old_free_hook; 58 | __realloc_hook = old_realloc_hook; 59 | /* Call recursively */ 60 | result = realloc(ptr, size); 61 | /* Save underlying hooks */ 62 | old_malloc_hook = __malloc_hook; 63 | old_free_hook = __free_hook; 64 | old_realloc_hook = __realloc_hook; 65 | /* Restore our own hooks */ 66 | __malloc_hook = my_malloc_hook; 67 | __free_hook = my_free_hook; 68 | __realloc_hook = my_realloc_hook; 69 | return result; 70 | } 71 | 72 | void *my_malloc_hook(size_t size, const void *caller) { 73 | ++allocated; 74 | void *result; 75 | /* Restore all old hooks */ 76 | __malloc_hook = old_malloc_hook; 77 | __free_hook = old_free_hook; 78 | __realloc_hook = old_realloc_hook; 79 | /* Call recursively */ 80 | result = malloc(size); 81 | /* Save underlying hooks */ 82 | old_malloc_hook = __malloc_hook; 83 | old_free_hook = __free_hook; 84 | old_realloc_hook = __realloc_hook; 85 | /* Restore our own hooks */ 86 | __malloc_hook = my_malloc_hook; 87 | __free_hook = my_free_hook; 88 | __realloc_hook = my_realloc_hook; 89 | return result; 90 | } 91 | 92 | void my_free_hook(void *ptr, const void *caller) { 93 | ++freed; 94 | /* Restore all old hooks */ 95 | __malloc_hook = old_malloc_hook; 96 | __free_hook = old_free_hook; 97 | __realloc_hook = old_realloc_hook; 98 | /* Call recursively */ 99 | free(ptr); 100 | /* Save underlying hooks */ 101 | old_malloc_hook = __malloc_hook; 102 | old_free_hook = __free_hook; 103 | old_realloc_hook = __realloc_hook; 104 | /* Restore our own hooks */ 105 | __malloc_hook = my_malloc_hook; 106 | __free_hook = my_free_hook; 107 | __realloc_hook = my_realloc_hook; 108 | } 109 | 110 | void stop(void *p) { 111 | int seconds = *((int *)p); 112 | printf("call endloop sleep for %d seconds\n", seconds); 113 | sleep(seconds); 114 | fflush(stdout); 115 | libmjpeg2http_endLoop(); 116 | } 117 | 118 | int main(int argc, char **argv) { 119 | if (argc < 5) { 120 | printf("usage example: ./mjpeg2http 192.168.2.1 8080 /dev/video0 " 121 | "this_is_token [/tmp/mjpeg2http_onetimetoken]\n"); 122 | return 1; 123 | } 124 | my_init(); 125 | 126 | char *tokenpipe = NULL; 127 | 128 | if (argc == 6) { 129 | tokenpipe = argv[5]; 130 | } 131 | 132 | for (;;) { 133 | int t1 = 30; 134 | pthread_t stopper; 135 | pthread_create(&stopper, NULL, stop, &t1); 136 | pthread_detach(stopper); 137 | libmjpeg2http_loop(argv[1], atoi(argv[2]), argv[3], argv[4], tokenpipe); 138 | printf("mem allocated=%u freed=%u\n", allocated, freed); 139 | } 140 | 141 | return 0; 142 | } 143 | -------------------------------------------------------------------------------- /video.c: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * based on: 5 | * https://www.kernel.org/doc/html/v4.10/media/uapi/v4l/capture.c.html 6 | * 7 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 8 | * follows: 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to 12 | * deal in the Software without restriction, including without limitation the 13 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 14 | * sell copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 26 | * IN THE SOFTWARE. 27 | */ 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #define CLEAR(x) memset(&(x), 0, sizeof(x)) 42 | 43 | struct buffer { 44 | void *start; 45 | size_t length; 46 | }; 47 | 48 | static char *dev_name; 49 | static int fd = -1; 50 | struct buffer *buffers; 51 | static unsigned int n_buffers; 52 | static int req_num = 30; 53 | static int req_den = 1; 54 | static int g_width = 640; 55 | static int g_height = 480; 56 | 57 | static int xioctl(int fh, int request, void *arg) { 58 | int r; 59 | 60 | do { 61 | r = ioctl(fh, request, arg); 62 | } while (-1 == r && EINTR == errno); 63 | 64 | return r; 65 | } 66 | 67 | int video_read_jpeg(void (*cb)(uint8_t *, uint32_t len), int maxsize) { 68 | struct v4l2_buffer buf; 69 | unsigned int i; 70 | 71 | CLEAR(buf); 72 | 73 | buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 74 | buf.memory = V4L2_MEMORY_USERPTR; 75 | 76 | if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { 77 | switch (errno) { 78 | case EAGAIN: 79 | return 0; 80 | 81 | case EIO: 82 | /* Could ignore EIO, see spec. */ 83 | 84 | /* fall through */ 85 | 86 | default: 87 | return -1; 88 | } 89 | } 90 | 91 | for (i = 0; i < n_buffers; ++i) 92 | if (buf.m.userptr == (unsigned long)buffers[i].start && 93 | buf.length == buffers[i].length) 94 | break; 95 | 96 | if (i >= n_buffers) { 97 | fprintf(stderr, "invalid buffer index\\n"); 98 | return -1; 99 | } 100 | if (buf.bytesused >= maxsize) { 101 | fprintf(stderr, "image too large\\n"); 102 | return -1; 103 | } 104 | 105 | cb((uint8_t *)buf.m.userptr, buf.bytesused); 106 | 107 | if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) 108 | return -1; 109 | 110 | return 1; 111 | } 112 | 113 | static int stop_capturing(void) { 114 | enum v4l2_buf_type type; 115 | 116 | type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 117 | if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) 118 | return -1; 119 | return 1; 120 | } 121 | 122 | static int start_capturing(void) { 123 | unsigned int i; 124 | enum v4l2_buf_type type; 125 | 126 | for (i = 0; i < n_buffers; ++i) { 127 | struct v4l2_buffer buf; 128 | 129 | CLEAR(buf); 130 | buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 131 | buf.memory = V4L2_MEMORY_USERPTR; 132 | buf.index = i; 133 | buf.m.userptr = (unsigned long)buffers[i].start; 134 | buf.length = buffers[i].length; 135 | 136 | if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) 137 | return -1; 138 | } 139 | type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 140 | if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) 141 | return -1; 142 | return 1; 143 | } 144 | 145 | static void uninit_device(void) { 146 | unsigned int i; 147 | 148 | for (i = 0; i < n_buffers; ++i) 149 | free(buffers[i].start); 150 | 151 | free(buffers); 152 | } 153 | 154 | static int init_userp(unsigned int buffer_size) { 155 | struct v4l2_requestbuffers req; 156 | 157 | CLEAR(req); 158 | 159 | req.count = 4; 160 | req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 161 | req.memory = V4L2_MEMORY_USERPTR; 162 | 163 | if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { 164 | if (EINVAL == errno) { 165 | fprintf(stderr, 166 | "%s does not support " 167 | "user pointer i/on", 168 | dev_name); 169 | return -1; 170 | } else { 171 | return -1; 172 | } 173 | } 174 | 175 | buffers = calloc(4, sizeof(*buffers)); 176 | 177 | if (!buffers) { 178 | fprintf(stderr, "Out of memory\\n"); 179 | return -1; 180 | } 181 | 182 | for (n_buffers = 0; n_buffers < 4; ++n_buffers) { 183 | buffers[n_buffers].length = buffer_size; 184 | buffers[n_buffers].start = malloc(buffer_size); 185 | 186 | if (!buffers[n_buffers].start) { 187 | fprintf(stderr, "Out of memory\\n"); 188 | for (int j = n_buffers - 1; j >= 0; --j) 189 | free(buffers[j].start); 190 | free(buffers); 191 | return -1; 192 | } 193 | } 194 | return 1; 195 | } 196 | 197 | static int setup_framerate() { 198 | 199 | struct v4l2_streamparm streamparm; 200 | streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 201 | 202 | if (-1 == xioctl(fd, VIDIOC_G_PARM, &streamparm)) 203 | return -1; 204 | 205 | if (streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) { 206 | streamparm.parm.capture.timeperframe.numerator = req_den; 207 | streamparm.parm.capture.timeperframe.denominator = req_num; 208 | 209 | if (-1 == xioctl(fd, VIDIOC_S_PARM, &streamparm)) 210 | return -1; 211 | 212 | if (streamparm.parm.capture.timeperframe.numerator != req_den || 213 | streamparm.parm.capture.timeperframe.denominator != req_num) { 214 | fprintf(stderr, 215 | "the driver changed the time per frame from " 216 | "%d/%d to %d/%d\n", 217 | req_den, req_num, streamparm.parm.capture.timeperframe.numerator, 218 | streamparm.parm.capture.timeperframe.denominator); 219 | } 220 | } else { 221 | fprintf(stderr, "the driver does not allow to change time per frame\n"); 222 | } 223 | 224 | return 1; 225 | } 226 | 227 | static int init_device(void) { 228 | struct v4l2_capability cap; 229 | struct v4l2_cropcap cropcap; 230 | struct v4l2_crop crop; 231 | struct v4l2_format fmt; 232 | unsigned int min; 233 | 234 | if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { 235 | if (EINVAL == errno) { 236 | fprintf(stderr, "%s is no V4L2 device\\n", dev_name); 237 | return -1; 238 | } else { 239 | return -1; 240 | } 241 | } 242 | 243 | if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { 244 | fprintf(stderr, "%s is no video capture device\\n", dev_name); 245 | return -1; 246 | } 247 | 248 | if (!(cap.capabilities & V4L2_CAP_STREAMING)) { 249 | fprintf(stderr, "%s does not support streaming i/o\\n", dev_name); 250 | return -1; 251 | } 252 | 253 | CLEAR(cropcap); 254 | 255 | cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 256 | 257 | if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { 258 | crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 259 | crop.c = cropcap.defrect; /* reset to default */ 260 | 261 | if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { 262 | switch (errno) { 263 | case EINVAL: 264 | /* Cropping not supported. */ 265 | break; 266 | default: 267 | /* Errors ignored. */ 268 | break; 269 | } 270 | } 271 | } 272 | 273 | if (setup_framerate() < 0) 274 | return -1; 275 | 276 | CLEAR(fmt); 277 | 278 | fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 279 | fmt.fmt.pix.width = g_width; 280 | fmt.fmt.pix.height = g_height; 281 | fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; 282 | fmt.fmt.pix.field = V4L2_FIELD_ANY; 283 | 284 | if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) 285 | return -1; 286 | 287 | /* Buggy driver paranoia. */ 288 | min = fmt.fmt.pix.width * 2; 289 | if (fmt.fmt.pix.bytesperline < min) 290 | fmt.fmt.pix.bytesperline = min; 291 | min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; 292 | if (fmt.fmt.pix.sizeimage < min) 293 | fmt.fmt.pix.sizeimage = min; 294 | 295 | return init_userp(fmt.fmt.pix.sizeimage); 296 | } 297 | 298 | static void close_device(void) { 299 | close(fd); 300 | fd = -1; 301 | } 302 | 303 | static int open_device(void) { 304 | struct stat st; 305 | 306 | if (-1 == stat(dev_name, &st)) { 307 | fprintf(stderr, "Cannot identify '%s': %d, %s\\n", dev_name, errno, 308 | strerror(errno)); 309 | return -1; 310 | } 311 | 312 | if (!S_ISCHR(st.st_mode)) { 313 | fprintf(stderr, "%s is no devicen", dev_name); 314 | return -1; 315 | } 316 | 317 | fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0); 318 | 319 | if (-1 == fd) { 320 | fprintf(stderr, "Cannot open '%s': %d, %s\\n", dev_name, errno, 321 | strerror(errno)); 322 | return -1; 323 | } 324 | return fd; 325 | } 326 | 327 | int video_init(const char *dev, int width, int height, int rate) { 328 | dev_name = strdup(dev); 329 | g_width = width; 330 | g_height = height; 331 | req_num = rate; 332 | if (open_device() < 0) 333 | goto errorOnOpen; 334 | if (init_device() < 0) 335 | goto errorOnInit; 336 | if (start_capturing() < 0) 337 | goto errorOnStart; 338 | return fd; 339 | 340 | errorOnStart: 341 | uninit_device(); 342 | errorOnInit: 343 | close(fd); 344 | errorOnOpen: 345 | free(dev_name); 346 | fprintf(stderr, "error %d, %s\\n", errno, strerror(errno)); 347 | return -1; 348 | } 349 | 350 | void video_deinit() { 351 | stop_capturing(); 352 | uninit_device(); 353 | close_device(); 354 | free(dev_name); 355 | } 356 | -------------------------------------------------------------------------------- /video.h: -------------------------------------------------------------------------------- 1 | /** 2 | * mjpeg2http 3 | * 4 | * Copyright (c) 2022 Antonino Nolano. Licensed under the MIT license, as 5 | * follows: 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef VIDEO_H 27 | #define VIDEO_H 28 | 29 | #include 30 | 31 | int video_init(const char *device, int width, int height, int rate); 32 | void video_deinit(); 33 | int video_read_jpeg(void (*cb)(uint8_t *, uint32_t len), int maxsize); 34 | 35 | #endif 36 | --------------------------------------------------------------------------------