/* $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.fr/>.
   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;
    int             eof;
} hb_track_t;

static hb_track_t * GetTrack( hb_list_t * list, hb_job_t *job )
{
    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 );
        if ( ! track2->eof )
        {
            buf    = hb_fifo_see( track2->fifo );
            if( !buf )
            {
                // XXX the libmkv muxer will produce unplayable files if the
                // audio & video are far out of sync.  To keep them in sync we require
                // that *all* fifos have a buffer then we take the oldest.
                // Unfortunately this means we can hang in a deadlock with the
                // reader process filling the fifos.
                if ( job->mux == HB_MUX_MKV )
                {
                    return NULL;
                }

                // To make sure we don't camp on one fifo & prevent the others
                // from making progress we take the earliest data of all the
                // data that's currently available but we don't care if some
                // fifos don't have data.
                continue;
            }
            if ( buf->size <= 0 )
            {
                // EOF - mark this track as done
                buf = hb_fifo_get( track2->fifo );
                hb_buffer_close( &buf );
                track2->eof = 1;
                continue;
            }
            if( !track || buf->start < pts )
            {
                track = track2;
                pts   = buf->start;
            }
        }
    }
    return track;
}

static int AllTracksDone( hb_list_t * list )
{
    hb_track_t  * track;
    int           i;

    for( i = 0; i < hb_list_count( list ); i++ )
    {
        track = hb_list_item( list, i );
        if ( track->eof == 0 )
        {
            return 0;
        }
    }
    return 1;
}

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 == 0 || job->pass == 2)
    {
        switch( job->mux )
        {
            case HB_MUX_MP4:
            case HB_MUX_PSP:
			case HB_MUX_IPOD:
                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;
            case HB_MUX_MKV:
                m = hb_mux_mkv_init( job );
        }
    }

    /* Create file, write headers */
    if( job->pass == 0 || job->pass == 2 )
    {
        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->priv.fifo_out;
        track->mux_data = audio->priv.mux_data;
        hb_list_add( list, track );
    }

	int thread_sleep_interval = 50;
	while( !*job->die )
    {
        if( !( track = GetTrack( list, job ) ) )
        {
            if ( AllTracksDone( list )  )
            {
                // all our input fifos have signaled EOF
                break;
            }
            hb_snooze( thread_sleep_interval );
            continue;
        }

        buf = hb_fifo_get( track->fifo );
        if( job->pass == 0 || job->pass == 2 )
        {
            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 == 0 || job->pass == 2 )
    {
        struct stat sb;
        uint64_t bytes_total, frames_total;

#define p state.param.muxing
        /* Update the UI */
        hb_state_t state;
        state.state   = HB_STATE_MUXING;
		p.progress = 0;
        hb_set_state( job->h, &state );
#undef p
        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 );
}