2 Copyright (C) 2012-2020 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.
34 /** @file src/sound_mxf.cc
35 * @brief SoundAsset class.
38 #include "sound_asset.h"
40 #include "exceptions.h"
41 #include "sound_frame.h"
42 #include "sound_asset_writer.h"
43 #include "sound_asset_reader.h"
44 #include "compose.hpp"
45 #include "dcp_assert.h"
46 #include <asdcp/AS_DCP.h>
47 #include <asdcp/KM_fileio.h>
48 #include <asdcp/Metadata.h>
49 #include <libxml++/nodes/element.h>
50 #include <boost/filesystem.hpp>
56 using std::shared_ptr;
57 using std::dynamic_pointer_cast;
61 SoundAsset::SoundAsset (boost::filesystem::path file)
63 /* XXX: this is a fallback language, which will be used if we can't find the RFC5646SpokenLanguage
64 * in the MXF header. Perhaps RFC5646SpokenLanguage is optional and we should just not write it
65 * if we don't know it.
69 ASDCP::PCM::MXFReader reader;
70 Kumu::Result_t r = reader.OpenRead (file.string().c_str());
71 if (ASDCP_FAILURE (r)) {
72 boost::throw_exception (MXFFileError ("could not open MXF file for reading", file.string(), r));
75 ASDCP::PCM::AudioDescriptor desc;
76 if (ASDCP_FAILURE (reader.FillAudioDescriptor (desc))) {
77 boost::throw_exception (ReadError ("could not read audio MXF information"));
80 _sampling_rate = desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator;
81 _channels = desc.ChannelCount;
82 _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator);
84 _intrinsic_duration = desc.ContainerDuration;
86 ASDCP::WriterInfo info;
87 if (ASDCP_FAILURE (reader.FillWriterInfo (info))) {
88 boost::throw_exception (ReadError ("could not read audio MXF information"));
91 ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
92 ASDCP::Result_t rr = reader.OP1aHeader().GetMDObjectByType(
93 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
94 reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
98 if (!soundfield->RFC5646SpokenLanguage.empty()) {
100 soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
105 _id = read_writer_info (info);
108 SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, LanguageTag language, Standard standard)
110 , _edit_rate (edit_rate)
111 , _intrinsic_duration (0)
112 , _channels (channels)
113 , _sampling_rate (sampling_rate)
114 , _language (language.to_string())
120 SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
122 ASDCP::PCM::MXFReader reader_A;
123 DCP_ASSERT (file ());
124 Kumu::Result_t r = reader_A.OpenRead (file()->string().c_str());
125 if (ASDCP_FAILURE (r)) {
126 boost::throw_exception (MXFFileError ("could not open MXF file for reading", file()->string(), r));
129 ASDCP::PCM::MXFReader reader_B;
130 r = reader_B.OpenRead (other->file()->string().c_str());
131 if (ASDCP_FAILURE (r)) {
132 boost::throw_exception (MXFFileError ("could not open MXF file for reading", other->file()->string(), r));
135 ASDCP::PCM::AudioDescriptor desc_A;
136 if (ASDCP_FAILURE (reader_A.FillAudioDescriptor (desc_A))) {
137 boost::throw_exception (ReadError ("could not read audio MXF information"));
139 ASDCP::PCM::AudioDescriptor desc_B;
140 if (ASDCP_FAILURE (reader_B.FillAudioDescriptor (desc_B))) {
141 boost::throw_exception (ReadError ("could not read audio MXF information"));
144 if (desc_A.EditRate != desc_B.EditRate) {
148 "audio edit rates differ: %1/%2 cf %3/%4",
149 desc_A.EditRate.Numerator, desc_A.EditRate.Denominator, desc_B.EditRate.Numerator, desc_B.EditRate.Denominator
153 } else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
157 "audio sampling rates differ: %1 cf %2",
158 desc_A.AudioSamplingRate.Numerator, desc_A.AudioSamplingRate.Denominator,
159 desc_B.AudioSamplingRate.Numerator, desc_B.AudioSamplingRate.Numerator
163 } else if (desc_A.Locked != desc_B.Locked) {
164 note (DCP_ERROR, String::compose ("audio locked flags differ: %1 cf %2", desc_A.Locked, desc_B.Locked));
166 } else if (desc_A.ChannelCount != desc_B.ChannelCount) {
167 note (DCP_ERROR, String::compose ("audio channel counts differ: %1 cf %2", desc_A.ChannelCount, desc_B.ChannelCount));
169 } else if (desc_A.QuantizationBits != desc_B.QuantizationBits) {
170 note (DCP_ERROR, String::compose ("audio bits per sample differ: %1 cf %2", desc_A.QuantizationBits, desc_B.QuantizationBits));
172 } else if (desc_A.BlockAlign != desc_B.BlockAlign) {
173 note (DCP_ERROR, String::compose ("audio bytes per sample differ: %1 cf %2", desc_A.BlockAlign, desc_B.BlockAlign));
175 } else if (desc_A.AvgBps != desc_B.AvgBps) {
176 note (DCP_ERROR, String::compose ("audio average bps differ: %1 cf %2", desc_A.AvgBps, desc_B.AvgBps));
178 } else if (desc_A.LinkedTrackID != desc_B.LinkedTrackID) {
179 note (DCP_ERROR, String::compose ("audio linked track IDs differ: %1 cf %2", desc_A.LinkedTrackID, desc_B.LinkedTrackID));
181 } else if (desc_A.ContainerDuration != desc_B.ContainerDuration) {
182 note (DCP_ERROR, String::compose ("audio container durations differ: %1 cf %2", desc_A.ContainerDuration, desc_B.ContainerDuration));
184 } else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
188 shared_ptr<const SoundAsset> other_sound = dynamic_pointer_cast<const SoundAsset> (other);
190 shared_ptr<const SoundAssetReader> reader = start_read ();
191 shared_ptr<const SoundAssetReader> other_reader = other_sound->start_read ();
193 for (int i = 0; i < _intrinsic_duration; ++i) {
195 shared_ptr<const SoundFrame> frame_A = reader->get_frame (i);
196 shared_ptr<const SoundFrame> frame_B = other_reader->get_frame (i);
198 if (frame_A->size() != frame_B->size()) {
199 note (DCP_ERROR, String::compose ("sizes of audio data for frame %1 differ", i));
203 if (memcmp (frame_A->data(), frame_B->data(), frame_A->size()) != 0) {
204 for (int sample= 0; sample < frame_A->samples(); ++sample) {
205 for (int channel = 0; channel < frame_A->channels(); ++channel) {
206 int32_t const d = abs(frame_A->get(channel, sample) - frame_B->get(channel, sample));
207 if (d > opt.max_audio_sample_error) {
208 note (DCP_ERROR, String::compose ("PCM data difference of %1", d));
219 shared_ptr<SoundAssetWriter>
220 SoundAsset::start_write (boost::filesystem::path file, vector<Channel> active_channels, bool atmos_sync)
222 if (atmos_sync && _channels < 14) {
223 throw MiscError ("Insufficient channels to write ATMOS sync (there must be at least 14)");
226 return shared_ptr<SoundAssetWriter> (new SoundAssetWriter(this, file, active_channels, atmos_sync));
229 shared_ptr<SoundAssetReader>
230 SoundAsset::start_read () const
232 return shared_ptr<SoundAssetReader> (new SoundAssetReader (this, key(), standard()));
236 SoundAsset::static_pkl_type (Standard standard)
240 return "application/x-smpte-mxf;asdcpKind=Sound";
242 return "application/mxf";
249 SoundAsset::valid_mxf (boost::filesystem::path file)
251 ASDCP::PCM::MXFReader reader;
252 Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
253 return !ASDCP_FAILURE (r);