Add include_mca_subdescriptors flag to SoundAsset writer.
[libdcp.git] / src / sound_asset_writer.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_writer.cc
36  *  @brief SoundAssetWriter class
37  */
38
39
40 #include "bitstream.h"
41 #include "compose.hpp"
42 #include "crypto_context.h"
43 #include "dcp_assert.h"
44 #include "exceptions.h"
45 #include "sound_asset.h"
46 #include "sound_asset_writer.h"
47 #include "warnings.h"
48 LIBDCP_DISABLE_WARNINGS
49 #include <asdcp/AS_DCP.h>
50 #include <asdcp/Metadata.h>
51 LIBDCP_ENABLE_WARNINGS
52 #include <iostream>
53
54
55 using std::min;
56 using std::max;
57 using std::cout;
58 using std::string;
59 using std::vector;
60 using namespace dcp;
61
62
63 struct SoundAssetWriter::ASDCPState
64 {
65         ASDCP::PCM::MXFWriter mxf_writer;
66         ASDCP::PCM::FrameBuffer frame_buffer;
67         ASDCP::WriterInfo writer_info;
68         ASDCP::PCM::AudioDescriptor desc;
69 };
70
71
72 SoundAssetWriter::SoundAssetWriter (SoundAsset* asset, boost::filesystem::path file, bool sync, bool include_mca_subdescriptors)
73         : AssetWriter (asset, file)
74         , _state (new SoundAssetWriter::ASDCPState)
75         , _asset (asset)
76         , _sync (sync)
77         , _include_mca_subdescriptors(include_mca_subdescriptors)
78 {
79         DCP_ASSERT (!_sync || _asset->channels() >= 14);
80         DCP_ASSERT (!_sync || _asset->standard() == Standard::SMPTE);
81
82         /* Derived from ASDCP::Wav::SimpleWaveHeader::FillADesc */
83         _state->desc.EditRate = ASDCP::Rational (_asset->edit_rate().numerator, _asset->edit_rate().denominator);
84         _state->desc.AudioSamplingRate = ASDCP::Rational (_asset->sampling_rate(), 1);
85         _state->desc.Locked = 0;
86         _state->desc.ChannelCount = _asset->channels();
87         _state->desc.QuantizationBits = 24;
88         _state->desc.BlockAlign = 3 * _asset->channels();
89         _state->desc.AvgBps = _asset->sampling_rate() * _state->desc.BlockAlign;
90         _state->desc.LinkedTrackID = 0;
91         if (asset->standard() == Standard::INTEROP) {
92                 _state->desc.ChannelFormat = ASDCP::PCM::CF_NONE;
93         } else {
94                 /* As required by Bv2.1 */
95                 _state->desc.ChannelFormat = ASDCP::PCM::CF_CFG_4;
96         }
97
98         /* I'm fairly sure this is not necessary, as ContainerDuration is written
99            in ASDCP's WriteMXFFooter, but it stops a valgrind warning.
100         */
101         _state->desc.ContainerDuration = 0;
102
103         _state->frame_buffer.Capacity (ASDCP::PCM::CalcFrameBufferSize (_state->desc));
104         _state->frame_buffer.Size (ASDCP::PCM::CalcFrameBufferSize (_state->desc));
105         memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity());
106
107         _asset->fill_writer_info (&_state->writer_info, _asset->id());
108
109         if (_sync) {
110                 _fsk.set_data (create_sync_packets());
111         }
112 }
113
114
115 SoundAssetWriter::~SoundAssetWriter()
116 {
117         try {
118                 /* Last-resort finalization to close the file, at least */
119                 if (!_finalized) {
120                         _state->mxf_writer.Finalize();
121                 }
122         } catch (...) {}
123 }
124
125
126 void
127 SoundAssetWriter::start ()
128 {
129         auto r = _state->mxf_writer.OpenWrite (_file.string().c_str(), _state->writer_info, _state->desc);
130         if (ASDCP_FAILURE(r)) {
131                 boost::throw_exception (FileError("could not open audio MXF for writing", _file.string(), r));
132         }
133
134         if (_asset->standard() == Standard::SMPTE && _include_mca_subdescriptors) {
135
136                 ASDCP::MXF::WaveAudioDescriptor* essence_descriptor = nullptr;
137                 _state->mxf_writer.OP1aHeader().GetMDObjectByType(
138                         asdcp_smpte_dict->ul(ASDCP::MDD_WaveAudioDescriptor), reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&essence_descriptor)
139                         );
140                 DCP_ASSERT (essence_descriptor);
141                 essence_descriptor->ChannelAssignment = asdcp_smpte_dict->ul(ASDCP::MDD_DCAudioChannelCfg_4_WTF);
142
143                 auto soundfield = new ASDCP::MXF::SoundfieldGroupLabelSubDescriptor(asdcp_smpte_dict);
144                 GenRandomValue (soundfield->MCALinkID);
145                 if (auto lang = _asset->language()) {
146                         soundfield->RFC5646SpokenLanguage = *lang;
147                 }
148
149                 const MCASoundField field = _asset->channels() > 10 ? MCASoundField::SEVEN_POINT_ONE : MCASoundField::FIVE_POINT_ONE;
150
151                 if (field == MCASoundField::SEVEN_POINT_ONE) {
152                         soundfield->MCATagSymbol = "sg71";
153                         soundfield->MCATagName = "7.1DS";
154 LIBDCP_DISABLE_WARNINGS
155                         soundfield->MCALabelDictionaryID = asdcp_smpte_dict->ul(ASDCP::MDD_DCAudioSoundfield_71);
156 LIBDCP_ENABLE_WARNINGS
157                 } else {
158                         soundfield->MCATagSymbol = "sg51";
159                         soundfield->MCATagName = "5.1";
160 LIBDCP_DISABLE_WARNINGS
161                         soundfield->MCALabelDictionaryID = asdcp_smpte_dict->ul(ASDCP::MDD_DCAudioSoundfield_51);
162 LIBDCP_ENABLE_WARNINGS
163                 }
164
165                 _state->mxf_writer.OP1aHeader().AddChildObject(soundfield);
166                 essence_descriptor->SubDescriptors.push_back(soundfield->InstanceUID);
167
168                 /* We must describe at least the number of channels in `field', even if they aren't
169                  * in the asset (I think)
170                  */
171                 int descriptors = max(_asset->channels(), field == MCASoundField::FIVE_POINT_ONE ? 6 : 8);
172
173                 auto const used = used_audio_channels();
174
175                 for (auto i = 0; i < descriptors; ++i) {
176                         auto dcp_channel = static_cast<dcp::Channel>(i);
177                         if (find(used.begin(), used.end(), dcp_channel) == used.end()) {
178                                 continue;
179                         }
180                         auto channel = new ASDCP::MXF::AudioChannelLabelSubDescriptor(asdcp_smpte_dict);
181                         GenRandomValue (channel->MCALinkID);
182                         channel->SoundfieldGroupLinkID = soundfield->MCALinkID;
183                         channel->MCAChannelID = i + 1;
184                         channel->MCATagSymbol = "ch" + channel_to_mca_id(dcp_channel, field);
185                         channel->MCATagName = channel_to_mca_name(dcp_channel, field);
186                         if (auto lang = _asset->language()) {
187                                 channel->RFC5646SpokenLanguage = *lang;
188                         }
189 LIBDCP_DISABLE_WARNINGS
190                         channel->MCALabelDictionaryID = channel_to_mca_universal_label(dcp_channel, field, asdcp_smpte_dict);
191 LIBDCP_ENABLE_WARNINGS
192                         _state->mxf_writer.OP1aHeader().AddChildObject(channel);
193                         essence_descriptor->SubDescriptors.push_back(channel->InstanceUID);
194                 }
195         }
196
197         _asset->set_file (_file);
198         _started = true;
199 }
200
201
202 void
203 SoundAssetWriter::write (float const * const * data, int frames)
204 {
205         DCP_ASSERT (!_finalized);
206         DCP_ASSERT (frames > 0);
207
208         static float const clip = 1.0f - (1.0f / pow (2, 23));
209
210         if (!_started) {
211                 start ();
212         }
213
214         int const ch = _asset->channels ();
215
216         for (int i = 0; i < frames; ++i) {
217
218                 byte_t* out = _state->frame_buffer.Data() + _frame_buffer_offset;
219
220                 /* Write one sample per channel */
221                 for (int j = 0; j < ch; ++j) {
222                         int32_t s = 0;
223                         if (j == 13 && _sync) {
224                                 s = _fsk.get();
225                         } else {
226                                 /* Convert sample to 24-bit int, clipping if necessary. */
227                                 float x = data[j][i];
228                                 if (x > clip) {
229                                         x = clip;
230                                 } else if (x < -clip) {
231                                         x = -clip;
232                                 }
233                                 s = x * (1 << 23);
234                         }
235                         *out++ = (s & 0xff);
236                         *out++ = (s & 0xff00) >> 8;
237                         *out++ = (s & 0xff0000) >> 16;
238                 }
239                 _frame_buffer_offset += 3 * ch;
240
241                 DCP_ASSERT (_frame_buffer_offset <= int(_state->frame_buffer.Capacity()));
242
243                 /* Finish the MXF frame if required */
244                 if (_frame_buffer_offset == int (_state->frame_buffer.Capacity())) {
245                         write_current_frame ();
246                         _frame_buffer_offset = 0;
247                         memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity());
248                 }
249         }
250 }
251
252 void
253 SoundAssetWriter::write_current_frame ()
254 {
255         auto const r = _state->mxf_writer.WriteFrame (_state->frame_buffer, _crypto_context->context(), _crypto_context->hmac());
256         if (ASDCP_FAILURE(r)) {
257                 boost::throw_exception (MiscError(String::compose("could not write audio MXF frame (%1)", static_cast<int>(r))));
258         }
259
260         ++_frames_written;
261
262         if (_sync) {
263                 /* We need a new set of sync packets for this frame */
264                 _fsk.set_data (create_sync_packets());
265         }
266 }
267
268 bool
269 SoundAssetWriter::finalize ()
270 {
271         if (_frame_buffer_offset > 0) {
272                 write_current_frame ();
273         }
274
275         if (_started) {
276                 auto const r = _state->mxf_writer.Finalize();
277                 if (ASDCP_FAILURE(r)) {
278                         boost::throw_exception (MiscError(String::compose ("could not finalise audio MXF (%1)", static_cast<int>(r))));
279                 }
280         }
281
282         _asset->_intrinsic_duration = _frames_written;
283         return AssetWriter::finalize ();
284 }
285
286
287 /** Calculate and return the sync packets required for this edit unit (aka "frame") */
288 vector<bool>
289 SoundAssetWriter::create_sync_packets ()
290 {
291         /* Parts of this code assumes 48kHz */
292         DCP_ASSERT (_asset->sampling_rate() == 48000);
293
294         /* Encoding of edit rate */
295         int edit_rate_code = 0;
296         /* How many 0 bits are used to pad the end of the packet */
297         int remaining_bits = 0;
298         /* How many packets in this edit unit (i.e. "frame") */
299         int packets = 0;
300         auto const edit_rate = _asset->edit_rate ();
301         if (edit_rate == Fraction(24, 1)) {
302                 edit_rate_code = 0;
303                 remaining_bits = 25;
304                 packets = 4;
305         } else if (edit_rate == Fraction(25, 1)) {
306                 edit_rate_code = 1;
307                 remaining_bits = 20;
308                 packets = 4;
309         } else if (edit_rate == Fraction(30, 1)) {
310                 edit_rate_code = 2;
311                 remaining_bits = 0;
312                 packets = 4;
313         } else if (edit_rate == Fraction(48, 1)) {
314                 edit_rate_code = 3;
315                 remaining_bits = 25;
316                 packets = 2;
317         } else if (edit_rate == Fraction(50, 1)) {
318                 edit_rate_code = 4;
319                 remaining_bits = 20;
320                 packets = 2;
321         } else if (edit_rate == Fraction(60, 1)) {
322                 edit_rate_code = 5;
323                 remaining_bits = 0;
324                 packets = 2;
325         } else if (edit_rate == Fraction(96, 1)) {
326                 edit_rate_code = 6;
327                 remaining_bits = 25;
328                 packets = 1;
329         } else if (edit_rate == Fraction(100, 1)) {
330                 edit_rate_code = 7;
331                 remaining_bits = 20;
332                 packets = 1;
333         } else if (edit_rate == Fraction(120, 1)) {
334                 edit_rate_code = 8;
335                 remaining_bits = 0;
336                 packets = 1;
337         }
338
339         Bitstream bs;
340
341         Kumu::UUID id;
342         DCP_ASSERT (id.DecodeHex(_asset->id().c_str()));
343
344         for (int i = 0; i < packets; ++i) {
345                 bs.write_from_byte (0x4d);
346                 bs.write_from_byte (0x56);
347                 bs.start_crc (0x1021);
348                 bs.write_from_byte (edit_rate_code, 4);
349                 bs.write_from_byte (0, 2);
350                 bs.write_from_byte (_sync_packet, 2);
351                 bs.write_from_byte (id.Value()[i * 4 + 0]);
352                 bs.write_from_byte (id.Value()[i * 4 + 1]);
353                 bs.write_from_byte (id.Value()[i * 4 + 2]);
354                 bs.write_from_byte (id.Value()[i * 4 + 3]);
355                 bs.write_from_word (_frames_written, 24);
356                 bs.write_crc ();
357                 bs.write_from_byte (0, 4);
358                 bs.write_from_word (0, remaining_bits);
359
360                 ++_sync_packet;
361                 if (_sync_packet == 4) {
362                         _sync_packet = 0;
363                 }
364         }
365
366         return bs.get();
367 }
368