cac5dffe958e5c986ba48f369bbb046f9830e1c2
[dcpomatic.git] / test / player_test.cc
1 /*
2     Copyright (C) 2014-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/player_test.cc
23  *  @brief Test Player class.
24  *  @ingroup selfcontained
25  */
26
27
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"
40 #include "lib/film.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"
48 #include "test.h"
49 #include <boost/test/unit_test.hpp>
50 #include <boost/algorithm/string.hpp>
51 #include <iostream>
52
53
54 using std::cout;
55 using std::list;
56 using std::shared_ptr;
57 using std::make_shared;
58 using std::vector;
59 using boost::bind;
60 using boost::optional;
61 #if BOOST_VERSION >= 106100
62 using namespace boost::placeholders;
63 #endif
64 using namespace dcpomatic;
65
66
67 static shared_ptr<AudioBuffers> accumulated;
68
69
70 static void
71 accumulate (shared_ptr<AudioBuffers> audio, DCPTime)
72 {
73         BOOST_REQUIRE (accumulated);
74         accumulated->append (audio);
75 }
76
77
78 /** Check that the Player correctly generates silence when used with a silent FFmpegContent */
79 BOOST_AUTO_TEST_CASE (player_silence_padding_test)
80 {
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);
84
85         accumulated = std::make_shared<AudioBuffers>(film->audio_channels(), 0);
86
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 ());
92
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);
96                 }
97         }
98 }
99
100
101 /* Test insertion of black frames between separate bits of video content */
102 BOOST_AUTO_TEST_CASE (player_black_fill_test)
103 {
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);
109
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);
116
117         make_and_verify_dcp (
118                 film,
119                 {
120                         dcp::VerificationNote::Code::MISSING_FFMC_IN_FEATURE,
121                         dcp::VerificationNote::Code::MISSING_FFEC_IN_FEATURE
122                 });
123
124         boost::filesystem::path ref;
125         ref = "test";
126         ref /= "data";
127         ref /= "black_fill_test";
128
129         boost::filesystem::path check;
130         check = "build";
131         check /= "test";
132         check /= "black_fill_test";
133         check /= film->dcp_name();
134
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.
138          */
139         check_dcp(ref.string(), check.string(), true);
140 }
141
142
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)
145 {
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);
151
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));
156
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)));
163 }
164
165
166 static Frame video_frames;
167 static Frame audio_frames;
168
169
170 static void
171 video (shared_ptr<PlayerVideo>, DCPTime)
172 {
173         ++video_frames;
174 }
175
176 static void
177 audio (shared_ptr<AudioBuffers> audio, DCPTime)
178 {
179         audio_frames += audio->frames();
180 }
181
182
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)
185 {
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);
190
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);
197         }
198 }
199
200
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
204  *  being faded out.
205  */
206 BOOST_AUTO_TEST_CASE (player_seek_test)
207 {
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);
213
214         Player player(film, Image::Alignment::COMPACT);
215         player.set_fast();
216         player.set_always_burn_open_subtitles();
217         player.set_play_referenced();
218
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
221                 );
222
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
231                    text altogether.
232                 */
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);
234         }
235 }
236
237
238 /** Test some more seeks towards the start of a DCP with awkward subtitles */
239 BOOST_AUTO_TEST_CASE (player_seek_test2)
240 {
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);
246
247         Player player(film, Image::Alignment::COMPACT);
248         player.set_fast();
249         player.set_always_burn_open_subtitles();
250         player.set_play_referenced();
251
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
254                  );
255
256         butler->seek(DCPTime::from_seconds(5), true);
257
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());
263                 write_image(
264                         video.first->image(bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true), String::compose("build/test/player_seek_test2_%1.png", i)
265                         );
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);
267         }
268 }
269
270
271 /** Test a bug when trimmed content follows other content */
272 BOOST_AUTO_TEST_CASE (player_trim_test)
273 {
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));
285
286        make_and_verify_dcp (film);
287 }
288
289
290 struct Sub {
291         PlayerText text;
292         TextType type;
293         optional<DCPTextTrack> track;
294         DCPTimePeriod period;
295 };
296
297
298 static void
299 store (list<Sub>* out, PlayerText text, TextType type, optional<DCPTextTrack> track, DCPTimePeriod period)
300 {
301         Sub s;
302         s.text = text;
303         s.type = type;
304         s.track = track;
305         s.period = period;
306         out->push_back (s);
307 }
308
309
310 /** Test ignoring both video and audio */
311 BOOST_AUTO_TEST_CASE (player_ignore_video_and_audio_test)
312 {
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);
321
322         Player player(film, Image::Alignment::COMPACT);
323         player.set_ignore_video();
324         player.set_ignore_audio();
325
326         list<Sub> out;
327         player.Text.connect(bind (&store, &out, _1, _2, _3, _4));
328         while (!player.pass()) {}
329
330         BOOST_CHECK_EQUAL (out.size(), 6U);
331 }
332
333
334 /** Trigger a crash due to the assertion failure in Player::emit_audio */
335 BOOST_AUTO_TEST_CASE (player_trim_crash)
336 {
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());
341
342         Player player(film, Image::Alignment::COMPACT);
343         player.set_fast();
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
346                 );
347
348         /* Wait for the butler to fill */
349         dcpomatic_sleep_seconds (5);
350
351         boon->set_trim_start(film, ContentTime::from_seconds(5));
352
353         butler->seek (DCPTime(), true);
354
355         /* Wait for the butler to refill */
356         dcpomatic_sleep_seconds (5);
357
358         butler->rethrow ();
359 }
360
361
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)
364 {
365         Cleanup cl;
366
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});
371
372         cl.run();
373 }
374
375
376 /** Test a crash when processing a 3D DCP */
377 BOOST_AUTO_TEST_CASE (player_3d_test_1)
378 {
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());
385
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);
391
392         make_and_verify_dcp (film);
393
394         auto dcp = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
395         auto film2 = new_test_film("player_3d_test_1b", {dcp});
396
397         film2->set_three_d (true);
398         make_and_verify_dcp (film2);
399 }
400
401
402 /** Test a crash when processing a 3D DCP as content in a 2D project */
403 BOOST_AUTO_TEST_CASE (player_3d_test_2)
404 {
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});
408
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);
414
415         make_and_verify_dcp (film);
416
417         auto dcp = std::make_shared<DCPContent>(film->dir(film->dcp_name()));
418         auto film2 = new_test_film("player_3d_test_2b", {dcp});
419
420         make_and_verify_dcp (film2);
421 }
422
423
424 /** Test a crash when there is video-only content at the end of the DCP and a frame-rate conversion is happening;
425  *  #1691.
426  */
427 BOOST_AUTO_TEST_CASE (player_silence_at_end_crash)
428 {
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);
435
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});
439
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_")) {
444                         video = i.path();
445                 }
446         }
447
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);
455 }
456
457
458 /** #2257 */
459 BOOST_AUTO_TEST_CASE (encrypted_dcp_with_no_kdm_gives_no_butler_error)
460 {
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 (
467                 film,
468                 {
469                         dcp::VerificationNote::Code::MISSING_CPL_METADATA,
470                 });
471
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 });
474
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);
477
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);
482         }
483
484         BOOST_CHECK_NO_THROW(butler.rethrow());
485 }
486
487
488 BOOST_AUTO_TEST_CASE (interleaved_subtitle_are_emitted_correctly)
489 {
490         boost::filesystem::path paths[2] = {
491                 "build/test/interleaved_subtitle_are_emitted_correctly1.srt",
492                 "build/test/interleaved_subtitle_are_emitted_correctly2.srt"
493         };
494
495         dcp::File subs_file[2] = { dcp::File(paths[0], "w"), dcp::File(paths[1], "w") };
496
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");
499
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");
502
503         subs_file[0].close();
504         subs_file[1].close();
505
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());
512
513         Player player(film, Image::Alignment::COMPACT);
514         dcp::Time last;
515         player.Text.connect([&last](PlayerText text, TextType, optional<DCPTextTrack>, dcpomatic::DCPTimePeriod) {
516                 for (auto sub: text.string) {
517                         BOOST_CHECK(sub.in() >= last);
518                         last = sub.in();
519                 }
520         });
521         while (!player.pass()) {}
522 }
523
524
525 BOOST_AUTO_TEST_CASE(multiple_sound_files_bug)
526 {
527         Cleanup cl;
528
529         Config::instance()->set_log_types(Config::instance()->log_types() | LogEntry::TYPE_DEBUG_PLAYER);
530
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();
534
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));
538
539         make_and_verify_dcp(film, { dcp::VerificationNote::Code::MISSING_CPL_METADATA });
540
541         check_mxf_audio_file(TestPaths::private_data() / "kook" / "reference.mxf", dcp_file(film, "pcm_"));
542
543         cl.run();
544 }
545
546
547 BOOST_AUTO_TEST_CASE(trimmed_sound_mix_bug_13)
548 {
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);
553
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));
559
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_"));
562 }
563
564
565 BOOST_AUTO_TEST_CASE(trimmed_sound_mix_bug_13_frame_rate_change)
566 {
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 });
570
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));
576
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);
581
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_"));
584 }
585
586
587 BOOST_AUTO_TEST_CASE(two_d_in_three_d_duplicates)
588 {
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 });
592
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));
597
598         auto player = std::make_shared<Player>(film, film->playlist());
599
600         std::vector<uint8_t> red_line(1998 * 3);
601         for (int i = 0; i < 1998; ++i) {
602                 red_line[i * 3] = 255;
603         };
604
605         std::vector<uint8_t> green_line(1998 * 3);
606         for (int i = 0; i < 1998; ++i) {
607                 green_line[i * 3 + 1] = 255;
608         };
609
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));
617                 } else {
618                         BOOST_CHECK(time == *last_time);
619                 }
620                 last_time = time;
621
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);
628                         } else {
629                                 BOOST_REQUIRE_EQUAL(memcmp(line, green_line.data(), 1998 * 3), 0);
630                         }
631                 }
632         });
633
634         BOOST_CHECK(film->length() == DCPTime::from_seconds(20));
635         while (!player->pass()) {}
636 }
637
638
639 BOOST_AUTO_TEST_CASE(three_d_in_two_d_chooses_left)
640 {
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();
644
645         auto film = new_test_film("three_d_in_two_d_chooses_left", { left, right, mono });
646
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());
651
652         mono->set_position(film, dcpomatic::DCPTime::from_seconds(10));
653
654         auto player = std::make_shared<Player>(film, film->playlist());
655
656         std::vector<uint8_t> red_line(1998 * 3);
657         for (int i = 0; i < 1998; ++i) {
658                 red_line[i * 3] = 255;
659         };
660
661         std::vector<uint8_t> blue_line(1998 * 3);
662         for (int i = 0; i < 1998; ++i) {
663                 blue_line[i * 3 + 2] = 255;
664         };
665
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));
670                 last_time = time;
671
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);
678                         } else {
679                                 BOOST_REQUIRE_EQUAL(memcmp(line, blue_line.data(), 1998 * 3), 0);
680                         }
681                 }
682         });
683
684         BOOST_CHECK(film->length() == DCPTime::from_seconds(20));
685         while (!player->pass()) {}
686 }
687
688
689 BOOST_AUTO_TEST_CASE(check_seek_with_no_video)
690 {
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());
694
695         boost::signals2::signal<void (std::shared_ptr<PlayerVideo>, dcpomatic::DCPTime)> Video;
696
697         optional<dcpomatic::DCPTime> earliest;
698
699         player->Video.connect(
700                 [&earliest](shared_ptr<PlayerVideo>, dcpomatic::DCPTime time) {
701                         if (!earliest || time < *earliest) {
702                                 earliest = time;
703                         }
704                 });
705
706         player->seek(dcpomatic::DCPTime::from_seconds(60 * 60), false);
707
708         for (int i = 0; i < 10; ++i) {
709                 player->pass();
710         }
711
712         BOOST_REQUIRE(earliest);
713         BOOST_CHECK(*earliest >= dcpomatic::DCPTime(60 * 60));
714 }
715
716
717 BOOST_AUTO_TEST_CASE(unmapped_audio_does_not_raise_buffer_error)
718 {
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 });
721
722         content->audio->set_mapping(AudioMapping(6 * 2, MAX_DCP_AUDIO_CHANNELS));
723
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);
726
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.
729          */
730         dcpomatic_sleep_seconds(10);
731         butler.rethrow();
732 }
733