2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
35 /** @file src/sound_asset_writer.cc
36 * @brief SoundAssetWriter class
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"
49 LIBDCP_DISABLE_WARNINGS
50 #include <asdcp/AS_DCP.h>
51 #include <asdcp/Metadata.h>
52 LIBDCP_ENABLE_WARNINGS
64 struct SoundAssetWriter::ASDCPState
66 ASDCP::PCM::MXFWriter mxf_writer;
67 ASDCP::PCM::FrameBuffer frame_buffer;
68 ASDCP::WriterInfo writer_info;
69 ASDCP::PCM::AudioDescriptor desc;
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)
77 , _extra_active_channels(extra_active_channels)
79 , _include_mca_subdescriptors(include_mca_subdescriptors)
81 DCP_ASSERT (!_sync || _asset->channels() >= 14);
82 DCP_ASSERT (!_sync || _asset->standard() == Standard::SMPTE);
84 /* None of these are allowed in extra_active_channels; some are implicit, and (it seems) should never have a descriptor
87 vector<Channel> disallowed_extra = {
96 Channel::SIGN_LANGUAGE,
97 Channel::CHANNEL_COUNT
99 for (auto disallowed: disallowed_extra) {
100 DCP_ASSERT(std::find(extra_active_channels.begin(), extra_active_channels.end(), disallowed) == extra_active_channels.end());
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;
115 /* As required by Bv2.1 */
116 _state->desc.ChannelFormat = ASDCP::PCM::CF_CFG_4;
119 /* I'm fairly sure this is not necessary, as ContainerDuration is written
120 in ASDCP's WriteMXFFooter, but it stops a valgrind warning.
122 _state->desc.ContainerDuration = 0;
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());
128 _asset->fill_writer_info (&_state->writer_info, _asset->id());
131 _fsk.set_data (create_sync_packets());
136 SoundAssetWriter::~SoundAssetWriter()
139 /* Last-resort finalization to close the file, at least */
141 _state->mxf_writer.Finalize();
148 SoundAssetWriter::start ()
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));
155 if (_asset->standard() == Standard::SMPTE && _include_mca_subdescriptors) {
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)
161 DCP_ASSERT (essence_descriptor);
162 essence_descriptor->ChannelAssignment = asdcp_smpte_dict->ul(ASDCP::MDD_DCAudioChannelCfg_4_WTF);
164 auto soundfield = new ASDCP::MXF::SoundfieldGroupLabelSubDescriptor(asdcp_smpte_dict);
165 GenRandomValue (soundfield->MCALinkID);
166 if (auto lang = _asset->language()) {
167 soundfield->RFC5646SpokenLanguage = *lang;
170 MCASoundField const field =
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;
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
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
190 _state->mxf_writer.OP1aHeader().AddChildObject(soundfield);
191 essence_descriptor->SubDescriptors.push_back(soundfield->InstanceUID);
193 std::vector<dcp::Channel> dcp_channels = {
202 std::copy(_extra_active_channels.begin(), _extra_active_channels.end(), back_inserter(dcp_channels));
203 std::sort(dcp_channels.begin(), dcp_channels.end());
204 auto last = std::unique(dcp_channels.begin(), dcp_channels.end());
205 dcp_channels.erase(last, dcp_channels.end());
207 for (auto dcp_channel: dcp_channels) {
208 auto channel = new ASDCP::MXF::AudioChannelLabelSubDescriptor(asdcp_smpte_dict);
209 GenRandomValue (channel->MCALinkID);
210 channel->SoundfieldGroupLinkID = soundfield->MCALinkID;
211 channel->MCAChannelID = static_cast<int>(dcp_channel) + 1;
212 channel->MCATagSymbol = "ch" + channel_to_mca_id(dcp_channel, field);
213 channel->MCATagName = channel_to_mca_name(dcp_channel, field);
214 if (auto lang = _asset->language()) {
215 channel->RFC5646SpokenLanguage = *lang;
217 LIBDCP_DISABLE_WARNINGS
218 channel->MCALabelDictionaryID = channel_to_mca_universal_label(dcp_channel, field, asdcp_smpte_dict);
219 LIBDCP_ENABLE_WARNINGS
220 _state->mxf_writer.OP1aHeader().AddChildObject(channel);
221 essence_descriptor->SubDescriptors.push_back(channel->InstanceUID);
225 _asset->set_file (_file);
231 SoundAssetWriter::write(float const * const * data, int data_channels, int frames)
233 do_write(data, data_channels, frames);
238 SoundAssetWriter::write(int32_t const * const * data, int data_channels, int frames)
240 do_write(data, data_channels, frames);
245 SoundAssetWriter::write_current_frame ()
247 auto const r = _state->mxf_writer.WriteFrame (_state->frame_buffer, _crypto_context->context(), _crypto_context->hmac());
248 if (ASDCP_FAILURE(r)) {
249 boost::throw_exception (MiscError(String::compose("could not write audio MXF frame (%1)", static_cast<int>(r))));
255 /* We need a new set of sync packets for this frame */
256 _fsk.set_data (create_sync_packets());
261 SoundAssetWriter::finalize ()
263 if (_frame_buffer_offset > 0) {
264 write_current_frame ();
268 auto const r = _state->mxf_writer.Finalize();
269 if (ASDCP_FAILURE(r)) {
270 boost::throw_exception (MiscError(String::compose ("could not finalise audio MXF (%1)", static_cast<int>(r))));
274 _asset->_intrinsic_duration = _frames_written;
275 return AssetWriter::finalize ();
279 /** Calculate and return the sync packets required for this edit unit (aka "frame") */
281 SoundAssetWriter::create_sync_packets ()
283 /* Parts of this code assumes 48kHz */
284 DCP_ASSERT (_asset->sampling_rate() == 48000);
286 /* Encoding of edit rate */
287 int edit_rate_code = 0;
288 /* How many 0 bits are used to pad the end of the packet */
289 int remaining_bits = 0;
290 /* How many packets in this edit unit (i.e. "frame") */
292 auto const edit_rate = _asset->edit_rate ();
293 if (edit_rate == Fraction(24, 1)) {
297 } else if (edit_rate == Fraction(25, 1)) {
301 } else if (edit_rate == Fraction(30, 1)) {
305 } else if (edit_rate == Fraction(48, 1)) {
309 } else if (edit_rate == Fraction(50, 1)) {
313 } else if (edit_rate == Fraction(60, 1)) {
317 } else if (edit_rate == Fraction(96, 1)) {
321 } else if (edit_rate == Fraction(100, 1)) {
325 } else if (edit_rate == Fraction(120, 1)) {
334 DCP_ASSERT (id.DecodeHex(_asset->id().c_str()));
336 for (int i = 0; i < packets; ++i) {
337 bs.write_from_byte (0x4d);
338 bs.write_from_byte (0x56);
339 bs.start_crc (0x1021);
340 bs.write_from_byte (edit_rate_code, 4);
341 bs.write_from_byte (0, 2);
342 bs.write_from_byte (_sync_packet, 2);
343 bs.write_from_byte (id.Value()[i * 4 + 0]);
344 bs.write_from_byte (id.Value()[i * 4 + 1]);
345 bs.write_from_byte (id.Value()[i * 4 + 2]);
346 bs.write_from_byte (id.Value()[i * 4 + 3]);
347 bs.write_from_word (_frames_written, 24);
349 bs.write_from_byte (0, 4);
350 bs.write_from_word (0, remaining_bits);
353 if (_sync_packet == 4) {
363 SoundAssetWriter::frame_buffer_data() const
365 return _state->frame_buffer.Data();
370 SoundAssetWriter::frame_buffer_capacity() const
372 return _state->frame_buffer.Capacity();