1 /*****************************************************************************
2 * vhs.c : VHS effect video filter
3 *****************************************************************************
4 * Copyright (C) 2013 Vianney Boyer
7 * Authors: Vianney Boyer <vlcvboyer -at- gmail -dot- com>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_filter.h>
35 #include <vlc_picture.h>
39 #include "filter_picture.h"
41 static inline int64_t MOD(int64_t a
, int64_t b
) {
42 return ( ( a
% b
) + b
) % b
; }
44 #define MAX_BLUE_RED_LINES 100
50 vlc_tick_t i_stop_trigger
;
59 int32_t *i_height
; /* note: each plane may have different dimensions */
61 int32_t *i_visible_pitch
;
62 vlc_tick_t i_start_time
;
63 vlc_tick_t i_last_time
;
64 vlc_tick_t i_cur_time
;
66 /* sliding & offset effect */
67 int32_t i_phase_speed
;
70 int32_t i_sliding_ofs
;
71 int32_t i_sliding_speed
;
72 vlc_tick_t i_offset_trigger
;
73 vlc_tick_t i_sliding_trigger
;
74 vlc_tick_t i_sliding_stop_trig
;
75 bool i_sliding_type_duplicate
;
77 /* blue red lines effect */
78 vlc_tick_t i_BR_line_trigger
;
79 blue_red_line_t
*p_BR_lines
[MAX_BLUE_RED_LINES
];
83 /*****************************************************************************
85 *****************************************************************************/
87 static picture_t
*Filter( filter_t
*, picture_t
* );
89 static int vhs_allocate_data( filter_t
*, picture_t
* );
90 static void vhs_free_allocated_data( filter_t
* );
92 static int vhs_blue_red_line_effect( filter_t
*, picture_t
* );
93 static void vhs_blue_red_dots_effect( filter_t
*, picture_t
* );
94 static int vhs_sliding_effect( filter_t
*, picture_t
* );
96 static int vhs_sliding_effect_apply( filter_t
*, picture_t
* );
98 /*****************************************************************************
100 *****************************************************************************/
102 static int Open ( vlc_object_t
* );
103 static void Close( vlc_object_t
* );
106 set_description( N_("VHS movie effect video filter") )
107 set_shortname( N_("VHS movie" ) )
108 set_capability( "video filter", 0 )
109 set_category( CAT_VIDEO
)
110 set_subcategory( SUBCAT_VIDEO_VFILTER
)
112 set_callbacks( Open
, Close
)
118 static int Open( vlc_object_t
*p_this
)
120 filter_t
*p_filter
= (filter_t
*)p_this
;
123 /* Assert video in match with video out */
124 if( !es_format_IsSimilar( &p_filter
->fmt_in
, &p_filter
->fmt_out
) ) {
125 msg_Err( p_filter
, "Input and output format does not match" );
129 /* Reject 0 bpp and unsupported chroma */
130 const vlc_fourcc_t fourcc
= p_filter
->fmt_in
.video
.i_chroma
;
131 const vlc_chroma_description_t
*p_chroma
=
132 vlc_fourcc_GetChromaDescription( p_filter
->fmt_in
.video
.i_chroma
);
133 if( !p_chroma
|| p_chroma
->pixel_size
== 0
134 || p_chroma
->plane_count
< 3 || p_chroma
->pixel_size
> 1
135 || !vlc_fourcc_IsYUV( fourcc
) )
137 msg_Err( p_filter
, "Unsupported chroma (%4.4s)", (char*)&fourcc
);
141 /* Allocate structure */
142 p_filter
->p_sys
= p_sys
= calloc(1, sizeof(*p_sys
) );
143 if( unlikely( !p_sys
) )
147 p_filter
->pf_video_filter
= Filter
;
148 p_sys
->i_start_time
= p_sys
->i_cur_time
= p_sys
->i_last_time
= vlc_tick_now();
156 static void Close( vlc_object_t
*p_this
) {
157 filter_t
*p_filter
= (filter_t
*)p_this
;
158 filter_sys_t
*p_sys
= p_filter
->p_sys
;
160 /* Free allocated memory */
161 vhs_free_allocated_data( p_filter
);
168 static picture_t
*Filter( filter_t
*p_filter
, picture_t
*p_pic_in
) {
169 if( unlikely( !p_pic_in
|| !p_filter
) )
172 filter_sys_t
*p_sys
= p_filter
->p_sys
;
174 picture_t
*p_pic_out
= filter_NewPicture( p_filter
);
175 if( unlikely( !p_pic_out
) ) {
176 picture_Release( p_pic_in
);
183 p_sys
->i_last_time
= p_sys
->i_cur_time
;
184 p_sys
->i_cur_time
= vlc_tick_now();
189 if ( unlikely( !p_sys
->b_init
) )
190 if ( unlikely( vhs_allocate_data( p_filter
, p_pic_in
) != VLC_SUCCESS
) ) {
191 picture_Release( p_pic_in
);
194 p_sys
->b_init
= true;
197 * preset output pic: raw copy src to dst
199 picture_CopyPixels(p_pic_out
, p_pic_in
);
202 * apply effects on picture
204 if ( unlikely( vhs_blue_red_line_effect( p_filter
, p_pic_out
) != VLC_SUCCESS
) )
205 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
207 if ( unlikely( vhs_sliding_effect(p_filter
, p_pic_out
) != VLC_SUCCESS
) )
208 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
210 vhs_blue_red_dots_effect( p_filter
, p_pic_out
);
212 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
218 static int vhs_allocate_data( filter_t
*p_filter
, picture_t
*p_pic_in
) {
219 filter_sys_t
*p_sys
= p_filter
->p_sys
;
221 vhs_free_allocated_data( p_filter
);
224 * take into account different characteristics for each plane
226 p_sys
->i_planes
= p_pic_in
->i_planes
;
227 p_sys
->i_height
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
228 p_sys
->i_width
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
229 p_sys
->i_visible_pitch
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
231 if( unlikely( !p_sys
->i_height
|| !p_sys
->i_width
|| !p_sys
->i_visible_pitch
) ) {
232 vhs_free_allocated_data( p_filter
);
236 for ( int32_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++) {
237 p_sys
->i_visible_pitch
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_pitch
;
238 p_sys
->i_height
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_lines
;
239 p_sys
->i_width
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_pitch
/ p_pic_in
->p
[i_p
].i_pixel_pitch
;
245 * Free allocated data
247 static void vhs_free_allocated_data( filter_t
*p_filter
) {
248 filter_sys_t
*p_sys
= p_filter
->p_sys
;
250 for ( uint32_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
251 FREENULL( p_sys
->p_BR_lines
[i_b
] );
254 FREENULL( p_sys
->i_height
);
255 FREENULL( p_sys
->i_width
);
256 FREENULL( p_sys
->i_visible_pitch
);
261 * Horizontal blue or red lines random management and effect
263 static int vhs_blue_red_line_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
264 filter_sys_t
*p_sys
= p_filter
->p_sys
;
266 #define BR_LINES_GENERATOR_PERIOD VLC_TICK_FROM_SEC(50)
267 #define BR_LINES_DURATION VLC_TICK_FROM_MS(20)
269 /* generate new blue or red lines */
270 if ( p_sys
->i_BR_line_trigger
<= p_sys
->i_cur_time
) {
271 for ( uint32_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
272 if (p_sys
->p_BR_lines
[i_b
] == NULL
) {
274 p_sys
->p_BR_lines
[i_b
] = calloc( 1, sizeof(blue_red_line_t
) );
275 if ( unlikely( !p_sys
->p_BR_lines
[i_b
] ) )
278 /* set random parameters */
279 p_sys
->p_BR_lines
[i_b
]->i_offset
= (unsigned)vlc_mrand48()
280 % __MAX( 1, p_sys
->i_height
[Y_PLANE
] - 10 )
283 p_sys
->p_BR_lines
[i_b
]->b_blue_red
= (unsigned)vlc_mrand48() & 0x01;
285 p_sys
->p_BR_lines
[i_b
]->i_stop_trigger
= p_sys
->i_cur_time
286 + (uint64_t)vlc_mrand48() % BR_LINES_DURATION
287 + BR_LINES_DURATION
/ 2;
291 p_sys
->i_BR_line_trigger
= p_sys
->i_cur_time
292 + (uint64_t)vlc_mrand48() % BR_LINES_GENERATOR_PERIOD
293 + BR_LINES_GENERATOR_PERIOD
/ 2;
297 /* manage and apply current blue/red lines */
298 for ( uint8_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
299 if ( p_sys
->p_BR_lines
[i_b
] ) {
300 /* remove outdated ones */
301 if ( p_sys
->p_BR_lines
[i_b
]->i_stop_trigger
<= p_sys
->i_cur_time
) {
302 FREENULL( p_sys
->p_BR_lines
[i_b
] );
306 /* otherwise apply */
307 for ( int32_t i_p
=0; i_p
< p_sys
->i_planes
; i_p
++ ) {
308 uint32_t i_pix_ofs
= p_sys
->p_BR_lines
[i_b
]->i_offset
309 * p_pic_out
->p
[i_p
].i_visible_lines
310 / p_sys
->i_height
[Y_PLANE
]
311 * p_pic_out
->p
[i_p
].i_pitch
;
315 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
], 127,
316 p_pic_out
->p
[i_p
].i_visible_pitch
);
319 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
320 (p_sys
->p_BR_lines
[i_b
]->b_blue_red
?255:0),
321 p_pic_out
->p
[i_p
].i_visible_pitch
);
324 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
325 (p_sys
->p_BR_lines
[i_b
]->b_blue_red
?0:255),
326 p_pic_out
->p
[i_p
].i_visible_pitch
);
336 * insert randomly blue and red dots on the picture
338 static void vhs_blue_red_dots_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
339 #define BR_DOTS_RATIO 10000
341 filter_sys_t
*p_sys
= p_filter
->p_sys
;
343 for ( int32_t i_dots
= 0;
344 i_dots
< p_sys
->i_width
[Y_PLANE
] * p_sys
->i_height
[Y_PLANE
] / BR_DOTS_RATIO
;
347 uint32_t i_length
= (unsigned)vlc_mrand48()
348 % __MAX( 1, p_sys
->i_width
[Y_PLANE
] / 30 ) + 1;
350 uint16_t i_x
= (unsigned)vlc_mrand48()
351 % __MAX( 1, p_sys
->i_width
[Y_PLANE
] - i_length
);
352 uint16_t i_y
= (unsigned)vlc_mrand48() % p_sys
->i_height
[Y_PLANE
];
353 bool b_color
= ( ( (unsigned)vlc_mrand48() % 2 ) == 0);
355 for ( int32_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++ ) {
356 uint32_t i_pix_ofs
= i_y
357 * p_pic_out
->p
[i_p
].i_visible_lines
358 / p_sys
->i_height
[Y_PLANE
]
359 * p_pic_out
->p
[i_p
].i_pitch
361 * p_pic_out
->p
[i_p
].i_pixel_pitch
;
363 uint32_t i_length_in_plane
= i_length
364 * p_pic_out
->p
[i_p
].i_visible_pitch
365 / p_pic_out
->p
[Y_PLANE
].i_visible_pitch
;
369 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
], 127,
373 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
378 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
390 static int vhs_sliding_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
391 filter_sys_t
*p_sys
= p_filter
->p_sys
;
394 * one shot offset section
397 #define OFFSET_AVERAGE_PERIOD VLC_TICK_FROM_SEC(10)
399 /* start trigger to be (re)initialized */
400 if ( p_sys
->i_offset_trigger
== 0
401 || p_sys
->i_sliding_speed
!= 0 ) { /* do not mix sliding and offset */
403 /* random trigger for offset effect */
404 p_sys
->i_offset_trigger
= p_sys
->i_cur_time
405 + ((uint64_t) vlc_mrand48() ) % OFFSET_AVERAGE_PERIOD
406 + OFFSET_AVERAGE_PERIOD
/ 2;
407 p_sys
->i_offset_ofs
= 0;
408 } else if (p_sys
->i_offset_trigger
<= p_sys
->i_cur_time
) {
409 /* trigger for offset effect occurs */
410 p_sys
->i_offset_trigger
= 0;
411 p_sys
->i_offset_ofs
= (uint32_t)vlc_mrand48()
412 % p_sys
->i_height
[Y_PLANE
];
415 p_sys
->i_offset_ofs
= 0;
422 #define MAX_PHASE_OFS (p_sys->i_height[Y_PLANE]*100/15)
424 p_sys
->i_phase_speed
+= MOD( (int32_t)vlc_mrand48(), 3) - 1;
425 p_sys
->i_phase_ofs
+= p_sys
->i_phase_speed
;
426 p_sys
->i_phase_ofs
= VLC_CLIP( p_sys
->i_phase_ofs
, -MAX_PHASE_OFS
, +MAX_PHASE_OFS
);
427 if ( abs( p_sys
->i_phase_ofs
) >= MAX_PHASE_OFS
)
428 p_sys
->i_phase_speed
= 0;
435 #define SLIDING_AVERAGE_PERIOD VLC_TICK_FROM_SEC(20)
436 #define SLIDING_AVERAGE_DURATION VLC_TICK_FROM_SEC(3)
438 /* start trigger to be (re)initialized */
439 if ( ( p_sys
->i_sliding_stop_trig
== 0 ) &&
440 ( p_sys
->i_sliding_trigger
== 0 ) &&
441 ( p_sys
->i_sliding_speed
== 0 ) ) {
443 /* random trigger which enable sliding effect */
444 p_sys
->i_sliding_trigger
= p_sys
->i_cur_time
445 + (uint64_t)vlc_mrand48() % SLIDING_AVERAGE_PERIOD
446 + SLIDING_AVERAGE_PERIOD
/ 2;
449 /* start trigger just occurs */
450 else if ( ( p_sys
->i_sliding_stop_trig
== 0 ) &&
451 ( p_sys
->i_sliding_trigger
<= p_sys
->i_cur_time
) &&
452 ( p_sys
->i_sliding_speed
== 0 ) ) {
454 /* init sliding parameters */
455 p_sys
->i_sliding_trigger
= 0;
456 p_sys
->i_sliding_stop_trig
= p_sys
->i_cur_time
457 + (uint64_t)vlc_mrand48() % SLIDING_AVERAGE_DURATION
458 + SLIDING_AVERAGE_DURATION
/ 2;
459 p_sys
->i_sliding_ofs
= 0;
460 /* note: sliding speed unit = image per 100 s */
461 p_sys
->i_sliding_speed
= MOD( (int32_t)vlc_mrand48(), 1001 ) - 500;
462 p_sys
->i_sliding_type_duplicate
= (unsigned)vlc_mrand48() & 0x01;
465 /* stop trigger disabling sliding effect occurs */
466 else if ( ( p_sys
->i_sliding_stop_trig
<= p_sys
->i_cur_time
)
467 && ( p_sys
->i_sliding_trigger
== 0 ) ) {
469 /* first increase speed to ensure we will stop sliding on plain pict */
470 if ( abs( p_sys
->i_sliding_speed
) < 5 )
471 p_sys
->i_sliding_speed
+= 1;
473 /* check if offset is close to 0 and then ready to stop */
474 if ( abs( p_sys
->i_sliding_ofs
) < abs( p_sys
->i_sliding_speed
475 * p_sys
->i_height
[Y_PLANE
]
476 * ( p_sys
->i_cur_time
- p_sys
->i_last_time
) / CLOCK_FREQ
)
477 || abs( p_sys
->i_sliding_ofs
) < p_sys
->i_height
[Y_PLANE
] * 100 / 20 ) {
479 /* reset sliding parameters */
480 p_sys
->i_sliding_ofs
= p_sys
->i_sliding_speed
= 0;
481 p_sys
->i_sliding_trigger
= p_sys
->i_sliding_stop_trig
= 0;
482 p_sys
->i_sliding_type_duplicate
= false;
487 p_sys
->i_sliding_ofs
= MOD( p_sys
->i_sliding_ofs
488 + p_sys
->i_sliding_speed
* p_sys
->i_height
[Y_PLANE
]
489 * ( p_sys
->i_cur_time
- p_sys
->i_last_time
)
491 p_sys
->i_height
[Y_PLANE
] * 100 );
493 return vhs_sliding_effect_apply( p_filter
, p_pic_out
);
497 * apply both sliding and offset effect
499 static int vhs_sliding_effect_apply( filter_t
*p_filter
, picture_t
*p_pic_out
)
501 filter_sys_t
*p_sys
= p_filter
->p_sys
;
503 for ( uint8_t i_p
= 0; i_p
< p_pic_out
->i_planes
; i_p
++ ) {
504 /* first allocate temporary buffer for swap operation */
506 if ( !p_sys
->i_sliding_type_duplicate
) {
507 p_temp_buf
= calloc( p_pic_out
->p
[i_p
].i_lines
508 * p_pic_out
->p
[i_p
].i_pitch
, sizeof(uint8_t) );
509 if ( unlikely( !p_temp_buf
) )
511 memcpy( p_temp_buf
, p_pic_out
->p
[i_p
].p_pixels
,
512 p_pic_out
->p
[i_p
].i_lines
* p_pic_out
->p
[i_p
].i_pitch
);
515 p_temp_buf
= p_pic_out
->p
[i_p
].p_pixels
;
517 /* copy lines to output_pic */
518 for ( int32_t i_y
= 0; i_y
< p_pic_out
->p
[i_p
].i_visible_lines
; i_y
++ )
520 int32_t i_ofs
= p_sys
->i_offset_ofs
+ p_sys
->i_sliding_ofs
;
522 if ( ( p_sys
->i_sliding_speed
== 0 ) || !p_sys
->i_sliding_type_duplicate
)
523 i_ofs
+= p_sys
->i_phase_ofs
;
525 i_ofs
= MOD( i_ofs
/ 100, p_sys
->i_height
[Y_PLANE
] );
526 i_ofs
*= p_pic_out
->p
[i_p
].i_visible_lines
;
527 i_ofs
/= p_sys
->i_height
[Y_PLANE
];
529 memcpy( &p_pic_out
->p
[i_p
].p_pixels
[ i_y
* p_pic_out
->p
[i_p
].i_pitch
],
530 &p_temp_buf
[ ( ( i_y
+ i_ofs
) % p_pic_out
->p
[i_p
].i_visible_lines
) * p_pic_out
->p
[i_p
].i_pitch
],
531 p_pic_out
->p
[i_p
].i_visible_pitch
);
533 if ( !p_sys
->i_sliding_type_duplicate
)