diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 7f6de4470e..472568bd6c 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -56,6 +56,7 @@ OBJS = ac3_parser.o \ qsv_api.o \ raw.o \ refstruct.o \ + threadprogress.o \ utils.o \ version.o \ vlc.o \ diff --git a/libavcodec/avcodec.c b/libavcodec/avcodec.c index 525fe516bd..888dd76228 100644 --- a/libavcodec/avcodec.c +++ b/libavcodec/avcodec.c @@ -441,6 +441,7 @@ av_cold void ff_codec_close(AVCodecContext *avctx) av_frame_free(&avci->recon_frame); ff_refstruct_unref(&avci->pool); + ff_refstruct_pool_uninit(&avci->progress_frame_pool); ff_hwaccel_uninit(avctx); diff --git a/libavcodec/codec_internal.h b/libavcodec/codec_internal.h index d6757e2def..832e6440d7 100644 --- a/libavcodec/codec_internal.h +++ b/libavcodec/codec_internal.h @@ -62,6 +62,10 @@ * Codec initializes slice-based threading with a main function */ #define FF_CODEC_CAP_SLICE_THREAD_HAS_MF (1 << 5) +/** + * The decoder might make use of the ProgressFrame API. + */ +#define FF_CODEC_CAP_USES_PROGRESSFRAMES (1 << 11) /* * The codec supports frame threading and has inter-frame dependencies, so it * uses ff_thread_report/await_progress(). diff --git a/libavcodec/decode.c b/libavcodec/decode.c index c4a21acdaf..495614f29f 100644 --- a/libavcodec/decode.c +++ b/libavcodec/decode.c @@ -49,8 +49,10 @@ #include "hwconfig.h" #include "internal.h" #include "packet_internal.h" +#include "progressframe.h" #include "refstruct.h" #include "thread.h" +#include "threadprogress.h" typedef struct DecodeContext { AVCodecInternal avci; @@ -1668,6 +1670,116 @@ int ff_reget_buffer(AVCodecContext *avctx, AVFrame *frame, int flags) return ret; } +typedef struct ProgressInternal { + ThreadProgress progress; + struct AVFrame *f; +} ProgressInternal; + +static void check_progress_consistency(const ProgressFrame *f) +{ + av_assert1(!!f->f == !!f->progress); + av_assert1(!f->progress || f->progress->f == f->f); +} + +static int progress_frame_get(AVCodecContext *avctx, ProgressFrame *f) +{ + FFRefStructPool *pool = avctx->internal->progress_frame_pool; + + av_assert1(!f->f && !f->progress); + + f->progress = ff_refstruct_pool_get(pool); + if (!f->progress) + return AVERROR(ENOMEM); + + f->f = f->progress->f; + return 0; +} + +int ff_progress_frame_get_buffer(AVCodecContext *avctx, ProgressFrame *f, int flags) +{ + int ret; + + ret = progress_frame_get(avctx, f); + if (ret < 0) + return ret; + + ret = ff_thread_get_buffer(avctx, f->progress->f, flags); + if (ret < 0) { + f->f = NULL; + ff_refstruct_unref(&f->progress); + return ret; + } + return 0; +} + +void ff_progress_frame_ref(ProgressFrame *dst, const ProgressFrame *src) +{ + av_assert1(src->progress && src->f && src->f == src->progress->f); + av_assert1(!dst->f && !dst->progress); + dst->f = src->f; + dst->progress = ff_refstruct_ref(src->progress); +} + +void ff_progress_frame_unref(ProgressFrame *f) +{ + check_progress_consistency(f); + f->f = NULL; + ff_refstruct_unref(&f->progress); +} + +void ff_progress_frame_replace(ProgressFrame *dst, const ProgressFrame *src) +{ + if (dst == src) + return; + ff_progress_frame_unref(dst); + check_progress_consistency(src); + if (src->f) + ff_progress_frame_ref(dst, src); +} + +void ff_progress_frame_report(ProgressFrame *f, int n) +{ + ff_thread_progress_report(&f->progress->progress, n); +} + +void ff_progress_frame_await(const ProgressFrame *f, int n) +{ + ff_thread_progress_await(&f->progress->progress, n); +} + +static av_cold int progress_frame_pool_init_cb(FFRefStructOpaque opaque, void *obj) +{ + const AVCodecContext *avctx = opaque.nc; + ProgressInternal *progress = obj; + int ret; + + ret = ff_thread_progress_init(&progress->progress, avctx->active_thread_type & FF_THREAD_FRAME); + if (ret < 0) + return ret; + + progress->f = av_frame_alloc(); + if (!progress->f) + return AVERROR(ENOMEM); + + return 0; +} + +static void progress_frame_pool_reset_cb(FFRefStructOpaque unused, void *obj) +{ + ProgressInternal *progress = obj; + + ff_thread_progress_reset(&progress->progress); + av_frame_unref(progress->f); +} + +static av_cold void progress_frame_pool_free_entry_cb(FFRefStructOpaque opaque, void *obj) +{ + ProgressInternal *progress = obj; + + ff_thread_progress_destroy(&progress->progress); + av_frame_free(&progress->f); +} + int ff_decode_preinit(AVCodecContext *avctx) { AVCodecInternal *avci = avctx->internal; @@ -1766,6 +1878,16 @@ int ff_decode_preinit(AVCodecContext *avctx) if (!avci->in_pkt || !avci->last_pkt_props) return AVERROR(ENOMEM); + if (ffcodec(avctx->codec)->caps_internal & FF_CODEC_CAP_USES_PROGRESSFRAMES) { + avci->progress_frame_pool = + ff_refstruct_pool_alloc_ext(sizeof(ProgressInternal), + FF_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR, + avctx, progress_frame_pool_init_cb, + progress_frame_pool_reset_cb, + progress_frame_pool_free_entry_cb, NULL); + if (!avci->progress_frame_pool) + return AVERROR(ENOMEM); + } ret = decode_bsfs_init(avctx); if (ret < 0) return ret; diff --git a/libavcodec/internal.h b/libavcodec/internal.h index 64fe0122c8..bc20a797ae 100644 --- a/libavcodec/internal.h +++ b/libavcodec/internal.h @@ -61,6 +61,8 @@ typedef struct AVCodecInternal { struct FramePool *pool; + struct FFRefStructPool *progress_frame_pool; + void *thread_ctx; /** diff --git a/libavcodec/progressframe.h b/libavcodec/progressframe.h new file mode 100644 index 0000000000..dc841f30d2 --- /dev/null +++ b/libavcodec/progressframe.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022 Andreas Rheinhardt + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_PROGRESSFRAME_H +#define AVCODEC_PROGRESSFRAME_H + +/** + * ProgressFrame is an API to easily share frames without an underlying + * av_frame_ref(). Its main usecase is in frame-threading scenarios, + * yet it could also be used for purely single-threaded decoders that + * want to keep multiple references to the same frame. + * + * The underlying principle behind the API is that all that is needed + * to share a frame is a reference count and a contract between all parties. + * The ProgressFrame provides the reference count and the frame is unreferenced + * via ff_thread_release_buffer() when the reference count reaches zero. + * + * In order to make this API also usable for frame-threaded decoders it also + * provides a way of exchanging simple information about the state of + * decoding the frame via ff_thread_progress_report() and + * ff_thread_progress_await(). + * + * The typical contract for frame-threaded decoders is as follows: + * Thread A initializes a ProgressFrame via ff_thread_progress_get_buffer() + * (which already allocates the AVFrame's data buffers), calls + * ff_thread_finish_setup() and starts decoding the frame. Later threads + * receive a reference to this frame, which means they get a pointer + * to the AVFrame and the internal reference count gets incremented. + * Later threads whose frames use A's frame as reference as well as + * the thread that will eventually output A's frame will wait for + * progress on said frame reported by A. As soon as A has reported + * that it has finished decoding its frame, it must no longer modify it + * (neither its data nor its properties). + * + * Because creating a reference with this API does not involve reads + * from the actual AVFrame, the decoding thread may modify the properties + * (i.e. non-data fields) until it has indicated to be done with this + * frame. This is important for e.g. propagating decode_error_flags; + * it also allows to add side-data late. + */ + +struct AVCodecContext; + +typedef struct ProgressFrame { + struct AVFrame *f; + struct ProgressInternal *progress; +} ProgressFrame; + +/** + * Notify later decoding threads when part of their reference frame is ready. + * Call this when some part of the frame is finished decoding. + * Later calls with lower values of progress have no effect. + * + * @param f The frame being decoded. + * @param progress Value, in arbitrary units, of how much of the frame has decoded. + * + * @warning Calling this on a blank ProgressFrame causes undefined behaviour + */ +void ff_progress_frame_report(ProgressFrame *f, int progress); + +/** + * Wait for earlier decoding threads to finish reference frames. + * Call this before accessing some part of a frame, with a given + * value for progress, and it will return after the responsible decoding + * thread calls ff_thread_progress_report() with the same or + * higher value for progress. + * + * @param f The frame being referenced. + * @param progress Value, in arbitrary units, to wait for. + * + * @warning Calling this on a blank ProgressFrame causes undefined behaviour + */ +void ff_progress_frame_await(const ProgressFrame *f, int progress); + +/** + * This function sets up the ProgressFrame, i.e. gets ProgressFrame.f + * and also calls ff_thread_get_buffer() on the frame. + * + * @note: This must only be called by codecs with the + * FF_CODEC_CAP_USES_PROGRESSFRAMES internal cap. + */ +int ff_progress_frame_get_buffer(struct AVCodecContext *avctx, + ProgressFrame *f, int flags); + +/** + * Give up a reference to the underlying frame contained in a ProgressFrame + * and reset the ProgressFrame, setting all pointers to NULL. + * + * @note: This implies that when using this API the check for whether + * a frame exists is by checking ProgressFrame.f and not + * ProgressFrame.f->data[0] or ProgressFrame.f->buf[0]. + */ +void ff_progress_frame_unref(ProgressFrame *f); + +/** + * Set dst->f to src->f and make dst a co-owner of src->f. + * dst can then be used to wait on progress of the underlying frame. + * + * @note: There is no underlying av_frame_ref() here. dst->f and src->f + * really point to the same AVFrame. Typically this means that + * the decoding thread is allowed to set all the properties of + * the AVFrame until it has indicated to have finished decoding. + * Afterwards later threads may read all of these fields. + * Access to the frame's data is governed by + * ff_thread_progress_report/await(). + */ +void ff_progress_frame_ref(ProgressFrame *dst, const ProgressFrame *src); + +/** + * Do nothing if dst and src already refer to the same AVFrame; + * otherwise unreference dst and if src is not blank, put a reference + * to src's AVFrame in its place (in case src is not blank). + */ +void ff_progress_frame_replace(ProgressFrame *dst, const ProgressFrame *src); + +#endif /* AVCODEC_PROGRESSFRAME_H */ diff --git a/libavcodec/pthread_frame.c b/libavcodec/pthread_frame.c index f19571f6f8..0da9db1f57 100644 --- a/libavcodec/pthread_frame.c +++ b/libavcodec/pthread_frame.c @@ -781,6 +781,7 @@ static av_cold int init_thread(PerThreadContext *p, int *threads_to_free, if (!copy->internal) return AVERROR(ENOMEM); copy->internal->thread_ctx = p; + copy->internal->progress_frame_pool = avctx->internal->progress_frame_pool; copy->delay = avctx->delay; diff --git a/libavcodec/tests/avcodec.c b/libavcodec/tests/avcodec.c index 08ca507bf0..58f54cac74 100644 --- a/libavcodec/tests/avcodec.c +++ b/libavcodec/tests/avcodec.c @@ -141,7 +141,8 @@ int main(void){ ret = 1; } } - if (codec2->caps_internal & (FF_CODEC_CAP_ALLOCATE_PROGRESS | + if (codec2->caps_internal & (FF_CODEC_CAP_USES_PROGRESSFRAMES | + FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_SETS_PKT_DTS | FF_CODEC_CAP_SKIP_FRAME_FILL_PARAM | FF_CODEC_CAP_EXPORTS_CROPPING |