1 /*******************************************************************************
2 # #
3 # MJPG-streamer allows to stream JPG frames from an input-plugin #
4 # to several output plugins #
5 # #
6 # Copyright (C) 2007 Tom Stöveken #
7 # #
8 # This program is free software; you can redistribute it and/or modify #
9 # it under the terms of the GNU General Public License as published by #
10 # the Free Software Foundation; version 2 of the License. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program; if not, write to the Free Software #
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
20 # #
21 *******************************************************************************/
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <sys/ioctl.h>
28 #include <errno.h>
29 #include <signal.h>
30 #include <sys/socket.h>
31 #include <arpa/inet.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <getopt.h>
35 #include <pthread.h>
36 #include <dlfcn.h>
37 #include <fcntl.h>
38 #include <syslog.h>
39 #include <linux/types.h> /* for videodev2.h */
40 #include <linux/videodev2.h>
41
42 #include "utils.h"
43 #include "mjpg_streamer.h"
44
45 /* globals */
46 static globals global;
47
48 /******************************************************************************
49 Description.: Display a help message
50 Input Value.: argv[0] is the program name and the parameter progname
51 Return Value: -
52 ******************************************************************************/
help(char * progname)53 static void help(char *progname)
54 {
55 fprintf(stderr, "-----------------------------------------------------------------------\n");
56 fprintf(stderr, "Usage: %s\n" \
57 " -i | --input \"<input-plugin.so> [parameters]\"\n" \
58 " -o | --output \"<output-plugin.so> [parameters]\"\n" \
59 " [-h | --help ]........: display this help\n" \
60 " [-v | --version ].....: display version information\n" \
61 " [-b | --background]...: fork to the background, daemon mode\n", progname);
62 fprintf(stderr, "-----------------------------------------------------------------------\n");
63 fprintf(stderr, "Example #1:\n" \
64 " To open an UVC webcam \"/dev/video1\" and stream it via HTTP:\n" \
65 " %s -i \"input_uvc.so -d /dev/video1\" -o \"output_http.so\"\n", progname);
66 fprintf(stderr, "-----------------------------------------------------------------------\n");
67 fprintf(stderr, "Example #2:\n" \
68 " To open an UVC webcam and stream via HTTP port 8090:\n" \
69 " %s -i \"input_uvc.so\" -o \"output_http.so -p 8090\"\n", progname);
70 fprintf(stderr, "-----------------------------------------------------------------------\n");
71 fprintf(stderr, "Example #3:\n" \
72 " To get help for a certain input plugin:\n" \
73 " %s -i \"input_uvc.so --help\"\n", progname);
74 fprintf(stderr, "-----------------------------------------------------------------------\n");
75 fprintf(stderr, "In case the modules (=plugins) can not be found:\n" \
76 " * Set the default search path for the modules with:\n" \
77 " export LD_LIBRARY_PATH=/path/to/plugins,\n" \
78 " * or put the plugins into the \"/lib/\" or \"/usr/lib\" folder,\n" \
79 " * or instead of just providing the plugin file name, use a complete\n" \
80 " path and filename:\n" \
81 " %s -i \"/path/to/modules/input_uvc.so\"\n", progname);
82 fprintf(stderr, "-----------------------------------------------------------------------\n");
83 }
84
85 /******************************************************************************
86 Description.: pressing CTRL+C sends signals to this process instead of just
87 killing it plugins can tidily shutdown and free allocated
88 resources. The function prototype is defined by the system,
89 because it is a callback function.
90 Input Value.: sig tells us which signal was received
91 Return Value: -
92 ******************************************************************************/
signal_handler(int sig)93 static void signal_handler(int sig)
94 {
95 int i;
96
97 /* signal "stop" to threads */
98 LOG("setting signal to stop\n");
99 global.stop = 1;
100 usleep(1000 * 1000);
101
102 /* clean up threads */
103 LOG("force cancellation of threads and cleanup resources\n");
104 for(i = 0; i < global.incnt; i++) {
105 global.in[i].stop(i);
106 /*for (j = 0; j<MAX_PLUGIN_ARGUMENTS; j++) {
107 if (global.in[i].param.argv[j] != NULL) {
108 free(global.in[i].param.argv[j]);
109 }
110 }*/
111 }
112
113 for(i = 0; i < global.outcnt; i++) {
114 global.out[i].stop(global.out[i].param.id);
115 pthread_cond_destroy(&global.in[i].db_update);
116 pthread_mutex_destroy(&global.in[i].db);
117 /*for (j = 0; j<MAX_PLUGIN_ARGUMENTS; j++) {
118 if (global.out[i].param.argv[j] != NULL)
119 free(global.out[i].param.argv[j]);
120 }*/
121 }
122 usleep(1000 * 1000);
123
124 /* close handles of input plugins */
125 for(i = 0; i < global.incnt; i++) {
126 dlclose(global.in[i].handle);
127 }
128
129 for(i = 0; i < global.outcnt; i++) {
130 int j, skip = 0;
131 DBG("about to decrement usage counter for handle of %s, id #%02d, handle: %p\n", \
132 global.out[i].plugin, global.out[i].param.id, global.out[i].handle);
133
134 for(j=i+1; j<global.outcnt; j++) {
135 if ( global.out[i].handle == global.out[j].handle ) {
136 DBG("handles are pointing to the same destination (%p == %p)\n", global.out[i].handle, global.out[j].handle);
137 skip = 1;
138 }
139 }
140 if ( skip ) {
141 continue;
142 }
143
144 DBG("closing handle %p\n", global.out[i].handle);
145
146 dlclose(global.out[i].handle);
147 }
148 DBG("all plugin handles closed\n");
149
150 LOG("done\n");
151
152 closelog();
153 exit(0);
154 return;
155 }
156
split_parameters(char * parameter_string,int * argc,char ** argv)157 static int split_parameters(char *parameter_string, int *argc, char **argv)
158 {
159 int count = 1;
160 argv[0] = NULL; // the plugin may set it to 'INPUT_PLUGIN_NAME'
161 if(parameter_string != NULL && strlen(parameter_string) != 0) {
162 char *arg = NULL, *saveptr = NULL, *token = NULL;
163
164 arg = strdup(parameter_string);
165
166 if(strchr(arg, ' ') != NULL) {
167 token = strtok_r(arg, " ", &saveptr);
168 if(token != NULL) {
169 argv[count] = strdup(token);
170 count++;
171 while((token = strtok_r(NULL, " ", &saveptr)) != NULL) {
172 argv[count] = strdup(token);
173 count++;
174 if(count >= MAX_PLUGIN_ARGUMENTS) {
175 IPRINT("ERROR: too many arguments to input plugin\n");
176 return 0;
177 }
178 }
179 }
180 }
181 free(arg);
182 }
183 *argc = count;
184 return 1;
185 }
186
187 /******************************************************************************
188 Description.:
189 Input Value.:
190 Return Value:
191 ******************************************************************************/
main(int argc,char * argv[])192 int main(int argc, char *argv[])
193 {
194 //char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
195 char *input[MAX_INPUT_PLUGINS];
196 char *output[MAX_OUTPUT_PLUGINS];
197 int daemon = 0, i, j;
198 size_t tmp = 0;
199
200 output[0] = "output_http.so --port 8080";
201 global.outcnt = 0;
202 global.incnt = 0;
203
204 /* parameter parsing */
205 while(1) {
206 int c = 0;
207 static struct option long_options[] = {
208 {"help", no_argument, NULL, 'h'},
209 {"input", required_argument, NULL, 'i'},
210 {"output", required_argument, NULL, 'o'},
211 {"version", no_argument, NULL, 'v'},
212 {"background", no_argument, NULL, 'b'},
213 {NULL, 0, NULL, 0}
214 };
215
216 c = getopt_long(argc, argv, "hi:o:vb", long_options, NULL);
217
218 /* no more options to parse */
219 if(c == -1) break;
220
221 switch(c) {
222 case 'i':
223 input[global.incnt++] = strdup(optarg);
224 break;
225
226 case 'o':
227 output[global.outcnt++] = strdup(optarg);
228 break;
229
230 case 'v':
231 printf("MJPG Streamer Version: %s\n",
232 #ifdef GIT_HASH
233 GIT_HASH
234 #else
235 SOURCE_VERSION
236 #endif
237 );
238 return 0;
239 break;
240
241 case 'b':
242 daemon = 1;
243 break;
244
245 case 'h': /* fall through */
246 default:
247 help(argv[0]);
248 exit(EXIT_FAILURE);
249 }
250 }
251
252 openlog("MJPG-streamer ", LOG_PID | LOG_CONS, LOG_USER);
253 //openlog("MJPG-streamer ", LOG_PID|LOG_CONS|LOG_PERROR, LOG_USER);
254 syslog(LOG_INFO, "starting application");
255
256 /* fork to the background */
257 if(daemon) {
258 LOG("enabling daemon mode");
259 daemon_mode();
260 }
261
262 /* ignore SIGPIPE (send by OS if transmitting to closed TCP sockets) */
263 signal(SIGPIPE, SIG_IGN);
264
265 /* register signal handler for <CTRL>+C in order to clean up */
266 if(signal(SIGINT, signal_handler) == SIG_ERR) {
267 LOG("could not register signal handler\n");
268 closelog();
269 exit(EXIT_FAILURE);
270 }
271
272 /*
273 * messages like the following will only be visible on your terminal
274 * if not running in daemon mode
275 */
276 #ifdef GIT_HASH
277 LOG("MJPG Streamer Version: git rev: %s\n", GIT_HASH);
278 #else
279 LOG("MJPG Streamer Version.: %s\n", SOURCE_VERSION);
280 #endif
281
282 /* check if at least one output plugin was selected */
283 if(global.outcnt == 0) {
284 /* no? Then use the default plugin instead */
285 global.outcnt = 1;
286 }
287
288 /* open input plugin */
289 for(i = 0; i < global.incnt; i++) {
290 /* this mutex and the conditional variable are used to synchronize access to the global picture buffer */
291 if(pthread_mutex_init(&global.in[i].db, NULL) != 0) {
292 LOG("could not initialize mutex variable\n");
293 closelog();
294 exit(EXIT_FAILURE);
295 }
296 if(pthread_cond_init(&global.in[i].db_update, NULL) != 0) {
297 LOG("could not initialize condition variable\n");
298 closelog();
299 exit(EXIT_FAILURE);
300 }
301
302 tmp = (size_t)(strchr(input[i], ' ') - input[i]);
303 global.in[i].stop = 0;
304 global.in[i].context = NULL;
305 global.in[i].buf = NULL;
306 global.in[i].size = 0;
307 global.in[i].plugin = (tmp > 0) ? strndup(input[i], tmp) : strdup(input[i]);
308 global.in[i].handle = dlopen(global.in[i].plugin, RTLD_LAZY);
309 if(!global.in[i].handle) {
310 LOG("ERROR: could not find input plugin\n");
311 LOG(" Perhaps you want to adjust the search path with:\n");
312 LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
313 LOG(" dlopen: %s\n", dlerror());
314 closelog();
315 exit(EXIT_FAILURE);
316 }
317 global.in[i].init = dlsym(global.in[i].handle, "input_init");
318 if(global.in[i].init == NULL) {
319 LOG("%s\n", dlerror());
320 exit(EXIT_FAILURE);
321 }
322 global.in[i].stop = dlsym(global.in[i].handle, "input_stop");
323 if(global.in[i].stop == NULL) {
324 LOG("%s\n", dlerror());
325 exit(EXIT_FAILURE);
326 }
327 global.in[i].run = dlsym(global.in[i].handle, "input_run");
328 if(global.in[i].run == NULL) {
329 LOG("%s\n", dlerror());
330 exit(EXIT_FAILURE);
331 }
332 /* try to find optional command */
333 global.in[i].cmd = dlsym(global.in[i].handle, "input_cmd");
334
335 global.in[i].param.parameters = strchr(input[i], ' ');
336
337 for (j = 0; j<MAX_PLUGIN_ARGUMENTS; j++) {
338 global.in[i].param.argv[j] = NULL;
339 }
340
341 split_parameters(global.in[i].param.parameters, &global.in[i].param.argc, global.in[i].param.argv);
342 global.in[i].param.global = &global;
343 global.in[i].param.id = i;
344
345 if(global.in[i].init(&global.in[i].param, i)) {
346 LOG("input_init() return value signals to exit\n");
347 closelog();
348 exit(0);
349 }
350 }
351
352 /* open output plugin */
353 for(i = 0; i < global.outcnt; i++) {
354 tmp = (size_t)(strchr(output[i], ' ') - output[i]);
355 global.out[i].plugin = (tmp > 0) ? strndup(output[i], tmp) : strdup(output[i]);
356 global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
357 if(!global.out[i].handle) {
358 LOG("ERROR: could not find output plugin %s\n", global.out[i].plugin);
359 LOG(" Perhaps you want to adjust the search path with:\n");
360 LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
361 LOG(" dlopen: %s\n", dlerror());
362 closelog();
363 exit(EXIT_FAILURE);
364 }
365 global.out[i].init = dlsym(global.out[i].handle, "output_init");
366 if(global.out[i].init == NULL) {
367 LOG("%s\n", dlerror());
368 exit(EXIT_FAILURE);
369 }
370 global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
371 if(global.out[i].stop == NULL) {
372 LOG("%s\n", dlerror());
373 exit(EXIT_FAILURE);
374 }
375 global.out[i].run = dlsym(global.out[i].handle, "output_run");
376 if(global.out[i].run == NULL) {
377 LOG("%s\n", dlerror());
378 exit(EXIT_FAILURE);
379 }
380
381 /* try to find optional command */
382 global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");
383
384 global.out[i].param.parameters = strchr(output[i], ' ');
385
386 for (j = 0; j<MAX_PLUGIN_ARGUMENTS; j++) {
387 global.out[i].param.argv[j] = NULL;
388 }
389 split_parameters(global.out[i].param.parameters, &global.out[i].param.argc, global.out[i].param.argv);
390
391 global.out[i].param.global = &global;
392 global.out[i].param.id = i;
393 if(global.out[i].init(&global.out[i].param, i)) {
394 LOG("output_init() return value signals to exit\n");
395 closelog();
396 exit(EXIT_FAILURE);
397 }
398 }
399
400 /* start to read the input, push pictures into global buffer */
401 DBG("starting %d input plugin\n", global.incnt);
402 for(i = 0; i < global.incnt; i++) {
403 syslog(LOG_INFO, "starting input plugin %s", global.in[i].plugin);
404 if(global.in[i].run(i)) {
405 LOG("can not run input plugin %d: %s\n", i, global.in[i].plugin);
406 closelog();
407 return 1;
408 }
409 }
410
411 DBG("starting %d output plugin(s)\n", global.outcnt);
412 for(i = 0; i < global.outcnt; i++) {
413 syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);
414 global.out[i].run(global.out[i].param.id);
415 }
416
417 /* wait for signals */
418 pause();
419
420 return 0;
421 }
422