1 #include "config.h"
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 #include <fcntl.h>
7 #include <errno.h>
8 #include <signal.h>
9 #include <string.h>
10 #include <pthread.h>
11 #include <inttypes.h>
12 #include <sys/time.h>
13 #include <sys/types.h>
14 #include <sys/socket.h>
15 #include <sys/ipc.h>
16 #include <sys/shm.h>
17
18 #include "grab-ng.h"
19 #include "commands.h" /* FIXME: *drv globals */
20 #include "sound.h"
21 #include "capture.h"
22 #include "webcam.h"
23
24 #define MAX_THREADS 4
25 #define REORDER_SIZE 32
26
27 /*-------------------------------------------------------------------------*/
28 /* data fifos (audio/video) */
29
30 void
fifo_init(struct FIFO * fifo,char * name,int slots,int writers)31 fifo_init(struct FIFO *fifo, char *name, int slots, int writers)
32 {
33 pthread_mutex_init(&fifo->lock, NULL);
34 pthread_cond_init(&fifo->hasdata, NULL);
35 fifo->name = name;
36 fifo->slots = slots;
37 fifo->writers = writers;
38 fifo->read = 0;
39 fifo->write = 0;
40 fifo->eof = 0;
41 fifo->max = 0;
42 }
43
44 int
fifo_put(struct FIFO * fifo,void * data)45 fifo_put(struct FIFO *fifo, void *data)
46 {
47 int full;
48
49 pthread_mutex_lock(&fifo->lock);
50 if (NULL == data) {
51 fifo->eof++;
52 if (debug)
53 fprintf(stderr,"fifo %s: EOF %d/%d\n",
54 fifo->name,fifo->eof,fifo->writers);
55 if (fifo->writers == fifo->eof)
56 pthread_cond_broadcast(&fifo->hasdata);
57 pthread_mutex_unlock(&fifo->lock);
58 return 0;
59 }
60 if ((fifo->write + 1) % fifo->slots == fifo->read) {
61 pthread_mutex_unlock(&fifo->lock);
62 fprintf(stderr,"fifo %s is full\n",fifo->name);
63 return -1;
64 }
65 if (debug > 1)
66 fprintf(stderr,"put %s %d=%p [pid=%d]\n",
67 fifo->name,fifo->write,data,getpid());
68 fifo->data[fifo->write] = data;
69 fifo->write++;
70 full = (fifo->write + fifo->slots - fifo->read) % fifo->slots;
71 if (fifo->max < full)
72 fifo->max = full;
73 if (fifo->write >= fifo->slots)
74 fifo->write = 0;
75 pthread_cond_signal(&fifo->hasdata);
76 pthread_mutex_unlock(&fifo->lock);
77 return 0;
78 }
79
80 void*
fifo_get(struct FIFO * fifo)81 fifo_get(struct FIFO *fifo)
82 {
83 void *data;
84
85 pthread_mutex_lock(&fifo->lock);
86 while (fifo->write == fifo->read && fifo->writers != fifo->eof) {
87 pthread_cond_wait(&fifo->hasdata, &fifo->lock);
88 }
89 if (fifo->write == fifo->read) {
90 pthread_cond_signal(&fifo->hasdata);
91 pthread_mutex_unlock(&fifo->lock);
92 return NULL;
93 }
94 if (debug > 1)
95 fprintf(stderr,"get %s %d=%p [pid=%d]\n",
96 fifo->name,fifo->read,fifo->data[fifo->read],getpid());
97 data = fifo->data[fifo->read];
98 fifo->read++;
99 if (fifo->read >= fifo->slots)
100 fifo->read = 0;
101 pthread_mutex_unlock(&fifo->lock);
102 return data;
103 }
104
105 static void*
flushit(void * arg)106 flushit(void *arg)
107 {
108 int old;
109
110 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&old);
111 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&old);
112 for (;;) {
113 sleep(1);
114 sync();
115 }
116 return NULL;
117 }
118
119 /*-------------------------------------------------------------------------*/
120 /* color space conversion / compression thread */
121
122 struct ng_convthread_handle {
123 /* converter data / state */
124 struct ng_convert_handle *c;
125
126 /* thread data */
127 struct FIFO *in;
128 struct FIFO *out;
129 };
130
131 void*
ng_convert_thread(void * arg)132 ng_convert_thread(void *arg)
133 {
134 struct ng_convthread_handle *h = arg;
135 struct ng_video_buf *in, *out;
136
137 if (debug)
138 fprintf(stderr,"convert_thread start [pid=%d]\n",getpid());
139 ng_convert_init(h->c);
140 for (;;) {
141 in = fifo_get(h->in);
142 if (NULL == in)
143 break;
144 out = ng_convert_frame(h->c,NULL,in);
145 if (NULL != webcam && 0 == webcam_put(webcam,out)) {
146 free(webcam);
147 webcam = NULL;
148 }
149 fifo_put(h->out,out);
150 }
151 fifo_put(h->out,NULL);
152 ng_convert_fini(h->c);
153 if (debug)
154 fprintf(stderr,"convert_thread done [pid=%d]\n",getpid());
155 return NULL;
156 }
157
158 /*-------------------------------------------------------------------------*/
159 /* parameter negotiation -- look what the driver can do and what */
160 /* convert functions are available */
161
162 int
ng_grabber_setformat(struct ng_video_fmt * fmt,int fix_ratio)163 ng_grabber_setformat(struct ng_video_fmt *fmt, int fix_ratio)
164 {
165 struct ng_video_fmt gfmt;
166 int rc;
167
168 /* no capture support */
169 if (!(f_drv & CAN_CAPTURE))
170 return -1;
171
172 /* try setting the format */
173 gfmt = *fmt;
174 rc = drv->setformat(h_drv,&gfmt);
175 if (debug)
176 fprintf(stderr,"setformat: %s (%dx%d): %s\n",
177 ng_vfmt_to_desc[gfmt.fmtid],
178 gfmt.width,gfmt.height,
179 (0 == rc) ? "ok" : "failed");
180 if (0 != rc)
181 return -1;
182
183 if (fix_ratio) {
184 /* fixup aspect ratio if needed */
185 ng_ratio_fixup(&gfmt.width, &gfmt.height, NULL, NULL);
186 gfmt.bytesperline = 0;
187 if (0 != drv->setformat(h_drv,&gfmt)) {
188 fprintf(stderr,"Oops: ratio size renegotiation failed\n");
189 exit(1);
190 }
191 }
192
193 /* return the real format the grabber uses now */
194 *fmt = gfmt;
195 return 0;
196 }
197
198 struct ng_video_conv*
ng_grabber_findconv(struct ng_video_fmt * fmt,int fix_ratio)199 ng_grabber_findconv(struct ng_video_fmt *fmt,
200 int fix_ratio)
201 {
202 struct ng_video_fmt gfmt;
203 struct ng_video_conv *conv;
204 int i;
205
206 /* check all available conversion functions */
207 for (i = 0;;) {
208 conv = ng_conv_find_to(fmt->fmtid, &i);
209 if (NULL == conv)
210 break;
211 gfmt = *fmt;
212 gfmt.fmtid = conv->fmtid_in;
213 if (0 == ng_grabber_setformat(&gfmt,fix_ratio))
214 goto found;
215 }
216 fprintf(stderr,"no way to get: %dx%d %s\n",
217 fmt->width,fmt->height,ng_vfmt_to_desc[fmt->fmtid]);
218 return NULL;
219
220 found:
221 *fmt = gfmt;
222 return conv;
223 }
224
225 struct ng_video_buf*
ng_grabber_grab_image(int single)226 ng_grabber_grab_image(int single)
227 {
228 return single ? drv->getimage(h_drv) : drv->nextframe(h_drv);
229 }
230
231 struct ng_video_buf*
ng_grabber_get_image(struct ng_video_fmt * fmt)232 ng_grabber_get_image(struct ng_video_fmt *fmt)
233 {
234 struct ng_video_fmt gfmt;
235 struct ng_video_conv *conv;
236 struct ng_convert_handle *ch;
237 struct ng_video_buf *buf;
238
239 if (0 == ng_grabber_setformat(fmt,1))
240 return ng_grabber_grab_image(1);
241 gfmt = *fmt;
242 if (NULL == (conv = ng_grabber_findconv(&gfmt,1)))
243 return NULL;
244 ch = ng_convert_alloc(conv,&gfmt,fmt);
245 if (NULL == (buf = ng_grabber_grab_image(1)))
246 return NULL;
247 buf = ng_convert_single(ch,buf);
248 return buf;
249 }
250
251 /*-------------------------------------------------------------------------*/
252
253 struct movie_handle {
254 /* general */
255 pthread_mutex_t lock;
256 const struct ng_writer *writer;
257 void *handle;
258 pthread_t tflush;
259 uint64_t start;
260 uint64_t rts;
261 uint64_t stopby;
262 int slots;
263
264 /* video */
265 struct ng_video_fmt vfmt;
266 int fps;
267 int frames;
268 int seq;
269 struct FIFO vfifo;
270 pthread_t tvideo;
271 uint64_t vts;
272
273 /* video converter thread */
274 struct FIFO cfifo;
275 int cthreads;
276 struct ng_convthread_handle *hconv[MAX_THREADS];
277 pthread_t tconv[MAX_THREADS];
278
279 /* audio */
280 const struct ng_dsp_driver *dsp;
281 void *hdsp;
282 struct ng_audio_fmt afmt;
283 unsigned long bytes_per_sec;
284 unsigned long bytes;
285 struct FIFO afifo;
286 pthread_t taudio;
287 pthread_t raudio;
288 uint64_t ats;
289
290 uint64_t rdrift;
291 uint64_t vdrift;
292 };
293
294 static void*
writer_audio_thread(void * arg)295 writer_audio_thread(void *arg)
296 {
297 struct movie_handle *h = arg;
298 struct ng_audio_buf *buf;
299
300 if (debug)
301 fprintf(stderr,"writer_audio_thread start [pid=%d]\n",getpid());
302 for (;;) {
303 buf = fifo_get(&h->afifo);
304 if (NULL == buf)
305 break;
306 pthread_mutex_lock(&h->lock);
307 h->writer->wr_audio(h->handle,buf);
308 pthread_mutex_unlock(&h->lock);
309 free(buf);
310 }
311 if (debug)
312 fprintf(stderr,"writer_audio_thread done\n");
313 return NULL;
314 }
315
316 /*
317 * with multiple compression threads we might receive
318 * the frames out-of-order
319 */
320 static void*
writer_video_thread(void * arg)321 writer_video_thread(void *arg)
322 {
323 struct movie_handle *h = arg;
324 struct ng_video_buf *buf;
325 struct ng_video_buf *reorder[REORDER_SIZE];
326 int seq,slot;
327
328 if (debug)
329 fprintf(stderr,"writer_video_thread start [pid=%d]\n",getpid());
330 seq = 0;
331 memset(&reorder,0,sizeof(reorder));
332 for (;;) {
333 buf = fifo_get(&h->vfifo);
334 if (NULL == buf)
335 break;
336 slot = buf->info.seq % REORDER_SIZE;
337 if (debug > 1)
338 fprintf(stderr,"video write: get seq=%d [%d]\n",
339 buf->info.seq,slot);
340 if (reorder[slot]) {
341 fprintf(stderr,"panic: reorder buffer full\n");
342 exit(1);
343 }
344 reorder[slot] = buf;
345
346 for (;;) {
347 slot = seq % REORDER_SIZE;
348 if (NULL == reorder[slot])
349 break;
350 buf = reorder[slot];
351 reorder[slot] = NULL;
352 if (debug > 1)
353 fprintf(stderr,"video write: put seq=%d [%d/%d]\n",
354 buf->info.seq,slot,seq);
355 seq++;
356
357 pthread_mutex_lock(&h->lock);
358 h->writer->wr_video(h->handle,buf);
359 if (buf->info.twice)
360 h->writer->wr_video(h->handle,buf);
361 pthread_mutex_unlock(&h->lock);
362 ng_release_video_buf(buf);
363 }
364 }
365 if (debug)
366 fprintf(stderr,"writer_video_thread done\n");
367 return NULL;
368 }
369
370 static void*
record_audio_thread(void * arg)371 record_audio_thread(void *arg)
372 {
373 struct movie_handle *h = arg;
374 struct ng_audio_buf *buf;
375
376 if (debug)
377 fprintf(stderr,"record_audio_thread start [pid=%d]\n",getpid());
378 for (;;) {
379 buf = h->dsp->read(h->hdsp,h->stopby);
380 if (NULL == buf)
381 break;
382 if (0 == buf->size)
383 continue;
384 h->ats = buf->info.ts;
385 h->rts = ng_get_timestamp() - h->start;
386 h->rdrift = h->rts - h->ats;
387 h->vdrift = h->vts - h->ats;
388 if (0 != fifo_put(&h->afifo,buf))
389 free(buf);
390 }
391 fifo_put(&h->afifo,NULL);
392 if (debug)
393 fprintf(stderr,"record_audio_thread done\n");
394 return NULL;
395 }
396
397 struct movie_handle*
movie_writer_init(char * moviename,char * audioname,const struct ng_writer * writer,struct ng_video_fmt * video,const void * priv_video,int fps,struct ng_audio_fmt * audio,const void * priv_audio,char * dsp,int slots,int threads)398 movie_writer_init(char *moviename, char *audioname,
399 const struct ng_writer *writer,
400 struct ng_video_fmt *video,const void *priv_video,int fps,
401 struct ng_audio_fmt *audio,const void *priv_audio,char *dsp,
402 int slots, int threads)
403 {
404 struct movie_handle *h;
405 struct ng_video_conv *conv;
406 void *dummy;
407 int i;
408
409 if (debug)
410 fprintf(stderr,"movie_init_writer start\n");
411 h = malloc(sizeof(*h));
412 if (NULL == h)
413 return NULL;
414 memset(h,0,sizeof(*h));
415 pthread_mutex_init(&h->lock, NULL);
416 h->writer = writer;
417 h->slots = slots;
418
419 /* audio */
420 if (audio->fmtid != AUDIO_NONE) {
421 h->dsp = ng_dsp_open(dsp,audio,1,&h->hdsp);
422 if (NULL == h->dsp) {
423 free(h);
424 return NULL;
425 }
426 fifo_init(&h->afifo,"audio",slots,1);
427 pthread_create(&h->taudio,NULL,writer_audio_thread,h);
428 h->bytes_per_sec = ng_afmt_to_bits[audio->fmtid] *
429 ng_afmt_to_channels[audio->fmtid] * audio->rate / 8;
430 h->afmt = *audio;
431 }
432
433 /* video */
434 if (video->fmtid != VIDEO_NONE) {
435 if (0 == ng_grabber_setformat(video,1)) {
436 /* native format works -- no conversion needed */
437 fifo_init(&h->vfifo,"video",slots,1);
438 pthread_create(&h->tvideo,NULL,writer_video_thread,h);
439 } else {
440 /* have to convert video frames */
441 struct ng_video_fmt gfmt = *video;
442 if (NULL == (conv = ng_grabber_findconv(&gfmt,1))) {
443 if (h->afmt.fmtid != AUDIO_NONE)
444 h->dsp->close(h->hdsp);
445 free(h);
446 return NULL;
447 }
448 h->cthreads = threads;
449 if (h->cthreads < 1)
450 h->cthreads = 1;
451 if (h->cthreads > MAX_THREADS)
452 h->cthreads = MAX_THREADS;
453 fifo_init(&h->vfifo,"video",slots,h->cthreads);
454 fifo_init(&h->cfifo,"conv",slots,1);
455 pthread_create(&h->tvideo,NULL,writer_video_thread,h);
456 for (i = 0; i < h->cthreads; i++) {
457 h->hconv[i] = malloc(sizeof(struct ng_convthread_handle));
458 memset(h->hconv[i],0,sizeof(struct ng_convthread_handle));
459 h->hconv[i]->c = ng_convert_alloc(conv,&gfmt,video);
460 h->hconv[i]->in = &h->cfifo;
461 h->hconv[i]->out = &h->vfifo;
462 pthread_create(&h->tconv[i],NULL,ng_convert_thread,
463 h->hconv[i]);
464 }
465 }
466 h->vfmt = *video;
467 h->fps = fps;
468 }
469
470 /* open file */
471 h->handle = writer->wr_open(moviename,audioname,
472 video,priv_video,fps,
473 audio,priv_audio);
474 if (debug)
475 fprintf(stderr,"movie_init_writer end (h=%p)\n",h->handle);
476 if (NULL != h->handle)
477 return h;
478
479 /* Oops -- wr_open() didn't work. cleanup. */
480 if (h->afmt.fmtid != AUDIO_NONE) {
481 pthread_cancel(h->taudio);
482 pthread_join(h->taudio,&dummy);
483 h->dsp->close(h->hdsp);
484 }
485 if (h->vfmt.fmtid != VIDEO_NONE) {
486 pthread_cancel(h->tvideo);
487 pthread_join(h->tvideo,&dummy);
488 }
489 for (i = 0; i < h->cthreads; i++) {
490 pthread_cancel(h->tconv[i]);
491 pthread_join(h->tconv[i],&dummy);
492 }
493 free(h);
494 return NULL;
495 }
496
497 int
movie_writer_start(struct movie_handle * h)498 movie_writer_start(struct movie_handle *h)
499 {
500 int rc = 0;
501
502 if (debug)
503 fprintf(stderr,"movie_writer_start\n");
504 h->start = ng_get_timestamp();
505 if (h->afmt.fmtid != AUDIO_NONE)
506 if (0 != h->dsp->startrec(h->hdsp))
507 rc = -1;
508 if (h->vfmt.fmtid != VIDEO_NONE)
509 if (0 != drv->startvideo(h_drv,h->fps,h->slots))
510 rc = -1;
511 if (h->afmt.fmtid != AUDIO_NONE)
512 pthread_create(&h->raudio,NULL,record_audio_thread,h);
513 pthread_create(&h->tflush,NULL,flushit,NULL);
514 return rc;
515 }
516
517 int
movie_writer_stop(struct movie_handle * h)518 movie_writer_stop(struct movie_handle *h)
519 {
520 char line[128];
521 uint64_t stopby;
522 int frames,i;
523 void *dummy;
524
525 if (debug)
526 fprintf(stderr,"movie_writer_stop\n");
527
528 if (h->vfmt.fmtid != VIDEO_NONE && h->afmt.fmtid != AUDIO_NONE) {
529 for (frames = 0; frames < 16; frames++) {
530 stopby = (uint64_t)(h->frames + frames) * (uint64_t)1000000000000ULL / h->fps;
531 if (stopby > h->ats)
532 break;
533 }
534 frames++;
535 h->stopby = (uint64_t)(h->frames + frames) * (uint64_t)1000000000000ULL / h->fps;
536 while (frames) {
537 movie_grab_put_video(h,NULL);
538 frames--;
539 }
540 } else if (h->afmt.fmtid != AUDIO_NONE) {
541 h->stopby = h->ats;
542 }
543
544 /* send EOF */
545 if (h->cthreads)
546 fifo_put(&h->cfifo,NULL);
547 else
548 fifo_put(&h->vfifo,NULL);
549
550 /* join threads */
551 if (h->afmt.fmtid != AUDIO_NONE) {
552 pthread_join(h->raudio,&dummy);
553 pthread_join(h->taudio,&dummy);
554 }
555 if (h->vfmt.fmtid != VIDEO_NONE)
556 pthread_join(h->tvideo,&dummy);
557 for (i = 0; i < h->cthreads; i++)
558 pthread_join(h->tconv[i],&dummy);
559 pthread_cancel(h->tflush);
560 pthread_join(h->tflush,&dummy);
561
562 /* close file */
563 h->writer->wr_close(h->handle);
564 if (h->afmt.fmtid != AUDIO_NONE)
565 h->dsp->close(h->hdsp);
566 if (h->vfmt.fmtid != VIDEO_NONE)
567 drv->stopvideo(h_drv);
568
569 /* fifo stats */
570 sprintf(line, "fifo max fill: audio %d/%d, video %d/%d, convert %d/%d",
571 h->afifo.max,h->afifo.slots,
572 h->vfifo.max,h->vfifo.slots,
573 h->cfifo.max,h->cfifo.slots);
574 rec_status(line);
575
576 free(h);
577 return 0;
578 }
579
580 /*-------------------------------------------------------------------------*/
581
582 static void
movie_print_timestamps(struct movie_handle * h)583 movie_print_timestamps(struct movie_handle *h)
584 {
585 char line[128];
586
587 if (NULL == rec_status)
588 return;
589
590 sprintf(line,"rec %d:%02d.%02d - a/r: %c%d.%02ds [%d], a/v: %c%d.%02ds [%d]",
591 (int)(h->rts / 1000000000 / 60),
592 (int)(h->rts / 1000000000 % 60),
593 (int)((h->rts % 1000000000) / 10000000),
594 (h->rdrift > 0) ? '+' : '-',
595 (int)((h->rdrift / 1000000000)),
596 (int)((h->rdrift % 1000000000) / 10000000),
597 (int)(h->rdrift * h->fps / (uint64_t)1000000000000ULL),
598 (h->vdrift > 0) ? '+' : '-',
599 (int)((h->vdrift / 1000000000)),
600 (int)((h->vdrift % 1000000000) / 10000000),
601 (int)(h->vdrift * h->fps / (uint64_t)1000000000000ULL));
602 rec_status(line);
603 }
604
605 int
movie_grab_put_video(struct movie_handle * h,struct ng_video_buf ** ret)606 movie_grab_put_video(struct movie_handle *h, struct ng_video_buf **ret)
607 {
608 struct ng_video_buf *buf;
609 int expected,rc;
610
611 if (debug > 1)
612 fprintf(stderr,"grab_put_video\n");
613
614 /* fetch next frame */
615 buf = ng_grabber_grab_image(0);
616 if (NULL == buf) {
617 if (debug)
618 fprintf(stderr,"grab_put_video: grab image failed\n");
619 return -1;
620 }
621 #if 0 /* FIXME */
622 buf = ng_filter_single(cur_filter,buf);
623 #endif
624
625 /* rate control */
626 expected = (buf->info.ts - h->vdrift) * h->fps / (uint64_t)1000000000000ULL;
627 if (expected < h->frames-1) {
628 if (debug > 1)
629 fprintf(stderr,"rate: ignoring frame [%d %d]\n",
630 expected, h->frames);
631 ng_release_video_buf(buf);
632 return 0;
633 }
634 if (expected > h->frames+1) {
635 fprintf(stderr,"rate: queueing frame twice (%d)\n",
636 expected-h->frames);
637 buf->info.twice++;
638 h->frames++;
639 }
640 h->frames++;
641 h->vts = buf->info.ts;
642 buf->info.seq = h->seq;
643
644 /* return a pointer to the frame if requested */
645 if (NULL != ret) {
646 buf->refcount++;
647 *ret = buf;
648 }
649
650 /* put into fifo */
651 if (h->cthreads)
652 rc = fifo_put(&h->cfifo,buf);
653 else
654 rc = fifo_put(&h->vfifo,buf);
655 if (0 != rc) {
656 ng_release_video_buf(buf);
657 return h->frames;
658 }
659 h->seq++;
660
661 /* feedback */
662 movie_print_timestamps(h);
663 return h->frames;
664 }
665