├── .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 |
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 |
--------------------------------------------------------------------------------