41 #include "compose.hpp"
45 #include "filesystem.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)
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);
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());
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;
116 _state->desc.ChannelFormat = ASDCP::PCM::CF_CFG_4;
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()
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);
196 std::vector<dcp::Channel> dcp_channels = {
208 std::copy(_extra_active_channels.begin(), _extra_active_channels.end(), back_inserter(dcp_channels));
211 std::sort(dcp_channels.begin(), dcp_channels.end());
212 dcp_channels.erase(std::unique(dcp_channels.begin(), dcp_channels.end()), dcp_channels.end());
216 std::remove_if(dcp_channels.begin(), dcp_channels.end(), [
this](
dcp::Channel channel) {
217 return static_cast<int>(channel) >= _asset->channels();
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;
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);
248 do_write(data, data_channels, frames);
255 do_write(data, data_channels, frames);
260 SoundAssetWriter::write_current_frame ()
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))));
278 if (_frame_buffer_offset > 0) {
279 write_current_frame ();
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))));
302 int edit_rate_code = 0;
304 int remaining_bits = 0;
307 auto const edit_rate = _asset->edit_rate ();
312 }
else if (edit_rate ==
Fraction(25, 1)) {
316 }
else if (edit_rate ==
Fraction(30, 1)) {
320 }
else if (edit_rate ==
Fraction(48, 1)) {
324 }
else if (edit_rate ==
Fraction(50, 1)) {
328 }
else if (edit_rate ==
Fraction(60, 1)) {
332 }
else if (edit_rate ==
Fraction(96, 1)) {
336 }
else if (edit_rate ==
Fraction(100, 1)) {
340 }
else if (edit_rate ==
Fraction(120, 1)) {
349 DCP_ASSERT (
id.DecodeHex(_asset->id().c_str()));
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);
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]);
364 bs.write_from_byte (0, 4);
365 bs.write_from_word (0, remaining_bits);
378 SoundAssetWriter::frame_buffer_data()
const
380 return _state->frame_buffer.Data();
385 SoundAssetWriter::frame_buffer_capacity()
const
387 return _state->frame_buffer.Capacity();
Parent class for classes which can write MXF-based assets.
boost::filesystem::path _file
void set_file(boost::filesystem::path file) const
An exception related to a file.
A fraction (i.e. a thing with an integer numerator and an integer denominator).
A miscellaneous exception.
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.
int sampling_rate() const
int64_t _intrinsic_duration
Exceptions thrown by libdcp.
Namespace for everything in libdcp.
@ LFE
low-frequency effects (sub)