Loading...
Searching...
No Matches
DecoderThreading.hpp
Go to the documentation of this file.
1#pragma once
2#include <Media/Libav.hpp>
3
4#if SCORE_HAS_LIBAV
5extern "C" {
6#include <libavcodec/avcodec.h>
7#include <libavutil/cpu.h>
8#include <libavutil/imgutils.h>
9#include <libavutil/opt.h>
10}
11
12#include <algorithm>
13#include <cstdint>
14#include <cstdlib>
15#include <string>
16#include <string_view>
17
31namespace Video
32{
33
35enum class DecodeUseCase : uint8_t
36{
43 Live,
46};
47
49{
50 int count{1};
51 int type{};
52 bool zeroDelay{};
53 const char* reason{""};
54};
55
56namespace threading
57{
60inline constexpr int max_frame_threads = 16;
63inline constexpr int max_slice_threads = 64;
65inline constexpr int64_t frame_memory_budget = int64_t(1) << 30;
66
71inline bool sliceRich(const AVCodec& codec) noexcept
72{
73 static constexpr std::string_view names[]{
74 "prores", // slices of at most 8 macroblocks
75 "dnxhd", // one slice per macroblock row
76 "dvvideo", // fixed DIF segments
77 "hap", // texture blocks, decompressed in row chunks
78 "dxv", // same
79 "exr", // scanline blocks or tiles
80 "v210", // row-independent unpacking
81 };
82 const std::string_view name = codec.name ? codec.name : "";
83 return std::find(std::begin(names), std::end(names), name) != std::end(names);
84}
85
88inline bool ownPoolHasNoFrameDelay(const AVCodec& codec) noexcept
89{
90 static constexpr std::string_view names[]{
91 "libvpx", "libvpx-vp9", "libaom-av1", "libjxl", "libjxl_anim"};
92 const std::string_view name = codec.name ? codec.name : "";
93 return std::find(std::begin(names), std::end(names), name) != std::end(names);
94}
95
97inline bool ownPoolHasDelayOption(const AVCodec& codec) noexcept
98{
99 return codec.priv_class
100 && av_opt_find(
101 (void*)&codec.priv_class, "max_frame_delay", nullptr, 0,
102 AV_OPT_SEARCH_FAKE_OBJ);
103}
104
105inline int cpuCount() noexcept
106{
107 return std::max(1, av_cpu_count());
108}
109
110inline int64_t frameBytes(const AVCodecParameters* par) noexcept
111{
112 if(!par || par->width <= 0 || par->height <= 0)
113 return 0;
114 if(par->format >= 0)
115 {
116 const int sz = av_image_get_buffer_size(
117 (AVPixelFormat)par->format, par->width, par->height, 1);
118 if(sz > 0)
119 return sz;
120 }
121 return int64_t(par->width) * par->height * 4;
122}
123
124inline int frameThreads(const AVCodecParameters* par) noexcept
125{
126 int n = std::min(cpuCount(), max_frame_threads);
127 if(const auto bytes = frameBytes(par); bytes > 0)
128 n = std::min<int64_t>(n, std::max<int64_t>(2, frame_memory_budget / bytes));
129 return n;
130}
131
132inline int sliceThreads(const AVCodecParameters* par) noexcept
133{
134 int n = std::min(cpuCount(), max_slice_threads);
135 // No codec slices finer than a 16-line macroblock row.
136 if(par && par->height > 0)
137 n = std::min(n, std::max(1, (par->height + 15) / 16));
138 return n;
139}
140
143inline int envThreadType(bool& set) noexcept
144{
145 set = false;
146 const char* env = std::getenv("SCORE_VIDEO_THREADING");
147 if(!env)
148 return 0;
149 const std::string_view v = env;
150 set = true;
151 if(v == "frame")
152 return FF_THREAD_FRAME;
153 if(v == "slice")
154 return FF_THREAD_SLICE;
155 if(v == "none")
156 return 0;
157 set = false;
158 return 0;
159}
160
161inline int envThreadCount() noexcept
162{
163 const char* env = std::getenv("SCORE_VIDEO_THREADS");
164 return env ? std::max(0, std::atoi(env)) : 0;
165}
166}
167
175 const AVCodec& codec, const AVCodecParameters* par, DecodeUseCase use,
176 int userThreads = 0, bool hardware = false) noexcept
177{
178 using namespace threading;
179#if defined(__EMSCRIPTEN__)
180 // A multithreaded context crashes on teardown on the emscripten pthread
181 // runtime, even when no frame was decoded.
182 return {1, 0, false, "single-threaded on wasm"};
183#endif
184 if(hardware || (codec.capabilities & AV_CODEC_CAP_HARDWARE))
185 return {1, 0, false, "hardware decoder"};
186
187 if(const int n = envThreadCount(); n > 0)
188 userThreads = n;
189 bool forced{};
190 const int forcedType = envThreadType(forced);
191
192 const int caps = codec.capabilities;
193 const bool canFrame = caps & AV_CODEC_CAP_FRAME_THREADS;
194 const bool canSlice = caps & AV_CODEC_CAP_SLICE_THREADS;
195 const bool delayOk = use == DecodeUseCase::Playback;
196
197 if(caps & AV_CODEC_CAP_OTHER_THREADS)
198 {
199 // Some wrappers (older libdav1d) map FF_THREAD_FRAME onto their frame
200 // pipelining, so the mask is what keeps it on or off.
201 const int count = userThreads > 0 ? userThreads : 0;
202 if(delayOk)
203 return {
204 count, FF_THREAD_FRAME | FF_THREAD_SLICE, false, "codec's own thread pool"};
205 if(ownPoolHasNoFrameDelay(codec))
206 return {count, FF_THREAD_SLICE, false, "codec's own thread pool, no frame delay"};
207 if(ownPoolHasDelayOption(codec))
208 return {
209 count, FF_THREAD_SLICE, true, "codec's own thread pool, frame delay disabled"};
210 return {1, 0, false, "codec's own thread pool may delay frames"};
211 }
212
213 if(forced)
214 {
215 if(forcedType == FF_THREAD_FRAME && canFrame)
216 return {
217 userThreads > 0 ? userThreads : frameThreads(par), FF_THREAD_FRAME,
218 false, "forced by SCORE_VIDEO_THREADING"};
219 if(forcedType == FF_THREAD_SLICE && canSlice)
220 return {
221 userThreads > 0 ? userThreads : sliceThreads(par), FF_THREAD_SLICE,
222 false, "forced by SCORE_VIDEO_THREADING"};
223 if(forcedType == 0)
224 return {1, 0, false, "forced by SCORE_VIDEO_THREADING"};
225 }
226
227 if(delayOk && canFrame && !(canSlice && sliceRich(codec)))
228 return {
229 userThreads > 0 ? userThreads : frameThreads(par), FF_THREAD_FRAME, false,
230 canSlice ? "frame threads: the stream may hold a single slice"
231 : "frame threads: the only model the codec has"};
232
233 if(canSlice)
234 return {
235 userThreads > 0 ? userThreads : sliceThreads(par), FF_THREAD_SLICE, false,
236 !delayOk ? "slice threads: no frame delay allowed"
237 : canFrame ? "slice threads: the format mandates many slices"
238 : "slice threads: the only model the codec has"};
239
240 if(canFrame)
241 return {1, 0, false, "frame threading would delay output"};
242 return {1, 0, false, "codec has no threading"};
243}
244
246inline void applyDecoderThreading(AVCodecContext& ctx, const DecoderThreading& t) noexcept
247{
248 ctx.thread_count = t.count;
249 if(t.type)
250 ctx.thread_type = t.type;
251 if(t.zeroDelay && ctx.priv_data)
252 av_opt_set_int(ctx.priv_data, "max_frame_delay", 1, 0);
253}
254
257inline std::string describeDecoderThreading(
258 const AVCodecContext& ctx, const DecoderThreading& t)
259{
260 std::string s = ctx.codec && ctx.codec->name ? ctx.codec->name : "?";
261 switch(ctx.active_thread_type)
262 {
263 case FF_THREAD_FRAME:
264 s += ": frame threads x";
265 break;
266 case FF_THREAD_SLICE:
267 s += ": slice threads x";
268 break;
269 default:
270 if(ctx.codec && (ctx.codec->capabilities & AV_CODEC_CAP_OTHER_THREADS))
271 s += ctx.thread_count > 0 ? ": own thread pool x" : ": own thread pool, auto";
272 else
273 s += ": single thread";
274 break;
275 }
276 if(ctx.active_thread_type
277 || (ctx.thread_count > 0 && ctx.codec
278 && (ctx.codec->capabilities & AV_CODEC_CAP_OTHER_THREADS)))
279 s += std::to_string(ctx.thread_count);
280 s += " (";
281 s += t.reason;
282 s += ")";
283 return s;
284}
285
286}
287#endif
int envThreadType(bool &set) noexcept
Definition DecoderThreading.hpp:143
constexpr int max_frame_threads
Definition DecoderThreading.hpp:60
void applyDecoderThreading(AVCodecContext &ctx, const DecoderThreading &t) noexcept
Applies to a context allocated for its codec, before avcodec_open2.
Definition DecoderThreading.hpp:246
bool ownPoolHasNoFrameDelay(const AVCodec &codec) noexcept
Definition DecoderThreading.hpp:88
constexpr int max_slice_threads
Definition DecoderThreading.hpp:63
DecodeUseCase
What consumes the decoded frames, which sets how much delay is acceptable.
Definition DecoderThreading.hpp:36
@ FrameExact
A packet in must give its frame out (per-frame seeking, scrubbing).
@ Live
Cameras and network streams: any delay is visible latency.
@ Thumbnail
One frame is decoded, then the context is dropped.
DecoderThreading chooseDecoderThreading(const AVCodec &codec, const AVCodecParameters *par, DecodeUseCase use, int userThreads=0, bool hardware=false) noexcept
Definition DecoderThreading.hpp:174
std::string describeDecoderThreading(const AVCodecContext &ctx, const DecoderThreading &t)
Definition DecoderThreading.hpp:257
constexpr int64_t frame_memory_budget
Upper bound for the decoded frames frame threading keeps in flight.
Definition DecoderThreading.hpp:65
bool ownPoolHasDelayOption(const AVCodec &codec) noexcept
Wrappers whose pipelining can be switched off (libdav1d).
Definition DecoderThreading.hpp:97
bool sliceRich(const AVCodec &codec) noexcept
Definition DecoderThreading.hpp:71
Definition DecoderThreading.hpp:49
int type
FF_THREAD_FRAME / FF_THREAD_SLICE, 0 leaves the default.
Definition DecoderThreading.hpp:51
int count
AVCodecContext::thread_count; 0 lets the codec pick.
Definition DecoderThreading.hpp:50
bool zeroDelay
ask a codec with its own thread pool for no frame delay
Definition DecoderThreading.hpp:52