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