/* 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];
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;
#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>
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 */
}
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));
+ }
+ }
+ }
+ }
+ }
}
}
->>>>>>> Support CPL metadata.
+
+UnknownChannelIdError::UnknownChannelIdError (std::string id)
+ : runtime_error (String::compose("Unrecognised channel id '%1'", id))
+{
+
+}
};
+class UnknownChannelIdError : public std::runtime_error
+{
+public:
+ UnknownChannelIdError (std::string s);
+};
+
+
}
#endif
/*
- Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
#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>
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());
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)
{
}
}
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>
/*
- Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
#include "mxf.h"
#include "types.h"
+#include "language_tag.h"
#include "metadata.h"
#include "sound_frame.h"
#include "sound_asset_reader.h"
{
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 (
return _intrinsic_duration;
}
+ LanguageTag language () const {
+ return _language;
+ }
+
static bool valid_mxf (boost::filesystem::path);
static std::string static_pkl_type (Standard standard);
int64_t _intrinsic_duration;
int _channels; ///< number of channels
int _sampling_rate; ///< sampling rate in Hz
+ LanguageTag _language;
};
}
#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;
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;
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);
}
}
+
+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)
{
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 ();
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 ();
/** index of the sync packet (0-3) which starts the next edit unit */
int _sync_packet;
FSK _fsk;
+
+ std::vector<Channel> _active_channels;
};
}
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);
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) + ",";
}
}
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);
+}
+
+
#define LIBDCP_TYPES_H
#include <libcxml/cxml.h>
+#include <asdcp/KLV.h>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#include <string>
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,
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;
}
std::string to_string () const;
private:
- Field _field;
+ MCASoundField _field;
std::vector<boost::optional<Channel> > _channels;
};
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);
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);
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);
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);
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);
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);
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);
#include <boost/test/unit_test.hpp>
using std::string;
+using std::vector;
using boost::shared_ptr;
}
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;
}
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;
}
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;
--- /dev/null
+/*
+ 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
+ );
+ }
+}
</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>
<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>
}
}
- 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());
{
/* 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];
};
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;
}
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;
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);
local_time_test.cc
make_digest_test.cc
markers_test.cc
+ mca_test.cc
kdm_test.cc
key_test.cc
language_tag_test.cc