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