From 5fef66ff1bbfbb7046eac7686f6b9be148093048 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Fri, 28 Apr 2023 13:37:28 +0200 Subject: lavc/pthread_frame: add support for thread-safe hwaccels --- libavcodec/avcodec.h | 6 +++ libavcodec/hwconfig.h | 1 + libavcodec/pthread_frame.c | 105 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 93 insertions(+), 19 deletions(-) diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index 1e91b9cb53..149c5bcdb2 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -2245,6 +2245,12 @@ typedef struct AVHWAccel { * that avctx->hwaccel_priv_data is invalid. */ int (*frame_params)(AVCodecContext *avctx, AVBufferRef *hw_frames_ctx); + + /** + * Copy necessary context variables from a previous thread context to the current one. + * For thread-safe hwaccels only. + */ + int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src); } AVHWAccel; /** diff --git a/libavcodec/hwconfig.h b/libavcodec/hwconfig.h index f03b744cdf..d88dc37c8c 100644 --- a/libavcodec/hwconfig.h +++ b/libavcodec/hwconfig.h @@ -24,6 +24,7 @@ #define HWACCEL_CAP_ASYNC_SAFE (1 << 0) +#define HWACCEL_CAP_THREAD_SAFE (1 << 1) typedef struct AVCodecHWConfigInternal { diff --git a/libavcodec/pthread_frame.c b/libavcodec/pthread_frame.c index 74864e19c5..b87b2e9c23 100644 --- a/libavcodec/pthread_frame.c +++ b/libavcodec/pthread_frame.c @@ -104,6 +104,12 @@ typedef struct PerThreadContext { int hwaccel_serializing; int async_serializing; + // set to 1 in ff_thread_finish_setup() when a threadsafe hwaccel is used; + // cannot check hwaccel caps directly, because + // worked threads clear hwaccel state for thread-unsafe hwaccels + // after each decode call + int hwaccel_threadsafe; + atomic_int debug_threads; ///< Set if the FF_DEBUG_THREADS option is set. } PerThreadContext; @@ -117,8 +123,8 @@ typedef struct FrameThreadContext { unsigned pthread_init_cnt; ///< Number of successfully initialized mutexes/conditions pthread_mutex_t buffer_mutex; ///< Mutex used to protect get/release_buffer(). /** - * This lock is used for ensuring threads run in serial when hwaccel - * is used. + * This lock is used for ensuring threads run in serial when thread-unsafe + * hwaccel is used. */ pthread_mutex_t hwaccel_mutex; pthread_mutex_t async_mutex; @@ -133,13 +139,19 @@ typedef struct FrameThreadContext { * While it is set, ff_thread_en/decode_frame won't return any results. */ - /* hwaccel state is temporarily stored here in order to transfer its ownership - * to the next decoding thread without the need for extra synchronization */ + /* hwaccel state for thread-unsafe hwaccels is temporarily stored here in + * order to transfer its ownership to the next decoding thread without the + * need for extra synchronization */ const AVHWAccel *stash_hwaccel; void *stash_hwaccel_context; void *stash_hwaccel_priv; } FrameThreadContext; +static int hwaccel_serial(const AVCodecContext *avctx) +{ + return avctx->hwaccel && !(avctx->hwaccel->caps_internal & HWACCEL_CAP_THREAD_SAFE); +} + static void async_lock(FrameThreadContext *fctx) { pthread_mutex_lock(&fctx->async_mutex); @@ -202,9 +214,9 @@ static attribute_align_arg void *frame_worker_thread(void *arg) * cannot be true here. */ av_assert0(!p->hwaccel_serializing); - /* if the previous thread uses hwaccel then we take the lock to ensure - * the threads don't run concurrently */ - if (avctx->hwaccel) { + /* if the previous thread uses thread-unsafe hwaccel then we take the + * lock to ensure the threads don't run concurrently */ + if (hwaccel_serial(avctx)) { pthread_mutex_lock(&p->parent->hwaccel_mutex); p->hwaccel_serializing = 1; } @@ -220,7 +232,8 @@ static attribute_align_arg void *frame_worker_thread(void *arg) ff_thread_finish_setup(avctx); if (p->hwaccel_serializing) { - /* wipe hwaccel state to avoid stale pointers lying around; + /* wipe hwaccel state for thread-unsafe hwaccels to avoid stale + * pointers lying around; * the state was transferred to FrameThreadContext in * ff_thread_finish_setup(), so nothing is leaked */ avctx->hwaccel = NULL; @@ -230,7 +243,8 @@ static attribute_align_arg void *frame_worker_thread(void *arg) p->hwaccel_serializing = 0; pthread_mutex_unlock(&p->parent->hwaccel_mutex); } - av_assert0(!avctx->hwaccel); + av_assert0(!avctx->hwaccel || + (avctx->hwaccel->caps_internal & HWACCEL_CAP_THREAD_SAFE)); if (p->async_serializing) { p->async_serializing = 0; @@ -328,8 +342,49 @@ FF_ENABLE_DEPRECATION_WARNINGS if (codec->update_thread_context_for_user) err = codec->update_thread_context_for_user(dst, src); } else { - if (codec->update_thread_context) + const PerThreadContext *p_src = src->internal->thread_ctx; + PerThreadContext *p_dst = dst->internal->thread_ctx; + + if (codec->update_thread_context) { err = codec->update_thread_context(dst, src); + if (err < 0) + return err; + } + + // reset dst hwaccel state if needed + av_assert0(p_dst->hwaccel_threadsafe || + (!dst->hwaccel && !dst->internal->hwaccel_priv_data)); + if (p_dst->hwaccel_threadsafe && + (!p_src->hwaccel_threadsafe || dst->hwaccel != src->hwaccel)) { + ff_hwaccel_uninit(dst); + p_dst->hwaccel_threadsafe = 0; + } + + // propagate hwaccel state for threadsafe hwaccels + if (p_src->hwaccel_threadsafe) { + if (!dst->hwaccel) { + if (src->hwaccel->priv_data_size) { + av_assert0(src->hwaccel->update_thread_context); + + dst->internal->hwaccel_priv_data = + av_mallocz(src->hwaccel->priv_data_size); + if (!dst->internal->hwaccel_priv_data) + return AVERROR(ENOMEM); + } + dst->hwaccel = src->hwaccel; + } + av_assert0(dst->hwaccel == src->hwaccel); + + if (src->hwaccel->update_thread_context) { + err = src->hwaccel->update_thread_context(dst, src); + if (err < 0) { + av_log(dst, AV_LOG_ERROR, "Error propagating hwaccel state\n"); + ff_hwaccel_uninit(dst); + return err; + } + } + p_dst->hwaccel_threadsafe = 1; + } } return err; @@ -437,10 +492,12 @@ static int submit_packet(PerThreadContext *p, AVCodecContext *user_avctx, } /* transfer the stashed hwaccel state, if any */ - av_assert0(!p->avctx->hwaccel); - FFSWAP(const AVHWAccel*, p->avctx->hwaccel, fctx->stash_hwaccel); - FFSWAP(void*, p->avctx->hwaccel_context, fctx->stash_hwaccel_context); - FFSWAP(void*, p->avctx->internal->hwaccel_priv_data, fctx->stash_hwaccel_priv); + av_assert0(!p->avctx->hwaccel || p->hwaccel_threadsafe); + if (!p->hwaccel_threadsafe) { + FFSWAP(const AVHWAccel*, p->avctx->hwaccel, fctx->stash_hwaccel); + FFSWAP(void*, p->avctx->hwaccel_context, fctx->stash_hwaccel_context); + FFSWAP(void*, p->avctx->internal->hwaccel_priv_data, fctx->stash_hwaccel_priv); + } av_packet_unref(p->avpkt); ret = av_packet_ref(p->avpkt, avpkt); @@ -594,7 +651,10 @@ void ff_thread_finish_setup(AVCodecContext *avctx) { if (!(avctx->active_thread_type&FF_THREAD_FRAME)) return; - if (avctx->hwaccel && !p->hwaccel_serializing) { + p->hwaccel_threadsafe = avctx->hwaccel && + (avctx->hwaccel->caps_internal & HWACCEL_CAP_THREAD_SAFE); + + if (hwaccel_serial(avctx) && !p->hwaccel_serializing) { pthread_mutex_lock(&p->parent->hwaccel_mutex); p->hwaccel_serializing = 1; } @@ -607,13 +667,16 @@ void ff_thread_finish_setup(AVCodecContext *avctx) { async_lock(p->parent); } - /* save hwaccel state for passing to the next thread; + /* thread-unsafe hwaccels share a single private data instance, so we + * save hwaccel state for passing to the next thread; * this is done here so that this worker thread can wipe its own hwaccel * state after decoding, without requiring synchronization */ av_assert0(!p->parent->stash_hwaccel); - p->parent->stash_hwaccel = avctx->hwaccel; - p->parent->stash_hwaccel_context = avctx->hwaccel_context; - p->parent->stash_hwaccel_priv = avctx->internal->hwaccel_priv_data; + if (hwaccel_serial(avctx)) { + p->parent->stash_hwaccel = avctx->hwaccel; + p->parent->stash_hwaccel_context = avctx->hwaccel_context; + p->parent->stash_hwaccel_priv = avctx->internal->hwaccel_priv_data; + } pthread_mutex_lock(&p->progress_mutex); if(atomic_load(&p->state) == STATE_SETUP_FINISHED){ @@ -681,6 +744,10 @@ void ff_frame_thread_free(AVCodecContext *avctx, int thread_count) pthread_join(p->thread, NULL); } + + if (p->hwaccel_threadsafe) + ff_hwaccel_uninit(ctx); + if (codec->close && p->thread_init != UNINITIALIZED) codec->close(ctx); -- cgit v1.2.3