Use an optional<> where there should be one.
[libdcp.git] / src / sound_asset.cc
1 /*
2     Copyright (C) 2012-2016 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/KM_fileio.h>
47 #include <asdcp/AS_DCP.h>
48 #include <libxml++/nodes/element.h>
49 #include <boost/filesystem.hpp>
50 #include <stdexcept>
51
52 using std::string;
53 using std::vector;
54 using std::list;
55 using boost::shared_ptr;
56 using boost::dynamic_pointer_cast;
57 using namespace dcp;
58
59 SoundAsset::SoundAsset (boost::filesystem::path file)
60         : Asset (file)
61 {
62         ASDCP::PCM::MXFReader reader;
63         Kumu::Result_t r = reader.OpenRead (file.string().c_str());
64         if (ASDCP_FAILURE (r)) {
65                 boost::throw_exception (MXFFileError ("could not open MXF file for reading", file.string(), r));
66         }
67
68         ASDCP::PCM::AudioDescriptor desc;
69         if (ASDCP_FAILURE (reader.FillAudioDescriptor (desc))) {
70                 boost::throw_exception (DCPReadError ("could not read audio MXF information"));
71         }
72
73         _sampling_rate = desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator;
74         _channels = desc.ChannelCount;
75         _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator);
76
77         _intrinsic_duration = desc.ContainerDuration;
78
79         ASDCP::WriterInfo info;
80         if (ASDCP_FAILURE (reader.FillWriterInfo (info))) {
81                 boost::throw_exception (DCPReadError ("could not read audio MXF information"));
82         }
83
84         _id = read_writer_info (info);
85 }
86
87 SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels)
88         : _edit_rate (edit_rate)
89         , _intrinsic_duration (0)
90         , _channels (channels)
91         , _sampling_rate (sampling_rate)
92 {
93
94 }
95
96 bool
97 SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
98 {
99         ASDCP::PCM::MXFReader reader_A;
100         DCP_ASSERT (file ());
101         Kumu::Result_t r = reader_A.OpenRead (file()->string().c_str());
102         if (ASDCP_FAILURE (r)) {
103                 boost::throw_exception (MXFFileError ("could not open MXF file for reading", file()->string(), r));
104         }
105
106         ASDCP::PCM::MXFReader reader_B;
107         r = reader_B.OpenRead (other->file()->string().c_str());
108         if (ASDCP_FAILURE (r)) {
109                 boost::throw_exception (MXFFileError ("could not open MXF file for reading", other->file()->string(), r));
110         }
111
112         ASDCP::PCM::AudioDescriptor desc_A;
113         if (ASDCP_FAILURE (reader_A.FillAudioDescriptor (desc_A))) {
114                 boost::throw_exception (DCPReadError ("could not read audio MXF information"));
115         }
116         ASDCP::PCM::AudioDescriptor desc_B;
117         if (ASDCP_FAILURE (reader_B.FillAudioDescriptor (desc_B))) {
118                 boost::throw_exception (DCPReadError ("could not read audio MXF information"));
119         }
120
121         if (desc_A.EditRate != desc_B.EditRate) {
122                 note (
123                         DCP_ERROR,
124                         String::compose (
125                                 "audio edit rates differ: %1/%2 cf %3/%4",
126                                 desc_A.EditRate.Numerator, desc_A.EditRate.Denominator, desc_B.EditRate.Numerator, desc_B.EditRate.Denominator
127                                 )
128                         );
129                 return false;
130         } else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
131                 note (
132                         DCP_ERROR,
133                         String::compose (
134                                 "audio sampling rates differ: %1 cf %2",
135                                 desc_A.AudioSamplingRate.Numerator, desc_A.AudioSamplingRate.Denominator,
136                                 desc_B.AudioSamplingRate.Numerator, desc_B.AudioSamplingRate.Numerator
137                                 )
138                         );
139                 return false;
140         } else if (desc_A.Locked != desc_B.Locked) {
141                 note (DCP_ERROR, String::compose ("audio locked flags differ: %1 cf %2", desc_A.Locked, desc_B.Locked));
142                 return false;
143         } else if (desc_A.ChannelCount != desc_B.ChannelCount) {
144                 note (DCP_ERROR, String::compose ("audio channel counts differ: %1 cf %2", desc_A.ChannelCount, desc_B.ChannelCount));
145                 return false;
146         } else if (desc_A.QuantizationBits != desc_B.QuantizationBits) {
147                 note (DCP_ERROR, String::compose ("audio bits per sample differ: %1 cf %2", desc_A.QuantizationBits, desc_B.QuantizationBits));
148                 return false;
149         } else if (desc_A.BlockAlign != desc_B.BlockAlign) {
150                 note (DCP_ERROR, String::compose ("audio bytes per sample differ: %1 cf %2", desc_A.BlockAlign, desc_B.BlockAlign));
151                 return false;
152         } else if (desc_A.AvgBps != desc_B.AvgBps) {
153                 note (DCP_ERROR, String::compose ("audio average bps differ: %1 cf %2", desc_A.AvgBps, desc_B.AvgBps));
154                 return false;
155         } else if (desc_A.LinkedTrackID != desc_B.LinkedTrackID) {
156                 note (DCP_ERROR, String::compose ("audio linked track IDs differ: %1 cf %2", desc_A.LinkedTrackID, desc_B.LinkedTrackID));
157                 return false;
158         } else if (desc_A.ContainerDuration != desc_B.ContainerDuration) {
159                 note (DCP_ERROR, String::compose ("audio container durations differ: %1 cf %2", desc_A.ContainerDuration, desc_B.ContainerDuration));
160                 return false;
161         } else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
162                 /* XXX */
163         }
164
165         shared_ptr<const SoundAsset> other_sound = dynamic_pointer_cast<const SoundAsset> (other);
166
167         shared_ptr<const SoundAssetReader> reader = start_read ();
168         shared_ptr<const SoundAssetReader> other_reader = other_sound->start_read ();
169
170         for (int i = 0; i < _intrinsic_duration; ++i) {
171
172                 shared_ptr<const SoundFrame> frame_A = reader->get_frame (i);
173                 shared_ptr<const SoundFrame> frame_B = other_reader->get_frame (i);
174
175                 if (frame_A->size() != frame_B->size()) {
176                         note (DCP_ERROR, String::compose ("sizes of audio data for frame %1 differ", i));
177                         return false;
178                 }
179
180                 if (memcmp (frame_A->data(), frame_B->data(), frame_A->size()) != 0) {
181                         for (int i = 0; i < frame_A->size(); ++i) {
182                                 int const d = abs (frame_A->data()[i] - frame_B->data()[i]);
183                                 if (d > opt.max_audio_sample_error) {
184                                         note (DCP_ERROR, String::compose ("PCM data difference of %1", d));
185                                         return false;
186                                 }
187                         }
188                 }
189         }
190
191         return true;
192 }
193
194 shared_ptr<SoundAssetWriter>
195 SoundAsset::start_write (boost::filesystem::path file, Standard standard)
196 {
197         /* XXX: can't we use a shared_ptr here? */
198         return shared_ptr<SoundAssetWriter> (new SoundAssetWriter (this, file, standard));
199 }
200
201 shared_ptr<SoundAssetReader>
202 SoundAsset::start_read () const
203 {
204         return shared_ptr<SoundAssetReader> (new SoundAssetReader (this));
205 }
206
207 string
208 SoundAsset::pkl_type (Standard standard) const
209 {
210         switch (standard) {
211         case INTEROP:
212                 return "application/x-smpte-mxf;asdcpKind=Sound";
213         case SMPTE:
214                 return "application/mxf";
215         default:
216                 DCP_ASSERT (false);
217         }
218 }
219
220 bool
221 SoundAsset::valid_mxf (boost::filesystem::path file)
222 {
223         ASDCP::PCM::MXFReader reader;
224         Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
225         return !ASDCP_FAILURE (r);
226 }