00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027 #include <stdlib.h>
00028 #include <string.h>
00029
00030 #include <vlc/vlc.h>
00031 #include <vlc/vout.h>
00032
00033 #include "filter_common.h"
00034
00035
00036
00037
00038 static int Create ( vlc_object_t * );
00039 static void Destroy ( vlc_object_t * );
00040
00041 static int Init ( vout_thread_t * );
00042 static void End ( vout_thread_t * );
00043 static int Manage ( vout_thread_t * );
00044 static void Render ( vout_thread_t *, picture_t * );
00045
00046 static void UpdateStats ( vout_thread_t *, picture_t * );
00047
00048 static int SendEvents( vlc_object_t *, char const *,
00049 vlc_value_t, vlc_value_t, void * );
00050
00051
00052
00053
00054 #define GEOMETRY_TEXT N_("Crop geometry (pixels)")
00055 #define GEOMETRY_LONGTEXT N_("Set the geometry of the zone to crop. This is set as <width> x <height> + <left offset> + <top offset>.")
00056
00057 #define AUTOCROP_TEXT N_("Automatic cropping")
00058 #define AUTOCROP_LONGTEXT N_("Activate automatic black border cropping.")
00059
00060 vlc_module_begin();
00061 set_description( _("Crop video filter") );
00062 set_shortname( N_("Crop" ));
00063 set_category( CAT_VIDEO );
00064 set_subcategory( SUBCAT_VIDEO_VFILTER );
00065 set_capability( "video filter", 0 );
00066
00067 add_string( "crop-geometry", NULL, NULL, GEOMETRY_TEXT, GEOMETRY_LONGTEXT, VLC_FALSE );
00068 add_bool( "autocrop", 0, NULL, AUTOCROP_TEXT, AUTOCROP_LONGTEXT, VLC_FALSE );
00069
00070 add_shortcut( "crop" );
00071 set_callbacks( Create, Destroy );
00072 vlc_module_end();
00073
00074
00075
00076
00077
00078
00079
00080 struct vout_sys_t
00081 {
00082 vout_thread_t *p_vout;
00083
00084 unsigned int i_x, i_y;
00085 unsigned int i_width, i_height, i_aspect;
00086
00087 vlc_bool_t b_autocrop;
00088
00089
00090 unsigned int i_lastchange;
00091 vlc_bool_t b_changed;
00092 };
00093
00094
00095
00096
00097 static int Control( vout_thread_t *p_vout, int i_query, va_list args )
00098 {
00099 return vout_vaControl( p_vout->p_sys->p_vout, i_query, args );
00100 }
00101
00102
00103
00104
00105
00106
00107 static int Create( vlc_object_t *p_this )
00108 {
00109 vout_thread_t *p_vout = (vout_thread_t *)p_this;
00110
00111
00112 p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
00113 if( p_vout->p_sys == NULL )
00114 {
00115 msg_Err( p_vout, "out of memory" );
00116 return VLC_ENOMEM;
00117 }
00118
00119 p_vout->pf_init = Init;
00120 p_vout->pf_end = End;
00121 p_vout->pf_manage = Manage;
00122 p_vout->pf_render = Render;
00123 p_vout->pf_display = NULL;
00124 p_vout->pf_control = Control;
00125
00126 return VLC_SUCCESS;
00127 }
00128
00129
00130
00131
00132 static int Init( vout_thread_t *p_vout )
00133 {
00134 int i_index;
00135 char *psz_var;
00136 picture_t *p_pic;
00137 video_format_t fmt = {0};
00138
00139 I_OUTPUTPICTURES = 0;
00140
00141 p_vout->p_sys->i_lastchange = 0;
00142 p_vout->p_sys->b_changed = VLC_FALSE;
00143
00144
00145 p_vout->output.i_chroma = p_vout->render.i_chroma;
00146 p_vout->output.i_width = p_vout->render.i_width;
00147 p_vout->output.i_height = p_vout->render.i_height;
00148 p_vout->output.i_aspect = p_vout->render.i_aspect;
00149 p_vout->fmt_out = p_vout->fmt_in;
00150
00151
00152 p_vout->p_sys->b_autocrop = config_GetInt( p_vout, "autocrop" );
00153
00154
00155 psz_var = config_GetPsz( p_vout, "crop-geometry" );
00156 if( psz_var )
00157 {
00158 char *psz_parser, *psz_tmp;
00159
00160 psz_parser = psz_tmp = psz_var;
00161 while( *psz_tmp && *psz_tmp != 'x' ) psz_tmp++;
00162
00163 if( *psz_tmp )
00164 {
00165 psz_tmp[0] = '\0';
00166 p_vout->p_sys->i_width = atoi( psz_parser );
00167
00168 psz_parser = ++psz_tmp;
00169 while( *psz_tmp && *psz_tmp != '+' ) psz_tmp++;
00170
00171 if( *psz_tmp )
00172 {
00173 psz_tmp[0] = '\0';
00174 p_vout->p_sys->i_height = atoi( psz_parser );
00175
00176 psz_parser = ++psz_tmp;
00177 while( *psz_tmp && *psz_tmp != '+' ) psz_tmp++;
00178
00179 if( *psz_tmp )
00180 {
00181 psz_tmp[0] = '\0';
00182 p_vout->p_sys->i_x = atoi( psz_parser );
00183 p_vout->p_sys->i_y = atoi( ++psz_tmp );
00184 }
00185 else
00186 {
00187 p_vout->p_sys->i_x = atoi( psz_parser );
00188 p_vout->p_sys->i_y =
00189 ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
00190 }
00191 }
00192 else
00193 {
00194 p_vout->p_sys->i_height = atoi( psz_parser );
00195 p_vout->p_sys->i_x =
00196 ( p_vout->output.i_width - p_vout->p_sys->i_width ) / 2;
00197 p_vout->p_sys->i_y =
00198 ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
00199 }
00200 }
00201 else
00202 {
00203 p_vout->p_sys->i_width = atoi( psz_parser );
00204 p_vout->p_sys->i_height = p_vout->output.i_height;
00205 p_vout->p_sys->i_x =
00206 ( p_vout->output.i_width - p_vout->p_sys->i_width ) / 2;
00207 p_vout->p_sys->i_y =
00208 ( p_vout->output.i_height - p_vout->p_sys->i_height ) / 2;
00209 }
00210
00211
00212 if( p_vout->p_sys->i_x + p_vout->p_sys->i_width
00213 > p_vout->output.i_width )
00214 {
00215 p_vout->p_sys->i_x = 0;
00216 if( p_vout->p_sys->i_width > p_vout->output.i_width )
00217 {
00218 p_vout->p_sys->i_width = p_vout->output.i_width;
00219 }
00220 }
00221
00222 if( p_vout->p_sys->i_y + p_vout->p_sys->i_height
00223 > p_vout->output.i_height )
00224 {
00225 p_vout->p_sys->i_y = 0;
00226 if( p_vout->p_sys->i_height > p_vout->output.i_height )
00227 {
00228 p_vout->p_sys->i_height = p_vout->output.i_height;
00229 }
00230 }
00231
00232 free( psz_var );
00233 }
00234 else
00235 {
00236 p_vout->p_sys->i_width = p_vout->fmt_out.i_visible_width;
00237 p_vout->p_sys->i_height = p_vout->fmt_out.i_visible_height;
00238 p_vout->p_sys->i_x = p_vout->fmt_out.i_x_offset;
00239 p_vout->p_sys->i_y = p_vout->fmt_out.i_y_offset;
00240 }
00241
00242
00243 msg_Dbg( p_vout, "cropping at %ix%i+%i+%i, %sautocropping",
00244 p_vout->p_sys->i_width, p_vout->p_sys->i_height,
00245 p_vout->p_sys->i_x, p_vout->p_sys->i_y,
00246 p_vout->p_sys->b_autocrop ? "" : "not " );
00247
00248
00249 p_vout->p_sys->i_aspect = p_vout->fmt_out.i_aspect
00250 * p_vout->fmt_out.i_visible_height / p_vout->p_sys->i_height
00251 * p_vout->p_sys->i_width / p_vout->fmt_out.i_visible_width;
00252
00253 fmt.i_width = fmt.i_visible_width = p_vout->p_sys->i_width;
00254 fmt.i_height = fmt.i_visible_height = p_vout->p_sys->i_height;
00255 fmt.i_x_offset = fmt.i_y_offset = 0;
00256 fmt.i_chroma = p_vout->render.i_chroma;
00257 fmt.i_aspect = p_vout->p_sys->i_aspect;
00258 fmt.i_sar_num = p_vout->p_sys->i_aspect * fmt.i_height / fmt.i_width;
00259 fmt.i_sar_den = VOUT_ASPECT_FACTOR;
00260
00261
00262 p_vout->p_sys->p_vout = vout_Create( p_vout, &fmt );
00263 if( p_vout->p_sys->p_vout == NULL )
00264 {
00265 msg_Err( p_vout, "failed to create vout" );
00266 return VLC_EGENERIC;
00267 }
00268
00269 ALLOCATE_DIRECTBUFFERS( VOUT_MAX_PICTURES );
00270
00271 ADD_CALLBACKS( p_vout->p_sys->p_vout, SendEvents );
00272
00273 ADD_PARENT_CALLBACKS( SendEventsToChild );
00274
00275 return VLC_SUCCESS;
00276 }
00277
00278
00279
00280
00281 static void End( vout_thread_t *p_vout )
00282 {
00283 int i_index;
00284
00285
00286 for( i_index = I_OUTPUTPICTURES ; i_index ; )
00287 {
00288 i_index--;
00289 free( PP_OUTPUTPICTURE[ i_index ]->p_data_orig );
00290 }
00291 }
00292
00293
00294
00295
00296
00297
00298 static void Destroy( vlc_object_t *p_this )
00299 {
00300 vout_thread_t *p_vout = (vout_thread_t *)p_this;
00301
00302 if( p_vout->p_sys->p_vout )
00303 {
00304 DEL_CALLBACKS( p_vout->p_sys->p_vout, SendEvents );
00305 vlc_object_detach( p_vout->p_sys->p_vout );
00306 vout_Destroy( p_vout->p_sys->p_vout );
00307 }
00308
00309 DEL_PARENT_CALLBACKS( SendEventsToChild );
00310
00311 free( p_vout->p_sys );
00312 }
00313
00314
00315
00316
00317
00318
00319
00320 static int Manage( vout_thread_t *p_vout )
00321 {
00322 video_format_t fmt = {0};
00323
00324 if( !p_vout->p_sys->b_changed )
00325 {
00326 return VLC_SUCCESS;
00327 }
00328
00329 vout_Destroy( p_vout->p_sys->p_vout );
00330
00331 fmt.i_width = fmt.i_visible_width = p_vout->p_sys->i_width;
00332 fmt.i_height = fmt.i_visible_height = p_vout->p_sys->i_height;
00333 fmt.i_x_offset = fmt.i_y_offset = 0;
00334 fmt.i_chroma = p_vout->render.i_chroma;
00335 fmt.i_aspect = p_vout->p_sys->i_aspect;
00336 fmt.i_sar_num = p_vout->p_sys->i_aspect * fmt.i_height / fmt.i_width;
00337 fmt.i_sar_den = VOUT_ASPECT_FACTOR;
00338
00339 p_vout->p_sys->p_vout = vout_Create( p_vout, &fmt );
00340 if( p_vout->p_sys->p_vout == NULL )
00341 {
00342 msg_Err( p_vout, "failed to create vout" );
00343 return VLC_EGENERIC;
00344 }
00345
00346 p_vout->p_sys->b_changed = VLC_FALSE;
00347 p_vout->p_sys->i_lastchange = 0;
00348
00349 return VLC_SUCCESS;
00350 }
00351
00352
00353
00354
00355
00356
00357
00358
00359 static void Render( vout_thread_t *p_vout, picture_t *p_pic )
00360 {
00361 picture_t *p_outpic = NULL;
00362 int i_plane;
00363
00364 if( p_vout->p_sys->b_changed )
00365 {
00366 return;
00367 }
00368
00369 while( ( p_outpic =
00370 vout_CreatePicture( p_vout->p_sys->p_vout, 0, 0, 0 )
00371 ) == NULL )
00372 {
00373 if( p_vout->b_die || p_vout->b_error )
00374 {
00375 vout_DestroyPicture( p_vout->p_sys->p_vout, p_outpic );
00376 return;
00377 }
00378
00379 msleep( VOUT_OUTMEM_SLEEP );
00380 }
00381
00382 vout_DatePicture( p_vout->p_sys->p_vout, p_outpic, p_pic->date );
00383 vout_LinkPicture( p_vout->p_sys->p_vout, p_outpic );
00384
00385 for( i_plane = 0 ; i_plane < p_pic->i_planes ; i_plane++ )
00386 {
00387 uint8_t *p_in, *p_out, *p_out_end;
00388 int i_in_pitch = p_pic->p[i_plane].i_pitch;
00389 const int i_out_pitch = p_outpic->p[i_plane].i_pitch;
00390 const int i_copy_pitch = p_outpic->p[i_plane].i_visible_pitch;
00391
00392 p_in = p_pic->p[i_plane].p_pixels
00393
00394 + i_in_pitch * ( p_pic->p[i_plane].i_visible_lines *
00395 p_vout->p_sys->i_y / p_vout->output.i_height )
00396
00397 + i_in_pitch * p_vout->p_sys->i_x / p_vout->output.i_width;
00398
00399 p_out = p_outpic->p[i_plane].p_pixels;
00400 p_out_end = p_out + i_out_pitch * p_outpic->p[i_plane].i_visible_lines;
00401
00402 while( p_out < p_out_end )
00403 {
00404 p_vout->p_vlc->pf_memcpy( p_out, p_in, i_copy_pitch );
00405 p_in += i_in_pitch;
00406 p_out += i_out_pitch;
00407 }
00408 }
00409
00410 vout_UnlinkPicture( p_vout->p_sys->p_vout, p_outpic );
00411 vout_DisplayPicture( p_vout->p_sys->p_vout, p_outpic );
00412
00413
00414 if( p_vout->p_sys->b_autocrop )
00415 {
00416 UpdateStats( p_vout, p_pic );
00417 }
00418 }
00419
00420 static void UpdateStats( vout_thread_t *p_vout, picture_t *p_pic )
00421 {
00422 uint8_t *p_in = p_pic->p[0].p_pixels;
00423 int i_pitch = p_pic->p[0].i_pitch;
00424 int i_visible_pitch = p_pic->p[0].i_visible_pitch;
00425 int i_lines = p_pic->p[0].i_visible_lines;
00426 int i_firstwhite = -1, i_lastwhite = -1, i;
00427
00428
00429 switch( p_vout->output.i_chroma )
00430 {
00431 case VLC_FOURCC('I','4','2','0'):
00432
00433
00434 for( i = i_lines ; i-- ; )
00435 {
00436 const int i_col = i * i_pitch / i_lines;
00437
00438 if( p_in[i_col/2] > 40
00439 && p_in[i_visible_pitch/2] > 40
00440 && p_in[i_visible_pitch/2 + i_col/2] > 40 )
00441 {
00442 if( i_lastwhite == -1 )
00443 {
00444 i_lastwhite = i;
00445 }
00446 i_firstwhite = i;
00447 }
00448 p_in += i_pitch;
00449 }
00450 break;
00451
00452 default:
00453 break;
00454 }
00455
00456
00457 if( i_lastwhite == -1 )
00458 {
00459 p_vout->p_sys->i_lastchange = 0;
00460 return;
00461 }
00462
00463 if( (unsigned int)(i_lastwhite - i_firstwhite)
00464 < p_vout->p_sys->i_height / 2 )
00465 {
00466 p_vout->p_sys->i_lastchange = 0;
00467 return;
00468 }
00469
00470 if( (unsigned int)(i_lastwhite - i_firstwhite)
00471 < p_vout->p_sys->i_height + 16
00472 && (unsigned int)(i_lastwhite - i_firstwhite + 16)
00473 > p_vout->p_sys->i_height )
00474 {
00475 p_vout->p_sys->i_lastchange = 0;
00476 return;
00477 }
00478
00479
00480 p_vout->p_sys->i_lastchange++;
00481 if( p_vout->p_sys->i_lastchange < 25 )
00482 {
00483 return;
00484 }
00485
00486
00487 if( i_firstwhite & 1 )
00488 {
00489 i_firstwhite--;
00490 }
00491
00492 if( !(i_lastwhite & 1) )
00493 {
00494 i_lastwhite++;
00495 }
00496
00497
00498 p_vout->p_sys->i_y = i_firstwhite;
00499 p_vout->p_sys->i_height = i_lastwhite - i_firstwhite + 1;
00500
00501 p_vout->p_sys->i_aspect = p_vout->output.i_aspect
00502 * p_vout->output.i_height / p_vout->p_sys->i_height
00503 * p_vout->p_sys->i_width / p_vout->output.i_width;
00504
00505 p_vout->p_sys->b_changed = VLC_TRUE;
00506 }
00507
00508
00509
00510
00511 static int SendEvents( vlc_object_t *p_this, char const *psz_var,
00512 vlc_value_t oldval, vlc_value_t newval, void *_p_vout )
00513 {
00514 vout_thread_t *p_vout = (vout_thread_t *)_p_vout;
00515 vlc_value_t sentval = newval;
00516
00517
00518 if( !strcmp( psz_var, "mouse-x" ) )
00519 {
00520 sentval.i_int += p_vout->p_sys->i_x;
00521 }
00522 else if( !strcmp( psz_var, "mouse-y" ) )
00523 {
00524 sentval.i_int += p_vout->p_sys->i_y;
00525 }
00526
00527 var_Set( p_vout, psz_var, sentval );
00528
00529 return VLC_SUCCESS;
00530 }
00531
00532
00533
00534
00535 static int SendEventsToChild( vlc_object_t *p_this, char const *psz_var,
00536 vlc_value_t oldval, vlc_value_t newval, void *p_data )
00537 {
00538 vout_thread_t *p_vout = (vout_thread_t *)p_this;
00539 var_Set( p_vout->p_sys->p_vout, psz_var, newval );
00540 return VLC_SUCCESS;
00541 }