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>
36 #include <vlc_mtime.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 mtime_t i_stop_trigger
;
57 int32_t *i_height
; /* note: each plane may have different dimensions */
59 int32_t *i_visible_pitch
;
64 /* sliding & offset effect */
65 int32_t i_phase_speed
;
68 int32_t i_sliding_ofs
;
69 int32_t i_sliding_speed
;
70 mtime_t i_offset_trigger
;
71 mtime_t i_sliding_trigger
;
72 mtime_t i_sliding_stop_trig
;
73 bool i_sliding_type_duplicate
;
75 /* blue red lines effect */
76 mtime_t i_BR_line_trigger
;
77 blue_red_line_t
*p_BR_lines
[MAX_BLUE_RED_LINES
];
81 /*****************************************************************************
83 *****************************************************************************/
85 static picture_t
*Filter( filter_t
*, picture_t
* );
87 static int vhs_allocate_data( filter_t
*, picture_t
* );
88 static void vhs_free_allocated_data( filter_t
* );
90 static int vhs_blue_red_line_effect( filter_t
*, picture_t
* );
91 static void vhs_blue_red_dots_effect( filter_t
*, picture_t
* );
92 static int vhs_sliding_effect( filter_t
*, picture_t
* );
94 static int vhs_sliding_effect_apply( filter_t
*, picture_t
* );
96 /*****************************************************************************
98 *****************************************************************************/
100 static int Open ( vlc_object_t
* );
101 static void Close( vlc_object_t
* );
104 set_description( N_("VHS movie effect video filter") )
105 set_shortname( N_("VHS movie" ) )
106 set_capability( "video filter", 0 )
107 set_category( CAT_VIDEO
)
108 set_subcategory( SUBCAT_VIDEO_VFILTER
)
110 set_callbacks( Open
, Close
)
116 static int Open( vlc_object_t
*p_this
)
118 filter_t
*p_filter
= (filter_t
*)p_this
;
121 /* Assert video in match with video out */
122 if( !es_format_IsSimilar( &p_filter
->fmt_in
, &p_filter
->fmt_out
) ) {
123 msg_Err( p_filter
, "Input and output format does not match" );
127 /* Reject 0 bpp and unsupported chroma */
128 const vlc_fourcc_t fourcc
= p_filter
->fmt_in
.video
.i_chroma
;
129 const vlc_chroma_description_t
*p_chroma
=
130 vlc_fourcc_GetChromaDescription( p_filter
->fmt_in
.video
.i_chroma
);
131 if( !p_chroma
|| p_chroma
->pixel_size
== 0
132 || p_chroma
->plane_count
< 3 || p_chroma
->pixel_size
> 1
133 || !vlc_fourcc_IsYUV( fourcc
) )
135 msg_Err( p_filter
, "Unsupported chroma (%4.4s)", (char*)&fourcc
);
139 /* Allocate structure */
140 p_filter
->p_sys
= p_sys
= calloc(1, sizeof(*p_sys
) );
141 if( unlikely( !p_sys
) )
145 p_filter
->pf_video_filter
= Filter
;
146 p_sys
->i_start_time
= p_sys
->i_cur_time
= p_sys
->i_last_time
= mdate();
154 static void Close( vlc_object_t
*p_this
) {
155 filter_t
*p_filter
= (filter_t
*)p_this
;
156 filter_sys_t
*p_sys
= p_filter
->p_sys
;
158 /* Free allocated memory */
159 vhs_free_allocated_data( p_filter
);
166 static picture_t
*Filter( filter_t
*p_filter
, picture_t
*p_pic_in
) {
167 if( unlikely( !p_pic_in
|| !p_filter
) )
170 filter_sys_t
*p_sys
= p_filter
->p_sys
;
172 picture_t
*p_pic_out
= filter_NewPicture( p_filter
);
173 if( unlikely( !p_pic_out
) ) {
174 picture_Release( p_pic_in
);
181 p_sys
->i_last_time
= p_sys
->i_cur_time
;
182 p_sys
->i_cur_time
= mdate();
187 if ( unlikely( !p_sys
->b_init
) )
188 if ( unlikely( vhs_allocate_data( p_filter
, p_pic_in
) != VLC_SUCCESS
) ) {
189 picture_Release( p_pic_in
);
192 p_sys
->b_init
= true;
195 * preset output pic: raw copy src to dst
197 picture_CopyPixels(p_pic_out
, p_pic_in
);
200 * apply effects on picture
202 if ( unlikely( vhs_blue_red_line_effect( p_filter
, p_pic_out
) != VLC_SUCCESS
) )
203 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
205 if ( unlikely( vhs_sliding_effect(p_filter
, p_pic_out
) != VLC_SUCCESS
) )
206 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
208 vhs_blue_red_dots_effect( p_filter
, p_pic_out
);
210 return CopyInfoAndRelease( p_pic_out
, p_pic_in
);
216 static int vhs_allocate_data( filter_t
*p_filter
, picture_t
*p_pic_in
) {
217 filter_sys_t
*p_sys
= p_filter
->p_sys
;
219 vhs_free_allocated_data( p_filter
);
222 * take into account different characteristics for each plane
224 p_sys
->i_planes
= p_pic_in
->i_planes
;
225 p_sys
->i_height
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
226 p_sys
->i_width
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
227 p_sys
->i_visible_pitch
= calloc( p_sys
->i_planes
, sizeof(int32_t) );
229 if( unlikely( !p_sys
->i_height
|| !p_sys
->i_width
|| !p_sys
->i_visible_pitch
) ) {
230 vhs_free_allocated_data( p_filter
);
234 for ( int32_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++) {
235 p_sys
->i_visible_pitch
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_pitch
;
236 p_sys
->i_height
[i_p
] = (int) p_pic_in
->p
[i_p
].i_visible_lines
;
237 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
;
243 * Free allocated data
245 static void vhs_free_allocated_data( filter_t
*p_filter
) {
246 filter_sys_t
*p_sys
= p_filter
->p_sys
;
248 for ( uint32_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
249 FREENULL( p_sys
->p_BR_lines
[i_b
] );
252 FREENULL( p_sys
->i_height
);
253 FREENULL( p_sys
->i_width
);
254 FREENULL( p_sys
->i_visible_pitch
);
259 * Horizontal blue or red lines random management and effect
261 static int vhs_blue_red_line_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
262 filter_sys_t
*p_sys
= p_filter
->p_sys
;
264 #define BR_LINES_GENERATOR_PERIOD ( CLOCK_FREQ * 50 )
265 #define BR_LINES_DURATION ( CLOCK_FREQ * 1/50 )
267 /* generate new blue or red lines */
268 if ( p_sys
->i_BR_line_trigger
<= p_sys
->i_cur_time
) {
269 for ( uint32_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
270 if (p_sys
->p_BR_lines
[i_b
] == NULL
) {
272 p_sys
->p_BR_lines
[i_b
] = calloc( 1, sizeof(blue_red_line_t
) );
273 if ( unlikely( !p_sys
->p_BR_lines
[i_b
] ) )
276 /* set random parameters */
277 p_sys
->p_BR_lines
[i_b
]->i_offset
= (unsigned)vlc_mrand48()
278 % __MAX( 1, p_sys
->i_height
[Y_PLANE
] - 10 )
281 p_sys
->p_BR_lines
[i_b
]->b_blue_red
= (unsigned)vlc_mrand48() & 0x01;
283 p_sys
->p_BR_lines
[i_b
]->i_stop_trigger
= p_sys
->i_cur_time
284 + (uint64_t)vlc_mrand48() % BR_LINES_DURATION
285 + BR_LINES_DURATION
/ 2;
289 p_sys
->i_BR_line_trigger
= p_sys
->i_cur_time
290 + (uint64_t)vlc_mrand48() % BR_LINES_GENERATOR_PERIOD
291 + BR_LINES_GENERATOR_PERIOD
/ 2;
295 /* manage and apply current blue/red lines */
296 for ( uint8_t i_b
= 0; i_b
< MAX_BLUE_RED_LINES
; i_b
++ )
297 if ( p_sys
->p_BR_lines
[i_b
] ) {
298 /* remove outdated ones */
299 if ( p_sys
->p_BR_lines
[i_b
]->i_stop_trigger
<= p_sys
->i_cur_time
) {
300 FREENULL( p_sys
->p_BR_lines
[i_b
] );
304 /* otherwise apply */
305 for ( int32_t i_p
=0; i_p
< p_sys
->i_planes
; i_p
++ ) {
306 uint32_t i_pix_ofs
= p_sys
->p_BR_lines
[i_b
]->i_offset
307 * p_pic_out
->p
[i_p
].i_visible_lines
308 / p_sys
->i_height
[Y_PLANE
]
309 * p_pic_out
->p
[i_p
].i_pitch
;
313 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
], 127,
314 p_pic_out
->p
[i_p
].i_visible_pitch
);
317 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
318 (p_sys
->p_BR_lines
[i_b
]->b_blue_red
?255:0),
319 p_pic_out
->p
[i_p
].i_visible_pitch
);
322 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
323 (p_sys
->p_BR_lines
[i_b
]->b_blue_red
?0:255),
324 p_pic_out
->p
[i_p
].i_visible_pitch
);
334 * insert randomly blue and red dots on the picture
336 static void vhs_blue_red_dots_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
337 #define BR_DOTS_RATIO 10000
339 filter_sys_t
*p_sys
= p_filter
->p_sys
;
341 for ( int32_t i_dots
= 0;
342 i_dots
< p_sys
->i_width
[Y_PLANE
] * p_sys
->i_height
[Y_PLANE
] / BR_DOTS_RATIO
;
345 uint32_t i_length
= (unsigned)vlc_mrand48()
346 % __MAX( 1, p_sys
->i_width
[Y_PLANE
] / 30 ) + 1;
348 uint16_t i_x
= (unsigned)vlc_mrand48()
349 % __MAX( 1, p_sys
->i_width
[Y_PLANE
] - i_length
);
350 uint16_t i_y
= (unsigned)vlc_mrand48() % p_sys
->i_height
[Y_PLANE
];
351 bool b_color
= ( ( (unsigned)vlc_mrand48() % 2 ) == 0);
353 for ( int32_t i_p
= 0; i_p
< p_sys
->i_planes
; i_p
++ ) {
354 uint32_t i_pix_ofs
= i_y
355 * p_pic_out
->p
[i_p
].i_visible_lines
356 / p_sys
->i_height
[Y_PLANE
]
357 * p_pic_out
->p
[i_p
].i_pitch
359 * p_pic_out
->p
[i_p
].i_pixel_pitch
;
361 uint32_t i_length_in_plane
= i_length
362 * p_pic_out
->p
[i_p
].i_visible_pitch
363 / p_pic_out
->p
[Y_PLANE
].i_visible_pitch
;
367 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
], 127,
371 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
376 memset( &p_pic_out
->p
[i_p
].p_pixels
[i_pix_ofs
],
388 static int vhs_sliding_effect( filter_t
*p_filter
, picture_t
*p_pic_out
) {
389 filter_sys_t
*p_sys
= p_filter
->p_sys
;
392 * one shot offset section
395 #define OFFSET_AVERAGE_PERIOD (10 * CLOCK_FREQ)
397 /* start trigger to be (re)initialized */
398 if ( p_sys
->i_offset_trigger
== 0
399 || p_sys
->i_sliding_speed
!= 0 ) { /* do not mix sliding and offset */
401 /* random trigger for offset effect */
402 p_sys
->i_offset_trigger
= p_sys
->i_cur_time
403 + ((uint64_t) vlc_mrand48() ) % OFFSET_AVERAGE_PERIOD
404 + OFFSET_AVERAGE_PERIOD
/ 2;
405 p_sys
->i_offset_ofs
= 0;
406 } else if (p_sys
->i_offset_trigger
<= p_sys
->i_cur_time
) {
407 /* trigger for offset effect occurs */
408 p_sys
->i_offset_trigger
= 0;
409 p_sys
->i_offset_ofs
= (uint32_t)vlc_mrand48()
410 % p_sys
->i_height
[Y_PLANE
];
413 p_sys
->i_offset_ofs
= 0;
420 #define MAX_PHASE_OFS (p_sys->i_height[Y_PLANE]*100/15)
422 p_sys
->i_phase_speed
+= MOD( (int32_t)vlc_mrand48(), 3) - 1;
423 p_sys
->i_phase_ofs
+= p_sys
->i_phase_speed
;
424 p_sys
->i_phase_ofs
= VLC_CLIP( p_sys
->i_phase_ofs
, -MAX_PHASE_OFS
, +MAX_PHASE_OFS
);
425 if ( abs( p_sys
->i_phase_ofs
) >= MAX_PHASE_OFS
)
426 p_sys
->i_phase_speed
= 0;
433 #define SLIDING_AVERAGE_PERIOD (20 * CLOCK_FREQ)
434 #define SLIDING_AVERAGE_DURATION ( 3 * CLOCK_FREQ)
436 /* start trigger to be (re)initialized */
437 if ( ( p_sys
->i_sliding_stop_trig
== 0 ) &&
438 ( p_sys
->i_sliding_trigger
== 0 ) &&
439 ( p_sys
->i_sliding_speed
== 0 ) ) {
441 /* random trigger which enable sliding effect */
442 p_sys
->i_sliding_trigger
= p_sys
->i_cur_time
443 + (uint64_t)vlc_mrand48() % SLIDING_AVERAGE_PERIOD
444 + SLIDING_AVERAGE_PERIOD
/ 2;
447 /* start trigger just occurs */
448 else if ( ( p_sys
->i_sliding_stop_trig
== 0 ) &&
449 ( p_sys
->i_sliding_trigger
<= p_sys
->i_cur_time
) &&
450 ( p_sys
->i_sliding_speed
== 0 ) ) {
452 /* init sliding parameters */
453 p_sys
->i_sliding_trigger
= 0;
454 p_sys
->i_sliding_stop_trig
= p_sys
->i_cur_time
455 + (uint64_t)vlc_mrand48() % SLIDING_AVERAGE_DURATION
456 + SLIDING_AVERAGE_DURATION
/ 2;
457 p_sys
->i_sliding_ofs
= 0;
458 /* note: sliding speed unit = image per 100 s */
459 p_sys
->i_sliding_speed
= MOD( (int32_t)vlc_mrand48(), 1001 ) - 500;
460 p_sys
->i_sliding_type_duplicate
= (unsigned)vlc_mrand48() & 0x01;
463 /* stop trigger disabling sliding effect occurs */
464 else if ( ( p_sys
->i_sliding_stop_trig
<= p_sys
->i_cur_time
)
465 && ( p_sys
->i_sliding_trigger
== 0 ) ) {
467 /* first increase speed to ensure we will stop sliding on plain pict */
468 if ( abs( p_sys
->i_sliding_speed
) < 5 )
469 p_sys
->i_sliding_speed
+= 1;
471 /* check if offset is close to 0 and then ready to stop */
472 if ( abs( p_sys
->i_sliding_ofs
) < abs( p_sys
->i_sliding_speed
473 * p_sys
->i_height
[Y_PLANE
]
474 * ( p_sys
->i_cur_time
- p_sys
->i_last_time
) / CLOCK_FREQ
)
475 || abs( p_sys
->i_sliding_ofs
) < p_sys
->i_height
[Y_PLANE
] * 100 / 20 ) {
477 /* reset sliding parameters */
478 p_sys
->i_sliding_ofs
= p_sys
->i_sliding_speed
= 0;
479 p_sys
->i_sliding_trigger
= p_sys
->i_sliding_stop_trig
= 0;
480 p_sys
->i_sliding_type_duplicate
= false;
485 p_sys
->i_sliding_ofs
= MOD( p_sys
->i_sliding_ofs
486 + p_sys
->i_sliding_speed
* p_sys
->i_height
[Y_PLANE
]
487 * ( p_sys
->i_cur_time
- p_sys
->i_last_time
)
489 p_sys
->i_height
[Y_PLANE
] * 100 );
491 return vhs_sliding_effect_apply( p_filter
, p_pic_out
);
495 * apply both sliding and offset effect
497 static int vhs_sliding_effect_apply( filter_t
*p_filter
, picture_t
*p_pic_out
)
499 filter_sys_t
*p_sys
= p_filter
->p_sys
;
501 for ( uint8_t i_p
= 0; i_p
< p_pic_out
->i_planes
; i_p
++ ) {
502 /* first allocate temporary buffer for swap operation */
504 if ( !p_sys
->i_sliding_type_duplicate
) {
505 p_temp_buf
= calloc( p_pic_out
->p
[i_p
].i_lines
506 * p_pic_out
->p
[i_p
].i_pitch
, sizeof(uint8_t) );
507 if ( unlikely( !p_temp_buf
) )
509 memcpy( p_temp_buf
, p_pic_out
->p
[i_p
].p_pixels
,
510 p_pic_out
->p
[i_p
].i_lines
* p_pic_out
->p
[i_p
].i_pitch
);
513 p_temp_buf
= p_pic_out
->p
[i_p
].p_pixels
;
515 /* copy lines to output_pic */
516 for ( int32_t i_y
= 0; i_y
< p_pic_out
->p
[i_p
].i_visible_lines
; i_y
++ )
518 int32_t i_ofs
= p_sys
->i_offset_ofs
+ p_sys
->i_sliding_ofs
;
520 if ( ( p_sys
->i_sliding_speed
== 0 ) || !p_sys
->i_sliding_type_duplicate
)
521 i_ofs
+= p_sys
->i_phase_ofs
;
523 i_ofs
= MOD( i_ofs
/ 100, p_sys
->i_height
[Y_PLANE
] );
524 i_ofs
*= p_pic_out
->p
[i_p
].i_visible_lines
;
525 i_ofs
/= p_sys
->i_height
[Y_PLANE
];
527 memcpy( &p_pic_out
->p
[i_p
].p_pixels
[ i_y
* p_pic_out
->p
[i_p
].i_pitch
],
528 &p_temp_buf
[ ( ( i_y
+ i_ofs
) % p_pic_out
->p
[i_p
].i_visible_lines
) * p_pic_out
->p
[i_p
].i_pitch
],
529 p_pic_out
->p
[i_p
].i_visible_pitch
);
531 if ( !p_sys
->i_sliding_type_duplicate
)