2 Copyright (C) 2014-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
22 /** @file test/player_test.cc
23 * @brief Test Player class.
24 * @ingroup selfcontained
28 #include "lib/audio_buffers.h"
29 #include "lib/audio_content.h"
30 #include "lib/butler.h"
31 #include "lib/compose.hpp"
32 #include "lib/config.h"
33 #include "lib/constants.h"
34 #include "lib/content_factory.h"
35 #include "lib/cross.h"
36 #include "lib/dcp_content.h"
37 #include "lib/dcp_content_type.h"
38 #include "lib/dcpomatic_log.h"
39 #include "lib/ffmpeg_content.h"
41 #include "lib/image_content.h"
42 #include "lib/image_png.h"
43 #include "lib/player.h"
44 #include "lib/ratio.h"
45 #include "lib/string_text_file_content.h"
46 #include "lib/text_content.h"
47 #include "lib/video_content.h"
49 #include <boost/test/unit_test.hpp>
50 #include <boost/algorithm/string.hpp>
56 using std::shared_ptr;
57 using std::make_shared;
60 using boost::optional;
61 #if BOOST_VERSION >= 106100
62 using namespace boost::placeholders;
64 using namespace dcpomatic;
67 static shared_ptr<AudioBuffers> accumulated;
71 accumulate (shared_ptr<AudioBuffers> audio, DCPTime)
73 BOOST_REQUIRE (accumulated);
74 accumulated->append (audio);
78 /** Check that the Player correctly generates silence when used with a silent FFmpegContent */
79 BOOST_AUTO_TEST_CASE (player_silence_padding_test)
81 auto c = std::make_shared<FFmpegContent>("test/data/test.mp4");
82 auto film = new_test_film("player_silence_padding_test", { c });
83 film->set_audio_channels (6);
85 accumulated = std::make_shared<AudioBuffers>(film->audio_channels(), 0);
87 Player player(film, Image::Alignment::COMPACT);
88 player.Audio.connect(bind(&accumulate, _1, _2));
89 while (!player.pass()) {}
90 BOOST_REQUIRE (accumulated->frames() >= 48000);
91 BOOST_CHECK_EQUAL (accumulated->channels(), film->audio_channels ());
93 for (int i = 0; i < 48000; ++i) {
94 for (int c = 0; c < accumulated->channels(); ++c) {
95 BOOST_CHECK_EQUAL (accumulated->data()[c][i], 0);
101 /* Test insertion of black frames between separate bits of video content */
102 BOOST_AUTO_TEST_CASE (player_black_fill_test)
104 auto contentA = std::make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
105 auto contentB = std::make_shared<ImageContent>("test/data/simple_testcard_640x480.png");
106 auto film = new_test_film("black_fill_test", { contentA, contentB });
107 film->set_dcp_content_type(DCPContentType::from_isdcf_name("FTR"));
108 film->set_sequence (false);
110 contentA->video->set_length (3);
111 contentA->set_position (film, DCPTime::from_frames(2, film->video_frame_rate()));
112 contentA->video->set_custom_ratio (1.85);
113 contentB->video->set_length (1);
114 contentB->set_position (film, DCPTime::from_frames(7, film->video_frame_rate()));
115 contentB->video->set_custom_ratio (1.85);
117 make_and_verify_dcp (
120 dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE,
121 dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
124 boost::filesystem::path ref;
127 ref /= "black_fill_test";
129 boost::filesystem::path check;
132 check /= "black_fill_test";
133 check /= film->dcp_name();
135 /* This test is concerned with the image, so we'll ignore any
136 * differences in sound between the DCP and the reference to avoid test
137 * failures for unrelated reasons.
139 check_dcp(ref.string(), check.string(), true);
143 /** Check behaviour with an awkward playlist whose data does not end on a video frame start */
144 BOOST_AUTO_TEST_CASE (player_subframe_test)
146 auto A = content_factory("test/data/flat_red.png")[0];
147 auto B = content_factory("test/data/awkward_length.wav")[0];
148 auto film = new_test_film("reels_test7", { A, B });
149 film->set_video_frame_rate (24);
150 A->video->set_length (3 * 24);
152 BOOST_CHECK (A->full_length(film) == DCPTime::from_frames(3 * 24, 24));
153 BOOST_CHECK (B->full_length(film) == DCPTime(289920));
154 /* Length should be rounded up from B's length to the next video frame */
155 BOOST_CHECK (film->length() == DCPTime::from_frames(3 * 24 + 1, 24));
157 Player player(film, Image::Alignment::COMPACT);
158 player.setup_pieces();
159 BOOST_REQUIRE_EQUAL(player._black._periods.size(), 1U);
160 BOOST_CHECK(player._black._periods.front() == DCPTimePeriod(DCPTime::from_frames(3 * 24, 24), DCPTime::from_frames(3 * 24 + 1, 24)));
161 BOOST_REQUIRE_EQUAL(player._silent._periods.size(), 1U);
162 BOOST_CHECK(player._silent._periods.front() == DCPTimePeriod(DCPTime(289920), DCPTime::from_frames(3 * 24 + 1, 24)));
166 static Frame video_frames;
167 static Frame audio_frames;
171 video (shared_ptr<PlayerVideo>, DCPTime)
177 audio (shared_ptr<AudioBuffers> audio, DCPTime)
179 audio_frames += audio->frames();
183 /** Check with a video-only file that the video and audio emissions happen more-or-less together */
184 BOOST_AUTO_TEST_CASE (player_interleave_test)
186 auto c = std::make_shared<FFmpegContent>("test/data/test.mp4");
187 auto s = std::make_shared<StringTextFileContent>("test/data/subrip.srt");
188 auto film = new_test_film("ffmpeg_transcoder_basic_test_subs", { c, s });
189 film->set_audio_channels (6);
191 Player player(film, Image::Alignment::COMPACT);
192 player.Video.connect(bind(&video, _1, _2));
193 player.Audio.connect(bind(&audio, _1, _2));
194 video_frames = audio_frames = 0;
195 while (!player.pass()) {
196 BOOST_CHECK (abs(video_frames - (audio_frames / 2000)) <= 8);
201 /** Test some seeks towards the start of a DCP with awkward subtitles; see mantis #1085
202 * and a number of others. I thought this was a player seek bug but in fact it was
203 * caused by the subtitle starting just after the start of the video frame and hence
206 BOOST_AUTO_TEST_CASE (player_seek_test)
208 auto film = std::make_shared<Film>(optional<boost::filesystem::path>());
209 auto dcp = std::make_shared<DCPContent>(TestPaths::private_data() / "awkward_subs");
210 film->examine_and_add_content (dcp, true);
211 BOOST_REQUIRE (!wait_for_jobs ());
212 dcp->only_text()->set_use (true);
214 Player player(film, Image::Alignment::COMPACT);
216 player.set_always_burn_open_subtitles();
217 player.set_play_referenced();
219 auto butler = std::make_shared<Butler>(
220 film, player, AudioMapping(), 2, bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, Image::Alignment::PADDED, true, false, Butler::Audio::DISABLED
223 for (int i = 0; i < 10; ++i) {
224 auto t = DCPTime::from_frames (i, 24);
225 butler->seek (t, true);
226 auto video = butler->get_video(Butler::Behaviour::BLOCKING, 0);
227 BOOST_CHECK_EQUAL(video.second.get(), t.get());
228 write_image(video.first->image(bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true), String::compose("build/test/player_seek_test_%1.png", i));
229 /* This 14.08 is empirically chosen (hopefully) to accept changes in rendering between the reference and a test machine
230 (17.10 and 16.04 seem to anti-alias a little differently) but to reject gross errors e.g. missing fonts or missing
233 check_image(TestPaths::private_data() / String::compose("player_seek_test_%1.png", i), String::compose("build/test/player_seek_test_%1.png", i), 14.08);
238 /** Test some more seeks towards the start of a DCP with awkward subtitles */
239 BOOST_AUTO_TEST_CASE (player_seek_test2)
241 auto film = std::make_shared<Film>(optional<boost::filesystem::path>());
242 auto dcp = std::make_shared<DCPContent>(TestPaths::private_data() / "awkward_subs2");
243 film->examine_and_add_content (dcp, true);
244 BOOST_REQUIRE (!wait_for_jobs ());
245 dcp->only_text()->set_use (true);
247 Player player(film, Image::Alignment::COMPACT);
249 player.set_always_burn_open_subtitles();
250 player.set_play_referenced();
252 auto butler = std::make_shared<Butler>
253 (film, player, AudioMapping(), 2, bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, Image::Alignment::PADDED, true, false, Butler::Audio::DISABLED
256 butler->seek(DCPTime::from_seconds(5), true);
258 for (int i = 0; i < 10; ++i) {
259 auto t = DCPTime::from_seconds(5) + DCPTime::from_frames (i, 24);
260 butler->seek (t, true);
261 auto video = butler->get_video(Butler::Behaviour::BLOCKING, 0);
262 BOOST_CHECK_EQUAL(video.second.get(), t.get());
264 video.first->image(bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true), String::compose("build/test/player_seek_test2_%1.png", i)
266 check_image(TestPaths::private_data() / String::compose("player_seek_test2_%1.png", i), String::compose("build/test/player_seek_test2_%1.png", i), 14.08);
271 /** Test a bug when trimmed content follows other content */
272 BOOST_AUTO_TEST_CASE (player_trim_test)
274 auto film = new_test_film("player_trim_test");
275 auto A = content_factory("test/data/flat_red.png")[0];
276 film->examine_and_add_content (A);
277 BOOST_REQUIRE (!wait_for_jobs ());
278 A->video->set_length (10 * 24);
279 auto B = content_factory("test/data/flat_red.png")[0];
280 film->examine_and_add_content (B);
281 BOOST_REQUIRE (!wait_for_jobs ());
282 B->video->set_length (10 * 24);
283 B->set_position (film, DCPTime::from_seconds(10));
284 B->set_trim_start(film, ContentTime::from_seconds(2));
286 make_and_verify_dcp (film);
293 optional<DCPTextTrack> track;
294 DCPTimePeriod period;
299 store (list<Sub>* out, PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
310 /** Test ignoring both video and audio */
311 BOOST_AUTO_TEST_CASE (player_ignore_video_and_audio_test)
313 auto film = new_test_film("player_ignore_video_and_audio_test");
314 auto ff = content_factory(TestPaths::private_data() / "boon_telly.mkv")[0];
315 film->examine_and_add_content (ff);
316 auto text = content_factory("test/data/subrip.srt")[0];
317 film->examine_and_add_content (text);
318 BOOST_REQUIRE (!wait_for_jobs());
319 text->only_text()->set_type (TextType::CLOSED_CAPTION);
320 text->only_text()->set_use (true);
322 Player player(film, Image::Alignment::COMPACT);
323 player.set_ignore_video();
324 player.set_ignore_audio();
327 player.Text.connect(bind (&store, &out, _1, _2, _3, _4));
328 while (!player.pass()) {}
330 BOOST_CHECK_EQUAL (out.size(), 6U);
334 /** Trigger a crash due to the assertion failure in Player::emit_audio */
335 BOOST_AUTO_TEST_CASE (player_trim_crash)
337 auto film = new_test_film("player_trim_crash");
338 auto boon = content_factory(TestPaths::private_data() / "boon_telly.mkv")[0];
339 film->examine_and_add_content (boon);
340 BOOST_REQUIRE (!wait_for_jobs());
342 Player player(film, Image::Alignment::COMPACT);
344 auto butler = std::make_shared<Butler>(
345 film, player, AudioMapping(), 6, bind(&PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, Image::Alignment::COMPACT, true, false, Butler::Audio::ENABLED
348 /* Wait for the butler to fill */
349 dcpomatic_sleep_seconds (5);
351 boon->set_trim_start(film, ContentTime::from_seconds(5));
353 butler->seek (DCPTime(), true);
355 /* Wait for the butler to refill */
356 dcpomatic_sleep_seconds (5);
362 /** Test a crash when the gap between the last audio and the start of a silent period is more than 1 sample */
363 BOOST_AUTO_TEST_CASE (player_silence_crash)
367 auto sine = content_factory("test/data/impulse_train.wav")[0];
368 auto film = new_test_film("player_silence_crash", { sine }, &cl);
369 sine->set_video_frame_rate(film, 23.976);
370 make_and_verify_dcp (film, {dcp::VerificationNote::Code::MISSING_CPL_METADATA});
376 /** Test a crash when processing a 3D DCP */
377 BOOST_AUTO_TEST_CASE (player_3d_test_1)
379 auto film = new_test_film("player_3d_test_1a");
380 auto left = content_factory("test/data/flat_red.png")[0];
381 film->examine_and_add_content (left);
382 auto right = content_factory("test/data/flat_blue.png")[0];
383 film->examine_and_add_content (right);
384 BOOST_REQUIRE (!wait_for_jobs());
386 left->video->set_frame_type (VideoFrameType::THREE_D_LEFT);
387 left->set_position (film, DCPTime());
388 right->video->set_frame_type (VideoFrameType::THREE_D_RIGHT);
389 right->set_position (film, DCPTime());
390 film->set_three_d (true);
392 make_and_verify_dcp (film);
394 auto dcp = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
395 auto film2 = new_test_film("player_3d_test_1b", {dcp});
397 film2->set_three_d (true);
398 make_and_verify_dcp (film2);
402 /** Test a crash when processing a 3D DCP as content in a 2D project */
403 BOOST_AUTO_TEST_CASE (player_3d_test_2)
405 auto left = content_factory("test/data/flat_red.png")[0];
406 auto right = content_factory("test/data/flat_blue.png")[0];
407 auto film = new_test_film("player_3d_test_2a", {left, right});
409 left->video->set_frame_type (VideoFrameType::THREE_D_LEFT);
410 left->set_position (film, DCPTime());
411 right->video->set_frame_type (VideoFrameType::THREE_D_RIGHT);
412 right->set_position (film, DCPTime());
413 film->set_three_d (true);
415 make_and_verify_dcp (film);
417 auto dcp = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
418 auto film2 = new_test_film("player_3d_test_2b", {dcp});
420 make_and_verify_dcp (film2);
424 /** Test a crash when there is video-only content at the end of the DCP and a frame-rate conversion is happening;
427 BOOST_AUTO_TEST_CASE (player_silence_at_end_crash)
429 /* 25fps DCP with some audio */
430 auto content1 = content_factory("test/data/flat_red.png")[0];
431 auto film1 = new_test_film("player_silence_at_end_crash_1", {content1});
432 content1->video->set_length (25);
433 film1->set_video_frame_rate (25);
434 make_and_verify_dcp (film1);
436 /* Make another project importing this DCP */
437 auto content2 = std::make_shared<DCPContent>(film1->dir(film1->dcp_name()));
438 auto film2 = new_test_film("player_silence_at_end_crash_2", {content2});
440 /* and importing just the video MXF on its own at the end */
441 optional<boost::filesystem::path> video;
442 for (auto i: boost::filesystem::directory_iterator(film1->dir(film1->dcp_name()))) {
443 if (boost::starts_with(i.path().filename().string(), "j2c_")) {
448 BOOST_REQUIRE (video);
449 auto content3 = content_factory(*video)[0];
450 film2->examine_and_add_content (content3);
451 BOOST_REQUIRE (!wait_for_jobs());
452 content3->set_position (film2, DCPTime::from_seconds(1.5));
453 film2->set_video_frame_rate (24);
454 make_and_verify_dcp (film2);
459 BOOST_AUTO_TEST_CASE (encrypted_dcp_with_no_kdm_gives_no_butler_error)
461 auto content = content_factory("test/data/flat_red.png")[0];
462 auto film = new_test_film("encrypted_dcp_with_no_kdm_gives_no_butler_error", { content });
463 int constexpr length = 24 * 25;
464 content->video->set_length(length);
465 film->set_encrypted (true);
466 make_and_verify_dcp (
469 dcp::VerificationNote::Code::MISSING_CPL_METADATA,
472 auto content2 = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
473 auto film2 = new_test_film("encrypted_dcp_with_no_kdm_gives_no_butler_error2", { content2 });
475 Player player(film, Image::Alignment::COMPACT);
476 Butler butler(film2, player, AudioMapping(), 2, bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, Image::Alignment::PADDED, true, false, Butler::Audio::ENABLED);
478 float buffer[2000 * 6];
479 for (int i = 0; i < length; ++i) {
480 butler.get_video(Butler::Behaviour::BLOCKING, 0);
481 butler.get_audio(Butler::Behaviour::BLOCKING, buffer, 2000);
484 BOOST_CHECK_NO_THROW(butler.rethrow());
488 BOOST_AUTO_TEST_CASE (interleaved_subtitle_are_emitted_correctly)
490 boost::filesystem::path paths[2] = {
491 "build/test/interleaved_subtitle_are_emitted_correctly1.srt",
492 "build/test/interleaved_subtitle_are_emitted_correctly2.srt"
495 dcp::File subs_file[2] = { dcp::File(paths[0], "w"), dcp::File(paths[1], "w") };
497 fprintf(subs_file[0].get(), "1\n00:00:01,000 -> 00:00:02,000\nSub 1/1\n\n");
498 fprintf(subs_file[0].get(), "2\n00:00:05,000 -> 00:00:06,000\nSub 1/2\n\n");
500 fprintf(subs_file[1].get(), "1\n00:00:00,500 -> 00:00:01,500\nSub 2/1\n\n");
501 fprintf(subs_file[1].get(), "2\n00:00:02,000 -> 00:00:03,000\nSub 2/2\n\n");
503 subs_file[0].close();
504 subs_file[1].close();
506 auto subs1 = content_factory(paths[0])[0];
507 auto subs2 = content_factory(paths[1])[0];
508 auto film = new_test_film("interleaved_subtitle_are_emitted_correctly", { subs1, subs2 });
509 film->set_sequence(false);
510 subs1->set_position(film, DCPTime());
511 subs2->set_position(film, DCPTime());
513 Player player(film, Image::Alignment::COMPACT);
515 player.Text.connect([&last](PlayerText text, TextType, optional<DCPTextTrack>, dcpomatic::DCPTimePeriod) {
516 for (auto sub: text.string) {
517 BOOST_CHECK(sub.in() >= last);
521 while (!player.pass()) {}
525 BOOST_AUTO_TEST_CASE(multiple_sound_files_bug)
529 Config::instance()->set_log_types(Config::instance()->log_types() | LogEntry::TYPE_DEBUG_PLAYER);
531 auto A = content_factory(TestPaths::private_data() / "kook" / "1.wav").front();
532 auto B = content_factory(TestPaths::private_data() / "kook" / "2.wav").front();
533 auto C = content_factory(TestPaths::private_data() / "kook" / "3.wav").front();
535 auto film = new_test_film("multiple_sound_files_bug", { A, B, C }, &cl);
536 film->set_audio_channels(16);
537 C->set_position(film, DCPTime(3840000));
539 make_and_verify_dcp(film, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
541 check_mxf_audio_file(TestPaths::private_data() / "kook" / "reference.mxf", dcp_file(film, "pcm_"));
547 BOOST_AUTO_TEST_CASE(trimmed_sound_mix_bug_13)
549 auto A = content_factory("test/data/sine_16_48_440_10.wav").front();
550 auto B = content_factory("test/data/sine_16_44.1_440_10.wav").front();
551 auto film = new_test_film("trimmed_sound_mix_bug_13", { A, B });
552 film->set_audio_channels(16);
554 A->set_position(film, DCPTime());
555 A->audio->set_gain(-12);
556 B->set_position(film, DCPTime());
557 B->audio->set_gain(-12);
558 B->set_trim_start(film, ContentTime(13));
560 make_and_verify_dcp(film, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
561 check_mxf_audio_file("test/data/trimmed_sound_mix_bug_13.mxf", dcp_file(film, "pcm_"));
565 BOOST_AUTO_TEST_CASE(trimmed_sound_mix_bug_13_frame_rate_change)
567 auto A = content_factory("test/data/sine_16_48_440_10.wav").front();
568 auto B = content_factory("test/data/sine_16_44.1_440_10.wav").front();
569 auto film = new_test_film("trimmed_sound_mix_bug_13_frame_rate_change", { A, B });
571 A->set_position(film, DCPTime());
572 A->audio->set_gain(-12);
573 B->set_position(film, DCPTime());
574 B->audio->set_gain(-12);
575 B->set_trim_start(film, ContentTime(13));
577 A->set_video_frame_rate(film, 24);
578 B->set_video_frame_rate(film, 24);
579 film->set_video_frame_rate(25);
580 film->set_audio_channels(16);
582 make_and_verify_dcp(film, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
583 check_mxf_audio_file("test/data/trimmed_sound_mix_bug_13_frame_rate_change.mxf", dcp_file(film, "pcm_"));
587 BOOST_AUTO_TEST_CASE(two_d_in_three_d_duplicates)
589 auto A = content_factory("test/data/flat_red.png").front();
590 auto B = content_factory("test/data/flat_green.png").front();
591 auto film = new_test_film("two_d_in_three_d_duplicates", { A, B });
593 film->set_three_d(true);
594 B->video->set_frame_type(VideoFrameType::THREE_D_LEFT_RIGHT);
595 B->set_position(film, DCPTime::from_seconds(10));
596 B->video->set_custom_size(dcp::Size(1998, 1080));
598 auto player = std::make_shared<Player>(film, film->playlist());
600 std::vector<uint8_t> red_line(1998 * 3);
601 for (int i = 0; i < 1998; ++i) {
602 red_line[i * 3] = 255;
605 std::vector<uint8_t> green_line(1998 * 3);
606 for (int i = 0; i < 1998; ++i) {
607 green_line[i * 3 + 1] = 255;
610 Eyes last_eyes = Eyes::RIGHT;
611 optional<DCPTime> last_time;
612 player->Video.connect([&last_eyes, &last_time, &red_line, &green_line](shared_ptr<PlayerVideo> video, dcpomatic::DCPTime time) {
613 BOOST_CHECK(last_eyes != video->eyes());
614 last_eyes = video->eyes();
615 if (video->eyes() == Eyes::LEFT) {
616 BOOST_CHECK(!last_time || time == *last_time + DCPTime::from_frames(1, 24));
618 BOOST_CHECK(time == *last_time);
622 auto image = video->image([](AVPixelFormat) { return AV_PIX_FMT_RGB24; }, VideoRange::FULL, false);
623 auto const size = image->size();
624 for (int y = 0; y < size.height; ++y) {
625 uint8_t* line = image->data()[0] + y * image->stride()[0];
626 if (time < DCPTime::from_seconds(10)) {
627 BOOST_REQUIRE_EQUAL(memcmp(line, red_line.data(), 1998 * 3), 0);
629 BOOST_REQUIRE_EQUAL(memcmp(line, green_line.data(), 1998 * 3), 0);
634 BOOST_CHECK(film->length() == DCPTime::from_seconds(20));
635 while (!player->pass()) {}
639 BOOST_AUTO_TEST_CASE(three_d_in_two_d_chooses_left)
641 auto left = content_factory("test/data/flat_red.png").front();
642 auto right = content_factory("test/data/flat_green.png").front();
643 auto mono = content_factory("test/data/flat_blue.png").front();
645 auto film = new_test_film("three_d_in_two_d_chooses_left", { left, right, mono });
647 left->video->set_frame_type(VideoFrameType::THREE_D_LEFT);
648 left->set_position(film, dcpomatic::DCPTime());
649 right->video->set_frame_type(VideoFrameType::THREE_D_RIGHT);
650 right->set_position(film, dcpomatic::DCPTime());
652 mono->set_position(film, dcpomatic::DCPTime::from_seconds(10));
654 auto player = std::make_shared<Player>(film, film->playlist());
656 std::vector<uint8_t> red_line(1998 * 3);
657 for (int i = 0; i < 1998; ++i) {
658 red_line[i * 3] = 255;
661 std::vector<uint8_t> blue_line(1998 * 3);
662 for (int i = 0; i < 1998; ++i) {
663 blue_line[i * 3 + 2] = 255;
666 optional<DCPTime> last_time;
667 player->Video.connect([&last_time, &red_line, &blue_line](shared_ptr<PlayerVideo> video, dcpomatic::DCPTime time) {
668 BOOST_CHECK(video->eyes() == Eyes::BOTH);
669 BOOST_CHECK(!last_time || time == *last_time + DCPTime::from_frames(1, 24));
672 auto image = video->image([](AVPixelFormat) { return AV_PIX_FMT_RGB24; }, VideoRange::FULL, false);
673 auto const size = image->size();
674 for (int y = 0; y < size.height; ++y) {
675 uint8_t* line = image->data()[0] + y * image->stride()[0];
676 if (time < DCPTime::from_seconds(10)) {
677 BOOST_REQUIRE_EQUAL(memcmp(line, red_line.data(), 1998 * 3), 0);
679 BOOST_REQUIRE_EQUAL(memcmp(line, blue_line.data(), 1998 * 3), 0);
684 BOOST_CHECK(film->length() == DCPTime::from_seconds(20));
685 while (!player->pass()) {}
689 BOOST_AUTO_TEST_CASE(check_seek_with_no_video)
691 auto content = content_factory(TestPaths::private_data() / "Fight.Club.1999.720p.BRRip.x264-x0r.srt")[0];
692 auto film = new_test_film("check_seek_with_no_video", { content });
693 auto player = std::make_shared<Player>(film, film->playlist());
695 boost::signals2::signal<void (std::shared_ptr<PlayerVideo>, dcpomatic::DCPTime)> Video;
697 optional<dcpomatic::DCPTime> earliest;
699 player->Video.connect(
700 [&earliest](shared_ptr<PlayerVideo>, dcpomatic::DCPTime time) {
701 if (!earliest || time < *earliest) {
706 player->seek(dcpomatic::DCPTime::from_seconds(60 * 60), false);
708 for (int i = 0; i < 10; ++i) {
712 BOOST_REQUIRE(earliest);
713 BOOST_CHECK(*earliest >= dcpomatic::DCPTime(60 * 60));
717 BOOST_AUTO_TEST_CASE(unmapped_audio_does_not_raise_buffer_error)
719 auto content = content_factory(TestPaths::private_data() / "arrietty_JP-EN.mkv")[0];
720 auto film = new_test_film("unmapped_audio_does_not_raise_buffer_error", { content });
722 content->audio->set_mapping(AudioMapping(6 * 2, MAX_DCP_AUDIO_CHANNELS));
724 Player player(film, Image::Alignment::COMPACT);
725 Butler butler(film, player, AudioMapping(), 2, bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, Image::Alignment::PADDED, true, false, Butler::Audio::ENABLED);
727 /* Wait for the butler thread to run for a while; in the case under test it will throw an exception because
728 * the video buffers are filled but no audio comes.
730 dcpomatic_sleep_seconds(10);