Use an enum class for Marker.
[libdcp.git] / src / sound_asset.cc
1 /*
2     Copyright (C) 2012-2020 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 /** @file  src/sound_mxf.cc
35  *  @brief SoundAsset class.
36  */
37
38 #include "sound_asset.h"
39 #include "util.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>
51 #include <stdexcept>
52
53 using std::string;
54 using std::vector;
55 using std::list;
56 using std::shared_ptr;
57 using std::dynamic_pointer_cast;
58 using namespace dcp;
59
60
61 SoundAsset::SoundAsset (boost::filesystem::path file)
62         : Asset (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.
66          */
67         , _language ("en-US")
68 {
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));
73         }
74
75         ASDCP::PCM::AudioDescriptor desc;
76         if (ASDCP_FAILURE (reader.FillAudioDescriptor (desc))) {
77                 boost::throw_exception (ReadError ("could not read audio MXF information"));
78         }
79
80         _sampling_rate = desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator;
81         _channels = desc.ChannelCount;
82         _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator);
83
84         _intrinsic_duration = desc.ContainerDuration;
85
86         ASDCP::WriterInfo info;
87         if (ASDCP_FAILURE (reader.FillWriterInfo (info))) {
88                 boost::throw_exception (ReadError ("could not read audio MXF information"));
89         }
90
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)
95                 );
96
97         if (KM_SUCCESS(rr)) {
98                 if (!soundfield->RFC5646SpokenLanguage.empty()) {
99                         char buffer[64];
100                         soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
101                         _language = buffer;
102                 }
103         }
104
105         _id = read_writer_info (info);
106 }
107
108 SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, LanguageTag language, Standard standard)
109         : MXF (standard)
110         , _edit_rate (edit_rate)
111         , _intrinsic_duration (0)
112         , _channels (channels)
113         , _sampling_rate (sampling_rate)
114         , _language (language.to_string())
115 {
116
117 }
118
119 bool
120 SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
121 {
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));
127         }
128
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));
133         }
134
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"));
138         }
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"));
142         }
143
144         if (desc_A.EditRate != desc_B.EditRate) {
145                 note (
146                         DCP_ERROR,
147                         String::compose (
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
150                                 )
151                         );
152                 return false;
153         } else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
154                 note (
155                         DCP_ERROR,
156                         String::compose (
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
160                                 )
161                         );
162                 return false;
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));
165                 return false;
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));
168                 return false;
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));
171                 return false;
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));
174                 return false;
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));
177                 return false;
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));
180                 return false;
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));
183                 return false;
184         } else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
185                 /* XXX */
186         }
187
188         shared_ptr<const SoundAsset> other_sound = dynamic_pointer_cast<const SoundAsset> (other);
189
190         shared_ptr<const SoundAssetReader> reader = start_read ();
191         shared_ptr<const SoundAssetReader> other_reader = other_sound->start_read ();
192
193         for (int i = 0; i < _intrinsic_duration; ++i) {
194
195                 shared_ptr<const SoundFrame> frame_A = reader->get_frame (i);
196                 shared_ptr<const SoundFrame> frame_B = other_reader->get_frame (i);
197
198                 if (frame_A->size() != frame_B->size()) {
199                         note (DCP_ERROR, String::compose ("sizes of audio data for frame %1 differ", i));
200                         return false;
201                 }
202
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));
209                                                 return false;
210                                         }
211                                 }
212                         }
213                 }
214         }
215
216         return true;
217 }
218
219 shared_ptr<SoundAssetWriter>
220 SoundAsset::start_write (boost::filesystem::path file, vector<Channel> active_channels, bool atmos_sync)
221 {
222         if (atmos_sync && _channels < 14) {
223                 throw MiscError ("Insufficient channels to write ATMOS sync (there must be at least 14)");
224         }
225
226         return shared_ptr<SoundAssetWriter> (new SoundAssetWriter(this, file, active_channels, atmos_sync));
227 }
228
229 shared_ptr<SoundAssetReader>
230 SoundAsset::start_read () const
231 {
232         return shared_ptr<SoundAssetReader> (new SoundAssetReader (this, key(), standard()));
233 }
234
235 string
236 SoundAsset::static_pkl_type (Standard standard)
237 {
238         switch (standard) {
239         case INTEROP:
240                 return "application/x-smpte-mxf;asdcpKind=Sound";
241         case SMPTE:
242                 return "application/mxf";
243         default:
244                 DCP_ASSERT (false);
245         }
246 }
247
248 bool
249 SoundAsset::valid_mxf (boost::filesystem::path file)
250 {
251         ASDCP::PCM::MXFReader reader;
252         Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
253         return !ASDCP_FAILURE (r);
254 }