Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2018-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / video output filter
9 : *
10 : * GPAC is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU Lesser General Public License as published by
12 : * the Free Software Foundation; either version 2, or (at your option)
13 : * any later version.
14 : *
15 : * GPAC is distributed in the hope that it will be useful,
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Lesser General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Lesser General Public
21 : * License along with this library; see the file COPYING. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 :
27 : #include <gpac/filters.h>
28 : #include <gpac/constants.h>
29 : #include <gpac/modules/video_out.h>
30 : #include <gpac/color.h>
31 :
32 : #ifndef GPAC_DISABLE_PLAYER
33 :
34 : //#define GPAC_DISABLE_3D
35 :
36 : #if !defined(GPAC_DISABLE_3D) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_USE_GLES1X)
37 : #define VOUT_USE_OPENGL
38 :
39 : //include openGL
40 : #include "../compositor/gl_inc.h"
41 :
42 : #define DEL_SHADER(_a) if (_a) { glDeleteShader(_a); _a = 0; }
43 : #define DEL_PROGRAM(_a) if (_a) { glDeleteProgram(_a); _a = 0; }
44 :
45 :
46 : #define TEXTURE_TYPE GL_TEXTURE_2D
47 :
48 :
49 : #if defined( _LP64 ) && defined(CONFIG_DARWIN_GL)
50 : #define GF_SHADERID u64
51 : #else
52 : #define GF_SHADERID u32
53 : #endif
54 :
55 :
56 : static char *default_glsl_vertex = "\
57 : attribute vec4 gfVertex;\
58 : attribute vec2 gfTexCoord;\
59 : varying vec2 TexCoord;\
60 : void main(void)\
61 : {\
62 : gl_Position = gl_ModelViewProjectionMatrix * gfVertex;\
63 : TexCoord = gfTexCoord;\
64 : }";
65 :
66 : #endif
67 :
68 : typedef enum
69 : {
70 : MODE_GL,
71 : MODE_GL_PBO,
72 : MODE_2D,
73 : MODE_2D_SOFT,
74 : } GF_VideoOutMode;
75 :
76 :
77 : enum
78 : {
79 : FLIP_NO,
80 : FLIP_VERT,
81 : FLIP_HORIZ,
82 : FLIP_BOTH,
83 : FLIP_BOTH2,
84 : };
85 :
86 :
87 : typedef struct
88 : {
89 : //options
90 : char *drv;
91 : GF_VideoOutMode disp;
92 : Bool vsync, linear, fullscreen, drop, hide, step;
93 : GF_Fraction64 dur;
94 : Double speed, hold;
95 : u32 back, vflip, vrot;
96 : GF_PropVec2i wsize, owsize;
97 : GF_PropVec2i wpos;
98 : Double start;
99 : u32 buffer, mbuffer, rbuffer;
100 : GF_Fraction vdelay;
101 : const char *out;
102 : GF_PropUIntList dumpframes;
103 :
104 : GF_PropVec4i olwnd;
105 : GF_PropVec2i olsize;
106 : GF_PropData oldata;
107 :
108 : GF_Filter *filter;
109 : GF_FilterPid *pid;
110 : u32 width, height, stride, pfmt, timescale;
111 : GF_Fraction fps;
112 :
113 : GF_VideoOutput *video_out;
114 :
115 : u64 first_cts_plus_one;
116 : u64 clock_at_first_cts, last_frame_clock, clock_at_first_frame;
117 : u32 last_pck_dur_us;
118 : Bool aborted;
119 : u32 display_width, display_height;
120 : Bool display_changed;
121 : Float dh, dw, oh, ow;
122 : Bool has_alpha;
123 : u32 nb_frames;
124 :
125 : u32 key_states;
126 :
127 : //if source is raw live grab (webcam/etc), we don't trust cts and always draw the frame
128 : //this is needed for cases where we have a sudden jump in timestamps as is the case with ffmpeg: not doing so would
129 : //hold the frame until its CTS is reached, triggering drops at capture time
130 : Bool raw_grab;
131 :
132 : #ifdef VOUT_USE_OPENGL
133 : GLint glsl_program;
134 : GF_SHADERID vertex_shader;
135 : GF_SHADERID fragment_shader;
136 :
137 : GF_GLTextureWrapper tx;
138 :
139 : GLuint overlay_tx;
140 : #endif // VOUT_USE_OPENGL
141 :
142 : u32 num_textures;
143 :
144 : u32 uv_w, uv_h, uv_stride, bit_depth;
145 : Bool is_yuv, in_fullscreen;
146 : u32 nb_drawn;
147 :
148 : GF_Fraction sar;
149 :
150 : Bool force_release;
151 : GF_FilterPacket *last_pck;
152 :
153 : s64 pid_delay;
154 : Bool buffer_done;
155 : Bool no_buffering;
156 : Bool dump_done;
157 :
158 : u32 dump_f_idx;
159 : char *dump_buffer;
160 :
161 : Bool force_vout;
162 :
163 : Bool do_seek;
164 : Bool update_oldata;
165 :
166 : Bool full_range;
167 : s32 cmx;
168 :
169 : u64 rebuffer;
170 :
171 : Bool force_reconfig_pid;
172 : } GF_VideoOutCtx;
173 :
174 : static GF_Err vout_draw_frame(GF_VideoOutCtx *ctx);
175 :
176 :
177 : static void vout_reset_overlay(GF_VideoOutCtx *ctx)
178 : {
179 : #ifdef VOUT_USE_OPENGL
180 2 : if (ctx->overlay_tx) {
181 0 : glDeleteTextures(1, &ctx->overlay_tx);
182 0 : ctx->overlay_tx = 0;
183 : }
184 : #endif
185 : }
186 :
187 : #ifdef VOUT_USE_OPENGL
188 :
189 : static void vout_make_gl_current(GF_VideoOutCtx *ctx)
190 : {
191 : GF_Event evt;
192 157 : evt.type = GF_EVENT_SET_GL;
193 157 : ctx->video_out->ProcessEvent(ctx->video_out, &evt);
194 : }
195 :
196 50 : static Bool vout_compile_shader(GF_SHADERID shader_id, const char *name, const char *source)
197 : {
198 50 : GLint blen = 0;
199 50 : GLsizei slen = 0;
200 : u32 len;
201 50 : if (!source || !shader_id) return 0;
202 50 : len = (u32) strlen(source);
203 50 : glShaderSource(shader_id, 1, &source, &len);
204 50 : glCompileShader(shader_id);
205 :
206 50 : glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH , &blen);
207 50 : if (blen > 1) {
208 0 : char* compiler_log = (char*) gf_malloc(blen);
209 : #if defined(GPAC_USE_GLES2)
210 : glGetShaderInfoLog(shader_id, blen, &slen, compiler_log);
211 : #elif defined(CONFIG_DARWIN_GL)
212 : glGetInfoLogARB((GLhandleARB) shader_id, blen, &slen, compiler_log);
213 : #else
214 0 : glGetInfoLogARB(shader_id, blen, &slen, compiler_log);
215 : #endif
216 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GLSL] Failed to compile shader %s: %s\n", name, compiler_log));
217 0 : gf_free (compiler_log);
218 0 : return 0;
219 : }
220 : return 1;
221 : }
222 : #endif // VOUT_USE_OPENGL
223 :
224 :
225 425 : static void vout_set_caption(GF_VideoOutCtx *ctx)
226 : {
227 : GF_Event evt;
228 : memset(&evt, 0, sizeof(GF_Event));
229 425 : evt.type = GF_EVENT_SET_CAPTION;
230 425 : evt.caption.caption = gf_filter_pid_orig_src_args(ctx->pid, GF_FALSE);
231 425 : if (!evt.caption.caption) evt.caption.caption = gf_filter_pid_get_source_filter_name(ctx->pid);
232 425 : if (evt.caption.caption) {
233 425 : if (!strncmp(evt.caption.caption, "src=", 4)) evt.caption.caption += 4;
234 425 : if (!strncmp(evt.caption.caption, "./", 2)) evt.caption.caption += 2;
235 425 : ctx->video_out->ProcessEvent(ctx->video_out, &evt);
236 : }
237 425 : }
238 :
239 29 : static GF_Err resize_video_output(GF_VideoOutCtx *ctx, u32 dw, u32 dh)
240 : {
241 : GF_Event evt;
242 :
243 : memset(&evt, 0, sizeof(GF_Event));
244 29 : evt.type = GF_EVENT_VIDEO_SETUP;
245 29 : evt.setup.width = dw;
246 29 : evt.setup.height = dh;
247 :
248 : #ifdef VOUT_USE_OPENGL
249 29 : if (ctx->disp<MODE_2D) {
250 25 : evt.setup.use_opengl = GF_TRUE;
251 : //always double buffer
252 25 : evt.setup.back_buffer = gf_opts_get_bool("core", "gl-doublebuf");
253 : } else
254 : #endif
255 : {
256 4 : evt.setup.back_buffer = 1;
257 : }
258 29 : evt.setup.disable_vsync = !ctx->vsync;
259 29 : ctx->video_out->ProcessEvent(ctx->video_out, &evt);
260 29 : if (evt.setup.use_opengl) {
261 25 : gf_opengl_init();
262 : }
263 29 : if (!ctx->in_fullscreen) {
264 29 : ctx->display_width = evt.setup.width;
265 29 : ctx->display_height = evt.setup.height;
266 : }
267 29 : ctx->display_changed = GF_TRUE;
268 :
269 : #if defined(VOUT_USE_OPENGL) && defined(WIN32)
270 : if (evt.setup.use_opengl)
271 : gf_opengl_init();
272 :
273 : if ((ctx->disp<MODE_2D) && (glCompileShader == NULL)) {
274 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[VideoOut] Failed to load openGL, fallback to 2D blit\n"));
275 : evt.setup.use_opengl = GF_FALSE;
276 : evt.setup.back_buffer = 1;
277 : ctx->disp = MODE_2D;
278 : ctx->video_out->ProcessEvent(ctx->video_out, &evt);
279 : }
280 : #endif
281 :
282 29 : if (!ctx->in_fullscreen) {
283 : memset(&evt, 0, sizeof(GF_Event));
284 29 : evt.type = GF_EVENT_SIZE;
285 29 : evt.size.width = dw;
286 29 : evt.size.height = dh;
287 29 : ctx->video_out->ProcessEvent(ctx->video_out, &evt);
288 : }
289 :
290 29 : if (ctx->pid) {
291 : GF_FilterEvent fevt;
292 29 : GF_FEVT_INIT(fevt, GF_FEVT_VISIBILITY_HINT, ctx->pid);
293 29 : fevt.visibility_hint.max_x = dw;
294 29 : fevt.visibility_hint.max_y = dh;
295 29 : gf_filter_pid_send_event(ctx->pid, &fevt);
296 : }
297 29 : return GF_OK;
298 : }
299 :
300 37 : static GF_Err vout_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
301 : {
302 : GF_Event evt;
303 : const GF_PropertyValue *p;
304 : u32 w, h, pfmt, stride, stride_uv, timescale, dw, dh, hw, hh;
305 : Bool full_range;
306 : Bool sar_changed = GF_FALSE;
307 : s32 cmx;
308 37 : GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter);
309 :
310 : //if we have a pending packet, draw it now
311 37 : if (ctx->last_pck) {
312 0 : vout_draw_frame(ctx);
313 0 : gf_filter_pck_unref(ctx->last_pck);
314 0 : ctx->last_pck = NULL;
315 : }
316 :
317 :
318 37 : if (is_remove) {
319 : assert(ctx->pid==pid);
320 0 : ctx->pid=NULL;
321 0 : return GF_OK;
322 : }
323 : assert(!ctx->pid || (ctx->pid==pid));
324 37 : if (!gf_filter_pid_check_caps(pid))
325 : return GF_NOT_SUPPORTED;
326 :
327 37 : w = h = pfmt = timescale = stride = 0;
328 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE);
329 37 : if (p) timescale = p->value.uint;
330 :
331 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
332 37 : if (p) w = p->value.uint;
333 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
334 37 : if (p) h = p->value.uint;
335 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT);
336 37 : if (p) pfmt = p->value.uint;
337 :
338 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS);
339 37 : if (p) ctx->fps = p->value.frac;
340 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE);
341 37 : if (p) stride = p->value.uint;
342 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV);
343 37 : stride_uv = p ? p->value.uint : 0;
344 :
345 37 : ctx->sar.num = ctx->sar.den = 1;
346 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR);
347 37 : if (p && p->value.frac.den && p->value.frac.num) {
348 0 : if (ctx->sar.num * p->value.frac.den != p->value.frac.num * ctx->sar.den)
349 : sar_changed = GF_TRUE;
350 0 : ctx->sar = p->value.frac;
351 : }
352 :
353 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY);
354 37 : ctx->pid_delay = p ? p->value.longsint : 0;
355 :
356 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAY_BUFFER);
357 37 : ctx->no_buffering = (p && !p->value.sint) ? GF_TRUE : GF_FALSE;
358 37 : if (ctx->no_buffering) {
359 19 : ctx->buffer_done = GF_TRUE;
360 19 : ctx->rebuffer = 0;
361 : }
362 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_RAWGRAB);
363 37 : ctx->raw_grab = (p && p->value.boolean) ? GF_TRUE : GF_FALSE;
364 :
365 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_RANGE);
366 37 : full_range = (p && p->value.boolean) ? GF_TRUE : GF_FALSE;
367 37 : p = gf_filter_pid_get_property(pid, GF_PROP_PID_COLR_MX);
368 37 : cmx = p ? (s32) p->value.uint : GF_CICP_MX_UNSPECIFIED;
369 :
370 :
371 37 : if (!ctx->pid) {
372 : GF_FilterEvent fevt;
373 :
374 28 : GF_FEVT_INIT(fevt, GF_FEVT_BUFFER_REQ, pid);
375 28 : fevt.buffer_req.max_buffer_us = ctx->buffer * 1000;
376 : //we have a max buffer, move our computed max to playout and setup max buffer
377 28 : if (ctx->mbuffer > ctx->buffer) {
378 0 : fevt.buffer_req.max_playout_us = fevt.buffer_req.max_buffer_us;
379 0 : fevt.buffer_req.max_buffer_us = ctx->mbuffer * 1000;
380 : }
381 :
382 28 : gf_filter_pid_send_event(pid, &fevt);
383 :
384 28 : gf_filter_pid_init_play_event(pid, &fevt, ctx->start, ctx->speed, "VideoOut");
385 28 : if (ctx->dur.num && ctx->dur.den) {
386 0 : fevt.play.end_range = ((Double)ctx->dur.num) / ctx->dur.den;
387 0 : fevt.play.end_range += fevt.play.start_range;
388 : }
389 28 : gf_filter_pid_send_event(pid, &fevt);
390 :
391 28 : ctx->start = fevt.play.start_range;
392 28 : if ((ctx->speed<0) && (fevt.play.speed>=0))
393 0 : ctx->speed = fevt.play.speed;
394 :
395 28 : ctx->pid = pid;
396 : }
397 37 : vout_set_caption(ctx);
398 :
399 37 : if (ctx->first_cts_plus_one && ctx->timescale && (ctx->timescale != timescale) ) {
400 0 : ctx->first_cts_plus_one-=1;
401 0 : ctx->first_cts_plus_one *= timescale;
402 0 : ctx->first_cts_plus_one /= ctx->timescale;
403 0 : ctx->first_cts_plus_one+=1;
404 : }
405 37 : if (!timescale) timescale = 1;
406 37 : ctx->timescale = timescale;
407 :
408 : //pid not yet ready
409 37 : if (!pfmt || !w || !h) return GF_OK;
410 :
411 37 : if ((ctx->width==w) && (ctx->height == h) && (ctx->pfmt == pfmt) && (full_range==ctx->full_range) && (cmx==ctx->cmx) && !sar_changed && !ctx->force_reconfig_pid) return GF_OK;
412 :
413 29 : ctx->full_range = full_range;
414 29 : ctx->cmx = cmx;
415 : dw = w;
416 : dh = h;
417 :
418 29 : if ((ctx->disp<MODE_2D) && (ctx->vrot % 2)) {
419 : dw = h;
420 : dh = w;
421 : }
422 :
423 29 : if (ctx->sar.den != ctx->sar.num) {
424 0 : dw = dw * ctx->sar.num / ctx->sar.den;
425 : }
426 29 : if (ctx->wsize.x==0) ctx->wsize.x = w;
427 29 : if (ctx->wsize.y==0) ctx->wsize.y = h;
428 29 : if ((ctx->wsize.x>0) && (ctx->wsize.y>0)) {
429 0 : dw = ctx->wsize.x;
430 0 : dh = ctx->wsize.y;
431 : }
432 : //in 2D mode we need to send a setup event to resize backbuffer to the video source wsize
433 29 : if (ctx->disp>=MODE_2D) {
434 : dw = w;
435 : dh = h;
436 4 : if (ctx->sar.den != ctx->sar.num) {
437 0 : dw = dw * ctx->sar.num / ctx->sar.den;
438 : }
439 : }
440 :
441 29 : if ((dw != ctx->display_width) || (dh != ctx->display_height) ) {
442 29 : resize_video_output(ctx, dw, dh);
443 0 : } else if ((ctx->wsize.x>0) && (ctx->wsize.y>0)) {
444 0 : ctx->display_width = ctx->wsize.x;
445 0 : ctx->display_height = ctx->wsize.y;
446 0 : ctx->display_changed = GF_TRUE;
447 : }
448 :
449 29 : ctx->owsize.x = ctx->display_width;
450 29 : ctx->owsize.y = ctx->display_height;
451 :
452 29 : if (ctx->fullscreen) {
453 2 : u32 nw=ctx->display_width, nh=ctx->display_height;
454 2 : ctx->video_out->SetFullScreen(ctx->video_out, GF_TRUE, &nw, &nh);
455 2 : ctx->in_fullscreen = GF_TRUE;
456 2 : ctx->owsize.x = nw;
457 2 : ctx->owsize.y = nh;
458 27 : } else if ((ctx->wpos.x!=-1) && (ctx->wpos.y!=-1)) {
459 : memset(&evt, 0, sizeof(GF_Event));
460 0 : evt.type = GF_EVENT_MOVE;
461 : evt.move.relative = 0;
462 0 : evt.move.x = ctx->wpos.x;
463 0 : evt.move.y = ctx->wpos.y;
464 0 : ctx->video_out->ProcessEvent(ctx->video_out, &evt);
465 : }
466 :
467 29 : ctx->timescale = timescale;
468 29 : ctx->width = w;
469 29 : ctx->height = h;
470 29 : ctx->pfmt = pfmt;
471 29 : ctx->uv_w = ctx->uv_h = 0;
472 29 : ctx->uv_stride = stride_uv;
473 29 : ctx->bit_depth = 0;
474 29 : ctx->has_alpha = GF_FALSE;
475 :
476 29 : if (!stride) {
477 10 : gf_pixel_get_size_info(ctx->pfmt, w, h, NULL, &stride, &ctx->uv_stride, NULL, &ctx->uv_h);
478 : }
479 29 : ctx->stride = stride ? stride : w;
480 29 : if (ctx->dump_buffer) {
481 0 : gf_free(ctx->dump_buffer);
482 0 : ctx->dump_buffer = NULL;
483 : }
484 :
485 29 : hw = ctx->width/2;
486 29 : if (ctx->width %2) hw++;
487 29 : hh = ctx->height/2;
488 29 : if (ctx->height %2) hh++;
489 :
490 29 : ctx->is_yuv = GF_FALSE;
491 29 : switch (ctx->pfmt) {
492 1 : case GF_PIXEL_YUV444_10:
493 1 : ctx->bit_depth = 10;
494 2 : case GF_PIXEL_YUV444:
495 : case GF_PIXEL_YUVA444:
496 2 : ctx->uv_w = ctx->width;
497 2 : ctx->uv_h = ctx->height;
498 2 : ctx->uv_stride = ctx->stride;
499 2 : ctx->is_yuv = GF_TRUE;
500 2 : if (ctx->pfmt==GF_PIXEL_YUVA444) {
501 0 : ctx->has_alpha = GF_TRUE;
502 : //ctx->pfmt = GF_PIXEL_YUV444;
503 : }
504 : break;
505 1 : case GF_PIXEL_YUV422_10:
506 1 : ctx->bit_depth = 10;
507 2 : case GF_PIXEL_YUV422:
508 2 : ctx->uv_w = ctx->width/2;
509 2 : ctx->uv_h = ctx->height;
510 2 : ctx->uv_stride = ctx->stride/2;
511 2 : ctx->is_yuv = GF_TRUE;
512 2 : break;
513 1 : case GF_PIXEL_YUV_10:
514 1 : ctx->bit_depth = 10;
515 10 : case GF_PIXEL_YUV:
516 : case GF_PIXEL_YVU:
517 10 : ctx->uv_w = ctx->width/2;
518 10 : if (ctx->width % 2) ctx->uv_w++;
519 10 : ctx->uv_h = ctx->height/2;
520 10 : if (ctx->height % 2) ctx->uv_h++;
521 10 : if (!ctx->uv_stride) {
522 0 : ctx->uv_stride = ctx->stride/2;
523 0 : if (ctx->stride%2) ctx->uv_stride ++;
524 : }
525 10 : ctx->is_yuv = GF_TRUE;
526 10 : break;
527 0 : case GF_PIXEL_NV12_10:
528 : case GF_PIXEL_NV21_10:
529 0 : ctx->bit_depth = 10;
530 2 : case GF_PIXEL_NV12:
531 : case GF_PIXEL_NV21:
532 2 : ctx->uv_w = ctx->width/2;
533 2 : ctx->uv_h = ctx->height/2;
534 2 : ctx->uv_stride = ctx->stride;
535 2 : ctx->is_yuv = GF_TRUE;
536 2 : break;
537 0 : case GF_PIXEL_UYVY_10:
538 : case GF_PIXEL_YUYV_10:
539 : case GF_PIXEL_YVYU_10:
540 : case GF_PIXEL_VYUY_10:
541 0 : ctx->bit_depth = 10;
542 4 : case GF_PIXEL_UYVY:
543 : case GF_PIXEL_YUYV:
544 : case GF_PIXEL_YVYU:
545 : case GF_PIXEL_VYUY:
546 4 : ctx->uv_w = ctx->width/2;
547 4 : ctx->uv_h = ctx->height;
548 4 : if (!ctx->uv_stride) {
549 4 : ctx->uv_stride = ctx->stride/2;
550 4 : if (ctx->stride%2) ctx->uv_stride ++;
551 : }
552 4 : ctx->is_yuv = GF_TRUE;
553 4 : break;
554 0 : case GF_PIXEL_YUV444_PACK:
555 : case GF_PIXEL_YUVA444_PACK:
556 : case GF_PIXEL_YUV444_10_PACK:
557 0 : ctx->uv_w = ctx->width;
558 0 : ctx->uv_h = ctx->height;
559 0 : ctx->uv_stride = ctx->stride;
560 0 : ctx->is_yuv = GF_TRUE;
561 0 : if (ctx->pfmt==GF_PIXEL_YUVA444_PACK) {
562 0 : ctx->has_alpha = GF_TRUE;
563 : //ctx->pfmt = GF_PIXEL_YUV444;
564 : }
565 : break;
566 :
567 0 : case GF_PIXEL_ALPHAGREY:
568 : case GF_PIXEL_GREYALPHA:
569 : case GF_PIXEL_BGRA:
570 : case GF_PIXEL_ARGB:
571 : case GF_PIXEL_ABGR:
572 : case GF_PIXEL_RGBA:
573 0 : ctx->has_alpha = GF_TRUE;
574 0 : break;
575 : case GF_PIXEL_GREYSCALE:
576 : case GF_PIXEL_RGB:
577 : case GF_PIXEL_BGR:
578 : case GF_PIXEL_BGRX:
579 : case GF_PIXEL_XRGB:
580 : case GF_PIXEL_XBGR:
581 : case GF_PIXEL_RGBX:
582 : case GF_PIXEL_RGB_444:
583 : case GF_PIXEL_RGB_555:
584 : case GF_PIXEL_RGB_565:
585 : break;
586 :
587 : case 0:
588 : //not yet set, happens with some decoders/stream settings - wait until first frame is available and PF is known
589 : return GF_OK;
590 0 : default:
591 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[VideoOut] Pixel format %s unknown\n", gf_4cc_to_str(ctx->pfmt)));
592 : return GF_OK;
593 : }
594 :
595 : #ifdef VOUT_USE_OPENGL
596 29 : if (ctx->disp<MODE_2D) {
597 : memset(&evt, 0, sizeof(GF_Event));
598 25 : evt.type = GF_EVENT_SET_GL;
599 25 : ctx->video_out->ProcessEvent(ctx->video_out, &evt);
600 : }
601 :
602 29 : vout_make_gl_current(ctx);
603 29 : DEL_SHADER(ctx->vertex_shader);
604 29 : DEL_SHADER(ctx->fragment_shader);
605 29 : DEL_PROGRAM(ctx->glsl_program );
606 29 : gf_gl_txw_reset(&ctx->tx);
607 :
608 29 : if (ctx->disp<MODE_2D) {
609 25 : char *frag_shader_src = NULL;
610 : GF_Matrix mx;
611 : Float ft_hw, ft_hh;
612 :
613 25 : ctx->glsl_program = glCreateProgram();
614 25 : ctx->vertex_shader = glCreateShader(GL_VERTEX_SHADER);
615 25 : vout_compile_shader(ctx->vertex_shader, "vertex", default_glsl_vertex);
616 :
617 25 : ctx->fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
618 25 : gf_gl_txw_setup(&ctx->tx, ctx->pfmt, ctx->width, ctx->height, ctx->stride, ctx->uv_stride, ctx->linear, NULL, ctx->full_range, ctx->cmx);
619 :
620 25 : gf_dynstrcat(&frag_shader_src, "#version 120\n", NULL);
621 :
622 25 : gf_gl_txw_insert_fragment_shader(ctx->tx.pix_fmt, "maintx", &frag_shader_src);
623 25 : gf_dynstrcat(&frag_shader_src, "varying vec2 TexCoord;\n"
624 : "void main(void) {\n"
625 : "gl_FragColor = maintx_sample(TexCoord.st);\n"
626 : "}"
627 : , NULL);
628 25 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] Fragment shader is %s\n", frag_shader_src));
629 :
630 25 : vout_compile_shader(ctx->fragment_shader, "fragment", frag_shader_src);
631 25 : gf_free(frag_shader_src);
632 :
633 25 : glAttachShader(ctx->glsl_program, ctx->vertex_shader);
634 25 : glAttachShader(ctx->glsl_program, ctx->fragment_shader);
635 25 : glLinkProgram(ctx->glsl_program);
636 :
637 : #ifdef WIN32
638 : if (glMapBuffer == NULL) {
639 : if (ctx->disp == MODE_GL_PBO) {
640 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[VideoOut] GL PixelBufferObject extensions not found, fallback to regular GL\n"));
641 : ctx->disp = MODE_GL;
642 : }
643 : }
644 : #endif
645 :
646 25 : glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
647 25 : glViewport(0, 0, ctx->width, ctx->height);
648 :
649 25 : gf_mx_init(mx);
650 25 : ft_hw = ((Float)ctx->width)/2;
651 25 : ft_hh = ((Float)ctx->height)/2;
652 25 : gf_mx_ortho(&mx, -ft_hw, ft_hw, -ft_hh, ft_hh, 50, -50);
653 25 : glMatrixMode(GL_PROJECTION);
654 25 : glLoadMatrixf(mx.m);
655 :
656 25 : glMatrixMode(GL_MODELVIEW);
657 25 : glLoadIdentity();
658 :
659 25 : glClear(GL_DEPTH_BUFFER_BIT);
660 25 : glDisable(GL_NORMALIZE);
661 25 : glDisable(GL_DEPTH_TEST);
662 25 : glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
663 25 : glDisable(GL_LINE_SMOOTH);
664 25 : glDisable(GL_LINE_SMOOTH);
665 25 : glDisable(GL_LIGHTING);
666 25 : glDisable(GL_CULL_FACE);
667 25 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
668 25 : if (ctx->has_alpha) {
669 0 : glEnable(GL_BLEND);
670 : } else {
671 25 : glDisable(GL_BLEND);
672 : }
673 : } else
674 : #endif //VOUT_USE_OPENGL
675 : {
676 4 : switch (ctx->pfmt) {
677 0 : case GF_PIXEL_NV12:
678 : case GF_PIXEL_NV21:
679 : case GF_PIXEL_NV12_10:
680 : case GF_PIXEL_NV21_10:
681 0 : ctx->num_textures = 2;
682 0 : break;
683 0 : case GF_PIXEL_UYVY:
684 : case GF_PIXEL_VYUY:
685 : case GF_PIXEL_YVYU:
686 : case GF_PIXEL_YUYV:
687 0 : ctx->num_textures = 1;
688 0 : break;
689 4 : default:
690 4 : if (ctx->is_yuv) {
691 4 : ctx->num_textures = 3;
692 : } else {
693 0 : ctx->num_textures = 1;
694 : }
695 : break;
696 : }
697 : }
698 29 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[VideoOut] Reconfig input wsize %d x %d, %d textures\n", ctx->width, ctx->height, ctx->num_textures));
699 : return GF_OK;
700 : }
701 :
702 101 : static Bool vout_on_event(void *cbk, GF_Event *evt)
703 : {
704 : GF_FilterEvent fevt;
705 : Bool is_down=GF_FALSE;
706 : GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) cbk;
707 :
708 101 : fevt.base.type = 0;
709 101 : switch (evt->type) {
710 8 : case GF_EVENT_SIZE:
711 8 : if ((ctx->display_width != evt->size.width) || (ctx->display_height != evt->size.height)) {
712 2 : ctx->display_width = evt->size.width;
713 2 : ctx->display_height = evt->size.height;
714 2 : ctx->display_changed = GF_TRUE;
715 2 : ctx->owsize.x = ctx->display_width;
716 2 : ctx->owsize.y = ctx->display_height;
717 : vout_reset_overlay(ctx);
718 :
719 2 : if (ctx->pid) {
720 2 : GF_FEVT_INIT(fevt, GF_FEVT_VISIBILITY_HINT, ctx->pid);
721 2 : fevt.visibility_hint.max_x = ctx->display_width;
722 2 : fevt.visibility_hint.max_y = ctx->display_height;
723 2 : gf_filter_pid_send_event(ctx->pid, &fevt);
724 : }
725 : }
726 : break;
727 : case GF_EVENT_CLICK:
728 : case GF_EVENT_MOUSEUP:
729 : case GF_EVENT_MOUSEDOWN:
730 : case GF_EVENT_MOUSEOVER:
731 : case GF_EVENT_MOUSEOUT:
732 : case GF_EVENT_MOUSEMOVE:
733 : case GF_EVENT_MOUSEWHEEL:
734 : case GF_EVENT_LONGKEYPRESS:
735 : case GF_EVENT_TEXTINPUT:
736 0 : GF_FEVT_INIT(fevt, GF_FEVT_USER, ctx->pid);
737 0 : fevt.user_event.event = *evt;
738 0 : break;
739 :
740 0 : case GF_EVENT_KEYDOWN:
741 : is_down = GF_TRUE;
742 0 : case GF_EVENT_KEYUP:
743 0 : switch (evt->key.key_code) {
744 0 : case GF_KEY_SHIFT:
745 0 : if (is_down) {
746 0 : ctx->key_states |= GF_KEY_MOD_SHIFT;
747 : } else {
748 0 : ctx->key_states &= ~GF_KEY_MOD_SHIFT;
749 : }
750 : break;
751 0 : case GF_KEY_CONTROL:
752 0 : if (is_down) {
753 0 : ctx->key_states |= GF_KEY_MOD_CTRL;
754 : } else {
755 0 : ctx->key_states &= ~GF_KEY_MOD_CTRL;
756 : }
757 : break;
758 0 : case GF_KEY_ALT:
759 0 : if (is_down) {
760 0 : ctx->key_states |= GF_KEY_MOD_ALT;
761 : } else {
762 0 : ctx->key_states &= ~GF_KEY_MOD_ALT;
763 : }
764 : break;
765 : }
766 0 : evt->key.flags = ctx->key_states;
767 0 : GF_FEVT_INIT(fevt, GF_FEVT_USER, ctx->pid);
768 0 : fevt.user_event.event = *evt;
769 0 : break;
770 : }
771 101 : if (gf_filter_ui_event(ctx->filter, evt))
772 : return GF_TRUE;
773 :
774 101 : if (fevt.base.type)
775 2 : gf_filter_pid_send_event(ctx->pid, &fevt);
776 :
777 : return GF_TRUE;
778 : }
779 :
780 : GF_VideoOutput *gf_filter_claim_opengl_provider(GF_Filter *filter);
781 : Bool gf_filter_unclaim_opengl_provider(GF_Filter *filter, GF_VideoOutput *vout);
782 :
783 28 : static GF_Err vout_initialize(GF_Filter *filter)
784 : {
785 : const char *sOpt;
786 : void *os_wnd_handler, *os_disp_handler;
787 : u32 init_flags;
788 : GF_Err e;
789 28 : GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter);
790 :
791 28 : ctx->filter = filter;
792 :
793 : #ifdef VOUT_USE_OPENGL
794 28 : if (ctx->disp <= MODE_GL_PBO) {
795 24 : ctx->video_out = (GF_VideoOutput *) gf_filter_claim_opengl_provider(filter);
796 : }
797 : #endif
798 :
799 28 : if (!ctx->video_out)
800 27 : ctx->video_out = (GF_VideoOutput *) gf_module_load(GF_VIDEO_OUTPUT_INTERFACE, ctx->drv);
801 :
802 :
803 28 : if (ctx->dumpframes.nb_items) {
804 19 : ctx->hide = GF_TRUE;
805 19 : ctx->vsync = GF_FALSE;
806 : }
807 :
808 : /*if not init we run with a NULL audio compositor*/
809 28 : if (!ctx->video_out) {
810 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] No output modules found, cannot load video output\n"));
811 : return GF_IO_ERR;
812 : }
813 28 : if (!gf_opts_get_key("core", "video-output")) {
814 0 : gf_opts_set_key("core", "video-output", ctx->video_out->module_name);
815 : }
816 :
817 28 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] Setting up video module %s\n", ctx->video_out->module_name));
818 28 : ctx->video_out->on_event = vout_on_event;
819 28 : ctx->video_out->evt_cbk_hdl = ctx;
820 :
821 28 : os_wnd_handler = os_disp_handler = NULL;
822 28 : init_flags = 0;
823 28 : sOpt = gf_opts_get_key("Temp", "OSWnd");
824 28 : if (sOpt) sscanf(sOpt, "%p", &os_wnd_handler);
825 28 : sOpt = gf_opts_get_key("Temp", "OSDisp");
826 28 : if (sOpt) sscanf(sOpt, "%p", &os_disp_handler);
827 28 : sOpt = gf_opts_get_key("Temp", "InitFlags");
828 28 : if (sOpt) sscanf(sOpt, "%d", &init_flags);
829 :
830 28 : if (ctx->hide)
831 19 : init_flags |= GF_TERM_INIT_HIDE;
832 :
833 28 : e = ctx->video_out->Setup(ctx->video_out, os_wnd_handler, os_disp_handler, init_flags);
834 28 : if (e!=GF_OK) {
835 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("Failed to Setup Video Driver %s!\n", ctx->video_out->module_name));
836 0 : gf_modules_close_interface((GF_BaseInterface *)ctx->video_out);
837 0 : ctx->video_out = NULL;
838 0 : return e;
839 : }
840 :
841 : #ifdef VOUT_USE_OPENGL
842 28 : if ( !(ctx->video_out->hw_caps & GF_VIDEO_HW_OPENGL) )
843 : #endif
844 : {
845 0 : if (ctx->disp < MODE_2D) {
846 0 : GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("No openGL support - using 2D rasterizer!\n", ctx->video_out->module_name));
847 0 : ctx->disp = MODE_2D;
848 : }
849 : }
850 :
851 : #ifdef VOUT_USE_OPENGL
852 28 : if (ctx->disp <= MODE_GL_PBO) {
853 : GF_Event evt;
854 : memset(&evt, 0, sizeof(GF_Event));
855 24 : evt.type = GF_EVENT_VIDEO_SETUP;
856 24 : evt.setup.width = 320;
857 24 : evt.setup.height = 240;
858 24 : evt.setup.use_opengl = GF_TRUE;
859 24 : evt.setup.back_buffer = 1;
860 24 : evt.setup.disable_vsync = !ctx->vsync;
861 24 : ctx->video_out->ProcessEvent(ctx->video_out, &evt);
862 :
863 24 : if (evt.setup.use_opengl) {
864 24 : gf_opengl_init();
865 : }
866 24 : gf_filter_register_opengl_provider(filter, GF_TRUE);
867 : }
868 : #endif
869 :
870 28 : gf_filter_set_event_target(filter, GF_TRUE);
871 28 : return GF_OK;
872 : }
873 :
874 28 : static void vout_finalize(GF_Filter *filter)
875 : {
876 28 : GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter);
877 :
878 28 : if (ctx->last_pck) {
879 0 : gf_filter_pck_unref(ctx->last_pck);
880 0 : ctx->last_pck = NULL;
881 : }
882 :
883 28 : if ((ctx->nb_frames==1) || (ctx->hold<0)) {
884 0 : u32 holdms = (u32) (((ctx->hold>0) ? ctx->hold : -ctx->hold) * 1000);
885 0 : gf_sleep(holdms);
886 : }
887 :
888 : #ifdef VOUT_USE_OPENGL
889 28 : DEL_SHADER(ctx->vertex_shader);
890 28 : DEL_SHADER(ctx->fragment_shader);
891 28 : DEL_PROGRAM(ctx->glsl_program );
892 28 : gf_gl_txw_reset(&ctx->tx);
893 : #endif //VOUT_USE_OPENGL
894 :
895 : /*stop and shutdown*/
896 28 : if (ctx->video_out) {
897 28 : if (! gf_filter_unclaim_opengl_provider(filter, ctx->video_out)) {
898 27 : ctx->video_out->Shutdown(ctx->video_out);
899 27 : gf_modules_close_interface((GF_BaseInterface *)ctx->video_out);
900 : }
901 28 : ctx->video_out = NULL;
902 : }
903 28 : if (ctx->dump_buffer) gf_free(ctx->dump_buffer);
904 :
905 28 : }
906 :
907 : #ifdef VOUT_USE_OPENGL
908 :
909 0 : static void vout_draw_overlay(GF_VideoOutCtx *ctx)
910 : {
911 : Float dw, dh, ox, oy;
912 :
913 0 : dw = ((Float)ctx->olwnd.z) / 2;
914 0 : dh = ((Float)ctx->olwnd.w) / 2;
915 0 : ox = ((Float)ctx->olwnd.x);
916 0 : oy = ((Float)ctx->olwnd.y);
917 :
918 0 : GLfloat squareVertices[] = {
919 0 : ox+dw, oy+dh,
920 0 : ox+dw, oy-dh,
921 0 : ox-dw, oy-dh,
922 : ox-dw, oy+dh,
923 : };
924 :
925 0 : GLfloat textureVertices[] = {
926 : 1.0f, 0.0f,
927 : 1.0f, 1.0f,
928 : 0.0f, 1.0f,
929 : 0.0f, 0.0f,
930 : };
931 :
932 : #if 0
933 : if (flip_texture) {
934 : textureVertices[1] = textureVertices[7] = 1.0f;
935 : textureVertices[3] = textureVertices[5] = 0.0f;
936 : }
937 : #endif
938 :
939 0 : u16 indices[4] = {0, 1, 2, 3};
940 :
941 0 : glEnable(GL_TEXTURE_2D);
942 0 : glEnable(GL_BLEND);
943 0 : glBindTexture(GL_TEXTURE_2D, ctx->overlay_tx);
944 :
945 0 : glEnableClientState(GL_VERTEX_ARRAY);
946 0 : glVertexPointer(2, GL_FLOAT, 0, squareVertices);
947 0 : glClientActiveTexture(GL_TEXTURE0);
948 0 : glEnableClientState(GL_TEXTURE_COORD_ARRAY);
949 0 : glTexCoordPointer(2, GL_FLOAT, 0, textureVertices);
950 :
951 0 : glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, indices);
952 0 : glDisableClientState(GL_VERTEX_ARRAY);
953 0 : glDisableClientState(GL_TEXTURE_COORD_ARRAY);
954 :
955 0 : glActiveTexture(GL_TEXTURE0);
956 0 : glBindTexture(TEXTURE_TYPE, 0);
957 0 : glDisable(TEXTURE_TYPE);
958 0 : }
959 :
960 128 : static void vout_draw_gl_quad(GF_VideoOutCtx *ctx, Bool flip_texture)
961 : {
962 : Float dw, dh;
963 : Bool flip_h = GF_FALSE;
964 : Bool flip_v = GF_FALSE;
965 : u32 i;
966 :
967 128 : gf_gl_txw_bind(&ctx->tx, "maintx", ctx->glsl_program, 0);
968 :
969 128 : dw = ((Float)ctx->dw) / 2;
970 128 : dh = ((Float)ctx->dh) / 2;
971 :
972 :
973 384 : GLfloat squareVertices[] = {
974 : dw, dh,
975 128 : dw, -dh,
976 128 : -dw, -dh,
977 : -dw, dh,
978 : };
979 :
980 128 : GLfloat textureVertices[] = {
981 : 1.0f, 0.0f,
982 : 1.0f, 1.0f,
983 : 0.0f, 1.0f,
984 : 0.0f, 0.0f,
985 : };
986 :
987 128 : if (flip_texture) {
988 61 : textureVertices[1] = textureVertices[7] = 1.0f;
989 61 : textureVertices[3] = textureVertices[5] = 0.0f;
990 : }
991 :
992 128 : switch (ctx->vflip) {
993 : case FLIP_VERT:
994 : flip_v = GF_TRUE;
995 : break;
996 : case FLIP_HORIZ:
997 : flip_h = GF_TRUE;
998 : break;
999 : case FLIP_BOTH:
1000 : case FLIP_BOTH2:
1001 : flip_v = GF_TRUE;
1002 : flip_h = GF_TRUE;
1003 : break;
1004 : }
1005 :
1006 : if (flip_h) {
1007 : GLfloat v = textureVertices[0];
1008 0 : textureVertices[0] = textureVertices[2] = textureVertices[4];
1009 0 : textureVertices[4] = textureVertices[6] = v;
1010 : }
1011 128 : if (flip_v) {
1012 0 : GLfloat v = textureVertices[1];
1013 0 : textureVertices[1] = textureVertices[7] = textureVertices[3];
1014 0 : textureVertices[3] = textureVertices[5] = v;
1015 : }
1016 :
1017 0 : for (i=0; i < ctx->vrot; i++) {
1018 0 : GLfloat vx = textureVertices[0];
1019 0 : GLfloat vy = textureVertices[1];
1020 :
1021 0 : textureVertices[0] = textureVertices[2];
1022 0 : textureVertices[1] = textureVertices[3];
1023 0 : textureVertices[2] = textureVertices[4];
1024 0 : textureVertices[3] = textureVertices[5];
1025 0 : textureVertices[4] = textureVertices[6];
1026 0 : textureVertices[5] = textureVertices[7];
1027 0 : textureVertices[6] = vx;
1028 0 : textureVertices[7] = vy;
1029 : }
1030 :
1031 128 : int loc = glGetAttribLocation(ctx->glsl_program, "gfVertex");
1032 128 : if (loc >= 0) {
1033 128 : glVertexAttribPointer(loc, 2, GL_FLOAT, 0, 0, squareVertices);
1034 128 : glEnableVertexAttribArray(loc);
1035 :
1036 : //setup texcoord location
1037 128 : loc = glGetAttribLocation(ctx->glsl_program, "gfTexCoord");
1038 128 : if (loc >= 0) {
1039 128 : glVertexAttribPointer(loc, 2, GL_FLOAT, 0, 0, textureVertices);
1040 128 : glEnableVertexAttribArray(loc);
1041 :
1042 128 : glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
1043 : } else {
1044 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Failed to gfTexCoord uniform in shader\n"));
1045 : }
1046 : } else {
1047 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Failed to gfVertex uniform in shader\n"));
1048 : }
1049 :
1050 128 : glActiveTexture(GL_TEXTURE0);
1051 128 : glBindTexture(TEXTURE_TYPE, 0);
1052 128 : glDisable(TEXTURE_TYPE);
1053 :
1054 128 : glUseProgram(0);
1055 :
1056 128 : if (ctx->dump_f_idx) {
1057 : FILE *fout;
1058 : char szFileName[1024];
1059 19 : if (strchr(gf_file_basename(ctx->out), '.')) {
1060 19 : snprintf(szFileName, 1024, "%s", ctx->out);
1061 : } else {
1062 0 : snprintf(szFileName, 1024, "%s_%d.rgb", ctx->out, ctx->dump_f_idx);
1063 : }
1064 19 : if (!ctx->dump_buffer)
1065 19 : ctx->dump_buffer = gf_malloc(sizeof(char)*ctx->display_width*ctx->display_height*3);
1066 :
1067 19 : glFlush();
1068 :
1069 19 : glReadPixels(0, 0, ctx->display_width, ctx->display_height, GL_RGB, GL_UNSIGNED_BYTE, ctx->dump_buffer);
1070 : GL_CHECK_ERR()
1071 19 : fout = gf_fopen(szFileName, "wb");
1072 19 : if (fout) {
1073 19 : gf_fwrite(ctx->dump_buffer, ctx->display_width*ctx->display_height*3, fout);
1074 19 : gf_fclose(fout);
1075 : } else {
1076 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error writing frame %d buffer to %s\n", ctx->nb_frames, szFileName));
1077 : }
1078 : }
1079 128 : }
1080 :
1081 61 : static void vout_draw_gl_hw_textures(GF_VideoOutCtx *ctx, GF_FilterFrameInterface *hwf)
1082 : {
1083 61 : if (ctx->tx.internal_textures) {
1084 1 : glDeleteTextures(ctx->tx.nb_textures, ctx->tx.textures);
1085 1 : ctx->tx.internal_textures = GF_FALSE;
1086 : }
1087 61 : ctx->tx.frame_ifce = hwf;
1088 61 : gf_gl_txw_bind(&ctx->tx, "maintx", ctx->glsl_program, 0);
1089 :
1090 61 : glMatrixMode(GL_TEXTURE);
1091 61 : glLoadIdentity();
1092 61 : glMatrixMode(GL_MODELVIEW);
1093 61 : glLoadIdentity();
1094 :
1095 : //and draw
1096 61 : vout_draw_gl_quad(ctx, ctx->tx.flip ? GF_TRUE : GF_FALSE);
1097 : GL_CHECK_ERR()
1098 61 : }
1099 :
1100 128 : static void vout_draw_gl(GF_VideoOutCtx *ctx, GF_FilterPacket *pck)
1101 : {
1102 : char *data;
1103 : GF_Matrix mx;
1104 : Float hw, hh;
1105 : u32 wsize;
1106 : GF_FilterFrameInterface *frame_ifce;
1107 :
1108 128 : vout_make_gl_current(ctx);
1109 :
1110 128 : if (ctx->display_changed) {
1111 : u32 v_w, v_h;
1112 24 : if (ctx->vrot % 2) {
1113 0 : v_h = ctx->width;
1114 0 : v_w = ctx->height;
1115 : } else {
1116 24 : v_w = ctx->width;
1117 24 : v_h = ctx->height;
1118 : }
1119 :
1120 : //if we fill width to display width and height is outside
1121 24 : if (ctx->display_width * v_h / v_w > ctx->display_height) {
1122 0 : ctx->dw = (Float) (ctx->display_height * v_w / v_h);
1123 0 : ctx->dw *= ctx->sar.num;
1124 0 : ctx->dw /= ctx->sar.den;
1125 0 : ctx->dh = (Float) ctx->display_height;
1126 0 : ctx->oh = (Float) 0;
1127 0 : ctx->ow = (Float) (ctx->display_width - ctx->dw ) / 2;
1128 : } else {
1129 24 : ctx->dh = (Float) (ctx->display_width * v_h / v_w);
1130 24 : ctx->dh *= ctx->sar.den;
1131 24 : ctx->dh /= ctx->sar.num;
1132 24 : ctx->dw = (Float) ctx->display_width;
1133 24 : ctx->ow = (Float) 0;
1134 24 : ctx->oh = (Float) (ctx->display_height - ctx->dh ) / 2;
1135 : }
1136 24 : if (ctx->dump_buffer) {
1137 0 : gf_free(ctx->dump_buffer);
1138 0 : ctx->dump_buffer = NULL;
1139 : }
1140 :
1141 24 : ctx->display_changed = GF_FALSE;
1142 : }
1143 :
1144 128 : glViewport(0, 0, ctx->display_width, ctx->display_height);
1145 :
1146 128 : gf_mx_init(mx);
1147 128 : hw = ((Float)ctx->display_width)/2;
1148 128 : hh = ((Float)ctx->display_height)/2;
1149 128 : gf_mx_ortho(&mx, -hw, hw, -hh, hh, 10, -5);
1150 128 : glMatrixMode(GL_PROJECTION);
1151 128 : glLoadMatrixf(mx.m);
1152 :
1153 128 : glMatrixMode(GL_TEXTURE);
1154 128 : glLoadIdentity();
1155 :
1156 128 : glMatrixMode(GL_MODELVIEW);
1157 128 : glLoadIdentity();
1158 :
1159 128 : if (ctx->has_alpha) {
1160 : Float r, g, b;
1161 0 : r = GF_COL_R(ctx->back); r /= 255;
1162 0 : g = GF_COL_G(ctx->back); g /= 255;
1163 0 : b = GF_COL_B(ctx->back); b /= 255;
1164 0 : glClearColor(r, g, b, 1.0);
1165 : } else {
1166 128 : glClearColor(0, 0, 0, 1.0);
1167 : }
1168 128 : glClear(GL_COLOR_BUFFER_BIT);
1169 :
1170 : //we are not sure if we are the only ones using the gl context, reset our defaults
1171 128 : glDisable(GL_LIGHTING);
1172 128 : glDisable(GL_CULL_FACE);
1173 128 : glDisable(GL_DEPTH_TEST);
1174 128 : if (ctx->has_alpha) {
1175 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1176 0 : glEnable(GL_BLEND);
1177 : } else {
1178 128 : glDisable(GL_BLEND);
1179 : }
1180 128 : if (ctx->has_alpha) {
1181 : Float r, g, b;
1182 0 : r = GF_COL_R(ctx->back); r /= 255;
1183 0 : g = GF_COL_G(ctx->back); g /= 255;
1184 0 : b = GF_COL_B(ctx->back); b /= 255;
1185 0 : glClearColor(r, g, b, 1.0);
1186 0 : glClear(GL_COLOR_BUFFER_BIT);
1187 : }
1188 :
1189 128 : if (!pck)
1190 : goto exit;
1191 :
1192 128 : if (!ctx->glsl_program) return;
1193 :
1194 128 : frame_ifce = gf_filter_pck_get_frame_interface(pck);
1195 128 : if (frame_ifce && (frame_ifce->flags & GF_FRAME_IFCE_MAIN_GLFB)) {
1196 : goto exit;
1197 : }
1198 :
1199 128 : glUseProgram(ctx->glsl_program);
1200 :
1201 :
1202 128 : if (frame_ifce && frame_ifce->get_gl_texture) {
1203 61 : vout_draw_gl_hw_textures(ctx, frame_ifce);
1204 : } else {
1205 67 : data = (char*) gf_filter_pck_get_data(pck, &wsize);
1206 :
1207 : //upload texture
1208 67 : gf_gl_txw_upload(&ctx->tx, data, frame_ifce);
1209 :
1210 : //and draw
1211 67 : vout_draw_gl_quad(ctx, GF_FALSE);
1212 : }
1213 :
1214 128 : exit:
1215 :
1216 : //we don't lock since most of the time overlay is not set
1217 128 : if (ctx->oldata.ptr) {
1218 : // overlay is set, lock filter to make sure the data is still valid
1219 0 : gf_filter_lock(ctx->filter, GF_TRUE);
1220 0 : if (ctx->oldata.ptr && (ctx->oldata.size <= 4 * ctx->olsize.x * ctx->olsize.y) ) {
1221 0 : if (!ctx->overlay_tx) {
1222 0 : glGenTextures(1, &ctx->overlay_tx);
1223 :
1224 0 : glEnable(GL_TEXTURE_2D);
1225 : #if !defined(GPAC_USE_GLES1X)
1226 0 : glBindTexture(GL_TEXTURE_2D, ctx->overlay_tx);
1227 : #if defined(GPAC_USE_GLES2)
1228 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1229 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1230 : #else
1231 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1232 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1233 : #endif
1234 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1235 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1236 : #endif
1237 :
1238 0 : glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ctx->olsize.x, ctx->olsize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, ctx->oldata.ptr);
1239 0 : ctx->update_oldata = GF_FALSE;
1240 0 : } else if (ctx->update_oldata) {
1241 0 : glEnable(GL_TEXTURE_2D);
1242 0 : glBindTexture(GL_TEXTURE_2D, ctx->overlay_tx);
1243 0 : glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ctx->olsize.x, ctx->olsize.y, GL_RGBA, GL_UNSIGNED_BYTE, ctx->oldata.ptr);
1244 0 : ctx->update_oldata = GF_FALSE;
1245 : }
1246 0 : vout_draw_overlay(ctx);
1247 : }
1248 0 : gf_filter_lock(ctx->filter, GF_FALSE);
1249 : }
1250 :
1251 : //final flush
1252 128 : ctx->video_out->Flush(ctx->video_out, NULL);
1253 : }
1254 : #endif
1255 :
1256 48 : void vout_draw_2d(GF_VideoOutCtx *ctx, GF_FilterPacket *pck)
1257 : {
1258 : char *data;
1259 : u32 wsize;
1260 : GF_Err e;
1261 : GF_VideoSurface src_surf;
1262 : GF_Window dst_wnd, src_wnd;
1263 :
1264 48 : if (!pck)
1265 0 : return;
1266 :
1267 : memset(&src_surf, 0, sizeof(GF_VideoSurface));
1268 48 : src_surf.width = ctx->width;
1269 48 : src_surf.height = ctx->height;
1270 : src_surf.pitch_x = 0;
1271 48 : src_surf.pitch_y = ctx->stride;
1272 48 : src_surf.pixel_format = ctx->pfmt;
1273 48 : src_surf.global_alpha = 0xFF;
1274 :
1275 48 : data = (char *) gf_filter_pck_get_data(pck, &wsize);
1276 48 : if (!data) {
1277 : u32 stride_luma;
1278 : u32 stride_chroma;
1279 0 : GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck);
1280 0 : if (frame_ifce->flags & GF_FRAME_IFCE_BLOCKING)
1281 0 : ctx->force_release = GF_TRUE;
1282 0 : if (! frame_ifce->get_plane) {
1283 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Hardware GL texture blit not supported with non-GL blitter\n"));
1284 0 : return;
1285 : }
1286 0 : e = frame_ifce->get_plane(frame_ifce, 0, (const u8 **) &src_surf.video_buffer, &stride_luma);
1287 0 : if (e) {
1288 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error fetching chroma plane from hardware frame\n"));
1289 : return;
1290 : }
1291 0 : if (ctx->is_yuv && (ctx->num_textures>1)) {
1292 0 : e = frame_ifce->get_plane(frame_ifce, 1, (const u8 **) &src_surf.u_ptr, &stride_chroma);
1293 0 : if (e) {
1294 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error fetching luma U plane from hardware frame\n"));
1295 : return;
1296 : }
1297 0 : if ((ctx->pfmt!= GF_PIXEL_NV12) && (ctx->pfmt!= GF_PIXEL_NV21)) {
1298 0 : e = frame_ifce->get_plane(frame_ifce, 2, (const u8 **) &src_surf.v_ptr, &stride_chroma);
1299 0 : if (e) {
1300 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error fetching luma V plane from hardware frame\n"));
1301 : return;
1302 : }
1303 : }
1304 : }
1305 : } else {
1306 48 : src_surf.video_buffer = data;
1307 : }
1308 :
1309 :
1310 48 : if (ctx->display_changed) {
1311 : GF_Event evt;
1312 :
1313 : memset(&evt, 0, sizeof(GF_Event));
1314 4 : evt.type = GF_EVENT_VIDEO_SETUP;
1315 4 : evt.setup.back_buffer = 1;
1316 4 : evt.setup.width = ctx->display_width;
1317 4 : evt.setup.height = ctx->display_height;
1318 4 : e = ctx->video_out->ProcessEvent(ctx->video_out, &evt);
1319 4 : if (e) {
1320 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error resizing 2D backbuffer %s\n", gf_error_to_string(e) ));
1321 : }
1322 : //if we fill width to display width and height is outside
1323 4 : if (ctx->display_width * ctx->height / ctx->width > ctx->display_height) {
1324 0 : ctx->dw = (Float) (ctx->display_height * ctx->width * ctx->sar.num / ctx->height / ctx->sar.den);
1325 0 : ctx->dh = (Float) ctx->display_height;
1326 0 : ctx->oh = (Float) 0;
1327 0 : ctx->ow = (Float) (ctx->display_width - ctx->dw ) / 2;
1328 : } else {
1329 4 : ctx->dh = (Float) (ctx->display_width * ctx->height *ctx->sar.den / ctx->width / ctx->sar.num);
1330 4 : ctx->dw = (Float) ctx->display_width;
1331 4 : ctx->ow = (Float) 0;
1332 4 : ctx->oh = (Float) (ctx->display_height - ctx->dh ) / 2;
1333 : }
1334 4 : ctx->display_changed = GF_FALSE;
1335 : }
1336 :
1337 48 : dst_wnd.x = (u32) ctx->ow;
1338 48 : dst_wnd.y = (u32) ctx->oh;
1339 48 : dst_wnd.w = (u32) ctx->dw;
1340 48 : dst_wnd.h = (u32) ctx->dh;
1341 : e = GF_EOS;
1342 :
1343 48 : src_wnd.x = src_wnd.y = 0;
1344 48 : src_wnd.w = ctx->width;
1345 48 : src_wnd.h = ctx->height;
1346 :
1347 48 : if ((ctx->disp!=MODE_2D_SOFT) && ctx->video_out->Blit) {
1348 12 : e = ctx->video_out->Blit(ctx->video_out, &src_surf, &src_wnd, &dst_wnd, 0);
1349 12 : if (e) {
1350 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error bliting surface %s - retrying in software mode\n", gf_error_to_string(e) ));
1351 : }
1352 : }
1353 12 : if (e) {
1354 : GF_VideoSurface backbuffer;
1355 :
1356 36 : e = ctx->video_out->LockBackBuffer(ctx->video_out, &backbuffer, GF_TRUE);
1357 36 : if (!e) {
1358 36 : gf_stretch_bits(&backbuffer, &src_surf, &dst_wnd, &src_wnd, 0xFF, GF_FALSE, NULL, NULL);
1359 36 : ctx->video_out->LockBackBuffer(ctx->video_out, &backbuffer, GF_FALSE);
1360 : } else {
1361 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Cannot lock back buffer - Error %s\n", gf_error_to_string(e) ));
1362 : }
1363 : }
1364 :
1365 48 : if (e != GF_OK)
1366 : return;
1367 :
1368 :
1369 48 : if (ctx->dump_f_idx) {
1370 : char szFileName[1024];
1371 : GF_VideoSurface backbuffer;
1372 :
1373 0 : e = ctx->video_out->LockBackBuffer(ctx->video_out, &backbuffer, GF_TRUE);
1374 0 : if (!e) {
1375 : FILE *fout;
1376 0 : const char *src_ext = strchr(gf_file_basename(ctx->out), '.');
1377 0 : const char *ext = gf_pixel_fmt_sname(backbuffer.pixel_format);
1378 0 : if (!ext) ext = "rgb";
1379 :
1380 0 : if (src_ext && !strcmp(ext, src_ext+1)) {
1381 0 : snprintf(szFileName, 1024, "%s", ctx->out);
1382 : } else {
1383 0 : snprintf(szFileName, 1024, "%s_%d.%s", ctx->out, ctx->dump_f_idx, ext);
1384 : }
1385 0 : fout = gf_fopen(szFileName, "wb");
1386 0 : if (fout) {
1387 : u32 i;
1388 0 : for (i=0; i<backbuffer.height; i++) {
1389 0 : gf_fwrite(backbuffer.video_buffer + i*backbuffer.pitch_y, backbuffer.pitch_y, fout);
1390 : }
1391 0 : gf_fclose(fout);
1392 : } else {
1393 : e = GF_IO_ERR;
1394 : }
1395 0 : ctx->video_out->LockBackBuffer(ctx->video_out, &backbuffer, GF_FALSE);
1396 : }
1397 :
1398 0 : if (e) {
1399 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error writing frame %d buffer to %s: %s\n", ctx->nb_frames, szFileName, gf_error_to_string(e) ));
1400 : }
1401 : }
1402 :
1403 48 : dst_wnd.x = 0;
1404 48 : dst_wnd.y = 0;
1405 48 : dst_wnd.w = ctx->display_width;
1406 48 : dst_wnd.h = ctx->display_height;
1407 48 : e = ctx->video_out->Flush(ctx->video_out, &dst_wnd);
1408 48 : if (e) {
1409 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] Error flushing vido output %s%s\n", gf_error_to_string(e)));
1410 : return;
1411 : }
1412 : }
1413 :
1414 695 : static GF_Err vout_process(GF_Filter *filter)
1415 : {
1416 : GF_FilterPacket *pck;
1417 695 : GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter);
1418 :
1419 695 : if (ctx->force_vout) {
1420 0 : ctx->force_vout = GF_FALSE;
1421 0 : ctx->width = ctx->display_width = ctx->olwnd.z;
1422 0 : ctx->height = ctx->display_height = ctx->olwnd.w;
1423 0 : resize_video_output(ctx, ctx->width, ctx->height);
1424 0 : ctx->owsize.x = ctx->display_width;
1425 0 : ctx->owsize.y = ctx->display_height;
1426 : }
1427 695 : if (ctx->force_reconfig_pid) {
1428 0 : vout_configure_pid(filter, ctx->pid, GF_FALSE);
1429 0 : ctx->force_reconfig_pid = GF_FALSE;
1430 : }
1431 695 : ctx->video_out->ProcessEvent(ctx->video_out, NULL);
1432 :
1433 695 : if (!ctx->step && !ctx->speed) {
1434 : //we don't lock here since we don't access the pointer
1435 0 : if (ctx->oldata.ptr && ctx->update_oldata)
1436 0 : return vout_draw_frame(ctx);
1437 0 : gf_filter_ask_rt_reschedule(filter, 50000);
1438 0 : return GF_OK;
1439 : }
1440 :
1441 695 : if (ctx->do_seek) {
1442 : GF_FilterEvent evt;
1443 0 : GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->pid);
1444 0 : gf_filter_pid_send_event(ctx->pid, &evt);
1445 :
1446 0 : gf_filter_pid_init_play_event(ctx->pid, &evt, ctx->start, ctx->speed, "VideoOut");
1447 0 : gf_filter_pid_send_event(ctx->pid, &evt);
1448 0 : ctx->do_seek = GF_FALSE;
1449 0 : if (ctx->last_pck) {
1450 0 : gf_filter_pck_unref(ctx->last_pck);
1451 0 : ctx->last_pck = NULL;
1452 : }
1453 0 : ctx->nb_frames = 0;
1454 0 : ctx->buffer_done = GF_FALSE;
1455 0 : ctx->no_buffering = 0;
1456 0 : ctx->first_cts_plus_one = 0;
1457 0 : ctx->clock_at_first_cts = ctx->last_frame_clock = ctx->clock_at_first_frame = 0;
1458 : return GF_OK;
1459 : }
1460 :
1461 695 : if (!ctx->pid) {
1462 : //we don't lock here since we don't access the pointer
1463 0 : if (ctx->oldata.ptr && ctx->update_oldata)
1464 0 : return vout_draw_frame(ctx);
1465 0 : return ctx->oldata.ptr ? GF_OK : GF_EOS;
1466 : }
1467 :
1468 695 : pck = gf_filter_pid_get_packet(ctx->pid);
1469 695 : if (!pck) {
1470 19 : if (gf_filter_pid_is_eos(ctx->pid)) {
1471 11 : if (!ctx->aborted) {
1472 : GF_FilterEvent evt;
1473 9 : GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->pid);
1474 9 : gf_filter_pid_send_event(ctx->pid, &evt);
1475 :
1476 9 : ctx->aborted = GF_TRUE;
1477 : }
1478 : }
1479 19 : if (ctx->aborted) {
1480 19 : if (ctx->last_pck) {
1481 8 : gf_filter_pck_unref(ctx->last_pck);
1482 8 : ctx->last_pck = NULL;
1483 : }
1484 19 : if ((ctx->nb_frames>1) && ctx->last_pck_dur_us) {
1485 9 : gf_filter_ask_rt_reschedule(filter, ctx->last_pck_dur_us);
1486 9 : ctx->last_pck_dur_us = 0;
1487 : //we don't lock here since we don't access the pointer
1488 9 : if (ctx->oldata.ptr && ctx->update_oldata)
1489 0 : return vout_draw_frame(ctx);
1490 : return GF_OK;
1491 : }
1492 0 : } else if (ctx->rbuffer && ctx->buffer_done) {
1493 0 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[VideoOut] buffer empty, rebuffering\n"));
1494 0 : ctx->rebuffer = gf_sys_clock_high_res();
1495 0 : ctx->buffer_done = GF_FALSE;
1496 : }
1497 :
1498 : //check if all sinks are done - if not keep requesting a process to pump window event loop
1499 10 : if (!gf_filter_all_sinks_done(filter)) {
1500 0 : gf_filter_ask_rt_reschedule(filter, 100000);
1501 0 : if (ctx->display_changed)
1502 : goto draw_frame;
1503 :
1504 : //we don't lock here since we don't access the pointer
1505 0 : if (ctx->oldata.ptr && ctx->update_oldata)
1506 0 : return vout_draw_frame(ctx);
1507 : return GF_OK;
1508 : }
1509 10 : return ctx->aborted ? GF_EOS : GF_OK;
1510 : }
1511 :
1512 676 : if (!ctx->width || !ctx->height) {
1513 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[VideoOut] pid display wsize unknown, discarding packet\n"));
1514 0 : gf_filter_pid_drop_packet(ctx->pid);
1515 0 : return GF_OK;
1516 : }
1517 :
1518 676 : if (ctx->dumpframes.nb_items) {
1519 : u32 i;
1520 :
1521 380 : ctx->nb_frames++;
1522 380 : ctx->dump_f_idx = 0;
1523 :
1524 380 : for (i=0; i<ctx->dumpframes.nb_items; i++) {
1525 380 : if (ctx->dumpframes.vals[i] == 0) {
1526 0 : ctx->dump_f_idx = ctx->nb_frames;
1527 0 : break;
1528 : }
1529 380 : if (ctx->dumpframes.vals[i] == ctx->nb_frames) {
1530 19 : ctx->dump_f_idx = ctx->dumpframes.vals[i];
1531 19 : break;
1532 : }
1533 361 : if (ctx->dumpframes.vals[i] > ctx->nb_frames) {
1534 : break;
1535 : }
1536 : }
1537 :
1538 380 : if (ctx->dump_f_idx) {
1539 19 : ctx->last_pck = pck;
1540 19 : vout_draw_frame(ctx);
1541 19 : ctx->last_pck = NULL;
1542 : }
1543 380 : gf_filter_pid_drop_packet(ctx->pid);
1544 :
1545 380 : if (ctx->dumpframes.vals[ctx->dumpframes.nb_items-1] <= ctx->nb_frames) {
1546 19 : gf_filter_pid_set_discard(ctx->pid, GF_TRUE);
1547 : }
1548 : return GF_OK;
1549 : }
1550 :
1551 296 : if (ctx->buffer) {
1552 177 : if (gf_filter_pck_is_blocking_ref(pck)) {
1553 1 : ctx->buffer_done = GF_TRUE;
1554 1 : ctx->rebuffer = 0;
1555 1 : ctx->buffer = ctx->rbuffer = 0;
1556 : } else {
1557 : //query full buffer duration in us
1558 176 : u64 dur = gf_filter_pid_query_buffer_duration(ctx->pid, GF_FALSE);
1559 :
1560 176 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[VideoOut] buffer %d / %d ms\r", (u32) (dur/1000), ctx->buffer));
1561 176 : if (!ctx->buffer_done) {
1562 8 : if ((dur < ctx->buffer * 1000) && !gf_filter_pid_has_seen_eos(ctx->pid)) {
1563 0 : gf_filter_ask_rt_reschedule(filter, 100000);
1564 :
1565 0 : if (gf_filter_reporting_enabled(filter)) {
1566 : char szStatus[1024];
1567 0 : sprintf(szStatus, "buffering %d / %d ms", (u32) (dur/1000), ctx->buffer);
1568 0 : gf_filter_update_status(filter, -1, szStatus);
1569 : }
1570 : //we don't lock here since we don't access the pointer
1571 0 : if (ctx->oldata.ptr && ctx->update_oldata)
1572 0 : return vout_draw_frame(ctx);
1573 : return GF_OK;
1574 : }
1575 8 : ctx->buffer_done = GF_TRUE;
1576 8 : if (ctx->rebuffer) {
1577 0 : u64 rebuf_time = gf_sys_clock_high_res() - ctx->rebuffer;
1578 0 : ctx->rebuffer = 0;
1579 0 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[VideoOut] rebuffer done in "LLU" ms\n", (u32) (rebuf_time/1000)));
1580 0 : if (ctx->clock_at_first_cts)
1581 0 : ctx->clock_at_first_cts += rebuf_time;
1582 : }
1583 168 : } else if (ctx->rbuffer) {
1584 0 : if ((dur < ctx->rbuffer * 1000) && !gf_filter_pid_has_seen_eos(ctx->pid)) {
1585 0 : GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[VideoOut] buffer %u less than min threshold %u, rebuffering\n", (u32) (dur/1000), ctx->rbuffer));
1586 0 : ctx->rebuffer = gf_sys_clock_high_res();
1587 0 : ctx->buffer_done = GF_FALSE;
1588 0 : return GF_OK;
1589 : }
1590 : }
1591 : }
1592 : }
1593 :
1594 :
1595 453 : if (!ctx->step && (ctx->vsync || ctx->drop)) {
1596 : u64 ref_clock = 0;
1597 296 : u64 cts = gf_filter_pck_get_cts(pck);
1598 296 : u64 clock_us, now = gf_sys_clock_high_res();
1599 : Bool check_clock = GF_FALSE;
1600 : GF_Fraction64 media_ts;
1601 : s64 delay;
1602 :
1603 296 : if (ctx->dur.num && ctx->clock_at_first_cts && ctx->first_cts_plus_one) {
1604 0 : if ((cts - ctx->first_cts_plus_one + 1) * ctx->dur.den > (u64) (ctx->dur.num * ctx->timescale)) {
1605 : GF_FilterEvent evt;
1606 0 : if (ctx->last_pck) {
1607 0 : gf_filter_pck_unref(ctx->last_pck);
1608 0 : ctx->last_pck = NULL;
1609 : }
1610 0 : ctx->aborted = GF_TRUE;
1611 0 : GF_FEVT_INIT(evt, GF_FEVT_STOP, ctx->pid);
1612 0 : gf_filter_pid_send_event(ctx->pid, &evt);
1613 0 : gf_filter_pid_set_discard(ctx->pid, GF_TRUE);
1614 : return GF_EOS;
1615 0 : } else if (gf_filter_pid_has_seen_eos(ctx->pid)) {
1616 0 : gf_filter_ask_rt_reschedule(filter, 100000);
1617 : }
1618 : }
1619 :
1620 296 : delay = ctx->pid_delay;
1621 296 : if (ctx->vdelay.den)
1622 296 : delay += ctx->vdelay.num * (s32)ctx->timescale / (s32)ctx->vdelay.den;
1623 :
1624 296 : if (delay>=0) {
1625 296 : cts += delay;
1626 0 : } else if (cts < (u64) (-delay) ) {
1627 0 : if (ctx->vsync) {
1628 0 : if (ctx->last_pck) {
1629 0 : gf_filter_pck_unref(ctx->last_pck);
1630 0 : ctx->last_pck = NULL;
1631 : }
1632 0 : gf_filter_pid_drop_packet(ctx->pid);
1633 0 : gf_filter_ask_rt_reschedule(filter, 50000);
1634 0 : return GF_OK;
1635 : }
1636 : cts = 0;
1637 : } else {
1638 0 : cts -= (u64) -delay;
1639 : }
1640 :
1641 : //check if we have a clock hint from an audio output
1642 296 : gf_filter_get_clock_hint(filter, &clock_us, &media_ts);
1643 296 : if (clock_us && media_ts.den) {
1644 : u32 safety;
1645 : //ref frame TS in video stream timescale
1646 0 : s64 ref_ts = (s64) (media_ts.num * ctx->timescale);
1647 0 : ref_ts /= media_ts.den;
1648 : //compute time ellapsed since last clock ref in timescale
1649 0 : s64 diff = now;
1650 0 : diff -= (s64) clock_us;
1651 0 : if (ctx->timescale!=1000000) {
1652 0 : diff *= ctx->timescale;
1653 0 : diff /= 1000000;
1654 : }
1655 : assert(diff>=0);
1656 : //ref stream hypothetical timestamp at now
1657 0 : ref_ts += diff;
1658 0 : ctx->first_cts_plus_one = cts + 1;
1659 :
1660 : //allow 10ms video advance
1661 : #define DEF_VIDEO_AUDIO_ADVANCE_MS 15
1662 0 : safety = DEF_VIDEO_AUDIO_ADVANCE_MS * ctx->timescale / 1000;
1663 0 : if (!ctx->step && !ctx->raw_grab && ((s64) cts > ref_ts + safety)) {
1664 0 : u32 resched_time = (u32) ((cts-ref_ts - safety) * 1000000 / ctx->timescale);
1665 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms display frame CTS "LLU" CTS greater than reference clock CTS "LLU" (%g sec), waiting\n", gf_sys_clock(), cts, ref_ts, ((Double)media_ts.num)/media_ts.den));
1666 : //the clock is not updated continuously, only when audio sound card writes. We therefore
1667 : //cannot know if the sampling was recent or old, so ask for a short reschedule time
1668 0 : if (resched_time>100000)
1669 : resched_time = 100000;
1670 0 : gf_filter_ask_rt_reschedule(filter, resched_time );
1671 : //not ready yet
1672 0 : return GF_OK;
1673 : }
1674 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms frame CTS "LLU" latest reference clock CTS "LLU" (%g sec)\n", gf_sys_clock(), cts, ref_ts, ((Double)media_ts.num)/media_ts.den));
1675 : ref_clock = ref_ts;
1676 : check_clock = GF_TRUE;
1677 296 : } else if (!ctx->first_cts_plus_one) {
1678 : //init clock on second frame, first frame likely will have large rendering time
1679 : //due to GPU config. While this is not important for recorded media, it may impact
1680 : //monitoring of live source (webcams) by consuming frame really late which may
1681 : //block source sampling when blocking mode is enabled
1682 :
1683 : //store first frame TS
1684 18 : if (!ctx->clock_at_first_cts) {
1685 9 : ctx->clock_at_first_cts = 1 + cts;
1686 : } else {
1687 : //comute CTS diff in ms
1688 9 : u64 diff = cts - ctx->clock_at_first_cts + 1;
1689 9 : diff *= 1000;
1690 9 : diff /= ctx->timescale;
1691 : //diff less than 100ms (eg 10fps or more), init on the second frame
1692 : //do not apply this if playing at speed lower than nominal speed
1693 9 : if ((diff<100) && (ABS(ctx->speed)>=1)) {
1694 9 : ctx->first_cts_plus_one = 1 + cts;
1695 9 : ctx->clock_at_first_cts = now;
1696 : }
1697 : //otherwise (low frame rate), init on the first frame
1698 : else {
1699 0 : ctx->first_cts_plus_one = ctx->clock_at_first_cts /* - 1 + 1*/;
1700 0 : ctx->clock_at_first_cts = ctx->last_frame_clock;
1701 : check_clock = GF_TRUE;
1702 : }
1703 : }
1704 : ref_clock = cts;
1705 : } else {
1706 : check_clock = GF_TRUE;
1707 : }
1708 :
1709 : if (check_clock) {
1710 : s64 diff;
1711 278 : if (ctx->speed>=0) {
1712 278 : if (cts < ctx->first_cts_plus_one) cts = ctx->first_cts_plus_one;
1713 278 : diff = (s64) ((now - ctx->clock_at_first_cts) * ctx->speed);
1714 :
1715 278 : if (ctx->timescale != 1000000)
1716 278 : diff -= (s64) ( (cts - ctx->first_cts_plus_one + 1) * 1000000 / ctx->timescale);
1717 : else
1718 0 : diff -= (s64) (cts - ctx->first_cts_plus_one + 1);
1719 :
1720 : } else {
1721 0 : diff = (s64) ((now - ctx->clock_at_first_cts) * -ctx->speed);
1722 :
1723 0 : if (ctx->timescale != 1000000)
1724 0 : diff -= (s64) ( (ctx->first_cts_plus_one-1 - cts) * 1000000 / ctx->timescale);
1725 : else
1726 0 : diff -= (s64) (ctx->first_cts_plus_one-1 - cts);
1727 : }
1728 :
1729 278 : if (!ctx->raw_grab && (diff < -2000)) {
1730 139 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms frame cts "LLU"/%d "LLU" us too early, waiting\n", gf_sys_clock(), cts, ctx->timescale, -diff));
1731 139 : if (diff<-100000) diff = -100000;
1732 139 : gf_filter_ask_rt_reschedule(filter, (u32) (-diff));
1733 :
1734 139 : if (ctx->display_changed) goto draw_frame;
1735 : //not ready yet
1736 : return GF_OK;
1737 : }
1738 139 : if (ABS(diff)>2000) {
1739 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms frame cts "LLU"/%d is "LLD" us too %s\n", gf_sys_clock(), cts, ctx->timescale, diff<0 ? -diff : diff, diff<0 ? "early" : "late"));
1740 : } else {
1741 : diff = 0;
1742 : }
1743 :
1744 139 : if (ctx->timescale != 1000000)
1745 139 : ref_clock = diff * ctx->timescale / 1000000 + cts;
1746 : else
1747 0 : ref_clock = diff + cts;
1748 :
1749 139 : ctx->last_pck_dur_us = gf_filter_pck_get_duration(pck);
1750 139 : ctx->last_pck_dur_us *= 1000000;
1751 139 : ctx->last_pck_dur_us /= ctx->timescale;
1752 : }
1753 : //detach packet from pid, so that we can query next cts
1754 157 : gf_filter_pck_ref(&pck);
1755 157 : gf_filter_pid_drop_packet(ctx->pid);
1756 :
1757 157 : if (ctx->drop) {
1758 : u64 next_ts;
1759 : Bool do_drop = GF_FALSE;
1760 :
1761 0 : if (ctx->speed > 2){
1762 0 : u32 speed = (u32) ABS(ctx->speed);
1763 :
1764 0 : ctx->nb_drawn++;
1765 0 : if (ctx->nb_drawn % speed) {
1766 : do_drop = GF_TRUE;
1767 : } else {
1768 0 : ctx->nb_drawn = 0;
1769 : }
1770 : }
1771 : //peeking next packet CTS might fail if no packet in buffer, in which case we don't drop
1772 0 : else if (gf_filter_pid_get_first_packet_cts(ctx->pid, &next_ts)) {
1773 0 : if (next_ts<ref_clock) {
1774 : do_drop = GF_TRUE;
1775 : }
1776 : }
1777 :
1778 : if (do_drop) {
1779 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms drop frame "LLU" ms reference clock "LLU" ms\n", gf_sys_clock(), (1000*cts)/ctx->timescale, (1000*ref_clock)/ctx->timescale));
1780 :
1781 0 : gf_filter_pck_unref(pck);
1782 0 : gf_filter_ask_rt_reschedule(filter, 10);
1783 0 : return GF_OK;
1784 : }
1785 : }
1786 :
1787 157 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms display frame cts "LLU"/%d "LLU" ms\n", gf_sys_clock(), cts, ctx->timescale, (1000*cts)/ctx->timescale));
1788 : } else {
1789 0 : gf_filter_pck_ref(&pck);
1790 0 : gf_filter_pid_drop_packet(ctx->pid);
1791 :
1792 : #ifndef GPAC_DISABLE_LOG
1793 : #ifdef VOUT_USE_OPENGL
1794 0 : u64 cts = gf_filter_pck_get_cts(pck);
1795 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] At %d ms display frame cts "LLU"/%d "LLU" ms\n", gf_sys_clock(), cts, ctx->timescale, (1000*cts)/ctx->timescale));
1796 : #endif
1797 : #endif
1798 :
1799 : }
1800 :
1801 157 : if (ctx->last_pck)
1802 88 : gf_filter_pck_unref(ctx->last_pck);
1803 157 : ctx->last_pck = pck;
1804 157 : ctx->nb_frames++;
1805 :
1806 :
1807 157 : draw_frame:
1808 :
1809 157 : if (ctx->last_pck && gf_filter_reporting_enabled(filter)) {
1810 : char szStatus[1024];
1811 0 : u64 dur = gf_filter_pid_query_buffer_duration(ctx->pid, GF_FALSE);
1812 : Double fps = 0;
1813 0 : if (ctx->clock_at_first_frame) {
1814 0 : fps = ctx->nb_frames;
1815 0 : fps *= 1000000;
1816 0 : fps /= (1 + gf_sys_clock_high_res() - ctx->clock_at_first_cts);
1817 : } else {
1818 0 : ctx->clock_at_first_frame = gf_sys_clock_high_res();
1819 : }
1820 0 : sprintf(szStatus, "%dx%d->%dx%d %s frame TS "LLU"/%d buffer %d / %d ms %02.02f FPS", ctx->width, ctx->height, ctx->display_width, ctx->display_height,
1821 0 : gf_pixel_fmt_name(ctx->pfmt), gf_filter_pck_get_cts(ctx->last_pck), ctx->timescale, (u32) (dur/1000), ctx->buffer, fps);
1822 0 : gf_filter_update_status(filter, -1, szStatus);
1823 : }
1824 :
1825 157 : if (ctx->step) {
1826 0 : ctx->speed = 0;
1827 0 : ctx->step = GF_FALSE;
1828 : }
1829 157 : return vout_draw_frame(ctx);
1830 : }
1831 :
1832 176 : static GF_Err vout_draw_frame(GF_VideoOutCtx *ctx)
1833 : {
1834 176 : ctx->force_release = GF_FALSE;
1835 176 : if ((ctx->pfmt && ctx->last_pck) || !ctx->pid) {
1836 : #ifdef VOUT_USE_OPENGL
1837 176 : if (ctx->disp < MODE_2D) {
1838 128 : gf_rmt_begin_gl(vout_draw_gl);
1839 128 : glGetError();
1840 128 : vout_draw_gl(ctx, ctx->last_pck);
1841 128 : gf_rmt_end_gl();
1842 128 : glGetError();
1843 : } else
1844 : #endif
1845 : {
1846 48 : vout_draw_2d(ctx, ctx->last_pck);
1847 : }
1848 : }
1849 176 : if (ctx->no_buffering)
1850 19 : ctx->force_release = GF_TRUE;
1851 157 : else if (ctx->speed && ctx->last_pck && gf_filter_pck_is_blocking_ref(ctx->last_pck) )
1852 61 : ctx->force_release = GF_TRUE;
1853 :
1854 176 : if (ctx->force_release && ctx->last_pck && !ctx->dump_f_idx) {
1855 61 : gf_filter_pck_unref(ctx->last_pck);
1856 61 : ctx->last_pck = NULL;
1857 : }
1858 : //remember the clock right after the flush (in case of vsync)
1859 176 : ctx->last_frame_clock = gf_sys_clock_high_res();
1860 :
1861 : //note we don't ask RT reschedule in frame duration, we take the decision on postponing the next frame in the next process call
1862 176 : return GF_OK;
1863 : }
1864 :
1865 388 : static Bool vout_process_event(GF_Filter *filter, const GF_FilterEvent *fevt)
1866 : {
1867 388 : if (fevt->base.type==GF_FEVT_INFO_UPDATE) {
1868 388 : GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter);
1869 388 : vout_set_caption(ctx);
1870 388 : return GF_TRUE;
1871 : }
1872 0 : if (!fevt->base.on_pid && (fevt->base.type==GF_FEVT_USER)) {
1873 0 : GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter);
1874 : GF_Err e;
1875 0 : if (!ctx->video_out) return GF_FALSE;
1876 0 : e = ctx->video_out->ProcessEvent(ctx->video_out, (GF_Event *) &fevt->user_event.event);
1877 0 : if (e) return GF_FALSE;
1878 0 : return GF_TRUE;
1879 : }
1880 : //cancel
1881 : return GF_TRUE;
1882 : }
1883 :
1884 0 : GF_Err vout_update_arg(GF_Filter *filter, const char *arg_name, const GF_PropertyValue *new_val)
1885 : {
1886 0 : GF_VideoOutCtx *ctx = (GF_VideoOutCtx *) gf_filter_get_udta(filter);
1887 :
1888 0 : if (!strcmp(arg_name, "olwnd")) {
1889 0 : if (!ctx->pid) {
1890 0 : if ((ctx->width != new_val->value.vec4i.z) || (ctx->height != new_val->value.vec4i.w))
1891 0 : ctx->force_vout = GF_TRUE;
1892 : }
1893 : return GF_OK;
1894 0 : } else if (!strcmp(arg_name, "oldata")) {
1895 0 : if (new_val->value.data.ptr) {
1896 0 : ctx->update_oldata = GF_TRUE;
1897 : } else {
1898 : vout_reset_overlay(ctx);
1899 : }
1900 0 : if (!ctx->pid) {
1901 0 : if (!ctx->height)
1902 0 : ctx->force_vout = GF_TRUE;
1903 0 : ctx->update_oldata = GF_TRUE;
1904 0 : gf_filter_post_process_task(filter);
1905 : }
1906 : return GF_OK;
1907 0 : } else if (!strcmp(arg_name, "olsize")) {
1908 : vout_reset_overlay(ctx);
1909 : return GF_OK;
1910 : }
1911 :
1912 0 : if (!strcmp(arg_name, "fullscreen")) {
1913 0 : if (new_val->value.boolean) {
1914 0 : u32 nw=ctx->display_width, nh=ctx->display_height;
1915 0 : ctx->video_out->SetFullScreen(ctx->video_out, GF_TRUE, &nw, &nh);
1916 0 : ctx->in_fullscreen = GF_TRUE;
1917 0 : ctx->owsize.x = nw;
1918 0 : ctx->owsize.y = nh;
1919 : } else {
1920 0 : u32 nw=ctx->display_width, nh=ctx->display_height;
1921 0 : ctx->video_out->SetFullScreen(ctx->video_out, GF_FALSE, &nw, &nh);
1922 0 : ctx->in_fullscreen = GF_FALSE;
1923 0 : ctx->owsize.x = ctx->display_width;
1924 0 : ctx->owsize.y = ctx->display_height;
1925 : }
1926 : return GF_OK;
1927 : }
1928 0 : if (!strcmp(arg_name, "vrot")) {
1929 0 : if (ctx->disp<MODE_2D)
1930 0 : ctx->force_reconfig_pid = GF_TRUE;
1931 : return GF_OK;
1932 : }
1933 0 : if (!strcmp(arg_name, "step")) {
1934 : return GF_OK;
1935 : }
1936 0 : if (!strcmp(arg_name, "start")) {
1937 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[VideoOut] Seek request to %f\n", new_val->value.number));
1938 0 : if (!ctx->pid)
1939 : return GF_OK;
1940 0 : ctx->do_seek = GF_TRUE;
1941 : }
1942 0 : if (!strcmp(arg_name, "speed") && ctx->pid) {
1943 : GF_FilterEvent fevt;
1944 0 : GF_FEVT_INIT(fevt, 0, ctx->pid);
1945 0 : if (new_val->value.number) {
1946 0 : if (!ctx->speed) {
1947 0 : fevt.base.type = GF_FEVT_RESUME;
1948 : } else {
1949 0 : fevt.base.type = GF_FEVT_SET_SPEED;
1950 0 : fevt.play.speed = new_val->value.number;
1951 : }
1952 : } else {
1953 0 : fevt.base.type = GF_FEVT_PAUSE;
1954 : }
1955 0 : gf_filter_pid_send_event(ctx->pid, &fevt);
1956 : }
1957 : //reinit clock
1958 0 : ctx->first_cts_plus_one = 0;
1959 0 : ctx->clock_at_first_cts = ctx->last_frame_clock = ctx->clock_at_first_frame = 0;
1960 0 : return GF_OK;
1961 : }
1962 :
1963 :
1964 :
1965 : #define OFFS(_n) #_n, offsetof(GF_VideoOutCtx, _n)
1966 :
1967 : static const GF_FilterArgs VideoOutArgs[] =
1968 : {
1969 : { OFFS(drv), "video driver name", GF_PROP_NAME, NULL, NULL, 0},
1970 : { OFFS(vsync), "enable video screen sync", GF_PROP_BOOL, "true", NULL, GF_FS_ARG_HINT_ADVANCED},
1971 : { OFFS(drop), "enable dropping late frames", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
1972 : { OFFS(disp), "display mode\n"
1973 : "- gl: OpenGL\n"
1974 : "- pbo: OpenGL with PBO\n"
1975 : "- blit: 2D hardware blit\n"
1976 : "- soft: software blit", GF_PROP_UINT, "gl", "gl|pbo|blit|soft", GF_FS_ARG_HINT_ADVANCED},
1977 : { OFFS(start), "set playback start offset. Negative value means percent of media duration with -1 equal to duration", GF_PROP_DOUBLE, "0.0", NULL, GF_FS_ARG_UPDATE},
1978 : { OFFS(dur), "only play the specified duration", GF_PROP_FRACTION64, "0", NULL, 0},
1979 : { OFFS(speed), "set playback speed when vsync is on. If speed is negative and start is 0, start is set to -1", GF_PROP_DOUBLE, "1.0", NULL, GF_FS_ARG_UPDATE},
1980 : { OFFS(hold), "number of seconds to hold display for single-frame streams. A negative value force a hold on last frame for single or multi-frames streams", GF_PROP_DOUBLE, "1.0", NULL, 0},
1981 : { OFFS(linear), "use linear filtering instead of nearest pixel for GL mode", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
1982 : { OFFS(back), "back color for transparent images", GF_PROP_UINT, "0x808080", NULL, GF_FS_ARG_HINT_ADVANCED},
1983 : { OFFS(wsize), "default init window size. 0x0 holds the window size of the first frame. Negative values indicate video media size", GF_PROP_VEC2I, "-1x-1", NULL, GF_FS_ARG_HINT_ADVANCED},
1984 : { OFFS(wpos), "default position (0,0 top-left)", GF_PROP_VEC2I, "-1x-1", NULL, GF_FS_ARG_HINT_ADVANCED},
1985 : { OFFS(vdelay), "set delay in sec, positive value displays after audio clock", GF_PROP_FRACTION, "0", NULL, GF_FS_ARG_HINT_ADVANCED|GF_FS_ARG_UPDATE},
1986 : { OFFS(hide), "hide output window", GF_PROP_BOOL, "false", NULL, 0},
1987 : { OFFS(fullscreen), "use fullscreen", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_UPDATE},
1988 : { OFFS(buffer), "set playout buffer in ms", GF_PROP_UINT, "100", NULL, 0},
1989 : { OFFS(mbuffer), "set max buffer occupancy in ms (if less than buffer, use buffer)", GF_PROP_UINT, "0", NULL, 0},
1990 : { OFFS(rbuffer), "rebuffer trigger in ms (if 0 or more than buffer, disable rebuffering", GF_PROP_UINT, "0", NULL, GF_FS_ARG_UPDATE},
1991 : { OFFS(dumpframes), "ordered list of frames to dump, 1 being first frame - see filter help. Special value 0 means dump all frames", GF_PROP_UINT_LIST, NULL, NULL, GF_FS_ARG_HINT_EXPERT},
1992 : { OFFS(out), "radical of dump frame filenames. If no extension is provided, frames are exported as $OUT_%d.PFMT", GF_PROP_STRING, "dump", NULL, GF_FS_ARG_HINT_EXPERT},
1993 : { OFFS(step), "step frame", GF_PROP_BOOL, "false", NULL, GF_ARG_HINT_HIDE|GF_FS_ARG_UPDATE},
1994 :
1995 : { OFFS(olwnd), "overlay window position and size", GF_PROP_VEC4I, NULL, NULL, GF_ARG_HINT_HIDE|GF_FS_ARG_UPDATE},
1996 : { OFFS(olsize), "overlay texture size (must be RGBA)", GF_PROP_VEC2I, NULL, NULL, GF_ARG_HINT_HIDE|GF_FS_ARG_UPDATE_SYNC},
1997 : { OFFS(oldata), "overlay texture data (must be RGBA)", GF_PROP_CONST_DATA, NULL, NULL, GF_ARG_HINT_HIDE|GF_FS_ARG_UPDATE_SYNC},
1998 : { OFFS(owsize), "output window size (readonly)", GF_PROP_VEC2I, NULL, NULL, GF_ARG_HINT_EXPERT},
1999 : { OFFS(buffer_done), "buffer done indication (readonly)", GF_PROP_BOOL, NULL, NULL, GF_ARG_HINT_EXPERT},
2000 : { OFFS(rebuffer), "time at which rebuffer started, 0 if not rebuffering (readonly)", GF_PROP_LUINT, NULL, NULL, GF_ARG_HINT_EXPERT},
2001 :
2002 : { OFFS(vflip), "flip video (GL only)\n"
2003 : "- no: no flipping\n"
2004 : "- v: vertical flip\n"
2005 : "- h: horizontal flip\n"
2006 : "- vh: horizontal and vertical\n"
2007 : "- hv: same as vh"
2008 : , GF_PROP_UINT, "no", "no|v|h|vh|hv", GF_FS_ARG_UPDATE | GF_FS_ARG_HINT_ADVANCED},
2009 : { OFFS(vrot), "rotate video by given angle\n"
2010 : "- 0: no rotation\n"
2011 : "- 90: rotate 90 degree counter clockwise\n"
2012 : "- 180: rotate 180 degree\n"
2013 : "- 270: rotate 90 degree clockwise"
2014 : , GF_PROP_UINT, "0","0|90|180|270", GF_FS_ARG_UPDATE | GF_FS_ARG_HINT_ADVANCED},
2015 : {0}
2016 : };
2017 :
2018 : static const GF_FilterCapability VideoOutCaps[] =
2019 : {
2020 : CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
2021 : CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_RAW)
2022 : };
2023 :
2024 :
2025 : GF_FilterRegister VideoOutRegister = {
2026 : .name = "vout",
2027 : GF_FS_SET_DESCRIPTION("Video output")
2028 : GF_FS_SET_HELP("This filter displays a single visual pid in a window.\n"\
2029 : "The window is created unless a window handle (HWND, xWindow, etc) is indicated in the config file ( [Temp]OSWnd=ptr).\n"\
2030 : "The output uses GPAC video output module indicated in [-drv]() option or in the config file (see GPAC core help).\n"\
2031 : "The video output module can be further configured (see GPAC core help).\n"\
2032 : "The filter can use OpenGL or 2D blit of the graphics card, depending on the OS support.\n"\
2033 : "The filter can be used do dump frames as written on the graphics card.\n"\
2034 : "In this case, the window is not visible and only the listed frames are drawn to the GPU.\n"\
2035 : "The pixel format of the dumped frame is always RGB in OpenGL and matches the video backbuffer format in 2D mode.\n"\
2036 : )
2037 : .private_size = sizeof(GF_VideoOutCtx),
2038 : .flags = GF_FS_REG_MAIN_THREAD,
2039 : .args = VideoOutArgs,
2040 : SETCAPS(VideoOutCaps),
2041 : .initialize = vout_initialize,
2042 : .finalize = vout_finalize,
2043 : .configure_pid = vout_configure_pid,
2044 : .process = vout_process,
2045 : .process_event = vout_process_event,
2046 : .update_arg = vout_update_arg
2047 : };
2048 :
2049 2877 : const GF_FilterRegister *vout_register(GF_FilterSession *session)
2050 : {
2051 2877 : return &VideoOutRegister;
2052 : }
2053 : #else
2054 : const GF_FilterRegister *vout_register(GF_FilterSession *session)
2055 : {
2056 : return NULL;
2057 : }
2058 : #endif // GPAC_DISABLE_PLAYER
|