Blender V4.5
ffmpeg_swscale.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#ifdef WITH_FFMPEG
10# include "ffmpeg_swscale.hh"
11
12# include <cstdint>
13# include <mutex>
14
15# include "BLI_mutex.hh"
16# include "BLI_threads.h"
17# include "BLI_vector.hh"
18
19extern "C" {
20# include <libavutil/opt.h>
21# include <libavutil/pixfmt.h>
22# include <libswscale/swscale.h>
23
24# include "ffmpeg_compat.h"
25}
26
27/* libswscale context creation and destruction is expensive.
28 * Maintain a cache of already created contexts. */
29
30static constexpr int64_t swscale_cache_max_entries = 32;
31
32struct SwscaleContext {
33 int src_width = 0, src_height = 0;
34 int dst_width = 0, dst_height = 0;
35 AVPixelFormat src_format = AV_PIX_FMT_NONE, dst_format = AV_PIX_FMT_NONE;
36 bool src_full_range = false, dst_full_range = false;
37 int src_colorspace = -1, dst_colorspace = -1;
38 int flags = 0;
39
40 SwsContext *context = nullptr;
41 int64_t last_use_timestamp = 0;
42 bool is_used = false;
43};
44
45static blender::Mutex swscale_cache_lock;
46static int64_t swscale_cache_timestamp = 0;
47static blender::Vector<SwscaleContext> *swscale_cache = nullptr;
48
49static SwsContext *sws_create_context(int src_width,
50 int src_height,
51 int av_src_format,
52 int dst_width,
53 int dst_height,
54 int av_dst_format,
55 int sws_flags)
56{
57# if defined(FFMPEG_SWSCALE_THREADING)
58 /* sws_getContext does not allow passing flags that ask for multi-threaded
59 * scaling context, so do it the hard way. */
60 SwsContext *c = sws_alloc_context();
61 if (c == nullptr) {
62 return nullptr;
63 }
64 av_opt_set_int(c, "srcw", src_width, 0);
65 av_opt_set_int(c, "srch", src_height, 0);
66 av_opt_set_int(c, "src_format", av_src_format, 0);
67 av_opt_set_int(c, "dstw", dst_width, 0);
68 av_opt_set_int(c, "dsth", dst_height, 0);
69 av_opt_set_int(c, "dst_format", av_dst_format, 0);
70 av_opt_set_int(c, "sws_flags", sws_flags, 0);
71 av_opt_set_int(c, "threads", BLI_system_thread_count(), 0);
72
73 if (sws_init_context(c, nullptr, nullptr) < 0) {
74 sws_freeContext(c);
75 return nullptr;
76 }
77# else
78 SwsContext *c = sws_getContext(src_width,
79 src_height,
80 AVPixelFormat(av_src_format),
81 dst_width,
82 dst_height,
83 AVPixelFormat(av_dst_format),
84 sws_flags,
85 nullptr,
86 nullptr,
87 nullptr);
88# endif
89
90 return c;
91}
92
93static void init_swscale_cache_if_needed()
94{
95 if (swscale_cache == nullptr) {
96 swscale_cache = new blender::Vector<SwscaleContext>();
97 swscale_cache_timestamp = 0;
98 }
99}
100
101static bool remove_oldest_swscale_context()
102{
103 int64_t oldest_index = -1;
104 int64_t oldest_time = 0;
105 for (int64_t index = 0; index < swscale_cache->size(); index++) {
106 SwscaleContext &ctx = (*swscale_cache)[index];
107 if (ctx.is_used) {
108 continue;
109 }
110 int64_t time = swscale_cache_timestamp - ctx.last_use_timestamp;
111 if (time > oldest_time) {
112 oldest_time = time;
113 oldest_index = index;
114 }
115 }
116
117 if (oldest_index >= 0) {
118 SwscaleContext &ctx = (*swscale_cache)[oldest_index];
119 sws_freeContext(ctx.context);
120 swscale_cache->remove_and_reorder(oldest_index);
121 return true;
122 }
123 return false;
124}
125
126static void maintain_swscale_cache_size()
127{
128 while (swscale_cache->size() > swscale_cache_max_entries) {
129 if (!remove_oldest_swscale_context()) {
130 /* Could not remove anything (all contexts are actively used),
131 * stop trying. */
132 break;
133 }
134 }
135}
136
137SwsContext *ffmpeg_sws_get_context(int src_width,
138 int src_height,
139 int av_src_format,
140 bool src_full_range,
141 int src_color_space,
142 int dst_width,
143 int dst_height,
144 int av_dst_format,
145 bool dst_full_range,
146 int dst_color_space,
147 int sws_flags)
148{
149 std::lock_guard lock(swscale_cache_lock);
150
151 init_swscale_cache_if_needed();
152
153 swscale_cache_timestamp++;
154
155 /* Search for unused context that has suitable parameters. */
156 SwsContext *ctx = nullptr;
157 for (SwscaleContext &c : *swscale_cache) {
158 if (!c.is_used && c.src_width == src_width && c.src_height == src_height &&
159 c.src_format == av_src_format && c.src_full_range == src_full_range &&
160 c.src_colorspace == src_color_space && c.dst_width == dst_width &&
161 c.dst_height == dst_height && c.dst_format == av_dst_format &&
162 c.dst_full_range == dst_full_range && c.dst_colorspace == dst_color_space &&
163 c.flags == sws_flags)
164 {
165 ctx = c.context;
166 /* Mark as used. */
167 c.is_used = true;
168 c.last_use_timestamp = swscale_cache_timestamp;
169 break;
170 }
171 }
172 if (ctx == nullptr) {
173 /* No free matching context in cache: create a new one. */
174 ctx = sws_create_context(
175 src_width, src_height, av_src_format, dst_width, dst_height, av_dst_format, sws_flags);
176
177 int src_range, dst_range, brightness, contrast, saturation;
178 const int *table, *inv_table;
179 if (sws_getColorspaceDetails(ctx,
180 (int **)&inv_table,
181 &src_range,
182 (int **)&table,
183 &dst_range,
184 &brightness,
185 &contrast,
186 &saturation) >= 0)
187 {
188 if (src_full_range) {
189 src_range = 1;
190 }
191 if (dst_full_range) {
192 dst_range = 1;
193 }
194 if (src_color_space >= 0) {
195 inv_table = sws_getCoefficients(src_color_space);
196 }
197 if (dst_color_space >= 0) {
198 table = sws_getCoefficients(dst_color_space);
199 }
200 sws_setColorspaceDetails(
201 ctx, (int *)inv_table, src_range, table, dst_range, brightness, contrast, saturation);
202 }
203
204 SwscaleContext c;
205 c.src_width = src_width;
206 c.src_height = src_height;
207 c.dst_width = dst_width;
208 c.dst_height = dst_height;
209 c.src_format = AVPixelFormat(av_src_format);
210 c.dst_format = AVPixelFormat(av_dst_format);
211 c.src_full_range = src_full_range;
212 c.dst_full_range = dst_full_range;
213 c.src_colorspace = src_color_space;
214 c.dst_colorspace = dst_color_space;
215 c.flags = sws_flags;
216 c.context = ctx;
217 c.is_used = true;
218 c.last_use_timestamp = swscale_cache_timestamp;
219 swscale_cache->append(c);
220
221 maintain_swscale_cache_size();
222 }
223 return ctx;
224}
225
226void ffmpeg_sws_release_context(SwsContext *ctx)
227{
228 std::lock_guard lock(swscale_cache_lock);
229 init_swscale_cache_if_needed();
230
231 bool found = false;
232 for (SwscaleContext &c : *swscale_cache) {
233 if (c.context == ctx) {
234 BLI_assert_msg(c.is_used, "Releasing ffmpeg swscale context that is not in use");
235 c.is_used = false;
236 found = true;
237 break;
238 }
239 }
240 BLI_assert_msg(found, "Releasing ffmpeg swscale context that is not in cache");
241 UNUSED_VARS_NDEBUG(found);
242 maintain_swscale_cache_size();
243}
244
245void ffmpeg_sws_exit()
246{
247 std::lock_guard lock(swscale_cache_lock);
248 if (swscale_cache != nullptr) {
249 for (SwscaleContext &c : *swscale_cache) {
250 sws_freeContext(c.context);
251 }
252 delete swscale_cache;
253 swscale_cache = nullptr;
254 }
255}
256
257void ffmpeg_sws_scale_frame(SwsContext *ctx, AVFrame *dst, const AVFrame *src)
258{
259# if defined(FFMPEG_SWSCALE_THREADING)
260 sws_scale_frame(ctx, dst, src);
261# else
262 sws_scale(ctx, src->data, src->linesize, 0, src->height, dst->data, dst->linesize);
263# endif
264}
265
266#endif /* WITH_FFMPEG */
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
int BLI_system_thread_count(void)
Definition threads.cc:253
#define UNUSED_VARS_NDEBUG(...)
volatile int lock
long long int int64_t
int64_t size() const
void remove_and_reorder(const int64_t index)
int context(const bContext *C, const char *member, bContextDataResult *result)
std::mutex Mutex
Definition BLI_mutex.hh:47