Logo Search packages:      
Sourcecode: xawtv version File versions  Download package

capture.c

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include "grab-ng.h"
#include "commands.h"       /* FIXME: *drv globals */
#include "sound.h"
#include "capture.h"
#include "webcam.h"

#define MAX_THREADS    4
#define REORDER_SIZE  32

/*-------------------------------------------------------------------------*/
/* data fifos (audio/video)                                                */

void
fifo_init(struct FIFO *fifo, char *name, int slots, int writers)
{
    pthread_mutex_init(&fifo->lock, NULL);
    pthread_cond_init(&fifo->hasdata, NULL);
    fifo->name    = name;
    fifo->slots   = slots;
    fifo->writers = writers;
    fifo->read    = 0;
    fifo->write   = 0;
    fifo->eof     = 0;
    fifo->max     = 0;
}

int
fifo_put(struct FIFO *fifo, void *data)
{
    int full;
    
    pthread_mutex_lock(&fifo->lock);
    if (NULL == data) {
      fifo->eof++;
      if (debug)
          fprintf(stderr,"fifo %s: EOF %d/%d\n",
                fifo->name,fifo->eof,fifo->writers);
      if (fifo->writers == fifo->eof)
          pthread_cond_broadcast(&fifo->hasdata);
      pthread_mutex_unlock(&fifo->lock);
      return 0;
    }
    if ((fifo->write + 1) % fifo->slots == fifo->read) {
      pthread_mutex_unlock(&fifo->lock);
      fprintf(stderr,"fifo %s is full\n",fifo->name);
      return -1;
    }
    if (debug > 1)
      fprintf(stderr,"put %s %d=%p [pid=%d]\n",
            fifo->name,fifo->write,data,getpid());
    fifo->data[fifo->write] = data;
    fifo->write++;
    full = (fifo->write + fifo->slots - fifo->read) % fifo->slots;
    if (fifo->max < full)
      fifo->max = full;
    if (fifo->write >= fifo->slots)
      fifo->write = 0;
    pthread_cond_signal(&fifo->hasdata);
    pthread_mutex_unlock(&fifo->lock);
    return 0;
}

void*
fifo_get(struct FIFO *fifo)
{
    void *data;

    pthread_mutex_lock(&fifo->lock);
    while (fifo->write == fifo->read && fifo->writers != fifo->eof) {
      pthread_cond_wait(&fifo->hasdata, &fifo->lock);
    }
    if (fifo->write == fifo->read) {
      pthread_cond_signal(&fifo->hasdata);
      pthread_mutex_unlock(&fifo->lock);
      return NULL;
    }
    if (debug > 1)
      fprintf(stderr,"get %s %d=%p [pid=%d]\n",
            fifo->name,fifo->read,fifo->data[fifo->read],getpid());
    data = fifo->data[fifo->read];
    fifo->read++;
    if (fifo->read >= fifo->slots)
      fifo->read = 0;
    pthread_mutex_unlock(&fifo->lock);
    return data;
}

static void*
flushit(void *arg)
{
    int old;

    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&old);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&old);
    for (;;) {
      sleep(1);
      sync();
    }
    return NULL;
}

/*-------------------------------------------------------------------------*/
/* color space conversion / compression thread                             */

struct ng_convthread_handle {
    /* converter data / state */
    struct ng_convert_handle *c;

    /* thread data */
    struct FIFO              *in;
    struct FIFO              *out;
};

void*
ng_convert_thread(void *arg)
{
    struct ng_convthread_handle *h = arg;
    struct ng_video_buf *in, *out;
    
    if (debug)
      fprintf(stderr,"convert_thread start [pid=%d]\n",getpid());
    ng_convert_init(h->c);
    for (;;) {
      in  = fifo_get(h->in);
      if (NULL == in)
          break;
      out = ng_convert_frame(h->c,NULL,in);
      if (NULL != webcam && 0 == webcam_put(webcam,out)) {
          free(webcam);
          webcam = NULL;
      }
      fifo_put(h->out,out);
    }
    fifo_put(h->out,NULL);
    ng_convert_fini(h->c);
    if (debug)
      fprintf(stderr,"convert_thread done [pid=%d]\n",getpid());
    return NULL;
}

/*-------------------------------------------------------------------------*/
/* parameter negotiation -- look what the driver can do and what           */
/* convert functions are available                                         */

int
ng_grabber_setformat(struct ng_video_fmt *fmt, int fix_ratio)
{
    struct ng_video_fmt gfmt;
    int rc;
    
    /* no capture support */
    if (!(f_drv & CAN_CAPTURE))
      return -1;

    /* try setting the format */
    gfmt = *fmt;
    rc = drv->setformat(h_drv,&gfmt);
    if (debug)
      fprintf(stderr,"setformat: %s (%dx%d): %s\n",
            ng_vfmt_to_desc[gfmt.fmtid],
            gfmt.width,gfmt.height,
            (0 == rc) ? "ok" : "failed");
    if (0 != rc)
      return -1;

    if (fix_ratio) {
      /* fixup aspect ratio if needed */
      ng_ratio_fixup(&gfmt.width, &gfmt.height, NULL, NULL);
      gfmt.bytesperline = 0;
      if (0 != drv->setformat(h_drv,&gfmt)) {
          fprintf(stderr,"Oops: ratio size renegotiation failed\n");
          exit(1);
      }
    }

    /* return the real format the grabber uses now */
    *fmt = gfmt;
    return 0;
}

struct ng_video_conv*
ng_grabber_findconv(struct ng_video_fmt *fmt,
                int fix_ratio)
{
    struct ng_video_fmt  gfmt;
    struct ng_video_conv *conv;
    int i;
    
    /* check all available conversion functions */
    for (i = 0;;) {
      conv = ng_conv_find_to(fmt->fmtid, &i);
      if (NULL == conv)
          break;
      gfmt = *fmt;
      gfmt.fmtid = conv->fmtid_in;
      if (0 == ng_grabber_setformat(&gfmt,fix_ratio))
          goto found;
    }
    fprintf(stderr,"no way to get: %dx%d %s\n",
          fmt->width,fmt->height,ng_vfmt_to_desc[fmt->fmtid]);
    return NULL;

 found:
    *fmt = gfmt;
    return conv;
}

struct ng_video_buf*
ng_grabber_grab_image(int single)
{
    return single ? drv->getimage(h_drv) : drv->nextframe(h_drv);
}

struct ng_video_buf*
ng_grabber_get_image(struct ng_video_fmt *fmt)
{
    struct ng_video_fmt gfmt;
    struct ng_video_conv *conv;
    struct ng_convert_handle *ch;
    struct ng_video_buf *buf;
    
    if (0 == ng_grabber_setformat(fmt,1))
      return ng_grabber_grab_image(1);
    gfmt = *fmt;
    if (NULL == (conv = ng_grabber_findconv(&gfmt,1)))
      return NULL;
    ch = ng_convert_alloc(conv,&gfmt,fmt);
    if (NULL == (buf = ng_grabber_grab_image(1)))
      return NULL;
    buf = ng_convert_single(ch,buf);
    return buf;
}

/*-------------------------------------------------------------------------*/

struct movie_handle {
    /* general */
    pthread_mutex_t           lock;
    const struct ng_writer    *writer;
    void                      *handle;
    pthread_t                 tflush;
    uint64_t                  start;
    uint64_t                  rts;
    uint64_t                  stopby;
    int                       slots;

    /* video */
    struct ng_video_fmt       vfmt;
    int                       fps;
    int                       frames;
    int                       seq;
    struct FIFO               vfifo;
    pthread_t                 tvideo;
    uint64_t                  vts;

    /* video converter thread */
    struct FIFO               cfifo;
    int                       cthreads;
    struct ng_convthread_handle *hconv[MAX_THREADS];
    pthread_t                 tconv[MAX_THREADS];

    /* audio */
    const struct ng_dsp_driver *dsp;
    void                      *hdsp;
    struct ng_audio_fmt       afmt;
    unsigned long             bytes_per_sec;
    unsigned long             bytes;
    struct FIFO               afifo;
    pthread_t                 taudio;
    pthread_t                 raudio;
    uint64_t                  ats;

    uint64_t                  rdrift;
    uint64_t                  vdrift;
};

static void*
writer_audio_thread(void *arg)
{
    struct movie_handle *h = arg;
    struct ng_audio_buf *buf;

    if (debug)
      fprintf(stderr,"writer_audio_thread start [pid=%d]\n",getpid());
    for (;;) {
      buf = fifo_get(&h->afifo);
      if (NULL == buf)
          break;
      pthread_mutex_lock(&h->lock);
      h->writer->wr_audio(h->handle,buf);
      pthread_mutex_unlock(&h->lock);
      free(buf);
    }
    if (debug)
      fprintf(stderr,"writer_audio_thread done\n");
    return NULL;
}

/*
 * with multiple compression threads we might receive
 * the frames out-of-order
 */
static void*
writer_video_thread(void *arg)
{
    struct movie_handle *h = arg;
    struct ng_video_buf *buf;
    struct ng_video_buf *reorder[REORDER_SIZE];
    int seq,slot;

    if (debug)
      fprintf(stderr,"writer_video_thread start [pid=%d]\n",getpid());
    seq = 0;
    memset(&reorder,0,sizeof(reorder));
    for (;;) {
        buf = fifo_get(&h->vfifo);
      if (NULL == buf)
          break;
      slot = buf->info.seq % REORDER_SIZE;
      if (debug > 1)
          fprintf(stderr,"video write: get seq=%d [%d]\n",
                buf->info.seq,slot);
      if (reorder[slot]) {
          fprintf(stderr,"panic: reorder buffer full\n");
          exit(1);
      }
      reorder[slot] = buf;
      
      for (;;) {
          slot = seq % REORDER_SIZE;
          if (NULL == reorder[slot])
            break;
          buf = reorder[slot];
          reorder[slot] = NULL;
          if (debug > 1)
            fprintf(stderr,"video write: put seq=%d [%d/%d]\n",
                  buf->info.seq,slot,seq);
          seq++;

          pthread_mutex_lock(&h->lock);
          h->writer->wr_video(h->handle,buf);
          if (buf->info.twice)
            h->writer->wr_video(h->handle,buf);
          pthread_mutex_unlock(&h->lock);
          ng_release_video_buf(buf);
      }
    }
    if (debug)
      fprintf(stderr,"writer_video_thread done\n");
    return NULL;
}

static void*
record_audio_thread(void *arg)
{
    struct movie_handle *h = arg;
    struct ng_audio_buf *buf;

    if (debug)
      fprintf(stderr,"record_audio_thread start [pid=%d]\n",getpid());
    for (;;) {
      buf = h->dsp->read(h->hdsp,h->stopby);
      if (NULL == buf)
          break;
      if (0 == buf->size)
          continue;
      h->ats    = buf->info.ts;
      h->rts    = ng_get_timestamp() - h->start;
      h->rdrift = h->rts - h->ats;
      h->vdrift = h->vts - h->ats;
      if (0 != fifo_put(&h->afifo,buf))
          free(buf);
    }
    fifo_put(&h->afifo,NULL);
    if (debug)
      fprintf(stderr,"record_audio_thread done\n");
    return NULL;
}

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)
{
    struct movie_handle *h;
    struct ng_video_conv *conv;
    void *dummy;
    int i;

    if (debug)
      fprintf(stderr,"movie_init_writer start\n");
    h = malloc(sizeof(*h));
    if (NULL == h)
      return NULL;
    memset(h,0,sizeof(*h));
    pthread_mutex_init(&h->lock, NULL);
    h->writer = writer;
    h->slots = slots;

    /* audio */
    if (audio->fmtid != AUDIO_NONE) {
      h->dsp = ng_dsp_open(dsp,audio,1,&h->hdsp);
      if (NULL == h->dsp) {
          free(h);
          return NULL;
      }
      fifo_init(&h->afifo,"audio",slots,1);
      pthread_create(&h->taudio,NULL,writer_audio_thread,h);
      h->bytes_per_sec = ng_afmt_to_bits[audio->fmtid] *
          ng_afmt_to_channels[audio->fmtid] * audio->rate / 8;
      h->afmt = *audio;
    }

    /* video */
    if (video->fmtid != VIDEO_NONE) {
      if (0 == ng_grabber_setformat(video,1)) {
          /* native format works -- no conversion needed */
          fifo_init(&h->vfifo,"video",slots,1);
          pthread_create(&h->tvideo,NULL,writer_video_thread,h);
      } else {
          /* have to convert video frames */
          struct ng_video_fmt gfmt = *video;
          if (NULL == (conv = ng_grabber_findconv(&gfmt,1))) {
            if (h->afmt.fmtid != AUDIO_NONE)
                h->dsp->close(h->hdsp);
            free(h);
            return NULL;
          }
          h->cthreads = threads;
          if (h->cthreads < 1)
            h->cthreads = 1;
          if (h->cthreads > MAX_THREADS)
            h->cthreads = MAX_THREADS;
          fifo_init(&h->vfifo,"video",slots,h->cthreads);
          fifo_init(&h->cfifo,"conv",slots,1);
          pthread_create(&h->tvideo,NULL,writer_video_thread,h);
          for (i = 0; i < h->cthreads; i++) {
            h->hconv[i] = malloc(sizeof(struct ng_convthread_handle));
            memset(h->hconv[i],0,sizeof(struct ng_convthread_handle));
            h->hconv[i]->c   = ng_convert_alloc(conv,&gfmt,video);
            h->hconv[i]->in  = &h->cfifo;
            h->hconv[i]->out = &h->vfifo;
            pthread_create(&h->tconv[i],NULL,ng_convert_thread,
                         h->hconv[i]);
          }
      }
      h->vfmt = *video;
      h->fps  = fps;
    } 
    
    /* open file */
    h->handle = writer->wr_open(moviename,audioname,
                        video,priv_video,fps,
                        audio,priv_audio);
    if (debug)
      fprintf(stderr,"movie_init_writer end (h=%p)\n",h->handle);
    if (NULL != h->handle)
      return h;

    /* Oops -- wr_open() didn't work.  cleanup.  */
    if (h->afmt.fmtid != AUDIO_NONE) {
      pthread_cancel(h->taudio);
      pthread_join(h->taudio,&dummy);
      h->dsp->close(h->hdsp);
    }
    if (h->vfmt.fmtid != VIDEO_NONE) {
      pthread_cancel(h->tvideo);
      pthread_join(h->tvideo,&dummy);
    }
    for (i = 0; i < h->cthreads; i++) {
      pthread_cancel(h->tconv[i]);
      pthread_join(h->tconv[i],&dummy);
    }
    free(h);
    return NULL;
}

int
movie_writer_start(struct movie_handle *h)
{
    int rc = 0;

    if (debug)
      fprintf(stderr,"movie_writer_start\n");
    h->start = ng_get_timestamp();
    if (h->afmt.fmtid != AUDIO_NONE)
      if (0 != h->dsp->startrec(h->hdsp))
          rc = -1;
    if (h->vfmt.fmtid != VIDEO_NONE)
      if (0 != drv->startvideo(h_drv,h->fps,h->slots))
          rc = -1;
    if (h->afmt.fmtid != AUDIO_NONE)
      pthread_create(&h->raudio,NULL,record_audio_thread,h);
    pthread_create(&h->tflush,NULL,flushit,NULL);
    return rc;
}

int
movie_writer_stop(struct movie_handle *h)
{
    char line[128];
    uint64_t  stopby;
    int frames,i;
    void *dummy;

    if (debug)
      fprintf(stderr,"movie_writer_stop\n");

    if (h->vfmt.fmtid != VIDEO_NONE && h->afmt.fmtid != AUDIO_NONE) {
      for (frames = 0; frames < 16; frames++) {
          stopby = (uint64_t)(h->frames + frames) * (uint64_t)1000000000000 / h->fps;
          if (stopby > h->ats)
            break;
      }
      frames++;
      h->stopby = (uint64_t)(h->frames + frames) * (uint64_t)1000000000000 / h->fps;
      while (frames) {
          movie_grab_put_video(h,NULL);
          frames--;
      }
    } else if (h->afmt.fmtid != AUDIO_NONE) {
      h->stopby = h->ats;
    }

    /* send EOF */
    if (h->cthreads)
      fifo_put(&h->cfifo,NULL);
    else
      fifo_put(&h->vfifo,NULL);

    /* join threads */
    if (h->afmt.fmtid != AUDIO_NONE) {
      pthread_join(h->raudio,&dummy);
      pthread_join(h->taudio,&dummy);
    }
    if (h->vfmt.fmtid != VIDEO_NONE)
      pthread_join(h->tvideo,&dummy);
    for (i = 0; i < h->cthreads; i++)
      pthread_join(h->tconv[i],&dummy);
    pthread_cancel(h->tflush);
    pthread_join(h->tflush,&dummy);

    /* close file */
    h->writer->wr_close(h->handle);
    if (h->afmt.fmtid != AUDIO_NONE)
      h->dsp->close(h->hdsp);
    if (h->vfmt.fmtid != VIDEO_NONE)
      drv->stopvideo(h_drv);

    /* fifo stats */
    sprintf(line, "fifo max fill: audio %d/%d, video %d/%d, convert %d/%d",
          h->afifo.max,h->afifo.slots,
          h->vfifo.max,h->vfifo.slots,
          h->cfifo.max,h->cfifo.slots);
    rec_status(line);

    free(h);
    return 0;
}

/*-------------------------------------------------------------------------*/

static void
movie_print_timestamps(struct movie_handle *h)
{
    char line[128];

    if (NULL == rec_status)
      return;

    sprintf(line,"rec %d:%02d.%02d  -  a/r: %c%d.%02ds [%d], a/v: %c%d.%02ds [%d]",
          (int)(h->rts / 1000000000 / 60),
          (int)(h->rts / 1000000000 % 60),
          (int)((h->rts % 1000000000) / 10000000),
          (h->rdrift > 0) ? '+' : '-',
          (int)((abs(h->rdrift) / 1000000000)),
          (int)((abs(h->rdrift) % 1000000000) / 10000000),
          (int)(h->rdrift * h->fps / (uint64_t)1000000000000),
          (h->vdrift > 0) ? '+' : '-',
          (int)((abs(h->vdrift) / 1000000000)),
          (int)((abs(h->vdrift) % 1000000000) / 10000000),
          (int)(h->vdrift * h->fps / (uint64_t)1000000000000));
    rec_status(line);
}

int
movie_grab_put_video(struct movie_handle *h, struct ng_video_buf **ret)
{
    struct ng_video_buf *buf;
    int expected;

    if (debug > 1)
      fprintf(stderr,"grab_put_video\n");

    /* fetch next frame */
    buf = ng_grabber_grab_image(0);
    if (NULL == buf) {
      if (debug)
          fprintf(stderr,"grab_put_video: grab image failed\n");
      return -1;
    }
#if 0 /* FIXME */
    buf = ng_filter_single(cur_filter,buf);
#endif

    /* rate control */
    expected = (buf->info.ts - h->vdrift) * h->fps / (uint64_t)1000000000000;
    if (expected < h->frames-1) {
      if (debug > 1)
          fprintf(stderr,"rate: ignoring frame [%d %d]\n",
                expected, h->frames);
      ng_release_video_buf(buf);
      return 0;
    }
    if (expected > h->frames+1) {
      fprintf(stderr,"rate: queueing frame twice (%d)\n",
            expected-h->frames);
      buf->info.twice++;
      h->frames++;
    }
    h->frames++;
    h->vts = buf->info.ts;
    buf->info.seq = h->seq++;

    /* return a pointer to the frame if requested */
    if (NULL != ret) {
      buf->refcount++;
      *ret = buf;
    }
    
    /* put into fifo */
    if (h->cthreads) {
      if (0 != fifo_put(&h->cfifo,buf))
          ng_release_video_buf(buf);    
    } else {
      if (0 != fifo_put(&h->vfifo,buf))
          ng_release_video_buf(buf);    
    }

    /* feedback */
    movie_print_timestamps(h);
    return h->frames;
}

Generated by  Doxygen 1.6.0   Back to index