blob: 1d3d544609851b2013e910ffebac1a52aa17a4bf [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 Salenikovich36c025c2015-03-03 19:06:44 -050041struct _VideoWidgetClass {
42 GtkBinClass parent_class;
43};
44
45struct _VideoWidget {
46 GtkBin parent;
47};
48
49typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
50
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040051typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
52
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050053struct _VideoWidgetPrivate {
54 GtkWidget *clutter_widget;
55 ClutterActor *stage;
56 ClutterActor *video_container;
57
58 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040059 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050060
61 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040062 VideoWidgetRenderer *local;
63};
64
65struct _VideoWidgetRenderer {
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040066 ClutterActor *actor;
67 Video::Renderer *renderer;
68 QMetaObject::Connection frame_update;
69 QMetaObject::Connection render_stop;
70 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050071};
72
73G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
74
75#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
76
77typedef struct _FrameInfo
78{
79 ClutterActor *image_actor;
80 ClutterContent *image;
81 guchar *data;
82 gint data_size;
83 gint width;
84 gint height;
85} FrameInfo;
86
87/*
88 * video_widget_dispose()
89 *
90 * The dispose function for the video_widget class.
91 */
92static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040093video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050094{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040095 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050096 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
97
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040098 QObject::disconnect(priv->remote->frame_update);
99 QObject::disconnect(priv->remote->render_stop);
100 QObject::disconnect(priv->remote->render_start);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500101
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400102 QObject::disconnect(priv->local->frame_update);
103 QObject::disconnect(priv->local->render_stop);
104 QObject::disconnect(priv->local->render_start);
105
106 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500107}
108
109
110/*
111 * video_widget_finalize()
112 *
113 * The finalize function for the video_widget class.
114 */
115static void
116video_widget_finalize(GObject *object)
117{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400118 VideoWidget *self = VIDEO_WIDGET(object);
119 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
120
121 g_free(priv->remote);
122 g_free(priv->local);
123
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500124 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
125}
126
127
128/*
129 * video_widget_class_init()
130 *
131 * This function init the video_widget_class.
132 */
133static void
134video_widget_class_init(VideoWidgetClass *klass)
135{
136 GObjectClass *object_class = G_OBJECT_CLASS(klass);
137
138 /* override method */
139 object_class->dispose = video_widget_dispose;
140 object_class->finalize = video_widget_finalize;
141}
142
143
144/*
145 * video_widget_init()
146 *
147 * This function init the video_widget.
148 * - init clutter
149 * - init all the widget members
150 */
151static void
152video_widget_init(VideoWidget *self)
153{
154 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
155
156 /* init clutter widget */
157 priv->clutter_widget = gtk_clutter_embed_new();
158 /* add it to the video_widget */
159 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
160 /* get the stage */
161 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
162
163 /* layout manager is used to arrange children in space, here we ask clutter
164 * to align children to fill the space when resizing the window */
165 clutter_actor_set_layout_manager(priv->stage,
166 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
167
168 /* add a scene container where we can add and remove our actors */
169 priv->video_container = clutter_actor_new();
170 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
171 clutter_actor_add_child(priv->stage, priv->video_container);
172
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400173 /* init the remote and local structs */
174 priv->remote = g_new0(VideoWidgetRenderer, 1);
175 priv->local = g_new0(VideoWidgetRenderer, 1);
176
177 /* arrange remote actors */
178 priv->remote->actor = clutter_actor_new();
179 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
180 /* the remote camera must always fill the container size */
181 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
182 CLUTTER_BIND_SIZE, 0);
183 clutter_actor_add_constraint(priv->remote->actor, constraint);
184
185 /* arrange local actor */
186 priv->local->actor = clutter_actor_new();
187 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
188 /* set size to square, but it will stay the aspect ratio when the image is rendered */
189 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
190 /* set position constraint to right cornder */
191 constraint = clutter_align_constraint_new(priv->video_container,
192 CLUTTER_ALIGN_BOTH, 0.99);
193 clutter_actor_add_constraint(priv->local->actor, constraint);
194 clutter_actor_set_opacity(priv->local->actor,
195 VIDEO_LOCAL_OPACITY_DEFAULT);
196
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500197 /* TODO: handle button event in screen */
198 // g_signal_connect(screen, "button-press-event",
199 // G_CALLBACK(on_button_press_in_screen_event_cb),
200 // self);
201
202 /* TODO: init drag & drop images */
203 // gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
204 // gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
205 // g_signal_connect(GTK_WIDGET(self), "drag-data-received",
206 // G_CALLBACK(on_drag_data_received_cb), NULL);
207}
208
209static FrameInfo *
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400210prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500211{
212 const QByteArray& data = renderer->currentFrame();
213 QSize res = renderer->size();
214
215 /* copy frame data */
216 gpointer frame_data = g_memdup((gconstpointer)data.constData(), data.size());
217
218 FrameInfo *frame = g_new0(FrameInfo, 1);
219
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500220 frame->image_actor = image_actor;
221 frame->data = (guchar *)frame_data;
222 frame->data_size = data.size();
223 frame->width = res.width();
224 frame->height = res.height();
225
226 return frame;
227}
228
229static void
230free_framedata(gpointer data)
231{
232 FrameInfo *frame = (FrameInfo *)data;
233 g_free(frame->data);
234 g_free(frame);
235}
236
237static gboolean
238clutter_render_image(FrameInfo *frame)
239{
240 g_return_val_if_fail(CLUTTER_IS_ACTOR(frame->image_actor), FALSE);
241
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400242 ClutterContent * image_new = clutter_image_new();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500243
244 const gint BPP = 4;
245 const gint ROW_STRIDE = BPP * frame->width;
246
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500247 GError *error = NULL;
248 clutter_image_set_data(
249 CLUTTER_IMAGE(image_new),
250 frame->data,
251 COGL_PIXEL_FORMAT_BGRA_8888,
252 frame->width,
253 frame->height,
254 ROW_STRIDE,
255 &error);
256 if (error) {
257 g_warning("error rendering image to clutter: %s", error->message);
258 g_error_free(error);
259 }
260
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400261 clutter_actor_set_content(frame->image_actor, image_new);
262 g_object_unref (image_new);
263 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
264 * that the aspect ratio is correct
265 */
266 clutter_actor_set_content_gravity(frame->image_actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500267
268 return FALSE; /* we do not want this function to be called again */
269}
270
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400271static void
272renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500273{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400274 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400275 QObject::disconnect(renderer->frame_update);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500276}
277
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400278static void
279renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500280{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400281 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400282 renderer->frame_update = QObject::connect(
283 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500284 &Video::Renderer::frameUpdated,
285 [=]() {
286
287 /* this callback comes from another thread;
288 * rendering must be done in the main loop;
289 * copy the frame and add it as a task on the main loop
290 */
291
292 /* for now use the video container for the remote image */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400293 FrameInfo *frame = prepare_framedata(renderer->renderer,
294 renderer->actor);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500295
296 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
297 (GSourceFunc)clutter_render_image,
298 frame,
299 (GDestroyNotify)free_framedata);
300 }
301 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500302}
303
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400304static void
305video_widget_set_renderer(VideoWidgetRenderer *renderer)
306{
307 if (renderer == NULL) return;
308
309 /* update the renderer */
310 QObject::disconnect(renderer->frame_update);
311 QObject::disconnect(renderer->render_stop);
312 QObject::disconnect(renderer->render_start);
313
314 renderer_start(renderer);
315
316 renderer->render_stop = QObject::connect(
317 renderer->renderer,
318 &Video::Renderer::stopped,
319 [=]() {
320 renderer_stop(renderer);
321 }
322 );
323
324 renderer->render_start = QObject::connect(
325 renderer->renderer,
326 &Video::Renderer::started,
327 [=]() {
328 renderer_start(renderer);
329 }
330 );
331}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500332
333/*
334 * video_widget_new()
335 *
336 * The function use to create a new video_widget
337 */
338GtkWidget*
339video_widget_new(void)
340{
341 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
342 return self;
343}
344
345void
346video_widget_set_remote_renderer(VideoWidget *self, Video::Renderer *renderer_remote_new)
347{
348 g_return_if_fail(IS_VIDEO_WIDGET(self));
349 if (renderer_remote_new == NULL) return;
350
351 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
352
353 /* update the renderer */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400354 priv->remote->renderer = renderer_remote_new;
355 video_widget_set_renderer(priv->remote);
356}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500357
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400358void
359video_widget_set_local_renderer(VideoWidget *self, Video::Renderer *renderer_local_new)
360{
361 g_return_if_fail(IS_VIDEO_WIDGET(self));
362 if (renderer_local_new == NULL) return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500363
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400364 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500365
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400366 /* update the renderer */
367 priv->local->renderer = renderer_local_new;
368 video_widget_set_renderer(priv->local);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500369}