[FFmpeg-devel] [PATCH, RFC] lavc/phtread_frame: update context in child thread in multi-thread mode

Hendrik Leppkes h.leppkes at gmail.com
Wed Jun 26 10:12:02 EEST 2019


On Wed, Jun 26, 2019 at 9:06 AM Linjie Fu <linjie.fu at intel.com> wrote:
>
> Currently in ff_thread_decode_frame, context is updated from child thread
> to main thread, and main thread releases the context in avcodec_close()
> when decode finishes.
>
> However, when resolution/format change in vp9, ff_get_format was called,
> and hwaccel_uninit() and hwaccel_init will be used to destroy and re-create
> the context. Due to the async between main-thread and child-thread,
> main-thread updated its context from child earlier than the context was
> refreshed in child-thread. And it will lead to:
>     1. memory leak in child-thread.
>     2. double free in main-thread while calling avcodec_close().
>
> Can be reproduced with a resolution change case in vp9, and use -vframes
> to terminate the decode between the dynamic resolution change frames:
>
> ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -v
> verbose -i ./test2360_1672_4980.ivf -pix_fmt p010le -f rawvideo -vsync
> passthrough -vframes 6 -y out.yuv
>
> Move update_context_from_thread from ff_thread_decode_frame(main thread)
> to frame_worker_thread(child thread), update the context in child thread
> right after the context was updated to avoid the async issue.
>
> Signed-off-by: Linjie Fu <linjie.fu at intel.com>
> ---
> Request for Comments, not quite familiar with the constraints.
> Thanks in advance.
>
>  libavcodec/internal.h      |   5 ++
>  libavcodec/pthread_frame.c | 164 ++++++++++++++++++++++++---------------------
>  2 files changed, 91 insertions(+), 78 deletions(-)
>
> diff --git a/libavcodec/internal.h b/libavcodec/internal.h
> index 5096ffa..9f4ed0b 100644
> --- a/libavcodec/internal.h
> +++ b/libavcodec/internal.h
> @@ -162,6 +162,11 @@ typedef struct AVCodecInternal {
>
>      void *thread_ctx;
>
> +    /**
> +     * Main thread AVCodecContext pointer
> +     */
> +    void *main_thread;
> +
>      DecodeSimpleContext ds;
>      DecodeFilterContext filter;
>
> diff --git a/libavcodec/pthread_frame.c b/libavcodec/pthread_frame.c
> index 36ac0ac..be42e98 100644
> --- a/libavcodec/pthread_frame.c
> +++ b/libavcodec/pthread_frame.c
> @@ -159,82 +159,6 @@ static void async_unlock(FrameThreadContext *fctx)
>  }
>
>  /**
> - * Codec worker thread.
> - *
> - * Automatically calls ff_thread_finish_setup() if the codec does
> - * not provide an update_thread_context method, or if the codec returns
> - * before calling it.
> - */
> -static attribute_align_arg void *frame_worker_thread(void *arg)
> -{
> -    PerThreadContext *p = arg;
> -    AVCodecContext *avctx = p->avctx;
> -    const AVCodec *codec = avctx->codec;
> -
> -    pthread_mutex_lock(&p->mutex);
> -    while (1) {
> -        while (atomic_load(&p->state) == STATE_INPUT_READY && !p->die)
> -            pthread_cond_wait(&p->input_cond, &p->mutex);
> -
> -        if (p->die) break;
> -
> -        if (!codec->update_thread_context && THREAD_SAFE_CALLBACKS(avctx))
> -            ff_thread_finish_setup(avctx);
> -
> -        /* If a decoder supports hwaccel, then it must call ff_get_format().
> -         * Since that call must happen before ff_thread_finish_setup(), the
> -         * decoder is required to implement update_thread_context() and call
> -         * ff_thread_finish_setup() manually. Therefore the above
> -         * ff_thread_finish_setup() call did not happen and hwaccel_serializing
> -         * 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) {
> -            pthread_mutex_lock(&p->parent->hwaccel_mutex);
> -            p->hwaccel_serializing = 1;
> -        }
> -
> -        av_frame_unref(p->frame);
> -        p->got_frame = 0;
> -        p->result = codec->decode(avctx, p->frame, &p->got_frame, &p->avpkt);
> -
> -        if ((p->result < 0 || !p->got_frame) && p->frame->buf[0]) {
> -            if (avctx->internal->allocate_progress)
> -                av_log(avctx, AV_LOG_ERROR, "A frame threaded decoder did not "
> -                       "free the frame on failure. This is a bug, please report it.\n");
> -            av_frame_unref(p->frame);
> -        }
> -
> -        if (atomic_load(&p->state) == STATE_SETTING_UP)
> -            ff_thread_finish_setup(avctx);
> -
> -        if (p->hwaccel_serializing) {
> -            p->hwaccel_serializing = 0;
> -            pthread_mutex_unlock(&p->parent->hwaccel_mutex);
> -        }
> -
> -        if (p->async_serializing) {
> -            p->async_serializing = 0;
> -
> -            async_unlock(p->parent);
> -        }
> -
> -        pthread_mutex_lock(&p->progress_mutex);
> -
> -        atomic_store(&p->state, STATE_INPUT_READY);
> -
> -        pthread_cond_broadcast(&p->progress_cond);
> -        pthread_cond_signal(&p->output_cond);
> -        pthread_mutex_unlock(&p->progress_mutex);
> -    }
> -    pthread_mutex_unlock(&p->mutex);
> -
> -    return NULL;
> -}
> -
> -/**
>   * Update the next thread's AVCodecContext with values from the reference thread's context.
>   *
>   * @param dst The destination context.
> @@ -313,6 +237,89 @@ FF_ENABLE_DEPRECATION_WARNINGS
>      return err;
>  }
>
> +
> +
> +/**
> + * Codec worker thread.
> + *
> + * Automatically calls ff_thread_finish_setup() if the codec does
> + * not provide an update_thread_context method, or if the codec returns
> + * before calling it.
> + */
> +static attribute_align_arg void *frame_worker_thread(void *arg)
> +{
> +    PerThreadContext *p = arg;
> +    AVCodecContext *avctx = p->avctx;
> +    const AVCodec *codec = avctx->codec;
> +
> +    pthread_mutex_lock(&p->mutex);
> +    while (1) {
> +        while (atomic_load(&p->state) == STATE_INPUT_READY && !p->die)
> +            pthread_cond_wait(&p->input_cond, &p->mutex);
> +
> +        if (p->die) break;
> +
> +        if (!codec->update_thread_context && THREAD_SAFE_CALLBACKS(avctx))
> +            ff_thread_finish_setup(avctx);
> +
> +        /* If a decoder supports hwaccel, then it must call ff_get_format().
> +         * Since that call must happen before ff_thread_finish_setup(), the
> +         * decoder is required to implement update_thread_context() and call
> +         * ff_thread_finish_setup() manually. Therefore the above
> +         * ff_thread_finish_setup() call did not happen and hwaccel_serializing
> +         * 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) {
> +            pthread_mutex_lock(&p->parent->hwaccel_mutex);
> +            p->hwaccel_serializing = 1;
> +        }
> +
> +        av_frame_unref(p->frame);
> +        p->got_frame = 0;
> +        p->result = codec->decode(avctx, p->frame, &p->got_frame, &p->avpkt);
> +
> +        if (p->avctx->internal->main_thread)
> +            update_context_from_thread((AVCodecContext *)p->avctx->internal->main_thread,
> +                                                                            p->avctx, 1);
> +
> +
> +        if ((p->result < 0 || !p->got_frame) && p->frame->buf[0]) {
> +            if (avctx->internal->allocate_progress)
> +                av_log(avctx, AV_LOG_ERROR, "A frame threaded decoder did not "
> +                       "free the frame on failure. This is a bug, please report it.\n");
> +            av_frame_unref(p->frame);
> +        }
> +
> +        if (atomic_load(&p->state) == STATE_SETTING_UP)
> +            ff_thread_finish_setup(avctx);
> +
> +        if (p->hwaccel_serializing) {
> +            p->hwaccel_serializing = 0;
> +            pthread_mutex_unlock(&p->parent->hwaccel_mutex);
> +        }
> +
> +        if (p->async_serializing) {
> +            p->async_serializing = 0;
> +
> +            async_unlock(p->parent);
> +        }
> +
> +        pthread_mutex_lock(&p->progress_mutex);
> +
> +        atomic_store(&p->state, STATE_INPUT_READY);
> +
> +        pthread_cond_broadcast(&p->progress_cond);
> +        pthread_cond_signal(&p->output_cond);
> +        pthread_mutex_unlock(&p->progress_mutex);
> +    }
> +    pthread_mutex_unlock(&p->mutex);
> +
> +    return NULL;
> +}
> +

Please don't move the entire function around, as it makes it
impossible to spot any differences in the function itself. Instead,
use a forward declaration of whichever function you want to call, so
the diff is more clear.

- Hendrik


More information about the ffmpeg-devel mailing list