1 /*****************************************************************************
2 * vhs.c : VHS effect video filter
3 *****************************************************************************
4 * Copyright (C) 2013 Vianney Boyer
6 * Authors: Vianney Boyer <vlcvboyer -at- gmail -dot- com>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
23 /*****************************************************************************
25 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_filter.h>
34 #include <vlc_picture.h>
38 #include "filter_picture.h"
40 static inline int64_t MOD(int64_t a
, int64_t b
) {
41 return ( ( a
% b
) + b
) % b
; }
43 #define MAX_BLUE_RED_LINES 100
49 vlc_tick_t i_stop_trigger
;
58 int32_t i_height
[VOUT_MAX_PLANES
]; /* note: each plane may have different dimensions */
59 int32_t i_width
[VOUT_MAX_PLANES
];
60 int32_t i_visible_pitch
[VOUT_MAX_PLANES
];
61 vlc_tick_t i_start_time
;
62 vlc_tick_t i_last_time
;
63 vlc_tick_t i_cur_time
;
65 /* sliding & offset effect */
71 vlc_tick_t i_offset_trigger
;
72 vlc_tick_t i_sliding_trigger
;
73 vlc_tick_t i_sliding_stop_trig
;
74 bool i_sliding_type_duplicate
;
76 /* blue red lines effect */
77 vlc_tick_t i_BR_line_trigger
;
78 blue_red_line_t
*p_BR_lines
[MAX_BLUE_RED_LINES
];
82 /*****************************************************************************
84 *****************************************************************************/
86 static picture_t
*Filter( filter_t
*, picture_t
* );
88 static int vhs_allocate_data( filter_t
*, picture_t
* );
89 static void vhs_free_allocated_data( filter_t
* );
91 static int vhs_blue_red_line_effect( filter_t
*, picture_t
* );
92 static void vhs_blue_red_dots_effect( filter_t
*, picture_t
* );
93 static int vhs_sliding_effect( filter_t
*, picture_t
* );
95 static int vhs_sliding_effect_apply( filter_t
*, picture_t
* );
97 /*****************************************************************************
99 *****************************************************************************/
101 static int Open ( filter_t
* );
102 static void Close( filter_t
* );
105 set_description( N_("VHS movie effect video filter") )
106 set_shortname( N_("VHS movie" ) )
107 set_category( CAT_VIDEO
)
108 set_subcategory( SUBCAT_VIDEO_VFILTER
)
110 set_callback_video_filter( Open
)
116 static int Open( filter_t
*p_filter
)
120 /* Assert video in match with video out */
121 if( !es_format_IsSimilar( &p_filter
->fmt_in
, &p_filter
->fmt_out
) ) {
122 msg_Err( p_filter
, "Input and output format does not match" );
126 /* Reject 0 bpp and unsupported chroma */
127 const vlc_fourcc_t fourcc
= p_filter
->fmt_in
.video
.i_chroma
;
128 const vlc_chroma_description_t
*p_chroma
=
129 vlc_fourcc_GetChromaDescription( p_filter
->fmt_in
.video
.i_chroma
);
130 if( !p_chroma
|| p_chroma
->pixel_size
== 0
131 || p_chroma
->plane_count
< 3 || p_chroma
->pixel_size
> 1
132 || !vlc_fourcc_IsYUV( fourcc
) )
134 msg_Err( p_filter
, "Unsupported chroma (%4.4s)", (char*)&fourcc
);
138 /* Allocate structure */
139 p_filter
->p_sys
= p_sys
= calloc(1, sizeof(*p_sys
) );
140 if( unlikely( !p_sys
) )
144 static const struct vlc_filter_operations filter_ops
=
146 .filter_video
= Filter
, .close
= Close
,
148 p_filter
->ops
= &filter_ops
;
149 p_sys
->i_start_time
= p_sys
->i_cur_time
= p_sys
->i_last_time
= vlc_tick_now();
157 static void Close( filter_t
*p_filter
) {
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
;
228 for ( size_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++) {
229 p_sys
->i_visible_pitch
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_pitch
;
230 p_sys
->i_height
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_lines
;
231 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
;
237 * Free allocated data
239 static void vhs_free_allocated_data( filter_t
*p_filter
) {
240 filter_sys_t
*p_sys
= p_filter
->p_sys
;
242 for ( uint32_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
243 FREENULL( p_sys
->p_BR_lines
[i_b
] );
248 static vlc_tick_t
RandomEnd(filter_sys_t
*p_sys
, vlc_tick_t modulo
)
250 return p_sys
->i_cur_time
+ (uint64_t)vlc_mrand48() % modulo
+ modulo
/ 2;
254 * Horizontal blue or red lines random management and effect
256 static int vhs_blue_red_line_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
257 filter_sys_t
*p_sys
= p_filter
->p_sys
;
259 #define BR_LINES_GENERATOR_PERIOD VLC_TICK_FROM_SEC(50)
260 #define BR_LINES_DURATION VLC_TICK_FROM_MS(20)
262 /* generate new blue or red lines */
263 if ( p_sys
->i_BR_line_trigger
<= p_sys
->i_cur_time
) {
264 for ( size_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
265 if (p_sys
->p_BR_lines
[i_b
] == NULL
) {
267 p_sys
->p_BR_lines
[i_b
] = calloc( 1, sizeof(blue_red_line_t
) );
268 if ( unlikely( !p_sys
->p_BR_lines
[i_b
] ) )
271 /* set random parameters */
272 p_sys
->p_BR_lines
[i_b
]->i_offset
= (unsigned)vlc_mrand48()
273 % __MAX( 1, p_sys
->i_height
[Y_PLANE
] - 10 )
276 p_sys
->p_BR_lines
[i_b
]->b_blue_red
= (unsigned)vlc_mrand48() & 0x01;
278 p_sys
->p_BR_lines
[i_b
]->i_stop_trigger
= RandomEnd( p_sys
, BR_LINES_DURATION
);
282 p_sys
->i_BR_line_trigger
= RandomEnd( p_sys
, BR_LINES_GENERATOR_PERIOD
);
286 /* manage and apply current blue/red lines */
287 for ( size_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
288 if ( p_sys
->p_BR_lines
[i_b
] ) {
289 /* remove outdated ones */
290 if ( p_sys
->p_BR_lines
[i_b
]->i_stop_trigger
<= p_sys
->i_cur_time
) {
291 FREENULL( p_sys
->p_BR_lines
[i_b
] );
295 /* otherwise apply */
296 for ( size_t i_p
=0; i_p
< p_sys
->i_planes
; i_p
++ ) {
297 uint32_t i_pix_ofs
= p_sys
->p_BR_lines
[i_b
]->i_offset
298 * p_pic_out
->p
[i_p
].i_visible_lines
299 / p_sys
->i_height
[Y_PLANE
]
300 * p_pic_out
->p
[i_p
].i_pitch
;
304 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
], 127,
305 p_pic_out
->p
[i_p
].i_visible_pitch
);
308 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
309 (p_sys
->p_BR_lines
[i_b
]->b_blue_red
?255:0),
310 p_pic_out
->p
[i_p
].i_visible_pitch
);
313 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
314 (p_sys
->p_BR_lines
[i_b
]->b_blue_red
?0:255),
315 p_pic_out
->p
[i_p
].i_visible_pitch
);
325 * insert randomly blue and red dots on the picture
327 static void vhs_blue_red_dots_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
328 #define BR_DOTS_RATIO 10000
330 filter_sys_t
*p_sys
= p_filter
->p_sys
;
332 for ( int32_t i_dots
= 0;
333 i_dots
< p_sys
->i_width
[Y_PLANE
] * p_sys
->i_height
[Y_PLANE
] / BR_DOTS_RATIO
;
336 uint32_t i_length
= (unsigned)vlc_mrand48()
337 % __MAX( 1, p_sys
->i_width
[Y_PLANE
] / 30 ) + 1;
339 uint16_t i_x
= (unsigned)vlc_mrand48()
340 % __MAX( 1, p_sys
->i_width
[Y_PLANE
] - i_length
);
341 uint16_t i_y
= (unsigned)vlc_mrand48() % p_sys
->i_height
[Y_PLANE
];
342 bool b_color
= ( ( (unsigned)vlc_mrand48() % 2 ) == 0);
344 for ( size_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++ ) {
345 uint32_t i_pix_ofs
= i_y
346 * p_pic_out
->p
[i_p
].i_visible_lines
347 / p_sys
->i_height
[Y_PLANE
]
348 * p_pic_out
->p
[i_p
].i_pitch
350 * p_pic_out
->p
[i_p
].i_pixel_pitch
;
352 uint32_t i_length_in_plane
= i_length
353 * p_pic_out
->p
[i_p
].i_visible_pitch
354 / p_pic_out
->p
[Y_PLANE
].i_visible_pitch
;
358 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
], 127,
362 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
367 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
379 static int vhs_sliding_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
380 filter_sys_t
*p_sys
= p_filter
->p_sys
;
383 * one shot offset section
386 #define OFFSET_AVERAGE_PERIOD VLC_TICK_FROM_SEC(10)
388 /* start trigger to be (re)initialized */
389 if ( p_sys
->i_offset_trigger
== 0
390 || p_sys
->i_sliding_speed
!= 0 ) { /* do not mix sliding and offset */
392 /* random trigger for offset effect */
393 p_sys
->i_offset_trigger
= RandomEnd( p_sys
, OFFSET_AVERAGE_PERIOD
);
394 p_sys
->i_offset_ofs
= 0;
395 } else if (p_sys
->i_offset_trigger
<= p_sys
->i_cur_time
) {
396 /* trigger for offset effect occurs */
397 p_sys
->i_offset_trigger
= 0;
398 p_sys
->i_offset_ofs
= (uint32_t)vlc_mrand48()
399 % p_sys
->i_height
[Y_PLANE
];
402 p_sys
->i_offset_ofs
= 0;
409 #define MAX_PHASE_OFS (p_sys->i_height[Y_PLANE]*100/15)
411 p_sys
->i_phase_speed
+= MOD( (int32_t)vlc_mrand48(), 3) - 1;
412 p_sys
->i_phase_ofs
+= p_sys
->i_phase_speed
;
413 p_sys
->i_phase_ofs
= VLC_CLIP( p_sys
->i_phase_ofs
, -MAX_PHASE_OFS
, +MAX_PHASE_OFS
);
414 if ( abs( p_sys
->i_phase_ofs
) >= MAX_PHASE_OFS
)
415 p_sys
->i_phase_speed
= 0;
422 #define SLIDING_AVERAGE_PERIOD VLC_TICK_FROM_SEC(20)
423 #define SLIDING_AVERAGE_DURATION VLC_TICK_FROM_SEC(3)
425 /* start trigger to be (re)initialized */
426 if ( ( p_sys
->i_sliding_stop_trig
== 0 ) &&
427 ( p_sys
->i_sliding_trigger
== 0 ) &&
428 ( p_sys
->i_sliding_speed
== 0 ) ) {
430 /* random trigger which enable sliding effect */
431 p_sys
->i_sliding_trigger
= RandomEnd( p_sys
, SLIDING_AVERAGE_PERIOD
);
434 /* start trigger just occurs */
435 else if ( ( p_sys
->i_sliding_stop_trig
== 0 ) &&
436 ( p_sys
->i_sliding_trigger
<= p_sys
->i_cur_time
) &&
437 ( p_sys
->i_sliding_speed
== 0 ) ) {
439 /* init sliding parameters */
440 p_sys
->i_sliding_trigger
= 0;
441 p_sys
->i_sliding_stop_trig
= RandomEnd( p_sys
, SLIDING_AVERAGE_DURATION
);
442 p_sys
->i_sliding_ofs
= 0;
443 /* note: sliding speed unit = image per 100 s */
444 p_sys
->i_sliding_speed
= MOD( (int32_t)vlc_mrand48(), 1001 ) - 500;
445 p_sys
->i_sliding_type_duplicate
= (unsigned)vlc_mrand48() & 0x01;
448 /* stop trigger disabling sliding effect occurs */
449 else if ( ( p_sys
->i_sliding_stop_trig
<= p_sys
->i_cur_time
)
450 && ( p_sys
->i_sliding_trigger
== 0 ) ) {
452 /* first increase speed to ensure we will stop sliding on plain pict */
453 if ( abs( p_sys
->i_sliding_speed
) < 5 )
454 p_sys
->i_sliding_speed
+= 1;
456 int threshold
= p_sys
->i_sliding_speed
457 * p_sys
->i_height
[Y_PLANE
]
458 * SEC_FROM_VLC_TICK( p_sys
->i_cur_time
- p_sys
->i_last_time
);
460 /* check if offset is close to 0 and then ready to stop */
461 if ( abs( p_sys
->i_sliding_ofs
) < abs( threshold
)
462 || abs( p_sys
->i_sliding_ofs
) < p_sys
->i_height
[Y_PLANE
] * 100 / 20 ) {
464 /* reset sliding parameters */
465 p_sys
->i_sliding_ofs
= p_sys
->i_sliding_speed
= 0;
466 p_sys
->i_sliding_trigger
= p_sys
->i_sliding_stop_trig
= 0;
467 p_sys
->i_sliding_type_duplicate
= false;
472 p_sys
->i_sliding_ofs
= MOD( p_sys
->i_sliding_ofs
473 + p_sys
->i_sliding_speed
* p_sys
->i_height
[Y_PLANE
]
474 * SEC_FROM_VLC_TICK( p_sys
->i_cur_time
- p_sys
->i_last_time
),
475 p_sys
->i_height
[Y_PLANE
] * 100 );
477 return vhs_sliding_effect_apply( p_filter
, p_pic_out
);
481 * apply both sliding and offset effect
483 static int vhs_sliding_effect_apply( filter_t
*p_filter
, picture_t
*p_pic_out
)
485 filter_sys_t
*p_sys
= p_filter
->p_sys
;
487 for ( size_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++ ) {
488 /* first allocate temporary buffer for swap operation */
490 if ( !p_sys
->i_sliding_type_duplicate
) {
491 p_temp_buf
= calloc( p_pic_out
->p
[i_p
].i_lines
492 * p_pic_out
->p
[i_p
].i_pitch
, sizeof(uint8_t) );
493 if ( unlikely( !p_temp_buf
) )
495 memcpy( p_temp_buf
, p_pic_out
->p
[i_p
].p_pixels
,
496 p_pic_out
->p
[i_p
].i_lines
* p_pic_out
->p
[i_p
].i_pitch
);
499 p_temp_buf
= p_pic_out
->p
[i_p
].p_pixels
;
501 /* copy lines to output_pic */
502 size_t lines
= p_pic_out
->p
[i_p
].i_visible_lines
;
503 for ( size_t i_y
= 0; i_y
< lines
; i_y
++ )
505 int i_ofs
= p_sys
->i_offset_ofs
+ p_sys
->i_sliding_ofs
;
507 if ( ( p_sys
->i_sliding_speed
== 0 ) || !p_sys
->i_sliding_type_duplicate
)
508 i_ofs
+= p_sys
->i_phase_ofs
;
510 i_ofs
= MOD( i_ofs
/ 100, p_sys
->i_height
[Y_PLANE
] );
511 i_ofs
*= p_pic_out
->p
[i_p
].i_visible_lines
;
512 i_ofs
/= p_sys
->i_height
[Y_PLANE
];
514 memcpy( &p_pic_out
->p
[i_p
].p_pixels
[ i_y
* p_pic_out
->p
[i_p
].i_pitch
],
515 &p_temp_buf
[ ( ( i_y
+ i_ofs
) % p_pic_out
->p
[i_p
].i_visible_lines
) * p_pic_out
->p
[i_p
].i_pitch
],
516 p_pic_out
->p
[i_p
].i_visible_pitch
);
518 if ( !p_sys
->i_sliding_type_duplicate
)