40 #include "compose.hpp"
44 #include "filesystem.h"
51 LIBDCP_DISABLE_WARNINGS
52 #include <asdcp/AS_DCP.h>
53 #include <asdcp/KM_fileio.h>
54 #include <asdcp/Metadata.h>
55 LIBDCP_ENABLE_WARNINGS
56 #include <libxml++/nodes/element.h>
57 #include <boost/filesystem.hpp>
61 using std::dynamic_pointer_cast;
63 using std::shared_ptr;
66 using boost::optional;
70 SoundAsset::SoundAsset (boost::filesystem::path file)
73 Kumu::FileReaderFactory factory;
74 ASDCP::PCM::MXFReader reader(factory);
75 auto r = reader.OpenRead(dcp::filesystem::fix_long_path(file).
string().c_str());
76 if (ASDCP_FAILURE(r)) {
77 boost::throw_exception (
MXFFileError(
"could not open MXF file for reading", file.string(), r));
80 ASDCP::PCM::AudioDescriptor desc;
81 if (ASDCP_FAILURE (reader.FillAudioDescriptor(desc))) {
82 boost::throw_exception (
ReadError(
"could not read audio MXF information"));
85 _sampling_rate = desc.AudioSamplingRate.Denominator ? (desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator) : 0;
86 _bit_depth = desc.QuantizationBits;
87 _channels = desc.ChannelCount;
88 _edit_rate =
Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator);
90 _intrinsic_duration = desc.ContainerDuration;
92 ASDCP::WriterInfo info;
93 if (ASDCP_FAILURE (reader.FillWriterInfo(info))) {
94 boost::throw_exception (
ReadError(
"could not read audio MXF information"));
97 ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
98 auto rr = reader.OP1aHeader().GetMDObjectByType(
99 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
100 reinterpret_cast<ASDCP::MXF::InterchangeObject**
>(&soundfield)
103 if (KM_SUCCESS(rr)) {
104 if (!soundfield->RFC5646SpokenLanguage.empty()) {
106 soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer,
sizeof(buffer));
111 list<ASDCP::MXF::InterchangeObject*> channel_labels;
112 rr = reader.OP1aHeader().GetMDObjectsByType(
113 asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
117 if (KM_SUCCESS(rr)) {
118 _active_channels = channel_labels.size();
121 _id = read_writer_info (info);
125 SoundAsset::SoundAsset (
Fraction edit_rate,
int sampling_rate,
int channels,
LanguageTag language, Standard standard)
127 , _edit_rate (edit_rate)
128 , _channels (channels)
129 , _sampling_rate (sampling_rate)
130 , _language (language.to_string())
137 SoundAsset::equals(shared_ptr<const Asset> other,
EqualityOptions const& opt, NoteHandler note)
const
139 if (opt.sound_assets_can_differ) {
143 Kumu::FileReaderFactory factory;
144 ASDCP::PCM::MXFReader reader_A(factory);
146 auto r = reader_A.OpenRead(dcp::filesystem::fix_long_path(*
file()).
string().c_str());
147 if (ASDCP_FAILURE(r)) {
148 boost::throw_exception (
MXFFileError(
"could not open MXF file for reading",
file()->
string(), r));
151 ASDCP::PCM::MXFReader reader_B(factory);
152 r = reader_B.OpenRead(dcp::filesystem::fix_long_path(*other->file()).string().c_str());
153 if (ASDCP_FAILURE (r)) {
154 boost::throw_exception (
MXFFileError(
"could not open MXF file for reading", other->file()->string(), r));
157 ASDCP::PCM::AudioDescriptor desc_A;
158 if (ASDCP_FAILURE (reader_A.FillAudioDescriptor(desc_A))) {
159 boost::throw_exception (
ReadError (
"could not read audio MXF information"));
161 ASDCP::PCM::AudioDescriptor desc_B;
162 if (ASDCP_FAILURE (reader_B.FillAudioDescriptor(desc_B))) {
163 boost::throw_exception (
ReadError (
"could not read audio MXF information"));
166 if (desc_A.EditRate != desc_B.EditRate) {
170 "audio edit rates differ: %1/%2 cf %3/%4",
171 desc_A.EditRate.Numerator, desc_A.EditRate.Denominator, desc_B.EditRate.Numerator, desc_B.EditRate.Denominator
175 }
else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
179 "audio sampling rates differ: %1 cf %2",
180 desc_A.AudioSamplingRate.Numerator, desc_A.AudioSamplingRate.Denominator,
181 desc_B.AudioSamplingRate.Numerator, desc_B.AudioSamplingRate.Numerator
185 }
else if (desc_A.Locked != desc_B.Locked) {
186 note (NoteType::ERROR, String::compose (
"audio locked flags differ: %1 cf %2", desc_A.Locked, desc_B.Locked));
188 }
else if (desc_A.ChannelCount != desc_B.ChannelCount) {
189 note (NoteType::ERROR, String::compose (
"audio channel counts differ: %1 cf %2", desc_A.ChannelCount, desc_B.ChannelCount));
191 }
else if (desc_A.QuantizationBits != desc_B.QuantizationBits) {
192 note (NoteType::ERROR, String::compose (
"audio bits per sample differ: %1 cf %2", desc_A.QuantizationBits, desc_B.QuantizationBits));
194 }
else if (desc_A.BlockAlign != desc_B.BlockAlign) {
195 note (NoteType::ERROR, String::compose (
"audio bytes per sample differ: %1 cf %2", desc_A.BlockAlign, desc_B.BlockAlign));
197 }
else if (desc_A.AvgBps != desc_B.AvgBps) {
198 note (NoteType::ERROR, String::compose (
"audio average bps differ: %1 cf %2", desc_A.AvgBps, desc_B.AvgBps));
200 }
else if (desc_A.LinkedTrackID != desc_B.LinkedTrackID) {
201 note (NoteType::ERROR, String::compose (
"audio linked track IDs differ: %1 cf %2", desc_A.LinkedTrackID, desc_B.LinkedTrackID));
203 }
else if (desc_A.ContainerDuration != desc_B.ContainerDuration) {
204 note (NoteType::ERROR, String::compose (
"audio container durations differ: %1 cf %2", desc_A.ContainerDuration, desc_B.ContainerDuration));
206 }
else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
210 auto other_sound = dynamic_pointer_cast<const SoundAsset> (other);
212 auto reader = start_read ();
213 auto other_reader = other_sound->start_read ();
217 auto frame_A = reader->get_frame (i);
218 auto frame_B = other_reader->get_frame (i);
220 if (frame_A->size() != frame_B->size()) {
221 note (NoteType::ERROR, String::compose (
"sizes of audio data for frame %1 differ", i));
225 if (memcmp (frame_A->data(), frame_B->data(), frame_A->size()) != 0) {
226 for (
int sample = 0; sample < frame_A->samples(); ++sample) {
227 for (
int channel = 0; channel < frame_A->channels(); ++channel) {
228 int32_t
const d = abs(frame_A->get(channel, sample) - frame_B->get(channel, sample));
230 note (NoteType::ERROR, String::compose(
"PCM data difference of %1 in frame %2, channel %3, sample %4", d, i, channel, sample));
242 shared_ptr<SoundAssetWriter>
244 boost::filesystem::path file,
245 vector<dcp::Channel> extra_active_channels,
246 AtmosSync atmos_sync,
247 MCASubDescriptors include_mca_subdescriptors
250 if (atmos_sync == AtmosSync::ENABLED &&
_channels < 14) {
251 throw MiscError (
"Insufficient channels to write ATMOS sync (there must be at least 14)");
254 return shared_ptr<SoundAssetWriter>(
255 new SoundAssetWriter(
this,
file, extra_active_channels, atmos_sync == AtmosSync::ENABLED, include_mca_subdescriptors == MCASubDescriptors::ENABLED)
260 shared_ptr<SoundAssetReader>
261 SoundAsset::start_read ()
const
268 SoundAsset::static_pkl_type (Standard standard)
271 case Standard::INTEROP:
272 return "application/x-smpte-mxf;asdcpKind=Sound";
273 case Standard::SMPTE:
274 return "application/mxf";
282 SoundAsset::valid_mxf (boost::filesystem::path file)
284 Kumu::FileReaderFactory factory;
285 ASDCP::PCM::MXFReader reader(factory);
286 Kumu::Result_t r = reader.OpenRead(dcp::filesystem::fix_long_path(
file).
string().c_str());
287 return !ASDCP_FAILURE (r);
Parent class for DCP assets, i.e. picture, sound, subtitles, closed captions, CPLs,...
boost::optional< boost::filesystem::path > file() const
A class to describe what "equality" means for a particular test.
int max_audio_sample_error
A fraction (i.e. a thing with an integer numerator and an integer denominator).
An exception related to an MXF file.
Parent for classes which represent MXF files.
boost::optional< Key > key() const
A miscellaneous exception.
Any error that occurs when reading data from a DCP.
A helper class for writing to SoundAssets.
int active_channels() const
int _channels
number of channels in the MXF
boost::optional< int > _active_channels
estimate of the number of active channels
std::shared_ptr< SoundAssetWriter > start_write(boost::filesystem::path file, std::vector< dcp::Channel > extra_active_channels, AtmosSync atmos_sync, MCASubDescriptors mca_subdescriptors)
int64_t _intrinsic_duration
Class to describe what equality means when calling Asset::equals().
Exceptions thrown by libdcp.
Namespace for everything in libdcp.
SoundAssetReader typedef.
Utility methods and classes.