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