From 54e2fe8d6fc853007c70e736df3fe8c2bb72b480 Mon Sep 17 00:00:00 2001 From: cnhenry Date: Wed, 5 Aug 2015 23:26:45 -0500 Subject: [PATCH] Implemented video frames ready to send through ToxAV. NOTE: Contains YUV stride issues --- src/audio_call.c | 41 +++++++++++-- src/audio_call.h | 4 +- src/video_call.c | 129 ++++++++++++++++++++++++++++++--------- src/video_device.c | 149 ++++++++++++++++++++++++++++++++++++++------- src/windows.h | 1 + 5 files changed, 266 insertions(+), 58 deletions(-) diff --git a/src/audio_call.c b/src/audio_call.c index be7ad1b..00dc1e1 100644 --- a/src/audio_call.c +++ b/src/audio_call.c @@ -29,6 +29,10 @@ #include "line_info.h" #include "notify.h" + #ifdef VIDEO + #include "video_call.h" + #endif /* VIDEO */ + #include #include #include @@ -132,7 +136,6 @@ ToxAV *init_audio(ToxWindow *self, Tox *tox) CallContrl.video_enabled = false; CallContrl.video_bit_rate = 0; - CallContrl.video_sample_rate = 0; CallContrl.video_frame_duration = 0; #endif /* VIDEO */ @@ -275,8 +278,8 @@ void call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_e TOXAV_ERR_ANSWER error; CallControl* cc = user_data; ToxWindow* window = cc->window; - cc->audio_enabled = audio_enabled; - cc->video_enabled = video_enabled; + //cc->audio_enabled = audio_enabled; + //cc->video_enabled = video_enabled; cc->pending_call = true; callback_recv_invite(av, friend_number, user_data); @@ -288,15 +291,15 @@ void callstate_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_ ToxWindow* window = cc->window; cc->call_state = state; - if( state == TOXAV_FRIEND_CALL_STATE_FINISHED ) { + if ( state == TOXAV_FRIEND_CALL_STATE_FINISHED ) { if ( CallContrl.pending_call ) { CallContrl.pending_call = false; - callback_call_rejected(CallContrl.av, friend_number, &CallContrl); + callback_call_rejected(av, friend_number, &CallContrl); } else { callback_call_ended(av, friend_number, &CallContrl); } } else { - if( state == TOXAV_FRIEND_CALL_STATE_ERROR ) { + if ( state == TOXAV_FRIEND_CALL_STATE_ERROR ) { line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "ToxAV callstate error!"); CallContrl.pending_call = false; callback_call_ended(av, friend_number, &CallContrl); @@ -305,6 +308,32 @@ void callstate_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_ callback_call_started(av, friend_number, &CallContrl); } } + +#ifdef VIDEO + Call* this_call = &CallContrl.calls[friend_number]; + VideoDeviceError error; + if ( state & TOXAV_FRIEND_CALL_STATE_RECEIVING_V ) { + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Receiving video frames"); + error = open_primary_video_device(vdt_output, &this_call->out_idx); + } else { + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "No longer receiving video frames"); + error = close_video_device(vdt_output, &this_call->out_idx); + } + + if ( error == vde_FailedStart) + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to start input video device"); + + if ( error == vde_InternalError ) + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Internal error with opening input video device"); + + if ( error != vde_None ) + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to open output video device!"); + + if ( state & TOXAV_FRIEND_CALL_STATE_SENDING_V ) { + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Sending video frames"); + } + +#endif /* VIDEO */ } void receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, diff --git a/src/audio_call.h b/src/audio_call.h index 70b8e61..06e4e6d 100644 --- a/src/audio_call.h +++ b/src/audio_call.h @@ -57,11 +57,9 @@ typedef struct CallControl { bool video_enabled; uint32_t audio_bit_rate; uint32_t video_bit_rate; - uint32_t audio_sample_rate; - uint32_t video_sample_rate; int32_t audio_frame_duration; int32_t video_frame_duration; - + uint32_t audio_sample_rate; uint8_t audio_channels; } CallControl; diff --git a/src/video_call.c b/src/video_call.c index 905d04c..8f7fe6d 100644 --- a/src/video_call.c +++ b/src/video_call.c @@ -29,8 +29,10 @@ ToxAV *init_video(ToxWindow *self, Tox *tox, ToxAV *av, CallControl *user_data) { user_data->video_enabled = true; + user_data->video_bit_rate = 5000; + user_data->video_frame_duration = 10; - if ( !user_data->av ) { + if ( !av ) { line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Video failed to init ToxAV"); return NULL; } @@ -46,20 +48,38 @@ ToxAV *init_video(ToxWindow *self, Tox *tox, ToxAV *av, CallControl *user_data) return av; } +void terminate_video() +{ + int i; + for (i = 0; i < MAX_CALLS; ++i) + stop_video_transmission(&CallContrl.calls[i], i); + + terminate_video_devices(); +} + void read_video_device_callback(int16_t width, int16_t height, const uint8_t* y, const uint8_t* u, const uint8_t* v, void* data) { TOXAV_ERR_SEND_FRAME error; int32_t friend_number = *((int32_t*)data); /* TODO: Or pass an array of call_idx's */ - line_info_add(CallContrl.window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Read video device"); + if ( toxav_video_send_frame(CallContrl.av, friend_number, width, height, y, u, v, &error ) == false ) { + line_info_add(CallContrl.window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to send video frame"); + if ( error == TOXAV_ERR_SEND_FRAME_NULL ) { + line_info_add(CallContrl.window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Error NULL video frame"); + } else if ( error == TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND ) { + line_info_add(CallContrl.window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Error friend not found"); + } else if ( error = TOXAV_ERR_SEND_FRAME_INVALID ) { + line_info_add(CallContrl.window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Error invalid video frame %i, %i, %i", width, height, friend_number); + } + } } void write_video_device_callback(uint16_t width, uint16_t height, - uint8_t const *y, uint8_t const *u, uint8_t const *v, - int32_t ystride, int32_t ustride, int32_t vstride, - void *user_data) + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, + void *user_data) { - + write_video_out(width, height, y, u, v, ystride, ustride, vstride, user_data); } int start_video_transmission(ToxWindow *self, ToxAV *av, Call *call) @@ -69,10 +89,7 @@ int start_video_transmission(ToxWindow *self, ToxAV *av, Call *call) return -1; } - //if (set_call(call, true) == -1) - // return -1; - - VideoDeviceError error = open_primary_video_device(input, &call->in_idx); + VideoDeviceError error = open_primary_video_device(vdt_input, &call->in_idx); if ( error == vde_FailedStart) line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to start input video device"); @@ -88,13 +105,6 @@ int start_video_transmission(ToxWindow *self, ToxAV *av, Call *call) read_video_device_callback, &self->num) != vde_None) line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to register input video handler!"); - /* - if ( open_primary_device(output, &call->out_idx, - CallContrl.audio_sample_rate, CallContrl.audio_frame_duration, CallContrl.audio_channels) != de_None ) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to open output video device!"); - call->has_output = 0; - }*/ - return 0; } @@ -109,13 +119,47 @@ int stop_video_transmission(Call *call, int friend_number) return -1; } +/* + * End of transmission + */ + + + + + +/* + * Callbacks + */ + void receive_video_frame_cb(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, uint8_t const *y, uint8_t const *u, uint8_t const *v, uint8_t const *a, int32_t ystride, int32_t ustride, int32_t vstride, int32_t astride, void *user_data) { - CallControl* cc = user_data; + CallControl* cc = (CallControl*)user_data; + ToxWindow* window = CallContrl.window; + + Call* this_call = &CallContrl.calls[friend_number]; + VideoDeviceError error; + if ( CallContrl.call_state & TOXAV_FRIEND_CALL_STATE_SENDING_V ) { + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Receiving video frames"); + error = open_primary_video_device(vdt_output, &this_call->out_idx); + } else { + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "No longer receiving video frames"); + error = close_video_device(vdt_output, &this_call->out_idx); + } + + if ( error == vde_FailedStart) + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to start input video device"); + + if ( error == vde_InternalError ) + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Internal error with opening input video device"); + + if ( error != vde_None ) + line_info_add(window, NULL, NULL, NULL, SYS_MSG, 0, 0, "Failed to open output video device!"); + + write_video_device_callback(width, height, y, u, v, ystride, ustride, vstride, user_data); } @@ -124,8 +168,10 @@ void video_bit_rate_status_cb(ToxAV *av, uint32_t friend_number, { CallControl* cc = user_data; - if ( stable ) + if ( stable && CallContrl.call_state & TOXAV_FRIEND_CALL_STATE_SENDING_V ) { cc->video_bit_rate = bit_rate; + toxav_video_bit_rate_set(CallContrl.av, friend_number, CallContrl.video_bit_rate, false, NULL); + } } @@ -137,11 +183,12 @@ void callback_video_starting(void* av, uint32_t friend_number, void *arg) int i; for (i = 0; i < MAX_WINDOWS_NUM; ++i) { if (windows[i].is_call && windows[i].num == friend_number) { - line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Video starting"); if(0 != start_video_transmission(&windows[i], av, &cc->calls[friend_number])) { line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Error starting transmission!"); return; } + windows[i].is_video = true; + line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Video starting"); } } } @@ -150,24 +197,26 @@ void callback_video_ending(void* av, uint32_t friend_number, void *arg) CallControl *cc = (CallControl*)arg; ToxWindow* windows = cc->window; - line_info_add(&windows[friend_number], NULL, NULL, NULL, SYS_MSG, 0, 0, "Video ending"); - int i; for (i = 0; i < MAX_WINDOWS_NUM; ++i) { if (windows[i].is_call && windows[i].num == friend_number) { - line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Video ending"); + line_info_add(&windows[i], NULL, NULL, NULL, SYS_MSG, 0, 0, "Video ending"); + windows[i].is_video = false; } } stop_video_transmission(&cc->calls[friend_number], friend_number); } +/* + * End of Callbacks + */ - - +/* + * Commands from chat_commands.h + */ void cmd_video(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "cmd_video"); const char *error_str; if ( argc != 0 ) { @@ -185,6 +234,21 @@ void cmd_video(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[M goto on_error; } + if ( !self->is_call ) { + error_str = "Not in call!"; + goto on_error; + } + + if ( self->is_video ) { + error_str = "Video is already running in this call."; + goto on_error; + } + + if ( toxav_video_bit_rate_set(CallContrl.av, self->num, CallContrl.video_bit_rate, false, NULL) == false ) { + error_str = "ToxAV video bit rate uninitialized."; + goto on_error; + } + callback_video_starting(CallContrl.av, self->num, &CallContrl); return; @@ -194,7 +258,6 @@ on_error: void cmd_end_video(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "cmd_end_video"); const char *error_str; if ( argc != 0 ) { @@ -207,6 +270,16 @@ void cmd_end_video(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*arg goto on_error; } + if ( !self->is_call ) { + error_str = "Not in call!"; + goto on_error; + } + + if ( !self->is_video ) { + error_str = "Video is not running in this call."; + goto on_error; + } + callback_video_ending(CallContrl.av, self->num, &CallContrl); return; @@ -246,7 +319,7 @@ on_error: print_err (self, error_str); } -/* This changes primary device only */ +/* This changes primary video device only */ void cmd_change_video_device(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MAX_STR_SIZE]) { line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "Change video device"); diff --git a/src/video_device.c b/src/video_device.c index 50d719a..93b53ff 100644 --- a/src/video_device.c +++ b/src/video_device.c @@ -144,8 +144,7 @@ VideoDeviceError init_video_devices() #endif /* __linux__ */ /* TODO: Add OSX implementation for listing input video devices */ - size[vdt_output] = 0; - /* TODO: List output video devices */ + size[vdt_output] = 1; // Start poll thread if (pthread_mutex_init(&video_mutex, NULL) != 0) @@ -174,7 +173,8 @@ VideoDeviceError terminate_video_devices() return (VideoDeviceError) vde_None; } -VideoDeviceError register_video_device_callback(int32_t friend_number, uint32_t device_idx, VideoDataHandleCallback callback, void* data) +VideoDeviceError register_video_device_callback(int32_t friend_number, uint32_t device_idx, + VideoDataHandleCallback callback, void* data) { if (size[vdt_input] <= device_idx || !video_devices_running[vdt_input][device_idx] || video_devices_running[vdt_input][device_idx]->fd == NULL) return vde_InvalidSelection; @@ -329,7 +329,9 @@ VideoDeviceError open_video_device(VideoDeviceType type, int32_t selection, uint #endif /* __linux__ */ /*TODO: Add OSX implementation of opening video devices */ +#ifdef __OSX__ +#endif /*__OSX__*/ /* Initialize X11 window associated to device */ if ((device->x_display = XOpenDisplay(NULL)) == NULL) { @@ -338,8 +340,10 @@ VideoDeviceError open_video_device(VideoDeviceType type, int32_t selection, uint int screen = DefaultScreen(device->x_display); - if ((device->x_window = XCreateSimpleWindow(device->x_display, RootWindow(device->x_display, screen), 0, 0, - device->video_width, device->video_height, 0, BlackPixel(device->x_display, screen), WhitePixel(device->x_display, screen))) == NULL) { + if ((device->x_window = XCreateSimpleWindow(device->x_display, RootWindow(device->x_display, screen), + 0, 0, device->video_width, device->video_height, 0, + BlackPixel(device->x_display, screen), + WhitePixel(device->x_display, screen))) == NULL) { return vde_FailedStart; } @@ -350,15 +354,44 @@ VideoDeviceError open_video_device(VideoDeviceType type, int32_t selection, uint return vde_FailedStart; } - XMapWindow(device->x_display, device->x_window); - //XClearWindow(device->x_display, device->x_window); + XClearWindow(device->x_display, device->x_window); XMapRaised(device->x_display, device->x_window); XFlush(device->x_display); vpx_img_alloc(&device->input, VPX_IMG_FMT_I420, device->video_width, device->video_height, 1); video_thread_paused = false; + } else { + /* Initialize X11 window associated to device */ + if ((device->x_display = XOpenDisplay(NULL)) == NULL) { + return vde_FailedStart; + } + + int screen = DefaultScreen(device->x_display); + + if ((device->x_window = XCreateSimpleWindow(device->x_display, RootWindow(device->x_display, screen), + 0, 0, 800, 400, 0, + BlackPixel(device->x_display, screen), + WhitePixel(device->x_display, screen))) == NULL) { + return vde_FailedStart; + } + + XStoreName(device->x_display, device->x_window, "Video Receive"); + XSelectInput(device->x_display, device->x_window, ExposureMask|ButtonPressMask|KeyPressMask); + + if ((device->x_gc = DefaultGC(device->x_display, screen)) == NULL) { + return vde_FailedStart; + } + + + XMapWindow(device->x_display, device->x_window); + //XClearWindow(device->x_display, device->x_window); + //XMapRaised(device->x_display, device->x_window); + XFlush(device->x_display); + + vpx_img_alloc(&device->input, VPX_IMG_FMT_I420, device->video_width, device->video_height, 1); + } unlock; @@ -370,7 +403,56 @@ __inline VideoDeviceError write_video_out(uint16_t width, uint16_t height, int32_t ystride, int32_t ustride, int32_t vstride, void *user_data) { + VideoDevice* device = video_devices_running[vdt_output][0]; + if (!device) return vde_DeviceNotActive; + + pthread_mutex_lock(device->mutex); + + /* Resize X11 window to correct size */ + if(device->video_width != width || device->video_height != height) { + device->video_width = width; + device->video_height = height; + XResizeWindow(device->x_display, device->x_window, width, height); + vpx_img_free(&device->input); + vpx_img_alloc(&device->input, VPX_IMG_FMT_I420, width, height, 1); + } + int screen = DefaultScreen(device->x_display); + + /* Display video image */ + ystride = abs(ystride); + ustride = abs(ustride); + vstride = abs(vstride); + + uint16_t *img_data = malloc(width * height * 6); + yuv420tobgr(width, height, y, u, v, + ystride, ustride, vstride, img_data); + + XImage image = { + .width = width, + .height = height, + .depth = 24, + .bits_per_pixel = 32, + .format = ZPixmap, + .byte_order = LSBFirst, + .bitmap_unit = 8, + .bitmap_bit_order = LSBFirst, + .bytes_per_line = width * 4, + .red_mask = 0xFF0000, + .green_mask = 0xFF00, + .blue_mask = 0xFF, + .data = (char*)img_data + }; + + Pixmap pixmap = XCreatePixmap(device->x_display, device->x_window, width, height, 24); + XPutImage(device->x_display, pixmap, device->x_gc, &image, 0, 0, 0, 0, width, height); + XCopyArea(device->x_display, pixmap, device->x_window, device->x_gc, 0, 0, width, height, 0, 0); + XFreePixmap(device->x_display, pixmap); + XFlush(device->x_display); + free(img_data); + + pthread_mutex_unlock(device->mutex); + return vde_None; } void* video_thread_poll (void* arg) // TODO: maybe use thread for every input source @@ -407,14 +489,22 @@ void* video_thread_poll (void* arg) // TODO: maybe use thread for every input so void *data = (void*)device->buffers[buf.index].start; uint16_t video_width = device->video_width; uint16_t video_height = device->video_height; + uint8_t *y = device->input.planes[0]; + uint8_t *u = device->input.planes[1]; + uint8_t *v = device->input.planes[2]; int screen = DefaultScreen(device->x_display); /* Convert to YUV420 for ToxAV */ - yuv422to420(device->input.planes[0], device->input.planes[2], device->input.planes[1], data, video_width, video_height); + yuv422to420(y, u, v, data, video_width, video_height); + + /* Send data to friend through ToxAV */ + if ( device->cb ) + device->cb(video_width, video_height, y, u, v, device->cb_data); /* Display image for video preview */ uint8_t *img_data = malloc(video_width * video_height * 4); - yuv420tobgr(video_width, video_height, device->input.planes[0], device->input.planes[1], device->input.planes[2], video_width, video_width/2, video_width/2, img_data); + yuv420tobgr(video_width, video_height, y, u, v, + video_width, video_width/2, video_width/2, img_data); XImage image = { .width = video_width, .height = video_height, @@ -430,18 +520,20 @@ void* video_thread_poll (void* arg) // TODO: maybe use thread for every input so .blue_mask = 0xFF, .data = (char*)img_data }; + Pixmap pixmap = XCreatePixmap(device->x_display, device->x_window, video_width, video_height, 24); XPutImage(device->x_display, pixmap, device->x_gc, &image, 0, 0, 0, 0, video_width, video_height); XCopyArea(device->x_display, pixmap, device->x_window, device->x_gc, 0, 0, video_width, video_height, 0, 0); XFreePixmap(device->x_display, pixmap); - free(img_data); + XFlush(device->x_display); - if ( device->cb ) device->cb(device->video_width, device->video_height, device->input.planes[0], device->input.planes[1], device->input.planes[2], device->cb_data); if (-1 == xioctl(device->fd, VIDIOC_QBUF, &buf)) { unlock; continue; } + + free(img_data); } unlock; } @@ -470,20 +562,32 @@ VideoDeviceError close_video_device(VideoDeviceType type, uint32_t device_idx) if ( !device->ref_count ) { if (type == vdt_input) { + + enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if(-1 == xioctl(device->fd, VIDIOC_STREAMOFF, &buf_type)) {} + int i; for(i = 0; i < device->n_buffers; ++i) { if (-1 == munmap(device->buffers[i].start, device->buffers[i].length)) {} } - - vpx_img_free(&device->input); close(device->fd); + + vpx_img_free(&device->input); XDestroyWindow(device->x_display, device->x_window); + XFlush(device->x_display); XCloseDisplay(device->x_display); - } - else { + pthread_mutex_destroy(device->mutex); + free(device->buffers); + free(device); + } else { + vpx_img_free(&device->input); + XDestroyWindow(device->x_display, device->x_window); + XFlush(device->x_display); + XCloseDisplay(device->x_display); + pthread_mutex_destroy(device->mutex); + free(device); } - free(device); } else device->ref_count--; @@ -491,7 +595,9 @@ VideoDeviceError close_video_device(VideoDeviceType type, uint32_t device_idx) return rc; } -void yuv420tobgr(uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, unsigned int ystride, unsigned int ustride, unsigned int vstride, uint8_t *out) +void yuv420tobgr(uint16_t width, uint16_t height, const uint8_t *y, + const uint8_t *u, const uint8_t *v, unsigned int ystride, + unsigned int ustride, unsigned int vstride, uint8_t *out) { unsigned long int i, j; for (i = 0; i < height; ++i) { @@ -514,16 +620,17 @@ void yuv420tobgr(uint16_t width, uint16_t height, const uint8_t *y, const uint8_ } } -void yuv422to420(uint8_t *plane_y, uint8_t *plane_u, uint8_t *plane_v, uint8_t *input, uint16_t width, uint16_t height) +void yuv422to420(uint8_t *plane_y, uint8_t *plane_u, uint8_t *plane_v, + uint8_t *input, uint16_t width, uint16_t height) { uint8_t *end = input + width * height * 2; while(input != end) { uint8_t *line_end = input + width * 2; while(input != line_end) { - *plane_y++ = *input++; - *plane_v++ = *input++; *plane_y++ = *input++; *plane_u++ = *input++; + *plane_y++ = *input++; + *plane_v++ = *input++; } line_end = input + width * 2; @@ -541,7 +648,7 @@ void print_video_devices(ToxWindow* self, VideoDeviceType type) int i; for (i = 0; i < size[type]; ++i) - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%d: %s", i, video_devices_names[type][i]); + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, 0, "%d: %s", i, &video_devices_names[type][i]); return; } \ No newline at end of file diff --git a/src/windows.h b/src/windows.h index 35a6ec6..2b45873 100644 --- a/src/windows.h +++ b/src/windows.h @@ -154,6 +154,7 @@ struct ToxWindow { #ifdef VIDEO int video_device_selection[2]; /* -1 if not set, if set uses these selections instead of primary video device */ + bool is_video; #endif /* VIDEO */ #endif /* AUDIO */