OSDN Git Service

Optimize MP4 files for HTTP streaming (on the CLI it's -O or --optimize).
[handbrake-jp/handbrake-jp-git.git] / libhb / render.c
1 /* $Id: render.c,v 1.17 2005/04/14 17:37:54 titer Exp $
2
3    This file is part of the HandBrake source code.
4    Homepage: <http://handbrake.m0k.org/>.
5    It may be used under the terms of the GNU General Public License. */
6
7 #include "hb.h"
8
9 #include "ffmpeg/avcodec.h"
10 #include "ffmpeg/swscale.h"
11
12 struct hb_work_private_s
13 {
14     hb_job_t * job;
15
16     struct SwsContext  * context;
17     AVPicture            pic_tmp_in;
18     AVPicture            pic_tmp_crop;
19     AVPicture            pic_tmp_out;        
20     hb_buffer_t        * buf_scale;
21     hb_fifo_t          * subtitle_queue;
22     hb_fifo_t          * delay_queue;
23     int                  frames_to_extend;
24     int                  dropped_frames;
25     int                  extended_frames;
26 };
27
28 int  renderInit( hb_work_object_t *, hb_job_t * );
29 int  renderWork( hb_work_object_t *, hb_buffer_t **, hb_buffer_t ** );
30 void renderClose( hb_work_object_t * );
31
32 hb_work_object_t hb_render =
33 {   
34     WORK_RENDER,
35     "Renderer",
36     renderInit,
37     renderWork,
38     renderClose
39 };
40
41 /*
42  * getU() & getV()
43  *
44  * Utility function that finds where the U is in the YUV sub-picture
45  *
46  * The Y data is at the top, followed by U and V, but the U and V
47  * are half the width of the Y, i.e. each chroma element covers 2x2
48  * of the Y's.
49  */
50 static uint8_t *getU(uint8_t *data, int width, int height, int x, int y)
51 {
52     return(&data[(((y/2) * (width/2)) + (x/2)) + (width*height)]);
53 }
54
55 static uint8_t *getV(uint8_t *data, int width, int height, int x, int y)
56 {
57     return(&data[(((y/2) * (width/2)) + (x/2)) + (width*height) + 
58                  (width*height)/4]);
59 }
60
61 static void ApplySub( hb_job_t * job, hb_buffer_t * buf,
62                       hb_buffer_t ** _sub )
63 {
64     hb_buffer_t * sub = *_sub;
65     hb_title_t * title = job->title;
66     int i, j, offset_top, offset_left;
67     uint8_t * lum, * alpha, * out, * sub_chromaU, * sub_chromaV;
68
69     if( !sub )
70     {
71         return;
72     }
73
74     /* If necessary, move the subtitle so it is not in a cropped zone.
75        When it won't fit, we center it so we loose as much on both ends.
76        Otherwise we try to leave a 20px margin around it. */
77
78     if( sub->height > title->height - job->crop[0] - job->crop[1] - 40 )
79         offset_top = job->crop[0] + ( title->height - job->crop[0] -
80                 job->crop[1] - sub->height ) / 2;
81     else if( sub->y < job->crop[0] + 20 )
82         offset_top = job->crop[0] + 20;
83     else if( sub->y > title->height - job->crop[1] - 20 - sub->height )
84         offset_top = title->height - job->crop[1] - 20 - sub->height;
85     else
86         offset_top = sub->y;
87
88     if( sub->width > title->width - job->crop[2] - job->crop[3] - 40 )
89         offset_left = job->crop[2] + ( title->width - job->crop[2] -
90                 job->crop[3] - sub->width ) / 2;
91     else if( sub->x < job->crop[2] + 20 )
92         offset_left = job->crop[2] + 20;
93     else if( sub->x > title->width - job->crop[3] - 20 - sub->width )
94         offset_left = title->width - job->crop[3] - 20 - sub->width;
95     else
96         offset_left = sub->x;
97
98     lum   = sub->data;
99     alpha = lum + sub->width * sub->height;
100     sub_chromaU = alpha + sub->width * sub->height;
101     sub_chromaV = sub_chromaU + sub->width * sub->height;
102
103     out   = buf->data + offset_top * title->width + offset_left;
104
105     for( i = 0; i < sub->height; i++ )
106     {
107         if( offset_top + i >= 0 && offset_top + i < title->height )
108         {
109             for( j = 0; j < sub->width; j++ )
110             {
111                 if( offset_left + j >= 0 && offset_left + j < title->width )
112                 {
113                     uint8_t *chromaU, *chromaV;
114
115                     /*
116                      * Merge the luminance and alpha with the picture
117                      */
118                     out[j] = ( (uint16_t) out[j] * ( 16 - (uint16_t) alpha[j] ) +
119                                (uint16_t) lum[j] * (uint16_t) alpha[j] ) >> 4;   
120                     /*
121                      * Set the chroma (colour) based on whether there is
122                      * any alpha at all. Don't try to blend with the picture.
123                      */
124                     chromaU = getU(buf->data, title->width, title->height,
125                                    offset_left+j, offset_top+i);
126                     
127                     chromaV = getV(buf->data, title->width, title->height,
128                                    offset_left+j, offset_top+i);
129                     
130                     if( alpha[j] > 0 )
131                     {
132                         /*
133                          * Add the chroma from the sub-picture, as this is 
134                          * not a transparent element.
135                          */
136                         *chromaU = sub_chromaU[j];
137                         *chromaV = sub_chromaV[j];
138                     } 
139                 }
140             }
141         }
142
143         lum   += sub->width;
144         alpha += sub->width;
145         sub_chromaU += sub->width;
146         sub_chromaV += sub->width;
147         out   += title->width;
148     }
149
150     hb_buffer_close( _sub );
151 }
152
153 int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
154                 hb_buffer_t ** buf_out )
155 {
156     hb_work_private_t * pv = w->private_data;
157     hb_job_t   * job   = pv->job;
158     hb_title_t * title = job->title;
159     hb_buffer_t * in = *buf_in, * buf_tmp_in = *buf_in;
160     hb_buffer_t * ivtc_buffer = NULL;
161     
162     if(!in->data)
163     {
164         /* If the input buffer is end of stream, send out an empty one
165          * to the next stage as well. Note that this will result in us
166          * losing the current contents of the delay queue.
167          */
168        *buf_out = hb_buffer_init(0);
169        return HB_WORK_OK;
170     }
171
172     /*
173      * During the indepth_scan ditch the buffers here before applying filters or attempting to
174      * use the subtitles.
175      */
176     if( job->indepth_scan )
177     {      
178         *buf_out = NULL;
179         return HB_WORK_OK;
180     }
181     
182     /* Push subtitles onto queue just in case we need to delay a frame */
183     if( in->sub )
184     {
185         hb_fifo_push( pv->subtitle_queue, in->sub );
186     }
187     else
188     {
189         hb_fifo_push( pv->subtitle_queue,  hb_buffer_init(0) );
190     }
191     
192     /* Setup render buffer */
193     hb_buffer_t * buf_render = hb_buffer_init( 3 * job->width * job->height / 2 );  
194
195     /* Apply filters */
196     if( job->filters )
197     {
198         int filter_count = hb_list_count( job->filters );
199         int i;
200         
201         for( i = 0; i < filter_count; i++ )
202         {
203             hb_filter_object_t * filter = hb_list_item( job->filters, i );
204             
205             if( !filter )
206             {
207                 continue;
208             }            
209             
210             hb_buffer_t * buf_tmp_out = NULL;
211             
212             int result = filter->work( buf_tmp_in,
213                                        &buf_tmp_out, 
214                                        PIX_FMT_YUV420P, 
215                                        title->width, 
216                                        title->height, 
217                                        filter->private_data );
218             
219             /* 
220              * FILTER_OK:      set temp buffer to filter buffer, continue 
221              * FILTER_DELAY:   set temp buffer to NULL, abort 
222              * FILTER_DROP:    set temp buffer to NULL, pop subtitle, abort 
223              * FILTER_FAILED:  leave temp buffer alone, continue 
224              */
225             if( result == FILTER_OK )
226             {
227                 buf_tmp_in = buf_tmp_out;
228             }
229             else if( result == FILTER_DELAY )
230             {
231                 buf_tmp_in = NULL;
232                 break;
233             }            
234             else if( result == FILTER_DROP )
235             {
236                 hb_fifo_get( pv->subtitle_queue );
237                 buf_tmp_in = NULL;
238                 if( job->vfr )
239                 {
240                     pv->frames_to_extend += 4;
241                     pv->dropped_frames++;
242                 }
243                 break;
244             }
245         }
246     }   
247         
248     /* Apply subtitles */
249     if( buf_tmp_in )
250     {
251         hb_buffer_t * subtitles = hb_fifo_get( pv->subtitle_queue );        
252         if( subtitles )
253         {
254             ApplySub( job, buf_tmp_in, &subtitles );
255         }
256     }
257     
258     /* Apply crop/scale if specified */
259     if( buf_tmp_in && pv->context )
260     {
261         avpicture_fill( &pv->pic_tmp_in, buf_tmp_in->data, 
262                         PIX_FMT_YUV420P,
263                         title->width, title->height );
264         
265         avpicture_fill( &pv->pic_tmp_out, buf_render->data, 
266                         PIX_FMT_YUV420P,
267                         job->width, job->height );
268
269         // Crop; this alters the pointer to the data to point to the correct place for cropped frame
270         av_picture_crop( &pv->pic_tmp_crop, &pv->pic_tmp_in, PIX_FMT_YUV420P,
271                          job->crop[0], job->crop[2] );
272
273         // Scale pic_crop into pic_render according to the context set up in renderInit
274         sws_scale(pv->context,
275                   pv->pic_tmp_crop.data, pv->pic_tmp_crop.linesize,
276                   0, title->height - (job->crop[0] + job->crop[1]),
277                   pv->pic_tmp_out.data,  pv->pic_tmp_out.linesize);
278         
279         hb_buffer_copy_settings( buf_render, buf_tmp_in );
280         
281         buf_tmp_in = buf_render;
282     }  
283
284     /* Set output to render buffer */
285     (*buf_out) = buf_render;
286
287     if( buf_tmp_in == NULL )
288     {
289         /* Teardown and cleanup buffers if we are emitting NULL */
290         if( buf_in && *buf_in )
291         {
292             hb_buffer_close( buf_in );
293             *buf_in = NULL;
294         }        
295         if( buf_out && *buf_out )
296         {
297             hb_buffer_close( buf_out );        
298             *buf_out = NULL;
299         }
300     }
301     else if( buf_tmp_in != buf_render )
302     {    
303         /* Copy temporary results and settings into render buffer */
304         memcpy( buf_render->data, buf_tmp_in->data, buf_render->size );
305         hb_buffer_copy_settings( buf_render, buf_tmp_in );
306     }
307     
308     if (*buf_out)
309     {
310         hb_fifo_push( pv->delay_queue, *buf_out );
311         *buf_out = NULL;        
312     }
313
314     /*
315      * Keep the last three frames in our queue, this ensures that we have the last
316      * two always in there should we need to rewrite the durations on them.
317      */
318     if( hb_fifo_size( pv->delay_queue ) >= 3 )
319     {
320         *buf_out = hb_fifo_get( pv->delay_queue );
321     } 
322
323     if( *buf_out )
324     {
325         if( pv->frames_to_extend )
326         {
327             /*
328              * A frame's been dropped by VFR detelecine.
329              * Gotta make up the lost time. This will also
330              * slow down the video to 23.976fps.
331              * The dropped frame ran for 3003 ticks, so
332              * divvy it up amongst the 4 frames left behind.
333              * This is what the delay_queue is for;
334              * telecined sequences start 2 frames before
335              * the dropped frame, so to slow down the right
336              * ones you need a 2 frame delay between
337              * reading input and writing output.
338              */
339             ivtc_buffer = *buf_out;
340             
341             if (pv->frames_to_extend % 4)
342                 ivtc_buffer->stop += 751;
343             else
344                 ivtc_buffer->stop += 750;
345                 
346             pv->frames_to_extend--;
347             pv->extended_frames++;
348         }
349     }
350
351     return HB_WORK_OK;
352 }
353
354 void renderClose( hb_work_object_t * w )
355 {
356     hb_work_private_t * pv = w->private_data;   
357         
358     hb_log("render: dropped frames: %i (%i ticks)", pv->dropped_frames, (pv->dropped_frames * 3003) );
359     hb_log("render: extended frames: %i (%i ticks)", pv->extended_frames, ( ( pv->extended_frames / 4 ) * 3003 ) );
360     hb_log("render: Lost time: %i frames (%i ticks)", (pv->dropped_frames * 4) - (pv->extended_frames), (pv->dropped_frames * 3003) - ( ( pv->extended_frames / 4 ) * 3003 ) );
361
362     /* Cleanup subtitle queue */
363     if( pv->subtitle_queue )
364     {
365         hb_fifo_close( &pv->subtitle_queue );
366     }
367     
368     if( pv->delay_queue )
369     {
370         hb_fifo_close( &pv->delay_queue );
371     }
372    
373     /* Cleanup filters */
374     /* TODO: Move to work.c? */
375     if( pv->job->filters )
376     {
377         int filter_count = hb_list_count( pv->job->filters );
378         int i;
379         
380         for( i = 0; i < filter_count; i++ )
381         {
382             hb_filter_object_t * filter = hb_list_item( pv->job->filters, i );
383             
384             if( !filter ) continue;
385
386             filter->close( filter->private_data );
387         }
388         
389         hb_list_close( &pv->job->filters );
390     }    
391    
392     /* Cleanup render work structure */
393     free( pv );
394     w->private_data = NULL;    
395 }
396
397 int renderInit( hb_work_object_t * w, hb_job_t * job )
398 {   
399     /* Allocate new private work object */
400     hb_work_private_t * pv = calloc( 1, sizeof( hb_work_private_t ) );
401     pv->job = job;
402     w->private_data = pv;
403
404     /* Get title and title size */
405     hb_title_t * title = job->title;
406
407     /* If crop or scale is specified, setup rescale context */
408     if( job->crop[0] || job->crop[1] || job->crop[2] || job->crop[3] ||
409         job->width != title->width || job->height != title->height )
410     {
411         pv->context = sws_getContext(title->width  - (job->crop[2] + job->crop[3]),
412                                      title->height - (job->crop[0] + job->crop[1]),
413                                      PIX_FMT_YUV420P,
414                                      job->width, job->height, PIX_FMT_YUV420P,
415                                      (uint16_t)(SWS_LANCZOS|SWS_ACCURATE_RND), NULL, NULL, NULL);
416     }   
417     
418     /* Setup FIFO queue for subtitle cache */
419     pv->subtitle_queue = hb_fifo_init( 8 );    
420     pv->delay_queue = hb_fifo_init( 8 );
421     pv->frames_to_extend = 0;
422     pv->dropped_frames = 0;
423     pv->extended_frames = 0;
424     
425     /* Setup filters */
426     /* TODO: Move to work.c? */
427     if( job->filters )
428     {
429         int filter_count = hb_list_count( job->filters );
430         int i;
431         
432         for( i = 0; i < filter_count; i++ )
433         {
434             hb_filter_object_t * filter = hb_list_item( job->filters, i );
435
436             if( !filter ) continue;
437             
438             filter->private_data = filter->init( PIX_FMT_YUV420P,
439                                                  title->width,
440                                                  title->height,
441                                                  filter->settings );
442         }
443     }
444     
445     return 0;
446 }