From cdc8bc6a7da0b4f8c3dbfcf560fea61473cf1ca3 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Sun, 6 Sep 2020 20:10:13 +0200 Subject: [PATCH] Support MCA sound channel tags in MXF/CPL. --- examples/make_dcp.cc | 8 +- src/asset_reader.h | 4 + src/cpl.cc | 75 +++++++++- src/exceptions.cc | 7 +- src/exceptions.h | 7 + src/sound_asset.cc | 31 ++++- src/sound_asset.h | 12 +- src/sound_asset_writer.cc | 76 ++++++++-- src/sound_asset_writer.h | 5 +- src/types.cc | 238 +++++++++++++++++++++----------- src/types.h | 26 ++-- test/cpl_metadata_test.cc | 18 +-- test/dcp_test.cc | 9 +- test/encryption_test.cc | 4 +- test/mca_test.cc | 86 ++++++++++++ test/ref/cpl_metadata_test1.xml | 2 +- test/ref/cpl_metadata_test2.xml | 2 +- test/sync_test.cc | 6 +- test/test.cc | 14 +- test/test.h | 2 +- test/wscript | 1 + 21 files changed, 502 insertions(+), 131 deletions(-) create mode 100644 test/mca_test.cc diff --git a/examples/make_dcp.cc b/examples/make_dcp.cc index 0c7ebf77..734ae880 100644 --- a/examples/make_dcp.cc +++ b/examples/make_dcp.cc @@ -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 sound_asset (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 2, dcp::SMPTE)); - boost::shared_ptr sound_writer = sound_asset->start_write ("DCP/sound.mxf"); + boost::shared_ptr 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 active_channels; + active_channels.push_back (dcp::LEFT); + active_channels.push_back (dcp::RIGHT); + boost::shared_ptr sound_writer = sound_asset->start_write ("DCP/sound.mxf", active_channels); /* Write some sine waves */ float* audio[2]; diff --git a/src/asset_reader.h b/src/asset_reader.h index dbd2761b..714d960c 100644 --- a/src/asset_reader.h +++ b/src/asset_reader.h @@ -69,6 +69,10 @@ public: return boost::shared_ptr (new F (_reader, n, _crypto_context)); } + R* reader () const { + return _reader; + } + protected: R* _reader; boost::shared_ptr _crypto_context; diff --git a/src/cpl.cc b/src/cpl.cc index 494f53be..711c9e1b 100644 --- a/src/cpl.cc +++ b/src/cpl.cc @@ -46,6 +46,7 @@ #include "dcp_assert.h" #include "compose.hpp" #include "raw_convert.h" +#include #include #include #include @@ -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 asset = _reels.front()->main_sound()->asset(); + if (asset) { + shared_ptr reader = asset->start_read (); + ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield; + ASDCP::Result_t r = reader->reader()->OP1aHeader().GetMDObjectByType( + ASDCP::DefaultSMPTEDict().ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor), + reinterpret_cast(&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 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(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(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)); + } + } + } + } + } } diff --git a/src/exceptions.cc b/src/exceptions.cc index 16676e20..ebe8609a 100644 --- a/src/exceptions.cc +++ b/src/exceptions.cc @@ -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)) +{ + +} diff --git a/src/exceptions.h b/src/exceptions.h index 12624175..b9bcfd37 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -276,6 +276,13 @@ public: }; +class UnknownChannelIdError : public std::runtime_error +{ +public: + UnknownChannelIdError (std::string s); +}; + + } #endif diff --git a/src/sound_asset.cc b/src/sound_asset.cc index 7f2bf5e3..15626b9a 100644 --- a/src/sound_asset.cc +++ b/src/sound_asset.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2016 Carl Hetherington + Copyright (C) 2012-2020 Carl Hetherington This file is part of libdcp. @@ -43,8 +43,9 @@ #include "sound_asset_reader.h" #include "compose.hpp" #include "dcp_assert.h" -#include #include +#include +#include #include #include #include @@ -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(&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 other, EqualityOptions opt, NoteHand } shared_ptr -SoundAsset::start_write (boost::filesystem::path file, bool atmos_sync) +SoundAsset::start_write (boost::filesystem::path file, vector 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 (new SoundAssetWriter(this, file, atmos_sync)); + return shared_ptr (new SoundAssetWriter(this, file, active_channels, atmos_sync)); } shared_ptr diff --git a/src/sound_asset.h b/src/sound_asset.h index 9656cf90..80f1cfb6 100644 --- a/src/sound_asset.h +++ b/src/sound_asset.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2016 Carl Hetherington + Copyright (C) 2012-2020 Carl Hetherington 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 start_write (boost::filesystem::path file, bool atmos_sync = false); + boost::shared_ptr start_write (boost::filesystem::path file, std::vector active_channels, bool atmos_sync = false); boost::shared_ptr 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; }; } diff --git a/src/sound_asset_writer.cc b/src/sound_asset_writer.cc index ff1d02c2..3980dc62 100644 --- a/src/sound_asset_writer.cc +++ b/src/sound_asset_writer.cc @@ -39,6 +39,8 @@ #include "compose.hpp" #include "crypto_context.h" #include +#include +#include #include 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 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(&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(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 (); diff --git a/src/sound_asset_writer.h b/src/sound_asset_writer.h index f1b1514d..d3b260e6 100644 --- a/src/sound_asset_writer.h +++ b/src/sound_asset_writer.h @@ -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 active_channels, bool sync); + void start (); void write_current_frame (); std::vector 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 _active_channels; }; } diff --git a/src/types.cc b/src/types.cc index 9d67ee67..669d4fca 100644 --- a/src/types.cc +++ b/src/types.cc @@ -596,40 +596,14 @@ MainSoundConfiguration::MainSoundConfiguration (string s) BOOST_FOREACH (string i, channels) { if (i == "-") { _channels.push_back(optional()); - } 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); +} + + diff --git a/src/types.h b/src/types.h index 9a4b3e13..ed0b7b84 100644 --- a/src/types.h +++ b/src/types.h @@ -39,6 +39,7 @@ #define LIBDCP_TYPES_H #include +#include #include #include #include @@ -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 > _channels; }; diff --git a/test/cpl_metadata_test.cc b/test/cpl_metadata_test.cc index 216ef600..5082b8f6 100644 --- a/test/cpl_metadata_test.cc +++ b/test/cpl_metadata_test.cc @@ -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); diff --git a/test/dcp_test.cc b/test/dcp_test.cc index a6efe024..610b175e 100644 --- a/test/dcp_test.cc +++ b/test/dcp_test.cc @@ -52,6 +52,7 @@ #include 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 ms (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 1, dcp::SMPTE)); + shared_ptr ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::SMPTE)); ms->set_metadata (mxf_meta); - shared_ptr sound_writer = ms->start_write ("build/test/DCP/dcp_test2/audio.mxf"); + shared_ptr sound_writer = ms->start_write ("build/test/DCP/dcp_test2/audio.mxf", vector()); SF_INFO info; info.format = 0; @@ -199,9 +200,9 @@ BOOST_AUTO_TEST_CASE (dcp_test5) } picture_writer->finalize (); - shared_ptr ms (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 1, dcp::SMPTE)); + shared_ptr ms (new dcp::SoundAsset(dcp::Fraction(24, 1), 48000, 1, dcp::LanguageTag("en-GB"), dcp::SMPTE)); ms->set_metadata (mxf_meta); - shared_ptr sound_writer = ms->start_write ("build/test/DCP/dcp_test5/audio.mxf"); + shared_ptr sound_writer = ms->start_write ("build/test/DCP/dcp_test5/audio.mxf", vector()); SF_INFO info; info.format = 0; diff --git a/test/encryption_test.cc b/test/encryption_test.cc index c452c07b..2793e8ad 100644 --- a/test/encryption_test.cc +++ b/test/encryption_test.cc @@ -98,10 +98,10 @@ BOOST_AUTO_TEST_CASE (encryption_test) } writer->finalize (); - shared_ptr ms (new dcp::SoundAsset (dcp::Fraction (24, 1), 48000, 1, dcp::SMPTE)); + shared_ptr 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 sound_writer = ms->start_write ("build/test/DCP/encryption_test/audio.mxf"); + shared_ptr sound_writer = ms->start_write ("build/test/DCP/encryption_test/audio.mxf", vector()); SF_INFO info; info.format = 0; diff --git a/test/mca_test.cc b/test/mca_test.cc new file mode 100644 index 00000000..f1d05423 --- /dev/null +++ b/test/mca_test.cc @@ -0,0 +1,86 @@ +/* + Copyright (C) 2020 Carl Hetherington + + 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 . + + 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 +#include +#include + + +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 sound_asset(new dcp::SoundAsset(private_test / "data" / dcp::String::compose("51_sound_with_mca_%1.mxf", i))); + shared_ptr reel_sound_asset(new dcp::ReelSoundAsset(sound_asset, 0)); + shared_ptr 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()); + + 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 ignore; + check_xml ( + dynamic_cast( + ref.node_child("ReelList")->node_children("Reel").front()->node_child("AssetList")->node_child("CompositionMetadataAsset")->node_child("MCASubDescriptors")->node() + ), + dynamic_cast( + check.node_child("ReelList")->node_children("Reel").front()->node_child("AssetList")->node_child("CompositionMetadataAsset")->node_child("MCASubDescriptors")->node() + ), + ignore, + true + ); + } +} diff --git a/test/ref/cpl_metadata_test1.xml b/test/ref/cpl_metadata_test1.xml index b734edbb..5d49e765 100644 --- a/test/ref/cpl_metadata_test1.xml +++ b/test/ref/cpl_metadata_test1.xml @@ -55,7 +55,7 @@ 4.5 - 71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,Sync,-,- + 71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,FSKSync,-,- 48000 1 1998 diff --git a/test/ref/cpl_metadata_test2.xml b/test/ref/cpl_metadata_test2.xml index d61a7113..a7b20ff3 100644 --- a/test/ref/cpl_metadata_test2.xml +++ b/test/ref/cpl_metadata_test2.xml @@ -32,7 +32,7 @@ 24 1 24 - 71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,Sync,-,- + 71/L,R,C,LFE,-,-,-,-,-,-,-,-,-,FSKSync,-,- 48000 1 1998 diff --git a/test/sync_test.cc b/test/sync_test.cc index 9fe6a304..0bffdb83 100644 --- a/test/sync_test.cc +++ b/test/sync_test.cc @@ -109,7 +109,7 @@ BOOST_AUTO_TEST_CASE (sync_test1) } } - shared_ptr writer = asset.start_write ("build/test/foo.mxf", true); + shared_ptr writer = asset.start_write ("build/test/foo.mxf", vector(), 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 writer = asset.start_write ("build/test/foo.mxf", true); + shared_ptr writer = asset.start_write ("build/test/foo.mxf", vector(), true); int const frames = 2000; float** junk = new float*[channels]; diff --git a/test/test.cc b/test/test.cc index dd143264..62013687 100644 --- a/test/test.cc +++ b/test/test.cc @@ -95,12 +95,12 @@ struct TestConfig }; void -check_xml (xmlpp::Element* ref, xmlpp::Element* test, list ignore) +check_xml (xmlpp::Element* ref, xmlpp::Element* test, list 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 ignore) xmlpp::Element* test_el = dynamic_cast (*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 (*k); xmlpp::ContentNode* test_cn = dynamic_cast (*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; diff --git a/test/test.h b/test/test.h index 95827af7..9009a58b 100644 --- a/test/test.h +++ b/test/test.h @@ -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 ignore); +extern void check_xml (xmlpp::Element* ref, xmlpp::Element* test, std::list ignore_tags, bool ignore_whitespace = false); extern void check_xml (std::string ref, std::string test, std::list ignore); extern void check_file (boost::filesystem::path ref, boost::filesystem::path check); extern boost::shared_ptr simple_picture (boost::filesystem::path path, std::string suffix); diff --git a/test/wscript b/test/wscript index 5fd2f430..aa134bde 100644 --- a/test/wscript +++ b/test/wscript @@ -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 -- 2.30.2