summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libhb/common.h2
-rw-r--r--libhb/decsub.c8
-rw-r--r--libhb/denoise.c5
-rw-r--r--libhb/fifo.c278
-rw-r--r--libhb/hb.c10
-rw-r--r--libhb/internal.h3
-rw-r--r--libhb/reader.c28
-rw-r--r--libhb/render.c10
-rwxr-xr-xlibhb/stream.c2
-rw-r--r--libhb/work.c37
-rw-r--r--macosx/Controller.mm7
-rw-r--r--test/test.c8
12 files changed, 336 insertions, 62 deletions
diff --git a/libhb/common.h b/libhb/common.h
index f32c620ab..e71959142 100644
--- a/libhb/common.h
+++ b/libhb/common.h
@@ -258,7 +258,7 @@ struct hb_job_s
int largeFileSize;
- int subtitle_scan;
+ int indepth_scan;
hb_subtitle_t ** select_subtitle;
int subtitle_force;
char * native_language;
diff --git a/libhb/decsub.c b/libhb/decsub.c
index eb3b35a34..2b76ecf29 100644
--- a/libhb/decsub.c
+++ b/libhb/decsub.c
@@ -177,7 +177,7 @@ static void ParseControls( hb_work_object_t * w )
/*
* If we are doing a subtitle scan then note down
*/
- if( job->subtitle_scan )
+ if( job->indepth_scan )
{
for( n=0; n < hb_list_count(title->list_subtitle); n++ )
{
@@ -198,8 +198,8 @@ static void ParseControls( hb_work_object_t * w )
break;
case 0x02: // 0x02 - STP_DSP - Stop Display, no arguments
- if(!pv->pts_stop)
- pv->pts_stop = pv->pts + date * 900;
+ if(!pv->pts_stop)
+ pv->pts_stop = pv->pts + date * 900;
break;
case 0x03: // 0x03 - SET_COLOR - Set Colour indices
@@ -463,7 +463,7 @@ static hb_buffer_t * Decode( hb_work_object_t * w )
/* Get infos about the subtitle */
ParseControls( w );
- if( job->subtitle_scan || ( job->subtitle_force && pv->pts_forced == 0 ) )
+ if( job->indepth_scan || ( job->subtitle_force && pv->pts_forced == 0 ) )
{
/*
* Don't encode subtitles when doing a scan.
diff --git a/libhb/denoise.c b/libhb/denoise.c
index 5022d4721..70902a6ec 100644
--- a/libhb/denoise.c
+++ b/libhb/denoise.c
@@ -304,6 +304,11 @@ hb_filter_private_t * hb_denoise_init( int pix_fmt,
}
hb_filter_private_t * pv = malloc( sizeof(struct hb_filter_private_s) );
+
+ /*
+ * Clear the memory to avoid freeing uninitialised memory later.
+ */
+ memset( pv, 0, sizeof( struct hb_filter_private_s ) );
pv->pix_fmt = pix_fmt;
pv->width[0] = width;
diff --git a/libhb/fifo.c b/libhb/fifo.c
index 44c64d9b0..a36bb7774 100644
--- a/libhb/fifo.c
+++ b/libhb/fifo.c
@@ -10,27 +10,181 @@
#include <malloc.h>
#endif
-hb_buffer_t * hb_buffer_init( int size )
+/* Fifo */
+struct hb_fifo_s
{
- hb_buffer_t * b;
+ hb_lock_t * lock;
+ int capacity;
+ int size;
+ int buffer_size;
+ hb_buffer_t * first;
+ hb_buffer_t * last;
+};
+
+#define MAX_BUFFER_POOLS 15
+#define BUFFER_POOL_MAX_ELEMENTS 2048
+
+struct hb_buffer_pools_s
+{
+ int entries;
+ int allocated;
+ hb_fifo_t *pool[MAX_BUFFER_POOLS];
+ hb_lock_t *lock;
+};
+
+struct hb_buffer_pools_s buffers;
+
+void hb_buffer_pool_init( void )
+{
+ hb_fifo_t *buffer_pool;
+ int size = 512;
+ int max_size = 32768;;
+
+ buffers.entries = 0;
+ buffers.lock = hb_lock_init();
+ buffers.allocated = 0;
+ while(size <= max_size) {
+ buffer_pool = buffers.pool[buffers.entries++] = hb_fifo_init(BUFFER_POOL_MAX_ELEMENTS);
+ buffer_pool->buffer_size = size;
+ size *= 2;
+ }
+}
+
+void hb_buffer_pool_free( void )
+{
+ int i;
+ int count;
+ int freed = 0;
+ hb_buffer_t *b;
+
+ hb_lock(buffers.lock);
+
+ for( i = 0; i < buffers.entries; i++)
+ {
+ count = 0;
+ while( ( b = hb_fifo_get(buffers.pool[i]) ) )
+ {
+ freed += b->alloc;
+ if( b->data )
+ {
+ free( b->data );
+ b->data = NULL;
+ }
+ free( b );
+ count++;
+ }
+ hb_log("Freed %d buffers of size %d", count, buffers.pool[i]->buffer_size);
+ }
+
+ hb_log("Allocated %d bytes of buffers on this pass and Freed %d bytes, %d bytes leaked",
+ buffers.allocated, freed, buffers.allocated - freed);
+ buffers.allocated = 0;
+ hb_unlock(buffers.lock);
+}
+
+
+hb_buffer_t * hb_buffer_init( int size )
+{
+ hb_buffer_t * b;
+ int i;
+ hb_fifo_t *buffer_pool = NULL;
+ uint8_t *data;
+ int b_alloc;
+ int resize = 0;
+
+ /*
+ * The buffer pools are allocated in increasing size
+ */
+ for( i = 0; i < buffers.entries; i++ )
+ {
+ if( buffers.pool[i]->buffer_size >= size )
+ {
+ /*
+ * This pool is big enough, but are there any buffers in it?
+ */
+ if( hb_fifo_size( buffers.pool[i] ) )
+ {
+ /*
+ * We've found a matching buffer pool, with buffers.
+ */
+ buffer_pool = buffers.pool[i];
+ resize = buffers.pool[i]->buffer_size;
+ } else {
+ /*
+ * Buffer pool is empty,
+ */
+ if( resize ) {
+ /*
+ * This is the second time through, so break out of here to avoid
+ * using too large a buffer for a small job.
+ */
+ break;
+ }
+ resize = buffers.pool[i]->buffer_size;
+ }
+ }
+ }
+
+ /*
+ * Don't reuse the 0 size buffers, not much gain.
+ */
+ if( size != 0 && buffer_pool )
+ {
+ b = hb_fifo_get( buffer_pool );
+
+ if( b )
+ {
+ /*
+ * Zero the contents of the buffer, would be nice if we
+ * didn't have to do this.
+ *
+ hb_log("Reused buffer size %d for size %d from pool %d depth %d",
+ b->alloc, size, smallest_pool->buffer_size,
+ hb_fifo_size(smallest_pool));
+ */
+ data = b->data;
+ b_alloc = b->alloc;
+ memset( b, 0, sizeof(hb_buffer_t) );
+ b->alloc = b_alloc;
+ b->size = size;
+ b->data = data;
+ return( b );
+ }
+ }
+
+ /*
+ * No existing buffers, create a new one
+ */
if( !( b = calloc( sizeof( hb_buffer_t ), 1 ) ) )
{
hb_log( "out of memory" );
return NULL;
}
- b->alloc = size;
b->size = size;
+
+ if( resize )
+ {
+ size = resize;
+ }
+ b->alloc = size;
+
+ /*
+ hb_log("Allocating new buffer of size %d for size %d",
+ b->alloc,
+ b->size);
+ */
+
if (!size)
return b;
#if defined( SYS_DARWIN ) || defined( SYS_FREEBSD )
- b->data = malloc( size );
+ b->data = malloc( b->alloc );
#elif defined( SYS_CYGWIN )
/* FIXME */
- b->data = malloc( size + 17 );
+ b->data = malloc( b->alloc + 17 );
#else
- b->data = memalign( 16, size );
+ b->data = memalign( 16, b->alloc );
#endif
if( !b->data )
@@ -39,27 +193,122 @@ hb_buffer_t * hb_buffer_init( int size )
free( b );
return NULL;
}
+
+ buffers.allocated += b->alloc;
+
return b;
}
void hb_buffer_realloc( hb_buffer_t * b, int size )
{
/* No more alignment, but we don't care */
+ if( size < 2048 ) {
+ size = 2048;
+ }
b->data = realloc( b->data, size );
+ buffers.allocated -= b->alloc;
b->alloc = size;
+ buffers.allocated += b->alloc;
}
void hb_buffer_close( hb_buffer_t ** _b )
{
hb_buffer_t * b = *_b;
+ hb_fifo_t *buffer_pool = NULL;
+ int i;
+
+ /*
+ * Put the buffer into our free list in the matching buffer pool, if there is one.
+ */
+ if( b->alloc != 0 )
+ {
+ for( i = 0; i < buffers.entries; i++ )
+ {
+ if( b->alloc == buffers.pool[i]->buffer_size )
+ {
+ buffer_pool = buffers.pool[i];
+ break;
+ }
+ }
+ }
- if( b->data )
+ if( buffer_pool )
{
- free( b->data );
+ if( !hb_fifo_is_full( buffer_pool ) )
+ {
+ if(b->data)
+ {
+ /*
+ hb_log("Putting a buffer of size %d on pool %d, depth %d",
+ b->alloc,
+ buffer_pool->buffer_size,
+ hb_fifo_size(buffer_pool));
+ */
+ hb_fifo_push( buffer_pool, b );
+ } else {
+ free(b);
+ }
+ } else {
+ /*
+ * Got a load of these size ones already, free this buffer.
+ *
+ hb_log("Buffer pool for size %d full, freeing buffer", b->alloc);
+ */
+ if( b->data )
+ {
+ free( b->data );
+ }
+ buffers.allocated -= b->alloc;
+ free( b );
+ }
+ } else {
+ /*
+ * Need a new buffer pool for this size.
+ */
+ hb_lock(buffers.lock);
+ if ( b->alloc != 0 && buffers.entries < MAX_BUFFER_POOLS)
+ {
+ buffer_pool = buffers.pool[buffers.entries++] = hb_fifo_init(BUFFER_POOL_MAX_ELEMENTS);
+ buffer_pool->buffer_size = b->alloc;
+ hb_fifo_push( buffer_pool, b );
+ /*
+ hb_log("*** Allocated a new buffer pool for size %d [%d]", b->alloc,
+ buffers.entries );
+ */
+ } else {
+ if( b->alloc != 0 )
+ {
+ for( i = buffers.entries-1; i >= 0; i-- )
+ {
+ if( hb_fifo_size(buffers.pool[i]) == 0 )
+ {
+ /*
+ * Reuse this pool as it is empty.
+ */
+ buffers.pool[i]->buffer_size = b->alloc;
+ hb_fifo_push( buffers.pool[i], b );
+ b = NULL;
+ break;
+ }
+ }
+ }
+
+ if( b )
+ {
+ if( b->data )
+ {
+ free( b->data );
+ b->data = NULL;
+ buffers.allocated -= b->alloc;
+ }
+ free( b );
+ }
+ }
+ hb_unlock(buffers.lock);
}
- free( b );
*_b = NULL;
+
}
void hb_buffer_copy_settings( hb_buffer_t * dst, const hb_buffer_t * src )
@@ -71,22 +320,13 @@ void hb_buffer_copy_settings( hb_buffer_t * dst, const hb_buffer_t * src )
dst->flags = src->flags;
}
-/* Fifo */
-struct hb_fifo_s
-{
- hb_lock_t * lock;
- int capacity;
- int size;
- hb_buffer_t * first;
- hb_buffer_t * last;
-};
-
hb_fifo_t * hb_fifo_init( int capacity )
{
hb_fifo_t * f;
f = calloc( sizeof( hb_fifo_t ), 1 );
f->lock = hb_lock_init();
f->capacity = capacity;
+ f->buffer_size = 0;
return f;
}
diff --git a/libhb/hb.c b/libhb/hb.c
index fbcd81043..7a15a1181 100644
--- a/libhb/hb.c
+++ b/libhb/hb.c
@@ -104,6 +104,11 @@ hb_handle_t * hb_init_real( int verbose, int update_check )
}
}
+ /*
+ * Initialise buffer pool
+ */
+ hb_buffer_pool_init();
+
/* CPU count detection */
hb_log( "hb_init: checking cpu count" );
h->cpu_count = hb_get_cpu_count();
@@ -607,7 +612,7 @@ void hb_add( hb_handle_t * h, hb_job_t * job )
*/
memset( audio_lang, 0, sizeof( audio_lang ) );
- if ( job->subtitle_scan || job->native_language ) {
+ if ( job->indepth_scan || job->native_language ) {
/*
* Find the first audio language that is being encoded
@@ -659,7 +664,7 @@ void hb_add( hb_handle_t * h, hb_job_t * job )
* If doing a subtitle scan then add all the matching subtitles for this
* language.
*/
- if ( job->subtitle_scan )
+ if ( job->indepth_scan )
{
for( i=0; i < hb_list_count( title->list_subtitle ); i++ )
{
@@ -926,6 +931,7 @@ void hb_close( hb_handle_t ** _h )
hb_lock_close( &h->pause_lock );
free( h );
*_h = NULL;
+
}
/**
diff --git a/libhb/internal.h b/libhb/internal.h
index 0b460593c..d3bbb637d 100644
--- a/libhb/internal.h
+++ b/libhb/internal.h
@@ -64,6 +64,9 @@ struct hb_buffer_s
hb_buffer_t * next;
};
+void hb_buffer_pool_init( void );
+void hb_buffer_pool_free( void );
+
hb_buffer_t * hb_buffer_init( int size );
void hb_buffer_realloc( hb_buffer_t *, int size );
void hb_buffer_close( hb_buffer_t ** );
diff --git a/libhb/reader.c b/libhb/reader.c
index 506774557..2fd39eb63 100644
--- a/libhb/reader.c
+++ b/libhb/reader.c
@@ -118,7 +118,7 @@ static void ReaderFunc( void * _r )
}
}
- if( r->job->subtitle_scan )
+ if( r->job->indepth_scan )
{
/*
* Need to update the progress during a subtitle scan
@@ -139,7 +139,7 @@ static void ReaderFunc( void * _r )
p.seconds = -1;
hb_set_state( r->job->h, &state );
}
-
+
hb_demux_ps( r->ps, list );
while( ( buf = hb_list_item( list, 0 ) ) )
@@ -194,18 +194,21 @@ static hb_fifo_t * GetFifoForId( hb_job_t * job, int id )
if( id == 0xE0 )
{
- if( !job->subtitle_scan )
+ if( job->indepth_scan )
{
- return job->fifo_mpeg2;
- } else {
/*
- * Ditch the mpeg2 video when doing a subtitle scan.
+ * Ditch the video here during the indepth scan until
+ * we can improve the MPEG2 decode performance.
*/
return NULL;
+ }
+ else
+ {
+ return job->fifo_mpeg2;
}
}
- if (job->subtitle_scan) {
+ if( job->indepth_scan ) {
/*
* Count the occurances of the subtitles, don't actually
* return any to encode unless we are looking fro forced
@@ -233,12 +236,15 @@ static hb_fifo_t * GetFifoForId( hb_job_t * job, int id )
return subtitle->fifo_in;
}
}
- for( i = 0; i < hb_list_count( title->list_audio ); i++ )
+ if( !job->indepth_scan )
{
- audio = hb_list_item( title->list_audio, i );
- if( id == audio->id )
+ for( i = 0; i < hb_list_count( title->list_audio ); i++ )
{
- return audio->fifo_in;
+ audio = hb_list_item( title->list_audio, i );
+ if( id == audio->id )
+ {
+ return audio->fifo_in;
+ }
}
}
diff --git a/libhb/render.c b/libhb/render.c
index 5577c2f76..05dcdbbf9 100644
--- a/libhb/render.c
+++ b/libhb/render.c
@@ -160,6 +160,16 @@ int renderWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
*buf_out = hb_buffer_init(0);
return HB_WORK_OK;
}
+
+ /*
+ * During the indepth_scan ditch the buffers here before applying filters or attempting to
+ * use the subtitles.
+ */
+ if( job->indepth_scan )
+ {
+ *buf_out = NULL;
+ return HB_WORK_OK;
+ }
/* Push subtitles onto queue just in case we need to delay a frame */
if( in->sub )
diff --git a/libhb/stream.c b/libhb/stream.c
index ab8963c41..b168308bd 100755
--- a/libhb/stream.c
+++ b/libhb/stream.c
@@ -676,7 +676,7 @@ static void hb_stream_put_back(hb_stream_t *stream, int i)
stream->ps_decode_buffer[read_buffer_index].read_pos -= i;
}
else
- fprintf(stderr, "hb_stream_put_back - trying to step beyond the start of the buffer, read_pos = %d amt to put back = %d\n", stream->ps_decode_buffer[read_buffer_index].read_pos, i);
+ hb_error("hb_stream_put_back - trying to step beyond the start of the buffer, read_pos = %d amt to put back = %d\n", stream->ps_decode_buffer[read_buffer_index].read_pos, i);
}
}
diff --git a/libhb/work.c b/libhb/work.c
index e73bf1693..5b3f914c2 100644
--- a/libhb/work.c
+++ b/libhb/work.c
@@ -22,6 +22,8 @@ static void work_func();
static void do_job( hb_job_t *, int cpu_count );
static void work_loop( void * );
+#define FIFO_SIZE 32
+
/**
* Allocates work object and launches work thread with work_func.
* @param jobs Handle to hb_list_t.
@@ -174,10 +176,10 @@ static void do_job( hb_job_t * job, int cpu_count )
}
hb_log (" + PixelRatio: %d, width:%d, height: %d",job->pixel_ratio,job->width, job->height);
job->fifo_mpeg2 = hb_fifo_init( 2048 );
- job->fifo_raw = hb_fifo_init( 8 );
- job->fifo_sync = hb_fifo_init( 8 );
- job->fifo_render = hb_fifo_init( 8 );
- job->fifo_mpeg4 = hb_fifo_init( 8 );
+ job->fifo_raw = hb_fifo_init( FIFO_SIZE );
+ job->fifo_sync = hb_fifo_init( FIFO_SIZE );
+ job->fifo_render = hb_fifo_init( FIFO_SIZE );
+ job->fifo_mpeg4 = hb_fifo_init( FIFO_SIZE );
/* Synchronization */
hb_list_add( job->list_work, ( w = getWork( WORK_SYNC ) ) );
@@ -216,7 +218,7 @@ static void do_job( hb_job_t * job, int cpu_count )
hb_list_add( job->list_work, w );
- if( job->select_subtitle && !job->subtitle_scan )
+ if( job->select_subtitle && !job->indepth_scan )
{
/*
* Must be second pass of a two pass with subtitle scan enabled, so
@@ -237,8 +239,8 @@ static void do_job( hb_job_t * job, int cpu_count )
{
hb_log( " + subtitle %x, %s", subtitle->id, subtitle->lang );
- subtitle->fifo_in = hb_fifo_init( 8 );
- subtitle->fifo_raw = hb_fifo_init( 8 );
+ subtitle->fifo_in = hb_fifo_init( FIFO_SIZE );
+ subtitle->fifo_raw = hb_fifo_init( FIFO_SIZE );
/*
* Disable forced subtitles if we didn't find any in the scan
@@ -246,7 +248,7 @@ static void do_job( hb_job_t * job, int cpu_count )
*
* select_subtitle implies that we did a scan.
*/
- if( !job->subtitle_scan && job->subtitle_force &&
+ if( !job->indepth_scan && job->subtitle_force &&
job->select_subtitle )
{
if( subtitle->forced_hits == 0 )
@@ -255,7 +257,7 @@ static void do_job( hb_job_t * job, int cpu_count )
}
}
- if (!job->subtitle_scan || job->subtitle_force) {
+ if (!job->indepth_scan || job->subtitle_force) {
/*
* Don't add threads for subtitles when we are scanning, unless
* looking for forced subtitles.
@@ -437,9 +439,9 @@ static void do_job( hb_job_t * job, int cpu_count )
/* set up the audio work structures */
audio->fifo_in = hb_fifo_init( 2048 );
- audio->fifo_raw = hb_fifo_init( 8 );
- audio->fifo_sync = hb_fifo_init( 8 );
- audio->fifo_out = hb_fifo_init( 8 );
+ audio->fifo_raw = hb_fifo_init( FIFO_SIZE );
+ audio->fifo_sync = hb_fifo_init( FIFO_SIZE );
+ audio->fifo_out = hb_fifo_init( FIFO_SIZE );
switch( audio->codec )
{
@@ -512,7 +514,7 @@ static void do_job( hb_job_t * job, int cpu_count )
{
w = hb_list_item( job->list_work, i );
w->done = &job->done;
- w->thread_sleep_interval = 10;
+ w->thread_sleep_interval = 10;
w->init( w, job );
w->thread = hb_thread_init( w->name, work_loop, w,
HB_LOW_PRIORITY );
@@ -520,7 +522,7 @@ static void do_job( hb_job_t * job, int cpu_count )
done = 0;
w = hb_list_item( job->list_work, 0 );
- w->thread_sleep_interval = 50;
+ w->thread_sleep_interval = 50;
w->init( w, job );
while( !*job->die )
{
@@ -573,6 +575,9 @@ static void do_job( hb_job_t * job, int cpu_count )
hb_fifo_close( &job->fifo_sync );
hb_fifo_close( &job->fifo_render );
hb_fifo_close( &job->fifo_mpeg4 );
+
+ hb_buffer_pool_free();
+
for (i=0; i < hb_list_count(title->list_subtitle); i++) {
subtitle = hb_list_item( title->list_subtitle, i);
if( subtitle )
@@ -590,7 +595,7 @@ static void do_job( hb_job_t * job, int cpu_count )
hb_fifo_close( &audio->fifo_out );
}
- if( job->subtitle_scan )
+ if( job->indepth_scan )
{
/*
* Before closing the title print out our subtitle stats if we need to
@@ -661,7 +666,7 @@ static void do_job( hb_job_t * job, int cpu_count )
if( job->select_subtitle )
{
- if( job->subtitle_scan )
+ if( job->indepth_scan )
{
for( i=0; i < hb_list_count( title->list_subtitle ); i++ )
{
diff --git a/macosx/Controller.mm b/macosx/Controller.mm
index b2fb41fe6..297eaf7e2 100644
--- a/macosx/Controller.mm
+++ b/macosx/Controller.mm
@@ -1590,7 +1590,7 @@ static int hb_group_count(hb_handle_t * h)
job->x264opts = NULL;
- job->subtitle_scan = 1;
+ job->indepth_scan = 1;
job->select_subtitle = (hb_subtitle_t**)malloc(sizeof(hb_subtitle_t*));
*(job->select_subtitle) = NULL;
@@ -1617,8 +1617,7 @@ static int hb_group_count(hb_handle_t * h)
if( [fVidTwoPassCheck state] == NSOnState )
{
hb_subtitle_t **subtitle_tmp = job->select_subtitle;
- job->select_subtitle = NULL;
- job->subtitle_scan = 0;
+ job->indepth_scan = 0;
job->pass = 1;
job->sequence_id++; // for job grouping
@@ -1636,7 +1635,7 @@ static int hb_group_count(hb_handle_t * h)
}
else
{
- job->subtitle_scan = 0;
+ job->indepth_scan = 0;
job->pass = 0;
job->sequence_id++; // for job grouping
hb_add( fHandle, job );
diff --git a/test/test.c b/test/test.c
index cddcadf8e..a5ddddf25 100644
--- a/test/test.c
+++ b/test/test.c
@@ -632,7 +632,7 @@ static int HandleEvents( hb_handle_t * h )
job->x264opts = NULL;
- job->subtitle_scan = subtitle_scan;
+ job->indepth_scan = subtitle_scan;
fprintf( stderr, "Subtitle Scan Enabled - enabling "
"subtitles if found for foreign language segments\n");
job->select_subtitle = malloc(sizeof(hb_subtitle_t*));
@@ -659,7 +659,7 @@ static int HandleEvents( hb_handle_t * h )
job->pass = 1;
- job->subtitle_scan = 0;
+ job->indepth_scan = 0;
/*
* If turbo options have been selected then append them
@@ -702,7 +702,7 @@ static int HandleEvents( hb_handle_t * h )
* selected in the first pass (using the whacky select-subtitle
* attribute of the job).
*/
- job->subtitle_scan = 0;
+ job->indepth_scan = 0;
job->x264opts = x264opts2;
@@ -715,7 +715,7 @@ static int HandleEvents( hb_handle_t * h )
* precludes encoding of any actual subtitles.
*/
- job->subtitle_scan = 0;
+ job->indepth_scan = 0;
job->pass = 0;
hb_add( h, job );
}