blob: f8ad9b20e8a58dab3b42446ffdadab70048b2250 [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>
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -040037#include <video/sourcesmodel.h>
38 #include <QtCore/QUrl>
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050039
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040040#define VIDEO_LOCAL_SIZE 150
41#define VIDEO_LOCAL_OPACITY_DEFAULT 150 /* out of 255 */
42
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040043/* check video frame queues at this rate;
44 * use 30 ms (about 30 fps) since we don't expect to
45 * receive video frames faster than that */
46#define FRAME_RATE_PERIOD 30
47
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050048struct _VideoWidgetClass {
49 GtkBinClass parent_class;
50};
51
52struct _VideoWidget {
53 GtkBin parent;
54};
55
56typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
57
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040058typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
59
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050060struct _VideoWidgetPrivate {
61 GtkWidget *clutter_widget;
62 ClutterActor *stage;
63 ClutterActor *video_container;
64
65 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040066 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050067
68 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040069 VideoWidgetRenderer *local;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040070
71 guint frame_timeout_source;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040072};
73
74struct _VideoWidgetRenderer {
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040075 GAsyncQueue *frame_queue;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040076 ClutterActor *actor;
77 Video::Renderer *renderer;
78 QMetaObject::Connection frame_update;
79 QMetaObject::Connection render_stop;
80 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050081};
82
83G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
84
85#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
86
87typedef struct _FrameInfo
88{
89 ClutterActor *image_actor;
90 ClutterContent *image;
91 guchar *data;
92 gint data_size;
93 gint width;
94 gint height;
95} FrameInfo;
96
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040097/* static prototypes */
98static FrameInfo *prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor);
99static void free_framedata(gpointer data);
100static void clutter_render_image(FrameInfo *frame);
101static gboolean check_frame_queue(VideoWidget *self);
102static void renderer_stop(VideoWidgetRenderer *renderer);
103static void renderer_start(VideoWidgetRenderer *renderer);
104static void video_widget_set_renderer(VideoWidgetRenderer *renderer);
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400105static void on_drag_data_received(GtkWidget *, GdkDragContext *, gint, gint, GtkSelectionData *, guint, guint32, gpointer);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400106
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500107/*
108 * video_widget_dispose()
109 *
110 * The dispose function for the video_widget class.
111 */
112static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400113video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500114{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400115 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500116 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
117
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400118 QObject::disconnect(priv->remote->frame_update);
119 QObject::disconnect(priv->remote->render_stop);
120 QObject::disconnect(priv->remote->render_start);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500121
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400122 QObject::disconnect(priv->local->frame_update);
123 QObject::disconnect(priv->local->render_stop);
124 QObject::disconnect(priv->local->render_start);
125
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400126 /* dispose may be called multiple times, make sure
127 * not to call g_source_remove more than once */
128 if (priv->frame_timeout_source) {
129 g_source_remove(priv->frame_timeout_source);
130 priv->frame_timeout_source = 0;
131 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400132
Stepan Salenikovichbf74af82015-03-13 11:35:58 -0400133 if (priv->remote->frame_queue) {
134 g_async_queue_unref(priv->remote->frame_queue);
135 priv->remote->frame_queue = NULL;
136 }
137
138 if (priv->local->frame_queue) {
139 g_async_queue_unref(priv->local->frame_queue);
140 priv->local->frame_queue = NULL;
141 }
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400142
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400143 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500144}
145
146
147/*
148 * video_widget_finalize()
149 *
150 * The finalize function for the video_widget class.
151 */
152static void
153video_widget_finalize(GObject *object)
154{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400155 VideoWidget *self = VIDEO_WIDGET(object);
156 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
157
158 g_free(priv->remote);
159 g_free(priv->local);
160
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500161 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
162}
163
164
165/*
166 * video_widget_class_init()
167 *
168 * This function init the video_widget_class.
169 */
170static void
171video_widget_class_init(VideoWidgetClass *klass)
172{
173 GObjectClass *object_class = G_OBJECT_CLASS(klass);
174
175 /* override method */
176 object_class->dispose = video_widget_dispose;
177 object_class->finalize = video_widget_finalize;
178}
179
180
181/*
182 * video_widget_init()
183 *
184 * This function init the video_widget.
185 * - init clutter
186 * - init all the widget members
187 */
188static void
189video_widget_init(VideoWidget *self)
190{
191 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
192
193 /* init clutter widget */
194 priv->clutter_widget = gtk_clutter_embed_new();
195 /* add it to the video_widget */
196 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
197 /* get the stage */
198 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
199
200 /* layout manager is used to arrange children in space, here we ask clutter
201 * to align children to fill the space when resizing the window */
202 clutter_actor_set_layout_manager(priv->stage,
203 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
204
205 /* add a scene container where we can add and remove our actors */
206 priv->video_container = clutter_actor_new();
207 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
208 clutter_actor_add_child(priv->stage, priv->video_container);
209
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400210 /* init the remote and local structs */
211 priv->remote = g_new0(VideoWidgetRenderer, 1);
212 priv->local = g_new0(VideoWidgetRenderer, 1);
213
214 /* arrange remote actors */
215 priv->remote->actor = clutter_actor_new();
216 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
217 /* the remote camera must always fill the container size */
218 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
219 CLUTTER_BIND_SIZE, 0);
220 clutter_actor_add_constraint(priv->remote->actor, constraint);
221
222 /* arrange local actor */
223 priv->local->actor = clutter_actor_new();
224 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
225 /* set size to square, but it will stay the aspect ratio when the image is rendered */
226 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
227 /* set position constraint to right cornder */
228 constraint = clutter_align_constraint_new(priv->video_container,
229 CLUTTER_ALIGN_BOTH, 0.99);
230 clutter_actor_add_constraint(priv->local->actor, constraint);
231 clutter_actor_set_opacity(priv->local->actor,
232 VIDEO_LOCAL_OPACITY_DEFAULT);
233
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400234 /* init frame queues and the timeout sources to check them */
235 priv->remote->frame_queue = g_async_queue_new_full((GDestroyNotify)free_framedata);
236 priv->local->frame_queue = g_async_queue_new_full((GDestroyNotify)free_framedata);
237 priv->frame_timeout_source = g_timeout_add(FRAME_RATE_PERIOD, (GSourceFunc)check_frame_queue, self);
238
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500239 /* TODO: handle button event in screen */
240 // g_signal_connect(screen, "button-press-event",
241 // G_CALLBACK(on_button_press_in_screen_event_cb),
242 // self);
243
244 /* TODO: init drag & drop images */
Stepan Salenikovichcb6aa462015-03-21 15:13:37 -0400245 gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
246 gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
247 g_signal_connect(GTK_WIDGET(self), "drag-data-received", G_CALLBACK(on_drag_data_received), NULL);
248}
249
250/*
251 * on_drag_data_received()
252 *
253 * Handle dragged data in the video widget window.
254 * Dropping an image causes the client to switch the video input to that image.
255 */
256static void
257on_drag_data_received(G_GNUC_UNUSED GtkWidget *self,
258 G_GNUC_UNUSED GdkDragContext *context,
259 G_GNUC_UNUSED gint x,
260 G_GNUC_UNUSED gint y,
261 GtkSelectionData *selection_data,
262 G_GNUC_UNUSED guint info,
263 G_GNUC_UNUSED guint32 time,
264 G_GNUC_UNUSED gpointer data)
265{
266 gchar **uris = gtk_selection_data_get_uris(selection_data);
267
268 /* only play the first selection */
269 if (uris && *uris)
270 Video::SourcesModel::instance()->setFile(QUrl(*uris));
271
272 g_strfreev(uris);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500273}
274
275static FrameInfo *
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400276prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500277{
278 const QByteArray& data = renderer->currentFrame();
279 QSize res = renderer->size();
280
281 /* copy frame data */
282 gpointer frame_data = g_memdup((gconstpointer)data.constData(), data.size());
283
284 FrameInfo *frame = g_new0(FrameInfo, 1);
285
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500286 frame->image_actor = image_actor;
287 frame->data = (guchar *)frame_data;
288 frame->data_size = data.size();
289 frame->width = res.width();
290 frame->height = res.height();
291
292 return frame;
293}
294
295static void
296free_framedata(gpointer data)
297{
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400298 if (data == NULL) return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500299 FrameInfo *frame = (FrameInfo *)data;
300 g_free(frame->data);
301 g_free(frame);
302}
303
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400304static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500305clutter_render_image(FrameInfo *frame)
306{
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400307 if (frame == NULL) return;
308 g_return_if_fail(CLUTTER_IS_ACTOR(frame->image_actor));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500309
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400310 ClutterContent * image_new = clutter_image_new();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500311
312 const gint BPP = 4;
313 const gint ROW_STRIDE = BPP * frame->width;
314
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500315 GError *error = NULL;
316 clutter_image_set_data(
317 CLUTTER_IMAGE(image_new),
318 frame->data,
319 COGL_PIXEL_FORMAT_BGRA_8888,
320 frame->width,
321 frame->height,
322 ROW_STRIDE,
323 &error);
324 if (error) {
325 g_warning("error rendering image to clutter: %s", error->message);
326 g_error_free(error);
327 }
328
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400329 clutter_actor_set_content(frame->image_actor, image_new);
330 g_object_unref (image_new);
331 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
332 * that the aspect ratio is correct
333 */
334 clutter_actor_set_content_gravity(frame->image_actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400335}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500336
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400337static gboolean
338check_frame_queue(VideoWidget *self)
339{
340 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
341 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
342
343 /* get the latest frame in the queue */
344 gpointer local_data_last = NULL;
345 gpointer local_data_next = g_async_queue_try_pop(priv->local->frame_queue);
346 while(local_data_next != NULL) {
347 // if (local_data_last != NULL) g_debug("skipping local frame");
348 /* make sure to free the frame we're skipping */
349 free_framedata(local_data_last);
350 local_data_last = local_data_next;
351 local_data_next = g_async_queue_try_pop(priv->local->frame_queue);
352 }
353
354 /* display the frame */
355 clutter_render_image((FrameInfo *)local_data_last);
356 free_framedata(local_data_last);
357
358 /* get the latest frame in the queue */
359 gpointer remote_data_last = NULL;
360 gpointer remote_data_next = g_async_queue_try_pop(priv->remote->frame_queue);
361 while(remote_data_next != NULL) {
362 // if (remote_data_last != NULL) g_debug("skipping remote frame");
363 /* make sure to free the frame we're skipping */
364 free_framedata(remote_data_last);
365 remote_data_last = remote_data_next;
366 remote_data_next = g_async_queue_try_pop(priv->remote->frame_queue);
367 }
368
369 /* display the frame */
370 clutter_render_image((FrameInfo *)remote_data_last);
371 free_framedata(remote_data_last);
372
373 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500374}
375
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400376static void
377renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500378{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400379 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400380 QObject::disconnect(renderer->frame_update);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500381}
382
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400383static void
384renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500385{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400386 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400387 QObject::disconnect(renderer->frame_update);
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400388 renderer->frame_update = QObject::connect(
389 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500390 &Video::Renderer::frameUpdated,
391 [=]() {
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400392 if (!renderer->renderer->isRendering())
393 g_warning("got frame but not rendering!");
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500394
395 /* this callback comes from another thread;
396 * rendering must be done in the main loop;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400397 * copy the frame and add it to the frame queue
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500398 */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400399 FrameInfo *frame = prepare_framedata(renderer->renderer,
400 renderer->actor);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400401 g_async_queue_push(renderer->frame_queue, frame);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500402 }
403 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500404}
405
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400406static void
407video_widget_set_renderer(VideoWidgetRenderer *renderer)
408{
409 if (renderer == NULL) return;
410
411 /* update the renderer */
412 QObject::disconnect(renderer->frame_update);
413 QObject::disconnect(renderer->render_stop);
414 QObject::disconnect(renderer->render_start);
415
416 renderer_start(renderer);
417
418 renderer->render_stop = QObject::connect(
419 renderer->renderer,
420 &Video::Renderer::stopped,
421 [=]() {
422 renderer_stop(renderer);
423 }
424 );
425
426 renderer->render_start = QObject::connect(
427 renderer->renderer,
428 &Video::Renderer::started,
429 [=]() {
430 renderer_start(renderer);
431 }
432 );
433}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500434
435/*
436 * video_widget_new()
437 *
438 * The function use to create a new video_widget
439 */
440GtkWidget*
441video_widget_new(void)
442{
443 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
444 return self;
445}
446
447void
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400448video_widget_add_renderer(VideoWidget *self, const VideoRenderer *new_renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500449{
450 g_return_if_fail(IS_VIDEO_WIDGET(self));
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400451 if (new_renderer == NULL || new_renderer->renderer == NULL)
452 return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500453
454 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
455
456 /* update the renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400457 switch(new_renderer->type) {
458 case VIDEO_RENDERER_REMOTE:
459 priv->remote->renderer = new_renderer->renderer;
460 video_widget_set_renderer(priv->remote);
461 break;
462 case VIDEO_RENDERER_LOCAL:
463 priv->local->renderer = new_renderer->renderer;
464 video_widget_set_renderer(priv->local);
465 break;
466 case VIDEO_RENDERER_COUNT:
467 break;
468 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500469}