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 #ifndef TIME_UNIT_PER_S
41 # define TIME_UNIT_PER_S ( ((int64_t) 1) << 32 )
44 static inline int64_t MOD(int64_t a, int64_t b) {
45 return ( ( a % b ) + b ) % b; }
47 #define MAX_BLUE_RED_LINES 100
53 uint64_t i_stop_trigger;
61 int32_t *i_height; /* note: each plane may have different dimensions */
63 int32_t *i_visible_pitch;
64 uint64_t i_start_time;
68 /* sliding & offset effect */
69 int32_t i_phase_speed;
72 int32_t i_sliding_ofs;
73 int32_t i_sliding_speed;
74 uint64_t i_offset_trigger;
75 uint64_t i_sliding_trigger;
76 uint64_t i_sliding_stop_trig;
77 bool i_sliding_type_duplicate;
79 /* blue red lines effect */
80 uint64_t i_BR_line_trigger;
81 blue_red_line_t *p_BR_lines[MAX_BLUE_RED_LINES];
85 /*****************************************************************************
87 *****************************************************************************/
89 static picture_t *Filter( filter_t *, picture_t * );
91 static int vhs_allocate_data( filter_t *, picture_t * );
92 static void vhs_free_allocated_data( filter_t * );
94 static int vhs_blue_red_line_effect( filter_t *, picture_t * );
95 static void vhs_blue_red_dots_effect( filter_t *, picture_t * );
96 static int vhs_sliding_effect( filter_t *, picture_t * );
98 static int vhs_sliding_effect_apply( filter_t *, picture_t * );
100 /*****************************************************************************
102 *****************************************************************************/
104 static int Open ( vlc_object_t * );
105 static void Close( vlc_object_t * );
108 set_description( N_("VHS movie effect video filter") )
109 set_shortname( N_("VHS movie" ) )
110 set_capability( "video filter2", 0 )
111 set_category( CAT_VIDEO )
112 set_subcategory( SUBCAT_VIDEO_VFILTER )
114 set_callbacks( Open, Close )
120 static int Open( vlc_object_t *p_this )
122 filter_t *p_filter = (filter_t*)p_this;
125 /* Assert video in match with video out */
126 if( !es_format_IsSimilar( &p_filter->fmt_in, &p_filter->fmt_out ) ) {
127 msg_Err( p_filter, "Input and output format does not match" );
131 /* Reject 0 bpp and unsupported chroma */
132 const vlc_fourcc_t fourcc = p_filter->fmt_in.video.i_chroma;
133 const vlc_chroma_description_t *p_chroma =
134 vlc_fourcc_GetChromaDescription( p_filter->fmt_in.video.i_chroma );
135 if( !p_chroma || p_chroma->pixel_size == 0
136 || p_chroma->plane_count < 3 || p_chroma->pixel_size > 1
137 || !vlc_fourcc_IsYUV( fourcc ) )
139 msg_Err( p_filter, "Unsupported chroma (%4.4s)", (char*)&fourcc );
143 /* Allocate structure */
144 p_filter->p_sys = p_sys = calloc(1, sizeof(*p_sys) );
145 if( unlikely( !p_sys ) )
149 p_filter->pf_video_filter = Filter;
150 p_sys->i_start_time = p_sys->i_cur_time = p_sys->i_last_time = NTPtime64();
158 static void Close( vlc_object_t *p_this ) {
159 filter_t *p_filter = (filter_t*)p_this;
160 filter_sys_t *p_sys = p_filter->p_sys;
162 /* Free allocated memory */
163 vhs_free_allocated_data( p_filter );
170 static picture_t *Filter( filter_t *p_filter, picture_t *p_pic_in ) {
171 if( unlikely( !p_pic_in || !p_filter) )
174 filter_sys_t *p_sys = p_filter->p_sys;
176 picture_t *p_pic_out = filter_NewPicture( p_filter );
177 if( unlikely( !p_pic_out ) ) {
178 picture_Release( p_pic_in );
185 p_sys->i_last_time = p_sys->i_cur_time;
186 p_sys->i_cur_time = NTPtime64();
191 if ( unlikely( !p_sys->b_init ) )
192 if ( unlikely( vhs_allocate_data( p_filter, p_pic_in ) != VLC_SUCCESS ) ) {
193 picture_Release( p_pic_in );
196 p_sys->b_init = true;
199 * preset output pic: raw copy src to dst
201 picture_CopyPixels(p_pic_out, p_pic_in);
204 * apply effects on picture
206 if ( unlikely( vhs_blue_red_line_effect( p_filter, p_pic_out ) != VLC_SUCCESS ) )
207 return CopyInfoAndRelease( p_pic_out, p_pic_in );
209 if ( unlikely( vhs_sliding_effect(p_filter, p_pic_out ) != VLC_SUCCESS ) )
210 return CopyInfoAndRelease( p_pic_out, p_pic_in );
212 vhs_blue_red_dots_effect( p_filter, p_pic_out );
214 return CopyInfoAndRelease( p_pic_out, p_pic_in );
220 static int vhs_allocate_data( filter_t *p_filter, picture_t *p_pic_in ) {
221 filter_sys_t *p_sys = p_filter->p_sys;
223 vhs_free_allocated_data( p_filter );
226 * take into account different characteristics for each plane
228 p_sys->i_planes = p_pic_in->i_planes;
229 p_sys->i_height = calloc( p_sys->i_planes, sizeof(int32_t) );
230 p_sys->i_width = calloc( p_sys->i_planes, sizeof(int32_t) );
231 p_sys->i_visible_pitch = calloc( p_sys->i_planes, sizeof(int32_t) );
233 if( unlikely( !p_sys->i_height || !p_sys->i_width || !p_sys->i_visible_pitch ) ) {
234 vhs_free_allocated_data( p_filter );
238 for ( int32_t i_p = 0; i_p < p_sys->i_planes; i_p++) {
239 p_sys->i_visible_pitch [i_p] = (int) p_pic_in->p[i_p].i_visible_pitch;
240 p_sys->i_height[i_p] = (int) p_pic_in->p[i_p].i_visible_lines;
241 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;
247 * Free allocated data
249 static void vhs_free_allocated_data( filter_t *p_filter ) {
250 filter_sys_t *p_sys = p_filter->p_sys;
252 for ( uint32_t i_b = 0; i_b < MAX_BLUE_RED_LINES; i_b++ )
253 FREENULL( p_sys->p_BR_lines[i_b] );
256 FREENULL( p_sys->i_height );
257 FREENULL( p_sys->i_width );
258 FREENULL( p_sys->i_visible_pitch );
263 * Horizontal blue or red lines random management and effect
265 static int vhs_blue_red_line_effect( filter_t *p_filter, picture_t *p_pic_out ) {
266 filter_sys_t *p_sys = p_filter->p_sys;
268 #define BR_LINES_GENERATOR_PERIOD ( TIME_UNIT_PER_S * 50 )
269 #define BR_LINES_DURATION ( TIME_UNIT_PER_S * 1/50 )
271 /* generate new blue or red lines */
272 if ( p_sys->i_BR_line_trigger <= p_sys->i_cur_time ) {
273 for ( uint32_t i_b = 0; i_b < MAX_BLUE_RED_LINES; i_b++ )
274 if (p_sys->p_BR_lines[i_b] == NULL) {
276 p_sys->p_BR_lines[i_b] = calloc( 1, sizeof(blue_red_line_t) );
277 if ( unlikely( !p_sys->p_BR_lines[i_b] ) )
280 /* set random parameters */
281 p_sys->p_BR_lines[i_b]->i_offset = (unsigned)vlc_mrand48()
282 % __MAX( 1, p_sys->i_height[Y_PLANE] - 10 )
285 p_sys->p_BR_lines[i_b]->b_blue_red = (unsigned)vlc_mrand48() & 0x01;
287 p_sys->p_BR_lines[i_b]->i_stop_trigger = p_sys->i_cur_time
288 + (uint64_t)vlc_mrand48() % BR_LINES_DURATION
289 + BR_LINES_DURATION / 2;
293 p_sys->i_BR_line_trigger = p_sys->i_cur_time
294 + (uint64_t)vlc_mrand48() % BR_LINES_GENERATOR_PERIOD
295 + BR_LINES_GENERATOR_PERIOD / 2;
299 /* manage and apply current blue/red lines */
300 for ( uint8_t i_b = 0; i_b < MAX_BLUE_RED_LINES; i_b++ )
301 if ( p_sys->p_BR_lines[i_b] ) {
302 /* remove outdated ones */
303 if ( p_sys->p_BR_lines[i_b]->i_stop_trigger <= p_sys->i_cur_time ) {
304 FREENULL( p_sys->p_BR_lines[i_b] );
308 /* otherwise apply */
309 for ( int32_t i_p=0; i_p < p_sys->i_planes; i_p++ ) {
310 uint32_t i_pix_ofs = p_sys->p_BR_lines[i_b]->i_offset
311 * p_pic_out->p[i_p].i_visible_lines
312 / p_sys->i_height[Y_PLANE]
313 * p_pic_out->p[i_p].i_pitch;
317 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs], 127,
318 p_pic_out->p[i_p].i_visible_pitch);
321 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
322 (p_sys->p_BR_lines[i_b]->b_blue_red?255:0),
323 p_pic_out->p[i_p].i_visible_pitch);
326 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
327 (p_sys->p_BR_lines[i_b]->b_blue_red?0:255),
328 p_pic_out->p[i_p].i_visible_pitch);
338 * insert randomly blue and red dots on the picture
340 static void vhs_blue_red_dots_effect( filter_t *p_filter, picture_t *p_pic_out ) {
341 #define BR_DOTS_RATIO 10000
343 filter_sys_t *p_sys = p_filter->p_sys;
345 for ( int32_t i_dots = 0;
346 i_dots < p_sys->i_width[Y_PLANE] * p_sys->i_height[Y_PLANE] / BR_DOTS_RATIO;
349 uint32_t i_length = (unsigned)vlc_mrand48()
350 % __MAX( 1, p_sys->i_width[Y_PLANE] / 30 ) + 1;
352 uint16_t i_x = (unsigned)vlc_mrand48()
353 % __MAX( 1, p_sys->i_width[Y_PLANE] - i_length );
354 uint16_t i_y = (unsigned)vlc_mrand48() % p_sys->i_height[Y_PLANE];
355 bool b_color = ( ( (unsigned)vlc_mrand48() % 2 ) == 0);
357 for ( int32_t i_p = 0; i_p < p_sys->i_planes; i_p++ ) {
358 uint32_t i_pix_ofs = i_y
359 * p_pic_out->p[i_p].i_visible_lines
360 / p_sys->i_height[Y_PLANE]
361 * p_pic_out->p[i_p].i_pitch
363 * p_pic_out->p[i_p].i_pixel_pitch;
365 uint32_t i_length_in_plane = i_length
366 * p_pic_out->p[i_p].i_visible_pitch
367 / p_pic_out->p[Y_PLANE].i_visible_pitch;
371 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs], 127,
375 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
380 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
392 static int vhs_sliding_effect( filter_t *p_filter, picture_t *p_pic_out ) {
393 filter_sys_t *p_sys = p_filter->p_sys;
396 * one shot offset section
399 #define OFFSET_AVERAGE_PERIOD (10 * TIME_UNIT_PER_S)
401 /* start trigger to be (re)initialized */
402 if ( p_sys->i_offset_trigger == 0
403 || p_sys->i_sliding_speed != 0 ) { /* do not mix sliding and offset */
405 /* random trigger for offset effect */
406 p_sys->i_offset_trigger = p_sys->i_cur_time
407 + ((uint64_t) vlc_mrand48() ) % OFFSET_AVERAGE_PERIOD
408 + OFFSET_AVERAGE_PERIOD / 2;
409 p_sys->i_offset_ofs = 0;
410 } else if (p_sys->i_offset_trigger <= p_sys->i_cur_time) {
411 /* trigger for offset effect occurs */
412 p_sys->i_offset_trigger = 0;
413 p_sys->i_offset_ofs = (uint32_t)vlc_mrand48()
414 % p_sys->i_height[Y_PLANE];
417 p_sys->i_offset_ofs = 0;
424 #define MAX_PHASE_OFS (p_sys->i_height[Y_PLANE]*100/15)
426 p_sys->i_phase_speed += MOD( (int32_t)vlc_mrand48(), 3) - 1;
427 p_sys->i_phase_ofs += p_sys->i_phase_speed;
428 p_sys->i_phase_ofs = VLC_CLIP( p_sys->i_phase_ofs, -MAX_PHASE_OFS, +MAX_PHASE_OFS);
429 if ( abs( p_sys->i_phase_ofs ) >= MAX_PHASE_OFS )
430 p_sys->i_phase_speed = 0;
437 #define SLIDING_AVERAGE_PERIOD (20 * TIME_UNIT_PER_S)
438 #define SLIDING_AVERAGE_DURATION ( 3 * TIME_UNIT_PER_S)
440 /* start trigger to be (re)initialized */
441 if ( ( p_sys->i_sliding_stop_trig == 0 ) &&
442 ( p_sys->i_sliding_trigger == 0 ) &&
443 ( p_sys->i_sliding_speed == 0 ) ) {
445 /* random trigger which enable sliding effect */
446 p_sys->i_sliding_trigger = p_sys->i_cur_time
447 + (uint64_t)vlc_mrand48() % SLIDING_AVERAGE_PERIOD
448 + SLIDING_AVERAGE_PERIOD / 2;
451 /* start trigger just occurs */
452 else if ( ( p_sys->i_sliding_stop_trig == 0 ) &&
453 ( p_sys->i_sliding_trigger <= p_sys->i_cur_time ) &&
454 ( p_sys->i_sliding_speed == 0 ) ) {
456 /* init sliding parameters */
457 p_sys->i_sliding_trigger = 0;
458 p_sys->i_sliding_stop_trig = p_sys->i_cur_time
459 + (uint64_t)vlc_mrand48() % SLIDING_AVERAGE_DURATION
460 + SLIDING_AVERAGE_DURATION / 2;
461 p_sys->i_sliding_ofs = 0;
462 /* note: sliding speed unit = image per 100 s */
463 p_sys->i_sliding_speed = MOD( (int32_t)vlc_mrand48(), 1001 ) - 500;
464 p_sys->i_sliding_type_duplicate = (unsigned)vlc_mrand48() & 0x01;
467 /* stop trigger disabling sliding effect occurs */
468 else if ( ( p_sys->i_sliding_stop_trig <= p_sys->i_cur_time )
469 && ( p_sys->i_sliding_trigger == 0 ) ) {
471 /* first increase speed to ensure we will stop sliding on plain pict */
472 if ( abs( p_sys->i_sliding_speed ) < 5 )
473 p_sys->i_sliding_speed += 1;
475 /* check if offset is close to 0 and then ready to stop */
476 if ( abs( p_sys->i_sliding_ofs ) < abs( p_sys->i_sliding_speed
477 * p_sys->i_height[Y_PLANE]
478 * ( p_sys->i_cur_time - p_sys->i_last_time ) / TIME_UNIT_PER_S )
479 || abs( p_sys->i_sliding_ofs ) < p_sys->i_height[Y_PLANE] * 100 / 20 ) {
481 /* reset sliding parameters */
482 p_sys->i_sliding_ofs = p_sys->i_sliding_speed = 0;
483 p_sys->i_sliding_trigger = p_sys->i_sliding_stop_trig = 0;
484 p_sys->i_sliding_type_duplicate = false;
489 p_sys->i_sliding_ofs = MOD( p_sys->i_sliding_ofs
490 + p_sys->i_sliding_speed * p_sys->i_height[Y_PLANE]
491 * ( p_sys->i_cur_time - p_sys->i_last_time)
493 p_sys->i_height[Y_PLANE] * 100 );
495 return vhs_sliding_effect_apply( p_filter, p_pic_out );
499 * apply both sliding and offset effect
501 static int vhs_sliding_effect_apply( filter_t *p_filter, picture_t *p_pic_out )
503 filter_sys_t *p_sys = p_filter->p_sys;
505 for ( uint8_t i_p = 0; i_p < p_pic_out->i_planes; i_p++ ) {
506 /* first allocate temporary buffer for swap operation */
508 if ( !p_sys->i_sliding_type_duplicate ) {
509 p_temp_buf= calloc( p_pic_out->p[i_p].i_lines
510 * p_pic_out->p[i_p].i_pitch, sizeof(uint8_t) );
511 if ( unlikely( !p_temp_buf ) )
513 memcpy( p_temp_buf, p_pic_out->p[i_p].p_pixels,
514 p_pic_out->p[i_p].i_lines * p_pic_out->p[i_p].i_pitch );
517 p_temp_buf = p_pic_out->p[i_p].p_pixels;
519 /* copy lines to output_pic */
520 for ( int32_t i_y = 0; i_y < p_pic_out->p[i_p].i_visible_lines; i_y++ )
522 int32_t i_ofs = p_sys->i_offset_ofs + p_sys->i_sliding_ofs;
524 if ( ( p_sys->i_sliding_speed == 0 ) || !p_sys->i_sliding_type_duplicate )
525 i_ofs += p_sys->i_phase_ofs;
527 i_ofs = MOD( i_ofs / 100, p_sys->i_height[Y_PLANE] );
528 i_ofs *= p_pic_out->p[i_p].i_visible_lines;
529 i_ofs /= p_sys->i_height[Y_PLANE];
531 memcpy( &p_pic_out->p[i_p].p_pixels[ i_y * p_pic_out->p[i_p].i_pitch ],
532 &p_temp_buf[ ( ( i_y + i_ofs ) % p_pic_out->p[i_p].i_visible_lines ) * p_pic_out->p[i_p].i_pitch ],
533 p_pic_out->p[i_p].i_visible_pitch );
535 if ( !p_sys->i_sliding_type_duplicate )