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>
37 #include <vlc_mtime.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 mtime_t i_stop_trigger
;
58 int32_t *i_height
; /* note: each plane may have different dimensions */
60 int32_t *i_visible_pitch
;
65 /* sliding & offset effect */
66 int32_t i_phase_speed
;
69 int32_t i_sliding_ofs
;
70 int32_t i_sliding_speed
;
71 mtime_t i_offset_trigger
;
72 mtime_t i_sliding_trigger
;
73 mtime_t i_sliding_stop_trig
;
74 bool i_sliding_type_duplicate
;
76 /* blue red lines effect */
77 mtime_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 ( vlc_object_t
* );
102 static void Close( vlc_object_t
* );
105 set_description( N_("VHS movie effect video filter") )
106 set_shortname( N_("VHS movie" ) )
107 set_capability( "video filter", 0 )
108 set_category( CAT_VIDEO
)
109 set_subcategory( SUBCAT_VIDEO_VFILTER
)
111 set_callbacks( Open
, Close
)
117 static int Open( vlc_object_t
*p_this
)
119 filter_t
*p_filter
= (filter_t
*)p_this
;
122 /* Assert video in match with video out */
123 if( !es_format_IsSimilar( &p_filter
->fmt_in
, &p_filter
->fmt_out
) ) {
124 msg_Err( p_filter
, "Input and output format does not match" );
128 /* Reject 0 bpp and unsupported chroma */
129 const vlc_fourcc_t fourcc
= p_filter
->fmt_in
.video
.i_chroma
;
130 const vlc_chroma_description_t
*p_chroma
=
131 vlc_fourcc_GetChromaDescription( p_filter
->fmt_in
.video
.i_chroma
);
132 if( !p_chroma
|| p_chroma
->pixel_size
== 0
133 || p_chroma
->plane_count
< 3 || p_chroma
->pixel_size
> 1
134 || !vlc_fourcc_IsYUV( fourcc
) )
136 msg_Err( p_filter
, "Unsupported chroma (%4.4s)", (char*)&fourcc
);
140 /* Allocate structure */
141 p_filter
->p_sys
= p_sys
= calloc(1, sizeof(*p_sys
) );
142 if( unlikely( !p_sys
) )
146 p_filter
->pf_video_filter
= Filter
;
147 p_sys
->i_start_time
= p_sys
->i_cur_time
= p_sys
->i_last_time
= mdate();
155 static void Close( vlc_object_t
*p_this
) {
156 filter_t
*p_filter
= (filter_t
*)p_this
;
157 filter_sys_t
*p_sys
= p_filter
->p_sys
;
159 /* Free allocated memory */
160 vhs_free_allocated_data( p_filter
);
167 static picture_t
*Filter( filter_t
*p_filter
, picture_t
*p_pic_in
) {
168 if( unlikely( !p_pic_in
|| !p_filter
) )
171 filter_sys_t
*p_sys
= p_filter
->p_sys
;
173 picture_t
*p_pic_out
= filter_NewPicture( p_filter
);
174 if( unlikely( !p_pic_out
) ) {
175 picture_Release( p_pic_in
);
182 p_sys
->i_last_time
= p_sys
->i_cur_time
;
183 p_sys
->i_cur_time
= mdate();
188 if ( unlikely( !p_sys
->b_init
) )
189 if ( unlikely( vhs_allocate_data( p_filter
, p_pic_in
) != VLC_SUCCESS
) ) {
190 picture_Release( p_pic_in
);
193 p_sys
->b_init
= true;
196 * preset output pic: raw copy src to dst
198 picture_CopyPixels(p_pic_out
, p_pic_in
);
201 * apply effects on picture
203 if ( unlikely( vhs_blue_red_line_effect( p_filter
, p_pic_out
) != VLC_SUCCESS
) )
204 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
206 if ( unlikely( vhs_sliding_effect(p_filter
, p_pic_out
) != VLC_SUCCESS
) )
207 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
209 vhs_blue_red_dots_effect( p_filter
, p_pic_out
);
211 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
217 static int vhs_allocate_data( filter_t
*p_filter
, picture_t
*p_pic_in
) {
218 filter_sys_t
*p_sys
= p_filter
->p_sys
;
220 vhs_free_allocated_data( p_filter
);
223 * take into account different characteristics for each plane
225 p_sys
->i_planes
= p_pic_in
->i_planes
;
226 p_sys
->i_height
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
227 p_sys
->i_width
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
228 p_sys
->i_visible_pitch
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
230 if( unlikely( !p_sys
->i_height
|| !p_sys
->i_width
|| !p_sys
->i_visible_pitch
) ) {
231 vhs_free_allocated_data( p_filter
);
235 for ( int32_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++) {
236 p_sys
->i_visible_pitch
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_pitch
;
237 p_sys
->i_height
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_lines
;
238 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
;
244 * Free allocated data
246 static void vhs_free_allocated_data( filter_t
*p_filter
) {
247 filter_sys_t
*p_sys
= p_filter
->p_sys
;
249 for ( uint32_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
250 FREENULL( p_sys
->p_BR_lines
[i_b
] );
253 FREENULL( p_sys
->i_height
);
254 FREENULL( p_sys
->i_width
);
255 FREENULL( p_sys
->i_visible_pitch
);
260 * Horizontal blue or red lines random management and effect
262 static int vhs_blue_red_line_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
263 filter_sys_t
*p_sys
= p_filter
->p_sys
;
265 #define BR_LINES_GENERATOR_PERIOD ( CLOCK_FREQ * 50 )
266 #define BR_LINES_DURATION ( CLOCK_FREQ * 1/50 )
268 /* generate new blue or red lines */
269 if ( p_sys
->i_BR_line_trigger
<= p_sys
->i_cur_time
) {
270 for ( uint32_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
271 if (p_sys
->p_BR_lines
[i_b
] == NULL
) {
273 p_sys
->p_BR_lines
[i_b
] = calloc( 1, sizeof(blue_red_line_t
) );
274 if ( unlikely( !p_sys
->p_BR_lines
[i_b
] ) )
277 /* set random parameters */
278 p_sys
->p_BR_lines
[i_b
]->i_offset
= (unsigned)vlc_mrand48()
279 % __MAX( 1, p_sys
->i_height
[Y_PLANE
] - 10 )
282 p_sys
->p_BR_lines
[i_b
]->b_blue_red
= (unsigned)vlc_mrand48() & 0x01;
284 p_sys
->p_BR_lines
[i_b
]->i_stop_trigger
= p_sys
->i_cur_time
285 + (uint64_t)vlc_mrand48() % BR_LINES_DURATION
286 + BR_LINES_DURATION
/ 2;
290 p_sys
->i_BR_line_trigger
= p_sys
->i_cur_time
291 + (uint64_t)vlc_mrand48() % BR_LINES_GENERATOR_PERIOD
292 + BR_LINES_GENERATOR_PERIOD
/ 2;
296 /* manage and apply current blue/red lines */
297 for ( uint8_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
298 if ( p_sys
->p_BR_lines
[i_b
] ) {
299 /* remove outdated ones */
300 if ( p_sys
->p_BR_lines
[i_b
]->i_stop_trigger
<= p_sys
->i_cur_time
) {
301 FREENULL( p_sys
->p_BR_lines
[i_b
] );
305 /* otherwise apply */
306 for ( int32_t i_p
=0; i_p
< p_sys
->i_planes
; i_p
++ ) {
307 uint32_t i_pix_ofs
= p_sys
->p_BR_lines
[i_b
]->i_offset
308 * p_pic_out
->p
[i_p
].i_visible_lines
309 / p_sys
->i_height
[Y_PLANE
]
310 * p_pic_out
->p
[i_p
].i_pitch
;
314 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
], 127,
315 p_pic_out
->p
[i_p
].i_visible_pitch
);
318 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
319 (p_sys
->p_BR_lines
[i_b
]->b_blue_red
?255:0),
320 p_pic_out
->p
[i_p
].i_visible_pitch
);
323 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
324 (p_sys
->p_BR_lines
[i_b
]->b_blue_red
?0:255),
325 p_pic_out
->p
[i_p
].i_visible_pitch
);
335 * insert randomly blue and red dots on the picture
337 static void vhs_blue_red_dots_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
338 #define BR_DOTS_RATIO 10000
340 filter_sys_t
*p_sys
= p_filter
->p_sys
;
342 for ( int32_t i_dots
= 0;
343 i_dots
< p_sys
->i_width
[Y_PLANE
] * p_sys
->i_height
[Y_PLANE
] / BR_DOTS_RATIO
;
346 uint32_t i_length
= (unsigned)vlc_mrand48()
347 % __MAX( 1, p_sys
->i_width
[Y_PLANE
] / 30 ) + 1;
349 uint16_t i_x
= (unsigned)vlc_mrand48()
350 % __MAX( 1, p_sys
->i_width
[Y_PLANE
] - i_length
);
351 uint16_t i_y
= (unsigned)vlc_mrand48() % p_sys
->i_height
[Y_PLANE
];
352 bool b_color
= ( ( (unsigned)vlc_mrand48() % 2 ) == 0);
354 for ( int32_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++ ) {
355 uint32_t i_pix_ofs
= i_y
356 * p_pic_out
->p
[i_p
].i_visible_lines
357 / p_sys
->i_height
[Y_PLANE
]
358 * p_pic_out
->p
[i_p
].i_pitch
360 * p_pic_out
->p
[i_p
].i_pixel_pitch
;
362 uint32_t i_length_in_plane
= i_length
363 * p_pic_out
->p
[i_p
].i_visible_pitch
364 / p_pic_out
->p
[Y_PLANE
].i_visible_pitch
;
368 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
], 127,
372 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
377 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
389 static int vhs_sliding_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
390 filter_sys_t
*p_sys
= p_filter
->p_sys
;
393 * one shot offset section
396 #define OFFSET_AVERAGE_PERIOD (10 * CLOCK_FREQ)
398 /* start trigger to be (re)initialized */
399 if ( p_sys
->i_offset_trigger
== 0
400 || p_sys
->i_sliding_speed
!= 0 ) { /* do not mix sliding and offset */
402 /* random trigger for offset effect */
403 p_sys
->i_offset_trigger
= p_sys
->i_cur_time
404 + ((uint64_t) vlc_mrand48() ) % OFFSET_AVERAGE_PERIOD
405 + OFFSET_AVERAGE_PERIOD
/ 2;
406 p_sys
->i_offset_ofs
= 0;
407 } else if (p_sys
->i_offset_trigger
<= p_sys
->i_cur_time
) {
408 /* trigger for offset effect occurs */
409 p_sys
->i_offset_trigger
= 0;
410 p_sys
->i_offset_ofs
= (uint32_t)vlc_mrand48()
411 % p_sys
->i_height
[Y_PLANE
];
414 p_sys
->i_offset_ofs
= 0;
421 #define MAX_PHASE_OFS (p_sys->i_height[Y_PLANE]*100/15)
423 p_sys
->i_phase_speed
+= MOD( (int32_t)vlc_mrand48(), 3) - 1;
424 p_sys
->i_phase_ofs
+= p_sys
->i_phase_speed
;
425 p_sys
->i_phase_ofs
= VLC_CLIP( p_sys
->i_phase_ofs
, -MAX_PHASE_OFS
, +MAX_PHASE_OFS
);
426 if ( abs( p_sys
->i_phase_ofs
) >= MAX_PHASE_OFS
)
427 p_sys
->i_phase_speed
= 0;
434 #define SLIDING_AVERAGE_PERIOD (20 * CLOCK_FREQ)
435 #define SLIDING_AVERAGE_DURATION ( 3 * CLOCK_FREQ)
437 /* start trigger to be (re)initialized */
438 if ( ( p_sys
->i_sliding_stop_trig
== 0 ) &&
439 ( p_sys
->i_sliding_trigger
== 0 ) &&
440 ( p_sys
->i_sliding_speed
== 0 ) ) {
442 /* random trigger which enable sliding effect */
443 p_sys
->i_sliding_trigger
= p_sys
->i_cur_time
444 + (uint64_t)vlc_mrand48() % SLIDING_AVERAGE_PERIOD
445 + SLIDING_AVERAGE_PERIOD
/ 2;
448 /* start trigger just occurs */
449 else if ( ( p_sys
->i_sliding_stop_trig
== 0 ) &&
450 ( p_sys
->i_sliding_trigger
<= p_sys
->i_cur_time
) &&
451 ( p_sys
->i_sliding_speed
== 0 ) ) {
453 /* init sliding parameters */
454 p_sys
->i_sliding_trigger
= 0;
455 p_sys
->i_sliding_stop_trig
= p_sys
->i_cur_time
456 + (uint64_t)vlc_mrand48() % SLIDING_AVERAGE_DURATION
457 + SLIDING_AVERAGE_DURATION
/ 2;
458 p_sys
->i_sliding_ofs
= 0;
459 /* note: sliding speed unit = image per 100 s */
460 p_sys
->i_sliding_speed
= MOD( (int32_t)vlc_mrand48(), 1001 ) - 500;
461 p_sys
->i_sliding_type_duplicate
= (unsigned)vlc_mrand48() & 0x01;
464 /* stop trigger disabling sliding effect occurs */
465 else if ( ( p_sys
->i_sliding_stop_trig
<= p_sys
->i_cur_time
)
466 && ( p_sys
->i_sliding_trigger
== 0 ) ) {
468 /* first increase speed to ensure we will stop sliding on plain pict */
469 if ( abs( p_sys
->i_sliding_speed
) < 5 )
470 p_sys
->i_sliding_speed
+= 1;
472 /* check if offset is close to 0 and then ready to stop */
473 if ( abs( p_sys
->i_sliding_ofs
) < abs( p_sys
->i_sliding_speed
474 * p_sys
->i_height
[Y_PLANE
]
475 * ( p_sys
->i_cur_time
- p_sys
->i_last_time
) / CLOCK_FREQ
)
476 || abs( p_sys
->i_sliding_ofs
) < p_sys
->i_height
[Y_PLANE
] * 100 / 20 ) {
478 /* reset sliding parameters */
479 p_sys
->i_sliding_ofs
= p_sys
->i_sliding_speed
= 0;
480 p_sys
->i_sliding_trigger
= p_sys
->i_sliding_stop_trig
= 0;
481 p_sys
->i_sliding_type_duplicate
= false;
486 p_sys
->i_sliding_ofs
= MOD( p_sys
->i_sliding_ofs
487 + p_sys
->i_sliding_speed
* p_sys
->i_height
[Y_PLANE
]
488 * ( p_sys
->i_cur_time
- p_sys
->i_last_time
)
490 p_sys
->i_height
[Y_PLANE
] * 100 );
492 return vhs_sliding_effect_apply( p_filter
, p_pic_out
);
496 * apply both sliding and offset effect
498 static int vhs_sliding_effect_apply( filter_t
*p_filter
, picture_t
*p_pic_out
)
500 filter_sys_t
*p_sys
= p_filter
->p_sys
;
502 for ( uint8_t i_p
= 0; i_p
< p_pic_out
->i_planes
; i_p
++ ) {
503 /* first allocate temporary buffer for swap operation */
505 if ( !p_sys
->i_sliding_type_duplicate
) {
506 p_temp_buf
= calloc( p_pic_out
->p
[i_p
].i_lines
507 * p_pic_out
->p
[i_p
].i_pitch
, sizeof(uint8_t) );
508 if ( unlikely( !p_temp_buf
) )
510 memcpy( p_temp_buf
, p_pic_out
->p
[i_p
].p_pixels
,
511 p_pic_out
->p
[i_p
].i_lines
* p_pic_out
->p
[i_p
].i_pitch
);
514 p_temp_buf
= p_pic_out
->p
[i_p
].p_pixels
;
516 /* copy lines to output_pic */
517 for ( int32_t i_y
= 0; i_y
< p_pic_out
->p
[i_p
].i_visible_lines
; i_y
++ )
519 int32_t i_ofs
= p_sys
->i_offset_ofs
+ p_sys
->i_sliding_ofs
;
521 if ( ( p_sys
->i_sliding_speed
== 0 ) || !p_sys
->i_sliding_type_duplicate
)
522 i_ofs
+= p_sys
->i_phase_ofs
;
524 i_ofs
= MOD( i_ofs
/ 100, p_sys
->i_height
[Y_PLANE
] );
525 i_ofs
*= p_pic_out
->p
[i_p
].i_visible_lines
;
526 i_ofs
/= p_sys
->i_height
[Y_PLANE
];
528 memcpy( &p_pic_out
->p
[i_p
].p_pixels
[ i_y
* p_pic_out
->p
[i_p
].i_pitch
],
529 &p_temp_buf
[ ( ( i_y
+ i_ofs
) % p_pic_out
->p
[i_p
].i_visible_lines
) * p_pic_out
->p
[i_p
].i_pitch
],
530 p_pic_out
->p
[i_p
].i_visible_pitch
);
532 if ( !p_sys
->i_sliding_type_duplicate
)