FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/tests/checkasm/hevc_pred.c
Date: 2026-05-03 23:58:45
Exec Total Coverage
Lines: 144 153 94.1%
Functions: 6 6 100.0%
Branches: 85 132 64.4%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2026 Jun Zhao <barryjzhao@tencent.com>
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include <string.h>
22 #include "checkasm.h"
23 #include "libavcodec/hevc/pred.h"
24 #include "libavutil/intreadwrite.h"
25 #include "libavutil/mem_internal.h"
26
27 static const uint32_t pixel_mask[3] = { 0xffffffff, 0x01ff01ff, 0x03ff03ff };
28
29 #define SIZEOF_PIXEL ((bit_depth + 7) / 8)
30 #define BUF_SIZE (2 * 64 * 64) /* Enough for 32x32 with stride=64 */
31 #define PRED_SIZE 128 /* Increased to 4 * MAX_TB_SIZE to accommodate C code reads */
32
33 #define randomize_buffers() \
34 do { \
35 uint32_t mask = pixel_mask[bit_depth - 8]; \
36 for (int i = 0; i < BUF_SIZE; i += 4) { \
37 uint32_t r = rnd() & mask; \
38 AV_WN32A(buf0 + i, r); \
39 AV_WN32A(buf1 + i, r); \
40 } \
41 /* Start from -4 so that AV_WN32A writes \
42 * top[-4..-1] and left[-4..-1], ensuring \
43 * top[-1] and left[-1] contain known data \
44 * since angular pred references them \
45 * (e.g. mode 10/26 edge filtering, \
46 * mode 18 diagonal, V/H neg extension). */\
47 for (int i = -4; i < PRED_SIZE; i += 4) { \
48 uint32_t r = rnd() & mask; \
49 AV_WN32A(top + i, r); \
50 AV_WN32A(left + i, r); \
51 } \
52 } while (0)
53
54 #define randomize_ref_buffers() \
55 do { \
56 uint32_t mask = pixel_mask[bit_depth - 8]; \
57 for (int i = -4; i < PRED_SIZE; i += 4) { \
58 uint32_t r = rnd() & mask; \
59 AV_WN32A(top + i, r); \
60 AV_WN32A(left + i, r); \
61 } \
62 } while (0)
63
64 28 static void check_pred_dc(HEVCPredContext *h,
65 uint8_t *buf0, uint8_t *buf1,
66 uint8_t *top, uint8_t *left, int bit_depth)
67 {
68 28 const char *const block_name[] = { "4x4", "8x8", "16x16", "32x32" };
69 28 const int block_size[] = { 4, 8, 16, 32 };
70 int log2_size;
71
72 28 declare_func(void, uint8_t *src, const uint8_t *top,
73 const uint8_t *left, ptrdiff_t stride,
74 int log2_size, int c_idx);
75
76 /* Test all 4 sizes: 4x4, 8x8, 16x16, 32x32 */
77
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 28 times.
140 for (log2_size = 2; log2_size <= 5; log2_size++) {
78 112 int size = block_size[log2_size - 2];
79 112 ptrdiff_t stride = 64 * SIZEOF_PIXEL;
80
81
2/2
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 104 times.
112 if (check_func(h->pred_dc, "hevc_pred_dc_%s_%d",
82 block_name[log2_size - 2], bit_depth)) {
83 /* Test with c_idx=0 (luma, with edge smoothing for size < 32) */
84
4/4
✓ Branch 1 taken 16384 times.
✓ Branch 2 taken 8 times.
✓ Branch 4 taken 264 times.
✓ Branch 5 taken 8 times.
16656 randomize_buffers();
85 8 call_ref(buf0, top, left, stride, log2_size, 0);
86 8 call_new(buf1, top, left, stride, log2_size, 0);
87
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (memcmp(buf0, buf1, size * stride))
88 fail();
89
90 /* Test with c_idx=1 (chroma, no edge smoothing) */
91
4/4
✓ Branch 1 taken 16384 times.
✓ Branch 2 taken 8 times.
✓ Branch 4 taken 264 times.
✓ Branch 5 taken 8 times.
16656 randomize_buffers();
92 8 call_ref(buf0, top, left, stride, log2_size, 1);
93 8 call_new(buf1, top, left, stride, log2_size, 1);
94
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (memcmp(buf0, buf1, size * stride))
95 fail();
96
97
1/8
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
8 bench_new(buf1, top, left, stride, log2_size, 0);
98 }
99 }
100 28 }
101
102 28 static void check_pred_planar(HEVCPredContext *h,
103 uint8_t *buf0, uint8_t *buf1,
104 uint8_t *top, uint8_t *left, int bit_depth)
105 {
106 28 const char *const block_name[] = { "4x4", "8x8", "16x16", "32x32" };
107 28 const int block_size[] = { 4, 8, 16, 32 };
108 int i;
109
110 28 declare_func(void, uint8_t *src, const uint8_t *top,
111 const uint8_t *left, ptrdiff_t stride);
112
113 /* Test all 4 sizes: 4x4, 8x8, 16x16, 32x32 */
114
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 28 times.
140 for (i = 0; i < 4; i++) {
115 112 int size = block_size[i];
116 112 ptrdiff_t stride = 64 * SIZEOF_PIXEL;
117
118
2/2
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 104 times.
112 if (check_func(h->pred_planar[i], "hevc_pred_planar_%s_%d",
119 block_name[i], bit_depth)) {
120
4/4
✓ Branch 1 taken 16384 times.
✓ Branch 2 taken 8 times.
✓ Branch 4 taken 264 times.
✓ Branch 5 taken 8 times.
16656 randomize_buffers();
121 8 call_ref(buf0, top, left, stride);
122 8 call_new(buf1, top, left, stride);
123
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (memcmp(buf0, buf1, size * stride))
124 fail();
125
126
1/8
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
8 bench_new(buf1, top, left, stride);
127 }
128 }
129 28 }
130
131 /*
132 * Angular prediction modes are divided into categories:
133 *
134 * Mode 10: Horizontal pure copy (H pure)
135 * Mode 26: Vertical pure copy (V pure)
136 * Modes 2-9: Horizontal positive angle (H pos) - uses left reference
137 * Modes 11-17: Horizontal negative angle (H neg) - needs reference extension
138 * Modes 18-25: Vertical negative angle (V neg) - needs reference extension
139 * Modes 27-34: Vertical positive angle (V pos) - uses top reference
140 *
141 * Each category has 4 NEON functions for 4x4, 8x8, 16x16, 32x32 sizes.
142 */
143 28 static void check_pred_angular(HEVCPredContext *h,
144 uint8_t *buf0, uint8_t *buf1,
145 uint8_t *top, uint8_t *left, int bit_depth)
146 {
147 28 const char *const block_name[] = { "4x4", "8x8", "16x16", "32x32" };
148 28 const int block_size[] = { 4, 8, 16, 32 };
149 int i, mode;
150
151 28 declare_func(void, uint8_t *src, const uint8_t *top,
152 const uint8_t *left, ptrdiff_t stride, int c_idx, int mode);
153
154 /* Test all 4 sizes */
155
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 28 times.
140 for (i = 0; i < 4; i++) {
156 112 int size = block_size[i];
157 112 ptrdiff_t stride = 64 * SIZEOF_PIXEL;
158
159 /* Test all 33 angular modes (2-34) */
160
2/2
✓ Branch 0 taken 3696 times.
✓ Branch 1 taken 112 times.
3808 for (mode = 2; mode <= 34; mode++) {
161 const char *mode_category;
162
163 /* Determine mode category for descriptive test name */
164
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 3584 times.
3696 if (mode == 10)
165 112 mode_category = "Hpure";
166
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 3472 times.
3584 else if (mode == 26)
167 112 mode_category = "Vpure";
168
3/4
✓ Branch 0 taken 3472 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 896 times.
✓ Branch 3 taken 2576 times.
3472 else if (mode >= 2 && mode <= 9)
169 896 mode_category = "Hpos";
170
3/4
✓ Branch 0 taken 2576 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 784 times.
✓ Branch 3 taken 1792 times.
2576 else if (mode >= 11 && mode <= 17)
171 784 mode_category = "Hneg";
172
3/4
✓ Branch 0 taken 1792 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 896 times.
✓ Branch 3 taken 896 times.
1792 else if (mode >= 18 && mode <= 25)
173 896 mode_category = "Vneg";
174 else /* mode >= 27 && mode <= 34 */
175 896 mode_category = "Vpos";
176
177
2/2
✓ Branch 3 taken 264 times.
✓ Branch 4 taken 3432 times.
3696 if (check_func(h->pred_angular[i],
178 "hevc_pred_angular_%s_%s_mode%d_%d",
179 block_name[i], mode_category, mode, bit_depth)) {
180 /* Test with c_idx=0 (luma) */
181
4/4
✓ Branch 1 taken 540672 times.
✓ Branch 2 taken 264 times.
✓ Branch 4 taken 8712 times.
✓ Branch 5 taken 264 times.
549648 randomize_buffers();
182 264 call_ref(buf0, top, left, stride, 0, mode);
183 264 call_new(buf1, top, left, stride, 0, mode);
184
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 264 times.
264 if (memcmp(buf0, buf1, size * stride))
185 fail();
186
187 /* Test with c_idx=1 (chroma) for modes 10/26 to cover
188 * the edge filtering skip path */
189
4/4
✓ Branch 0 taken 256 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 248 times.
264 if (mode == 10 || mode == 26) {
190
4/4
✓ Branch 1 taken 32768 times.
✓ Branch 2 taken 16 times.
✓ Branch 4 taken 528 times.
✓ Branch 5 taken 16 times.
33312 randomize_buffers();
191 16 call_ref(buf0, top, left, stride, 1, mode);
192 16 call_new(buf1, top, left, stride, 1, mode);
193
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (memcmp(buf0, buf1, size * stride))
194 fail();
195 }
196
197
1/8
✗ Branch 1 not taken.
✓ Branch 2 taken 264 times.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
264 bench_new(buf1, top, left, stride, 0, mode);
198 }
199 }
200 }
201 28 }
202
203 28 static void check_ref_filter_3tap(HEVCPredContext *h,
204 uint8_t *top, uint8_t *left, int bit_depth)
205 {
206 28 const char *const block_name[] = { "8x8", "16x16", "32x32" };
207 28 const int block_size[] = { 8, 16, 32 };
208 int i;
209
210 /* 3-tap filter: out[i] = (in[i+1] + 2*in[i] + in[i-1] + 2) >> 2
211 * Filters 2*size-1 samples (indices 0..2*size-2) plus corner [-1].
212 * Output: filtered_left[-1..2*size-1] and filtered_top[-1..2*size-1] */
213 28 declare_func(void, uint8_t *filtered_left, uint8_t *filtered_top,
214 const uint8_t *left, const uint8_t *top, int size);
215
216
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 28 times.
112 for (i = 0; i < 3; i++) {
217 84 int size = block_size[i];
218 84 int n = 2 * size;
219
220
2/2
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 78 times.
84 if (check_func(h->ref_filter_3tap[i],
221 "hevc_ref_filter_3tap_%s_%d",
222 block_name[i], bit_depth)) {
223 /* Allocate output buffers with space for [-1] indexing.
224 * Need n+1 elements: indices [-1..n-1] = n+1 pixels.
225 * Use (n+1)*SIZEOF_PIXEL bytes starting at offset SIZEOF_PIXEL. */
226 6 LOCAL_ALIGNED_32(uint8_t, fl_ref_buf, [PRED_SIZE + 16]);
227 6 LOCAL_ALIGNED_32(uint8_t, fl_new_buf, [PRED_SIZE + 16]);
228 6 LOCAL_ALIGNED_32(uint8_t, ft_ref_buf, [PRED_SIZE + 16]);
229 6 LOCAL_ALIGNED_32(uint8_t, ft_new_buf, [PRED_SIZE + 16]);
230 6 uint8_t *fl_ref = fl_ref_buf + 8;
231 6 uint8_t *fl_new = fl_new_buf + 8;
232 6 uint8_t *ft_ref = ft_ref_buf + 8;
233 6 uint8_t *ft_new = ft_new_buf + 8;
234
235
2/2
✓ Branch 1 taken 198 times.
✓ Branch 2 taken 6 times.
204 randomize_ref_buffers();
236 /* Clear output buffers so comparison is clean */
237 6 memset(fl_ref_buf, 0, PRED_SIZE + 16);
238 6 memset(fl_new_buf, 0, PRED_SIZE + 16);
239 6 memset(ft_ref_buf, 0, PRED_SIZE + 16);
240 6 memset(ft_new_buf, 0, PRED_SIZE + 16);
241
242 6 call_ref(fl_ref, ft_ref, left, top, size);
243 6 call_new(fl_new, ft_new, left, top, size);
244
245 /* Compare filtered_left[-1..2*size-1] and filtered_top[-1..2*size-1] */
246 6 if (memcmp(fl_ref - SIZEOF_PIXEL, fl_new - SIZEOF_PIXEL,
247
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 (n + 1) * SIZEOF_PIXEL))
248 fail();
249 6 if (memcmp(ft_ref - SIZEOF_PIXEL, ft_new - SIZEOF_PIXEL,
250
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 (n + 1) * SIZEOF_PIXEL))
251 fail();
252
253
1/8
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
6 bench_new(fl_new, ft_new, left, top, size);
254 }
255 }
256 28 }
257
258 28 static void check_ref_filter_strong(HEVCPredContext *h,
259 uint8_t *top, uint8_t *left,
260 int bit_depth)
261 {
262 /* Strong intra smoothing: only 32x32 luma.
263 * Interpolates top into filtered_top[0..62], sets filtered_top[-1] and [63].
264 * Modifies left[0..62] in-place. */
265 28 declare_func(void, uint8_t *filtered_top, uint8_t *left,
266 const uint8_t *top);
267
268
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 26 times.
28 if (check_func(h->ref_filter_strong,
269 "hevc_ref_filter_strong_%d", bit_depth)) {
270 2 LOCAL_ALIGNED_32(uint8_t, ft_ref_buf, [PRED_SIZE + 16]);
271 2 LOCAL_ALIGNED_32(uint8_t, ft_new_buf, [PRED_SIZE + 16]);
272 2 LOCAL_ALIGNED_32(uint8_t, left_ref_buf, [PRED_SIZE + 16]);
273 2 LOCAL_ALIGNED_32(uint8_t, left_new_buf, [PRED_SIZE + 16]);
274 2 uint8_t *ft_ref = ft_ref_buf + 8;
275 2 uint8_t *ft_new = ft_new_buf + 8;
276 2 uint8_t *left_ref = left_ref_buf + 8;
277 2 uint8_t *left_new = left_new_buf + 8;
278
279
2/2
✓ Branch 1 taken 66 times.
✓ Branch 2 taken 2 times.
68 randomize_ref_buffers();
280 2 memset(ft_ref_buf, 0, PRED_SIZE + 16);
281 2 memset(ft_new_buf, 0, PRED_SIZE + 16);
282
283 /* Copy left so both ref and new start with the same input
284 * (left is modified in-place) */
285 2 memcpy(left_ref_buf, left - 8, PRED_SIZE + 16);
286 2 memcpy(left_new_buf, left - 8, PRED_SIZE + 16);
287
288 2 call_ref(ft_ref, left_ref, top);
289 2 call_new(ft_new, left_new, top);
290
291 /* Compare filtered_top[-1..63] = 65 pixels */
292 2 if (memcmp(ft_ref - SIZEOF_PIXEL, ft_new - SIZEOF_PIXEL,
293
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 65 * SIZEOF_PIXEL))
294 fail();
295
296 /* Compare left[-1..63] = 65 pixels (left[-1] is unchanged,
297 * left[0..62] are modified, left[63] is unchanged) */
298 2 if (memcmp(left_ref - SIZEOF_PIXEL, left_new - SIZEOF_PIXEL,
299
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 65 * SIZEOF_PIXEL))
300 fail();
301
302
1/8
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
2 bench_new(ft_new, left_new, top);
303 }
304 28 }
305
306 14 void checkasm_check_hevc_pred(void)
307 {
308 14 LOCAL_ALIGNED_32(uint8_t, buf0, [BUF_SIZE]);
309 14 LOCAL_ALIGNED_32(uint8_t, buf1, [BUF_SIZE]);
310 14 LOCAL_ALIGNED_32(uint8_t, top_buf, [PRED_SIZE + 16]);
311 14 LOCAL_ALIGNED_32(uint8_t, left_buf, [PRED_SIZE + 16]);
312 /* Add offset of 8 bytes to allow negative indexing (top[-1], left[-1]) */
313 14 uint8_t *top = top_buf + 8;
314 14 uint8_t *left = left_buf + 8;
315 int bit_depth;
316
317
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
42 for (bit_depth = 8; bit_depth <= 10; bit_depth += 2) {
318 HEVCPredContext h;
319
320 28 ff_hevc_pred_init(&h, bit_depth);
321 28 check_pred_dc(&h, buf0, buf1, top, left, bit_depth);
322 }
323 14 report("pred_dc");
324
325
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
42 for (bit_depth = 8; bit_depth <= 10; bit_depth += 2) {
326 HEVCPredContext h;
327
328 28 ff_hevc_pred_init(&h, bit_depth);
329 28 check_pred_planar(&h, buf0, buf1, top, left, bit_depth);
330 }
331 14 report("pred_planar");
332
333
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
42 for (bit_depth = 8; bit_depth <= 10; bit_depth += 2) {
334 HEVCPredContext h;
335
336 28 ff_hevc_pred_init(&h, bit_depth);
337 28 check_pred_angular(&h, buf0, buf1, top, left, bit_depth);
338 }
339 14 report("pred_angular");
340
341
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
42 for (bit_depth = 8; bit_depth <= 10; bit_depth += 2) {
342 HEVCPredContext h;
343
344 28 ff_hevc_pred_init(&h, bit_depth);
345 28 check_ref_filter_3tap(&h, top, left, bit_depth);
346 }
347 14 report("ref_filter_3tap");
348
349
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
42 for (bit_depth = 8; bit_depth <= 10; bit_depth += 2) {
350 HEVCPredContext h;
351
352 28 ff_hevc_pred_init(&h, bit_depth);
353 28 check_ref_filter_strong(&h, top, left, bit_depth);
354 }
355 14 report("ref_filter_strong");
356 14 }
357