[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