/* $Id: muxcommon.c,v 1.23 2005/03/30 17:27:19 titer Exp $

   This file is part of the HandBrake source code.
   Homepage: <http://handbrake.m0k.org/>.
   It may be used under the terms of the GNU General Public License. */

#include "hb.h"

struct hb_mux_object_s
{
    HB_MUX_COMMON;
};

typedef struct
{
    hb_job_t * job;
    uint64_t   pts;

} hb_mux_t;

typedef struct
{
    hb_fifo_t     * fifo;
    hb_mux_data_t * mux_data;
    uint64_t        frames;
    uint64_t        bytes;

} hb_track_t;

static hb_track_t * GetTrack( hb_list_t * list )
{
    hb_buffer_t * buf;
    hb_track_t  * track = NULL, * track2;
    int64_t       pts = 0;
    int           i;

    for( i = 0; i < hb_list_count( list ); i++ )
    {
        track2 = hb_list_item( list, i );
        buf    = hb_fifo_see( track2->fifo );
        if( !buf )
        {
            return NULL;
        }
        if( !track || buf->start < pts )
        {
            track = track2;
            pts   = buf->start;
        }
    }
    return track;
}

static void MuxerFunc( void * _mux )
{
    hb_mux_t    * mux = _mux;
    hb_job_t    * job = mux->job;
    hb_title_t  * title = job->title;
    hb_audio_t  * audio;
    hb_list_t   * list;
    hb_buffer_t * buf;
    hb_track_t  * track;
    int           i;

    hb_mux_object_t * m = NULL;

    /* Get a real muxer */
    if( job->pass != 1 )
    {
        switch( job->mux )
        {
            case HB_MUX_MP4:
                m = hb_mux_mp4_init( job );
                break;
            case HB_MUX_AVI:
                m = hb_mux_avi_init( job );
                break;
            case HB_MUX_OGM:
                m = hb_mux_ogm_init( job );
                break;
        }
    }

    /* Wait for one buffer for each track */
    while( !*job->die && !job->done )
    {
        int ready;

        ready = 1;
        if( !hb_fifo_size( job->fifo_mpeg4 ) )
        {
            ready = 0;
        }
        for( i = 0; i < hb_list_count( title->list_audio ); i++ )
        {
            audio = hb_list_item( title->list_audio, i );
            if( !hb_fifo_size( audio->fifo_out ) )
            {
                ready = 0;
                break;
            }
        }

        if( ready )
        {
            break;
        }

        hb_snooze( 50 );
    }

    /* Create file, write headers */
    if( job->pass != 1 )
    {
        m->init( m );
    }

    /* Build list of fifos we're interested in */
    list = hb_list_init();

    track           = calloc( sizeof( hb_track_t ), 1 );
    track->fifo     = job->fifo_mpeg4;
    track->mux_data = job->mux_data;
    hb_list_add( list, track );

    for( i = 0; i < hb_list_count( title->list_audio ); i++ )
    {
        audio           = hb_list_item( title->list_audio, i );
        track           = calloc( sizeof( hb_track_t ), 1 );
        track->fifo     = audio->fifo_out;
        track->mux_data = audio->mux_data;
        hb_list_add( list, track );
    }

    while( !*job->die && !job->done )
    {
        if( !( track = GetTrack( list ) ) )
        {
            hb_snooze( 50 );
            continue;
        }

        buf = hb_fifo_get( track->fifo );
        if( job->pass != 1 )
        {
            m->mux( m, track->mux_data, buf );
            track->frames += 1;
            track->bytes  += buf->size;
            mux->pts = buf->stop;
        }
        hb_buffer_close( &buf );
    }

    if( job->pass != 1 )
    {
        struct stat sb;
        uint64_t bytes_total, frames_total;

        m->end( m );

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

            bytes_total  = 0;
            frames_total = 0;
            for( i = 0; i < hb_list_count( list ); i++ )
            {
                track = hb_list_item( list, i );
                hb_log( "mux: track %d, %lld bytes, %.2f kbps",
                        i, track->bytes,
                        90000.0 * track->bytes / mux->pts / 125 );
                if( !i && ( job->vquality < 0.0 || job->vquality > 1.0 ) )
                {
                    /* Video */
                    hb_log( "mux: video bitrate error, %+lld bytes",
                            track->bytes - mux->pts * job->vbitrate *
                            125 / 90000 );
                }
                bytes_total  += track->bytes;
                frames_total += track->frames;
            }

            if( bytes_total && frames_total )
            {
                hb_log( "mux: overhead, %.2f bytes per frame",
                        (float) ( sb.st_size - bytes_total ) /
                        frames_total );
            }
        }
    }

    free( m );

    for( i = 0; i < hb_list_count( list ); i++ )
    {
        track = hb_list_item( list, i );
        if( track->mux_data )
        {
            free( track->mux_data );
        }
        free( track );
    }
    hb_list_close( &list );

    free( mux );
}

hb_thread_t * hb_muxer_init( hb_job_t * job )
{
    hb_mux_t * mux = calloc( sizeof( hb_mux_t ), 1 );
    mux->job = job;
    return hb_thread_init( "muxer", MuxerFunc, mux,
                           HB_NORMAL_PRIORITY );
}
