[x264-devel] [PATCH 1/2] frame: synchronize input frame release and recycling

汪秀军 xiujunwang.dev at gmail.com
Fri Sep 25 15:39:05 UTC 2026


>From b7f39b0740a613205efc741cff000202d672d091 Mon Sep 17 00:00:00 2001
From: Xiujun Wang <xiujunwang.dev at gmail.com>
Date: Thu, 24 Sep 2026 22:06:01 +0800
Subject: [PATCH 1/2] frame: synchronize input frame release and recycling

The lookahead thread drops its last_nonb reference while the caller of
x264_encoder_encode() can release the same input frame in
encoder_frame_end(). The plain reference count decrements can race.
The last release can also push into frames.unused[0] concurrently with
the encoder popping from that shared list.

Serialize input frame decrements and unused-list push/pop with a mutex
in thread[0], shared by all encoder contexts and the lookahead context.
Keep allocation outside the critical section. The reference taken by
lookahead_update_last_nonb() precedes publication through ofbuf, and
reconstructed frame reference changes remain on the calling thread.

Reproduced with ThreadSanitizer on macOS arm64 at 0480cb05, using
128x72 I420 input, --threads 8 --sync-lookahead 32 --bframes 8
--b-adapt 2 --rc-lookahead 60 --preset ultrafast. The input-frame races
no longer occur with this change.

Signed-off-by: Xiujun Wang <xiujunwang.dev at gmail.com>
---
 common/common.h | 2 ++
 common/frame.c | 13 ++++++++++++-
 encoder/encoder.c | 4 ++++
 3 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/common/common.h b/common/common.h
index a22e2249..a3eeaffc 100644
--- a/common/common.h
+++ b/common/common.h
@@ -286,6 +286,8 @@ struct x264_t
     x264_threadpool_t *lookaheadpool;
     x264_pthread_mutex_t mutex;
     x264_pthread_cond_t cv;
+ /* Only thread[0]'s mutex is used, for input frame references and
recycling. */
+ x264_pthread_mutex_t mutex_fenc;

     /* bitstream output */
     struct
diff --git a/common/frame.c b/common/frame.c
index 7f19e1bd..539c92e9 100644
--- a/common/frame.c
+++ b/common/frame.c
@@ -771,18 +771,29 @@ x264_frame_t *x264_frame_shift( x264_frame_t **list )

 void x264_frame_push_unused( x264_t *h, x264_frame_t *frame )
 {
+ int b_fdec = frame->b_fdec;
+ if( !b_fdec )
+ x264_pthread_mutex_lock( &h->thread[0]->mutex_fenc );
     assert( frame->i_reference_count > 0 );
     frame->i_reference_count--;
     if( frame->i_reference_count == 0 )
- x264_frame_push( h->frames.unused[frame->b_fdec], frame );
+ x264_frame_push( h->frames.unused[b_fdec], frame );
+ if( !b_fdec )
+ x264_pthread_mutex_unlock( &h->thread[0]->mutex_fenc );
 }

 x264_frame_t *x264_frame_pop_unused( x264_t *h, int b_fdec )
 {
     x264_frame_t *frame;
+ if( !b_fdec )
+ x264_pthread_mutex_lock( &h->thread[0]->mutex_fenc );
     if( h->frames.unused[b_fdec][0] )
         frame = x264_frame_pop( h->frames.unused[b_fdec] );
     else
+ frame = NULL;
+ if( !b_fdec )
+ x264_pthread_mutex_unlock( &h->thread[0]->mutex_fenc );
+ if( !frame )
         frame = frame_new( h, b_fdec );
     if( !frame )
         return NULL;
diff --git a/encoder/encoder.c b/encoder/encoder.c
index 20e620aa..1ae9ba3f 100644
--- a/encoder/encoder.c
+++ b/encoder/encoder.c
@@ -1754,6 +1754,8 @@ x264_t *x264_encoder_open( x264_param_t *param,
void *api )
 #endif

     h->thread[0] = h;
+ if( x264_pthread_mutex_init( &h->mutex_fenc, NULL ) )
+ goto fail;
     for( int i = 1; i < h->param.i_threads + !!h->param.i_sync_lookahead; i++ )
         CHECKED_MALLOC( h->thread[i], sizeof(x264_t) );
     if( h->param.i_lookahead_threads > 1 )
@@ -4569,6 +4571,8 @@ void x264_encoder_close ( x264_t *h )
         x264_free( h->thread[i]->out.nal );
         x264_pthread_mutex_destroy( &h->thread[i]->mutex );
         x264_pthread_cond_destroy( &h->thread[i]->cv );
+ if( i == 0 )
+ x264_pthread_mutex_destroy( &h->mutex_fenc );
         x264_free( h->thread[i] );
     }
 #if HAVE_OPENCL
-- 
2.20.1


More information about the x264-devel mailing list