Example #1
0
hb_thread_t * hb_scan_init( hb_handle_t * handle, volatile int * die,
                            const char * path, int title_index,
                            hb_title_set_t * title_set, int preview_count,
                            int store_previews, uint64_t min_duration )
{
    hb_scan_t * data = calloc( sizeof( hb_scan_t ), 1 );

    data->h            = handle;
    data->die          = die;
    data->path         = strdup( path );
    data->title_index  = title_index;
    data->title_set    = title_set;

    data->preview_count  = preview_count;
    data->store_previews = store_previews;
    data->min_title_duration = min_duration;

    // Initialize scan state
    hb_state_t state;
#define p state.param.scanning
    state.state   = HB_STATE_SCANNING;
    p.title_cur   = 1;
    p.title_count = 1;
    p.preview_cur = 0;
    p.preview_count = 1;
    p.progress = 0.0;
#undef p
    hb_set_state(handle, &state);

    return hb_thread_init( "scan", ScanFunc, data, HB_NORMAL_PRIORITY );
}
Example #2
0
static void UpdateState( hb_work_private_t  * r, int64_t start)
{
    hb_state_t state;
    uint64_t now;
    double avg;

    now = hb_get_date();
    if( !r->st_first )
    {
        r->st_first = now;
    }

    hb_get_state2(r->job->h, &state);
#define p state.param.working
    if ( !r->job->indepth_scan )
    {
        state.state = HB_STATE_SEARCHING;
        p.progress  = (float) start / (float) r->job->pts_to_start;
    }
    else
    {
        state.state = HB_STATE_WORKING;
        p.progress  = (float) start / (float) r->duration;
    }
    if( p.progress > 1.0 )
    {
        p.progress = 1.0;
    }
    p.rate_cur = 0.0;
    p.rate_avg = 0.0;
    if (now > r->st_first)
    {
        int eta;

        avg = 1000.0 * (double)start / (now - r->st_first);
        if ( !r->job->indepth_scan )
            eta = ( r->job->pts_to_start - start ) / avg;
        else
            eta = ( r->duration - start ) / avg;
        p.hours   = eta / 3600;
        p.minutes = ( eta % 3600 ) / 60;
        p.seconds = eta % 60;
    }
    else
    {
        p.hours    = -1;
        p.minutes  = -1;
        p.seconds  = -1;
    }
#undef p

    hb_set_state( r->job->h, &state );
}
Example #3
0
static void UpdateState( hb_work_private_t  * r )
{
    hb_state_t state;
    uint64_t now;
    double avg;

    if (!r->job->indepth_scan || !r->start_found)
    {
        // Only update state when sync.c is not handling state updates
        return;
    }

    now = hb_get_date();
    if( !r->st_first )
    {
        r->st_first = now;
    }

    hb_get_state2(r->job->h, &state);
#define p state.param.working
    state.state = HB_STATE_WORKING;
    p.progress  = (float) r->last_pts / (float) r->duration;
    if( p.progress > 1.0 )
    {
        p.progress = 1.0;
    }
    p.rate_cur = 0.0;
    p.rate_avg = 0.0;
    if (now > r->st_first)
    {
        int eta;

        avg = 1000.0 * (double)r->last_pts / (now - r->st_first);
        eta = (r->duration - r->last_pts) / avg;
        if (eta < 0)
        {
            eta = 0;
        }
        p.hours   = eta / 3600;
        p.minutes = ( eta % 3600 ) / 60;
        p.seconds = eta % 60;
    }
    else
    {
        p.hours    = -1;
        p.minutes  = -1;
        p.seconds  = -1;
    }
#undef p

    hb_set_state( r->job->h, &state );
}
Example #4
0
static void InitWorkState( hb_handle_t * h )
{
    hb_state_t state;

    state.state = HB_STATE_WORKING;
#define p state.param.working
    p.progress  = 0.0;
    p.rate_cur  = 0.0;
    p.rate_avg  = 0.0;
    p.hours     = -1;
    p.minutes   = -1;
    p.seconds   = -1; 
#undef p

    hb_set_state( h, &state );

}
Example #5
0
/***********************************************************************
 * ReaderFunc
 ***********************************************************************
 *
 **********************************************************************/
static void ReaderFunc( void * _r )
{
    hb_reader_t  * r = _r;
    hb_fifo_t   ** fifos;
    hb_buffer_t  * buf;
    hb_list_t    * list;
    int            n;
    int            chapter = -1;
    int            chapter_end = r->job->chapter_end;

    if ( r->title->type == HB_BD_TYPE )
    {
        if ( !( r->bd = hb_bd_init( r->title->path ) ) )
            return;
    }
    else if ( r->title->type == HB_DVD_TYPE )
    {
        if ( !( r->dvd = hb_dvd_init( r->title->path ) ) )
            return;
    }
    else if ( r->title->type == HB_STREAM_TYPE ||
              r->title->type == HB_FF_STREAM_TYPE )
    {
        if ( !( r->stream = hb_stream_open( r->title->path, r->title ) ) )
            return;
    }
    else
    {
        // Unknown type, should never happen
        return;
    }

    if (r->bd)
    {
        if( !hb_bd_start( r->bd, r->title ) )
        {
            hb_bd_close( &r->bd );
            return;
        }
        if ( r->job->start_at_preview )
        {
            // XXX code from DecodePreviews - should go into its own routine
            hb_bd_seek( r->bd, (float)r->job->start_at_preview /
                         ( r->job->seek_points ? ( r->job->seek_points + 1.0 ) : 11.0 ) );
        }
        else if ( r->job->pts_to_start )
        {
            // Note, bd seeks always put us to an i-frame.  no need
            // to start decoding early using r->pts_to_start
            hb_bd_seek_pts( r->bd, r->job->pts_to_start );
            r->job->pts_to_start = 0;
            r->start_found = 1;
        }
        else
        {
            hb_bd_seek_chapter( r->bd, r->job->chapter_start );
        }
        if (r->job->angle > 1)
        {
            hb_bd_set_angle( r->bd, r->job->angle - 1 );
        }
    }
    else if (r->dvd)
    {
        /*
         * XXX this code is a temporary hack that should go away if/when
         *     chapter merging goes away in libhb/dvd.c
         * map the start and end chapter numbers to on-media chapter
         * numbers since chapter merging could cause the handbrake numbers
         * to diverge from the media numbers and, if our chapter_end is after
         * a media chapter that got merged, we'll stop ripping too early.
         */
        int start = r->job->chapter_start;
        hb_chapter_t *chap = hb_list_item( r->title->list_chapter, chapter_end - 1 );

        chapter_end = chap->index;
        if (start > 1)
        {
           chap = hb_list_item( r->title->list_chapter, start - 1 );
           start = chap->index;
        }
        /* end chapter mapping XXX */

        if( !hb_dvd_start( r->dvd, r->title, start ) )
        {
            hb_dvd_close( &r->dvd );
            return;
        }
        if (r->job->angle)
        {
            hb_dvd_set_angle( r->dvd, r->job->angle );
        }

        if ( r->job->start_at_preview )
        {
            // XXX code from DecodePreviews - should go into its own routine
            hb_dvd_seek( r->dvd, (float)r->job->start_at_preview /
                         ( r->job->seek_points ? ( r->job->seek_points + 1.0 ) : 11.0 ) );
        }
    }
    else if ( r->stream && r->job->start_at_preview )
    {
        
        // XXX code from DecodePreviews - should go into its own routine
        hb_stream_seek( r->stream, (float)( r->job->start_at_preview - 1 ) /
                        ( r->job->seek_points ? ( r->job->seek_points + 1.0 ) : 11.0 ) );

    } 
    else if ( r->stream && r->job->pts_to_start )
    {
        int64_t pts_to_start = r->job->pts_to_start;
        
        // Find out what the first timestamp of the stream is
        // and then seek to the appropriate offset from it
        if ( ( buf = hb_stream_read( r->stream ) ) )
        {
            if ( buf->start > 0 )
            {
                pts_to_start += buf->start;
                r->pts_to_start += buf->start;
                r->job->pts_to_start += buf->start;
            }
        }
        
        if ( hb_stream_seek_ts( r->stream, pts_to_start ) >= 0 )
        {
            // Seek takes us to the nearest I-frame before the timestamp
            // that we want.  So we will retrieve the start time of the
            // first packet we get, subtract that from pts_to_start, and
            // inspect the reset of the frames in sync.
            r->start_found = 2;
            r->job->pts_to_start = pts_to_start;
        }
    } 
    else if( r->stream )
    {
        /*
         * Standard stream, seek to the starting chapter, if set, and track the
         * end chapter so that we end at the right time.
         */
        int start = r->job->chapter_start;
        hb_chapter_t *chap = hb_list_item( r->title->list_chapter, chapter_end - 1 );
        
        chapter_end = chap->index;
        if (start > 1)
        {
            chap = hb_list_item( r->title->list_chapter, start - 1 );
            start = chap->index;
        }
        
        /*
         * Seek to the start chapter.
         */
        hb_stream_seek_chapter( r->stream, start );
    }

    list  = hb_list_init();

    while( !*r->die && !r->job->done )
    {
        if (r->bd)
            chapter = hb_bd_chapter( r->bd );
        else if (r->dvd)
            chapter = hb_dvd_chapter( r->dvd );
        else if (r->stream)
            chapter = hb_stream_chapter( r->stream );

        if( chapter < 0 )
        {
            hb_log( "reader: end of the title reached" );
            break;
        }
        if( chapter > chapter_end )
        {
            hb_log( "reader: end of chapter %d (media %d) reached at media chapter %d",
                    r->job->chapter_end, chapter_end, chapter );
            break;
        }

        if (r->bd)
        {
          if( (buf = hb_bd_read( r->bd )) == NULL )
          {
              break;
          }
        }
        else if (r->dvd)
        {
          if( (buf = hb_dvd_read( r->dvd )) == NULL )
          {
              break;
          }
        }
        else if (r->stream)
        {
          if ( (buf = hb_stream_read( r->stream )) == NULL )
          {
            break;
          }
          if ( r->start_found == 2 )
          {
            // We will inspect the timestamps of each frame in sync
            // to skip from this seek point to the timestamp we
            // want to start at.
            if ( buf->start > 0 && buf->start < r->job->pts_to_start )
            {
                r->job->pts_to_start -= buf->start;
            }
            else if ( buf->start >= r->job->pts_to_start )
            {
                r->job->pts_to_start = 0;
                r->start_found = 1;
            }
          }
        }

        if( r->job->indepth_scan )
        {
            /*
             * Need to update the progress during a subtitle scan
             */
            hb_state_t state;

#define p state.param.working

            state.state = HB_STATE_WORKING;
            p.progress = (double)chapter / (double)r->job->chapter_end;
            if( p.progress > 1.0 )
            {
                p.progress = 1.0;
            }
            p.rate_avg = 0.0;
            p.hours    = -1;
            p.minutes  = -1;
            p.seconds  = -1;
            hb_set_state( r->job->h, &state );
        }

        (hb_demux[r->title->demuxer])( buf, list, &r->demux );

        while( ( buf = hb_list_item( list, 0 ) ) )
        {
            hb_list_rem( list, buf );
            fifos = GetFifoForId( r->job, buf->id );

            if ( fifos && ! r->saw_video && !r->job->indepth_scan )
            {
                // The first data packet with a PTS from an audio or video stream
                // that we're decoding defines 'time zero'. Discard packets until
                // we get one.
                if ( buf->start != -1 && buf->renderOffset != -1 &&
                     ( buf->id == r->title->video_id || is_audio( r, buf->id ) ) )
                {
                    // force a new scr offset computation
                    r->scr_changes = r->demux.scr_changes - 1;
                    // create a stream state if we don't have one so the
                    // offset will get computed correctly.
                    id_to_st( r, buf, 1 );
                    r->saw_video = 1;
                    hb_log( "reader: first SCR %"PRId64" id 0x%x DTS %"PRId64,
                            r->demux.last_scr, buf->id, buf->renderOffset );
                }
                else
                {
                    fifos = NULL;
                }
            }
            if( fifos )
            {
                if ( buf->renderOffset != -1 )
                {
                    if ( r->scr_changes != r->demux.scr_changes )
                    {
                        // This is the first audio or video packet after an SCR
                        // change. Compute a new scr offset that would make this
                        // packet follow the last of this stream with the 
                        // correct average spacing.
                        stream_timing_t *st = id_to_st( r, buf, 0 );

                        // if this is the video stream and we don't have
                        // audio yet or this is an audio stream
                        // generate a new scr
                        if ( st->is_audio ||
                             ( st == r->stream_timing && !r->saw_audio ) )
                        {
                            new_scr_offset( r, buf );
                        }
                        else
                        {
                            // defer the scr change until we get some
                            // audio since audio has a timestamp per
                            // frame but video & subtitles don't. Clear
                            // the timestamps so the decoder will generate
                            // them from the frame durations.
                            buf->start = -1;
                            buf->renderOffset = -1;
                        }
                    }
                }
                if ( buf->start != -1 )
                {
                    int64_t start = buf->start - r->scr_offset;
                    if ( !r->start_found )
                        UpdateState( r, start );

                    if ( !r->start_found &&
                        start >= r->pts_to_start )
                    {
                        // pts_to_start point found
                        r->start_found = 1;
                    }
                    // This log is handy when you need to debug timing problems
                    //hb_log("id %x scr_offset %ld start %ld --> %ld", 
                    //        buf->id, r->scr_offset, buf->start, 
                    //        buf->start - r->scr_offset);
                    buf->start -= r->scr_offset;
                }
                if ( buf->renderOffset != -1 )
                {
                    if ( r->scr_changes == r->demux.scr_changes )
                    {
                        // This packet is referenced to the same SCR as the last.
                        // Adjust timestamp to remove the System Clock Reference
                        // offset then update the average inter-packet time
                        // for this stream.
                        buf->renderOffset -= r->scr_offset;
                        update_ipt( r, buf );
                    }
                }
                if ( !r->start_found )
                {
                    hb_buffer_close( &buf );
                    continue;
                }

                buf->sequence = r->sequence++;
                /* if there are mutiple output fifos, send a copy of the
                 * buffer down all but the first (we have to not ship the
                 * original buffer or we'll race with the thread that's
                 * consuming the buffer & inject garbage into the data stream). */
                for( n = 1; fifos[n] != NULL; n++)
                {
                    hb_buffer_t *buf_copy = hb_buffer_init( buf->size );
                    hb_buffer_copy_settings( buf_copy, buf );
                    memcpy( buf_copy->data, buf->data, buf->size );
                    push_buf( r, fifos[n], buf_copy );
                }
                push_buf( r, fifos[0], buf );
            }
            else
            {
                hb_buffer_close( &buf );
            }
        }
    }

    // send empty buffers downstream to video & audio decoders to signal we're done.
    if( !*r->die && !r->job->done )
    {
        push_buf( r, r->job->fifo_mpeg2, hb_buffer_init(0) );

        hb_audio_t *audio;
        for( n = 0; (audio = hb_list_item( r->job->title->list_audio, n)); ++n )
        {
            if ( audio->priv.fifo_in )
                push_buf( r, audio->priv.fifo_in, hb_buffer_init(0) );
        }

        hb_subtitle_t *subtitle;
        for( n = 0; (subtitle = hb_list_item( r->job->title->list_subtitle, n)); ++n )
        {
            if ( subtitle->fifo_in && subtitle->source == VOBSUB)
                push_buf( r, subtitle->fifo_in, hb_buffer_init(0) );
        }
    }

    hb_list_empty( &list );
    if (r->bd)
    {
        hb_bd_stop( r->bd );
        hb_bd_close( &r->bd );
    }
    else if (r->dvd)
    {
        hb_dvd_stop( r->dvd );
        hb_dvd_close( &r->dvd );
    }
    else if (r->stream)
    {
        hb_stream_close(&r->stream);
    }

    if ( r->stream_timing )
    {
        free( r->stream_timing );
    }

    hb_log( "reader: done. %d scr changes", r->demux.scr_changes );
    if ( r->demux.dts_drops )
    {
        hb_log( "reader: %d drops because DTS out of range", r->demux.dts_drops );
    }

    free( r );
    _r = NULL;
}
Example #6
0
static void ScanFunc( void * _data )
{
    hb_scan_t  * data = (hb_scan_t *) _data;
    hb_title_t * title;
    int          i;
    int          feature = 0;

    data->bd = NULL;
    data->dvd = NULL;
    data->stream = NULL;

    /* Try to open the path as a DVD. If it fails, try as a file */
    if( ( data->bd = hb_bd_init( data->path ) ) )
    {
        hb_log( "scan: BD has %d title(s)",
                hb_bd_title_count( data->bd ) );
        if( data->title_index )
        {
            /* Scan this title only */
            hb_list_add( data->list_title, hb_bd_title_scan( data->bd,
                         data->title_index, 0 ) );
        }
        else
        {
            /* Scan all titles */
            for( i = 0; i < hb_bd_title_count( data->bd ); i++ )
            {
                hb_list_add( data->list_title, hb_bd_title_scan( data->bd, 
                             i + 1, data->min_title_duration ) );
            }
            feature = hb_bd_main_feature( data->bd, data->list_title );
        }
    }
    else if( ( data->dvd = hb_dvd_init( data->path ) ) )
    {
        hb_log( "scan: DVD has %d title(s)",
                hb_dvd_title_count( data->dvd ) );
        if( data->title_index )
        {
            /* Scan this title only */
            hb_list_add( data->list_title, hb_dvd_title_scan( data->dvd,
                            data->title_index, 0 ) );
        }
        else
        {
            /* Scan all titles */
            for( i = 0; i < hb_dvd_title_count( data->dvd ); i++ )
            {
                hb_list_add( data->list_title, hb_dvd_title_scan( data->dvd, 
                            i + 1, data->min_title_duration ) );
            }
            feature = hb_dvd_main_feature( data->dvd, data->list_title );
        }
    }
    else if ( ( data->batch = hb_batch_init( data->path ) ) )
    {
        if( data->title_index )
        {
            /* Scan this title only */
            title = hb_batch_title_scan( data->batch, data->title_index );
            if ( title )
            {
                hb_list_add( data->list_title, title );
            }
        }
        else
        {
            /* Scan all titles */
            for( i = 0; i < hb_batch_title_count( data->batch ); i++ )
            {
                hb_title_t * title;

                title = hb_batch_title_scan( data->batch, i + 1 );
                if ( title != NULL )
                {
                    hb_list_add( data->list_title, title );
                }
            }
        }
    }
    else
    {
        hb_title_t * title = hb_title_init( data->path, 0 );
        if ( (data->stream = hb_stream_open( data->path, title, 1 ) ) != NULL )
        {
            title = hb_stream_title_scan( data->stream, title );
            if ( title )
                hb_list_add( data->list_title, title );
        }
        else
        {
            hb_title_close( &title );
            hb_log( "scan: unrecognized file type" );
            return;
        }
    }

    for( i = 0; i < hb_list_count( data->list_title ); )
    {
        int j;
        hb_state_t state;
        hb_audio_t * audio;

        if ( *data->die )
        {
            goto finish;
        }
        title = hb_list_item( data->list_title, i );

#define p state.param.scanning
        /* Update the UI */
        state.state   = HB_STATE_SCANNING;
        p.title_cur   = title->index;
        p.title_count = data->dvd ? hb_dvd_title_count( data->dvd ) : 
                        data->bd ? hb_bd_title_count( data->bd ) :
                        data->batch ? hb_batch_title_count( data->batch ) :
                                   hb_list_count(data->list_title);
        hb_set_state( data->h, &state );
#undef p

        /* Decode previews */
        /* this will also detect more AC3 / DTS information */
        if( !DecodePreviews( data, title ) )
        {
            /* TODO: free things */
            hb_list_rem( data->list_title, title );
            for( j = 0; j < hb_list_count( title->list_audio ); j++)
            {
                audio = hb_list_item( title->list_audio, j );
                if ( audio->priv.scan_cache )
                {
                    hb_fifo_flush( audio->priv.scan_cache );
                    hb_fifo_close( &audio->priv.scan_cache );
                }
            }
            hb_title_close( &title );
            continue;
        }

        /* Make sure we found audio rates and bitrates */
        for( j = 0; j < hb_list_count( title->list_audio ); )
        {
            audio = hb_list_item( title->list_audio, j );
            if ( audio->priv.scan_cache )
            {
                hb_fifo_flush( audio->priv.scan_cache );
                hb_fifo_close( &audio->priv.scan_cache );
            }
            if( !audio->config.in.bitrate )
            {
                hb_log( "scan: removing audio 0x%x because no bitrate found",
                        audio->id );
                hb_list_rem( title->list_audio, audio );
                free( audio );
                continue;
            }
            j++;
        }

        if ( data->dvd || data->bd )
        {
            // The subtitle width and height needs to be set to the 
            // title widht and height for DVDs.  title width and
            // height don't get set until we decode previews, so
            // we can't set subtitle width/height till we get here.
            for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
            {
                hb_subtitle_t *subtitle = hb_list_item( title->list_subtitle, j );
                if ( subtitle->source == VOBSUB || subtitle->source == PGSSUB )
                {
                    subtitle->width = title->width;
                    subtitle->height = title->height;
                }
            }
        }
        i++;
    }

    /* Init jobs templates */
    for( i = 0; i < hb_list_count( data->list_title ); i++ )
    {
        hb_job_t * job;

        title      = hb_list_item( data->list_title, i );
        job        = calloc( sizeof( hb_job_t ), 1 );
        title->job = job;

        job->title = title;
        job->feature = feature;

        /* Set defaults settings */
        job->chapter_start = 1;
        job->chapter_end   = hb_list_count( title->list_chapter );

        /* Autocrop by default. Gnark gnark */
        memcpy( job->crop, title->crop, 4 * sizeof( int ) );

        /* Preserve a source's pixel aspect, if it's available. */
        if( title->pixel_aspect_width && title->pixel_aspect_height )
        {
            job->anamorphic.par_width  = title->pixel_aspect_width;
            job->anamorphic.par_height = title->pixel_aspect_height;
        }

        if( title->aspect != 0 && title->aspect != 1. &&
            !job->anamorphic.par_width && !job->anamorphic.par_height)
        {
            hb_reduce( &job->anamorphic.par_width, &job->anamorphic.par_height,
                       (int)(title->aspect * title->height + 0.5), title->width );
        }

        job->width = title->width - job->crop[2] - job->crop[3];
        hb_fix_aspect( job, HB_KEEP_WIDTH );
        if( job->height > title->height - job->crop[0] - job->crop[1] )
        {
            job->height = title->height - job->crop[0] - job->crop[1];
            hb_fix_aspect( job, HB_KEEP_HEIGHT );
        }

        hb_log( "scan: title (%d) job->width:%d, job->height:%d",
                i, job->width, job->height );

        job->keep_ratio = 1;

        job->vcodec     = HB_VCODEC_FFMPEG_MPEG4;
        job->vquality   = -1.0;
        job->vbitrate   = 1000;
        job->pass       = 0;
        job->vrate      = title->rate;
        job->vrate_base = title->rate_base;

        job->list_audio = hb_list_init();
        job->list_subtitle = hb_list_init();
        job->list_filter = hb_list_init();

        job->mux = HB_MUX_MP4;
    }

finish:

    if( data->bd )
    {
        hb_bd_close( &data->bd );
    }
    if( data->dvd )
    {
        hb_dvd_close( &data->dvd );
    }
    if (data->stream)
    {
        hb_stream_close(&data->stream);
    }
    if( data->batch )
    {
        hb_batch_close( &data->batch );
    }
    free( data->path );
    free( data );
    _data = NULL;
}
Example #7
0
void muxClose( hb_work_object_t * w )
{
    hb_work_private_t * pv = w->private_data;
    hb_mux_t    * mux = pv->mux;
    hb_job_t    * job = pv->job;
    hb_track_t  * track;
    int           i;

    hb_lock( mux->mutex );
    if ( --mux->ref == 0 )
    {
        // Update state before closing muxer.  Closing the muxer
        // may initiate optimization which can take a while and
        // we want the muxing state to be visible while this is
        // happening.
        if( job->pass == 0 || job->pass == 2 )
        {
            /* Update the UI */
            hb_state_t state;
            state.state = HB_STATE_MUXING;
            state.param.muxing.progress = 0;
            hb_set_state( job->h, &state );
        }

        if( mux->m )
        {
            mux->m->end( mux->m );
            free( mux->m );
        }

        // we're all done muxing -- print final stats and cleanup.
        if( job->pass == 0 || job->pass == 2 )
        {
            struct stat sb;
            uint64_t bytes_total, frames_total;

            if( !stat( job->file, &sb ) )
            {
                hb_deep_log( 2, "mux: file size, %"PRId64" bytes", (uint64_t) sb.st_size );

                bytes_total  = 0;
                frames_total = 0;
                for( i = 0; i < mux->ntracks; ++i )
                {
                    track = mux->track[i];
                    hb_log( "mux: track %d, %"PRId64" frames, %"PRId64" bytes, %.2f kbps, fifo %d",
                            i, track->frames, track->bytes,
                            90000.0 * track->bytes / mux->pts / 125,
                            track->mf.flen );
                    if( !i && ( job->vquality < 0.0 || job->vquality > 1.0 ) )
                    {
                        /* Video */
                        hb_deep_log( 2, "mux: video bitrate error, %+"PRId64" bytes",
                                (int64_t)(track->bytes - mux->pts * job->vbitrate * 125 / 90000) );
                    }
                    bytes_total  += track->bytes;
                    frames_total += track->frames;
                }

                if( bytes_total && frames_total )
                {
                    hb_deep_log( 2, "mux: overhead, %.2f bytes per frame",
                            (float) ( sb.st_size - bytes_total ) /
                            frames_total );
                }
            }
        }
    
        for( i = 0; i < mux->ntracks; ++i )
        {
            hb_buffer_t * b;
            track = mux->track[i];
            while ( (b = mf_pull( track )) != NULL )
            {
                hb_buffer_close( &b );
            }
            if( track->mux_data )
            {
                free( track->mux_data );
                free( track->mf.fifo );
            }
            free( track );
        }
        hb_unlock( mux->mutex );
        hb_lock_close( &mux->mutex );
        free( mux );
    }
    else
    {
        hb_unlock( mux->mutex );
    }
    free( pv );
    w->private_data = NULL;
}