Allow specification of channels that need a MCASubDescriptor.
[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         _id = read_writer_info (info);
108 }
109
110
111 SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, LanguageTag language, Standard standard)
112         : MXF (standard)
113         , _edit_rate (edit_rate)
114         , _channels (channels)
115         , _sampling_rate (sampling_rate)
116         , _language (language.to_string())
117 {
118
119 }
120
121
122 bool
123 SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
124 {
125         ASDCP::PCM::MXFReader reader_A;
126         DCP_ASSERT (file());
127         auto r = reader_A.OpenRead (file()->string().c_str());
128         if (ASDCP_FAILURE(r)) {
129                 boost::throw_exception (MXFFileError("could not open MXF file for reading", file()->string(), r));
130         }
131
132         ASDCP::PCM::MXFReader reader_B;
133         r = reader_B.OpenRead (other->file()->string().c_str());
134         if (ASDCP_FAILURE (r)) {
135                 boost::throw_exception (MXFFileError("could not open MXF file for reading", other->file()->string(), r));
136         }
137
138         ASDCP::PCM::AudioDescriptor desc_A;
139         if (ASDCP_FAILURE (reader_A.FillAudioDescriptor(desc_A))) {
140                 boost::throw_exception (ReadError ("could not read audio MXF information"));
141         }
142         ASDCP::PCM::AudioDescriptor desc_B;
143         if (ASDCP_FAILURE (reader_B.FillAudioDescriptor(desc_B))) {
144                 boost::throw_exception (ReadError ("could not read audio MXF information"));
145         }
146
147         if (desc_A.EditRate != desc_B.EditRate) {
148                 note (
149                         NoteType::ERROR,
150                         String::compose (
151                                 "audio edit rates differ: %1/%2 cf %3/%4",
152                                 desc_A.EditRate.Numerator, desc_A.EditRate.Denominator, desc_B.EditRate.Numerator, desc_B.EditRate.Denominator
153                                 )
154                         );
155                 return false;
156         } else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
157                 note (
158                         NoteType::ERROR,
159                         String::compose (
160                                 "audio sampling rates differ: %1 cf %2",
161                                 desc_A.AudioSamplingRate.Numerator, desc_A.AudioSamplingRate.Denominator,
162                                 desc_B.AudioSamplingRate.Numerator, desc_B.AudioSamplingRate.Numerator
163                                 )
164                         );
165                 return false;
166         } else if (desc_A.Locked != desc_B.Locked) {
167                 note (NoteType::ERROR, String::compose ("audio locked flags differ: %1 cf %2", desc_A.Locked, desc_B.Locked));
168                 return false;
169         } else if (desc_A.ChannelCount != desc_B.ChannelCount) {
170                 note (NoteType::ERROR, String::compose ("audio channel counts differ: %1 cf %2", desc_A.ChannelCount, desc_B.ChannelCount));
171                 return false;
172         } else if (desc_A.QuantizationBits != desc_B.QuantizationBits) {
173                 note (NoteType::ERROR, String::compose ("audio bits per sample differ: %1 cf %2", desc_A.QuantizationBits, desc_B.QuantizationBits));
174                 return false;
175         } else if (desc_A.BlockAlign != desc_B.BlockAlign) {
176                 note (NoteType::ERROR, String::compose ("audio bytes per sample differ: %1 cf %2", desc_A.BlockAlign, desc_B.BlockAlign));
177                 return false;
178         } else if (desc_A.AvgBps != desc_B.AvgBps) {
179                 note (NoteType::ERROR, String::compose ("audio average bps differ: %1 cf %2", desc_A.AvgBps, desc_B.AvgBps));
180                 return false;
181         } else if (desc_A.LinkedTrackID != desc_B.LinkedTrackID) {
182                 note (NoteType::ERROR, String::compose ("audio linked track IDs differ: %1 cf %2", desc_A.LinkedTrackID, desc_B.LinkedTrackID));
183                 return false;
184         } else if (desc_A.ContainerDuration != desc_B.ContainerDuration) {
185                 note (NoteType::ERROR, String::compose ("audio container durations differ: %1 cf %2", desc_A.ContainerDuration, desc_B.ContainerDuration));
186                 return false;
187         } else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
188                 /* XXX */
189         }
190
191         auto other_sound = dynamic_pointer_cast<const SoundAsset> (other);
192
193         auto reader = start_read ();
194         auto other_reader = other_sound->start_read ();
195
196         for (int i = 0; i < _intrinsic_duration; ++i) {
197
198                 auto frame_A = reader->get_frame (i);
199                 auto frame_B = other_reader->get_frame (i);
200
201                 if (frame_A->size() != frame_B->size()) {
202                         note (NoteType::ERROR, String::compose ("sizes of audio data for frame %1 differ", i));
203                         return false;
204                 }
205
206                 if (memcmp (frame_A->data(), frame_B->data(), frame_A->size()) != 0) {
207                         for (int sample = 0; sample < frame_A->samples(); ++sample) {
208                                 for (int channel = 0; channel < frame_A->channels(); ++channel) {
209                                         int32_t const d = abs(frame_A->get(channel, sample) - frame_B->get(channel, sample));
210                                         if (d > opt.max_audio_sample_error) {
211                                                 note (NoteType::ERROR, String::compose("PCM data difference of %1 in frame %2, channel %3, sample %4", d, i, channel, sample));
212                                                 return false;
213                                         }
214                                 }
215                         }
216                 }
217         }
218
219         return true;
220 }
221
222
223 shared_ptr<SoundAssetWriter>
224 SoundAsset::start_write(
225         boost::filesystem::path file,
226         vector<dcp::Channel> extra_active_channels,
227         AtmosSync atmos_sync,
228         MCASubDescriptors include_mca_subdescriptors
229         )
230 {
231         if (atmos_sync == AtmosSync::ENABLED && _channels < 14) {
232                 throw MiscError ("Insufficient channels to write ATMOS sync (there must be at least 14)");
233         }
234
235         return shared_ptr<SoundAssetWriter>(
236                 new SoundAssetWriter(this, file, extra_active_channels, atmos_sync == AtmosSync::ENABLED, include_mca_subdescriptors == MCASubDescriptors::ENABLED)
237                 );
238 }
239
240
241 shared_ptr<SoundAssetReader>
242 SoundAsset::start_read () const
243 {
244         return shared_ptr<SoundAssetReader> (new SoundAssetReader(this, key(), standard()));
245 }
246
247
248 string
249 SoundAsset::static_pkl_type (Standard standard)
250 {
251         switch (standard) {
252         case Standard::INTEROP:
253                 return "application/x-smpte-mxf;asdcpKind=Sound";
254         case Standard::SMPTE:
255                 return "application/mxf";
256         default:
257                 DCP_ASSERT (false);
258         }
259 }
260
261
262 bool
263 SoundAsset::valid_mxf (boost::filesystem::path file)
264 {
265         ASDCP::PCM::MXFReader reader;
266         Kumu::Result_t r = reader.OpenRead (file.string().c_str());
267         return !ASDCP_FAILURE (r);
268 }