Cleanup: pass EqualityOptions as const&
[libdcp.git] / src / sound_asset.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34
35 /** @file  src/sound_asset.cc
36  *  @brief SoundAsset class
37  */
38
39
40 #include "compose.hpp"
41 #include "dcp_assert.h"
42 #include "exceptions.h"
43 #include "sound_asset.h"
44 #include "sound_asset_reader.h"
45 #include "sound_asset_writer.h"
46 #include "sound_frame.h"
47 #include "util.h"
48 #include "warnings.h"
49 LIBDCP_DISABLE_WARNINGS
50 #include <asdcp/AS_DCP.h>
51 #include <asdcp/KM_fileio.h>
52 #include <asdcp/Metadata.h>
53 LIBDCP_ENABLE_WARNINGS
54 #include <libxml++/nodes/element.h>
55 #include <boost/filesystem.hpp>
56 #include <stdexcept>
57
58
59 using std::dynamic_pointer_cast;
60 using std::list;
61 using std::shared_ptr;
62 using std::string;
63 using std::vector;
64 using boost::optional;
65 using namespace dcp;
66
67
68 SoundAsset::SoundAsset (boost::filesystem::path file)
69         : Asset (file)
70 {
71         ASDCP::PCM::MXFReader reader;
72         auto r = reader.OpenRead (file.string().c_str());
73         if (ASDCP_FAILURE(r)) {
74                 boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r));
75         }
76
77         ASDCP::PCM::AudioDescriptor desc;
78         if (ASDCP_FAILURE (reader.FillAudioDescriptor(desc))) {
79                 boost::throw_exception (ReadError("could not read audio MXF information"));
80         }
81
82         _sampling_rate = desc.AudioSamplingRate.Denominator ? (desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator) : 0;
83         _channels = desc.ChannelCount;
84         _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator);
85
86         _intrinsic_duration = desc.ContainerDuration;
87
88         ASDCP::WriterInfo info;
89         if (ASDCP_FAILURE (reader.FillWriterInfo(info))) {
90                 boost::throw_exception (ReadError("could not read audio MXF information"));
91         }
92
93         ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
94         auto rr = reader.OP1aHeader().GetMDObjectByType(
95                 asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
96                 reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
97                 );
98
99         if (KM_SUCCESS(rr)) {
100                 if (!soundfield->RFC5646SpokenLanguage.empty()) {
101                         char buffer[64];
102                         soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
103                         _language = buffer;
104                 }
105         }
106
107         list<ASDCP::MXF::InterchangeObject*> channel_labels;
108         rr = reader.OP1aHeader().GetMDObjectsByType(
109                 asdcp_smpte_dict->ul(ASDCP::MDD_AudioChannelLabelSubDescriptor),
110                 channel_labels
111                 );
112
113         if (KM_SUCCESS(rr)) {
114                 _active_channels = channel_labels.size();
115         }
116
117         _id = read_writer_info (info);
118 }
119
120
121 SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, LanguageTag language, Standard standard)
122         : MXF (standard)
123         , _edit_rate (edit_rate)
124         , _channels (channels)
125         , _sampling_rate (sampling_rate)
126         , _language (language.to_string())
127 {
128
129 }
130
131
132 bool
133 SoundAsset::equals(shared_ptr<const Asset> other, EqualityOptions const& opt, NoteHandler note) const
134 {
135         ASDCP::PCM::MXFReader reader_A;
136         DCP_ASSERT (file());
137         auto r = reader_A.OpenRead (file()->string().c_str());
138         if (ASDCP_FAILURE(r)) {
139                 boost::throw_exception (MXFFileError("could not open MXF file for reading", file()->string(), r));
140         }
141
142         ASDCP::PCM::MXFReader reader_B;
143         r = reader_B.OpenRead (other->file()->string().c_str());
144         if (ASDCP_FAILURE (r)) {
145                 boost::throw_exception (MXFFileError("could not open MXF file for reading", other->file()->string(), r));
146         }
147
148         ASDCP::PCM::AudioDescriptor desc_A;
149         if (ASDCP_FAILURE (reader_A.FillAudioDescriptor(desc_A))) {
150                 boost::throw_exception (ReadError ("could not read audio MXF information"));
151         }
152         ASDCP::PCM::AudioDescriptor desc_B;
153         if (ASDCP_FAILURE (reader_B.FillAudioDescriptor(desc_B))) {
154                 boost::throw_exception (ReadError ("could not read audio MXF information"));
155         }
156
157         if (desc_A.EditRate != desc_B.EditRate) {
158                 note (
159                         NoteType::ERROR,
160                         String::compose (
161                                 "audio edit rates differ: %1/%2 cf %3/%4",
162                                 desc_A.EditRate.Numerator, desc_A.EditRate.Denominator, desc_B.EditRate.Numerator, desc_B.EditRate.Denominator
163                                 )
164                         );
165                 return false;
166         } else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
167                 note (
168                         NoteType::ERROR,
169                         String::compose (
170                                 "audio sampling rates differ: %1 cf %2",
171                                 desc_A.AudioSamplingRate.Numerator, desc_A.AudioSamplingRate.Denominator,
172                                 desc_B.AudioSamplingRate.Numerator, desc_B.AudioSamplingRate.Numerator
173                                 )
174                         );
175                 return false;
176         } else if (desc_A.Locked != desc_B.Locked) {
177                 note (NoteType::ERROR, String::compose ("audio locked flags differ: %1 cf %2", desc_A.Locked, desc_B.Locked));
178                 return false;
179         } else if (desc_A.ChannelCount != desc_B.ChannelCount) {
180                 note (NoteType::ERROR, String::compose ("audio channel counts differ: %1 cf %2", desc_A.ChannelCount, desc_B.ChannelCount));
181                 return false;
182         } else if (desc_A.QuantizationBits != desc_B.QuantizationBits) {
183                 note (NoteType::ERROR, String::compose ("audio bits per sample differ: %1 cf %2", desc_A.QuantizationBits, desc_B.QuantizationBits));
184                 return false;
185         } else if (desc_A.BlockAlign != desc_B.BlockAlign) {
186                 note (NoteType::ERROR, String::compose ("audio bytes per sample differ: %1 cf %2", desc_A.BlockAlign, desc_B.BlockAlign));
187                 return false;
188         } else if (desc_A.AvgBps != desc_B.AvgBps) {
189                 note (NoteType::ERROR, String::compose ("audio average bps differ: %1 cf %2", desc_A.AvgBps, desc_B.AvgBps));
190                 return false;
191         } else if (desc_A.LinkedTrackID != desc_B.LinkedTrackID) {
192                 note (NoteType::ERROR, String::compose ("audio linked track IDs differ: %1 cf %2", desc_A.LinkedTrackID, desc_B.LinkedTrackID));
193                 return false;
194         } else if (desc_A.ContainerDuration != desc_B.ContainerDuration) {
195                 note (NoteType::ERROR, String::compose ("audio container durations differ: %1 cf %2", desc_A.ContainerDuration, desc_B.ContainerDuration));
196                 return false;
197         } else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
198                 /* XXX */
199         }
200
201         auto other_sound = dynamic_pointer_cast<const SoundAsset> (other);
202
203         auto reader = start_read ();
204         auto other_reader = other_sound->start_read ();
205
206         for (int i = 0; i < _intrinsic_duration; ++i) {
207
208                 auto frame_A = reader->get_frame (i);
209                 auto frame_B = other_reader->get_frame (i);
210
211                 if (frame_A->size() != frame_B->size()) {
212                         note (NoteType::ERROR, String::compose ("sizes of audio data for frame %1 differ", i));
213                         return false;
214                 }
215
216                 if (memcmp (frame_A->data(), frame_B->data(), frame_A->size()) != 0) {
217                         for (int sample = 0; sample < frame_A->samples(); ++sample) {
218                                 for (int channel = 0; channel < frame_A->channels(); ++channel) {
219                                         int32_t const d = abs(frame_A->get(channel, sample) - frame_B->get(channel, sample));
220                                         if (d > opt.max_audio_sample_error) {
221                                                 note (NoteType::ERROR, String::compose("PCM data difference of %1 in frame %2, channel %3, sample %4", d, i, channel, sample));
222                                                 return false;
223                                         }
224                                 }
225                         }
226                 }
227         }
228
229         return true;
230 }
231
232
233 shared_ptr<SoundAssetWriter>
234 SoundAsset::start_write(
235         boost::filesystem::path file,
236         vector<dcp::Channel> extra_active_channels,
237         AtmosSync atmos_sync,
238         MCASubDescriptors include_mca_subdescriptors
239         )
240 {
241         if (atmos_sync == AtmosSync::ENABLED && _channels < 14) {
242                 throw MiscError ("Insufficient channels to write ATMOS sync (there must be at least 14)");
243         }
244
245         return shared_ptr<SoundAssetWriter>(
246                 new SoundAssetWriter(this, file, extra_active_channels, atmos_sync == AtmosSync::ENABLED, include_mca_subdescriptors == MCASubDescriptors::ENABLED)
247                 );
248 }
249
250
251 shared_ptr<SoundAssetReader>
252 SoundAsset::start_read () const
253 {
254         return shared_ptr<SoundAssetReader> (new SoundAssetReader(this, key(), standard()));
255 }
256
257
258 string
259 SoundAsset::static_pkl_type (Standard standard)
260 {
261         switch (standard) {
262         case Standard::INTEROP:
263                 return "application/x-smpte-mxf;asdcpKind=Sound";
264         case Standard::SMPTE:
265                 return "application/mxf";
266         default:
267                 DCP_ASSERT (false);
268         }
269 }
270
271
272 bool
273 SoundAsset::valid_mxf (boost::filesystem::path file)
274 {
275         ASDCP::PCM::MXFReader reader;
276         Kumu::Result_t r = reader.OpenRead (file.string().c_str());
277         return !ASDCP_FAILURE (r);
278 }
279
280
281 int
282 SoundAsset::active_channels() const
283 {
284         return _active_channels.get_value_or(_channels);
285 }
286