]> git.sesse.net Git - vlc/blob - modules/video_filter/vhs.c
decoder: fix data race in input_DecoderChangePause()
[vlc] / modules / video_filter / vhs.c
1 /*****************************************************************************
2  * vhs.c : VHS effect video filter
3  *****************************************************************************
4  * Copyright (C) 2013      Vianney Boyer
5  * $Id$
6  *
7  * Authors: Vianney Boyer <vlcvboyer -at- gmail -dot- com>
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 #   include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_filter.h>
35 #include <vlc_rand.h>
36 #include <vlc_mtime.h>
37
38 #include "filter_picture.h"
39
40 #ifndef TIME_UNIT_PER_S
41 #   define TIME_UNIT_PER_S ( ((int64_t) 1) << 32 )
42 #endif
43
44 static inline int64_t MOD(int64_t a, int64_t b) {
45     return ( ( a % b ) + b ) % b; }
46
47 #define MAX_BLUE_RED_LINES 100
48
49 typedef struct {
50     int32_t  i_offset;
51     uint16_t i_intensity;
52     bool     b_blue_red;
53     uint64_t i_stop_trigger;
54 } blue_red_line_t;
55
56 struct filter_sys_t {
57
58     /* general data */
59     bool b_init;
60     int32_t  i_planes;
61     int32_t *i_height; /* note: each plane may have different dimensions */
62     int32_t *i_width;
63     int32_t *i_visible_pitch;
64     uint64_t i_start_time;
65     uint64_t i_last_time;
66     uint64_t i_cur_time;
67
68     /* sliding & offset effect */
69     int32_t  i_phase_speed;
70     int32_t  i_phase_ofs;
71     int32_t  i_offset_ofs;
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;
78
79     /* blue red lines effect */
80     uint64_t i_BR_line_trigger;
81     blue_red_line_t *p_BR_lines[MAX_BLUE_RED_LINES];
82
83 };
84
85 /*****************************************************************************
86  * Prototypes
87  *****************************************************************************/
88
89 static picture_t *Filter( filter_t *, picture_t * );
90
91 static int  vhs_allocate_data( filter_t *, picture_t * );
92 static void vhs_free_allocated_data( filter_t * );
93
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 * );
97
98 static int  vhs_sliding_effect_apply( filter_t *, picture_t * );
99
100 /*****************************************************************************
101  * Module descriptor
102  *****************************************************************************/
103
104 static int  Open ( vlc_object_t * );
105 static void Close( vlc_object_t * );
106
107 vlc_module_begin()
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 )
113
114     set_callbacks( Open, Close )
115 vlc_module_end()
116
117 /**
118  * Open the filter
119  */
120 static int Open( vlc_object_t *p_this )
121 {
122     filter_t *p_filter = (filter_t*)p_this;
123     filter_sys_t *p_sys;
124
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" );
128         return VLC_EGENERIC;
129     }
130
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 ) )
138     {
139         msg_Err( p_filter, "Unsupported chroma (%4.4s)", (char*)&fourcc );
140         return VLC_EGENERIC;
141     }
142
143     /* Allocate structure */
144     p_filter->p_sys = p_sys = calloc(1, sizeof(*p_sys) );
145     if( unlikely( !p_sys ) )
146         return VLC_ENOMEM;
147
148     /* init data */
149     p_filter->pf_video_filter = Filter;
150     p_sys->i_start_time = p_sys->i_cur_time = p_sys->i_last_time = NTPtime64();
151
152     return VLC_SUCCESS;
153 }
154
155 /**
156  * Close the filter
157  */
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;
161
162     /* Free allocated memory */
163     vhs_free_allocated_data( p_filter );
164     free( p_sys );
165 }
166
167 /**
168  * Filter a picture
169  */
170 static picture_t *Filter( filter_t *p_filter, picture_t *p_pic_in ) {
171     if( unlikely( !p_pic_in || !p_filter) )
172         return NULL;
173
174     filter_sys_t *p_sys = p_filter->p_sys;
175
176     picture_t *p_pic_out = filter_NewPicture( p_filter );
177     if( unlikely( !p_pic_out ) ) {
178         picture_Release( p_pic_in );
179         return NULL;
180     }
181
182    /*
183     * manage time
184     */
185     p_sys->i_last_time = p_sys->i_cur_time;
186     p_sys->i_cur_time = NTPtime64();
187
188    /*
189     * allocate data
190     */
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 );
194             return NULL;
195         }
196     p_sys->b_init = true;
197
198    /*
199     * preset output pic: raw copy src to dst
200     */
201     picture_CopyPixels(p_pic_out, p_pic_in);
202
203    /*
204     * apply effects on picture
205     */
206     if ( unlikely( vhs_blue_red_line_effect( p_filter, p_pic_out ) != VLC_SUCCESS ) )
207         return CopyInfoAndRelease( p_pic_out, p_pic_in );
208
209     if ( unlikely( vhs_sliding_effect(p_filter, p_pic_out ) != VLC_SUCCESS ) )
210         return CopyInfoAndRelease( p_pic_out, p_pic_in );
211
212     vhs_blue_red_dots_effect( p_filter, p_pic_out );
213
214     return CopyInfoAndRelease( p_pic_out, p_pic_in );
215 }
216
217 /*
218  * Allocate data
219  */
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;
222
223     vhs_free_allocated_data( p_filter );
224
225    /*
226     * take into account different characteristics for each plane
227     */
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) );
232
233     if( unlikely( !p_sys->i_height || !p_sys->i_width || !p_sys->i_visible_pitch ) ) {
234         vhs_free_allocated_data( p_filter );
235         return VLC_ENOMEM;
236     }
237
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;
242     }
243     return VLC_SUCCESS;
244 }
245
246 /**
247  * Free allocated data
248  */
249 static void vhs_free_allocated_data( filter_t *p_filter ) {
250     filter_sys_t *p_sys = p_filter->p_sys;
251
252     for ( uint32_t i_b = 0; i_b < MAX_BLUE_RED_LINES; i_b++ )
253         FREENULL( p_sys->p_BR_lines[i_b] );
254
255     p_sys->i_planes = 0;
256     FREENULL( p_sys->i_height );
257     FREENULL( p_sys->i_width );
258     FREENULL( p_sys->i_visible_pitch );
259 }
260
261
262 /**
263  * Horizontal blue or red lines random management and effect
264  */
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;
267
268 #define BR_LINES_GENERATOR_PERIOD ( TIME_UNIT_PER_S * 50 )
269 #define BR_LINES_DURATION         ( TIME_UNIT_PER_S * 1/50 )
270
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) {
275                 /* allocate data */
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] ) )
278                     return VLC_ENOMEM;
279
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 )
283                                                  + 5;
284
285                 p_sys->p_BR_lines[i_b]->b_blue_red = (unsigned)vlc_mrand48() & 0x01;
286
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;
290
291                 break;
292             }
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;
296     }
297
298
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] );
305                 continue;
306             }
307
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;
314
315                 switch ( i_p ) {
316                   case Y_PLANE:
317                     memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs], 127,
318                             p_pic_out->p[i_p].i_visible_pitch);
319                     break;
320                   case U_PLANE:
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);
324                     break;
325                   case V_PLANE:
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);
329                     break;
330                 }
331
332             }
333         }
334     return VLC_SUCCESS;
335 }
336
337 /**
338  * insert randomly blue and red dots on the picture
339  */
340 static void vhs_blue_red_dots_effect( filter_t *p_filter, picture_t *p_pic_out ) {
341 #define BR_DOTS_RATIO 10000
342
343     filter_sys_t *p_sys = p_filter->p_sys;
344
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;
347           i_dots++) {
348
349         uint32_t i_length = (unsigned)vlc_mrand48()
350                           % __MAX( 1, p_sys->i_width[Y_PLANE] / 30 ) + 1;
351
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);
356
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
362                                + i_x
363                                * p_pic_out->p[i_p].i_pixel_pitch;
364
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;
368
369             switch ( i_p ) {
370               case Y_PLANE:
371                 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs], 127,
372                         i_length_in_plane );
373                 break;
374               case U_PLANE:
375                 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
376                         (b_color?255:0),
377                         i_length_in_plane );
378                 break;
379               case V_PLANE:
380                 memset( &p_pic_out->p[i_p].p_pixels[i_pix_ofs],
381                         (b_color?0:255),
382                         i_length_in_plane );
383                 break;
384             }
385         }
386     }
387 }
388
389 /**
390 * sliding effects
391 */
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;
394
395     /**
396     * one shot offset section
397     */
398
399 #define OFFSET_AVERAGE_PERIOD   (10 * TIME_UNIT_PER_S)
400
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 */
404
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];
415     }
416     else
417         p_sys->i_offset_ofs = 0;
418
419
420     /**
421     * phase section
422     */
423
424 #define MAX_PHASE_OFS (p_sys->i_height[Y_PLANE]*100/15)
425
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;
431
432
433     /**
434     * sliding section
435     */
436
437 #define SLIDING_AVERAGE_PERIOD   (20 * TIME_UNIT_PER_S)
438 #define SLIDING_AVERAGE_DURATION ( 3 * TIME_UNIT_PER_S)
439
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 ) ) {
444
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;
449     }
450
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 ) ) {
455
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;
465     }
466
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 ) ) {
470
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;
474
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 ) {
480
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;
485         }
486     }
487
488     /* update offset */
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)
492                                 / TIME_UNIT_PER_S,
493                                 p_sys->i_height[Y_PLANE] * 100 );
494
495     return vhs_sliding_effect_apply( p_filter, p_pic_out );
496 }
497
498 /**
499 * apply both sliding and offset effect
500 */
501 static int vhs_sliding_effect_apply( filter_t *p_filter, picture_t *p_pic_out )
502 {
503     filter_sys_t *p_sys = p_filter->p_sys;
504
505     for ( uint8_t i_p = 0; i_p < p_pic_out->i_planes; i_p++ ) {
506         /* first allocate temporary buffer for swap operation */
507         uint8_t *p_temp_buf;
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 ) )
512                 return VLC_ENOMEM;
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 );
515         }
516         else
517             p_temp_buf = p_pic_out->p[i_p].p_pixels;
518
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++ )
521         {
522             int32_t i_ofs = p_sys->i_offset_ofs + p_sys->i_sliding_ofs;
523
524             if ( ( p_sys->i_sliding_speed == 0 ) || !p_sys->i_sliding_type_duplicate )
525                 i_ofs += p_sys->i_phase_ofs;
526
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];
530
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 );
534         }
535         if ( !p_sys->i_sliding_type_duplicate )
536             free(p_temp_buf);
537     }
538
539     return VLC_SUCCESS;
540 }