2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
35 /** @file src/sound_asset.cc
36 * @brief SoundAsset class
40 #include "sound_asset.h"
42 #include "exceptions.h"
43 #include "sound_frame.h"
44 #include "sound_asset_writer.h"
45 #include "sound_asset_reader.h"
46 #include "compose.hpp"
47 #include "dcp_assert.h"
48 #include <asdcp/AS_DCP.h>
49 #include <asdcp/KM_fileio.h>
50 #include <asdcp/Metadata.h>
51 #include <libxml++/nodes/element.h>
52 #include <boost/filesystem.hpp>
59 using std::shared_ptr;
60 using std::dynamic_pointer_cast;
64 SoundAsset::SoundAsset (boost::filesystem::path file)
66 /* XXX: this is a fallback language, which will be used if we can't find the RFC5646SpokenLanguage
67 * in the MXF header. Perhaps RFC5646SpokenLanguage is optional and we should just not write it
68 * if we don't know it.
72 ASDCP::PCM::MXFReader reader;
73 auto r = reader.OpenRead (file.string().c_str());
74 if (ASDCP_FAILURE(r)) {
75 boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r));
78 ASDCP::PCM::AudioDescriptor desc;
79 if (ASDCP_FAILURE (reader.FillAudioDescriptor(desc))) {
80 boost::throw_exception (ReadError("could not read audio MXF information"));
83 _sampling_rate = desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator;
84 _channels = desc.ChannelCount;
85 _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator);
87 _intrinsic_duration = desc.ContainerDuration;
89 ASDCP::WriterInfo info;
90 if (ASDCP_FAILURE (reader.FillWriterInfo(info))) {
91 boost::throw_exception (ReadError("could not read audio MXF information"));
94 ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
95 auto rr = reader.OP1aHeader().GetMDObjectByType(
96 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
97 reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
100 if (KM_SUCCESS(rr)) {
101 if (!soundfield->RFC5646SpokenLanguage.empty()) {
103 soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
108 _id = read_writer_info (info);
112 SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, LanguageTag language, Standard standard)
114 , _edit_rate (edit_rate)
115 , _channels (channels)
116 , _sampling_rate (sampling_rate)
117 , _language (language.to_string())
124 SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
126 ASDCP::PCM::MXFReader reader_A;
128 auto r = reader_A.OpenRead (file()->string().c_str());
129 if (ASDCP_FAILURE(r)) {
130 boost::throw_exception (MXFFileError("could not open MXF file for reading", file()->string(), r));
133 ASDCP::PCM::MXFReader reader_B;
134 r = reader_B.OpenRead (other->file()->string().c_str());
135 if (ASDCP_FAILURE (r)) {
136 boost::throw_exception (MXFFileError("could not open MXF file for reading", other->file()->string(), r));
139 ASDCP::PCM::AudioDescriptor desc_A;
140 if (ASDCP_FAILURE (reader_A.FillAudioDescriptor(desc_A))) {
141 boost::throw_exception (ReadError ("could not read audio MXF information"));
143 ASDCP::PCM::AudioDescriptor desc_B;
144 if (ASDCP_FAILURE (reader_B.FillAudioDescriptor(desc_B))) {
145 boost::throw_exception (ReadError ("could not read audio MXF information"));
148 if (desc_A.EditRate != desc_B.EditRate) {
152 "audio edit rates differ: %1/%2 cf %3/%4",
153 desc_A.EditRate.Numerator, desc_A.EditRate.Denominator, desc_B.EditRate.Numerator, desc_B.EditRate.Denominator
157 } else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
161 "audio sampling rates differ: %1 cf %2",
162 desc_A.AudioSamplingRate.Numerator, desc_A.AudioSamplingRate.Denominator,
163 desc_B.AudioSamplingRate.Numerator, desc_B.AudioSamplingRate.Numerator
167 } else if (desc_A.Locked != desc_B.Locked) {
168 note (NoteType::ERROR, String::compose ("audio locked flags differ: %1 cf %2", desc_A.Locked, desc_B.Locked));
170 } else if (desc_A.ChannelCount != desc_B.ChannelCount) {
171 note (NoteType::ERROR, String::compose ("audio channel counts differ: %1 cf %2", desc_A.ChannelCount, desc_B.ChannelCount));
173 } else if (desc_A.QuantizationBits != desc_B.QuantizationBits) {
174 note (NoteType::ERROR, String::compose ("audio bits per sample differ: %1 cf %2", desc_A.QuantizationBits, desc_B.QuantizationBits));
176 } else if (desc_A.BlockAlign != desc_B.BlockAlign) {
177 note (NoteType::ERROR, String::compose ("audio bytes per sample differ: %1 cf %2", desc_A.BlockAlign, desc_B.BlockAlign));
179 } else if (desc_A.AvgBps != desc_B.AvgBps) {
180 note (NoteType::ERROR, String::compose ("audio average bps differ: %1 cf %2", desc_A.AvgBps, desc_B.AvgBps));
182 } else if (desc_A.LinkedTrackID != desc_B.LinkedTrackID) {
183 note (NoteType::ERROR, String::compose ("audio linked track IDs differ: %1 cf %2", desc_A.LinkedTrackID, desc_B.LinkedTrackID));
185 } else if (desc_A.ContainerDuration != desc_B.ContainerDuration) {
186 note (NoteType::ERROR, String::compose ("audio container durations differ: %1 cf %2", desc_A.ContainerDuration, desc_B.ContainerDuration));
188 } else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
192 auto other_sound = dynamic_pointer_cast<const SoundAsset> (other);
194 auto reader = start_read ();
195 auto other_reader = other_sound->start_read ();
197 for (int i = 0; i < _intrinsic_duration; ++i) {
199 auto frame_A = reader->get_frame (i);
200 auto frame_B = other_reader->get_frame (i);
202 if (frame_A->size() != frame_B->size()) {
203 note (NoteType::ERROR, String::compose ("sizes of audio data for frame %1 differ", i));
207 if (memcmp (frame_A->data(), frame_B->data(), frame_A->size()) != 0) {
208 for (int sample = 0; sample < frame_A->samples(); ++sample) {
209 for (int channel = 0; channel < frame_A->channels(); ++channel) {
210 int32_t const d = abs(frame_A->get(channel, sample) - frame_B->get(channel, sample));
211 if (d > opt.max_audio_sample_error) {
212 note (NoteType::ERROR, String::compose ("PCM data difference of %1", d));
224 shared_ptr<SoundAssetWriter>
225 SoundAsset::start_write (boost::filesystem::path file, vector<Channel> active_channels, bool atmos_sync)
227 if (atmos_sync && _channels < 14) {
228 throw MiscError ("Insufficient channels to write ATMOS sync (there must be at least 14)");
231 return shared_ptr<SoundAssetWriter> (new SoundAssetWriter(this, file, active_channels, atmos_sync));
235 shared_ptr<SoundAssetReader>
236 SoundAsset::start_read () const
238 return shared_ptr<SoundAssetReader> (new SoundAssetReader(this, key(), standard()));
243 SoundAsset::static_pkl_type (Standard standard)
246 case Standard::INTEROP:
247 return "application/x-smpte-mxf;asdcpKind=Sound";
248 case Standard::SMPTE:
249 return "application/mxf";
257 SoundAsset::valid_mxf (boost::filesystem::path file)
259 ASDCP::PCM::MXFReader reader;
260 Kumu::Result_t r = reader.OpenRead (file.string().c_str());
261 return !ASDCP_FAILURE (r);