Support MCA sound channel tags in MXF/CPL.
authorCarl Hetherington <cth@carlh.net>
Sun, 6 Sep 2020 18:10:13 +0000 (20:10 +0200)
committerCarl Hetherington <cth@carlh.net>
Sun, 20 Sep 2020 17:34:50 +0000 (19:34 +0200)
21 files changed:
examples/make_dcp.cc
src/asset_reader.h
src/cpl.cc
src/exceptions.cc
src/exceptions.h
src/sound_asset.cc
src/sound_asset.h
src/sound_asset_writer.cc
src/sound_asset_writer.h
src/types.cc
src/types.h
test/cpl_metadata_test.cc
test/dcp_test.cc
test/encryption_test.cc
test/mca_test.cc [new file with mode: 0644]
test/ref/cpl_metadata_test1.xml
test/ref/cpl_metadata_test2.xml
test/sync_test.cc
test/test.cc
test/test.h
test/wscript

index 0c7ebf77add9c1b4014804a304182f84bd8ad49f..734ae880329d92eb22cdb4011092ad745c0d1b6d 100644 (file)
@@ -71,8 +71,12 @@ main ()
        /* Now create a sound MXF.  As before, create an object and a writer.
           When creating the object we specify the sampling rate (48kHz) and the number of channels (2).
        */
-       boost::shared_ptr<dcp::SoundAsset> sound_asset (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 2, dcp::SMPTE));
-       boost::shared_ptr<dcp::SoundAssetWriter> sound_writer = sound_asset->start_write ("DCP/sound.mxf");
+       boost::shared_ptr<dcp::SoundAsset> sound_asset (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 2, dcp::LanguageTag("en-GB"), dcp::SMPTE));
+       /* Here we must also say which of our channels will have "real" sound data in them */
+       std::vector<dcp::Channel> active_channels;
+       active_channels.push_back (dcp::LEFT);
+       active_channels.push_back (dcp::RIGHT);
+       boost::shared_ptr<dcp::SoundAssetWriter> sound_writer = sound_asset->start_write ("DCP/sound.mxf", active_channels);
 
        /* Write some sine waves */
        float* audio[2];
index dbd2761b0798d68b0f17e36bc05db3dfec79cf93..714d960c538c756452b5fa5b48fea41cd53ac5ef 100644 (file)
@@ -69,6 +69,10 @@ public:
                return boost::shared_ptr<const F> (new F (_reader, n, _crypto_context));
        }
 
+       R* reader () const {
+               return _reader;
+       }
+
 protected:
        R* _reader;
        boost::shared_ptr<DecryptionContext> _crypto_context;
index 494f53bed07a2688ae9179c8555609faf15086e1..711c9e1bf72140a27b7ff39db08b97b85af8ccae 100644 (file)
@@ -46,6 +46,7 @@
 #include "dcp_assert.h"
 #include "compose.hpp"
 #include "raw_convert.h"
+#include <asdcp/Metadata.h>
 #include <libxml/parser.h>
 #include <libxml++/libxml++.h>
 #include <boost/algorithm/string.hpp>
@@ -67,7 +68,9 @@ using namespace dcp;
 static string const cpl_interop_ns = "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#";
 static string const cpl_smpte_ns   = "http://www.smpte-ra.org/schemas/429-7/2006/CPL";
 static string const cpl_metadata_ns = "http://www.smpte-ra.org/schemas/429-16/2014/CPL-Metadata";
-
+static string const mca_sub_descriptors_ns = "http://isdcf.com/ns/cplmd/mca";
+static string const smpte_395_ns = "http://www.smpte-ra.org/reg/395/2014/13/1/aaf";
+static string const smpte_335_ns = "http://www.smpte-ra.org/reg/335/2012";
 
 CPL::CPL (string annotation_text, ContentKind content_kind)
        /* default _content_title_text to annotation_text */
@@ -395,6 +398,76 @@ CPL::maybe_write_composition_metadata_asset (xmlpp::Element* node) const
                }
                meta->add_child("MainSubtitleLanguageList")->add_child_text(lang);
        }
+
+       if (_reels.front()->main_sound()) {
+               shared_ptr<const SoundAsset> asset = _reels.front()->main_sound()->asset();
+               if (asset) {
+                       shared_ptr<SoundAssetReader> reader = asset->start_read ();
+                       ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
+                       ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType(
+                               ASDCP::DefaultSMPTEDict().ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
+                               reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
+                               );
+                       if (KM_SUCCESS(r)) {
+                               xmlpp::Element* mca_subs = meta->add_child("mca:MCASubDescriptors");
+                               mca_subs->set_namespace_declaration (mca_sub_descriptors_ns, "mca");
+                               mca_subs->set_namespace_declaration (smpte_395_ns, "r0");
+                               mca_subs->set_namespace_declaration (smpte_335_ns, "r1");
+                               xmlpp::Element* sf = mca_subs->add_child("SoundfieldGroupLabelSubDescriptor", "r0");
+                               char buffer[64];
+                               soundfield->InstanceUID.EncodeString(buffer, sizeof(buffer));
+                               sf->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+                               soundfield->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
+                               sf->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
+                               soundfield->MCALinkID.EncodeString(buffer, sizeof(buffer));
+                               sf->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+                               soundfield->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
+                               sf->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
+                               if (!soundfield->MCATagName.empty()) {
+                                       soundfield->MCATagName.get().EncodeString(buffer, sizeof(buffer));
+                                       sf->add_child("MCATagName", "r1")->add_child_text(buffer);
+                               }
+                               if (!soundfield->RFC5646SpokenLanguage.empty()) {
+                                       soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
+                                       sf->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
+                               }
+
+                               list<ASDCP::MXF::InterchangeObject*> channels;
+                               ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectsByType(
+                                       ASDCP::DefaultSMPTEDict().ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
+                                       channels
+                                       );
+
+                               BOOST_FOREACH (ASDCP::MXF::InterchangeObject* i, channels) {
+                                       ASDCP::MXF::AudioChannelLabelSubDescriptor* channel = reinterpret_cast<ASDCP::MXF::AudioChannelLabelSubDescriptor*>(i);
+                                       xmlpp::Element* ch = mca_subs->add_child("AudioChannelLabelSubDescriptor", "r0");
+                                       channel->InstanceUID.EncodeString(buffer, sizeof(buffer));
+                                       ch->add_child("InstanceID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+                                       channel->MCALabelDictionaryID.EncodeString(buffer, sizeof(buffer));
+                                       ch->add_child("MCALabelDictionaryID", "r1")->add_child_text("urn:smpte:ul:" + string(buffer));
+                                       channel->MCALinkID.EncodeString(buffer, sizeof(buffer));
+                                       ch->add_child("MCALinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+                                       channel->MCATagSymbol.EncodeString(buffer, sizeof(buffer));
+                                       ch->add_child("MCATagSymbol", "r1")->add_child_text(buffer);
+                                       if (!channel->MCATagName.empty()) {
+                                               channel->MCATagName.get().EncodeString(buffer, sizeof(buffer));
+                                               ch->add_child("MCATagName", "r1")->add_child_text(buffer);
+                                       }
+                                       if (!channel->MCAChannelID.empty()) {
+                                               ch->add_child("MCAChannelID", "r1")->add_child_text(raw_convert<string>(channel->MCAChannelID.get()));
+                                       }
+                                       if (!channel->RFC5646SpokenLanguage.empty()) {
+                                               channel->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
+                                               ch->add_child("RFC5646SpokenLanguage", "r1")->add_child_text(buffer);
+                                       }
+                                       if (!channel->SoundfieldGroupLinkID.empty()) {
+                                               channel->SoundfieldGroupLinkID.get().EncodeString(buffer, sizeof(buffer));
+                                               ch->add_child("SoundfieldGroupLinkID", "r1")->add_child_text("urn:uuid:" + string(buffer));
+                                       }
+                               }
+                       }
+               }
+       }
 }
 
 
index 16676e20f70ac6560096a4c74ed1aeb489158026..ebe8609a0d5932966f3cf89a9d2783bb5561b6fd 100644 (file)
@@ -163,4 +163,9 @@ MainSoundConfigurationError::MainSoundConfigurationError (std::string s)
 
 }
 
->>>>>>> Support CPL metadata.
+
+UnknownChannelIdError::UnknownChannelIdError (std::string id)
+       : runtime_error (String::compose("Unrecognised channel id '%1'", id))
+{
+
+}
index 12624175cafe4a237c9f2dc3ecfd0ee0a894e433..b9bcfd3783a2e9772b1c9b19933b823e0fbfe0af 100644 (file)
@@ -276,6 +276,13 @@ public:
 };
 
 
+class UnknownChannelIdError : public std::runtime_error
+{
+public:
+       UnknownChannelIdError (std::string s);
+};
+
+
 }
 
 #endif
index 7f2bf5e3c45c29e08d0a4bdb4771b66df76dcccc..15626b9ae22a62a7a3dfa2d10ba82828a452de35 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
@@ -43,8 +43,9 @@
 #include "sound_asset_reader.h"
 #include "compose.hpp"
 #include "dcp_assert.h"
-#include <asdcp/KM_fileio.h>
 #include <asdcp/AS_DCP.h>
+#include <asdcp/KM_fileio.h>
+#include <asdcp/Metadata.h>
 #include <libxml++/nodes/element.h>
 #include <boost/filesystem.hpp>
 #include <stdexcept>
@@ -58,6 +59,11 @@ using namespace dcp;
 
 SoundAsset::SoundAsset (boost::filesystem::path file)
        : Asset (file)
+       /* XXX: this is a fallback language, which will be used if we can't find the RFC5646SpokenLanguage
+        * in the MXF header.  Perhaps RFC5646SpokenLanguage is optional and we should just not write it
+        * if we don't know it.
+        */
+       , _language ("en-US")
 {
        ASDCP::PCM::MXFReader reader;
        Kumu::Result_t r = reader.OpenRead (file.string().c_str());
@@ -81,15 +87,30 @@ SoundAsset::SoundAsset (boost::filesystem::path file)
                boost::throw_exception (ReadError ("could not read audio MXF information"));
        }
 
+       ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
+       ASDCP::Result_t rr = reader.OP1aHeader().GetMDObjectByType(
+               ASDCP::DefaultSMPTEDict().ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
+               reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
+               );
+
+       if (KM_SUCCESS(rr)) {
+               if (!soundfield->RFC5646SpokenLanguage.empty()) {
+                       char buffer[64];
+                       soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
+                       _language = dcp::LanguageTag (buffer);
+               }
+       }
+
        _id = read_writer_info (info);
 }
 
-SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, Standard standard)
+SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, LanguageTag language, Standard standard)
        : MXF (standard)
        , _edit_rate (edit_rate)
        , _intrinsic_duration (0)
        , _channels (channels)
        , _sampling_rate (sampling_rate)
+       , _language (language)
 {
 
 }
@@ -193,13 +214,13 @@ SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHand
 }
 
 shared_ptr<SoundAssetWriter>
-SoundAsset::start_write (boost::filesystem::path file, bool atmos_sync)
+SoundAsset::start_write (boost::filesystem::path file, vector<Channel> active_channels, bool atmos_sync)
 {
        if (atmos_sync && _channels < 14) {
                throw MiscError ("Insufficient channels to write ATMOS sync (there must be at least 14)");
        }
 
-       return shared_ptr<SoundAssetWriter> (new SoundAssetWriter(this, file, atmos_sync));
+       return shared_ptr<SoundAssetWriter> (new SoundAssetWriter(this, file, active_channels, atmos_sync));
 }
 
 shared_ptr<SoundAssetReader>
index 9656cf904d1758dbbc3596bb3b0a27f81752bd75..80f1cfb6f50dbb09568db3fe7b4a3c273b9fd90e 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
@@ -40,6 +40,7 @@
 
 #include "mxf.h"
 #include "types.h"
+#include "language_tag.h"
 #include "metadata.h"
 #include "sound_frame.h"
 #include "sound_asset_reader.h"
@@ -56,9 +57,9 @@ class SoundAsset : public Asset, public MXF
 {
 public:
        explicit SoundAsset (boost::filesystem::path file);
-       SoundAsset (Fraction edit_rate, int sampling_rate, int channels, Standard standard);
+       SoundAsset (Fraction edit_rate, int sampling_rate, int channels, LanguageTag language, Standard standard);
 
-       boost::shared_ptr<SoundAssetWriter> start_write (boost::filesystem::path file, bool atmos_sync = false);
+       boost::shared_ptr<SoundAssetWriter> start_write (boost::filesystem::path file, std::vector<Channel> active_channels, bool atmos_sync = false);
        boost::shared_ptr<SoundAssetReader> start_read () const;
 
        bool equals (
@@ -85,6 +86,10 @@ public:
                return _intrinsic_duration;
        }
 
+       LanguageTag language () const {
+               return _language;
+       }
+
        static bool valid_mxf (boost::filesystem::path);
        static std::string static_pkl_type (Standard standard);
 
@@ -102,6 +107,7 @@ private:
        int64_t _intrinsic_duration;
        int _channels;      ///< number of channels
        int _sampling_rate; ///< sampling rate in Hz
+       LanguageTag _language;
 };
 
 }
index ff1d02c23057fd5458aa5826ff1675a0884e8ed7..3980dc62036ad9286d41df06dbd394fecbc60241 100644 (file)
@@ -39,6 +39,8 @@
 #include "compose.hpp"
 #include "crypto_context.h"
 #include <asdcp/AS_DCP.h>
+#include <asdcp/Metadata.h>
+#include <boost/foreach.hpp>
 #include <iostream>
 
 using std::min;
@@ -48,6 +50,13 @@ using std::string;
 using std::vector;
 using namespace dcp;
 
+
+/* Some ASDCP objects store this as a *&, for reasons which are not
+ * at all clear, so we have to keep this around forever.
+ */
+static ASDCP::Dictionary const* smpte_dict = &ASDCP::DefaultSMPTEDict();
+
+
 struct SoundAssetWriter::ASDCPState
 {
        ASDCP::PCM::MXFWriter mxf_writer;
@@ -56,13 +65,14 @@ struct SoundAssetWriter::ASDCPState
        ASDCP::PCM::AudioDescriptor desc;
 };
 
-SoundAssetWriter::SoundAssetWriter (SoundAsset* asset, boost::filesystem::path file, bool sync)
+SoundAssetWriter::SoundAssetWriter (SoundAsset* asset, boost::filesystem::path file, vector<Channel> active_channels, bool sync)
        : AssetWriter (asset, file)
        , _state (new SoundAssetWriter::ASDCPState)
        , _asset (asset)
        , _frame_buffer_offset (0)
        , _sync (sync)
        , _sync_packet (0)
+       , _active_channels (active_channels)
 {
        DCP_ASSERT (!_sync || _asset->channels() >= 14);
        DCP_ASSERT (!_sync || _asset->standard() == SMPTE);
@@ -101,6 +111,62 @@ SoundAssetWriter::SoundAssetWriter (SoundAsset* asset, boost::filesystem::path f
        }
 }
 
+
+void
+SoundAssetWriter::start ()
+{
+       Kumu::Result_t r = _state->mxf_writer.OpenWrite (_file.string().c_str(), _state->writer_info, _state->desc);
+       if (ASDCP_FAILURE (r)) {
+               boost::throw_exception (FileError ("could not open audio MXF for writing", _file.string(), r));
+       }
+
+       if (_asset->standard() == dcp::SMPTE && !_active_channels.empty()) {
+
+               ASDCP::MXF::WaveAudioDescriptor* essence_descriptor = 0;
+               _state->mxf_writer.OP1aHeader().GetMDObjectByType(
+                       smpte_dict->ul(ASDCP::MDD_WaveAudioDescriptor), reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&essence_descriptor)
+                       );
+               DCP_ASSERT (essence_descriptor);
+               essence_descriptor->ChannelAssignment = smpte_dict->ul(ASDCP::MDD_DCAudioChannelCfg_MCA);
+
+               ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield = new ASDCP::MXF::SoundfieldGroupLabelSubDescriptor(smpte_dict);
+               GenRandomValue (soundfield->MCALinkID);
+               soundfield->RFC5646SpokenLanguage = _asset->language().to_string();
+
+               const MCASoundField field = _asset->channels() > 10 ? SEVEN_POINT_ONE : FIVE_POINT_ONE;
+
+               if (field == SEVEN_POINT_ONE) {
+                       soundfield->MCATagSymbol = "sg71";
+                       soundfield->MCATagName = "7.1DS";
+                       soundfield->MCALabelDictionaryID = smpte_dict->ul(ASDCP::MDD_DCAudioSoundfield_71);
+               } else {
+                       soundfield->MCATagSymbol = "sg51";
+                       soundfield->MCATagName = "5.1";
+                       soundfield->MCALabelDictionaryID = smpte_dict->ul(ASDCP::MDD_DCAudioSoundfield_51);
+               }
+
+               _state->mxf_writer.OP1aHeader().AddChildObject(soundfield);
+               essence_descriptor->SubDescriptors.push_back(soundfield->InstanceUID);
+
+               BOOST_FOREACH (Channel i, _active_channels) {
+                       ASDCP::MXF::AudioChannelLabelSubDescriptor* channel = new ASDCP::MXF::AudioChannelLabelSubDescriptor(smpte_dict);
+                       GenRandomValue (channel->MCALinkID);
+                       channel->SoundfieldGroupLinkID = soundfield->MCALinkID;
+                       channel->MCAChannelID = static_cast<int>(i) + 1;
+                       channel->MCATagSymbol = "ch" + channel_to_mca_id(i, field);
+                       channel->MCATagName = channel_to_mca_name(i, field);
+                       channel->RFC5646SpokenLanguage = _asset->language().to_string();
+                       channel->MCALabelDictionaryID = channel_to_mca_universal_label(i, field, smpte_dict);
+                       _state->mxf_writer.OP1aHeader().AddChildObject(channel);
+                       essence_descriptor->SubDescriptors.push_back(channel->InstanceUID);
+               }
+       }
+
+       _asset->set_file (_file);
+       _started = true;
+}
+
+
 void
 SoundAssetWriter::write (float const * const * data, int frames)
 {
@@ -110,13 +176,7 @@ SoundAssetWriter::write (float const * const * data, int frames)
        static float const clip = 1.0f - (1.0f / pow (2, 23));
 
        if (!_started) {
-               Kumu::Result_t r = _state->mxf_writer.OpenWrite (_file.string().c_str(), _state->writer_info, _state->desc);
-               if (ASDCP_FAILURE (r)) {
-                       boost::throw_exception (FileError ("could not open audio MXF for writing", _file.string(), r));
-               }
-
-               _asset->set_file (_file);
-               _started = true;
+               start ();
        }
 
        int const ch = _asset->channels ();
index f1b1514d800830275b828b549ff5b244a16a5f2c..d3b260e6a55c2eea5ef40595a462d365a3e54f5d 100644 (file)
@@ -70,8 +70,9 @@ private:
        friend class SoundAsset;
        friend struct ::sync_test1;
 
-       SoundAssetWriter (SoundAsset *, boost::filesystem::path, bool sync);
+       SoundAssetWriter (SoundAsset *, boost::filesystem::path, std::vector<Channel> active_channels, bool sync);
 
+       void start ();
        void write_current_frame ();
        std::vector<bool> create_sync_packets ();
 
@@ -90,6 +91,8 @@ private:
        /** index of the sync packet (0-3) which starts the next edit unit */
        int _sync_packet;
        FSK _fsk;
+
+       std::vector<Channel> _active_channels;
 };
 
 }
index 9d67ee67bfb4292bf5281433f15699fce04337bb..669d4fca86ad4145953e0a3ce5fc7285fa2e0823 100644 (file)
@@ -596,40 +596,14 @@ MainSoundConfiguration::MainSoundConfiguration (string s)
        BOOST_FOREACH (string i, channels) {
                if (i == "-") {
                        _channels.push_back(optional<Channel>());
-               } else if (i == "L") {
-                       _channels.push_back(LEFT);
-               } else if (i == "R") {
-                       _channels.push_back(RIGHT);
-               } else if (i == "C") {
-                       _channels.push_back(CENTRE);
-               } else if (i == "LFE") {
-                       _channels.push_back(LFE);
-               } else if (i == "Ls" || i == "Lss") {
-                       _channels.push_back(LS);
-               } else if (i == "Rs" || i == "Rss") {
-                       _channels.push_back(RS);
-               } else if (i == "HI") {
-                       _channels.push_back(HI);
-               } else if (i == "VIN") {
-                       _channels.push_back(VI);
-               } else if (i == "Lrs") {
-                       _channels.push_back(BSL);
-               } else if (i == "Rrs") {
-                       _channels.push_back(BSR);
-               } else if (i == "DBOX") {
-                       _channels.push_back(MOTION_DATA);
-               } else if (i == "Sync") {
-                       _channels.push_back(SYNC_SIGNAL);
-               } else if (i == "Sign") {
-                       _channels.push_back(SIGN_LANGUAGE);
                } else {
-                       throw MainSoundConfigurationError (s);
+                       _channels.push_back(mca_id_to_channel(i));
                }
        }
 }
 
 
-MainSoundConfiguration::MainSoundConfiguration (Field field, int channels)
+MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
        : _field (field)
 {
        _channels.resize (channels);
@@ -650,55 +624,7 @@ MainSoundConfiguration::to_string () const
                if (!i) {
                        c += "-,";
                } else {
-                       switch (*i) {
-                       case LEFT:
-                               c += "L,";
-                               break;
-                       case RIGHT:
-                               c += "R,";
-                               break;
-                       case CENTRE:
-                               c += "C,";
-                               break;
-                       case LFE:
-                               c += "LFE,";
-                               break;
-                       case LS:
-                               c += (_field == FIVE_POINT_ONE ? "Ls," : "Lss,");
-                               break;
-                       case RS:
-                               c += (_field == FIVE_POINT_ONE ? "Rs," : "Rss,");
-                               break;
-                       case HI:
-                               c += "HI,";
-                               break;
-                       case VI:
-                               c += "VIN,";
-                               break;
-                       case LC:
-                       case RC:
-                               c += "-,";
-                               break;
-                       case BSL:
-                               c += "Lrs,";
-                               break;
-                       case BSR:
-                               c += "Rrs,";
-                               break;
-                       case MOTION_DATA:
-                               c += "DBOX,";
-                               break;
-                       case SYNC_SIGNAL:
-                               c += "Sync,";
-                               break;
-                       case SIGN_LANGUAGE:
-                               /* XXX: not sure what this should be */
-                               c += "Sign,";
-                               break;
-                       default:
-                               c += "-,";
-                               break;
-                       }
+                       c += channel_to_mca_id(*i, _field) + ",";
                }
        }
 
@@ -757,3 +683,161 @@ dcp::string_to_status (string s)
        DCP_ASSERT (false);
 }
 
+
+Channel
+dcp::mca_id_to_channel (string id)
+{
+       if (id == "L") {
+               return LEFT;
+       } else if (id == "R") {
+               return RIGHT;
+       } else if (id == "C") {
+               return CENTRE;
+       } else if (id == "LFE") {
+               return LFE;
+       } else if (id == "Ls" || id == "Lss") {
+               return LS;
+       } else if (id == "Rs" || id == "Rss") {
+               return RS;
+       } else if (id == "HI") {
+               return HI;
+       } else if (id == "VIN") {
+               return VI;
+       } else if (id == "Lrs") {
+               return BSL;
+       } else if (id == "Rrs") {
+               return BSR;
+       } else if (id == "DBOX") {
+               return MOTION_DATA;
+       } else if (id == "FSKSync") {
+               return SYNC_SIGNAL;
+       } else if (id == "SLVS") {
+               return SIGN_LANGUAGE;
+       }
+
+       throw UnknownChannelIdError (id);
+}
+
+
+string
+dcp::channel_to_mca_id (Channel c, MCASoundField field)
+{
+       switch (c) {
+       case LEFT:
+               return "L";
+       case RIGHT:
+               return "R";
+       case CENTRE:
+               return "C";
+       case LFE:
+               return "LFE";
+       case LS:
+               return field == FIVE_POINT_ONE ? "Ls" : "Lss";
+       case RS:
+               return field == FIVE_POINT_ONE ? "Rs" : "Rss";
+       case HI:
+               return "HI";
+       case VI:
+               return "VIN";
+       case BSL:
+               return "Lrs";
+       case BSR:
+               return "Rrs";
+       case MOTION_DATA:
+               return "DBOX";
+       case SYNC_SIGNAL:
+               return "FSKSync";
+       case SIGN_LANGUAGE:
+               return "SLVS";
+       default:
+               break;
+       }
+
+       DCP_ASSERT (false);
+}
+
+
+string
+dcp::channel_to_mca_name (Channel c, MCASoundField field)
+{
+       switch (c) {
+       case LEFT:
+               return "Left";
+       case RIGHT:
+               return "Right";
+       case CENTRE:
+               return "Center";
+       case LFE:
+               return "LFE";
+       case LS:
+               return field == FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
+       case RS:
+               return field == FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
+       case HI:
+               return "Hearing Impaired";
+       case VI:
+               return "Visually Impaired-Narrative";
+       case BSL:
+               return "Left Rear Surround";
+       case BSR:
+               return "Right Rear Surround";
+       case MOTION_DATA:
+               return "D-BOX Motion Code Primary Stream";
+       case SYNC_SIGNAL:
+               return "FSK Sync";
+       case SIGN_LANGUAGE:
+               return "Sign Language Video Stream";
+       default:
+               break;
+       }
+
+       DCP_ASSERT (false);
+}
+
+
+ASDCP::UL
+dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
+{
+       static byte_t sync_signal[] = {
+               0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
+       };
+
+       static byte_t sign_language[] = {
+               0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
+       };
+
+       switch (c) {
+       case LEFT:
+               return dict->ul(ASDCP::MDD_DCAudioChannel_L);
+       case RIGHT:
+               return dict->ul(ASDCP::MDD_DCAudioChannel_R);
+       case CENTRE:
+               return dict->ul(ASDCP::MDD_DCAudioChannel_C);
+       case LFE:
+               return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
+       case LS:
+               return dict->ul(field == FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
+       case RS:
+               return dict->ul(field == FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
+       case HI:
+               return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
+       case VI:
+               return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
+       case BSL:
+               return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
+       case BSR:
+               return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
+       case MOTION_DATA:
+               return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
+       case SYNC_SIGNAL:
+               return ASDCP::UL(sync_signal);
+       case SIGN_LANGUAGE:
+               return ASDCP::UL(sign_language);
+       default:
+               break;
+       }
+
+       DCP_ASSERT (false);
+}
+
+
index 9a4b3e13a03bde432f228646ab07f8905c74109a..ed0b7b843757f8f7e06306fc70c77e37e9bc4806 100644 (file)
@@ -39,6 +39,7 @@
 #define LIBDCP_TYPES_H
 
 #include <libcxml/cxml.h>
+#include <asdcp/KLV.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/function.hpp>
 #include <string>
@@ -98,6 +99,20 @@ enum Channel {
        CHANNEL_COUNT = 16
 };
 
+
+enum MCASoundField
+{
+       FIVE_POINT_ONE,
+       SEVEN_POINT_ONE
+};
+
+
+extern std::string channel_to_mca_id (Channel c, MCASoundField field);
+extern Channel mca_id_to_channel (std::string);
+extern std::string channel_to_mca_name (Channel c, MCASoundField field);
+extern ASDCP::UL channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict);
+
+
 enum ContentKind
 {
        FEATURE,
@@ -402,15 +417,10 @@ bool operator== (Luminance const& a, Luminance const& b);
 class MainSoundConfiguration
 {
 public:
-       enum Field {
-               FIVE_POINT_ONE,
-               SEVEN_POINT_ONE,
-       };
-
        MainSoundConfiguration (std::string);
-       MainSoundConfiguration (Field field_, int channels);
+       MainSoundConfiguration (MCASoundField field_, int channels);
 
-       Field field () const {
+       MCASoundField field () const {
                return _field;
        }
 
@@ -424,7 +434,7 @@ public:
        std::string to_string () const;
 
 private:
-       Field _field;
+       MCASoundField _field;
        std::vector<boost::optional<Channel> > _channels;
 };
 
index 216ef600d4924d40e7591b673d485d8485f33664..5082b8f68485cfaa5d5f8832c2d5ecfc9a1fb915 100644 (file)
@@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE (main_sound_configuration_test1)
        dcp::MainSoundConfiguration msc("51/L,R,C,LFE,-,-");
        BOOST_CHECK_EQUAL (msc.to_string(), "51/L,R,C,LFE,-,-");
        BOOST_CHECK_EQUAL (msc.channels(), 6);
-       BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::FIVE_POINT_ONE);
+       BOOST_CHECK_EQUAL (msc.field(), dcp::FIVE_POINT_ONE);
        BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
        BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::RIGHT);
        BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
@@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE (main_sound_configuration_test2)
        dcp::MainSoundConfiguration msc("71/L,R,C,LFE,-,-");
        BOOST_CHECK_EQUAL (msc.to_string(), "71/L,R,C,LFE,-,-");
        BOOST_CHECK_EQUAL (msc.channels(), 6);
-       BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::SEVEN_POINT_ONE);
+       BOOST_CHECK_EQUAL (msc.field(), dcp::SEVEN_POINT_ONE);
        BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
        BOOST_CHECK_EQUAL (msc.mapping(1).get(), dcp::RIGHT);
        BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
@@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE (main_sound_configuration_test3)
        dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss");
        BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss");
        BOOST_CHECK_EQUAL (msc.channels(), 6);
-       BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::SEVEN_POINT_ONE);
+       BOOST_CHECK_EQUAL (msc.field(), dcp::SEVEN_POINT_ONE);
        BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
        BOOST_CHECK (!msc.mapping(1));
        BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
@@ -111,7 +111,7 @@ BOOST_AUTO_TEST_CASE (main_sound_configuration_test4)
        dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
        BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,-,-,-,-,-,-,-,-,-");
        BOOST_CHECK_EQUAL (msc.channels(), 15);
-       BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::SEVEN_POINT_ONE);
+       BOOST_CHECK_EQUAL (msc.field(), dcp::SEVEN_POINT_ONE);
        BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
        BOOST_CHECK (!msc.mapping(1));
        BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
@@ -126,10 +126,10 @@ BOOST_AUTO_TEST_CASE (main_sound_configuration_test4)
 
 BOOST_AUTO_TEST_CASE (main_sound_configuration_test5)
 {
-       dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,Sync,Sign");
-       BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,Sync,Sign");
+       dcp::MainSoundConfiguration msc("71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,FSKSync,SLVS");
+       BOOST_CHECK_EQUAL (msc.to_string(), "71/L,-,C,LFE,Lss,Rss,HI,VIN,-,-,Lrs,Rrs,DBOX,FSKSync,SLVS");
        BOOST_CHECK_EQUAL (msc.channels(), 15);
-       BOOST_CHECK_EQUAL (msc.field(), dcp::MainSoundConfiguration::SEVEN_POINT_ONE);
+       BOOST_CHECK_EQUAL (msc.field(), dcp::SEVEN_POINT_ONE);
        BOOST_CHECK_EQUAL (msc.mapping(0).get(), dcp::LEFT);
        BOOST_CHECK (!msc.mapping(1));
        BOOST_CHECK_EQUAL (msc.mapping(2).get(), dcp::CENTRE);
@@ -267,7 +267,7 @@ BOOST_AUTO_TEST_CASE (cpl_metadata_write_test1)
        cpl.set_facility ("the-facility");
        cpl.set_luminance (dcp::Luminance(4.5, dcp::Luminance::FOOT_LAMBERT));
 
-       dcp::MainSoundConfiguration msc(dcp::MainSoundConfiguration::SEVEN_POINT_ONE, 16);
+       dcp::MainSoundConfiguration msc(dcp::SEVEN_POINT_ONE, 16);
        msc.set_mapping (0, dcp::LEFT);
        msc.set_mapping (1, dcp::RIGHT);
        msc.set_mapping (2, dcp::CENTRE);
@@ -338,7 +338,7 @@ BOOST_AUTO_TEST_CASE (cpl_metadata_write_test2)
        cpl.set_issue_date ("2020-08-28T13:35:06+02:00");
        cpl.set_content_version (dcp::ContentVersion("id", "version"));
 
-       dcp::MainSoundConfiguration msc(dcp::MainSoundConfiguration::SEVEN_POINT_ONE, 16);
+       dcp::MainSoundConfiguration msc(dcp::SEVEN_POINT_ONE, 16);
        msc.set_mapping (0, dcp::LEFT);
        msc.set_mapping (1, dcp::RIGHT);
        msc.set_mapping (2, dcp::CENTRE);
index a6efe02477f22f589a8759a096b70ceaba3dc42f..610b175eebfdc96e7e8bcfb5114113af3f7a478e 100644 (file)
@@ -52,6 +52,7 @@
 #include <boost/test/unit_test.hpp>
 
 using std::string;
+using std::vector;
 using boost::shared_ptr;
 
 
@@ -103,9 +104,9 @@ BOOST_AUTO_TEST_CASE (dcp_test2)
        }
        picture_writer->finalize ();
 
-       shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 1, dcp::SMPTE));
+       shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::SMPTE));
        ms->set_metadata (mxf_meta);
-       shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write ("build/test/DCP/dcp_test2/audio.mxf");
+       shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write ("build/test/DCP/dcp_test2/audio.mxf", vector<dcp::Channel>());
 
        SF_INFO info;
        info.format = 0;
@@ -199,9 +200,9 @@ BOOST_AUTO_TEST_CASE (dcp_test5)
        }
        picture_writer->finalize ();
 
-       shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 1, dcp::SMPTE));
+       shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::SMPTE));
        ms->set_metadata (mxf_meta);
-       shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write ("build/test/DCP/dcp_test5/audio.mxf");
+       shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write ("build/test/DCP/dcp_test5/audio.mxf", vector<dcp::Channel>());
 
        SF_INFO info;
        info.format = 0;
index c452c07bc3df71f60078abe4b9749b13b30d068c..2793e8adbbf2d791f28a882a960de2b78cde8dbe 100644 (file)
@@ -98,10 +98,10 @@ BOOST_AUTO_TEST_CASE (encryption_test)
        }
        writer->finalize ();
 
-       shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 1, dcp::SMPTE));
+       shared_ptr<dcp::SoundAsset> ms (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::SMPTE));
        ms->set_metadata (mxf_metadata);
        ms->set_key (key);
-       shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write ("build/test/DCP/encryption_test/audio.mxf");
+       shared_ptr<dcp::SoundAssetWriter> sound_writer = ms->start_write ("build/test/DCP/encryption_test/audio.mxf", vector<dcp::Channel>());
 
        SF_INFO info;
        info.format = 0;
diff --git a/test/mca_test.cc b/test/mca_test.cc
new file mode 100644 (file)
index 0000000..f1d0542
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "compose.hpp"
+#include "cpl.h"
+#include "reel.h"
+#include "reel_sound_asset.h"
+#include "sound_asset.h"
+#include "test.h"
+#include <libcxml/cxml.h>
+#include <libxml++/libxml++.h>
+#include <boost/test/unit_test.hpp>
+
+
+using std::list;
+using std::string;
+using boost::shared_ptr;
+
+
+/** Check that when we read a MXF and write its MCA metadata to a CPL we get the same answer
+ *  as the original MXF for that CPL (for a couple of different MXFs).
+ */
+BOOST_AUTO_TEST_CASE (parse_mca_descriptors_from_mxf_test)
+{
+       for (int i = 1; i < 3; ++i) {
+               shared_ptr<dcp::SoundAsset> sound_asset(new dcp::SoundAsset(private_test / "data" / dcp::String::compose("51_sound_with_mca_%1.mxf", i)));
+               shared_ptr<dcp::ReelSoundAsset> reel_sound_asset(new dcp::ReelSoundAsset(sound_asset, 0));
+               shared_ptr<dcp::Reel> reel(new dcp::Reel());
+               reel->add (black_picture_asset(dcp::String::compose("build/test/parse_mca_descriptors_from_mxf_test%1", i), 24));
+               reel->add (reel_sound_asset);
+
+               dcp::CPL cpl("", dcp::FEATURE);
+               cpl.add (reel);
+               cpl.set_main_sound_configuration("51/L,R,C,LFE,Ls,Rs");
+               cpl.set_main_sound_sample_rate(48000);
+               cpl.set_main_picture_stored_area(dcp::Size(1998, 1080));
+               cpl.set_main_picture_active_area(dcp::Size(1998, 1080));
+               cpl.write_xml (dcp::String::compose("build/test/parse_mca_descriptors_from_mxf_test%1/cpl.xml", i), dcp::SMPTE, shared_ptr<dcp::CertificateChain>());
+
+               cxml::Document ref("CompositionPlaylist", private_test / dcp::String::compose("51_sound_with_mca_%1.cpl", i));
+               cxml::Document check("CompositionPlaylist", dcp::String::compose("build/test/parse_mca_descriptors_from_mxf_test%1/cpl.xml", i));
+
+               list<string> ignore;
+               check_xml (
+                       dynamic_cast<xmlpp::Element*>(
+                               ref.node_child("ReelList")->node_children("Reel").front()->node_child("AssetList")->node_child("CompositionMetadataAsset")->node_child("MCASubDescriptors")->node()
+                               ),
+                       dynamic_cast<xmlpp::Element*>(
+                               check.node_child("ReelList")->node_children("Reel").front()->node_child("AssetList")->node_child("CompositionMetadataAsset")->node_child("MCASubDescriptors")->node()
+                               ),
+                       ignore,
+                       true
+                       );
+       }
+}
index b734edbb4b9b79636efce845e7253fe5e57b6cba..5d49e765e1ce4d2a5eeaabff5fb3f9d610101499 100644 (file)
@@ -55,7 +55,7 @@
             </ContentVersion>
           </meta:AlternateContentVersionList>
           <meta:Luminance units="foot-lambert">4.5</meta:Luminance>
-          <meta:MainSoundConfiguration>71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,Sync,-,-</meta:MainSoundConfiguration>
+          <meta:MainSoundConfiguration>71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,FSKSync,-,-</meta:MainSoundConfiguration>
           <meta:MainSoundSampleRate>48000 1</meta:MainSoundSampleRate>
           <meta:MainPictureStoredArea>
             <meta:Width>1998</meta:Width>
index d61a7113e791d6a8f177ecea5249860ee17256d1..a7b20ff36390d316f93c157c8b89fc39450ffec9 100644 (file)
@@ -32,7 +32,7 @@
           <EditRate>24 1</EditRate>
           <IntrinsicDuration>24</IntrinsicDuration>
           <meta:FullContentTitleText/>
-          <meta:MainSoundConfiguration>71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,Sync,-,-</meta:MainSoundConfiguration>
+          <meta:MainSoundConfiguration>71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,FSKSync,-,-</meta:MainSoundConfiguration>
           <meta:MainSoundSampleRate>48000 1</meta:MainSoundSampleRate>
           <meta:MainPictureStoredArea>
             <meta:Width>1998</meta:Width>
index 9fe6a30478d7ad80b348d03553fc069ab9c6b5da..0bffdb838fb5a8bd93e3dc55c095d7e7fda25cb3 100644 (file)
@@ -109,7 +109,7 @@ BOOST_AUTO_TEST_CASE (sync_test1)
                }
        }
 
-       shared_ptr<dcp::SoundAssetWriter> writer = asset.start_write ("build/test/foo.mxf", true);
+       shared_ptr<dcp::SoundAssetWriter> writer = asset.start_write ("build/test/foo.mxf", vector<dcp::Channel>(), true);
 
        /* Compare the sync bits made by SoundAssetWriter to the "proper" ones in the MXF */
        BOOST_CHECK (ref == writer->create_sync_packets());
@@ -120,11 +120,11 @@ BOOST_AUTO_TEST_CASE (sync_test2)
 {
        /* Make a MXF with the same ID as atmos_pcm.mxf and write a frame of random stuff */
        int const channels = 14;
-       dcp::SoundAsset asset (dcp::Fraction(24, 1), 48000, channels, dcp::SMPTE);
+       dcp::SoundAsset asset (dcp::Fraction(24, 1), 48000, channels, dcp::LanguageTag("en-GB"), dcp::SMPTE);
        asset._id = "e004046e09234f90a4ae4355e7e83506";
        boost::system::error_code ec;
        boost::filesystem::remove ("build/test/foo.mxf", ec);
-       shared_ptr<dcp::SoundAssetWriter> writer = asset.start_write ("build/test/foo.mxf", true);
+       shared_ptr<dcp::SoundAssetWriter> writer = asset.start_write ("build/test/foo.mxf", vector<dcp::Channel>(), true);
 
        int const frames = 2000;
        float** junk = new float*[channels];
index dd1432642d37341e0cff4549e5bf03203f1058bc..62013687a8d83e97c8d7cf1c6c46a2c9fa16207d 100644 (file)
@@ -95,12 +95,12 @@ struct TestConfig
 };
 
 void
-check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore)
+check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore_tags, bool ignore_whitespace)
 {
        BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ());
        BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ());
 
-       if (find (ignore.begin(), ignore.end(), ref->get_name()) != ignore.end ()) {
+       if (find(ignore_tags.begin(), ignore_tags.end(), ref->get_name()) != ignore_tags.end()) {
                return;
        }
 
@@ -121,14 +121,20 @@ check_xml (xmlpp::Element* ref, xmlpp::Element* test, list<string> ignore)
                xmlpp::Element* test_el = dynamic_cast<xmlpp::Element*> (*l);
                BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el));
                if (ref_el && test_el) {
-                       check_xml (ref_el, test_el, ignore);
+                       check_xml (ref_el, test_el, ignore_tags, ignore_whitespace);
                }
 
                xmlpp::ContentNode* ref_cn = dynamic_cast<xmlpp::ContentNode*> (*k);
                xmlpp::ContentNode* test_cn = dynamic_cast<xmlpp::ContentNode*> (*l);
                BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn));
                if (ref_cn && test_cn) {
-                       BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content ());
+                       if (
+                               !ignore_whitespace ||
+                               ref_cn->get_content().find_first_not_of(" \t\r\n") != string::npos ||
+                               test_cn->get_content().find_first_not_of(" \t\r\n") != string::npos) {
+
+                               BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content ());
+                       }
                }
 
                ++k;
index 95827af7607b8f0b1fe878d7cc38cba1e90b2873..9009a58bcdcbdc7388a111561bee076e6a6c2a5e 100644 (file)
@@ -40,7 +40,7 @@ namespace dcp {
 extern boost::filesystem::path private_test;
 extern boost::filesystem::path xsd_test;
 
-extern void check_xml (xmlpp::Element* ref, xmlpp::Element* test, std::list<std::string> ignore);
+extern void check_xml (xmlpp::Element* ref, xmlpp::Element* test, std::list<std::string> ignore_tags, bool ignore_whitespace = false);
 extern void check_xml (std::string ref, std::string test, std::list<std::string> ignore);
 extern void check_file (boost::filesystem::path ref, boost::filesystem::path check);
 extern boost::shared_ptr<dcp::MonoPictureAsset> simple_picture (boost::filesystem::path path, std::string suffix);
index 5fd2f430a2436211d786e80555e6648f0c12f7ff..aa134bde4fde200d2cc9d5b39dec19c50b1707f6 100644 (file)
@@ -84,6 +84,7 @@ def build(bld):
                  local_time_test.cc
                  make_digest_test.cc
                  markers_test.cc
+                 mca_test.cc
                  kdm_test.cc
                  key_test.cc
                  language_tag_test.cc