X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=libhb%2Fmuxcommon.c;h=1f39df11d92ab52c14ac5fa398aca98ae72dab46;hb=ac24fef71ded1f1884000fb9ce039b71587a8f30;hp=17628df11656668f2689239ef5a5bcf59bd0747c;hpb=8efb73bbf4681fb94038ad5490e9a5ad4c455045;p=handbrake-jp%2Fhandbrake-jp-git.git
diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c
index 17628df1..1f39df11 100644
--- a/libhb/muxcommon.c
+++ b/libhb/muxcommon.c
@@ -1,7 +1,7 @@
/* $Id: muxcommon.c,v 1.23 2005/03/30 17:27:19 titer Exp $
This file is part of the HandBrake source code.
- Homepage: .
+ Homepage: .
It may be used under the terms of the GNU General Public License. */
#include "hb.h"
@@ -13,10 +13,11 @@ struct hb_mux_object_s
typedef struct
{
- hb_job_t * job;
- uint64_t pts;
-
-} hb_mux_t;
+ hb_buffer_t **fifo;
+ uint32_t in; // number of bufs put into fifo
+ uint32_t out; // number of bufs taken out of fifo
+ uint32_t flen; // fifo length (must be power of two)
+} mux_fifo_t;
typedef struct
{
@@ -24,32 +25,182 @@ typedef struct
hb_mux_data_t * mux_data;
uint64_t frames;
uint64_t bytes;
-
+ mux_fifo_t mf;
} hb_track_t;
-static hb_track_t * GetTrack( hb_list_t * list )
+typedef struct
{
- hb_buffer_t * buf;
- hb_track_t * track = NULL, * track2;
- int64_t pts = 0;
- int i;
+ hb_job_t *job;
+ double pts; // end time of next muxing chunk
+ double interleave; // size (in 90KHz ticks) of media chunks we mux
+ uint32_t ntracks; // total number of tracks we're muxing
+ uint32_t eof; // bitmask of track with eof
+ uint32_t rdy; // bitmask of tracks ready to output
+ uint32_t allEof; // valid bits in eof (all tracks)
+ uint32_t allRdy; // valid bits in rdy (audio & video tracks)
+ hb_track_t *track[32]; // array of tracks to mux ('ntrack' elements)
+ // NOTE- this array could be dynamically allocated
+ // but the eof & rdy logic has to be changed to
+ // handle more than 32 tracks anyway so we keep
+ // it simple and fast.
+} hb_mux_t;
- for( i = 0; i < hb_list_count( list ); i++ )
+// The muxer handles two different kinds of media: Video and audio tracks
+// are continuous: once they start they generate continuous, consecutive
+// sequence of bufs until they end. The muxer will time align all continuous
+// media tracks so that their data will be well interleaved in the output file.
+// (Smooth, low latency playback with minimal player buffering requires that
+// data that's going to be presented close together in time also be close
+// together in the output file). Since HB's audio and video encoders run at
+// different speeds, the time-aligning involves buffering *all* the continuous
+// media tracks until a frame with a timestamp beyond the current alignment
+// point arrives on the slowest fifo (usually the video encoder).
+//
+// The other kind of media, subtitles, close-captions, vobsubs and
+// similar tracks, are intermittent. They generate frames sporadically or on
+// human time scales (seconds) rather than near the video frame rate (milliseconds).
+// If intermittent sources were treated like continuous sources huge sections of
+// audio and video would get buffered waiting for the next subtitle to show up.
+// To keep this from happening the muxer doesn't wait for intermittent tracks
+// (essentially it assumes that they will always go through the HB processing
+// pipeline faster than the associated video). They are still time aligned and
+// interleaved at the appropriate point in the output file.
+
+// This routine adds another track for the muxer to process. The media input
+// stream will be read from HandBrake fifo 'fifo'. Buffers read from that
+// stream will be time-aligned with all the other media streams then passed
+// to the container-specific 'mux' routine with argument 'mux_data' (see
+// routine OutputTrackChunk). 'is_continuous' must be 1 for an audio or video
+// track and 0 otherwise (see above).
+
+static void add_mux_track( hb_mux_t *mux, hb_fifo_t *fifo, hb_mux_data_t *mux_data,
+ int is_continuous )
+{
+ int max_tracks = sizeof(mux->track) / sizeof(*(mux->track));
+ if ( mux->ntracks >= max_tracks )
{
- track2 = hb_list_item( list, i );
- buf = hb_fifo_see( track2->fifo );
- if( !buf )
+ hb_error( "add_mux_track: too many tracks (>%d)", max_tracks );
+ return;
+ }
+
+ hb_track_t *track = calloc( sizeof( hb_track_t ), 1 );
+ track->fifo = fifo;
+ track->mux_data = mux_data;
+ track->mf.flen = 8;
+ track->mf.fifo = calloc( sizeof(track->mf.fifo[0]), track->mf.flen );
+
+ int t = mux->ntracks++;
+ mux->track[t] = track;
+ mux->allEof |= 1 << t;
+ mux->allRdy |= is_continuous << t;
+}
+
+static void mf_push( hb_track_t *track, hb_buffer_t *buf )
+{
+ uint32_t mask = track->mf.flen - 1;
+ uint32_t in = track->mf.in;
+ if ( ( ( in + 1 ) & mask ) == ( track->mf.out & mask ) )
+ {
+ // fifo is full - expand it to double the current size.
+ // This is a bit tricky because when we change the size
+ // it changes the modulus (mask) used to convert the in
+ // and out counters to fifo indices. Since existing items
+ // will be referenced at a new location after the expand
+ // we can't just realloc the fifo. If there were
+ // hundreds of fifo entries it would be worth it to have code
+ // for each of the four possible before/after configurations
+ // but these fifos are small so we just allocate a new chunk
+ // of memory then do element by element copies using the old &
+ // new masks then free the old fifo's memory..
+ track->mf.flen *= 2;
+ uint32_t nmask = track->mf.flen - 1;
+ hb_buffer_t **nfifo = malloc( track->mf.flen * sizeof(*nfifo) );
+ int indx = track->mf.out;
+ while ( indx != track->mf.in )
{
- return NULL;
+ nfifo[indx & nmask] = track->mf.fifo[indx & mask];
+ ++indx;
}
- if( !track || buf->start < pts )
+ free( track->mf.fifo );
+ track->mf.fifo = nfifo;
+ mask = nmask;
+ }
+ track->mf.fifo[in & mask] = buf;
+ track->mf.in = in + 1;
+}
+
+static hb_buffer_t *mf_pull( hb_track_t *track )
+{
+ hb_buffer_t *b = NULL;
+ if ( track->mf.out != track->mf.in )
+ {
+ // the fifo isn't empty
+ b = track->mf.fifo[track->mf.out & (track->mf.flen - 1)];
+ ++track->mf.out;
+ }
+ return b;
+}
+
+static hb_buffer_t *mf_peek( hb_track_t *track )
+{
+ return track->mf.out == track->mf.in ?
+ NULL : track->mf.fifo[track->mf.out & (track->mf.flen - 1)];
+}
+
+static void MoveToInternalFifos( hb_mux_t *mux )
+{
+ int i;
+ int discard = mux->job->pass != 0 && mux->job->pass != 2;
+
+ for( i = 0; i < mux->ntracks; ++i )
+ {
+ if ( ( mux->eof & (1 << i) ) == 0 )
{
- track = track2;
- pts = buf->start;
- //hb_log("track: #%d, frame count %ld, fifo size %d", i, track->frames, hb_fifo_size( track->fifo));
+ hb_track_t *track = mux->track[i];
+ hb_buffer_t *buf;
+
+ // move all the buffers on the track's fifo to our internal
+ // fifo so that (a) we don't deadlock in the reader and
+ // (b) we can control how data from multiple tracks is
+ // interleaved in the output file.
+ while ( ( buf = hb_fifo_get( track->fifo ) ) )
+ {
+ if ( buf->size <= 0 )
+ {
+ // EOF - mark this track as done
+ hb_buffer_close( &buf );
+ mux->eof |= ( 1 << i );
+ mux->rdy |= ( 1 << i );
+ continue;
+ }
+ if ( discard )
+ {
+ hb_buffer_close( &buf );
+ continue;
+ }
+ mf_push( track, buf );
+ if ( buf->stop >= mux->pts )
+ {
+ // buffer is past our next interleave point so
+ // note that this track is ready to be output.
+ mux->rdy |= ( 1 << i );
+ }
+ }
}
}
- return track;
+}
+
+static void OutputTrackChunk( hb_mux_t *mux, hb_track_t *track, hb_mux_object_t *m )
+{
+ hb_buffer_t *buf;
+
+ while ( ( buf = mf_peek( track ) ) != NULL && buf->start < mux->pts )
+ {
+ m->mux( m, track->mux_data, mf_pull( track ) );
+ track->frames += 1;
+ track->bytes += buf->size;
+ hb_buffer_close( &buf );
+ }
}
static void MuxerFunc( void * _mux )
@@ -57,133 +208,154 @@ 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;
+ // set up to interleave track data in blocks of 1 video frame time.
+ // (the best case for buffering and playout latency). The container-
+ // specific muxers can reblock this into bigger chunks if necessary.
+ mux->interleave = 90000. * (double)job->vrate_base / (double)job->vrate;
+ mux->pts = mux->interleave;
+
/* Get a real muxer */
- if( job->pass != 1 )
+ 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;
- }
- }
-
- /* 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;
- }
+ 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 );
+ break;
+ default:
+ hb_error( "No muxer selected, exiting" );
+ *job->die = 1;
}
-
- if( ready )
+ /* Create file, write headers */
+ if( m )
{
- break;
+ m->init( m );
}
-
- 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();
- hb_log( "mux: audio list count %d", hb_list_count( title->list_audio ));
- track = calloc( sizeof( hb_track_t ), 1 );
- track->fifo = job->fifo_mpeg4;
- track->mux_data = job->mux_data;
- hb_list_add( list, track );
+ add_mux_track( mux, job->fifo_mpeg4, job->mux_data, 1 );
- hb_log( "mux: 1");
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 );
+ hb_audio_t *audio = hb_list_item( title->list_audio, i );
+ add_mux_track( mux, audio->priv.fifo_out, audio->priv.mux_data, 1 );
+ }
+
+ for( i = 0; i < hb_list_count( title->list_subtitle ); i++ )
+ {
+ hb_subtitle_t *subtitle = hb_list_item( title->list_subtitle, i );
+
+ if (subtitle->config.dest != PASSTHRUSUB)
+ continue;
+ add_mux_track( mux, subtitle->fifo_out, subtitle->mux_data, 0 );
}
- hb_log( "mux: 2");
- while( !*job->die && !job->done )
+ // The following 'while' is the main muxing loop.
+
+ int thread_sleep_interval = 50;
+ while( !*job->die )
{
- if( !( track = GetTrack( list ) ) )
+ MoveToInternalFifos( mux );
+ if ( ( mux->rdy & mux->allRdy ) != mux->allRdy )
{
- hb_snooze( 50 );
+ hb_snooze( thread_sleep_interval );
continue;
}
- buf = hb_fifo_get( track->fifo );
- if( job->pass != 1 )
+ // all tracks have at least 'interleave' ticks of data. Output
+ // all that we can in 'interleave' size chunks.
+ while ( ( mux->rdy & mux->allRdy ) == mux->allRdy )
{
- m->mux( m, track->mux_data, buf );
- track->frames += 1;
- track->bytes += buf->size;
- mux->pts = buf->stop;
+ for ( i = 0; i < mux->ntracks; ++i )
+ {
+ track = mux->track[i];
+ OutputTrackChunk( mux, track, m );
+
+ // if the track is at eof or still has data that's past
+ // our next interleave point then leave it marked as rdy.
+ // Otherwise clear rdy.
+ if ( ( mux->eof & (1 << i) ) == 0 &&
+ ( track->mf.out == track->mf.in ||
+ track->mf.fifo[(track->mf.in-1) & (track->mf.flen-1)]->stop
+ < mux->pts + mux->interleave ) )
+ {
+ mux->rdy &=~ ( 1 << i );
+ }
+ }
+
+ // if all the tracks are at eof we're just purging their
+ // remaining data -- keep going until all internal fifos are empty.
+ if ( mux->eof == mux->allEof )
+ {
+ for ( i = 0; i < mux->ntracks; ++i )
+ {
+ if ( mux->track[i]->mf.out != mux->track[i]->mf.in )
+ {
+ break;
+ }
+ }
+ if ( i >= mux->ntracks )
+ {
+ goto finished;
+ }
+ }
+ mux->pts += mux->interleave;
}
- hb_buffer_close( &buf );
}
- hb_log( "mux: 3");
- if( job->pass != 1 )
+ // we're all done muxing -- print final stats and cleanup.
+finished:
+ if( job->pass == 0 || job->pass == 2 )
{
struct stat sb;
uint64_t bytes_total, frames_total;
- m->end( m );
- hb_log( "mux: 4");
+ /* Update the UI */
+ hb_state_t state;
+ state.state = HB_STATE_MUXING;
+ state.param.muxing.progress = 0;
+ hb_set_state( job->h, &state );
+
+ if( m )
+ {
+ m->end( m );
+ }
if( !stat( job->file, &sb ) )
{
- hb_log( "mux: file size, %lld bytes", (uint64_t) sb.st_size );
+ hb_deep_log( 2, "mux: file size, %"PRId64" bytes", (uint64_t) sb.st_size );
bytes_total = 0;
frames_total = 0;
- for( i = 0; i < hb_list_count( list ); i++ )
+ for( i = 0; i < mux->ntracks; ++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 );
+ 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_log( "mux: video bitrate error, %+lld bytes",
- track->bytes - mux->pts * job->vbitrate *
- 125 / 90000 );
+ 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;
@@ -191,25 +363,28 @@ static void MuxerFunc( void * _mux )
if( bytes_total && frames_total )
{
- hb_log( "mux: overhead, %.2f bytes per frame",
+ hb_deep_log( 2, "mux: overhead, %.2f bytes per frame",
(float) ( sb.st_size - bytes_total ) /
frames_total );
}
}
}
+
+ if( m )
+ {
+ free( m );
+ }
- free( m );
-
- for( i = 0; i < hb_list_count( list ); i++ )
+ for( i = 0; i < mux->ntracks; ++i )
{
- track = hb_list_item( list, i );
+ track = mux->track[i];
if( track->mux_data )
{
free( track->mux_data );
+ free( track->mf.fifo );
}
free( track );
}
- hb_list_close( &list );
free( mux );
}