blob: 06d6d608f23d124b078fb22d8ca26509f8090e34 [file] [log] [blame]
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001/*
2 * Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
3 * Author: Sebastien Bourdelin <sebastien.bourdelin@savoirfairelinux.com>
4 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 * Additional permission under GNU GPL version 3 section 7:
21 *
22 * If you modify this program, or any covered work, by linking or
23 * combining it with the OpenSSL project's OpenSSL library (or a
24 * modified version of that library), containing parts covered by the
25 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
26 * grants you additional permission to convey the resulting work.
27 * Corresponding Source for a non-source form of such a combination
28 * shall include the source code for the parts of OpenSSL used as well
29 * as that of the covered work.
30 */
31
32#include "video_widget.h"
33
34#include <clutter/clutter.h>
35#include <clutter-gtk/clutter-gtk.h>
36#include <video/renderer.h>
37
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040038#define VIDEO_LOCAL_SIZE 150
39#define VIDEO_LOCAL_OPACITY_DEFAULT 150 /* out of 255 */
40
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040041/* check video frame queues at this rate;
42 * use 30 ms (about 30 fps) since we don't expect to
43 * receive video frames faster than that */
44#define FRAME_RATE_PERIOD 30
45
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050046struct _VideoWidgetClass {
47 GtkBinClass parent_class;
48};
49
50struct _VideoWidget {
51 GtkBin parent;
52};
53
54typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
55
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040056typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
57
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050058struct _VideoWidgetPrivate {
59 GtkWidget *clutter_widget;
60 ClutterActor *stage;
61 ClutterActor *video_container;
62
63 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040064 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050065
66 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040067 VideoWidgetRenderer *local;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040068
69 guint frame_timeout_source;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040070};
71
72struct _VideoWidgetRenderer {
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040073 GAsyncQueue *frame_queue;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040074 ClutterActor *actor;
75 Video::Renderer *renderer;
76 QMetaObject::Connection frame_update;
77 QMetaObject::Connection render_stop;
78 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050079};
80
81G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
82
83#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
84
85typedef struct _FrameInfo
86{
87 ClutterActor *image_actor;
88 ClutterContent *image;
89 guchar *data;
90 gint data_size;
91 gint width;
92 gint height;
93} FrameInfo;
94
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040095/* static prototypes */
96static FrameInfo *prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor);
97static void free_framedata(gpointer data);
98static void clutter_render_image(FrameInfo *frame);
99static gboolean check_frame_queue(VideoWidget *self);
100static void renderer_stop(VideoWidgetRenderer *renderer);
101static void renderer_start(VideoWidgetRenderer *renderer);
102static void video_widget_set_renderer(VideoWidgetRenderer *renderer);
103
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500104/*
105 * video_widget_dispose()
106 *
107 * The dispose function for the video_widget class.
108 */
109static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400110video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500111{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400112 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500113 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
114
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400115 QObject::disconnect(priv->remote->frame_update);
116 QObject::disconnect(priv->remote->render_stop);
117 QObject::disconnect(priv->remote->render_start);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500118
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400119 QObject::disconnect(priv->local->frame_update);
120 QObject::disconnect(priv->local->render_stop);
121 QObject::disconnect(priv->local->render_start);
122
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400123 /* dispose may be called multiple times, make sure
124 * not to call g_source_remove more than once */
125 if (priv->frame_timeout_source) {
126 g_source_remove(priv->frame_timeout_source);
127 priv->frame_timeout_source = 0;
128 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400129
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400130 if (priv->remote->frame_queue) {
131 g_async_queue_unref(priv->remote->frame_queue);
132 priv->remote->frame_queue = NULL;
133 }
134
135 if (priv->local->frame_queue) {
136 g_async_queue_unref(priv->local->frame_queue);
137 priv->local->frame_queue = NULL;
138 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400139
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400140 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500141}
142
143
144/*
145 * video_widget_finalize()
146 *
147 * The finalize function for the video_widget class.
148 */
149static void
150video_widget_finalize(GObject *object)
151{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400152 VideoWidget *self = VIDEO_WIDGET(object);
153 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
154
155 g_free(priv->remote);
156 g_free(priv->local);
157
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500158 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
159}
160
161
162/*
163 * video_widget_class_init()
164 *
165 * This function init the video_widget_class.
166 */
167static void
168video_widget_class_init(VideoWidgetClass *klass)
169{
170 GObjectClass *object_class = G_OBJECT_CLASS(klass);
171
172 /* override method */
173 object_class->dispose = video_widget_dispose;
174 object_class->finalize = video_widget_finalize;
175}
176
177
178/*
179 * video_widget_init()
180 *
181 * This function init the video_widget.
182 * - init clutter
183 * - init all the widget members
184 */
185static void
186video_widget_init(VideoWidget *self)
187{
188 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
189
190 /* init clutter widget */
191 priv->clutter_widget = gtk_clutter_embed_new();
192 /* add it to the video_widget */
193 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
194 /* get the stage */
195 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
196
197 /* layout manager is used to arrange children in space, here we ask clutter
198 * to align children to fill the space when resizing the window */
199 clutter_actor_set_layout_manager(priv->stage,
200 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
201
202 /* add a scene container where we can add and remove our actors */
203 priv->video_container = clutter_actor_new();
204 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
205 clutter_actor_add_child(priv->stage, priv->video_container);
206
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400207 /* init the remote and local structs */
208 priv->remote = g_new0(VideoWidgetRenderer, 1);
209 priv->local = g_new0(VideoWidgetRenderer, 1);
210
211 /* arrange remote actors */
212 priv->remote->actor = clutter_actor_new();
213 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
214 /* the remote camera must always fill the container size */
215 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
216 CLUTTER_BIND_SIZE, 0);
217 clutter_actor_add_constraint(priv->remote->actor, constraint);
218
219 /* arrange local actor */
220 priv->local->actor = clutter_actor_new();
221 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
222 /* set size to square, but it will stay the aspect ratio when the image is rendered */
223 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
224 /* set position constraint to right cornder */
225 constraint = clutter_align_constraint_new(priv->video_container,
226 CLUTTER_ALIGN_BOTH, 0.99);
227 clutter_actor_add_constraint(priv->local->actor, constraint);
228 clutter_actor_set_opacity(priv->local->actor,
229 VIDEO_LOCAL_OPACITY_DEFAULT);
230
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400231 /* init frame queues and the timeout sources to check them */
232 priv->remote->frame_queue = g_async_queue_new_full((GDestroyNotify)free_framedata);
233 priv->local->frame_queue = g_async_queue_new_full((GDestroyNotify)free_framedata);
234 priv->frame_timeout_source = g_timeout_add(FRAME_RATE_PERIOD, (GSourceFunc)check_frame_queue, self);
235
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500236 /* TODO: handle button event in screen */
237 // g_signal_connect(screen, "button-press-event",
238 // G_CALLBACK(on_button_press_in_screen_event_cb),
239 // self);
240
241 /* TODO: init drag & drop images */
242 // gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
243 // gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
244 // g_signal_connect(GTK_WIDGET(self), "drag-data-received",
245 // G_CALLBACK(on_drag_data_received_cb), NULL);
246}
247
248static FrameInfo *
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400249prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500250{
251 const QByteArray& data = renderer->currentFrame();
252 QSize res = renderer->size();
253
254 /* copy frame data */
255 gpointer frame_data = g_memdup((gconstpointer)data.constData(), data.size());
256
257 FrameInfo *frame = g_new0(FrameInfo, 1);
258
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500259 frame->image_actor = image_actor;
260 frame->data = (guchar *)frame_data;
261 frame->data_size = data.size();
262 frame->width = res.width();
263 frame->height = res.height();
264
265 return frame;
266}
267
268static void
269free_framedata(gpointer data)
270{
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400271 if (data == NULL) return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500272 FrameInfo *frame = (FrameInfo *)data;
273 g_free(frame->data);
274 g_free(frame);
275}
276
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400277static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500278clutter_render_image(FrameInfo *frame)
279{
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400280 if (frame == NULL) return;
281 g_return_if_fail(CLUTTER_IS_ACTOR(frame->image_actor));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500282
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400283 ClutterContent * image_new = clutter_image_new();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500284
285 const gint BPP = 4;
286 const gint ROW_STRIDE = BPP * frame->width;
287
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500288 GError *error = NULL;
289 clutter_image_set_data(
290 CLUTTER_IMAGE(image_new),
291 frame->data,
292 COGL_PIXEL_FORMAT_BGRA_8888,
293 frame->width,
294 frame->height,
295 ROW_STRIDE,
296 &error);
297 if (error) {
298 g_warning("error rendering image to clutter: %s", error->message);
299 g_error_free(error);
300 }
301
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400302 clutter_actor_set_content(frame->image_actor, image_new);
303 g_object_unref (image_new);
304 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
305 * that the aspect ratio is correct
306 */
307 clutter_actor_set_content_gravity(frame->image_actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400308}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500309
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400310static gboolean
311check_frame_queue(VideoWidget *self)
312{
313 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
314 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
315
316 /* get the latest frame in the queue */
317 gpointer local_data_last = NULL;
318 gpointer local_data_next = g_async_queue_try_pop(priv->local->frame_queue);
319 while(local_data_next != NULL) {
320 // if (local_data_last != NULL) g_debug("skipping local frame");
321 /* make sure to free the frame we're skipping */
322 free_framedata(local_data_last);
323 local_data_last = local_data_next;
324 local_data_next = g_async_queue_try_pop(priv->local->frame_queue);
325 }
326
327 /* display the frame */
328 clutter_render_image((FrameInfo *)local_data_last);
329 free_framedata(local_data_last);
330
331 /* get the latest frame in the queue */
332 gpointer remote_data_last = NULL;
333 gpointer remote_data_next = g_async_queue_try_pop(priv->remote->frame_queue);
334 while(remote_data_next != NULL) {
335 // if (remote_data_last != NULL) g_debug("skipping remote frame");
336 /* make sure to free the frame we're skipping */
337 free_framedata(remote_data_last);
338 remote_data_last = remote_data_next;
339 remote_data_next = g_async_queue_try_pop(priv->remote->frame_queue);
340 }
341
342 /* display the frame */
343 clutter_render_image((FrameInfo *)remote_data_last);
344 free_framedata(remote_data_last);
345
346 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500347}
348
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400349static void
350renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500351{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400352 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400353 QObject::disconnect(renderer->frame_update);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500354}
355
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400356static void
357renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500358{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400359 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400360 renderer->frame_update = QObject::connect(
361 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500362 &Video::Renderer::frameUpdated,
363 [=]() {
364
365 /* this callback comes from another thread;
366 * rendering must be done in the main loop;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400367 * copy the frame and add it to the frame queue
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500368 */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400369 FrameInfo *frame = prepare_framedata(renderer->renderer,
370 renderer->actor);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400371 g_async_queue_push(renderer->frame_queue, frame);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500372 }
373 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500374}
375
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400376static void
377video_widget_set_renderer(VideoWidgetRenderer *renderer)
378{
379 if (renderer == NULL) return;
380
381 /* update the renderer */
382 QObject::disconnect(renderer->frame_update);
383 QObject::disconnect(renderer->render_stop);
384 QObject::disconnect(renderer->render_start);
385
386 renderer_start(renderer);
387
388 renderer->render_stop = QObject::connect(
389 renderer->renderer,
390 &Video::Renderer::stopped,
391 [=]() {
392 renderer_stop(renderer);
393 }
394 );
395
396 renderer->render_start = QObject::connect(
397 renderer->renderer,
398 &Video::Renderer::started,
399 [=]() {
400 renderer_start(renderer);
401 }
402 );
403}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500404
405/*
406 * video_widget_new()
407 *
408 * The function use to create a new video_widget
409 */
410GtkWidget*
411video_widget_new(void)
412{
413 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
414 return self;
415}
416
417void
418video_widget_set_remote_renderer(VideoWidget *self, Video::Renderer *renderer_remote_new)
419{
420 g_return_if_fail(IS_VIDEO_WIDGET(self));
421 if (renderer_remote_new == NULL) return;
422
423 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
424
425 /* update the renderer */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400426 priv->remote->renderer = renderer_remote_new;
427 video_widget_set_renderer(priv->remote);
428}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500429
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400430void
431video_widget_set_local_renderer(VideoWidget *self, Video::Renderer *renderer_local_new)
432{
433 g_return_if_fail(IS_VIDEO_WIDGET(self));
434 if (renderer_local_new == NULL) return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500435
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400436 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500437
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400438 /* update the renderer */
439 priv->local->renderer = renderer_local_new;
440 video_widget_set_renderer(priv->local);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500441}