UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
ff_codec_conversions_test.cpp
Go to the documentation of this file.
1#ifdef HAVE_CONFIG_H
2#include "config.h"
3#include "config_unix.h"
4#include "config_win32.h"
5#endif
6
7#if defined HAVE_LAVC
8
9#include <algorithm>
10#include <array>
11#include <fstream>
12#include <iostream>
13#include <random>
14#include <string>
15#include <vector>
16
19#include "tv.h"
20#include "unit_common.h"
22#include "video_codec.h"
23
24using namespace std::string_literals;
25
26using std::array;
27using std::copy;
28using std::cerr;
29using std::cout;
30using std::default_random_engine;
31using std::uniform_int_distribution;
32using std::ifstream;
33using std::min;
34using std::max;
35using std::to_string;
36using std::vector;
37
38constexpr int MIN_12B = 16;
39constexpr int MAX_12B = 4079;
40
41extern "C" {
42 int ff_codec_conversions_test_yuv444pXXle_from_to_r10k();
43 int ff_codec_conversions_test_yuv444pXXle_from_to_r12l();
44 int ff_codec_conversions_test_yuv444p16le_from_to_rg48();
45 int ff_codec_conversions_test_yuv444p16le_from_to_rg48_out_of_range();
46 int ff_codec_conversions_test_pX10_from_to_v210();
47}
48
49#define CHECK(res) if ((res) != 0) { return res; }
50
51#define TIMER(t) struct timeval t{}; gettimeofday(&(t), nullptr)
52int ff_codec_conversions_test_yuv444pXXle_from_to_r10k()
53{
54 using namespace std::string_literals;
55
56 constexpr int width = 320;
57 constexpr int height = 240;
58 vector <unsigned char> rgba_buf(width * height * 4);
59
61 auto test_pattern = [&](AVPixelFormat avfmt) {
62 vector <unsigned char> r10k_buf(width * height * 4);
64 assert(vc_copylineRGBAtoR10k != nullptr);
65 vc_copylineRGBAtoR10k(r10k_buf.data(), rgba_buf.data(), 4L * width * height, 0, 8, 16);
66
67 struct to_lavc_vid_conv *from_conv = to_lavc_vid_conv_init(R10k, width, height, avfmt, 1);
68 auto to_conv = get_av_to_uv_conversion(avfmt, R10k);
69 assert(to_conv != nullptr && from_conv != nullptr);
70
71 TIMER(t0);
72 AVFrame *converted = to_lavc_vid_conv(from_conv, (char *) r10k_buf.data());
73 TIMER(t1);
74 av_to_uv_convert(to_conv, reinterpret_cast<char*>(r10k_buf.data()), converted, width, height, vc_get_linesize(width, R10k), nullptr);
75 TIMER(t2);
76 to_lavc_vid_conv_destroy(&from_conv);
78
79 if (getenv("PERF") != nullptr) {
80 cout << "test_yuv444p16le_from_to_r10k: duration - enc " << tv_diff(t1, t0) << ", dec " <<tv_diff(t2, t1) << "\n";
81 }
82
83 vector <unsigned char> rgba_buf_res(width * height * 4);
85 vc_copyliner10k(rgba_buf_res.data(), r10k_buf.data(), height * vc_get_linesize(width, RGBA), 0, 8, 16);
86
87 int max_diff = 0;
88 for (size_t i = 0; i < width * height; ++i) {
89 for (int j = 0; j < 3; ++j) {
90 max_diff = max<int>(max_diff, abs(rgba_buf[4 * i + j] - rgba_buf_res[4 * i + j]));
91 //fprintf(stderr, "%d %d\n", (int) rgba_buf[4 * i + j], (int) rgba_buf_res[4 * i + j]);
92 }
93 //fprintf(stderr, "R in 10 bits = %d\n", (int) (r10k_buf[4 * i] << 2) + (r10k_buf[4 * i + 1] >> 6));
94 }
95
96 if (getenv("DEBUG_DUMP") != nullptr) {
97 FILE *out = fopen("out.rgba","w");
98 fwrite(rgba_buf_res.data(), width * height * 4, 1, out);
99 fclose(out);
100 }
101
102 ASSERT_MESSAGE("Maximal allowed difference 1, found "s + to_string(max_diff), max_diff <= 1);
103 return 0;
104 };
105
106 for (auto f : { AV_PIX_FMT_YUV444P10LE, AV_PIX_FMT_YUV444P12LE, AV_PIX_FMT_YUV444P16LE }) {
107 int i = 0;
108 for_each(rgba_buf.begin(), rgba_buf.end(), [&](unsigned char & c) { c = (i++ / 4) % 0x100; });
109 CHECK(test_pattern(f));
110
111 array<unsigned char, 4> pattern{ 0xFFU, 0, 0, 0xFFU };
112 for_each(rgba_buf.begin(), rgba_buf.end(), [&](unsigned char & c) { c = pattern[i++ % 4]; });
113 CHECK(test_pattern(f));
114
115 default_random_engine rand_gen;
116 for_each(rgba_buf.begin(), rgba_buf.end(), [&](unsigned char & c) { c = rand_gen() % 0x100; });
117 CHECK(test_pattern(f));
118 }
119 return 0;
120}
121
122int ff_codec_conversions_test_yuv444pXXle_from_to_r12l()
123{
124 using namespace std::string_literals;
125
126 constexpr int width = 320;
127 constexpr int height = 240;
128 vector <unsigned char> rgb_buf(width * height * 3);
130 auto test_pattern = [&](AVPixelFormat avfmt) {
131 vector <unsigned char> r12l_buf(vc_get_datalen(width, height, R12L));
132 decoder_t rgb_to_r12l = get_decoder_from_to(RGB, R12L);
133 rgb_to_r12l(r12l_buf.data(), rgb_buf.data(), vc_get_datalen(width, height, R12L), 0, 8, 16);
134
135 struct to_lavc_vid_conv *from_conv = to_lavc_vid_conv_init(R12L, width, height, avfmt, 1);
136 auto to_conv = get_av_to_uv_conversion(avfmt, R12L);
137 assert(to_conv != nullptr && from_conv != nullptr);
138
139 TIMER(t0);
140 struct AVFrame *converted = to_lavc_vid_conv(from_conv, (char *) r12l_buf.data());
141 TIMER(t1);
142 av_to_uv_convert(to_conv, reinterpret_cast<char*>(r12l_buf.data()), converted, width, height, vc_get_linesize(width, R12L), nullptr);
143 TIMER(t2);
145 to_lavc_vid_conv_destroy(&from_conv);
146
147 if (getenv("PERF") != nullptr) {
148 cout << "test_yuv444p16le_from_to_r12l: duration - enc " << tv_diff(t1, t0) << ", dec " <<tv_diff(t2, t1) << "\n";
149 }
150
151
152 vector <unsigned char> rgb_buf_res(width * height * 3);
153 decoder_t r12l_to_rgb = get_decoder_from_to(R12L, RGB);
154 r12l_to_rgb(rgb_buf_res.data(), r12l_buf.data(), vc_get_datalen(width, height, RGB), 0, 8, 16);
155
156 int max_diff = 0;
157 for (size_t i = 0; i < width * height; ++i) {
158 for (int j = 0; j < 3; ++j) {
159 max_diff = max<int>(max_diff, abs(rgb_buf[3 * i + j] - rgb_buf_res[3 * i + j]));
160 }
161 }
162
163 if (getenv("DEBUG_DUMP") != nullptr) {
164 FILE *out = fopen("out.rgb","w");
165 fwrite(rgb_buf_res.data(), width * height * 3, 1, out);
166 fclose(out);
167 }
168
169 ASSERT_MESSAGE("Maximal allowed difference 1, found "s + to_string(max_diff), max_diff <= 1);
170 return 0;
171 };
172
173 for (auto f : { AV_PIX_FMT_YUV444P10LE, AV_PIX_FMT_YUV444P12LE, AV_PIX_FMT_YUV444P16LE }) {
174 int i = 0;
175 array<unsigned char, 3> pattern{ 0xFFU, 0, 0 };
176 for_each(rgb_buf.begin(), rgb_buf.end(), [&](unsigned char & c) { c = pattern[i++ % 3]; });
177 CHECK(test_pattern(f));
178
179 for_each(rgb_buf.begin(), rgb_buf.end(), [&](unsigned char & c) { c = (i++ / 3) % 0x100; });
180 CHECK(test_pattern(f));
181
182 default_random_engine rand_gen;
183 for_each(rgb_buf.begin(), rgb_buf.end(), [&](unsigned char & c) { c = rand_gen() % 0x100; });
184 CHECK(test_pattern(f));
185 }
186 return 0;
187}
188
189static void yuv444p16le_rg48_encode_decode(int width, int height, char *in, char *out) {
190 AVPixelFormat avfmt = AV_PIX_FMT_YUV444P16LE;
191
192 struct to_lavc_vid_conv *from_conv = to_lavc_vid_conv_init(RG48, width, height, avfmt, 1);
193 auto to_conv = get_av_to_uv_conversion(avfmt, RG48);
194 assert(to_conv && from_conv != nullptr);
195
196 TIMER(t0);
197 struct AVFrame *converted = to_lavc_vid_conv(from_conv, in);
198 TIMER(t1);
199 av_to_uv_convert(to_conv, reinterpret_cast<char*>(out), converted, width, height, vc_get_linesize(width, RG48), nullptr);
200 TIMER(t2);
202 to_lavc_vid_conv_destroy(&from_conv);
203
204 if (getenv("PERF") != nullptr) {
205 cout << "test_yuv444p16le_from_to_rg48: duration - enc " << tv_diff(t1, t0) << ", dec " <<tv_diff(t2, t1) << "\n";
206 }
207}
208
214int ff_codec_conversions_test_yuv444p16le_from_to_rg48_out_of_range()
215{
216 using namespace std::string_literals;
217
218 constexpr int MAX_DIFF = 16;
219
220 constexpr int width = 4096;
221 constexpr int height = 5;
222 vector <uint16_t> rg48_buf(width * height * 3);
223 vector <uint16_t> rg48_buf_res(width * height * 3);
224
225 for (int i = 0; i < 4096; i += 1) { // grayscale tones
226 rg48_buf[3 * i] =
227 rg48_buf[3 * i + 1] =
228 rg48_buf[3 * i + 2] = 65535 * i / 4095;
229 }
230 for (int line = 0; line < 3; ++line) { // R, G, B scale
231 for (int i = 0; i < 4096; i += 1) {
232 rg48_buf[(line + 1) * (width * 3) + 3 * i + line] = 65535 * i / 4095;
233 }
234 }
235 for (int i = 0; i < 4096; i += 1) { // custom
236 rg48_buf[4 * (width * 3) + 3 * i] = 65535;
237 rg48_buf[4 * (width * 3) + 3 * i + 1] = 65535;
238 rg48_buf[4 * (width * 3) + 3 * i + 2] = 0;
239 }
240
241 yuv444p16le_rg48_encode_decode(width, height, reinterpret_cast<char *>(rg48_buf.data()),
242 reinterpret_cast<char *>(rg48_buf_res.data()));
243
244 int max_diff = 0;
245 for (size_t i = 0; i < width * height; ++i) {
246 for (int j = 0; j < 3; ++j) {
247 int in = rg48_buf[3 * i + j];
248 in = min(max(MIN_12B << 4U, in), MAX_12B << 4U);
249 int out = rg48_buf_res[3 * i + j];
250 int diff = in - out;
251 if (abs(diff) >= MAX_DIFF && getenv("DEBUG") != nullptr) {
252 cout << "different value at pos: " << i << "," << j << " diff: " << diff << "\n";
253 }
254 max_diff = max<int>(max_diff, abs(diff));
255 out >>= 4;
256 ASSERT_MESSAGE("Value "s + to_string(out) + " out of range "s + to_string(MIN_12B) + "-"s + to_string(MAX_12B), out >= MIN_12B && out <= MAX_12B);
257 }
258 }
259
260 if (getenv("DEBUG_DUMP") != nullptr) {
261 std::ofstream in("in.rg48", std::ifstream::out | std::ifstream::binary);
262 in.write(reinterpret_cast<char *>(rg48_buf.data()), rg48_buf.size() * sizeof(decltype(rg48_buf)::value_type));
263 std::ofstream out("out.rg48", std::ifstream::out | std::ifstream::binary);
264 out.write(reinterpret_cast<char *>(rg48_buf_res.data()), rg48_buf_res.size() * sizeof(decltype(rg48_buf_res)::value_type));
265 }
266
267 ASSERT_MESSAGE("Maximal allowed difference "s + to_string (MAX_DIFF) + "/65535, found "s + to_string(max_diff), max_diff <= MAX_DIFF);
268 return 0;
269}
270
275int ff_codec_conversions_test_yuv444p16le_from_to_rg48()
276{
277 constexpr int MAX_DIFF = 16;
278
279 int width = 2048;
280 int height = 1;
281 vector <uint16_t> rg48_buf(width * height * 3, MIN_12B << 4); // SDI minimal value
282 vector <uint16_t> rg48_buf_res(width * height * 3);
283
284 for (int i = 16; i < 2040; i += 1) {
285 rg48_buf[3 * i] =
286 rg48_buf[3 * i + 1] =
287 rg48_buf[3 * i + 2] = (i * 2) << 4;
288 }
289 for (int i = 2040; i < 2048; i += 1) {
290 rg48_buf[3 * i] =
291 rg48_buf[3 * i + 1] =
292 rg48_buf[3 * i + 2] = MAX_12B << 4;
293 }
294
295 if (getenv("UG_TEST_RG48_INPUT") != nullptr) {
296 char *cpy = strdup(getenv("UG_TEST_RG48_INPUT"));
297 char *filename = strtok(cpy, ":");
298 width = atoi(strtok(nullptr, ":"));
299 height = atoi(strtok(nullptr, ":"));
300 rg48_buf.resize(width * height * 3);
301 rg48_buf_res.resize(width * height * 3);
302 ifstream in(filename, std::ifstream::in | std::ifstream::binary);
303 in.read(reinterpret_cast<char *>(rg48_buf.data()), rg48_buf.size() * sizeof(decltype(rg48_buf)::value_type));
304 }
305
306 yuv444p16le_rg48_encode_decode(width, height, reinterpret_cast<char *>(rg48_buf.data()),
307 reinterpret_cast<char *>(rg48_buf_res.data()));
308
309 int max_diff = 0;
310 for (int i = 0; i < width * height; ++i) {
311 for (int j = 0; j < 3; ++j) {
312 int in = rg48_buf[3 * i + j];
313 int out = rg48_buf_res[3 * i + j];
314 int diff = in - out;
315 if (abs(diff) >= MAX_DIFF && getenv("DEBUG") != nullptr) {
316 cout << "different value at pos: " << i << "," << j << " diff: " << diff << " in: " << in << " out: " << out << "\n";
317 }
318 max_diff = max<int>(max_diff, abs(diff));
319 out >>= 4;
320 ASSERT_MESSAGE("Value "s + to_string(out) + " out of range "s + to_string(MIN_12B) + "-"s + to_string(MAX_12B), out >= MIN_12B && out <= MAX_12B);
321 }
322 }
323
324 if (getenv("DEBUG_DUMP") != nullptr) {
325 std::ofstream in("in.rg48", std::ifstream::out | std::ifstream::binary);
326 in.write(reinterpret_cast<char *>(rg48_buf.data()), rg48_buf.size() * sizeof(decltype(rg48_buf)::value_type));
327 std::ofstream out("out.rg48", std::ifstream::out | std::ifstream::binary);
328 out.write(reinterpret_cast<char *>(rg48_buf_res.data()), rg48_buf_res.size() * sizeof(decltype(rg48_buf_res)::value_type));
329 }
330
331 ASSERT_MESSAGE("Maximal allowed difference "s + to_string (MAX_DIFF) + "/65535, found "s + to_string(max_diff), max_diff <= MAX_DIFF);
332 return 0;
333}
334
340int ff_codec_conversions_test_pX10_from_to_v210()
341{
342 constexpr codec_t codec = v210;
343 constexpr long width = 1920;
344 constexpr long height = 4;
345 const long linesize = vc_get_linesize(width, codec);
346 vector <uint32_t> in(height * linesize / sizeof(uint32_t));
347 vector <uint32_t> out(height * linesize / sizeof(uint32_t));
348 default_random_engine rand_gen;
349 uniform_int_distribution<uint32_t> dist(0, 0x3fffffffLU);
350
351 for (const auto &c : {AV_PIX_FMT_P010LE,
352#if P210_PRESENT
353 AV_PIX_FMT_P210LE
354#endif
355 }) {
356#if P210_PRESENT
357 if (c == AV_PIX_FMT_P210LE) {
358#else
359 if (false) {
360#endif
361 std::for_each(in.begin(), in.end(), [&](uint32_t & c) { c = dist(rand_gen); });
362 } else { // later using dummy "==" compare, chroma in odd and even line must be same for P010 to avoid rounding errors
363 std::for_each(in.begin(), in.begin() + linesize / sizeof(uint32_t), [&](uint32_t & c) { c = dist(rand_gen); });
364 for (int i = 1; i < height; ++i) {
365 std::copy(in.begin(), in.begin() + linesize / sizeof(uint32_t), in.begin() + linesize / sizeof(uint32_t) * i);
366 }
367 }
368
369 struct to_lavc_vid_conv *from_conv = to_lavc_vid_conv_init(codec, width, height, c, 1);
370 auto to_conv = get_av_to_uv_conversion(c, codec);
371 assert(from_conv != nullptr);
372 assert(to_conv != nullptr);
373
374 struct AVFrame *converted = to_lavc_vid_conv(from_conv, (char *) in.data());
375 av_to_uv_convert(to_conv, reinterpret_cast<char *>(out.data()), converted, width, height, vc_get_linesize(width, codec), nullptr);
377 to_lavc_vid_conv_destroy(&from_conv);
378
379 if (getenv("DEBUG_DUMP") != nullptr) {
380 for (int i = 0; i < 128; i += 1) {
381 auto *din = reinterpret_cast<uint32_t *>(in.data()) + i * 4;
382 auto *dout = reinterpret_cast<uint32_t *>(out.data()) + i * 4;
383 printf("0x%08x IN =%08x %08x %08x %08x\n", i * 16, din[0], din[1], din[2], din[3]);
384 printf("0x%08x OUT=%08x %08x %08x %08x\n", i * 16, dout[0], dout[1], dout[2], dout[3]);
385 }
386 }
387
388 ASSERT_MESSAGE("Error: output doesn't match input"s, in == out);
389 out[(width / 2) * height] = 123;
390 ASSERT_MESSAGE("Error: output matches input but it shouldn't"s, in != out);
391 }
392 return 0;
393}
394
395#endif // HAVE_LAVC
int vc_get_linesize(unsigned int width, codec_t codec)
pattern
Definition testcard.c:94
static __device__ vec2 abs(const vec2 &v)
Definition cuda_dxt.cu:118
av_to_uv_convert_t * get_av_to_uv_conversion(int av_codec, codec_t uv_codec)
void av_to_uv_conversion_destroy(av_to_uv_convert_t **s)
void av_to_uv_convert(const av_to_uv_convert_t *convert, char *dst, AVFrame *in, int width, int height, int pitch, const int rgb_shift[3])
static void vc_copyliner10k(unsigned char *__restrict dst, const unsigned char *__restrict src, int len, int rshift, int gshift, int bshift)
Converts from R10k to RGBA.
static void vc_copylineRGBAtoR10k(unsigned char *__restrict dst, const unsigned char *__restrict src, int dst_len, int rshift, int gshift, int bshift)
decoder_t get_decoder_from_to(codec_t in, codec_t out)
decoder_func_t * decoder_t
Definition pixfmt_conv.h:96
#define CHECK(cmd)
Definition wasapi.cpp:374
#define min(a, b)
Definition rtp.c:99
#define max(a, b)
Definition rtp.c:98
void to_lavc_vid_conv_destroy(struct to_lavc_vid_conv **s_p)
struct AVFrame * to_lavc_vid_conv(struct to_lavc_vid_conv *s, char *in_data)
struct to_lavc_vid_conv * to_lavc_vid_conv_init(codec_t in_pixfmt, int width, int height, enum AVPixelFormat out_pixfmt, int thread_count)
double tv_diff(struct timeval curr_time, struct timeval prev_time)
Definition tv.c:97
codec_t
Definition types.h:65
@ R10k
RGB 10-bit packed - RGBX, big-endian (2 bit padding)
Definition types.h:73
@ RGB
RGB 8-bit (packed into 24-bit word)
Definition types.h:80
@ RG48
16-bit RGB little-endian (RlRhGl...)
Definition types.h:95
@ R12L
RGB 12-bit packed, little-endian.
Definition types.h:74
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
@ v210
YCbCr 422 10-bit - xVY0U|xY2UY1|xUY3V|xY5VY4, little-endian (2 bit padding /'x'/),...
Definition types.h:75
#define ASSERT_MESSAGE(msg, expr)
Definition unit_common.h:20
size_t vc_get_datalen(unsigned int width, unsigned int height, codec_t codec)