Some tidying up of test Doxygen.
[dcpomatic.git] / test / video_level_test.cc
1 /*
2     Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic 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     DCP-o-matic 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
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 /** @file  test/video_level_test.cc
23  *  @brief Test that video level ranges are handled correctly.
24  *  @ingroup feature
25  */
26
27
28 #include "lib/content_factory.h"
29 #include "lib/content_video.h"
30 #include "lib/dcp_content.h"
31 #include "lib/film.h"
32 #include "lib/ffmpeg_content.h"
33 #include "lib/ffmpeg_decoder.h"
34 #include "lib/ffmpeg_image_proxy.h"
35 #include "lib/image.h"
36 #include "lib/image_content.h"
37 #include "lib/image_decoder.h"
38 #include "lib/ffmpeg_encoder.h"
39 #include "lib/job_manager.h"
40 #include "lib/transcode_job.h"
41 #include "lib/video_decoder.h"
42 #include "test.h"
43 #include <dcp/cpl.h>
44 #include <dcp/dcp.h>
45 #include <dcp/mono_picture_asset.h>
46 #include <dcp/mono_picture_frame.h>
47 #include <dcp/openjpeg_image.h>
48 #include <dcp/reel.h>
49 #include <dcp/reel_picture_asset.h>
50 #include <boost/test/unit_test.hpp>
51
52
53 using std::min;
54 using std::make_pair;
55 using std::max;
56 using std::pair;
57 using std::string;
58 using boost::dynamic_pointer_cast;
59 using boost::optional;
60 using boost::shared_ptr;
61
62
63 static
64 shared_ptr<Image>
65 grey_image (dcp::Size size, uint8_t pixel)
66 {
67         shared_ptr<Image> grey(new Image(AV_PIX_FMT_RGB24, size, true));
68         for (int y = 0; y < size.height; ++y) {
69                 uint8_t* p = grey->data()[0] + y * grey->stride()[0];
70                 for (int x = 0; x < size.width; ++x) {
71                         *p++ = pixel;
72                         *p++ = pixel;
73                         *p++ = pixel;
74                 }
75         }
76
77         return grey;
78 }
79
80
81 BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
82 {
83         dcp::Size size(640, 480);
84         uint8_t const grey_pixel = 128;
85         boost::filesystem::path const file = "build/test/ffmpeg_image_full_range_not_changed.png";
86
87         write_image (grey_image(size, grey_pixel), file);
88
89         FFmpegImageProxy proxy (file, VIDEO_RANGE_FULL);
90         ImageProxy::Result result = proxy.image ();
91         BOOST_REQUIRE (!result.error);
92
93         for (int y = 0; y < size.height; ++y) {
94                 uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
95                 for (int x = 0; x < size.width; ++x) {
96                         BOOST_REQUIRE (*p++ == grey_pixel);
97                 }
98         }
99 }
100
101
102 BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
103 {
104         dcp::Size size(640, 480);
105         uint8_t const grey_pixel = 128;
106         uint8_t const expanded_grey_pixel = static_cast<uint8_t>((grey_pixel - 16) * 256.0 / 219);
107         boost::filesystem::path const file = "build/test/ffmpeg_image_video_range_expanded.png";
108
109         write_image (grey_image(size, grey_pixel), file);
110
111         FFmpegImageProxy proxy (file, VIDEO_RANGE_VIDEO);
112         ImageProxy::Result result = proxy.image ();
113         BOOST_REQUIRE (!result.error);
114
115         for (int y = 0; y < size.height; ++y) {
116                 uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
117                 for (int x = 0; x < size.width; ++x) {
118                         BOOST_REQUIRE_EQUAL (*p++, expanded_grey_pixel);
119                 }
120         }
121 }
122
123
124 static optional<ContentVideo> content_video;
125
126
127 static
128 void
129 video_handler (ContentVideo cv)
130 {
131         content_video = cv;
132 }
133
134
135 static
136 pair<int, int>
137 pixel_range (shared_ptr<const Image> image)
138 {
139         pair<int, int> range(INT_MAX, 0);
140         switch (image->pixel_format()) {
141         case AV_PIX_FMT_RGB24:
142         {
143                 dcp::Size const size = image->sample_size(0);
144                 for (int y = 0; y < size.height; ++y) {
145                         uint8_t* p = image->data()[0] + y * image->stride()[0];
146                         for (int x = 0; x < size.width * 3; ++x) {
147                                 range.first = min(range.first, static_cast<int>(*p));
148                                 range.second = max(range.second, static_cast<int>(*p));
149                                 ++p;
150                         }
151                 }
152                 break;
153         }
154         case AV_PIX_FMT_YUV444P:
155         {
156                 for (int c = 0; c < 3; ++c) {
157                         dcp::Size const size = image->sample_size(c);
158                         for (int y = 0; y < size.height; ++y) {
159                                 uint8_t* p = image->data()[c] + y * image->stride()[c];
160                                 for (int x = 0; x < size.width; ++x) {
161                                         range.first = min(range.first, static_cast<int>(*p));
162                                         range.second = max(range.second, static_cast<int>(*p));
163                                         ++p;
164                                 }
165                         }
166                 }
167                 break;
168         }
169         case AV_PIX_FMT_YUV422P10LE:
170         case AV_PIX_FMT_YUV444P10LE:
171         case AV_PIX_FMT_YUV444P12LE:
172         {
173                 for (int c = 0; c < 3; ++c) {
174                         dcp::Size const size = image->sample_size(c);
175                         for (int y = 0; y < size.height; ++y) {
176                                 uint16_t* p = reinterpret_cast<uint16_t*>(image->data()[c]) + y * image->stride()[c] / 2;
177                                 for (int x = 0; x < size.width; ++x) {
178                                         range.first = min(range.first, static_cast<int>(*p));
179                                         range.second = max(range.second, static_cast<int>(*p));
180                                         ++p;
181                                 }
182                         }
183                 }
184                 break;
185         }
186         default:
187                 BOOST_REQUIRE_MESSAGE (false, "No support for pixel format " << image->pixel_format());
188         }
189
190         return range;
191 }
192
193
194 static
195 pair<int, int>
196 pixel_range (shared_ptr<Film> film, shared_ptr<const FFmpegContent> content)
197 {
198         shared_ptr<FFmpegDecoder> decoder(new FFmpegDecoder(film, content, false));
199         decoder->video->Data.connect (bind(&video_handler, _1));
200         content_video = boost::none;
201         while (!content_video) {
202                 BOOST_REQUIRE (!decoder->pass());
203         }
204
205         return pixel_range (content_video->image->image().image);
206 }
207
208
209 static
210 pair<int, int>
211 pixel_range (shared_ptr<Film> film, shared_ptr<const ImageContent> content)
212 {
213         shared_ptr<ImageDecoder> decoder(new ImageDecoder(film, content));
214         decoder->video->Data.connect (bind(&video_handler, _1));
215         content_video = boost::none;
216         while (!content_video) {
217                 BOOST_REQUIRE (!decoder->pass());
218         }
219
220         return pixel_range (content_video->image->image().image);
221 }
222
223
224 static
225 pair<int, int>
226 pixel_range (boost::filesystem::path dcp_path)
227 {
228         dcp::DCP dcp (dcp_path);
229         dcp.read ();
230
231         shared_ptr<dcp::MonoPictureAsset> picture = dynamic_pointer_cast<dcp::MonoPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
232         BOOST_REQUIRE (picture);
233         shared_ptr<dcp::OpenJPEGImage> frame = picture->start_read()->get_frame(0)->xyz_image();
234
235         int const width = frame->size().width;
236         int const height = frame->size().height;
237
238         pair<int, int> range(INT_MAX, 0);
239         for (int c = 0; c < 3; ++c) {
240                 for (int y = 0; y < height; ++y) {
241                         int* p = frame->data(c) + y * width;
242                         for (int x = 0; x < width; ++x) {
243                                 range.first = min(range.first, *p);
244                                 range.second = max(range.second, *p);
245                                 ++p;
246                         }
247                 }
248         }
249
250         return range;
251 }
252
253
254 /* Functions to make a Film with different sorts of content.
255  *
256  * In these names V = video range (limited)
257  *                F = full range  (not limited)
258  *                o = overridden
259  */
260
261
262 static
263 shared_ptr<Film>
264 movie_V (string name)
265 {
266         shared_ptr<Film> film = new_test_film2 (name);
267         shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
268         BOOST_REQUIRE (content);
269         film->examine_and_add_content (content);
270         BOOST_REQUIRE (!wait_for_jobs());
271
272         pair<int, int> range = pixel_range (film, content);
273         BOOST_CHECK_EQUAL (range.first, 15);
274         BOOST_CHECK_EQUAL (range.second, 243);
275
276         return film;
277 }
278
279
280 static
281 shared_ptr<Film>
282 movie_VoF (string name)
283 {
284         shared_ptr<Film> film = new_test_film2 (name);
285         shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
286         BOOST_REQUIRE (content);
287         film->examine_and_add_content (content);
288         BOOST_REQUIRE (!wait_for_jobs());
289         content->video->set_range (VIDEO_RANGE_FULL);
290
291         pair<int, int> range = pixel_range (film, content);
292         BOOST_CHECK_EQUAL (range.first, 15);
293         BOOST_CHECK_EQUAL (range.second, 243);
294
295         return film;
296 }
297
298
299 static
300 shared_ptr<Film>
301 movie_F (string name)
302 {
303         shared_ptr<Film> film = new_test_film2 (name);
304         shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
305         BOOST_REQUIRE (content);
306         film->examine_and_add_content (content);
307         BOOST_REQUIRE (!wait_for_jobs());
308
309         pair<int, int> range = pixel_range (film, content);
310         BOOST_CHECK_EQUAL (range.first, 0);
311         BOOST_CHECK_EQUAL (range.second, 1023);
312
313         return film;
314 }
315
316
317 static
318 shared_ptr<Film>
319 movie_FoV (string name)
320 {
321         shared_ptr<Film> film = new_test_film2 (name);
322         shared_ptr<FFmpegContent> content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
323         BOOST_REQUIRE (content);
324         film->examine_and_add_content (content);
325         BOOST_REQUIRE (!wait_for_jobs());
326         content->video->set_range (VIDEO_RANGE_VIDEO);
327
328         pair<int, int> range = pixel_range (film, content);
329         BOOST_CHECK_EQUAL (range.first, 0);
330         BOOST_CHECK_EQUAL (range.second, 1023);
331
332         return film;
333 }
334
335
336 static
337 shared_ptr<Film>
338 image_F (string name)
339 {
340         shared_ptr<Film> film = new_test_film2 (name);
341         shared_ptr<ImageContent> content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
342         BOOST_REQUIRE (content);
343         film->examine_and_add_content (content);
344         BOOST_REQUIRE (!wait_for_jobs());
345
346         pair<int, int> range = pixel_range (film, content);
347         BOOST_CHECK_EQUAL (range.first, 0);
348         BOOST_CHECK_EQUAL (range.second, 255);
349
350         return film;
351 }
352
353
354 static
355 shared_ptr<Film>
356 image_FoV (string name)
357 {
358         shared_ptr<Film> film = new_test_film2 (name);
359         shared_ptr<ImageContent> content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
360         BOOST_REQUIRE (content);
361         film->examine_and_add_content (content);
362         BOOST_REQUIRE (!wait_for_jobs());
363         content->video->set_range (VIDEO_RANGE_VIDEO);
364
365         pair<int, int> range = pixel_range (film, content);
366         BOOST_CHECK_EQUAL (range.first, 11);
367         BOOST_CHECK_EQUAL (range.second, 250);
368
369         return film;
370 }
371
372
373 static
374 shared_ptr<Film>
375 dcp_F (string name)
376 {
377         boost::filesystem::path const dcp = "test/data/RgbGreyTestcar_TST-1_F_MOS_2K_20201115_SMPTE_OV";
378         shared_ptr<Film> film = new_test_film2 (name);
379         shared_ptr<DCPContent> content(new DCPContent(dcp));
380         film->examine_and_add_content (shared_ptr<DCPContent>(new DCPContent(dcp)));
381         BOOST_REQUIRE (!wait_for_jobs());
382
383         pair<int, int> range = pixel_range (dcp);
384         BOOST_CHECK_EQUAL (range.first, 0);
385         BOOST_CHECK_EQUAL (range.second, 4081);
386
387         return film;
388 }
389
390
391
392 /* Functions to get the pixel range in different sorts of output */
393
394
395 /** Get the pixel range in a DCP made from film */
396 static
397 pair<int, int>
398 dcp_range (shared_ptr<Film> film)
399 {
400         film->make_dcp ();
401         BOOST_REQUIRE (!wait_for_jobs());
402         return pixel_range (film->dir(film->dcp_name()));
403 }
404
405
406 /** Get the pixel range in a video-range movie exported from film */
407 static
408 pair<int, int>
409 V_movie_range (shared_ptr<Film> film)
410 {
411         shared_ptr<TranscodeJob> job (new TranscodeJob(film));
412         job->set_encoder (
413                 shared_ptr<FFmpegEncoder>(
414                         new FFmpegEncoder (film, job, film->file("export.mov"), EXPORT_FORMAT_PRORES, true, false, false, 23)
415                         )
416                 );
417         JobManager::instance()->add (job);
418         BOOST_REQUIRE (!wait_for_jobs());
419
420         /* This is a bit of a hack; add the exported file into the project so we can decode it */
421         shared_ptr<FFmpegContent> content(new FFmpegContent(film->file("export.mov")));
422         film->examine_and_add_content (content);
423         BOOST_REQUIRE (!wait_for_jobs());
424
425         return pixel_range (film, content);
426 }
427
428
429 /* The tests */
430
431
432 BOOST_AUTO_TEST_CASE (movie_V_to_dcp)
433 {
434         pair<int, int> range = dcp_range (movie_V("movie_V_to_dcp"));
435         /* Video range has been correctly expanded to full for the DCP */
436         BOOST_CHECK_EQUAL (range.first, 0);
437         BOOST_CHECK_EQUAL (range.second, 4082);
438 }
439
440
441 BOOST_AUTO_TEST_CASE (movie_VoF_to_dcp)
442 {
443         pair<int, int> range = dcp_range (movie_VoF("movie_VoF_to_dcp"));
444         /* We said that video range data was really full range, so here we are in the DCP
445          * with video-range data.
446          */
447         BOOST_CHECK_EQUAL (range.first, 350);
448         BOOST_CHECK_EQUAL (range.second, 3832);
449 }
450
451
452 BOOST_AUTO_TEST_CASE (movie_F_to_dcp)
453 {
454         pair<int, int> range = dcp_range (movie_F("movie_F_to_dcp"));
455         /* The nearly-full-range of the input has been preserved */
456         BOOST_CHECK_EQUAL (range.first, 0);
457         BOOST_CHECK_EQUAL (range.second, 4082);
458 }
459
460
461 BOOST_AUTO_TEST_CASE (video_FoV_to_dcp)
462 {
463         pair<int, int> range = dcp_range (movie_FoV("video_FoV_to_dcp"));
464         /* The nearly-full-range of the input has become even more full, and clipped */
465         BOOST_CHECK_EQUAL (range.first, 0);
466         BOOST_CHECK_EQUAL (range.second, 4095);
467 }
468
469
470 BOOST_AUTO_TEST_CASE (image_F_to_dcp)
471 {
472         pair<int, int> range = dcp_range (image_F("image_F_to_dcp"));
473         BOOST_CHECK_EQUAL (range.first, 0);
474         BOOST_CHECK_EQUAL (range.second, 4081);
475 }
476
477
478 BOOST_AUTO_TEST_CASE (image_FoV_to_dcp)
479 {
480         pair<int, int> range = dcp_range (image_FoV("image_FoV_to_dcp"));
481         BOOST_CHECK_EQUAL (range.first, 431);
482         BOOST_CHECK_EQUAL (range.second, 4012);
483 }
484
485
486 BOOST_AUTO_TEST_CASE (movie_V_to_V_movie)
487 {
488         pair<int, int> range = V_movie_range (movie_V("movie_V_to_V_movie"));
489         BOOST_CHECK_EQUAL (range.first, 60);
490         BOOST_CHECK_EQUAL (range.second, 998);
491 }
492
493
494 BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie)
495 {
496         pair<int, int> range = V_movie_range (movie_VoF("movie_VoF_to_V_movie"));
497         BOOST_CHECK_EQUAL (range.first, 116);
498         BOOST_CHECK_EQUAL (range.second, 939);
499 }
500
501
502 BOOST_AUTO_TEST_CASE (movie_F_to_V_movie)
503 {
504         pair<int, int> range = V_movie_range (movie_F("movie_F_to_V_movie"));
505         BOOST_CHECK_EQUAL (range.first, 4);
506         BOOST_CHECK_EQUAL (range.second, 1019);
507 }
508
509
510 BOOST_AUTO_TEST_CASE (movie_FoV_to_V_movie)
511 {
512         pair<int, int> range = V_movie_range (movie_FoV("movie_FoV_to_V_movie"));
513         BOOST_CHECK_EQUAL (range.first, 4);
514         BOOST_CHECK_EQUAL (range.second, 1019);
515 }
516
517
518 BOOST_AUTO_TEST_CASE (image_F_to_V_movie)
519 {
520         pair<int, int> range = V_movie_range (image_F("image_F_to_V_movie"));
521         BOOST_CHECK_EQUAL (range.first, 64);
522         BOOST_CHECK_EQUAL (range.second, 960);
523 }
524
525
526 BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie)
527 {
528         pair<int, int> range = V_movie_range (image_FoV("image_FoV_to_V_movie"));
529         BOOST_CHECK_EQUAL (range.first, 102);
530         BOOST_CHECK_EQUAL (range.second, 923);
531 }
532
533
534 BOOST_AUTO_TEST_CASE (dcp_F_to_V_movie)
535 {
536         pair<int, int> range = V_movie_range (dcp_F("dcp_F_to_V_movie"));
537         BOOST_CHECK_EQUAL (range.first, 64);
538         BOOST_CHECK_EQUAL (range.second, 944);
539 }
540