1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
//! What `av_frame_get_buffer` actually costs.
//!
//! # The law this module exists to keep
//!
//! **A judge must dominate the allocator's arithmetic, not the
//! payload's.** Every ceiling in this crate answers the question "may
//! this frame be allocated?", and the only honest way to answer it is
//! to price what the *allocator* will do — not what the pixels or
//! samples nominally weigh. Those two numbers are not close:
//!
//! | shape | tight payload | `av_frame_get_buffer(0)` |
//! |---|---|---|
//! | `nv12` 16x16 | 384 | **1,792** |
//! | `yuv420p` 1x1 | 3 | **2,304** |
//! | `gray8` 65536x1 | 65,536 | **2,097,408** |
//! | `s16p`, 1 sample, 8ch | 16 | **768** |
//! | `dblp`, 1 sample, 255ch | 2,040 | **73,440** |
//! | `yuv420p` 1920x1080 | 3,110,400 | 3,133,696 |
//!
//! The last row is why this went unnoticed for so long: on ordinary
//! shapes the allocator overhead is under one percent, and every
//! under-pricing bug in this release has hidden behind a frame big
//! enough for the slack not to show.
//!
//! Two judges were pricing the payload — the hardware transfer judge
//! (768 priced, 1,792 allocated for a 16x16 NV12 destination) and the
//! resampler's output preflight (a 16-byte ceiling admitting a 768-byte
//! allocation) — so both now come through here.
//!
//! # Why estimates and not the formula
//!
//! `libavutil/frame.c` aligns the width up in a loop until the linesize
//! lands on the allocator's alignment, aligns the plane heights, and
//! adds a per-plane padding term; the constants differ by build and by
//! CPU (this machine resolves to a 32-byte alignment, others to 64).
//! Transcribing that is a fragile way to be exactly right and an easy
//! way to be quietly wrong.
//!
//! So these functions are deliberately **conservative upper bounds**,
//! and the contract is verified rather than argued: the tests price
//! every shape in the table above and compare against the real summed
//! `AVBufferRef.size`, asserting the estimate dominates in each case.
//! A build whose allocator grows hungrier fails those tests rather than
//! silently outgrowing a ceiling.
use c_int;
/// Alignment to charge for. This machine's `av_frame_get_buffer`
/// resolves to 32; 64 is the largest FFmpeg uses on any SIMD target,
/// and charging the larger keeps the bound valid across builds.
const ALIGN: usize = 64;
/// Per-plane slack covering `frame.c`'s padding term and the
/// allocator's own bookkeeping.
///
/// Measured need is smaller — the tightest observed case is a packed
/// 1-sample 8-channel frame at 544 bytes against 64 aligned — but the
/// term costs nothing on frames big enough to matter and is what keeps
/// the tiny shapes dominated.
const PLANE_SLACK: usize = 512;
/// Rounds `value` up to a multiple of `align`.
const
/// An upper bound on what `av_frame_get_buffer(0)` allocates for a
/// video frame of `width` x `height` in the pixel format `format_raw`.
///
/// Both dimensions are aligned up before pricing, because the allocator
/// aligns the linesize *and* the plane heights — which is what turns a
/// 65536x1 frame from 64 KiB of pixels into a 2 MiB allocation.
///
/// `None` when libavutil cannot size the format at those dimensions, so
/// callers can fail closed rather than guess.
/// An upper bound on what a frame of `width` x `height` can cost in
/// **any** format this build can emit.
///
/// For the judges that have real dimensions but no priceable layout —
/// a hardware `sw_format` libavutil will not size, a transfer candidate
/// this build cannot describe. The alternative reached for first was a
/// bare `w * h * 16`, which is not an upper bound at all: it omits the
/// dimension alignment and the per-plane slack that
/// [`video_frame_bytes`] applies to every other estimate, so the
/// "conservative" fallback could price *below* the accurate path.
///
/// Built from the same machinery instead — align both dimensions, then
/// charge the widest per-pixel rate and the same plane overhead — so it
/// dominates [`video_frame_bytes`] for the same extent by construction.
///
/// `None` only when the dimensions are not a picture.
/// An upper bound on what `av_frame_get_buffer(0)` allocates for an
/// audio frame of `nb_samples` samples across `channels` channels in
/// the sample format `format_raw`.
///
/// # The allocator's own ruler, in the allocator's own order
///
/// The first version of this function did the arithmetic itself:
/// multiply the samples by the channel count, align the product once,
/// add slack. That is the wrong *order* — `av_frame_get_buffer` rounds
/// the **sample extent** and only then multiplies by the channels — and
/// the error is not small. For packed `dbl` at eight channels it priced
/// 576 bytes against a real 2,080; at 255 channels, 2,560 against
/// 65,312. Twenty-five times under, on a formula that looked right.
///
/// So the arithmetic is not restated here at all. `av_samples_get_buffer_size`
/// with `align = 0` *is* the ruler `av_frame_get_buffer` measures with,
/// and asking it removes the whole class of getting the order wrong.
/// Measured across every sample format this build names, the relation
/// is exact:
///
/// ```text
/// allocated = av_samples_get_buffer_size(channels, nb_samples, fmt, 0)
/// + 32 * planes (planes = channels if planar, else 1)
/// ```
///
/// The per-plane term is charged at `PLANE_SLACK` rather than the
/// measured 32, which costs nothing and leaves room for a build whose
/// buffer header is larger.
///
/// `None` when libavutil will not size the request — an unnamed format,
/// a count it rejects — so callers fail closed rather than guess.
/// The probe square the per-pixel cost is measured on.
///
/// 256 divides every chroma subsampling FFmpeg has **and** every
/// alignment libavcodec uses, so the measurement is exact: no plane is
/// rounded up to cover a half-sized dimension, and no row is padded to
/// an alignment boundary. Measured at 257 the same census reads 16.934
/// bytes per pixel instead of 16.000 — that 5.8% is per-*row* padding,
/// a term linear in height rather than in pixels, and it is not part of
/// the per-pixel rate.
pub const PROBE_PIXELS: usize = 256 * 256;
/// Bytes a [`PROBE_PIXELS`]-pixel picture costs in the **most expensive
/// pixel format this build of libavcodec can describe**.
///
/// # Why the worst case and not the declared one
///
/// The first cut of this ceiling charged the format the *container*
/// declared, and a container's declaration is not an upper bound on
/// anything. It may be unset, it may be wrong, and it may be narrower
/// than what the decoder actually emits — a stream declaring `yuv420p`
/// at 1.5 bytes per pixel whose decoder outputs `rgbaf32` at 16 got a
/// ceiling more than ten times too generous, which is the same hole one
/// layer down from the one it was added to close.
///
/// So the rate is not negotiated with the file at all. Every stream is
/// charged the worst case, and the worst case is **measured**, not
/// tabulated: this build's descriptor list is walked once and each
/// format priced through `av_image_get_buffer_size`, the same function
/// `avcodec_default_get_buffer2` sizes from. A future FFmpeg that adds
/// a wider format is priced correctly without this crate learning its
/// name.
///
/// # The census, at the time of writing
///
/// 267 descriptors, 251 of them CPU formats that price (the rest are
/// hardware surfaces, which carry no CPU bytes and return no size). The
/// maximum is **16.000 bytes per pixel**, reached by eight formats —
/// `gbrapf32be/le`, `rgbaf32be/le`, `rgba128be/le`, `gbrap32be/le`.
/// Next below are the 12-byte `gbrpf32`/`rgbf32` family.
///
/// # What this trades
///
/// Over-refusal for cheap formats, and it is deliberate. At the 512 MiB
/// default the effective ceiling becomes ~33.55 Mpx, so 8K (33.18 Mpx)
/// still decodes in *any* format — including the 16-byte ones, where it
/// really does cost 506 MiB — but a 16K `yuv420p` frame, which would
/// only have cost 199 MB, is refused too. That is the honest shape of a
/// bound that has to hold before the format is known: the deployment
/// answer is to raise `max_frame_bytes`, which is exactly the setting that
/// says how much memory one frame may cost.
///
/// # The residual, stated
///
/// Row alignment adds at most `align x planes x height` bytes on top of
/// this rate — about 1 MB on an 8K frame, 0.2%, and covered by the fact
/// that `max_frame_bytes` is a policy number rather than a hardware
/// limit. It is only significant for degenerate aspect ratios (a
/// one-pixel-wide frame is all padding), which the *pixel* ceiling has
/// always been the wrong shape to bound and which this change neither
/// introduces nor worsens.
pub