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