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 /* 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 #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*/ - struct grab_desc *grabber; - void *grabber_data; + 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; }; /* @@ -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));