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