virtualize the interface for video grabbers, which should
authorLuigi Rizzo <rizzo@icir.org>
Sat, 29 Dec 2007 01:10:14 +0000 (01:10 +0000)
committerLuigi Rizzo <rizzo@icir.org>
Sat, 29 Dec 2007 01:10:14 +0000 (01:10 +0000)
make it easier to add support for more grabbers (V4L2,
firewire, and so on).

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@95288 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/console_gui.c
channels/console_video.c

index a8c095b..f733a1a 100644 (file)
@@ -503,17 +503,6 @@ static void handle_keyboard_input(struct video_desc *env, SDLKey key)
        return;
 }
 
-/*
- * Check if the grab point is inside the X screen.
- *
- * x represent the new grab value
- * limit represent the upper value to use
- */
-static int boundary_checks(int x, int limit)
-{
-       return (x <= 0) ? 0 : (x > limit ? limit : x);
-}
-
 /* implement superlinear acceleration on the movement */
 static int move_accel(int delta)
 {
@@ -521,6 +510,7 @@ static int move_accel(int delta)
        return (delta > 0) ? delta + d1 : delta - d1;
 }
 
+static void grabber_move(struct video_out_desc *, int dx, int dy);
 /*
  * Move the source of the captured video.
  *
@@ -529,21 +519,17 @@ static int move_accel(int delta)
  */
 static void move_capture_source(struct video_desc *env, int x_final_drag, int y_final_drag)
 {
-       int new_x, new_y;               /* new coordinates for grabbing local video */
-       int x = env->out.loc_src.x;     /* old value */
-       int y = env->out.loc_src.y;     /* old value */
+       int dx, dy;
 
        /* move the origin */
 #define POLARITY -1            /* +1 or -1 depending on the desired direction */
-       new_x = x + POLARITY*move_accel(x_final_drag - env->gui->x_drag) * 3;
-       new_y = y + POLARITY*move_accel(y_final_drag - env->gui->y_drag) * 3;
+       dx = POLARITY*move_accel(x_final_drag - env->gui->x_drag) * 3;
+       dy = POLARITY*move_accel(y_final_drag - env->gui->y_drag) * 3;
 #undef POLARITY
        env->gui->x_drag = x_final_drag;        /* update origin */
        env->gui->y_drag = y_final_drag;
 
-       /* check boundary and let the source to grab from the new points */
-       env->out.loc_src.x = boundary_checks(new_x, env->out.screen_width - env->out.loc_src.w);
-       env->out.loc_src.y = boundary_checks(new_y, env->out.screen_height - env->out.loc_src.h);
+       grabber_move(&env->out, dx, dy);
        return;
 }
 
index 3c1a7f3..fd2f9fe 100644 (file)
@@ -131,6 +131,7 @@ int console_video_formats =
 static void my_scale(struct fbuf_t *in, AVPicture *p_in,
        struct fbuf_t *out, AVPicture *p_out);
 
+struct grab_desc;              /* grabber description */
 struct video_codec_desc;       /* forward declaration */
 /*
  * Descriptor of the local source, made of the following pieces:
@@ -140,7 +141,7 @@ struct video_codec_desc;    /* forward declaration */
  *  + the encoding and RTP info, including timestamps to generate
  *    frames at the correct rate;
  *  + source-specific info, i.e. fd for /dev/video, dpy-image for x11, etc,
- *    filled in by video_open
+ *    filled in by grabber_open
  * NOTE: loc_src.data == NULL means the rest of the struct is invalid, and
  *     the video source is not available.
  */
@@ -158,7 +159,7 @@ struct video_out_desc {
 
        int sendvideo;
 
-       struct fbuf_t   loc_src;        /* local source buffer, allocated in video_open() */
+       struct fbuf_t   loc_src_geometry;       /* local source geometry only (from config file) */
        struct fbuf_t   enc_out;        /* encoder output buffer, allocated in video_out_init() */
 
        struct video_codec_desc *enc;   /* encoder */
@@ -169,14 +170,8 @@ struct video_out_desc {
        int             mtu;
        struct timeval  last_frame;     /* when we read the last frame ? */
 
-       /* device specific info */
-       int             fd;             /* file descriptor, for webcam */
-#ifdef HAVE_X11
-       Display         *dpy;                   /* x11 grabber info */
-       XImage          *image;
-       int             screen_width;   /* width of X screen */
-       int             screen_height;  /* height of X screen */
-#endif
+       struct grab_desc *grabber;
+       void            *grabber_data;
 };
 
 /*
@@ -231,28 +226,37 @@ static void fbuf_free(struct fbuf_t *b)
 
 /*------ end codec specific code -----*/
 
+/* descriptor for a grabber */
+struct grab_desc {
+       const char *name;
+       void *(*open)(const char *name, struct fbuf_t *geom, int fps);
+       struct fbuf_t *(*read)(void *d);
+       void (*move)(void *d, int dx, int dy);
+       void *(*close)(void *d);
+};
 
-/* Video4Linux stuff is only used in video_open() */
-#ifdef HAVE_VIDEODEV_H
-#include <linux/videodev.h>
-#endif
 
-/*!
- * Open the local video source and allocate a buffer
- * for storing the image. Return 0 on success, -1 on error
- */
-static int video_open(struct video_out_desc *v)
+#ifdef HAVE_X11
+struct grab_x11_desc {
+       Display         *dpy;                   /* x11 grabber info */
+       XImage          *image;
+       int             screen_width;   /* width of X screen */
+       int             screen_height;  /* height of X screen */
+       struct fbuf_t   b;
+};
+
+static void *grab_x11_open(const char *name, struct fbuf_t *geom, int fps)
 {
-       struct fbuf_t *b = &v->loc_src;
-       if (b->data)    /* buffer allocated means device already open */
-               return v->fd;
-       v->fd = -1;
-       /*
-        * if the device is "X11", then open the x11 grabber
-        */
-    if (!strcasecmp(v->videodevice, "X11")) {
        XImage *im;
        int screen_num;
+       struct grab_x11_desc *v;
+       struct fbuf_t *b;
+
+       if (strcasecmp(name, "X11"))
+               return NULL;    /* not us */
+       v = ast_calloc(1, sizeof(*v));
+       if (v == NULL)
+               return NULL;    /* no memory */
 
        /* init the connection with the X server */
        v->dpy = XOpenDisplay(NULL);
@@ -261,6 +265,8 @@ static int video_open(struct video_out_desc *v)
                goto error;
        }
 
+       v->b = *geom;   /* copy geometry */
+       b = &v->b;      /* shorthand */
        /* find width and height of the screen */
        screen_num = DefaultScreen(v->dpy);
        v->screen_width = DisplayWidth(v->dpy, screen_num);
@@ -290,24 +296,106 @@ static int video_open(struct video_out_desc *v)
 
        /* set the pointer but not the size as this is not malloc'ed */
        b->data = (uint8_t *)im->data;
-       v->fd = -2;
-    }
+       return v;
+
+error:
+       /* XXX maybe XDestroy (v->image) ? */
+       if (v->dpy)
+               XCloseDisplay(v->dpy);
+       v->dpy = NULL;
+       ast_free(v);
+       return NULL;
+}
+
+static struct fbuf_t *grab_x11_read(void *desc)
+{
+       /* read frame from X11 */
+       struct grab_x11_desc *v = desc;
+       struct fbuf_t *b = &v->b;
+
+       XGetSubImage(v->dpy,
+               RootWindow(v->dpy, DefaultScreen(v->dpy)),
+                       b->x, b->y, b->w, b->h, AllPlanes, ZPixmap, v->image, 0, 0);
+
+               b->data = (uint8_t *)v->image->data;
+       return b;
+}
+
+static int boundary_checks(int x, int limit)
+{
+        return (x <= 0) ? 0 : (x > limit ? limit : x);
+}
+
+/* move the origin for the grabbed area */
+static void grab_x11_move(void *desc, int dx, int dy)
+{
+       struct grab_x11_desc *v = desc;
+
+        v->b.x = boundary_checks(v->b.x + dx, v->screen_width - v->b.w);
+        v->b.y = boundary_checks(v->b.y + dy, v->screen_height - v->b.h);
+}
+
+static void *grab_x11_close(void *desc)
+{
+       struct grab_x11_desc *v = desc;
+
+       XCloseDisplay(v->dpy);
+       v->dpy = NULL;
+       v->image = NULL;
+       ast_free(v);
+       return NULL;
+}
+
+static struct grab_desc grab_x11_desc = {
+       .name = "X11",
+       .open = grab_x11_open,
+       .read = grab_x11_read,
+       .move = grab_x11_move,
+       .close = grab_x11_close,
+};
+
+#endif /* HAVE_X11 */
+
+/* Video4Linux stuff is only used in grabber_open() */
 #ifdef HAVE_VIDEODEV_H
-    else {
-       /* V4L specific */
+#include <linux/videodev.h>
+
+struct grab_v4l1_desc {
+       int fd;
+       int fps;
+       struct fbuf_t   b;
+};
+
+/*!
+ * Open the local video source and allocate a buffer
+ * for storing the image. Return 0 on success, -1 on error
+ */
+static void *grab_v4l1_open(const char *dev, struct fbuf_t *geom, int fps)
+{
        struct video_window vw = { 0 }; /* camera attributes */
        struct video_picture vp;
-       int i;
-       const char *dev = v->videodevice;
+       int fd, i;
+       struct grab_v4l1_desc *v;
+       struct fbuf_t *b;
 
-       v->fd = open(dev, O_RDONLY | O_NONBLOCK);
-       if (v->fd < 0) {
-               ast_log(LOG_WARNING, "error opening camera %s\n", v->videodevice);
-               return v->fd;
+       fd = open(dev, O_RDONLY | O_NONBLOCK);
+       if (fd < 0) {
+               ast_log(LOG_WARNING, "error opening camera %s\n", dev);
+               return NULL;
+       }
+
+       v = ast_calloc(1, sizeof(*v));
+       if (v == NULL) {
+               ast_log(LOG_WARNING, "no memory for camera %s\n", dev);
+               close(fd);
+               return NULL;    /* no memory */
        }
+       v->fd = fd;
+       v->b = *geom;
+       b = &v->b;      /* shorthand */
 
-       i = fcntl(v->fd, F_GETFL);
-       if (-1 == fcntl(v->fd, F_SETFL, i | O_NONBLOCK)) {
+       i = fcntl(fd, F_GETFL);
+       if (-1 == fcntl(fd, F_SETFL, i | O_NONBLOCK)) {
                /* non fatal, just emit a warning */
                ast_log(LOG_WARNING, "error F_SETFL for %s [%s]\n",
                        dev, strerror(errno));
@@ -316,15 +404,15 @@ static int video_open(struct video_out_desc *v)
         * In principle we could retry with a different format if the
         * one we are asking for is not supported.
         */
-       vw.width = v->loc_src.w;
-       vw.height = v->loc_src.h;
-       vw.flags = v->fps << 16;
-       if (ioctl(v->fd, VIDIOCSWIN, &vw) == -1) {
+       vw.width = b->w;
+       vw.height = b->h;
+       vw.flags = fps << 16;
+       if (ioctl(fd, VIDIOCSWIN, &vw) == -1) {
                ast_log(LOG_WARNING, "error setting format for %s [%s]\n",
                        dev, strerror(errno));
                goto error;
        }
-       if (ioctl(v->fd, VIDIOCGPICT, &vp) == -1) {
+       if (ioctl(fd, VIDIOCGPICT, &vp) == -1) {
                ast_log(LOG_WARNING, "error reading picture info\n");
                goto error;
        }
@@ -351,44 +439,95 @@ static int video_open(struct video_out_desc *v)
        b->size = (b->w * b->h * 3)/2;  /* yuv411 */
        ast_log(LOG_WARNING, "videodev %s opened, size %dx%d %d\n",
                dev, b->w, b->h, b->size);
-       v->loc_src.data = ast_calloc(1, b->size);
+       b->data = ast_calloc(1, b->size);
        if (!b->data) {
                ast_log(LOG_WARNING, "error allocating buffer %d bytes\n",
                        b->size);
                goto error;
        }
        ast_log(LOG_WARNING, "success opening camera\n");
-    }
-#endif /* HAVE_VIDEODEV_H */
-
-       if (v->image == NULL && v->fd < 0)
-               goto error;
-       b->used = 0;
-       return 0;
+       return v;
 
 error:
-       ast_log(LOG_WARNING, "fd %d dpy %p img %p data %p\n",
-               v->fd, v->dpy, v->image, v->loc_src.data);
-       /* XXX maybe XDestroy (v->image) ? */
-       if (v->dpy)
-               XCloseDisplay(v->dpy);
-       v->dpy = NULL;
-       if (v->fd >= 0)
-               close(v->fd);
+       close(v->fd);
+       fbuf_free(b);
+       ast_free(v);
+       return NULL;
+}
+
+static struct fbuf_t *grab_v4l1_read(void *desc)
+{
+       struct grab_v4l1_desc *v = desc;
+       struct fbuf_t *b = &v->b;
+       for (;;) {
+               int r, l = b->size - b->used;
+               r = read(v->fd, b->data + b->used, l);
+               // ast_log(LOG_WARNING, "read %d of %d bytes from webcam\n", r, l);
+               if (r < 0)      /* read error */
+                       break;
+               if (r == 0)     /* no data */
+                       break;
+               b->used += r;
+               if (r == l) {
+                       b->used = 0; /* prepare for next frame */
+                       return b;
+               }
+       }
+       return NULL;
+}
+
+static void *grab_v4l1_close(void *desc)
+{
+       struct grab_v4l1_desc *v = desc;
+
+       close(v->fd);
        v->fd = -1;
-       fbuf_free(&v->loc_src);
-       return -1;
+       fbuf_free(&v->b);
+       ast_free(v);
+       return NULL;
+}
+
+static struct grab_desc grab_v4l1_desc = {
+       .name = "v4l1",
+       .open = grab_v4l1_open,
+       .read = grab_v4l1_read,
+       .close = grab_v4l1_close,
+};
+#endif /* HAVE_VIDEODEV_H */
+
+static struct grab_desc *my_grabbers[] = {
+       &grab_x11_desc,
+       &grab_v4l1_desc,
+       NULL
+};
+
+/* try to open a video source, return 0 on success, 1 on error
+ */
+static int grabber_open(struct video_out_desc *v)
+{
+       struct grab_desc *g;
+       void *g_data;
+       int i;
+
+       for (i = 0; (g = my_grabbers[i]); i++) {
+               g_data = g->open(v->videodevice, &v->loc_src_geometry, v->fps);
+               if (g_data) {
+                       v->grabber = g;
+                       v->grabber_data = g_data;
+                       return 0;
+               }
+       }
+       return 1; /* no source found */
 }
 
 /*! \brief complete a buffer from the local video source.
  * Called by get_video_frames(), in turn called by the video thread.
  */
-static int video_read(struct video_out_desc *v)
+static struct fbuf_t *grabber_read(struct video_out_desc *v)
 {
        struct timeval now = ast_tvnow();
-       struct fbuf_t *b = &v->loc_src;
 
-       if (b->data == NULL)    /* not initialized */
+       if (v->grabber == NULL) /* not initialized */
                return 0;
 
        /* check if it is time to read */
@@ -397,36 +536,13 @@ static int video_read(struct video_out_desc *v)
        if (ast_tvdiff_ms(now, v->last_frame) < 1000/v->fps)
                return 0;       /* too early */
        v->last_frame = now; /* XXX actually, should correct for drift */
+       return v->grabber->read(v->grabber_data);
+}
 
-#ifdef HAVE_X11
-       if (v->image) {
-               /* read frame from X11 */
-               AVPicture p;
-               XGetSubImage(v->dpy,
-                   RootWindow(v->dpy, DefaultScreen(v->dpy)),
-                       b->x, b->y, b->w, b->h, AllPlanes, ZPixmap, v->image, 0, 0);
-
-               b->data = (uint8_t *)v->image->data;
-               fill_pict(b, &p);
-               return p.linesize[0] * b->h;
-       }
-#endif
-       if (v->fd < 0)                  /* no other source */
-               return 0;
-       for (;;) {
-               int r, l = v->loc_src.size - v->loc_src.used;
-               r = read(v->fd, v->loc_src.data + v->loc_src.used, l);
-               // ast_log(LOG_WARNING, "read %d of %d bytes from webcam\n", r, l);
-               if (r < 0)      /* read error */
-                       return 0;
-               if (r == 0)     /* no data */
-                       return 0;
-               v->loc_src.used += r;
-               if (r == l) {
-                       v->loc_src.used = 0; /* prepare for next frame */
-                       return v->loc_src.size;
-               }
-       }
+static void grabber_move(struct video_out_desc *v, int dx, int dy)
+{
+       if (v->grabber && v->grabber->move)
+                v->grabber->move(v->grabber_data, dx, dy);
 }
 
 /* Helper function to process incoming video.
@@ -476,19 +592,15 @@ static int video_out_uninit(struct video_desc *env)
                av_free(v->enc_in_frame);
                v->enc_in_frame = NULL;
        }
-       v->codec = NULL;        /* only a reference */
-       
-       fbuf_free(&v->loc_src);
+       v->codec = NULL;        /* nothing to free, this is only a reference */
+       /* release the buffers */
        fbuf_free(&env->enc_in);
        fbuf_free(&v->enc_out);
-       if (v->image) { /* X11 grabber */
-               XCloseDisplay(v->dpy);
-               v->dpy = NULL;
-               v->image = NULL;
-       }
-       if (v->fd >= 0) {
-               close(v->fd);
-               v->fd = -1;
+       /* close the grabber */
+       sleep(1);
+       if (v->grabber) {
+               v->grabber_data = v->grabber->close(v->grabber_data);
+               v->grabber = NULL;
        }
        return -1;
 }
@@ -512,10 +624,6 @@ static int video_out_init(struct video_desc *env)
        v->enc_in_frame         = NULL;
        v->enc_out.data         = NULL;
 
-       if (v->loc_src.data == NULL) {
-               ast_log(LOG_WARNING, "No local source active\n");
-               return -1;      /* error, but nothing to undo yet */
-       }
        codec = map_video_format(v->enc->format, CM_WR);
        v->codec = avcodec_find_encoder(codec);
        if (!v->codec) {
@@ -786,7 +894,7 @@ int console_write_video(struct ast_channel *chan, struct ast_frame *f)
 }
 
 
-/*! \brief read a frame from webcam or X11 through video_read(),
+/*! \brief read a frame from webcam or X11 through grabber_read(),
  * display it,  then encode and split it.
  * Return a list of ast_frame representing the video fragments.
  * The head pointer is returned by the function, the tail pointer
@@ -796,14 +904,9 @@ static struct ast_frame *get_video_frames(struct video_desc *env, struct ast_fra
 {
        struct video_out_desc *v = &env->out;
        struct ast_frame *dummy;
+       struct fbuf_t *loc_src = grabber_read(v);
 
-       if (!v->loc_src.data) {
-               static volatile int a = 0;
-               if (a++ < 2)
-                       ast_log(LOG_WARNING, "fail, no loc_src buffer\n");
-               return NULL;
-       }
-       if (!video_read(v))
+       if (!loc_src)
                return NULL;    /* can happen, e.g. we are reading too early */
 
        if (tail == NULL)
@@ -812,7 +915,7 @@ static struct ast_frame *get_video_frames(struct video_desc *env, struct ast_fra
        /* Scale the video for the encoder, then use it for local rendering
         * so we will see the same as the remote party.
         */
-       my_scale(&v->loc_src, NULL, &env->enc_in, NULL);
+       my_scale(loc_src, NULL, &env->enc_in, NULL);
        show_frame(env, WIN_LOCAL);
        if (!v->sendvideo)
                return NULL;
@@ -855,19 +958,23 @@ static void *video_thread(void *arg)
                setenv("DISPLAY", save_display, 1);
 
         /* initialize grab coordinates */
-        env->out.loc_src.x = 0;
-        env->out.loc_src.y = 0;
+        env->out.loc_src_geometry.x = 0;
+        env->out.loc_src_geometry.y = 0;
 
        ast_mutex_init(&env->dec_lock); /* used to sync decoder and renderer */
 
-       if (video_open(&env->out)) {
+       if (grabber_open(&env->out)) {
                ast_log(LOG_WARNING, "cannot open local video source\n");
        } else {
-               /* try to register the fd. Unfortunately, if the webcam
-                * driver does not support select/poll we are out of luck.
+#if 0
+               /* In principle, try to register the fd.
+                * In practice, many webcam drivers do not support select/poll,
+                * so don't bother and instead read periodically from the
+                * video thread.
                 */
                if (env->out.fd >= 0)
                        ast_channel_set_fd(env->owner, 1, env->out.fd);
+#endif
                video_out_init(env);
        }
 
@@ -925,11 +1032,11 @@ static void *video_thread(void *arg)
                }
            }
 
+               if (env->shutdown)
+                       break;
                f = get_video_frames(env, &p);  /* read and display */
                if (!f)
                        continue;
-               if (env->shutdown)
-                       break;
                chan = env->owner;
                ast_channel_lock(chan);
 
@@ -980,7 +1087,7 @@ static void copy_geometry(struct fbuf_t *src, struct fbuf_t *dst)
  */
 static void init_env(struct video_desc *env)
 {
-       struct fbuf_t *c = &(env->out.loc_src);         /* local source */
+       struct fbuf_t *c = &(env->out.loc_src_geometry);                /* local source */
        struct fbuf_t *ei = &(env->enc_in);             /* encoder input */
        struct fbuf_t *ld = &(env->loc_dpy);    /* local display */
        struct fbuf_t *rd = &(env->rem_dpy);            /* remote display */
@@ -1110,7 +1217,7 @@ int console_video_cli(struct video_desc *env, const char *var, int fd)
                }
                ast_cli(fd, "sizes: video %dx%d camera %dx%d local %dx%d remote %dx%d in %dx%d\n",
                        env->enc_in.w, env->enc_in.h,
-                       env->out.loc_src.w, env->out.loc_src.h,
+                       env->out.loc_src_geometry.w, env->out.loc_src_geometry.h,
                        env->loc_dpy.w, env->loc_dpy.h,
                        env->rem_dpy.w, env->rem_dpy.h,
                        in_w, in_h);
@@ -1156,7 +1263,7 @@ int console_video_config(struct video_desc **penv,
        CV_STR("videodevice", env->out.videodevice);
        CV_BOOL("sendvideo", env->out.sendvideo);
        CV_F("video_size", video_geom(&env->enc_in, val));
-       CV_F("camera_size", video_geom(&env->out.loc_src, val));
+       CV_F("camera_size", video_geom(&env->out.loc_src_geometry, val));
        CV_F("local_size", video_geom(&env->loc_dpy, val));
        CV_F("remote_size", video_geom(&env->rem_dpy, val));
        CV_STR("keypad", env->keypad_file);