summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--channels/console_board.c17
-rw-r--r--channels/console_gui.c580
-rw-r--r--channels/console_video.c395
3 files changed, 849 insertions, 143 deletions
diff --git a/channels/console_board.c b/channels/console_board.c
index fd286f29e..e80247c5f 100644
--- a/channels/console_board.c
+++ b/channels/console_board.c
@@ -313,6 +313,23 @@ int print_message(struct board *b, const char *s)
return 1;
}
+/* deletes a board.
+ * we make the free operation on any fields of the board structure allocated
+ * in dynamic memory
+ */
+void delete_board(struct board *b)
+{
+ if (b) {
+ /* deletes the text */
+ if (b->text)
+ ast_free (b->text);
+ /* deallocates the blank surface */
+ SDL_FreeSurface(b->blank);
+ /* deallocates the board */
+ ast_free(b);
+ }
+}
+
#if 0
/*! \brief refresh the screen, and also grab a bunch of events.
*/
diff --git a/channels/console_gui.c b/channels/console_gui.c
index 699e6b1fa..a228c3ce7 100644
--- a/channels/console_gui.c
+++ b/channels/console_gui.c
@@ -7,30 +7,72 @@
/*
* GUI layout, structure and management
-For the GUI we use SDL to create a large surface (gui->screen)
-containing tree sections: remote video on the left, local video
-on the right, and the keypad with all controls and text windows
-in the center.
-The central section is built using an image for the skin, fonts and
-other GUI elements. Comments embedded in the image to indicate to
-what function each area is mapped to.
+For the GUI we use SDL to create a large surface (gui->screen) with 4 areas:
+remote video on the left, local video on the right, keypad with all controls
+and text windows in the center, and source device thumbnails on the top.
+The top row is not displayed if no devices are specified in the config file.
+
+ ________________________________________________________________
+ | ______ ______ ______ ______ ______ ______ ______ |
+ | | tn.1 | | tn.2 | | tn.3 | | tn.4 | | tn.5 | | tn.6 | | tn.7 | |
+ | |______| |______| |______| |______| |______| |______| |______| |
+ | ______ ______ ______ ______ ______ ______ ______ |
+ | |______| |______| |______| |______| |______| |______| |______| |
+ | _________________ __________________ _________________ |
+ | | | | | | | |
+ | | | | | | | |
+ | | | | | | | |
+ | | remote video | | | | local video | |
+ | | | | | | ______ | |
+ | | | | keypad | | | PIP || |
+ | | | | | | |______|| |
+ | |_________________| | | |_________________| |
+ | | | |
+ | | | |
+ | |__________________| |
+ |________________________________________________________________|
+
+
+The central section is built using an image (jpg, png, maybe gif too)
+for the skin, and other GUI elements. Comments embedded in the image
+indicate to what function each area is mapped to.
+Another image (png with transparency) is used for the font.
Mouse and keyboard events are detected on the whole surface, and
handled differently according to their location:
-
+- center/right click on the local/remote window are used to resize
+ the corresponding window;
+- clicks on the thumbnail start/stop sources and select them as
+ primary or secondary video sources;
- drag on the local video window are used to move the captured
- area (in the case of X11 grabber) or the picture-in-picture
- location (in case of camera included on the X11 grab).
-- click on the keypad are mapped to the corresponding key;
+ area (in the case of X11 grabber) or the picture-in-picture position;
+- keystrokes on the keypad are mapped to the corresponding key;
+ keystrokes are used as keypad functions, or as text input
+ if we are in text-input mode.
- drag on some keypad areas (sliders etc.) are mapped to the
corresponding functions;
-- keystrokes are used as keypad functions, or as text input
- if we are in text-input mode.
Configuration options control the appeareance of the gui:
- keypad = /tmp/phone.jpg ; the skin
- keypad_font = /tmp/font.ttf ; the font to use for output (XXX deprecated)
+ keypad = /tmp/kpad2.jpg ; the skin
+ keypad_font = /tmp/font.png ; the font to use for output
+
+For future implementation, intresting features can be the following:
+- freeze of the video coming from our remote party
+- save of the whole SDL window as a picture
+- oudio output device switching
+
+The audio switching feature should allow changing the device
+or switching to a recorded message for audio sent to remote party.
+The selection of the device should happen clicking on a marker in the layout.
+For this reason above the thumbnails row in the layout we would like a new row,
+the elements composing the row could be message boards, reporting the name of the
+device or the path of the message to be played.
+
+For video input freeze and entire window capture, we define 2 new key types,
+those should be activated pressing the buttons on the keypad, associated with
+new regions inside the keypad pictureas comments
+
*
*/
@@ -42,13 +84,15 @@ Configuration options control the appeareance of the gui:
#include "asterisk/utils.h" /* ast_calloc and ast_realloc */
#include <math.h> /* sqrt */
-/* We use 3 'windows' in the GUI */
-enum { WIN_LOCAL, WIN_REMOTE, WIN_KEYPAD, WIN_MAX };
+/* We use a maximum of 12 'windows' in the GUI */
+enum { WIN_LOCAL, WIN_REMOTE, WIN_KEYPAD, WIN_SRC1,
+ WIN_SRC2, WIN_SRC3, WIN_SRC4, WIN_SRC5,
+ WIN_SRC6, WIN_SRC7, WIN_SRC8, WIN_SRC9, WIN_MAX };
#ifndef HAVE_SDL /* stubs if we don't have any sdl */
static void show_frame(struct video_desc *env, int out) {}
static void sdl_setup(struct video_desc *env) {}
-static struct gui_info *cleanup_sdl(struct gui_info *gui) { return NULL; }
+static struct gui_info *cleanup_sdl(struct gui_info* g, int n) { return NULL; }
static void eventhandler(struct video_desc *env, const char *caption) {}
static int keypad_cfg_read(struct gui_info *gui, const char *val) { return 0; }
@@ -65,6 +109,9 @@ static int keypad_cfg_read(struct gui_info *gui, const char *val) { return 0; }
#include <X11/Xlib.h>
#endif
+#define BORDER 5 /* border around our windows */
+#define SRC_MSG_BD_H 20 /* height of the message board below those windows */
+
enum kp_type { KP_NONE, KP_RECT, KP_CIRCLE };
struct keypad_entry {
int c; /* corresponding character */
@@ -75,11 +122,18 @@ struct keypad_entry {
/* our representation of a displayed window. SDL can only do one main
* window so we map everything within that one
*/
-struct display_window {
+struct display_window {
SDL_Overlay *bmp;
SDL_Rect rect; /* location of the window */
};
+/* each thumbnail message board has a rectangle associated for the geometry,
+ * and a board structure, we include these two elements in a singole structure */
+struct thumb_bd {
+ SDL_Rect rect; /* the rect for geometry and background */
+ struct board *board; /* the board */
+};
+
struct gui_info {
enum kb_output kb_output; /* where the keyboard output goes */
struct drag_info drag; /* info on the window are we dragging */
@@ -92,9 +146,11 @@ struct gui_info {
SDL_Surface *font; /* font to be used */
SDL_Rect font_rects[96]; /* only printable chars */
- /* each board has two rectangles,
+ /* each of the following board has two rectangles,
* [0] is the geometry relative to the keypad,
* [1] is the geometry relative to the whole screen
+ * we do not use the thumb_bd for these boards because here we need
+ * 2 rectangles for geometry
*/
SDL_Rect kp_msg[2]; /* incoming msg, relative to kpad */
struct board *bd_msg;
@@ -105,6 +161,12 @@ struct gui_info {
SDL_Rect kp_dialed[2]; /* dialed number */
struct board *bd_dialed;
+ /* other boards are one associated with the source windows
+ * above the keypad in the layout, we only have the geometry
+ * relative to the whole screen
+ */
+ struct thumb_bd thumb_bd_array[MAX_VIDEO_SOURCES];
+
/* variable-size array mapping keypad regions to functions */
int kp_size, kp_used;
struct keypad_entry *kp;
@@ -115,7 +177,7 @@ struct gui_info {
/*! \brief free the resources in struct gui_info and the descriptor itself.
* Return NULL so we can assign the value back to the descriptor in case.
*/
-static struct gui_info *cleanup_sdl(struct gui_info *gui)
+static struct gui_info *cleanup_sdl(struct gui_info *gui, int device_num)
{
int i;
@@ -142,11 +204,43 @@ static struct gui_info *cleanup_sdl(struct gui_info *gui)
SDL_FreeYUVOverlay(gui->win[i].bmp);
}
bzero(gui, sizeof(gui));
+
+ /* deallocates the space allocated for the keypad message boards */
+ if (gui->bd_dialed)
+ delete_board(gui->bd_dialed);
+ if (gui->bd_msg)
+ delete_board(gui->bd_msg);
+
+ /* deallocates the space allocated for the thumbnail message boards */
+ for (i = 0; i < device_num; i++) {
+ if (gui->thumb_bd_array[i].board) /* may be useless */
+ delete_board(gui->thumb_bd_array[i].board);
+ }
+
ast_free(gui);
SDL_Quit();
return NULL;
}
+/* messages to be displayed in the sources message boards
+ * below the source windows
+ */
+
+/* costants defined to describe status of devices */
+#define IS_PRIMARY 1
+#define IS_SECONDARY 2
+#define IS_ON 4
+
+char* src_msgs[] = {
+ " OFF",
+ "1 OFF",
+ " 2 OFF",
+ "1+2 OFF",
+ " ON",
+ "1 ON",
+ " 2 ON",
+ "1+2 ON",
+};
/*
* Display video frames (from local or remote stream) using the SDL library.
* - Set the video mode to use the resolution specified by the codec context
@@ -171,7 +265,7 @@ static void show_frame(struct video_desc *env, int out)
b_in = &env->enc_in;
b_out = &env->loc_dpy;
p_in = NULL;
- } else {
+ } else if (out == WIN_REMOTE) {
/* copy input format from the decoding context */
AVCodecContext *c;
if (env->in == NULL) /* XXX should not happen - decoder not ready */
@@ -184,7 +278,14 @@ static void show_frame(struct video_desc *env, int out)
b_out = &env->rem_dpy;
p_in = (AVPicture *)env->in->d_frame;
- }
+ } else {
+ int i = out-WIN_SRC1;
+ b_in = env->out.devices[i].dev_buf;
+ if (b_in == NULL)
+ return;
+ p_in = NULL;
+ b_out = &env->src_dpy[i];
+ }
bmp = gui->win[out].bmp;
SDL_LockYUVOverlay(bmp);
/* output picture info - this is sdl, YUV420P */
@@ -236,6 +337,21 @@ enum skin_area {
KEY_DIALED = 203, /* area for dialed numbers */
KEY_EDIT = 204, /* area for editing user input */
+#ifdef notyet /* XXX for future implementation */
+ KEY_AUDIO_SRCS = 210,
+ /*indexes between 210 and 219 (or more) have been reserved for the "keys"
+ associated with the audio device markers, clicking on these markers
+ will change the source device for audio output */
+
+ KEY_FREEZE = 220, /* freeze the incoming video */
+ KEY_CAPTURE = 221, /* capture the whole SDL window as a picture */
+#endif
+ /* video source switching key(s) */
+ KEY_PIP = 230,
+ /*indexes between 231 and 239 have been reserved for the "keys"
+ associated with the device thumbnails, clicking on these pictures
+ will change the source device for primary or secondary (PiP) video output*/
+ KEY_SRCS_WIN = 231, /* till 239 */
/* areas outside the keypad - simulated */
KEY_OUT_OF_KEYPAD = 241,
KEY_REM_DPY = 242,
@@ -270,15 +386,16 @@ static char *keypad_toggle(struct video_desc *env, int index)
ast_log(LOG_WARNING, "keypad_toggle(%i) called\n", index);
switch (index) {
- case KEY_SENDVIDEO:
+ case KEY_SENDVIDEO: /* send or do not send video */
env->out.sendvideo = !env->out.sendvideo;
break;
-#ifdef notyet
- case KEY_MUTE: {
- struct chan_oss_pvt *o = find_desc(oss_active);
- o->mute = !o->mute;
- }
+ case KEY_PIP: /* enable or disable Picture in Picture */
+ env->out.picture_in_picture = !env->out.picture_in_picture;
+ break;
+ case KEY_MUTE: /* send or do not send audio */
+ ast_cli_command(env->gui->outfd, "console mute toggle");
break;
+#ifdef notyet
case KEY_AUTOANSWER: {
struct chan_oss_pvt *o = find_desc(oss_active);
o->autoanswer = !o->autoanswer;
@@ -357,6 +474,130 @@ static void set_drag(struct drag_info *drag, int x, int y, enum drag_window win)
drag->drag_window = win;
}
+static int update_device_info(struct video_desc *env, int i)
+{
+ reset_board(env->gui->thumb_bd_array[i].board);
+ print_message(env->gui->thumb_bd_array[i].board,
+ src_msgs[env->out.devices[i].status_index]);
+ return 0;
+}
+
+/*! \brief Changes the video output (local video) source, controlling if
+ * it is already using that video device,
+ * and switching the correct fields of env->out.
+ * grabbers are always open and saved in the device table.
+ * The secondary or the primary device can be changed,
+ * according to the "button" parameter:
+ * the primary device is changed if button = SDL_BUTTON_LEFT;
+ * the secondary device is changed if button = not SDL_BUTTON_LEFT;
+ *
+ * the correct message boards of the sources are also updated
+ * with the new status
+ *
+ * \param env = pointer to the video environment descriptor
+ * \param index = index of the device the caller wants to use are primary or secondary device
+ * \param button = button clicked on the mouse
+ *
+ * returns 0 on success,
+ * returns 1 on error
+ */
+static int switch_video_out(struct video_desc *env, int index, Uint8 button)
+{
+ int *p; /* pointer to the index of the device to select */
+
+ if (index >= env->out.device_num) {
+ ast_log(LOG_WARNING, "no devices\n");
+ return 1;
+ }
+ /* select primary or secondary */
+ p = (button == SDL_BUTTON_LEFT) ? &env->out.device_primary :
+ &env->out.device_secondary;
+ /* controls if the device is already selected */
+ if (index == *p) {
+ ast_log(LOG_WARNING, "device %s already selected\n", env->out.devices[index].name);
+ return 0;
+ }
+ ast_log(LOG_WARNING, "switching to %s...\n", env->out.devices[index].name);
+ /* already open */
+ if (env->out.devices[index].grabber) {
+ /* we also have to update the messages in the source
+ message boards below the source windows */
+ /* first we update the board of the previous source */
+ if (p == &env->out.device_primary)
+ env->out.devices[*p].status_index &= ~IS_PRIMARY;
+ else
+ env->out.devices[*p].status_index &= ~IS_SECONDARY;
+ update_device_info(env, *p);
+ /* update the index used as primary or secondary */
+ *p = index;
+ ast_log(LOG_WARNING, "done\n");
+ /* then we update the board of the new primary or secondary source */
+ if (p == &env->out.device_primary)
+ env->out.devices[*p].status_index |= IS_PRIMARY;
+ else
+ env->out.devices[*p].status_index |= IS_SECONDARY;
+ update_device_info(env, *p);
+ return 0;
+ }
+ /* device is off, just do nothing */
+ ast_log(LOG_WARNING, "device is down\n");
+ return 1;
+}
+
+/*! \brief tries to switch the state of a device from on to off or off to on
+ * we also have to update the status of the device and the correct message board
+ *
+ * \param index = the device that must be turned on or off
+ * \param env = pointer to the video environment descriptor
+ *
+ * returns:
+ * - 0 on falure switching from off to on
+ * - 1 on success in switching from off to on
+ * - 2 on success in switching from on to off
+*/
+static int turn_on_off(int index, struct video_desc *env)
+{
+ struct video_device *p = &env->out.devices[index];
+
+ if (index >= env->out.device_num) {
+ ast_log(LOG_WARNING, "no devices\n");
+ return 0;
+ }
+
+ if (!p->grabber) { /* device off */
+ void *g_data; /* result of grabber_open() */
+ struct grab_desc *g;
+ int i;
+
+ /* see if the device can be used by one of the existing drivers */
+ for (i = 0; (g = console_grabbers[i]); i++) {
+ /* try open the device */
+ g_data = g->open(p->name, &env->out.loc_src_geometry, env->out.fps);
+ if (!g_data) /* no luck, try the next driver */
+ continue;
+ p->grabber = g;
+ p->grabber_data = g_data;
+ /* update the status of the source */
+ p->status_index |= IS_ON;
+ /* print the new message in the message board */
+ update_device_info(env, index);
+ return 1; /* open succeded */
+ }
+ return 0; /* failure */
+ } else {
+ /* the grabber must be closed */
+ p->grabber_data = p->grabber->close(p->grabber_data);
+ p->grabber = NULL;
+ /* dev_buf is already freed by grabber->close() */
+ p->dev_buf = NULL;
+ /* update the status of the source */
+ p->status_index &= ~IS_ON;
+ /* print the new message in the message board */
+ update_device_info(env, index);
+ return 2; /* closed */
+ }
+}
+
/*
* Handle SDL_MOUSEBUTTONDOWN type, finding the palette
* index value and calling the right callback.
@@ -367,29 +608,83 @@ static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button
{
uint8_t index = KEY_OUT_OF_KEYPAD; /* the key or region of the display we clicked on */
struct gui_info *gui = env->gui;
+
+ int i; /* integer variable used as iterator */
+ int x; /* integer variable usable as a container */
+
+ /* total width of source device thumbnails */
+ int src_wins_tot_w = env->out.device_num*(SRC_WIN_W+BORDER)+BORDER;
+
+ /* x coordinate of the center of the keypad */
+ int x0 = MAX(env->rem_dpy.w+gui->keypad->w/2+2*BORDER, src_wins_tot_w/2);
+
#if 0
ast_log(LOG_WARNING, "event %d %d have %d/%d regions at %p\n",
button.x, button.y, gui->kp_used, gui->kp_size, gui->kp);
#endif
/* for each mousedown we end previous drag */
gui->drag.drag_window = DRAG_NONE;
-
+
/* define keypad boundary */
- if (button.x < env->rem_dpy.w)
- index = KEY_REM_DPY; /* click on remote video */
- else if (button.x > env->rem_dpy.w + gui->keypad->w)
- index = KEY_LOC_DPY; /* click on local video */
- else if (button.y > gui->keypad->h)
- index = KEY_OUT_OF_KEYPAD; /* click outside the keypad */
- else if (gui->kp) {
- int i;
- for (i = 0; i < gui->kp_used; i++) {
- if (kp_match_area(&gui->kp[i], button.x - env->rem_dpy.w, button.y)) {
- index = gui->kp[i].c;
- break;
+ /* XXX this should be extended for clicks on different audio device markers */
+ if (button.y >= (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0)) {
+ /* if control reaches this point this means that the clicked point is
+ below the row of the additional sources windows*/
+ /* adjust the y coordinate as if additional devices windows were not present */
+ button.y -= (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0);
+ if (button.y < BORDER)
+ index = KEY_OUT_OF_KEYPAD;
+ else if (button.y >= MAX(MAX(env->rem_dpy.h, env->loc_dpy.h), gui->keypad->h))
+ index = KEY_OUT_OF_KEYPAD;
+ else if (button.x < x0 - gui->keypad->w/2 - BORDER - env->rem_dpy.w)
+ index = KEY_OUT_OF_KEYPAD;
+ else if (button.x < x0 - gui->keypad->w/2 - BORDER)
+ index = KEY_REM_DPY;
+ else if (button.x < x0 - gui->keypad->w/2)
+ index = KEY_OUT_OF_KEYPAD;
+ else if (button.x >= x0 + gui->keypad->w/2 + BORDER + env->loc_dpy.w)
+ index = KEY_OUT_OF_KEYPAD;
+ else if (button.x >= x0 + gui->keypad->w/2 + BORDER)
+ index = KEY_LOC_DPY;
+ else if (button.x >= x0 + gui->keypad->w/2)
+ index = KEY_OUT_OF_KEYPAD;
+ else if (gui->kp) {
+ /* we have to calculate the first coordinate
+ inside the keypad before calling the kp_match_area*/
+ int x_keypad = button.x - (x0 - gui->keypad->w/2);
+ /* find the key clicked (if one was clicked) */
+ for (i = 0; i < gui->kp_used; i++) {
+ if (kp_match_area(&gui->kp[i],x_keypad, button.y - BORDER)) {
+ index = gui->kp[i].c;
+ break;
+ }
}
}
+ } else if (button.y < BORDER) {
+ index = KEY_OUT_OF_KEYPAD;
+ } else { /* we are in the thumbnail area */
+ x = x0 - src_wins_tot_w/2 + BORDER;
+ if (button.y >= BORDER + SRC_WIN_H)
+ index = KEY_OUT_OF_KEYPAD;
+ else if (button.x < x)
+ index = KEY_OUT_OF_KEYPAD;
+ else if (button.x < x + src_wins_tot_w - BORDER) {
+ /* note that the additional device windows
+ are numbered from left to right
+ starting from 0, with a maximum of 8, the index associated on a click is:
+ KEY_SRCS_WIN + number_of_the_window */
+ for (i = 1; i <= env->out.device_num; i++) {
+ if (button.x < x+i*(SRC_WIN_W+BORDER)-BORDER) {
+ index = KEY_SRCS_WIN+i-1;
+ break;
+ } else if (button.x < x+i*(SRC_WIN_W+BORDER)) {
+ index = KEY_OUT_OF_KEYPAD;
+ break;
+ }
+ }
+ } else
+ index = KEY_OUT_OF_KEYPAD;
}
/* exec the function */
@@ -397,6 +692,37 @@ static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button
keypad_digit(env, index);
return;
}
+
+ else if (index >= KEY_SRCS_WIN && index < KEY_SRCS_WIN+env->out.device_num) {
+ index -= KEY_SRCS_WIN; /* index of the window, equal to the device index in the table */
+ /* if one of the additional device windows is clicked with
+ left or right mouse button, we have to switch to that device */
+ if (button.button == SDL_BUTTON_RIGHT || button.button == SDL_BUTTON_LEFT) {
+ switch_video_out(env, index, button.button);
+ return;
+ }
+ /* turn on or off the devices selectively with other mouse buttons */
+ else {
+ int ret = turn_on_off(index, env);
+ /* print a message according to what happened */
+ if (!ret)
+ ast_log(LOG_WARNING, "unable to turn on device %s\n",
+ env->out.devices[index].name);
+ else if (ret == 1)
+ ast_log(LOG_WARNING, "device %s changed state to on\n",
+ env->out.devices[index].name);
+ else if (ret == 2)
+ ast_log(LOG_WARNING, "device %s changed state to off\n",
+ env->out.devices[index].name);
+ return;
+ }
+ }
+
+ /* XXX for future implementation
+ else if (click on audio source marker)
+ change audio source device
+ */
+
switch (index) {
/* answer/close function */
case KEY_PICK_UP:
@@ -407,9 +733,10 @@ static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button
break;
/* other functions */
- case KEY_MUTE:
+ case KEY_MUTE: /* send or not send the audio */
case KEY_AUTOANSWER:
- case KEY_SENDVIDEO:
+ case KEY_SENDVIDEO: /* send or not send the video */
+ case KEY_PIP: /* activate/deactivate picture in picture mode */
keypad_toggle(env, index);
break;
@@ -418,6 +745,13 @@ static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button
case KEY_REMOTEVIDEO:
break;
+#ifdef notyet /* XXX for future implementations */
+ case KEY_FREEZE:
+ break
+ case KEY_CAPTURE:
+ break;
+#endif
+
case KEY_MESSAGEBOARD:
if (button.button == SDL_BUTTON_LEFT)
set_drag(&gui->drag, button.x, button.y, DRAG_MESSAGE);
@@ -427,8 +761,26 @@ static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button
case KEY_LOC_DPY:
case KEY_REM_DPY:
if (button.button == SDL_BUTTON_LEFT) {
- if (index == KEY_LOC_DPY)
+ /* values used to find the position of the picture in picture (if present) */
+ int pip_loc_x = (double)env->out.pip_x/env->enc_in.w * env->loc_dpy.w;
+ int pip_loc_y = (double)env->out.pip_y/env->enc_in.h * env->loc_dpy.h;
+ /* check if picture in picture is active and the click was on it */
+ if (index == KEY_LOC_DPY && env->out.picture_in_picture &&
+ button.x >= x0+gui->keypad->w/2+BORDER+pip_loc_x &&
+ button.x < x0+gui->keypad->w/2+BORDER+pip_loc_x+env->loc_dpy.w/3 &&
+ button.y >= BORDER+pip_loc_y &&
+ button.y < BORDER+pip_loc_y+env->loc_dpy.h/3) {
+ /* set the y cordinate to his previous value */
+ button.y += (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0);
+ /* starts dragging the picture inside the picture */
+ set_drag(&gui->drag, button.x, button.y, DRAG_PIP);
+ }
+ else if (index == KEY_LOC_DPY) {
+ /* set the y cordinate to his previous value */
+ button.y += (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0);
+ /* click in the local display, but not on the PiP */
set_drag(&gui->drag, button.x, button.y, DRAG_LOCAL);
+ }
break;
} else {
char buf[128];
@@ -437,9 +789,21 @@ static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button
fb->w, fb->h);
video_geom(fb, buf);
sdl_setup(env);
+ /* writes messages in the source boards, those can be
+ modified during the execution, because of the events
+ this must be done here, otherwise the status of sources will not be
+ shown after sdl_setup */
+ for (i = 0; i < env->out.device_num; i++) {
+ update_device_info(env, i);
+ }
+ /* we also have to refresh other boards,
+ to avoid messages to disappear after video resize */
+ print_message(gui->bd_msg, " \b");
+ print_message(gui->bd_dialed, " \b");
}
break;
case KEY_OUT_OF_KEYPAD:
+ ast_log(LOG_WARNING, "nothing clicked, coordinates: %d, %d\n", button.x, button.y);
break;
case KEY_DIGIT_BACKGROUND:
@@ -524,7 +888,7 @@ static void handle_keyboard_input(struct video_desc *env, SDL_keysym *ks)
return;
}
-static void grabber_move(struct video_out_desc *, int dx, int dy);
+static void grabber_move(struct video_device *, int dx, int dy);
int compute_drag(int *start, int end, int magnifier);
int compute_drag(int *start, int end, int magnifier)
@@ -539,6 +903,33 @@ int compute_drag(int *start, int end, int magnifier)
return delta;
}
+/*! \brief This function moves the picture in picture,
+ * controlling the limits of the containing buffer
+ * to avoid problems deriving from going through the limits.
+ *
+ * \param env = pointer to the descriptor of the video environment
+ * \param dx = the variation of the x position
+ * \param dy = the variation of the y position
+*/
+static void pip_move(struct video_desc* env, int dx, int dy) {
+ int new_pip_x = env->out.pip_x+dx;
+ int new_pip_y = env->out.pip_y+dy;
+ /* going beyond the left borders */
+ if (new_pip_x < 0)
+ new_pip_x = 0;
+ /* going beyond the right borders */
+ else if (new_pip_x > env->enc_in.w - env->enc_in.w/3)
+ new_pip_x = env->enc_in.w - env->enc_in.w/3;
+ /* going beyond the top borders */
+ if (new_pip_y < 0)
+ new_pip_y = 0;
+ /* going beyond the bottom borders */
+ else if (new_pip_y > env->enc_in.h - env->enc_in.h/3)
+ new_pip_y = env->enc_in.h - env->enc_in.h/3;
+ env->out.pip_x = new_pip_x;
+ env->out.pip_y = new_pip_y;
+}
+
/*
* I am seeing some kind of deadlock or stall around
* SDL_PumpEvents() while moving the window on a remote X server
@@ -593,11 +984,24 @@ static void eventhandler(struct video_desc *env, const char *caption)
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONUP:
- if (drag->drag_window == DRAG_LOCAL) {
+ if (drag->drag_window == DRAG_LOCAL && env->out.device_num) {
/* move the capture source */
int dx = compute_drag(&drag->x_start, ev[i].motion.x, 3);
int dy = compute_drag(&drag->y_start, ev[i].motion.y, 3);
- grabber_move(&env->out, dx, dy);
+ grabber_move(&env->out.devices[env->out.device_primary], dx, dy);
+ } else if (drag->drag_window == DRAG_PIP) {
+ /* move the PiP image inside the frames of the enc_in buffers */
+ int dx = ev[i].motion.x - drag->x_start;
+ int dy = ev[i].motion.y - drag->y_start;
+ /* dx and dy value are directly applied to env->out.pip_x and
+ env->out.pip_y, so they must work as if the format was cif */
+ dx = (double)dx*env->enc_in.w/env->loc_dpy.w;
+ dy = (double)dy*env->enc_in.h/env->loc_dpy.h;
+ /* sets starts to a new value */
+ drag->x_start = ev[i].motion.x;
+ drag->y_start = ev[i].motion.y;
+ /* ast_log(LOG_WARNING, "moving: %d, %d\n", dx, dy); */
+ pip_move(env, dx, dy);
} else if (drag->drag_window == DRAG_MESSAGE) {
/* scroll up/down the window */
int dy = compute_drag(&drag->y_start, ev[i].motion.y, 1);
@@ -808,7 +1212,15 @@ static void sdl_setup(struct video_desc *env)
const SDL_VideoInfo *info;
int kp_w = 0, kp_h = 0; /* keypad width and height */
struct gui_info *gui = env->gui;
-
+
+ /* Some helper variables used for filling the SDL window */
+ int x0; /* the x coordinate of the center of the keypad */
+ int x1; /* userful for calculating of the size of the parent window */
+ int y0; /* y coordinate of the keypad, the remote window and the local window */
+ int src_wins_tot_w; /* total width of the source windows */
+ int i;
+ int x; /* useful for the creation of the source windows; */
+
#ifdef HAVE_X11
const char *e = getenv("SDL_WINDOWID");
@@ -830,6 +1242,8 @@ static void sdl_setup(struct video_desc *env)
* initialize the SDL environment. We have one large window
* with local and remote video, and a keypad.
* At the moment we arrange them statically, as follows:
+ * - top row: thumbnails for local video sources;
+ * - next row: message boards for local video sources
* - on the left, the remote video;
* - on the center, the keypad
* - on the right, the local video
@@ -869,13 +1283,23 @@ static void sdl_setup(struct video_desc *env)
kp_h = gui->keypad->h;
}
}
- /* XXX same for other boards */
-#define BORDER 5 /* border around our windows */
- maxw = env->rem_dpy.w + env->loc_dpy.w + kp_w;
- maxh = MAX( MAX(env->rem_dpy.h, env->loc_dpy.h), kp_h);
- maxw += 4 * BORDER;
- maxh += 2 * BORDER;
-
+
+ /* total width of the thumbnails */
+ src_wins_tot_w = env->out.device_num*(SRC_WIN_W+BORDER)+BORDER;
+
+ /* x coordinate of the center of the keypad */
+ x0 = MAX(env->rem_dpy.w+kp_w/2+2*BORDER, src_wins_tot_w/2);
+
+ /* from center of the keypad to right border */
+ x1 = MAX(env->loc_dpy.w+kp_w/2+2*BORDER, src_wins_tot_w/2);
+
+ /* total width of the SDL window to create */
+ maxw = x0+x1;
+
+ /* total height of the mother window to create */
+ maxh = MAX( MAX(env->rem_dpy.h, env->loc_dpy.h), kp_h)+2*BORDER;
+ maxh += env->out.device_num ? (2*BORDER+SRC_WIN_H+SRC_MSG_BD_H) : 0;
+
gui->screen = SDL_SetVideoMode(maxw, maxh, depth, 0);
if (!gui->screen) {
ast_log(LOG_ERROR, "SDL: could not set video mode - exiting\n");
@@ -974,24 +1398,53 @@ static void sdl_setup(struct video_desc *env)
}
} while (0);
#endif /* HAVE_X11 */
+
+ y0 = env->out.device_num ? (3*BORDER+SRC_WIN_H+SRC_MSG_BD_H) : BORDER;
+
SDL_WM_SetCaption("Asterisk console Video Output", NULL);
+
+ /* intialize the windows for local and remote video */
if (set_win(gui->screen, &gui->win[WIN_REMOTE], dpy_fmt,
- env->rem_dpy.w, env->rem_dpy.h, BORDER, BORDER))
+ env->rem_dpy.w, env->rem_dpy.h, x0-kp_w/2-BORDER-env->rem_dpy.w, y0))
goto no_sdl;
if (set_win(gui->screen, &gui->win[WIN_LOCAL], dpy_fmt,
env->loc_dpy.w, env->loc_dpy.h,
- 3*BORDER+env->rem_dpy.w + kp_w, BORDER))
+ x0+kp_w/2+BORDER, y0))
goto no_sdl;
+
+ /* initialize device_num source windows (thumbnails) and boards
+ (for a maximum of 9 additional windows and boards) */
+ x = x0 - src_wins_tot_w/2 + BORDER;
+ for (i = 0; i < env->out.device_num; i++){
+ struct thumb_bd *p = &gui->thumb_bd_array[i];
+ if (set_win(gui->screen, &gui->win[i+WIN_SRC1], dpy_fmt,
+ SRC_WIN_W, SRC_WIN_H, x+i*(BORDER+SRC_WIN_W), BORDER))
+ goto no_sdl;
+ /* set geometry for the rect for the message board of the device */
+ p->rect.w = SRC_WIN_W;
+ p->rect.h = SRC_MSG_BD_H;
+ p->rect.x = x+i*(BORDER+SRC_WIN_W);
+ p->rect.y = 2*BORDER+SRC_WIN_H;
+ /* the white color is used as background */
+ SDL_FillRect(gui->screen, &p->rect,
+ SDL_MapRGB(gui->screen->format, 255, 255, 255));
+ /* if necessary, initialize boards for the sources */
+ if (!p->board)
+ p->board =
+ board_setup(gui->screen, &p->rect,
+ gui->font, gui->font_rects);
+ /* update board rect */
+ SDL_UpdateRect(gui->screen, p->rect.x, p->rect.y, p->rect.w, p->rect.h);
+ }
/* display the skin, but do not free it as we need it later to
- * restore text areas and maybe sliders too.
- */
+ restore text areas and maybe sliders too */
if (gui->keypad) {
struct SDL_Rect *dest = &gui->win[WIN_KEYPAD].rect;
struct SDL_Rect *src = (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) ? & gui->kp_rect : NULL;
/* set the coordinates of the keypad relative to the main screen */
- dest->x = 2*BORDER + env->rem_dpy.w;
- dest->y = BORDER;
+ dest->x = x0-kp_w/2;
+ dest->y = y0;
dest->w = kp_w;
dest->h = kp_h;
SDL_BlitSurface(gui->keypad, src, gui->screen, dest);
@@ -1003,7 +1456,7 @@ static void sdl_setup(struct video_desc *env)
no_sdl:
/* free resources in case of errors */
- env->gui = cleanup_sdl(gui);
+ env->gui = cleanup_sdl(gui, env->out.device_num);
}
/*
@@ -1043,6 +1496,7 @@ static int kp_match_area(const struct keypad_entry *e, int x, int y)
struct _s_k { const char *s; int k; };
static struct _s_k gui_key_map[] = {
+ {"PIP", KEY_PIP},
{"PICK_UP", KEY_PICK_UP },
{"PICKUP", KEY_PICK_UP },
{"HANG_UP", KEY_HANG_UP },
diff --git a/channels/console_video.c b/channels/console_video.c
index c17cb6d20..dbe7903df 100644
--- a/channels/console_video.c
+++ b/channels/console_video.c
@@ -87,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
@@ -145,9 +148,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:
@@ -157,7 +178,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 grabber_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.
*/
@@ -168,7 +190,6 @@ 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;
@@ -184,10 +205,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 ? */
+
+ /* 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*/
- struct grab_desc *grabber;
- void *grabber_data;
+ /* 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;
};
/*
@@ -216,7 +248,11 @@ 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]) */
-
+ /*display for sources in additional windows,
+ ideally infinite additional sources could be present
+ pratically we assume a maximum of 9 sources to show*/
+ struct fbuf_t src_dpy[MAX_VIDEO_SOURCES]; /* no buffer allocated here */
+
/* local information for grabbers, codecs, gui */
struct gui_info *gui;
struct video_dec_desc *in; /* remote video descriptor */
@@ -238,54 +274,108 @@ void fbuf_free(struct fbuf_t *b)
b->pix_fmt = x.pix_fmt;
}
+#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];
+
+ 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"
-/*! \brief Try to open a video source, return 0 on success, 1 on 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;
+ int i, j;
- for (i = 0; (g = console_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;
+ /* 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;
}
}
+ /* 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 */
+ }
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 struct fbuf_t *grabber_read(struct video_out_desc *v)
+static struct fbuf_t *grabber_read(struct video_device *dev, int fps)
{
struct timeval now = ast_tvnow();
- if (v->grabber == 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 */
- return v->grabber->read(v->grabber_data);
+ 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);
}
/*! \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_out_desc *v, int dx, int dy)
+static void grabber_move(struct video_device *dev, int dx, int dy)
{
- if (v->grabber && v->grabber->move)
- v->grabber->move(v->grabber_data, dx, dy);
+ if (dev->grabber && dev->grabber->move)
+ dev->grabber->move(dev->grabber_data, dx, dy);
}
/*
@@ -313,7 +403,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;
@@ -329,11 +420,18 @@ static int video_out_uninit(struct video_desc *env)
/* release the buffers */
fbuf_free(&env->enc_in);
fbuf_free(&v->enc_out);
- /* close the grabber */
- if (v->grabber) {
- v->grabber_data = v->grabber->close(v->grabber_data);
- v->grabber = NULL;
+ /* 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;
return -1;
}
@@ -462,7 +560,8 @@ void console_video_uninit(struct video_desc *env)
}
/*! 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)
@@ -471,23 +570,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 */
-
+ int sample_size = 1;
+
bzero(p, 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 */
@@ -495,6 +597,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;
}
@@ -506,22 +616,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");
@@ -529,7 +647,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 */
@@ -633,30 +751,76 @@ int console_write_video(struct ast_channel *chan, struct ast_frame *f)
}
-/*! \brief read a frame from webcam or X11 through grabber_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;
- struct fbuf_t *loc_src = grabber_read(v);
-
- if (!loc_src)
- return NULL; /* can happen, e.g. we are reading too early */
-
+ 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;
+ }
+ /* 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(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;
@@ -669,8 +833,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.
*/
@@ -679,6 +843,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
@@ -696,25 +861,25 @@ static void *video_thread(void *arg)
if (!ast_strlen_zero(save_display))
setenv("DISPLAY", save_display, 1);
- /* initialize grab coordinates */
- 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 (grabber_open(&env->out)) {
ast_log(LOG_WARNING, "cannot open local video source\n");
- } else {
-#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);
+ }
+
+ 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. */
+ 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 (;;) {
@@ -726,9 +891,9 @@ static void *video_thread(void *arg)
/* 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
@@ -777,8 +942,15 @@ static void *video_thread(void *arg)
if (!f)
continue;
chan = env->owner;
- if (chan == NULL)
+ 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);
@@ -809,7 +981,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;
@@ -833,6 +1005,7 @@ static void init_env(struct video_desc *env)
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 */
@@ -845,6 +1018,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;
}
/*!
@@ -884,7 +1069,10 @@ 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);
}
+ /* XXX below probably can use ast_pthread_create_detace\hed() */
ast_pthread_create_background(&env->vthread, NULL, video_thread, env);
+ /* detach the thread to make sure memory is freed on termination */
+ pthread_detach(env->vthread);
if (env->owner == NULL)
env->stayopen = 1; /* manually opened so don't close on hangup */
}
@@ -941,6 +1129,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)
{
@@ -948,7 +1180,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")) {
@@ -1005,14 +1237,17 @@ int console_video_config(struct video_desc **penv,
}
/* set default values */
- ast_copy_string(env->out.videodevice, "X11", sizeof(env->out.videodevice));
+ 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;
+ env->out.picture_in_picture = 0; /* PiP mode intially disabled */
}
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_geometry, val));