libdcp
sound_asset_writer.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 "bitstream.h"
41 #include "compose.hpp"
42 #include "crypto_context.h"
43 #include "dcp_assert.h"
44 #include "exceptions.h"
45 #include "filesystem.h"
46 #include "sound_asset.h"
47 #include "sound_asset_writer.h"
48 #include "warnings.h"
49 LIBDCP_DISABLE_WARNINGS
50 #include <asdcp/AS_DCP.h>
51 #include <asdcp/Metadata.h>
52 LIBDCP_ENABLE_WARNINGS
53 #include <iostream>
54 
55 
56 using std::min;
57 using std::max;
58 using std::cout;
59 using std::string;
60 using std::vector;
61 using namespace dcp;
62 
63 
64 struct SoundAssetWriter::ASDCPState
65 {
66  ASDCP::PCM::MXFWriter mxf_writer;
67  ASDCP::PCM::FrameBuffer frame_buffer;
68  ASDCP::WriterInfo writer_info;
69  ASDCP::PCM::AudioDescriptor desc;
70 };
71 
72 
73 SoundAssetWriter::SoundAssetWriter(SoundAsset* asset, boost::filesystem::path file, vector<dcp::Channel> extra_active_channels, bool sync, bool include_mca_subdescriptors)
74  : AssetWriter (asset, file)
75  , _state (new SoundAssetWriter::ASDCPState)
76  , _asset (asset)
77  , _extra_active_channels(extra_active_channels)
78  , _sync (sync)
79  , _include_mca_subdescriptors(include_mca_subdescriptors)
80 {
81  DCP_ASSERT (!_sync || _asset->channels() >= 14);
82  DCP_ASSERT (!_sync || _asset->standard() == Standard::SMPTE);
83 
84  /* None of these are allowed in extra_active_channels; some are implicit, and (it seems) should never have a descriptor
85  * written for them.
86  */
87  vector<Channel> disallowed_extra = {
88  Channel::LEFT,
89  Channel::RIGHT,
90  Channel::CENTRE,
91  Channel::LFE,
92  Channel::LS,
93  Channel::RS,
94  Channel::MOTION_DATA,
95  Channel::SYNC_SIGNAL,
96  Channel::SIGN_LANGUAGE,
97  Channel::CHANNEL_COUNT
98  };
99  for (auto disallowed: disallowed_extra) {
100  DCP_ASSERT(std::find(extra_active_channels.begin(), extra_active_channels.end(), disallowed) == extra_active_channels.end());
101  }
102 
103  /* Derived from ASDCP::Wav::SimpleWaveHeader::FillADesc */
104  _state->desc.EditRate = ASDCP::Rational (_asset->edit_rate().numerator, _asset->edit_rate().denominator);
105  _state->desc.AudioSamplingRate = ASDCP::Rational (_asset->sampling_rate(), 1);
106  _state->desc.Locked = 0;
107  _state->desc.ChannelCount = _asset->channels();
108  _state->desc.QuantizationBits = 24;
109  _state->desc.BlockAlign = 3 * _asset->channels();
110  _state->desc.AvgBps = _asset->sampling_rate() * _state->desc.BlockAlign;
111  _state->desc.LinkedTrackID = 0;
112  if (asset->standard() == Standard::INTEROP) {
113  _state->desc.ChannelFormat = ASDCP::PCM::CF_NONE;
114  } else {
115  /* As required by Bv2.1 */
116  _state->desc.ChannelFormat = ASDCP::PCM::CF_CFG_4;
117  }
118 
119  /* I'm fairly sure this is not necessary, as ContainerDuration is written
120  in ASDCP's WriteMXFFooter, but it stops a valgrind warning.
121  */
122  _state->desc.ContainerDuration = 0;
123 
124  _state->frame_buffer.Capacity (ASDCP::PCM::CalcFrameBufferSize (_state->desc));
125  _state->frame_buffer.Size (ASDCP::PCM::CalcFrameBufferSize (_state->desc));
126  memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity());
127 
128  _asset->fill_writer_info (&_state->writer_info, _asset->id());
129 
130  if (_sync) {
131  _fsk.set_data (create_sync_packets());
132  }
133 }
134 
135 
136 SoundAssetWriter::~SoundAssetWriter()
137 {
138  try {
139  /* Last-resort finalization to close the file, at least */
140  if (!_finalized) {
141  _state->mxf_writer.Finalize();
142  }
143  } catch (...) {}
144 }
145 
146 
147 void
148 SoundAssetWriter::start ()
149 {
150  auto r = _state->mxf_writer.OpenWrite(dcp::filesystem::fix_long_path(_file).string().c_str(), _state->writer_info, _state->desc);
151  if (ASDCP_FAILURE(r)) {
152  boost::throw_exception (FileError("could not open audio MXF for writing", _file.string(), r));
153  }
154 
155  if (_asset->standard() == Standard::SMPTE && _include_mca_subdescriptors) {
156 
157  ASDCP::MXF::WaveAudioDescriptor* essence_descriptor = nullptr;
158  _state->mxf_writer.OP1aHeader().GetMDObjectByType(
159  asdcp_smpte_dict->ul(ASDCP::MDD_WaveAudioDescriptor), reinterpret_cast<ASDCP::MXF::InterchangeObject**>(&essence_descriptor)
160  );
161  DCP_ASSERT (essence_descriptor);
162  essence_descriptor->ChannelAssignment = asdcp_smpte_dict->ul(ASDCP::MDD_DCAudioChannelCfg_4_WTF);
163 
164  auto soundfield = new ASDCP::MXF::SoundfieldGroupLabelSubDescriptor(asdcp_smpte_dict);
165  GenRandomValue (soundfield->MCALinkID);
166  if (auto lang = _asset->language()) {
167  soundfield->RFC5646SpokenLanguage = *lang;
168  }
169 
170  MCASoundField const field =
171  (
172  find(_extra_active_channels.begin(), _extra_active_channels.end(), dcp::Channel::BSL) != _extra_active_channels.end() ||
173  find(_extra_active_channels.begin(), _extra_active_channels.end(), dcp::Channel::BSR) != _extra_active_channels.end()
174  ) ? MCASoundField::SEVEN_POINT_ONE : MCASoundField::FIVE_POINT_ONE;
175 
176  if (field == MCASoundField::SEVEN_POINT_ONE) {
177  soundfield->MCATagSymbol = "sg71";
178  soundfield->MCATagName = "7.1DS";
179 LIBDCP_DISABLE_WARNINGS
180  soundfield->MCALabelDictionaryID = asdcp_smpte_dict->ul(ASDCP::MDD_DCAudioSoundfield_71);
181 LIBDCP_ENABLE_WARNINGS
182  } else {
183  soundfield->MCATagSymbol = "sg51";
184  soundfield->MCATagName = "5.1";
185 LIBDCP_DISABLE_WARNINGS
186  soundfield->MCALabelDictionaryID = asdcp_smpte_dict->ul(ASDCP::MDD_DCAudioSoundfield_51);
187 LIBDCP_ENABLE_WARNINGS
188  }
189 
190  _state->mxf_writer.OP1aHeader().AddChildObject(soundfield);
191  essence_descriptor->SubDescriptors.push_back(soundfield->InstanceUID);
192 
193  /* We always make a descriptor for these channels if they are present in the asset;
194  * there's no way for the caller to tell us whether they are active or not.
195  */
196  std::vector<dcp::Channel> dcp_channels = {
200  Channel::LFE,
201  Channel::LS,
203  };
204 
205  /* We add descriptors for some extra channels that the caller gave us (we made sure earlier
206  * that nothing "bad" is in this list).
207  */
208  std::copy(_extra_active_channels.begin(), _extra_active_channels.end(), back_inserter(dcp_channels));
209 
210  /* Remove duplicates */
211  std::sort(dcp_channels.begin(), dcp_channels.end());
212  dcp_channels.erase(std::unique(dcp_channels.begin(), dcp_channels.end()), dcp_channels.end());
213 
214  /* Remove channels that aren't actually in this MXF at all */
215  dcp_channels.erase(
216  std::remove_if(dcp_channels.begin(), dcp_channels.end(), [this](dcp::Channel channel) {
217  return static_cast<int>(channel) >= _asset->channels();
218  }),
219  dcp_channels.end()
220  );
221 
222  for (auto dcp_channel: dcp_channels) {
223  auto channel = new ASDCP::MXF::AudioChannelLabelSubDescriptor(asdcp_smpte_dict);
224  GenRandomValue (channel->MCALinkID);
225  channel->SoundfieldGroupLinkID = soundfield->MCALinkID;
226  channel->MCAChannelID = static_cast<int>(dcp_channel) + 1;
227  channel->MCATagSymbol = "ch" + channel_to_mca_id(dcp_channel, field);
228  channel->MCATagName = channel_to_mca_name(dcp_channel, field);
229  if (auto lang = _asset->language()) {
230  channel->RFC5646SpokenLanguage = *lang;
231  }
232 LIBDCP_DISABLE_WARNINGS
233  channel->MCALabelDictionaryID = channel_to_mca_universal_label(dcp_channel, field, asdcp_smpte_dict);
234 LIBDCP_ENABLE_WARNINGS
235  _state->mxf_writer.OP1aHeader().AddChildObject(channel);
236  essence_descriptor->SubDescriptors.push_back(channel->InstanceUID);
237  }
238  }
239 
240  _asset->set_file (_file);
241  _started = true;
242 }
243 
244 
245 void
246 SoundAssetWriter::write(float const * const * data, int data_channels, int frames)
247 {
248  do_write(data, data_channels, frames);
249 }
250 
251 
252 void
253 SoundAssetWriter::write(int32_t const * const * data, int data_channels, int frames)
254 {
255  do_write(data, data_channels, frames);
256 }
257 
258 
259 void
260 SoundAssetWriter::write_current_frame ()
261 {
262  auto const r = _state->mxf_writer.WriteFrame (_state->frame_buffer, _crypto_context->context(), _crypto_context->hmac());
263  if (ASDCP_FAILURE(r)) {
264  boost::throw_exception (MiscError(String::compose("could not write audio MXF frame (%1)", static_cast<int>(r))));
265  }
266 
267  ++_frames_written;
268 
269  if (_sync) {
270  /* We need a new set of sync packets for this frame */
271  _fsk.set_data (create_sync_packets());
272  }
273 }
274 
275 bool
277 {
278  if (_frame_buffer_offset > 0) {
279  write_current_frame ();
280  }
281 
282  if (_started) {
283  auto const r = _state->mxf_writer.Finalize();
284  if (ASDCP_FAILURE(r)) {
285  boost::throw_exception (MiscError(String::compose ("could not finalise audio MXF (%1)", static_cast<int>(r))));
286  }
287  }
288 
290  return AssetWriter::finalize ();
291 }
292 
293 
295 vector<bool>
297 {
298  /* Parts of this code assumes 48kHz */
299  DCP_ASSERT (_asset->sampling_rate() == 48000);
300 
301  /* Encoding of edit rate */
302  int edit_rate_code = 0;
303  /* How many 0 bits are used to pad the end of the packet */
304  int remaining_bits = 0;
305  /* How many packets in this edit unit (i.e. "frame") */
306  int packets = 0;
307  auto const edit_rate = _asset->edit_rate ();
308  if (edit_rate == Fraction(24, 1)) {
309  edit_rate_code = 0;
310  remaining_bits = 25;
311  packets = 4;
312  } else if (edit_rate == Fraction(25, 1)) {
313  edit_rate_code = 1;
314  remaining_bits = 20;
315  packets = 4;
316  } else if (edit_rate == Fraction(30, 1)) {
317  edit_rate_code = 2;
318  remaining_bits = 0;
319  packets = 4;
320  } else if (edit_rate == Fraction(48, 1)) {
321  edit_rate_code = 3;
322  remaining_bits = 25;
323  packets = 2;
324  } else if (edit_rate == Fraction(50, 1)) {
325  edit_rate_code = 4;
326  remaining_bits = 20;
327  packets = 2;
328  } else if (edit_rate == Fraction(60, 1)) {
329  edit_rate_code = 5;
330  remaining_bits = 0;
331  packets = 2;
332  } else if (edit_rate == Fraction(96, 1)) {
333  edit_rate_code = 6;
334  remaining_bits = 25;
335  packets = 1;
336  } else if (edit_rate == Fraction(100, 1)) {
337  edit_rate_code = 7;
338  remaining_bits = 20;
339  packets = 1;
340  } else if (edit_rate == Fraction(120, 1)) {
341  edit_rate_code = 8;
342  remaining_bits = 0;
343  packets = 1;
344  }
345 
346  Bitstream bs;
347 
348  Kumu::UUID id;
349  DCP_ASSERT (id.DecodeHex(_asset->id().c_str()));
350 
351  for (int i = 0; i < packets; ++i) {
352  bs.write_from_byte (0x4d);
353  bs.write_from_byte (0x56);
354  bs.start_crc (0x1021);
355  bs.write_from_byte (edit_rate_code, 4);
356  bs.write_from_byte (0, 2);
357  bs.write_from_byte (_sync_packet, 2);
358  bs.write_from_byte (id.Value()[i * 4 + 0]);
359  bs.write_from_byte (id.Value()[i * 4 + 1]);
360  bs.write_from_byte (id.Value()[i * 4 + 2]);
361  bs.write_from_byte (id.Value()[i * 4 + 3]);
362  bs.write_from_word (_frames_written, 24);
363  bs.write_crc ();
364  bs.write_from_byte (0, 4);
365  bs.write_from_word (0, remaining_bits);
366 
367  ++_sync_packet;
368  if (_sync_packet == 4) {
369  _sync_packet = 0;
370  }
371  }
372 
373  return bs.get();
374 }
375 
376 
377 byte_t*
378 SoundAssetWriter::frame_buffer_data() const
379 {
380  return _state->frame_buffer.Data();
381 }
382 
383 
384 int
385 SoundAssetWriter::frame_buffer_capacity() const
386 {
387  return _state->frame_buffer.Capacity();
388 }
389 
Bitstream class.
Parent class for classes which can write MXF-based assets.
Definition: asset_writer.h:61
virtual bool finalize()
Definition: asset_writer.cc:65
boost::filesystem::path _file
Definition: asset_writer.h:81
int64_t _frames_written
Definition: asset_writer.h:85
void set_file(boost::filesystem::path file) const
Definition: asset.cc:163
An exception related to a file.
Definition: exceptions.h:56
A fraction (i.e. a thing with an integer numerator and an integer denominator).
Definition: types.h:168
A miscellaneous exception.
Definition: exceptions.h:94
A helper class for writing to SoundAssets.
void write(float const *const *data, int channels, int frames)
std::vector< bool > create_sync_packets()
Representation of a sound asset.
Definition: sound_asset.h:72
int sampling_rate() const
Definition: sound_asset.h:114
int64_t _intrinsic_duration
Definition: sound_asset.h:154
DCP_ASSERT macro.
Exceptions thrown by libdcp.
Namespace for everything in libdcp.
Definition: array_data.h:50
Channel
Definition: types.h:93
@ LFE
low-frequency effects (sub)
@ CENTRE
centre
@ RS
right surround
@ LS
left surround
SoundAsset class.
SoundAssetWriter class.