Minor fixes
[oss-streamer.git] / oss-streamer.c
1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <sys/ioctl.h>
7 #include <errno.h>
8 #include <signal.h>
9 #include <sys/socket.h>
10 #include <arpa/inet.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <getopt.h>
14 #include <pthread.h>
15 #include <fcntl.h>
16
17 #define SOURCE_VERSION "0.0"
18
19 typedef enum { SNAPSHOT, STREAM } answer_t;
20
21 /* globals */
22 int stop=0, sd;
23 int force_delay=0;
24 #define BUF_SIZE 4096
25 FILE *source_dev;
26
27 /* signal fresh frames */
28 pthread_mutex_t db        = PTHREAD_MUTEX_INITIALIZER;
29 pthread_cond_t  db_update = PTHREAD_COND_INITIALIZER;
30
31 /* global JPG frame, this is more or less the "database" */
32 char g_buf[BUF_SIZE];
33
34 /* thread for clients that connected to this server */
35 void *client_thread( void *arg ) {
36   int fd = *((int *)arg);
37   fd_set fds;
38   char frame[BUF_SIZE];
39   int ok = 1;
40   char buffer[1024] = {0};
41   struct timeval to;
42
43   if (arg!=NULL) free(arg); else exit(1);
44
45   /* set timeout to 5 seconds */
46   to.tv_sec  = 5;
47   to.tv_usec = 0;
48   FD_ZERO(&fds);
49   FD_SET(fd, &fds);
50   if( select(fd+1, &fds, NULL, NULL, &to) <= 0) {
51     close(fd);
52     return NULL;
53   }
54
55   /* find out if we should deliver something other than a stream */
56
57   sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
58                     "Server: OSS Streamer\r\n" \
59                     "Content-Type: audio/wav\r\n" \
60                     "Cache-Control: no-cache\r\n" \
61                     "Cache-Control: private\r\n" \
62                     "Pragma: no-cache\r\n" \
63                     "\r\n"
64     );
65
66   ok = ( write(fd, buffer, strlen(buffer)) >= 0)?1:0;
67
68   while ( ok >= 0 && !stop ) {
69
70     /* having a problem with windows (do we not always) browsers not updating the
71        stream display, unless the browser cache is disabled - try and implement a delay
72        to allow movement to end before streem goes on - kind of works, but not well enough */
73
74     /* wait for fresh frames */
75     pthread_cond_wait(&db_update, &db);
76
77     /* read buffer */
78     memcpy(frame, g_buf, BUF_SIZE);
79
80     pthread_mutex_unlock( &db );
81
82     ok = ( write(fd, frame, BUF_SIZE) >= 0)?1:0;
83     if( ok == 0 ) break;
84
85   }
86
87   close(fd);
88
89   return NULL;
90 }
91
92 /* the single writer thread */
93 void *get_thread( void *arg ) {
94
95   char rbuffer[BUF_SIZE];
96
97   while( !stop ) {
98     /* grab a frame */
99     if( fread(rbuffer,1,BUF_SIZE,source_dev) < 0 ) {
100       fprintf(stderr, "Error grabbing\n");
101       exit(1);
102     }
103
104     /* copy frame to global buffer */
105     pthread_mutex_lock( &db );
106
107     memcpy(g_buf, rbuffer, BUF_SIZE);
108
109     /* signal fresh_frame */
110     pthread_cond_broadcast(&db_update);
111     pthread_mutex_unlock( &db );
112
113     /* only use usleep if the fps is below 5, otherwise the overhead is too long */
114   }
115
116   return NULL;
117 }
118
119 void help(char *progname)
120 {
121   fprintf(stderr, "------------------------------------------------------------------\n");
122   fprintf(stderr, "Usage: oss-stream" \
123                   " [-h | --help ]........: display this help\n" \
124                   " [-d | --device ]......: audio device to open (your camera)\n" \
125                   " [-p | --port ]........: TCP-port for the server\n" \
126                   " [-v | --version ].....: display version information\n" \
127                   " [-b | --background]...: fork to the background, daemon mode\n");
128   fprintf(stderr, "------------------------------------------------------------------\n");
129 }
130
131 void signal_handler(int sigm) {
132   /* signal "stop" to threads */
133   stop = 1;
134
135   /* cleanup most important structures */
136   fprintf(stderr, "shutdown...\n");
137   usleep(1000*1000);
138   if (close (sd) < 0)
139           perror ("close sd");;
140   pthread_cond_destroy(&db_update);
141   pthread_mutex_destroy(&db);
142   exit(0);
143   return;
144 }
145
146 void daemon_mode(void) {
147   int fr=0;
148
149   fr = fork();
150   if( fr < 0 ) {
151     fprintf(stderr, "fork() failed\n");
152     exit(1);
153   }
154   if ( fr > 0 ) {
155     exit(0);
156   }
157
158   if( setsid() < 0 ) {
159     fprintf(stderr, "setsid() failed\n");
160     exit(1);
161   }
162
163   fr = fork();
164   if( fr < 0 ) {
165     fprintf(stderr, "fork() failed\n");
166     exit(1);
167   }
168   if ( fr > 0 ) {
169     fprintf(stderr, "forked to background (%d)\n", fr);
170     exit(0);
171   }
172
173   umask(0);
174
175   chdir("/");
176   close(0);
177   close(1);
178   close(2);
179
180   open("/dev/null", O_RDWR);
181   dup(0);
182   dup(0);
183 }
184
185 /* #########################################################################
186 Main
187 ######################################################################### */
188 int main(int argc, char *argv[])
189 {
190   struct sockaddr_in addr;
191   int on=1;
192   pthread_t client, get;
193   char *dev = "/dev/dsp";
194   unsigned int stream_port = 0;
195   int daemon=0;
196
197   while(1) {
198     int option_index = 0, c=0;
199     static struct option long_options[] = \
200     {
201       {"h", no_argument, 0, 0},
202       {"help", no_argument, 0, 0},
203       {"d", required_argument, 0, 0},
204       {"device", required_argument, 0, 0},
205       {"p", required_argument, 0, 0},
206       {"port", required_argument, 0, 0},
207       {"v", no_argument, 0, 0},
208       {"version", no_argument, 0, 0},
209       {"b", no_argument, 0, 0},
210       {"background", no_argument, 0, 0},
211       {0, 0, 0, 0}
212     };
213
214     c = getopt_long_only(argc, argv, "", long_options, &option_index);
215
216     /* no more options to parse */
217     if (c == -1) break;
218
219     /* unrecognized option */
220     if(c=='?'){ help(argv[0]); return 0; }
221
222     switch (option_index) {
223       /* h, help */
224       case 0:
225       case 1:
226         help(argv[0]);
227         return 0;
228         break;
229
230       /* d, device */
231       case 2:
232       case 3:
233         dev = strdup(optarg);
234         break;
235
236       /* p, port */
237       case 4:
238       case 5:
239         stream_port=htons(atoi(optarg));
240         break;
241
242       /* v, version */
243       case 6:
244       case 7:
245         printf("OSS Streamer Version: %s\n" \
246                "Compilation Date....: %s\n" \
247                "Compilation Time....: %s\n", SOURCE_VERSION, __DATE__, __TIME__);
248         return 0;
249         break;
250
251       /* b, background */
252       case 8:
253       case 9:
254         daemon=1;
255         break;
256
257       default:
258         help(argv[0]);
259         return 0;
260     }
261   }
262
263   if ( stream_port==0 ) {
264     fprintf(stderr, "server port not set\n");
265     exit(2);
266   }
267
268   /* ignore SIGPIPE (send if transmitting to closed sockets) */
269   signal(SIGPIPE, SIG_IGN);
270   if (signal(SIGINT, signal_handler) == SIG_ERR) {
271     fprintf(stderr, "could not register signal handler\n");
272     exit(1);
273   }
274
275   /* fork to the background */
276   if ( daemon ) {
277     daemon_mode();
278   }
279
280   /* allocate audio datastructure */
281
282   /* open audio device and prepare data structure */
283   if (strcmp(dev,"stdin")==0) {
284     source_dev = stdin;
285   } else {
286     source_dev = fopen(dev,"r");
287   }
288   if (source_dev == NULL) {
289     fprintf(stderr, "error opening source device\n");
290     exit(1);
291   }
292
293   /* open socket for server */
294   sd = socket(PF_INET, SOCK_STREAM, 0);
295   if ( sd < 0 ) {
296     fprintf(stderr, "socket failed\n");
297     exit(1);
298   }
299
300   /* ignore "socket already in use" errors */
301   if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
302     perror("setsockopt(SO_REUSEADDR) failed");
303     exit(1);
304   }
305
306   /* configure server address to listen to all local IPs */
307   memset(&addr, 0, sizeof(addr));
308   addr.sin_family = AF_INET;
309   addr.sin_port = stream_port;
310   addr.sin_addr.s_addr = htonl(INADDR_ANY);
311   if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 ) {
312     fprintf(stderr, "bind failed\n");
313     perror("Bind");
314     exit(1);
315   }
316
317   /* start listening on socket */
318   if ( listen(sd, 10) != 0 ) {
319     fprintf(stderr, "listen failed\n");
320     exit(1);
321   }
322
323   /* start to read the camera, push picture buffers into global buffer */
324   pthread_create(&get, 0, get_thread, NULL);
325   pthread_detach(get);
326
327   /* start motor control server */
328
329   /* create a child for every client that connects */
330   while ( 1 ) {
331     int *pfd = (int *)calloc(1, sizeof(int));
332     *pfd = accept(sd, 0, 0);
333     pthread_create(&client, NULL, &client_thread, pfd);
334     pthread_detach(client);
335
336   }
337
338   return 0;
339 }