Bv2.1 7.3: audio sample rate must be 48kHz.
[libdcp.git] / test / test.cc
1 /*
2     Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp 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     libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 #define BOOST_TEST_DYN_LINK
35 #define BOOST_TEST_MODULE libdcp_test
36 #include "compose.hpp"
37 #include "cpl.h"
38 #include "dcp.h"
39 #include "interop_subtitle_asset.h"
40 #include "mono_picture_asset.h"
41 #include "picture_asset_writer.h"
42 #include "reel.h"
43 #include "reel_mono_picture_asset.h"
44 #include "reel_sound_asset.h"
45 #include "reel_closed_caption_asset.h"
46 #include "reel_subtitle_asset.h"
47 #include "sound_asset.h"
48 #include "sound_asset_writer.h"
49 #include "smpte_subtitle_asset.h"
50 #include "mono_picture_asset.h"
51 #include "openjpeg_image.h"
52 #include "j2k.h"
53 #include "picture_asset_writer.h"
54 #include "reel_mono_picture_asset.h"
55 #include "reel_asset.h"
56 #include "test.h"
57 #include "util.h"
58 #include <asdcp/KM_util.h>
59 #include <asdcp/KM_prng.h>
60 #include <sndfile.h>
61 #include <libxml++/libxml++.h>
62 #include <boost/test/unit_test.hpp>
63 #include <cstdio>
64 #include <iostream>
65
66 using std::string;
67 using std::min;
68 using std::vector;
69 using std::shared_ptr;
70 using boost::optional;
71
72
73 boost::filesystem::path private_test;
74 boost::filesystem::path xsd_test = "build/test/xsd with spaces";
75
76
77 struct TestConfig
78 {
79         TestConfig()
80         {
81                 dcp::init ();
82                 if (boost::unit_test::framework::master_test_suite().argc >= 2) {
83                         private_test = boost::unit_test::framework::master_test_suite().argv[1];
84                 }
85
86                 using namespace boost::filesystem;
87                 boost::system::error_code ec;
88                 remove_all (xsd_test, ec);
89                 boost::filesystem::create_directory (xsd_test);
90                 for (directory_iterator i = directory_iterator("xsd"); i != directory_iterator(); ++i) {
91                         copy_file (*i, xsd_test / i->path().filename());
92                 }
93         }
94 };
95
96 void
97 check_xml (xmlpp::Element* ref, xmlpp::Element* test, vector<string> ignore_tags, bool ignore_whitespace)
98 {
99         BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
100         BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
101
102         if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
103                 return;
104         }
105
106         auto whitespace_content = [](xmlpp::Node* node) {
107                 auto content = dynamic_cast<xmlpp::ContentNode*>(node);
108                 return content && content->get_content().find_first_not_of(" \t\r\n") == string::npos;
109         };
110
111         auto ref_children = ref->get_children ();
112         auto test_children = test->get_children ();
113
114         auto k = ref_children.begin ();
115         auto l = test_children.begin ();
116         while (k != ref_children.end() && l != test_children.end()) {
117
118                 if (dynamic_cast<xmlpp::CommentNode*>(*k)) {
119                         ++k;
120                         continue;
121                 }
122
123                 if (dynamic_cast<xmlpp::CommentNode*>(*l)) {
124                         ++l;
125                         continue;
126                 }
127
128                 if (whitespace_content(*k) && ignore_whitespace) {
129                         ++k;
130                         continue;
131                 }
132
133                 if (whitespace_content(*l) && ignore_whitespace) {
134                         ++l;
135                         continue;
136                 }
137
138                 /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */
139
140                 auto ref_el = dynamic_cast<xmlpp::Element*> (*k);
141                 auto test_el = dynamic_cast<xmlpp::Element*> (*l);
142                 BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
143                 if (ref_el && test_el) {
144                         check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
145                 }
146
147                 auto ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
148                 auto test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
149                 BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
150                 if (ref_cn && test_cn) {
151                         BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content());
152                 }
153
154                 ++k;
155                 ++l;
156         }
157
158         while (k != ref_children.end() && ignore_whitespace && whitespace_content(*k)) {
159                 ++k;
160         }
161
162         while (l != test_children.end() && ignore_whitespace && whitespace_content(*l)) {
163                 ++l;
164         }
165
166         BOOST_REQUIRE (k == ref_children.end());
167         BOOST_REQUIRE (l == test_children.end());
168
169         auto ref_attributes = ref->get_attributes ();
170         auto test_attributes = test->get_attributes ();
171         BOOST_CHECK_EQUAL (ref_attributes.size(), test_attributes.size ());
172
173         auto m = ref_attributes.begin();
174         auto n = test_attributes.begin();
175         while (m != ref_attributes.end ()) {
176                 BOOST_CHECK_EQUAL ((*m)->get_name(), (*n)->get_name());
177                 BOOST_CHECK_EQUAL ((*m)->get_value(), (*n)->get_value());
178
179                 ++m;
180                 ++n;
181         }
182 }
183
184 void
185 check_xml (string ref, string test, vector<string> ignore, bool ignore_whitespace)
186 {
187         xmlpp::DomParser* ref_parser = new xmlpp::DomParser ();
188         ref_parser->parse_memory (ref);
189         xmlpp::Element* ref_root = ref_parser->get_document()->get_root_node ();
190         xmlpp::DomParser* test_parser = new xmlpp::DomParser ();
191         test_parser->parse_memory (test);
192         xmlpp::Element* test_root = test_parser->get_document()->get_root_node ();
193
194         check_xml (ref_root, test_root, ignore, ignore_whitespace);
195 }
196
197 void
198 check_file (boost::filesystem::path ref, boost::filesystem::path check)
199 {
200         uintmax_t size = boost::filesystem::file_size (ref);
201         BOOST_CHECK_EQUAL (size, boost::filesystem::file_size(check));
202         FILE* ref_file = dcp::fopen_boost (ref, "rb");
203         BOOST_REQUIRE (ref_file);
204         FILE* check_file = dcp::fopen_boost (check, "rb");
205         BOOST_REQUIRE (check_file);
206
207         int const buffer_size = 65536;
208         uint8_t* ref_buffer = new uint8_t[buffer_size];
209         uint8_t* check_buffer = new uint8_t[buffer_size];
210
211         uintmax_t pos = 0;
212
213         while (pos < size) {
214                 uintmax_t this_time = min (uintmax_t(buffer_size), size - pos);
215                 size_t r = fread (ref_buffer, 1, this_time, ref_file);
216                 BOOST_CHECK_EQUAL (r, this_time);
217                 r = fread (check_buffer, 1, this_time, check_file);
218                 BOOST_CHECK_EQUAL (r, this_time);
219
220                 if (memcmp(ref_buffer, check_buffer, this_time) != 0) {
221                         for (int i = 0; i < buffer_size; ++i) {
222                                 if (ref_buffer[i] != check_buffer[i]) {
223                                         BOOST_CHECK_MESSAGE (
224                                                 false,
225                                                 dcp::String::compose("File %1 differs from reference %2 at offset %3", check, ref, pos + i)
226                                                 );
227                                         break;
228                                 }
229                         }
230                         break;
231                 }
232
233                 pos += this_time;
234         }
235
236         delete[] ref_buffer;
237         delete[] check_buffer;
238
239         fclose (ref_file);
240         fclose (check_file);
241 }
242
243
244 RNGFixer::RNGFixer ()
245 {
246         Kumu::cth_test = true;
247         Kumu::FortunaRNG().Reset();
248 }
249
250
251 RNGFixer::~RNGFixer ()
252 {
253         Kumu::cth_test = false;
254 }
255
256
257 shared_ptr<dcp::MonoPictureAsset>
258 simple_picture (boost::filesystem::path path, string suffix, int frames)
259 {
260         dcp::MXFMetadata mxf_meta;
261         mxf_meta.company_name = "OpenDCP";
262         mxf_meta.product_name = "OpenDCP";
263         mxf_meta.product_version = "0.0.25";
264
265         shared_ptr<dcp::MonoPictureAsset> mp (new dcp::MonoPictureAsset (dcp::Fraction (24, 1), dcp::SMPTE));
266         mp->set_metadata (mxf_meta);
267         shared_ptr<dcp::PictureAssetWriter> picture_writer = mp->start_write (path / dcp::String::compose("video%1.mxf", suffix), false);
268         dcp::ArrayData j2c ("test/data/flat_red.j2c");
269         for (int i = 0; i < frames; ++i) {
270                 picture_writer->write (j2c.data (), j2c.size ());
271         }
272         picture_writer->finalize ();
273
274         return mp;
275 }
276
277
278 shared_ptr<dcp::SoundAsset>
279 simple_sound (boost::filesystem::path path, string suffix, dcp::MXFMetadata mxf_meta, string language, int frames, int sample_rate)
280 {
281         int const channels = 1;
282
283         /* Set a valid language, then overwrite it, so that the language parameter can be badly formed */
284         shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), sample_rate, channels, dcp::LanguageTag("en-US"), dcp::SMPTE));
285         ms->_language = language;
286         ms->set_metadata (mxf_meta);
287         vector<dcp::Channel> active_channels;
288         active_channels.push_back (dcp::LEFT);
289         shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write (path / dcp::String::compose("audio%1.mxf", suffix), active_channels);
290
291         int const samples_per_frame = sample_rate / 24;
292
293         float* silence[channels];
294         for (auto i = 0; i < channels; ++i) {
295                 silence[i] = new float[samples_per_frame];
296                 memset (silence[i], 0, samples_per_frame * sizeof(float));
297         }
298
299         for (auto i = 0; i < frames; ++i) {
300                 sound_writer->write (silence, samples_per_frame);
301         }
302
303         sound_writer->finalize ();
304
305         for (auto i = 0; i < channels; ++i) {
306                 delete[] silence[i];
307         }
308
309         return ms;
310 }
311
312
313 shared_ptr<dcp::DCP>
314 make_simple (boost::filesystem::path path, int reels, int frames)
315 {
316         /* Some known metadata */
317         dcp::MXFMetadata mxf_meta;
318         mxf_meta.company_name = "OpenDCP";
319         mxf_meta.product_name = "OpenDCP";
320         mxf_meta.product_version = "0.0.25";
321
322         boost::filesystem::remove_all (path);
323         boost::filesystem::create_directories (path);
324         shared_ptr<dcp::DCP> d (new dcp::DCP (path));
325         shared_ptr<dcp::CPL> cpl (new dcp::CPL ("A Test DCP", dcp::FEATURE));
326         cpl->set_annotation_text ("A Test DCP");
327         cpl->set_issuer ("OpenDCP 0.0.25");
328         cpl->set_creator ("OpenDCP 0.0.25");
329         cpl->set_issue_date ("2012-07-17T04:45:18+00:00");
330         cpl->set_content_version (
331                 dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
332                 );
333
334         for (int i = 0; i < reels; ++i) {
335                 string suffix = reels == 1 ? "" : dcp::String::compose("%1", i);
336
337                 shared_ptr<dcp::MonoPictureAsset> mp = simple_picture (path, suffix, frames);
338                 shared_ptr<dcp::SoundAsset> ms = simple_sound (path, suffix, mxf_meta, "en-US", frames);
339
340                 cpl->add (shared_ptr<dcp::Reel> (
341                                   new dcp::Reel (
342                                           shared_ptr<dcp::ReelMonoPictureAsset>(new dcp::ReelMonoPictureAsset(mp, 0)),
343                                           shared_ptr<dcp::ReelSoundAsset>(new dcp::ReelSoundAsset(ms, 0))
344                                           )
345                                   ));
346         }
347
348         d->add (cpl);
349         return d;
350 }
351
352
353 shared_ptr<dcp::Subtitle>
354 simple_subtitle ()
355 {
356         return shared_ptr<dcp::Subtitle>(
357                 new dcp::SubtitleString(
358                         optional<string>(),
359                         false,
360                         false,
361                         false,
362                         dcp::Colour(255, 255, 255),
363                         42,
364                         1,
365                         dcp::Time(0, 0, 4, 0, 24),
366                         dcp::Time(0, 0, 8, 0, 24),
367                         0.5,
368                         dcp::HALIGN_CENTER,
369                         0.8,
370                         dcp::VALIGN_TOP,
371                         dcp::DIRECTION_LTR,
372                         "Hello world",
373                         dcp::NONE,
374                         dcp::Colour(255, 255, 255),
375                         dcp::Time(),
376                         dcp::Time()
377                         )
378                 );
379 }
380
381
382 shared_ptr<dcp::DCP>
383 make_simple_with_interop_subs (boost::filesystem::path path)
384 {
385         shared_ptr<dcp::DCP> dcp = make_simple (path);
386
387         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
388         subs->add (simple_subtitle());
389
390         boost::filesystem::create_directory (path / "subs");
391         dcp::ArrayData data(4096);
392         subs->add_font ("afont", data);
393         subs->write (path / "subs" / "subs.xml");
394
395         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
396         dcp->cpls().front()->reels().front()->add (reel_subs);
397
398         return dcp;
399 }
400
401
402 shared_ptr<dcp::DCP>
403 make_simple_with_smpte_subs (boost::filesystem::path path)
404 {
405         shared_ptr<dcp::DCP> dcp = make_simple (path);
406
407         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
408         subs->set_language (dcp::LanguageTag("de-DE"));
409         subs->set_start_time (dcp::Time());
410         subs->add (simple_subtitle());
411
412         subs->write (path / "subs.mxf");
413
414         shared_ptr<dcp::ReelSubtitleAsset> reel_subs(new dcp::ReelSubtitleAsset(subs, dcp::Fraction(24, 1), 240, 0));
415         dcp->cpls().front()->reels().front()->add (reel_subs);
416
417         return dcp;
418 }
419
420
421 shared_ptr<dcp::DCP>
422 make_simple_with_interop_ccaps (boost::filesystem::path path)
423 {
424         shared_ptr<dcp::DCP> dcp = make_simple (path);
425
426         shared_ptr<dcp::InteropSubtitleAsset> subs(new dcp::InteropSubtitleAsset());
427         subs->add (simple_subtitle());
428         subs->write (path / "ccap.xml");
429
430         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
431         dcp->cpls().front()->reels().front()->add (reel_caps);
432
433         return dcp;
434 }
435
436
437 shared_ptr<dcp::DCP>
438 make_simple_with_smpte_ccaps (boost::filesystem::path path)
439 {
440         shared_ptr<dcp::DCP> dcp = make_simple (path);
441
442         shared_ptr<dcp::SMPTESubtitleAsset> subs(new dcp::SMPTESubtitleAsset());
443         subs->set_language (dcp::LanguageTag("de-DE"));
444         subs->set_start_time (dcp::Time());
445         subs->add (simple_subtitle());
446         subs->write (path / "ccap.mxf");
447
448         shared_ptr<dcp::ReelClosedCaptionAsset> reel_caps(new dcp::ReelClosedCaptionAsset(subs, dcp::Fraction(24, 1), 240, 0));
449         dcp->cpls().front()->reels().front()->add (reel_caps);
450
451         return dcp;
452 }
453
454
455 shared_ptr<dcp::OpenJPEGImage>
456 black_image (dcp::Size size)
457 {
458         shared_ptr<dcp::OpenJPEGImage> image(new dcp::OpenJPEGImage(size));
459         int const pixels = size.width * size.height;
460         for (int i = 0; i < 3; ++i) {
461                 memset (image->data(i), 0, pixels * sizeof(int));
462         }
463         return image;
464 }
465
466
467 shared_ptr<dcp::ReelAsset>
468 black_picture_asset (boost::filesystem::path dir, int frames)
469 {
470         shared_ptr<dcp::OpenJPEGImage> image = black_image ();
471         dcp::ArrayData frame = dcp::compress_j2k (image, 100000000, 24, false, false);
472         BOOST_REQUIRE (frame.size() < 230000000 / (24 * 8));
473
474         shared_ptr<dcp::MonoPictureAsset> asset(new dcp::MonoPictureAsset(dcp::Fraction(24, 1), dcp::SMPTE));
475         asset->set_metadata (dcp::MXFMetadata("libdcp", "libdcp", "1.6.4devel"));
476         boost::filesystem::create_directories (dir);
477         shared_ptr<dcp::PictureAssetWriter> writer = asset->start_write (dir / "pic.mxf", true);
478         for (int i = 0; i < frames; ++i) {
479                 writer->write (frame.data(), frame.size());
480         }
481         writer->finalize ();
482
483         return shared_ptr<dcp::ReelAsset>(new dcp::ReelMonoPictureAsset(asset, 0));
484 }
485
486
487 BOOST_GLOBAL_FIXTURE (TestConfig);