Fix silent stereo mixdown exports when the project audio channel count is > 6.
[dcpomatic.git] / test / video_level_test.cc
1 /*
2     Copyright (C) 2020-2021 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/decoder_factory.h"
32 #include "lib/film.h"
33 #include "lib/ffmpeg_content.h"
34 #include "lib/ffmpeg_decoder.h"
35 #include "lib/ffmpeg_image_proxy.h"
36 #include "lib/image.h"
37 #include "lib/image_content.h"
38 #include "lib/image_decoder.h"
39 #include "lib/ffmpeg_encoder.h"
40 #include "lib/job_manager.h"
41 #include "lib/player.h"
42 #include "lib/player_video.h"
43 #include "lib/transcode_job.h"
44 #include "lib/video_decoder.h"
45 #include "test.h"
46 #include <dcp/cpl.h>
47 #include <dcp/dcp.h>
48 #include <dcp/mono_picture_asset.h>
49 #include <dcp/mono_picture_frame.h>
50 #include <dcp/openjpeg_image.h>
51 #include <dcp/reel.h>
52 #include <dcp/reel_picture_asset.h>
53 #include <boost/test/unit_test.hpp>
54
55
56 using std::min;
57 using std::max;
58 using std::pair;
59 using std::string;
60 using std::dynamic_pointer_cast;
61 using std::make_shared;
62 using boost::optional;
63 #if BOOST_VERSION >= 106100
64 using namespace boost::placeholders;
65 #endif
66 using std::shared_ptr;
67
68
69 static
70 shared_ptr<Image>
71 grey_image (dcp::Size size, uint8_t pixel)
72 {
73         auto grey = make_shared<Image>(AV_PIX_FMT_RGB24, size, Image::Alignment::PADDED);
74         for (int y = 0; y < size.height; ++y) {
75                 uint8_t* p = grey->data()[0] + y * grey->stride()[0];
76                 for (int x = 0; x < size.width; ++x) {
77                         *p++ = pixel;
78                         *p++ = pixel;
79                         *p++ = pixel;
80                 }
81         }
82
83         return grey;
84 }
85
86
87 BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
88 {
89         dcp::Size size(640, 480);
90         uint8_t const grey_pixel = 128;
91         boost::filesystem::path const file = "build/test/ffmpeg_image_full_range_not_changed.png";
92
93         write_image (grey_image(size, grey_pixel), file);
94
95         FFmpegImageProxy proxy (file);
96         ImageProxy::Result result = proxy.image (Image::Alignment::COMPACT);
97         BOOST_REQUIRE (!result.error);
98
99         for (int y = 0; y < size.height; ++y) {
100                 uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
101                 for (int x = 0; x < size.width; ++x) {
102                         BOOST_REQUIRE (*p++ == grey_pixel);
103                 }
104         }
105 }
106
107
108 BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
109 {
110         dcp::Size size(1998, 1080);
111         uint8_t const grey_pixel = 128;
112         uint8_t const expanded_grey_pixel = static_cast<uint8_t>(lrintf((grey_pixel - 16) * 256.0 / 219));
113         boost::filesystem::path const file = "build/test/ffmpeg_image_video_range_expanded.png";
114
115         write_image(grey_image(size, grey_pixel), file);
116
117         auto content = content_factory(file);
118         auto film = new_test_film2 ("ffmpeg_image_video_range_expanded", content);
119         content[0]->video->set_range (VideoRange::VIDEO);
120         auto player = make_shared<Player>(film, film->playlist());
121
122         shared_ptr<PlayerVideo> player_video;
123         player->Video.connect([&player_video](shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime) {
124                 player_video = pv;
125         });
126         while (!player_video) {
127                 BOOST_REQUIRE (!player->pass());
128         }
129
130         auto image = player_video->image ([](AVPixelFormat f) { return f; }, VideoRange::FULL, false);
131
132         for (int y = 0; y < size.height; ++y) {
133                 uint8_t* p = image->data()[0] + y * image->stride()[0];
134                 for (int x = 0; x < size.width; ++x) {
135                         BOOST_REQUIRE_EQUAL (*p++, expanded_grey_pixel);
136                 }
137         }
138 }
139
140
141 BOOST_AUTO_TEST_CASE(yuv_expanded_into_full_rgb)
142 {
143         auto convert = [](int y_val, int u_val, int v_val, AVPixelFormat pix_fmt) {
144                 auto const size = dcp::Size(640, 480);
145                 auto yuv = make_shared<Image>(AV_PIX_FMT_YUVA444P12LE, size, Image::Alignment::PADDED);
146                 BOOST_REQUIRE_EQUAL(yuv->planes(), 4);
147                 for (int y = 0; y < size.height; ++y) {
148                         uint16_t* Y = reinterpret_cast<uint16_t*>(yuv->data()[0] + y * yuv->stride()[0]);
149                         uint16_t* U = reinterpret_cast<uint16_t*>(yuv->data()[1] + y * yuv->stride()[1]);
150                         uint16_t* V = reinterpret_cast<uint16_t*>(yuv->data()[2] + y * yuv->stride()[2]);
151                         uint16_t* A = reinterpret_cast<uint16_t*>(yuv->data()[3] + y * yuv->stride()[3]);
152                         for (int x = 0; x < size.width; ++x) {
153                                 *Y++ = y_val;
154                                 *U++ = u_val;
155                                 *V++ = v_val;
156                                 *A++ = 4096;
157                         }
158                 }
159
160                 return yuv->crop_scale_window(
161                         Crop(), size, size, dcp::YUVToRGB::REC709,
162                         VideoRange::VIDEO,
163                         pix_fmt,
164                         VideoRange::FULL,
165                         Image::Alignment::COMPACT,
166                         false
167                         );
168         };
169
170         auto white24 = convert(3760, 2048, 2048, AV_PIX_FMT_RGB24);
171         BOOST_CHECK_EQUAL(white24->data()[0][0], 255);
172         BOOST_CHECK_EQUAL(white24->data()[0][1], 255);
173         BOOST_CHECK_EQUAL(white24->data()[0][2], 255);
174
175         auto black24 = convert(256, 2048, 2048, AV_PIX_FMT_RGB24);
176         BOOST_CHECK_EQUAL(black24->data()[0][0], 0);
177         BOOST_CHECK_EQUAL(black24->data()[0][1], 0);
178         BOOST_CHECK_EQUAL(black24->data()[0][2], 0);
179
180         auto white48 = convert(3760, 2048, 2048, AV_PIX_FMT_RGB48LE);
181         BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[0], 65283);
182         BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[1], 65283);
183         BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[2], 65283);
184
185         auto black48 = convert(256, 2048, 2048, AV_PIX_FMT_RGB48LE);
186         BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[0], 0);
187         BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[1], 0);
188         BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[2], 0);
189 }
190
191
192 static
193 pair<int, int>
194 pixel_range (shared_ptr<const Image> image)
195 {
196         pair<int, int> range(INT_MAX, 0);
197         switch (image->pixel_format()) {
198         case AV_PIX_FMT_RGB24:
199         {
200                 dcp::Size const size = image->sample_size(0);
201                 for (int y = 0; y < size.height; ++y) {
202                         uint8_t* p = image->data()[0] + y * image->stride()[0];
203                         for (int x = 0; x < size.width * 3; ++x) {
204                                 range.first = min(range.first, static_cast<int>(*p));
205                                 range.second = max(range.second, static_cast<int>(*p));
206                                 ++p;
207                         }
208                 }
209                 break;
210         }
211         case AV_PIX_FMT_YUV444P:
212         {
213                 for (int c = 0; c < 3; ++c) {
214                         dcp::Size const size = image->sample_size(c);
215                         for (int y = 0; y < size.height; ++y) {
216                                 uint8_t* p = image->data()[c] + y * image->stride()[c];
217                                 for (int x = 0; x < size.width; ++x) {
218                                         range.first = min(range.first, static_cast<int>(*p));
219                                         range.second = max(range.second, static_cast<int>(*p));
220                                         ++p;
221                                 }
222                         }
223                 }
224                 break;
225         }
226         case AV_PIX_FMT_YUV422P10LE:
227         case AV_PIX_FMT_YUV444P10LE:
228         case AV_PIX_FMT_YUV444P12LE:
229         {
230                 for (int c = 0; c < 3; ++c) {
231                         dcp::Size const size = image->sample_size(c);
232                         for (int y = 0; y < size.height; ++y) {
233                                 uint16_t* p = reinterpret_cast<uint16_t*>(image->data()[c]) + y * image->stride()[c] / 2;
234                                 for (int x = 0; x < size.width; ++x) {
235                                         range.first = min(range.first, static_cast<int>(*p));
236                                         range.second = max(range.second, static_cast<int>(*p));
237                                         ++p;
238                                 }
239                         }
240                 }
241                 break;
242         }
243         default:
244                 BOOST_REQUIRE_MESSAGE (false, "No support for pixel format " << image->pixel_format());
245         }
246
247         return range;
248 }
249
250
251 /** @return pixel range of the first frame in @ref content in its raw form, i.e.
252  *  straight out of the decoder with no level processing, scaling etc.
253  */
254 static
255 pair<int, int>
256 pixel_range (shared_ptr<const Film> film, shared_ptr<const Content> content)
257 {
258         auto decoder = decoder_factory(film, content, false, false, shared_ptr<Decoder>());
259         optional<ContentVideo> content_video;
260         decoder->video->Data.connect ([&content_video](ContentVideo cv) {
261                 content_video = cv;
262         });
263         while (!content_video) {
264                 BOOST_REQUIRE (!decoder->pass());
265         }
266
267         return pixel_range (content_video->image->image(Image::Alignment::COMPACT).image);
268 }
269
270
271 static
272 pair<int, int>
273 pixel_range (boost::filesystem::path dcp_path)
274 {
275         dcp::DCP dcp (dcp_path);
276         dcp.read ();
277
278         auto picture = dynamic_pointer_cast<dcp::MonoPictureAsset>(dcp.cpls().front()->reels().front()->main_picture()->asset());
279         BOOST_REQUIRE (picture);
280         auto frame = picture->start_read()->get_frame(0)->xyz_image();
281
282         int const width = frame->size().width;
283         int const height = frame->size().height;
284
285         pair<int, int> range(INT_MAX, 0);
286         for (int c = 0; c < 3; ++c) {
287                 for (int y = 0; y < height; ++y) {
288                         int* p = frame->data(c) + y * width;
289                         for (int x = 0; x < width; ++x) {
290                                 range.first = min(range.first, *p);
291                                 range.second = max(range.second, *p);
292                                 ++p;
293                         }
294                 }
295         }
296
297         return range;
298 }
299
300
301 /* Functions to make a Film with different sorts of content.
302  *
303  * In these names V = video range (limited)
304  *                F = full range  (not limited)
305  *                o = overridden
306  */
307
308
309 static
310 shared_ptr<Film>
311 movie_V (string name)
312 {
313         auto film = new_test_film2 (name);
314         auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4")[0]);
315         BOOST_REQUIRE (content);
316         film->examine_and_add_content (content);
317         BOOST_REQUIRE (!wait_for_jobs());
318
319         auto range = pixel_range (film, content);
320         BOOST_CHECK_EQUAL (range.first, 15);
321         BOOST_CHECK_EQUAL (range.second, 243);
322
323         return film;
324 }
325
326
327 static
328 shared_ptr<Film>
329 movie_VoF (string name)
330 {
331         auto film = new_test_film2 (name);
332         auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4")[0]);
333         BOOST_REQUIRE (content);
334         film->examine_and_add_content (content);
335         BOOST_REQUIRE (!wait_for_jobs());
336         content->video->set_range (VideoRange::FULL);
337
338         auto range = pixel_range (film, content);
339         BOOST_CHECK_EQUAL (range.first, 15);
340         BOOST_CHECK_EQUAL (range.second, 243);
341
342         return film;
343 }
344
345
346 static
347 shared_ptr<Film>
348 movie_F (string name)
349 {
350         auto film = new_test_film2 (name);
351         auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov")[0]);
352         BOOST_REQUIRE (content);
353         film->examine_and_add_content (content);
354         BOOST_REQUIRE (!wait_for_jobs());
355
356         auto range = pixel_range (film, content);
357         BOOST_CHECK_EQUAL (range.first, 0);
358         BOOST_CHECK_EQUAL (range.second, 1023);
359
360         return film;
361 }
362
363
364 static
365 shared_ptr<Film>
366 movie_FoV (string name)
367 {
368         auto film = new_test_film2 (name);
369         auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov")[0]);
370         BOOST_REQUIRE (content);
371         film->examine_and_add_content (content);
372         BOOST_REQUIRE (!wait_for_jobs());
373         content->video->set_range (VideoRange::VIDEO);
374
375         auto range = pixel_range (film, content);
376         BOOST_CHECK_EQUAL (range.first, 0);
377         BOOST_CHECK_EQUAL (range.second, 1023);
378
379         return film;
380 }
381
382
383 static
384 shared_ptr<Film>
385 image_F (string name)
386 {
387         auto film = new_test_film2 (name);
388         auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png")[0]);
389         BOOST_REQUIRE (content);
390         film->examine_and_add_content (content);
391         BOOST_REQUIRE (!wait_for_jobs());
392
393         auto range = pixel_range (film, content);
394         BOOST_CHECK_EQUAL (range.first, 0);
395         BOOST_CHECK_EQUAL (range.second, 255);
396
397         return film;
398 }
399
400
401 static
402 shared_ptr<Film>
403 image_FoV (string name)
404 {
405         auto film = new_test_film2 (name);
406         auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png")[0]);
407         BOOST_REQUIRE (content);
408         film->examine_and_add_content (content);
409         BOOST_REQUIRE (!wait_for_jobs());
410         content->video->set_range (VideoRange::VIDEO);
411
412         auto range = pixel_range (film, content);
413         /* We are taking some full-range content and saying it should be read as video range, after which its
414          * pixels will still be full range.
415          */
416         BOOST_CHECK_EQUAL (range.first, 0);
417         BOOST_CHECK_EQUAL (range.second, 255);
418
419         return film;
420 }
421
422
423 static
424 shared_ptr<Film>
425 dcp_F (string name)
426 {
427         boost::filesystem::path const dcp = "test/data/RgbGreyTestcar_TST-1_F_MOS_2K_20201115_SMPTE_OV";
428         auto film = new_test_film2 (name);
429         auto content = make_shared<DCPContent>(dcp);
430         film->examine_and_add_content (content);
431         BOOST_REQUIRE (!wait_for_jobs());
432
433         auto range = pixel_range (dcp);
434         BOOST_CHECK_EQUAL (range.first, 0);
435         BOOST_CHECK_EQUAL (range.second, 4081);
436
437         return film;
438 }
439
440
441
442 /* Functions to get the pixel range in different sorts of output */
443
444
445 /** Get the pixel range in a DCP made from film */
446 static
447 pair<int, int>
448 dcp_range (shared_ptr<Film> film)
449 {
450         make_and_verify_dcp (film);
451         return pixel_range (film->dir(film->dcp_name()));
452 }
453
454
455 /** Get the pixel range in a video-range movie exported from film */
456 static
457 pair<int, int>
458 V_movie_range (shared_ptr<Film> film)
459 {
460         auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
461         job->set_encoder (
462                 make_shared<FFmpegEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES_HQ, true, false, false, 23)
463                 );
464         JobManager::instance()->add (job);
465         BOOST_REQUIRE (!wait_for_jobs());
466
467         /* This is a bit of a hack; add the exported file into the project so we can decode it */
468         auto content = make_shared<FFmpegContent>(film->file("export.mov"));
469         film->examine_and_add_content (content);
470         BOOST_REQUIRE (!wait_for_jobs());
471
472         return pixel_range (film, content);
473 }
474
475
476 /* The tests */
477
478
479 BOOST_AUTO_TEST_CASE (movie_V_to_dcp)
480 {
481         auto range = dcp_range (movie_V("movie_V_to_dcp"));
482         /* Video range has been correctly expanded to full for the DCP */
483         check_int_close (range, {0, 4083}, 2);
484 }
485
486
487 BOOST_AUTO_TEST_CASE (movie_VoF_to_dcp)
488 {
489         auto range = dcp_range (movie_VoF("movie_VoF_to_dcp"));
490         /* We said that video range data was really full range, so here we are in the DCP
491          * with video-range data.
492          */
493         check_int_close (range, {350, 3832}, 2);
494 }
495
496
497 BOOST_AUTO_TEST_CASE (movie_F_to_dcp)
498 {
499         auto range = dcp_range (movie_F("movie_F_to_dcp"));
500         /* The nearly-full-range of the input has been preserved */
501         check_int_close (range, {0, 4083}, 2);
502 }
503
504
505 BOOST_AUTO_TEST_CASE (video_FoV_to_dcp)
506 {
507         auto range = dcp_range (movie_FoV("video_FoV_to_dcp"));
508         /* The nearly-full-range of the input has become even more full, and clipped */
509         check_int_close (range, {0, 4095}, 2);
510 }
511
512
513 BOOST_AUTO_TEST_CASE (image_F_to_dcp)
514 {
515         auto range = dcp_range (image_F("image_F_to_dcp"));
516         check_int_close (range, {0, 4083}, 3);
517 }
518
519
520 BOOST_AUTO_TEST_CASE (image_FoV_to_dcp)
521 {
522         auto range = dcp_range (image_FoV("image_FoV_to_dcp"));
523         /* The nearly-full-range of the input has become even more full, and clipped.
524          * XXX: I'm not sure why this doesn't quite hit 4095.
525          */
526         check_int_close (range, {0, 4095}, 16);
527 }
528
529
530 BOOST_AUTO_TEST_CASE (movie_V_to_V_movie)
531 {
532         auto range = V_movie_range (movie_V("movie_V_to_V_movie"));
533         BOOST_CHECK_EQUAL (range.first, 60);
534         BOOST_CHECK_EQUAL (range.second, 998);
535 }
536
537
538 BOOST_AUTO_TEST_CASE (movie_VoF_to_V_movie)
539 {
540         auto range = V_movie_range (movie_VoF("movie_VoF_to_V_movie"));
541         BOOST_CHECK_EQUAL (range.first, 116);
542         BOOST_CHECK_EQUAL (range.second, 939);
543 }
544
545
546 BOOST_AUTO_TEST_CASE (movie_F_to_V_movie)
547 {
548         auto range = V_movie_range (movie_F("movie_F_to_V_movie"));
549         BOOST_CHECK_EQUAL (range.first, 4);
550         BOOST_CHECK_EQUAL (range.second, 1019);
551 }
552
553
554 BOOST_AUTO_TEST_CASE (movie_FoV_to_V_movie)
555 {
556         auto range = V_movie_range (movie_FoV("movie_FoV_to_V_movie"));
557         BOOST_CHECK_EQUAL (range.first, 4);
558         BOOST_CHECK_EQUAL (range.second, 1019);
559 }
560
561
562 BOOST_AUTO_TEST_CASE (image_F_to_V_movie)
563 {
564         auto range = V_movie_range (image_F("image_F_to_V_movie"));
565         BOOST_CHECK_EQUAL (range.first, 64);
566         BOOST_CHECK_EQUAL (range.second, 960);
567 }
568
569
570 BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie)
571 {
572         auto range = V_movie_range (image_FoV("image_FoV_to_V_movie"));
573         BOOST_CHECK_EQUAL (range.first, 64);
574         BOOST_CHECK_EQUAL (range.second, 960);
575 }
576
577
578 BOOST_AUTO_TEST_CASE (dcp_F_to_V_movie)
579 {
580         auto range = V_movie_range (dcp_F("dcp_F_to_V_movie"));
581         BOOST_CHECK_EQUAL (range.first, 64);
582         BOOST_CHECK_EQUAL (range.second, 944);
583 }
584