(closes issue #13557)
[asterisk/asterisk.git] / channels / console_video.c
index 3c1a7f3..88bf807 100644 (file)
@@ -1,4 +1,20 @@
 /*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright 2007-2008, Marta Carbone, Sergio Fadda, Luigi Rizzo
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*
  * Experimental support for video sessions. We use SDL for rendering, ffmpeg
  * as the codec library for encoding and decoding, and Video4Linux and X11
  * to generate the local video stream.
@@ -21,6 +37,7 @@
 //#define OLD_FFMPEG   1       /* set for old ffmpeg with no swscale */
 
 #include "asterisk.h"
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include <sys/ioctl.h>
 #include "asterisk/cli.h"
 #include "asterisk/file.h"
@@ -70,6 +87,9 @@ codec parameters), as follows:
 
  rem_dpy       the format used to display the remote stream
 
+ src_dpy       is the format used to display the local video source streams
+       The number of these fbuf_t is determined at run time, with dynamic allocation
+
 We store the format info together with the buffer storing the data.
 As a future optimization, a format/buffer may reference another one
 if the formats are equivalent. This will save some unnecessary format
@@ -117,6 +137,11 @@ void console_video_uninit(struct video_desc *env)
 {
 }
 
+int get_gui_startup(struct video_desc* env)
+{
+       return 0; /* no gui here */
+}
+
 int console_video_formats = 0;
 
 #else /* defined(HAVE_FFMPEG) && defined(HAVE_SDL) */
@@ -128,9 +153,27 @@ int console_video_formats =
 
 
 
+/* function to scale and encode buffers */
 static void my_scale(struct fbuf_t *in, AVPicture *p_in,
        struct fbuf_t *out, AVPicture *p_out);
 
+/*
+ * this structure will be an entry in the table containing
+ * every device specified in the file oss.conf, it contains various infomation
+ * about the device
+ */
+struct video_device {
+       char                    *name;          /* name of the device                   */
+       /* allocated dynamically (see fill_table function) */
+       struct grab_desc        *grabber;       /* the grabber for the device type      */
+       void                    *grabber_data;  /* device's private data structure      */
+       struct fbuf_t           *dev_buf;       /* buffer for incoming data             */
+       struct timeval          last_frame;     /* when we read the last frame ?        */
+       int                     status_index;   /* what is the status of the device (source) */
+       /* status index is set using the IS_ON, IS_PRIMARY and IS_SECONDARY costants */
+       /* status_index is the index of the status message in the src_msgs array in console_gui.c */
+};
+
 struct video_codec_desc;       /* forward declaration */
 /*
  * Descriptor of the local source, made of the following pieces:
@@ -140,7 +183,8 @@ 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, part of source_specific information are in 
+ *    the device table (devices member), others are shared;
  * NOTE: loc_src.data == NULL means the rest of the struct is invalid, and
  *     the video source is not available.
  */
@@ -151,14 +195,13 @@ struct video_out_desc {
         * If we are successful, webcam_bufsize > 0 and we can read.
         */
        /* all the following is config file info copied from the parent */
-       char            videodevice[64];
        int             fps;
        int             bitrate;
        int             qmin;
 
        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 */
@@ -167,16 +210,21 @@ struct video_out_desc {
        AVFrame         *enc_in_frame;  /* enc_in mapped into avcodec format. */
                                        /* The initial part of AVFrame is an AVPicture */
        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
+       
+       /* Table of devices specified with "videodevice=" in oss.conf.
+        * Static size as we have a limited number of entries.
+        */
+       struct video_device     devices[MAX_VIDEO_SOURCES]; 
+       int                     device_num; /*number of devices in table*/
+       int                     device_primary; /*index of the actual primary device in the table*/
+       int                     device_secondary; /*index of the actual secondary device in the table*/
+
+       int                     picture_in_picture; /*Is the PiP mode activated? 0 = NO | 1 = YES*/
+
+       /* these are the coordinates of the picture inside the picture (visible if PiP mode is active) 
+       these coordinates are valid considering the containing buffer with cif geometry*/
+       int                     pip_x;
+       int                     pip_y;
 };
 
 /*
@@ -188,6 +236,7 @@ struct video_out_desc {
 struct video_desc {
        char                    codec_name[64]; /* the codec we use */
 
+       int                     stayopen;       /* set if gui starts manually */
        pthread_t               vthread;        /* video thread */
        ast_mutex_t             dec_lock;       /* sync decoder and video thread */
        int                     shutdown;       /* set to shutdown vthread */
@@ -204,6 +253,10 @@ struct video_desc {
        struct fbuf_t           rem_dpy;        /* display remote video, no buffer (it is in win[WIN_REMOTE].bmp) */
        struct fbuf_t           loc_dpy;        /* display local source, no buffer (managed by SDL in bmp[1]) */
 
+       /* geometry of the thumbnails for all video sources. */
+       struct fbuf_t           src_dpy[MAX_VIDEO_SOURCES]; /* no buffer allocated here */
+
+       int frame_freeze;       /* flag to freeze the incoming frame */
 
        /* local information for grabbers, codecs, gui */
        struct gui_info         *gui;
@@ -213,231 +266,130 @@ struct video_desc {
 
 static AVPicture *fill_pict(struct fbuf_t *b, AVPicture *p);
 
-static void fbuf_free(struct fbuf_t *b)
+void fbuf_free(struct fbuf_t *b)
 {
        struct fbuf_t x = *b;
 
        if (b->data && b->size)
                ast_free(b->data);
-       bzero(b, sizeof(*b));
+       memset(b, '\0', sizeof(*b));
        /* restore some fields */
        b->w = x.w;
        b->h = x.h;
        b->pix_fmt = x.pix_fmt;
 }
 
-#include "vcodecs.c"
-#include "console_gui.c"
-
-/*------ end codec specific code -----*/
-
-
-/* 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
+/* return the status of env->stayopen to chan_oss, as the latter
+ * does not have access to fields of struct video_desc
  */
-static int video_open(struct video_out_desc *v)
+int get_gui_startup(struct video_desc* env)
 {
-       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;
-
-       /* init the connection with the X server */
-       v->dpy = XOpenDisplay(NULL);
-       if (v->dpy == NULL) {
-               ast_log(LOG_WARNING, "error opening display\n");
-               goto error;
-       }
-
-       /* find width and height of the screen */
-       screen_num = DefaultScreen(v->dpy);
-       v->screen_width = DisplayWidth(v->dpy, screen_num);
-       v->screen_height = DisplayHeight(v->dpy, screen_num);
-
-       v->image = im = XGetImage(v->dpy,
-               RootWindow(v->dpy, DefaultScreen(v->dpy)),
-               b->x, b->y, b->w, b->h, AllPlanes, ZPixmap);
-       if (v->image == NULL) {
-               ast_log(LOG_WARNING, "error creating Ximage\n");
-               goto error;
-       }
-       switch (im->bits_per_pixel) {
-       case 32:
-               b->pix_fmt = PIX_FMT_RGBA32;
-               break;
-       case 16:
-               b->pix_fmt = (im->green_mask == 0x7e0) ? PIX_FMT_RGB565 : PIX_FMT_RGB555;
-               break;
-       }
-
-       ast_log(LOG_NOTICE, "image: data %p %d bpp fmt %d, mask 0x%lx 0x%lx 0x%lx\n",
-               im->data,
-               im->bits_per_pixel,
-               b->pix_fmt,
-               im->red_mask, im->green_mask, im->blue_mask);
+       return env ? env->stayopen : 0;
+}
 
-       /* set the pointer but not the size as this is not malloc'ed */
-       b->data = (uint8_t *)im->data;
-       v->fd = -2;
-    }
-#ifdef HAVE_VIDEODEV_H
-    else {
-       /* V4L specific */
-       struct video_window vw = { 0 }; /* camera attributes */
-       struct video_picture vp;
-       int i;
-       const char *dev = v->videodevice;
+#if 0
+/* helper function to print the amount of memory used by the process.
+ * Useful to track memory leaks, unfortunately this code is OS-specific
+ * so we keep it commented out.
+ */
+static int
+used_mem(const char *msg)
+{
+       char in[128];
 
-       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;
-       }
+       pid_t pid = getpid();
+       sprintf(in, "ps -o vsz= -o rss= %d", pid);
+       ast_log(LOG_WARNING, "used mem (vsize, rss) %s ", msg);
+       system(in);
+       return 0;
+}
+#endif
+       
+#include "vcodecs.c"
+#include "console_gui.c"
 
-       i = fcntl(v->fd, F_GETFL);
-       if (-1 == fcntl(v->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));
-       }
-       /* set format for the camera.
-        * 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) {
-               ast_log(LOG_WARNING, "error setting format for %s [%s]\n",
-                       dev, strerror(errno));
-               goto error;
-       }
-       if (ioctl(v->fd, VIDIOCGPICT, &vp) == -1) {
-               ast_log(LOG_WARNING, "error reading picture info\n");
-               goto error;
+/*! \brief Try to open video sources, return 0 on success, 1 on error
+ * opens all video sources found in the oss.conf configuration files.
+ * Saves the grabber and the datas in the device table (in the devices field
+ * of the descriptor referenced by v).
+ * Initializes the device_primary and device_secondary
+ * fields of v with the first devices that was
+ * successfully opened.
+ *
+ * \param v = video out environment descriptor
+ *
+ * returns 0 on success, 1 on error 
+*/
+static int grabber_open(struct video_out_desc *v)
+{
+       struct grab_desc *g;
+       void *g_data;
+       int i, j;
+
+       /* for each device in the device table... */
+       for (i = 0; i < v->device_num; i++) {
+               /* device already open */
+               if (v->devices[i].grabber)
+                       continue;
+               /* for each type of grabber supported... */
+               for (j = 0; (g = console_grabbers[j]); j++) {
+                       /* the grabber is opened and the informations saved in the device table */
+                       g_data = g->open(v->devices[i].name, &v->loc_src_geometry, v->fps);
+                       if (!g_data)
+                               continue;
+                       v->devices[i].grabber = g;
+                       v->devices[i].grabber_data = g_data;
+                       v->devices[i].status_index |= IS_ON;
+               }
        }
-       ast_log(LOG_WARNING,
-               "contrast %d bright %d colour %d hue %d white %d palette %d\n",
-               vp.contrast, vp.brightness,
-               vp.colour, vp.hue,
-               vp.whiteness, vp.palette);
-       /* set the video format. Here again, we don't necessary have to
-        * fail if the required format is not supported, but try to use
-        * what the camera gives us.
-        */
-       b->pix_fmt = vp.palette;
-       vp.palette = VIDEO_PALETTE_YUV420P;
-       if (ioctl(v->fd, VIDIOCSPICT, &vp) == -1) {
-               ast_log(LOG_WARNING, "error setting palette, using %d\n",
-                       b->pix_fmt);
-       } else
-               b->pix_fmt = vp.palette;
-       /* allocate the source buffer.
-        * XXX, the code here only handles yuv411, for other formats
-        * we need to look at pix_fmt and set size accordingly
-        */
-       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);
-       if (!b->data) {
-               ast_log(LOG_WARNING, "error allocating buffer %d bytes\n",
-                       b->size);
-               goto error;
+       /* the first working device is selected as the primary one and the secondary one */
+       for (i = 0; i < v->device_num; i++) {
+               if (!v->devices[i].grabber) 
+                       continue;
+               v->device_primary = i;
+               v->device_secondary = i;
+               return 0; /* source found */
        }
-       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;
-
-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);
-       v->fd = -1;
-       fbuf_free(&v->loc_src);
-       return -1;
+       return 1; /* no source found */
 }
 
-/*! \brief complete a buffer from the local video source.
+
+/*! \brief complete a buffer from the specified local video source.
  * Called by get_video_frames(), in turn called by the video thread.
+ *
+ * \param dev = video environment descriptor
+ * \param fps = frame per seconds, for every device
+ *
+ * returns:
+ * - NULL on falure
+ * - reference to the device buffer on success
  */
-static int video_read(struct video_out_desc *v)
+static struct fbuf_t *grabber_read(struct video_device *dev, int fps)
 {
        struct timeval now = ast_tvnow();
-       struct fbuf_t *b = &v->loc_src;
-
-       if (b->data == NULL)    /* not initialized */
-               return 0;
 
+       if (dev->grabber == NULL) /* not initialized */
+               return NULL;
+       
+       /* the last_frame field in this row of the device table (dev)
+       is always initialized, it is set during the parsing of the config
+       file, and never unset, function fill_device_table(). */
        /* check if it is time to read */
-       if (ast_tvzero(v->last_frame))
-               v->last_frame = now;
-       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 */
-
-#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;
-               }
-       }
+       if (ast_tvdiff_ms(now, dev->last_frame) < 1000/fps)
+               return NULL; /* too early */
+       dev->last_frame = now; /* XXX actually, should correct for drift */
+       return dev->grabber->read(dev->grabber_data);
 }
 
-/* Helper function to process incoming video.
- * For each incoming video call invoke ffmpeg_init() to intialize
- * the decoding structure then incoming video frames are processed
- * by write_video() which in turn calls pre_process_data(), to extract
- * the bitstream; accumulates data into a buffer within video_desc. When
- * a frame is complete (determined by the marker bit in the RTP header)
- * call decode_video() to decoding and if it successful call show_frame()
- * to display the frame.
+/*! \brief handler run when dragging with the left button on
+ * the local source window - the effect is to move the offset
+ * of the captured area.
  */
+static void grabber_move(struct video_device *dev, int dx, int dy)
+{
+       if (dev->grabber && dev->grabber->move)
+                dev->grabber->move(dev->grabber_data, dx, dy);
+}
 
 /*
  * Map the codec name to the library. If not recognised, use a default.
@@ -464,7 +416,8 @@ static struct video_codec_desc *map_config_video_format(char *name)
 static int video_out_uninit(struct video_desc *env)
 {
        struct video_out_desc *v = &env->out;
-
+       int i; /* integer variable used as iterator */
+       
        /* XXX this should be a codec callback */
        if (v->enc_ctx) {
                AVCodecContext *enc_ctx = (AVCodecContext *)v->enc_ctx;
@@ -476,27 +429,30 @@ 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 grabbers */
+       for (i = 0; i < v->device_num; i++) {
+               if (v->devices[i].grabber){
+                       v->devices[i].grabber_data =
+                               v->devices[i].grabber->close(v->devices[i].grabber_data);
+                       v->devices[i].grabber = NULL;
+                       /* dev_buf is already freed by grabber->close() */
+                       v->devices[i].dev_buf = NULL;
+               }
+               v->devices[i].status_index = 0;
        }
+       v->picture_in_picture = 0;
+       env->frame_freeze = 0;
        return -1;
 }
 
 /*
  * Initialize the encoder for the local source:
- * - AVCodecContext, AVCodec, AVFrame are used by ffmpeg for encoding;
- * - encbuf is used to store the encoded frame (to be sent)
+ * - enc_ctx, codec, enc_in_frame are used by ffmpeg for encoding;
+ * - enc_out is used to store the encoded frame (to be sent)
  * - mtu is used to determine the max size of video fragment
  * NOTE: we enter here with the video source already open.
  */
@@ -512,10 +468,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) {
@@ -594,7 +546,10 @@ static int video_out_init(struct video_desc *env)
        return 0;
 }
 
-/*! \brief uninitialize the entire environment.
+/*! \brief possibly uninitialize the video console.
+ * Called at the end of a call, should reset the 'owner' field,
+ * then possibly terminate the video thread if the gui has
+ * not been started manually.
  * In practice, signal the thread and give it a bit of time to
  * complete, giving up if it gets stuck. Because uninit
  * is called from hangup with the channel locked, and the thread
@@ -604,18 +559,24 @@ static int video_out_init(struct video_desc *env)
 void console_video_uninit(struct video_desc *env)
 {
        int i, t = 100; /* initial wait is shorter, than make it longer */
-       env->shutdown = 1;
-       for (i=0; env->shutdown && i < 10; i++) {
-                ast_channel_unlock(env->owner);
-                usleep(t);
-               t = 1000000;
-                ast_channel_lock(env->owner);
-        }
-       env->owner = NULL;
+       if (env->stayopen == 0) { /* gui opened by a call, do the shutdown */
+               env->shutdown = 1;
+               for (i=0; env->shutdown && i < 10; i++) {
+                       if (env->owner)
+                               ast_channel_unlock(env->owner);
+                       usleep(t);
+                       t = 1000000;
+                       if (env->owner)
+                               ast_channel_lock(env->owner);
+               }
+               env->vthread = NULL;
+       }
+       env->owner = NULL;      /* this is unconditional */
 }
 
 /*! fill an AVPicture from our fbuf info, as it is required by
- * the image conversion routines in ffmpeg.
+ * the image conversion routines in ffmpeg. Note that the pointers
+ * are recalculated if the fbuf has an offset (and so represents a picture in picture)
  * XXX This depends on the format.
  */
 static AVPicture *fill_pict(struct fbuf_t *b, AVPicture *p)
@@ -624,23 +585,26 @@ static AVPicture *fill_pict(struct fbuf_t *b, AVPicture *p)
        int l4 = b->w * b->h/4; /* size of U or V frame */
        int len = b->w;         /* Y linesize, bytes */
        int luv = b->w/2;       /* U/V linesize, bytes */
-
-       bzero(p, sizeof(*p));
+       int sample_size = 1;
+       
+       memset(p, '\0', sizeof(*p));
        switch (b->pix_fmt) {
        case PIX_FMT_RGB555:
        case PIX_FMT_RGB565:
-               len *= 2;
+               sample_size = 2;
                luv = 0;
                break;
        case PIX_FMT_RGBA32:
-               len *= 4;
+               sample_size = 4;
                luv = 0;
                break;
        case PIX_FMT_YUYV422:   /* Packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr */
-               len *= 2;       /* all data in first plane, probably */
+               sample_size = 2;        /* all data in first plane, probably */
                luv = 0;
                break;
        }
+       len *= sample_size;
+       
        p->data[0] = b->data;
        p->linesize[0] = len;
        /* these are only valid for component images */
@@ -648,6 +612,14 @@ static AVPicture *fill_pict(struct fbuf_t *b, AVPicture *p)
        p->data[2] = luv ? b->data + 5*l4 : b->data+len;
        p->linesize[1] = luv;
        p->linesize[2] = luv;
+       
+       /* add the offsets to the pointers previously calculated, 
+       it is necessary for the picture in picture mode */
+       p->data[0] += len*b->win_y + b->win_x*sample_size;
+       if (luv) { 
+               p->data[1] += luv*(b->win_y/2) + (b->win_x/2) * sample_size;
+               p->data[2] += luv*(b->win_y/2) + (b->win_x/2) * sample_size;
+       }
        return p;
 }
 
@@ -659,22 +631,30 @@ static void my_scale(struct fbuf_t *in, AVPicture *p_in,
        struct fbuf_t *out, AVPicture *p_out)
 {
        AVPicture my_p_in, my_p_out;
+       int eff_w=out->w, eff_h=out->h;
 
        if (p_in == NULL)
                p_in = fill_pict(in, &my_p_in);
        if (p_out == NULL)
                p_out = fill_pict(out, &my_p_out);
-
+       
+       /*if win_w is different from zero then we must change 
+       the size of the scaled buffer (the position is already 
+       encoded into the out parameter)*/
+       if (out->win_w) { /* picture in picture enabled */
+               eff_w=out->win_w;
+               eff_h=out->win_h;
+       }
 #ifdef OLD_FFMPEG
-       /* XXX img_convert is deprecated, and does not do rescaling */
+       /* XXX img_convert is deprecated, and does not do rescaling, PiP not supported */
        img_convert(p_out, out->pix_fmt,
                p_in, in->pix_fmt, in->w, in->h);
 #else /* XXX replacement */
     {
        struct SwsContext *convert_ctx;
-
+       
        convert_ctx = sws_getContext(in->w, in->h, in->pix_fmt,
-               out->w, out->h, out->pix_fmt,
+               eff_w, eff_h, out->pix_fmt,
                SWS_BICUBIC, NULL, NULL, NULL);
        if (convert_ctx == NULL) {
                ast_log(LOG_ERROR, "FFMPEG::convert_cmodel : swscale context initialization failed");
@@ -682,7 +662,7 @@ static void my_scale(struct fbuf_t *in, AVPicture *p_in,
        }
        if (0)
                ast_log(LOG_WARNING, "in %d %dx%d out %d %dx%d\n",
-                       in->pix_fmt, in->w, in->h, out->pix_fmt, out->w, out->h);
+                       in->pix_fmt, in->w, in->h, out->pix_fmt, eff_w, eff_h);
        sws_scale(convert_ctx,
                p_in->data, p_in->linesize,
                in->w, in->h, /* src slice */
@@ -761,11 +741,11 @@ int console_write_video(struct ast_channel *chan, struct ast_frame *f)
        }
        v->next_seq++;
 
-       if (f->data == NULL || f->datalen < 2) {
+       if (f->data.ptr == NULL || f->datalen < 2) {
                ast_log(LOG_WARNING, "empty video frame, discard\n");
                return 0;
        }
-       if (v->d_callbacks->dec_decap(v->dec_in_cur, f->data, f->datalen)) {
+       if (v->d_callbacks->dec_decap(v->dec_in_cur, f->data.ptr, f->datalen)) {
                ast_log(LOG_WARNING, "error in dec_decap, enter discard\n");
                v->discard = 1;
        }
@@ -786,40 +766,81 @@ int console_write_video(struct ast_channel *chan, struct ast_frame *f)
 }
 
 
-/*! \brief read a frame from webcam or X11 through video_read(),
- * display it,  then encode and split it.
+/*! \brief refreshes the buffers of all the device by calling the
+ * grabber_read on each device in the device table.
+ * it encodes the primary source buffer, if the picture in picture mode is
+ * enabled it encodes (in the buffer to split) the secondary source buffer too.
+ * The encoded buffer is splitted to build the local and the remote view.
  * Return a list of ast_frame representing the video fragments.
  * The head pointer is returned by the function, the tail pointer
  * is returned as an argument.
+ *
+ * \param env = video environment descriptor
+ * \param tail = tail ponter (pratically a return value)
  */
 static struct ast_frame *get_video_frames(struct video_desc *env, struct ast_frame **tail)
 {
        struct video_out_desc *v = &env->out;
        struct ast_frame *dummy;
-
-       if (!v->loc_src.data) {
-               static volatile int a = 0;
-               if (a++ < 2)
-                       ast_log(LOG_WARNING, "fail, no loc_src buffer\n");
+       struct fbuf_t *loc_src_primary = NULL, *p_read;
+       int i;
+       /* if no device was found in the config file */
+       if (!env->out.device_num)
                return NULL;
+       /* every time this function is called we refresh the buffers of every device,
+       updating the private device buffer in the device table */
+       for (i = 0; i < env->out.device_num; i++) {
+               p_read = grabber_read(&env->out.devices[i], env->out.fps);
+               /* it is used only if different from NULL, we mantain last good buffer otherwise */
+               if (p_read)
+                       env->out.devices[i].dev_buf = p_read;
        }
-       if (!video_read(v))
-               return NULL;    /* can happen, e.g. we are reading too early */
-
+       /* select the primary device buffer as the one to encode */
+       loc_src_primary = env->out.devices[env->out.device_primary].dev_buf;
+       /* loc_src_primary can be NULL if the device has been turned off during
+       execution of it is read too early */
+       if (loc_src_primary) {
+               /* Scale the video for the encoder, then use it for local rendering
+               so we will see the same as the remote party */
+               my_scale(loc_src_primary, NULL, &env->enc_in, NULL);
+       }
+       if (env->out.picture_in_picture) { /* the picture in picture mode is enabled */
+               struct fbuf_t *loc_src_secondary;
+               /* reads from the secondary source */
+               loc_src_secondary = env->out.devices[env->out.device_secondary].dev_buf;
+               if (loc_src_secondary) {
+                       env->enc_in.win_x = env->out.pip_x;
+                       env->enc_in.win_y = env->out.pip_y;
+                       env->enc_in.win_w = env->enc_in.w/3;
+                       env->enc_in.win_h = env->enc_in.h/3;
+                       /* scales to the correct geometry and inserts in
+                       the enc_in buffer the picture in picture */
+                       my_scale(loc_src_secondary, NULL, &env->enc_in, NULL);
+                       /* returns to normal parameters (not picture in picture) */
+                       env->enc_in.win_x = 0;
+                       env->enc_in.win_y = 0;
+                       env->enc_in.win_w = 0;
+                       env->enc_in.win_h = 0;
+               }
+               else {
+                       /* loc_src_secondary can be NULL if the device has been turned off during
+                       execution of it is read too early */
+                       env->out.picture_in_picture = 0; /* disable picture in picture */
+               }
+       }
+       show_frame(env, WIN_LOCAL); /* local rendering */
+       for (i = 0; i < env->out.device_num; i++) 
+               show_frame(env, i+WIN_SRC1); /* rendering of every source device in thumbnails */
        if (tail == NULL)
                tail = &dummy;
        *tail = NULL;
-       /* 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);
-       show_frame(env, WIN_LOCAL);
-       if (!v->sendvideo)
+       /* if no reason for encoding, do not encode */
+       if (!env->owner || !loc_src_primary || !v->sendvideo)
                return NULL;
        if (v->enc_out.data == NULL) {
                static volatile int a = 0;
                if (a++ < 2)
-                       ast_log(LOG_WARNING, "fail, no encbuf\n");
+                       ast_log(LOG_WARNING, "fail, no encoder output buffer\n");
                return NULL;
        }
        v->enc->enc_run(v);
@@ -827,8 +848,8 @@ static struct ast_frame *get_video_frames(struct video_desc *env, struct ast_fra
 }
 
 /*
- * Helper thread to periodically poll the video source and enqueue the
- * generated frames to the channel's queue.
+ * Helper thread to periodically poll the video sources and enqueue the
+ * generated frames directed to the remote party to the channel's queue.
  * Using a separate thread also helps because the encoding can be
  * computationally expensive so we don't want to starve the main thread.
  */
@@ -837,6 +858,7 @@ static void *video_thread(void *arg)
        struct video_desc *env = arg;
        int count = 0;
        char save_display[128] = "";
+       int i; /* integer variable used as iterator */
 
        /* if sdl_videodriver is set, override the environment. Also,
         * if it contains 'console' override DISPLAY around the call to SDL_Init
@@ -854,35 +876,41 @@ static void *video_thread(void *arg)
        if (!ast_strlen_zero(save_display))
                setenv("DISPLAY", save_display, 1);
 
-        /* initialize grab coordinates */
-        env->out.loc_src.x = 0;
-        env->out.loc_src.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 (env->out.fd >= 0)
-                       ast_channel_set_fd(env->owner, 1, env->out.fd);
-               video_out_init(env);
+       } 
+
+       if (env->out.device_num)
+               env->out.devices[env->out.device_primary].status_index |= IS_PRIMARY | IS_SECONDARY;
+       
+       /* even if no device is connected, we must call video_out_init,
+        * as some of the data structures it initializes are
+        * used in get_video_frames()
+        */
+       video_out_init(env);
+
+       /* Writes intial status of the sources. */
+       if (env->gui) {
+           for (i = 0; i < env->out.device_num; i++) {
+               print_message(env->gui->thumb_bd_array[i].board,
+                src_msgs[env->out.devices[i].status_index]);
+           }
        }
 
        for (;;) {
                struct timeval t = { 0, 50000 };        /* XXX 20 times/sec */
                struct ast_frame *p, *f;
-               struct ast_channel *chan = env->owner;
-               int fd = chan->alertpipe[1];
+               struct ast_channel *chan;
+               int fd;
                char *caption = NULL, buf[160];
 
                /* determine if video format changed */
                if (count++ % 10 == 0) {
-                       if (env->out.sendvideo)
+                       if (env->out.sendvideo && env->out.devices)
                            sprintf(buf, "%s %s %dx%d @@ %dfps %dkbps",
-                               env->out.videodevice, env->codec_name,
+                               env->out.devices[env->out.device_primary].name, env->codec_name,
                                env->enc_in.w, env->enc_in.h,
                                env->out.fps, env->out.bitrate/1000);
                        else
@@ -909,7 +937,8 @@ static void *video_thread(void *arg)
                while (v->dec_in_dpy) {
                        struct fbuf_t *tmp = v->dec_in_dpy;     /* store current pointer */
 
-                       if (v->d_callbacks->dec_run(v, tmp))
+                       /* decode the frame, but show it only if not frozen */
+                       if (v->d_callbacks->dec_run(v, tmp) && !env->frame_freeze)
                                show_frame(env, WIN_REMOTE);
                        tmp->used = 0;  /* mark buffer as free */
                        tmp->ebit = 0;
@@ -925,12 +954,22 @@ 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;
+               if (chan == NULL) {
+                       /* drop the chain of frames, nobody uses them */
+                       while (f) {
+                               struct ast_frame *g = AST_LIST_NEXT(f, frame_list);
+                               ast_frfree(f);
+                               f = g;
+                       }
+                       continue;
+               }
+               fd = chan->alertpipe[1];
                ast_channel_lock(chan);
 
                /* AST_LIST_INSERT_TAIL is only good for one frame, cannot use here */
@@ -960,7 +999,7 @@ static void *video_thread(void *arg)
        video_out_uninit(env);
 
        if (env->gui)
-               env->gui = cleanup_sdl(env->gui);
+               env->gui = cleanup_sdl(env->gui, env->out.device_num);
        ast_mutex_destroy(&env->dec_lock);
        env->shutdown = 0;
        return NULL;
@@ -980,10 +1019,11 @@ 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 */
+       int i; /* integer working as iterator */
 
        c->pix_fmt = PIX_FMT_YUV420P;   /* default - camera format */
        ei->pix_fmt = PIX_FMT_YUV420P;  /* encoder input */
@@ -996,6 +1036,18 @@ static void init_env(struct video_desc *env)
        copy_geometry(ei, c);   /* camera inherits from encoder input */
        copy_geometry(ei, rd);  /* remote display inherits from encoder input */
        copy_geometry(rd, ld);  /* local display inherits from remote display */
+
+       /* fix the size of buffers for small windows */
+       for (i = 0; i < env->out.device_num; i++) {
+               env->src_dpy[i].pix_fmt = PIX_FMT_YUV420P;
+               env->src_dpy[i].w = SRC_WIN_W;
+               env->src_dpy[i].h = SRC_WIN_H;
+       }
+       /* now we set the default coordinates for the picture in picture
+       frames inside the env_in buffers, those can be changed by dragging the
+       picture in picture with left click */
+       env->out.pip_x = ei->w - ei->w/3;
+       env->out.pip_y = ei->h - ei->h/3;
 }
 
 /*!
@@ -1007,11 +1059,12 @@ static void init_env(struct video_desc *env)
  */
 void console_video_start(struct video_desc *env, struct ast_channel *owner)
 {
+       ast_log(LOG_WARNING, "env %p chan %p\n", env, owner);
        if (env == NULL)        /* video not initialized */
                return;
-       if (owner == NULL)      /* nothing to do if we don't have a channel */
-               return;
-       env->owner = owner;
+       env->owner = owner;     /* work even if no owner is specified */
+       if (env->vthread)
+               return;         /* already initialized, nothing to do */
        init_env(env);
        env->out.enc = map_config_video_format(env->codec_name);
 
@@ -1034,8 +1087,9 @@ void console_video_start(struct video_desc *env, struct ast_channel *owner)
                env->out.bitrate = 65000;
                ast_log(LOG_WARNING, "bitrate unset, forcing to %d\n", env->out.bitrate);
        }
-
-       ast_pthread_create_background(&env->vthread, NULL, video_thread, env);
+       /* create the thread as detached so memory is freed on termination */
+       ast_pthread_create_detached_background(&env->vthread,
+               NULL, video_thread, env);
 }
 
 /*
@@ -1090,6 +1144,50 @@ static int video_geom(struct fbuf_t *b, const char *s)
        return 0;
 }
 
+
+/*! \brief add an entry to the video_device table,
+ * ignoring duplicate names.
+ * The table is a static array of 9 elements.
+ * The last_frame field of each entry of the table is initialized to
+ * the current time (we need a value inside this field, on stop of the
+ * GUI the last_frame value is not changed, to avoid checking if it is 0 we
+ * set the initial value on current time) XXX
+ *
+ * PARAMETERS:
+ * \param devices_p = pointer to the table of devices
+ * \param device_num_p = pointer to the number of devices
+ * \param s = name of the new device to insert
+ *
+ * returns 0 on success, 1 on error
+ */
+static int device_table_fill(struct video_device *devices, int *device_num_p, const char *s)
+{
+       int i;
+       struct video_device *p;
+
+       /* with the current implementation, we support a maximum of 9 devices.*/
+       if (*device_num_p >= 9)
+               return 0; /* more devices will be ignored */
+       /* ignore duplicate names */
+       for (i = 0; i < *device_num_p; i++) {
+               if (!strcmp(devices[i].name, s))
+                       return 0;
+       }
+       /* inserts the new video device */
+       p = &devices[*device_num_p];
+       /* XXX the string is allocated but NEVER deallocated,
+       the good time to do that is when the module is unloaded, now we skip the problem */
+       p->name = ast_strdup(s);                /* copy the name */
+       /* other fields initially NULL */
+       p->grabber = NULL;
+       p->grabber_data = NULL;
+       p->dev_buf = NULL;
+       p->last_frame = ast_tvnow();
+       p->status_index = 0;
+       (*device_num_p)++;                      /* one device added */
+       return 0;
+}
+
 /* extend ast_cli with video commands. Called by console_video_config */
 int console_video_cli(struct video_desc *env, const char *var, int fd)
 {
@@ -1097,7 +1195,7 @@ int console_video_cli(struct video_desc *env, const char *var, int fd)
                return 1;       /* unrecognised */
 
         if (!strcasecmp(var, "videodevice")) {
-               ast_cli(fd, "videodevice is [%s]\n", env->out.videodevice);
+               ast_cli(fd, "videodevice is [%s]\n", env->out.devices[env->out.device_primary].name);
         } else if (!strcasecmp(var, "videocodec")) {
                ast_cli(fd, "videocodec is [%s]\n", env->codec_name);
         } else if (!strcasecmp(var, "sendvideo")) {
@@ -1110,7 +1208,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);
@@ -1120,6 +1218,15 @@ int console_video_cli(struct video_desc *env, const char *var, int fd)
                ast_cli(fd, "qmin is [%d]\n", env->out.qmin);
         } else if (!strcasecmp(var, "fps")) {
                ast_cli(fd, "fps is [%d]\n", env->out.fps);
+        } else if (!strcasecmp(var, "startgui")) {
+               env->stayopen = 1;
+               console_video_start(env, NULL);
+        } else if (!strcasecmp(var, "stopgui") && env->stayopen != 0) {
+               env->stayopen = 0;
+               if (env->gui && env->owner)
+                       ast_cli_command(-1, "console hangup");
+               else /* not in a call */
+                       console_video_uninit(env);
         } else {
                return 1;       /* unrecognised */
        }
@@ -1145,22 +1252,25 @@ int console_video_config(struct video_desc **penv,
                        return 1;       /* error */
                
                }
-               /* set default values */
-               ast_copy_string(env->out.videodevice, "X11", sizeof(env->out.videodevice));
+               /* set default values - 0's are already there */
+               env->out.device_primary = 0;
+               env->out.device_secondary = 0;
                env->out.fps = 5;
                env->out.bitrate = 65000;
                env->out.sendvideo = 1;
                env->out.qmin = 3;
+               env->out.device_num = 0;
        }
        CV_START(var, val);
-       CV_STR("videodevice", env->out.videodevice);
+       CV_F("videodevice", device_table_fill(env->out.devices, &env->out.device_num, val));
        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);
        CV_F("region", keypad_cfg_read(env->gui, val));
+       CV_UINT("startgui", env->stayopen);     /* enable gui at startup */
        CV_STR("keypad_font", env->keypad_font);
        CV_STR("sdl_videodriver", env->sdl_videodriver);
        CV_UINT("fps", env->out.fps);