libdcp
sound_asset.cc
Go to the documentation of this file.
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 
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::string;
60 using std::vector;
61 using std::list;
62 using std::shared_ptr;
63 using std::dynamic_pointer_cast;
64 using namespace dcp;
65 
66 
67 SoundAsset::SoundAsset (boost::filesystem::path file)
68  : Asset (file)
69 {
70  ASDCP::PCM::MXFReader reader;
71  auto r = reader.OpenRead (file.string().c_str());
72  if (ASDCP_FAILURE(r)) {
73  boost::throw_exception (MXFFileError("could not open MXF file for reading", file.string(), r));
74  }
75 
76  ASDCP::PCM::AudioDescriptor desc;
77  if (ASDCP_FAILURE (reader.FillAudioDescriptor(desc))) {
78  boost::throw_exception (ReadError("could not read audio MXF information"));
79  }
80 
81  _sampling_rate = desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator;
82  _channels = desc.ChannelCount;
83  _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator);
84 
85  _intrinsic_duration = desc.ContainerDuration;
86 
87  ASDCP::WriterInfo info;
88  if (ASDCP_FAILURE (reader.FillWriterInfo(info))) {
89  boost::throw_exception (ReadError("could not read audio MXF information"));
90  }
91 
92  ASDCP::MXF::SoundfieldGroupLabelSubDescriptor* soundfield;
93  auto rr = reader.OP1aHeader().GetMDObjectByType(
94  asdcp_smpte_dict->ul(ASDCP::MDD_SoundfieldGroupLabelSubDescriptor),
95  reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&soundfield)
96  );
97 
98  if (KM_SUCCESS(rr)) {
99  if (!soundfield->RFC5646SpokenLanguage.empty()) {
100  char buffer[64];
101  soundfield->RFC5646SpokenLanguage.get().EncodeString(buffer, sizeof(buffer));
102  _language = buffer;
103  }
104  }
105 
106  _id = read_writer_info (info);
107 }
108 
109 
110 SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, LanguageTag language, Standard standard)
111  : MXF (standard)
112  , _edit_rate (edit_rate)
113  , _channels (channels)
114  , _sampling_rate (sampling_rate)
115  , _language (language.to_string())
116 {
117 
118 }
119 
120 
121 bool
122 SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
123 {
124  ASDCP::PCM::MXFReader reader_A;
125  DCP_ASSERT (file());
126  auto r = reader_A.OpenRead (file()->string().c_str());
127  if (ASDCP_FAILURE(r)) {
128  boost::throw_exception (MXFFileError("could not open MXF file for reading", file()->string(), r));
129  }
130 
131  ASDCP::PCM::MXFReader reader_B;
132  r = reader_B.OpenRead (other->file()->string().c_str());
133  if (ASDCP_FAILURE (r)) {
134  boost::throw_exception (MXFFileError("could not open MXF file for reading", other->file()->string(), r));
135  }
136 
137  ASDCP::PCM::AudioDescriptor desc_A;
138  if (ASDCP_FAILURE (reader_A.FillAudioDescriptor(desc_A))) {
139  boost::throw_exception (ReadError ("could not read audio MXF information"));
140  }
141  ASDCP::PCM::AudioDescriptor desc_B;
142  if (ASDCP_FAILURE (reader_B.FillAudioDescriptor(desc_B))) {
143  boost::throw_exception (ReadError ("could not read audio MXF information"));
144  }
145 
146  if (desc_A.EditRate != desc_B.EditRate) {
147  note (
148  NoteType::ERROR,
149  String::compose (
150  "audio edit rates differ: %1/%2 cf %3/%4",
151  desc_A.EditRate.Numerator, desc_A.EditRate.Denominator, desc_B.EditRate.Numerator, desc_B.EditRate.Denominator
152  )
153  );
154  return false;
155  } else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
156  note (
157  NoteType::ERROR,
158  String::compose (
159  "audio sampling rates differ: %1 cf %2",
160  desc_A.AudioSamplingRate.Numerator, desc_A.AudioSamplingRate.Denominator,
161  desc_B.AudioSamplingRate.Numerator, desc_B.AudioSamplingRate.Numerator
162  )
163  );
164  return false;
165  } else if (desc_A.Locked != desc_B.Locked) {
166  note (NoteType::ERROR, String::compose ("audio locked flags differ: %1 cf %2", desc_A.Locked, desc_B.Locked));
167  return false;
168  } else if (desc_A.ChannelCount != desc_B.ChannelCount) {
169  note (NoteType::ERROR, String::compose ("audio channel counts differ: %1 cf %2", desc_A.ChannelCount, desc_B.ChannelCount));
170  return false;
171  } else if (desc_A.QuantizationBits != desc_B.QuantizationBits) {
172  note (NoteType::ERROR, String::compose ("audio bits per sample differ: %1 cf %2", desc_A.QuantizationBits, desc_B.QuantizationBits));
173  return false;
174  } else if (desc_A.BlockAlign != desc_B.BlockAlign) {
175  note (NoteType::ERROR, String::compose ("audio bytes per sample differ: %1 cf %2", desc_A.BlockAlign, desc_B.BlockAlign));
176  return false;
177  } else if (desc_A.AvgBps != desc_B.AvgBps) {
178  note (NoteType::ERROR, String::compose ("audio average bps differ: %1 cf %2", desc_A.AvgBps, desc_B.AvgBps));
179  return false;
180  } else if (desc_A.LinkedTrackID != desc_B.LinkedTrackID) {
181  note (NoteType::ERROR, String::compose ("audio linked track IDs differ: %1 cf %2", desc_A.LinkedTrackID, desc_B.LinkedTrackID));
182  return false;
183  } else if (desc_A.ContainerDuration != desc_B.ContainerDuration) {
184  note (NoteType::ERROR, String::compose ("audio container durations differ: %1 cf %2", desc_A.ContainerDuration, desc_B.ContainerDuration));
185  return false;
186  } else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
187  /* XXX */
188  }
189 
190  auto other_sound = dynamic_pointer_cast<const SoundAsset> (other);
191 
192  auto reader = start_read ();
193  auto other_reader = other_sound->start_read ();
194 
195  for (int i = 0; i < _intrinsic_duration; ++i) {
196 
197  auto frame_A = reader->get_frame (i);
198  auto frame_B = other_reader->get_frame (i);
199 
200  if (frame_A->size() != frame_B->size()) {
201  note (NoteType::ERROR, String::compose ("sizes of audio data for frame %1 differ", i));
202  return false;
203  }
204 
205  if (memcmp (frame_A->data(), frame_B->data(), frame_A->size()) != 0) {
206  for (int sample = 0; sample < frame_A->samples(); ++sample) {
207  for (int channel = 0; channel < frame_A->channels(); ++channel) {
208  int32_t const d = abs(frame_A->get(channel, sample) - frame_B->get(channel, sample));
209  if (d > opt.max_audio_sample_error) {
210  note (NoteType::ERROR, String::compose("PCM data difference of %1 in frame %2, channel %3, sample %4", d, i, channel, sample));
211  return false;
212  }
213  }
214  }
215  }
216  }
217 
218  return true;
219 }
220 
221 
222 shared_ptr<SoundAssetWriter>
223 SoundAsset::start_write (boost::filesystem::path file, bool atmos_sync)
224 {
225  if (atmos_sync && _channels < 14) {
226  throw MiscError ("Insufficient channels to write ATMOS sync (there must be at least 14)");
227  }
228 
229  return shared_ptr<SoundAssetWriter> (new SoundAssetWriter(this, file, atmos_sync));
230 }
231 
232 
233 shared_ptr<SoundAssetReader>
234 SoundAsset::start_read () const
235 {
236  return shared_ptr<SoundAssetReader> (new SoundAssetReader(this, key(), standard()));
237 }
238 
239 
240 string
241 SoundAsset::static_pkl_type (Standard standard)
242 {
243  switch (standard) {
244  case Standard::INTEROP:
245  return "application/x-smpte-mxf;asdcpKind=Sound";
246  case Standard::SMPTE:
247  return "application/mxf";
248  default:
249  DCP_ASSERT (false);
250  }
251 }
252 
253 
254 bool
255 SoundAsset::valid_mxf (boost::filesystem::path file)
256 {
257  ASDCP::PCM::MXFReader reader;
258  Kumu::Result_t r = reader.OpenRead (file.string().c_str());
259  return !ASDCP_FAILURE (r);
260 }
Parent class for DCP assets, i.e. picture, sound, subtitles, closed captions, CPLs,...
Definition: asset.h:70
boost::optional< boost::filesystem::path > file() const
Definition: asset.h:97
A fraction (i.e. a thing with an integer numerator and an integer denominator).
Definition: types.h:214
An exception related to an MXF file.
Definition: exceptions.h:82
Parent for classes which represent MXF files.
Definition: mxf.h:74
boost::optional< Key > key() const
Definition: mxf.h:104
A miscellaneous exception.
Definition: exceptions.h:94
Any error that occurs when reading data from a DCP.
Definition: exceptions.h:106
A helper class for writing to SoundAssets.
int _channels
number of channels
Definition: sound_asset.h:127
int64_t _intrinsic_duration
Definition: sound_asset.h:126
DCP_ASSERT macro.
Exceptions thrown by libdcp.
Namespace for everything in libdcp.
Definition: array_data.h:50
SoundAsset class.
SoundAssetReader typedef.
SoundAssetWriter class.
SoundFrame class.
A class to describe what "equality" means for a particular test.
Definition: types.h:249
int max_audio_sample_error
Definition: types.h:258
Utility methods and classes.