From 8c5aed0c60927ad3f22ef3b620a4c2e000316a65 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Thu, 16 Mar 2023 21:07:10 +0100 Subject: [PATCH] Allow writing to sound assets with int32_t as well as float. --- src/sound_asset_writer.cc | 65 ++++++---------- src/sound_asset_writer.h | 80 ++++++++++++++++++++ test/sound_asset_writer_test.cc | 126 +++++++++++++++++++++++++------- 3 files changed, 201 insertions(+), 70 deletions(-) diff --git a/src/sound_asset_writer.cc b/src/sound_asset_writer.cc index f789eb3b..0300407c 100644 --- a/src/sound_asset_writer.cc +++ b/src/sound_asset_writer.cc @@ -202,54 +202,17 @@ LIBDCP_ENABLE_WARNINGS void SoundAssetWriter::write(float const * const * data, int data_channels, int frames) { - DCP_ASSERT (!_finalized); - DCP_ASSERT (frames > 0); - - auto const asset_channels = _asset->channels(); - DCP_ASSERT (data_channels <= asset_channels); - - static float const clip = 1.0f - (1.0f / pow (2, 23)); - - if (!_started) { - start (); - } - - for (int i = 0; i < frames; ++i) { - - byte_t* out = _state->frame_buffer.Data() + _frame_buffer_offset; - - /* Write one sample per asset channel */ - for (int j = 0; j < asset_channels; ++j) { - int32_t s = 0; - if (j == 13 && _sync) { - s = _fsk.get(); - } else if (j < data_channels) { - /* Convert sample to 24-bit int, clipping if necessary. */ - float x = data[j][i]; - if (x > clip) { - x = clip; - } else if (x < -clip) { - x = -clip; - } - s = x * (1 << 23); - } - *out++ = (s & 0xff); - *out++ = (s & 0xff00) >> 8; - *out++ = (s & 0xff0000) >> 16; - } - _frame_buffer_offset += 3 * asset_channels; + do_write(data, data_channels, frames); +} - DCP_ASSERT (_frame_buffer_offset <= int(_state->frame_buffer.Capacity())); - /* Finish the MXF frame if required */ - if (_frame_buffer_offset == int (_state->frame_buffer.Capacity())) { - write_current_frame (); - _frame_buffer_offset = 0; - memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity()); - } - } +void +SoundAssetWriter::write(int32_t const * const * data, int data_channels, int frames) +{ + do_write(data, data_channels, frames); } + void SoundAssetWriter::write_current_frame () { @@ -367,3 +330,17 @@ SoundAssetWriter::create_sync_packets () return bs.get(); } + +byte_t* +SoundAssetWriter::frame_buffer_data() const +{ + return _state->frame_buffer.Data(); +} + + +int +SoundAssetWriter::frame_buffer_capacity() const +{ + return _state->frame_buffer.Capacity(); +} + diff --git a/src/sound_asset_writer.h b/src/sound_asset_writer.h index 6d8191c7..d8ebdc7b 100644 --- a/src/sound_asset_writer.h +++ b/src/sound_asset_writer.h @@ -38,8 +38,10 @@ #include "asset_writer.h" +#include "dcp_assert.h" #include "fsk.h" #include "types.h" +#include "sound_asset.h" #include "sound_frame.h" #include #include @@ -52,6 +54,30 @@ struct sync_test1; namespace dcp { +namespace sound_asset_writer { + +template +int32_t convert(T) { return {}; } + +template <> +inline int32_t convert(int32_t x) +{ + int constexpr clip = (1 << 23); + return std::max(-clip, std::min(clip, x)); +} + +template<> +inline int32_t convert(float x) +{ + float constexpr clip = 1.0f - (1.0f / (1 << 23)); + float constexpr scale = (1 << 23); + auto const clipped = std::max(-clip, std::min(clip, x)); + return std::lround(clipped * scale); +} + +} + + class SoundAsset; @@ -76,12 +102,66 @@ public: */ void write(float const * const * data, int channels, int frames); + /** @param data Pointer an array of int32_t pointers, one for each channel. + * The 24-bit audio sample should be in the lower 24 bits of the int32_t. + * @param channels Number of channels in data; if this is less than the channels in the asset + * the remaining asset channels will be padded with silence. + * @param frames Number of frames i.e. number of floats that are given for each channel. + */ + void write(int32_t const * const * data, int channels, int frames); + bool finalize () override; private: friend class SoundAsset; friend struct ::sync_test1; + byte_t* frame_buffer_data() const; + int frame_buffer_capacity() const; + + template + void + do_write(T const * const * data, int data_channels, int frames) + { + DCP_ASSERT(!_finalized); + DCP_ASSERT(frames > 0); + + auto const asset_channels = _asset->channels(); + DCP_ASSERT(data_channels <= asset_channels); + + if (!_started) { + start(); + } + + for (int i = 0; i < frames; ++i) { + + auto out = frame_buffer_data() + _frame_buffer_offset; + + /* Write one sample per asset channel */ + for (int j = 0; j < asset_channels; ++j) { + int32_t s = 0; + if (j == 13 && _sync) { + s = _fsk.get(); + } else if (j < data_channels) { + s = sound_asset_writer::convert(data[j][i]); + } + *out++ = (s & 0xff); + *out++ = (s & 0xff00) >> 8; + *out++ = (s & 0xff0000) >> 16; + } + _frame_buffer_offset += 3 * asset_channels; + + DCP_ASSERT(_frame_buffer_offset <= frame_buffer_capacity()); + + /* Finish the MXF frame if required */ + if (_frame_buffer_offset == frame_buffer_capacity()) { + write_current_frame(); + _frame_buffer_offset = 0; + memset(frame_buffer_data(), 0, frame_buffer_capacity()); + } + } + } + SoundAssetWriter(SoundAsset *, boost::filesystem::path, bool sync, bool include_mca_subdescriptors); void start (); diff --git a/test/sound_asset_writer_test.cc b/test/sound_asset_writer_test.cc index 32ab06b5..a413a174 100644 --- a/test/sound_asset_writer_test.cc +++ b/test/sound_asset_writer_test.cc @@ -37,28 +37,23 @@ #include #include #include +#include -BOOST_AUTO_TEST_CASE(sound_asset_writer_no_padding_test) +using std::shared_ptr; + + +static +void +no_padding_test(boost::filesystem::path path, std::function, boost::random::mt19937&, boost::random::uniform_int_distribution<>&)> write) { - auto path = boost::filesystem::path("build/test/sound_asset_writer_no_padding_test.mxf"); dcp::SoundAsset asset({24, 1}, 48000, 6, dcp::LanguageTag{"en-GB"}, dcp::Standard::SMPTE); auto writer = asset.start_write(path); boost::random::mt19937 rng(1); boost::random::uniform_int_distribution<> dist(0, 32767); - std::vector> buffers(6); - float* pointers[6]; - for (auto channel = 0; channel < 6; ++channel) { - buffers[channel].resize(2000); - for (int sample = 0; sample < 2000; ++sample) { - buffers[channel][sample] = static_cast(dist(rng)) / (1 << 23); - } - pointers[channel] = buffers[channel].data(); - } - - writer->write(pointers, 6, 2000); + write(writer, rng, dist); writer->finalize(); dcp::SoundAsset check(path); @@ -75,26 +70,61 @@ BOOST_AUTO_TEST_CASE(sound_asset_writer_no_padding_test) } -BOOST_AUTO_TEST_CASE(sound_asset_writer_padding_test) +BOOST_AUTO_TEST_CASE(sound_asset_writer_float_no_padding_test) +{ + auto path = boost::filesystem::path("build/test/sound_asset_writer_float_no_padding_test.mxf"); + + auto write = [](shared_ptr writer, boost::random::mt19937& rng, boost::random::uniform_int_distribution<>& dist) { + std::vector> buffers(6); + float* pointers[6]; + for (auto channel = 0; channel < 6; ++channel) { + buffers[channel].resize(2000); + for (int sample = 0; sample < 2000; ++sample) { + buffers[channel][sample] = static_cast(dist(rng)) / (1 << 23); + } + pointers[channel] = buffers[channel].data(); + } + + writer->write(pointers, 6, 2000); + }; + + no_padding_test(path, write); +} + + +BOOST_AUTO_TEST_CASE(sound_asset_writer_int_no_padding_test) +{ + auto path = boost::filesystem::path("build/test/sound_asset_writer_int_no_padding_test.mxf"); + + auto write = [](shared_ptr writer, boost::random::mt19937& rng, boost::random::uniform_int_distribution<>& dist) { + std::vector> buffers(6); + int32_t* pointers[6]; + for (auto channel = 0; channel < 6; ++channel) { + buffers[channel].resize(2000); + for (int sample = 0; sample < 2000; ++sample) { + buffers[channel][sample] = dist(rng); + } + pointers[channel] = buffers[channel].data(); + } + + writer->write(pointers, 6, 2000); + }; + + no_padding_test(path, write); +} + + +static +void +padding_test(boost::filesystem::path path, std::function, boost::random::mt19937&, boost::random::uniform_int_distribution<>&)> write) { - auto path = boost::filesystem::path("build/test/sound_asset_writer_padding_test.mxf"); dcp::SoundAsset asset({24, 1}, 48000, 14, dcp::LanguageTag{"en-GB"}, dcp::Standard::SMPTE); auto writer = asset.start_write(path); boost::random::mt19937 rng(1); boost::random::uniform_int_distribution<> dist(0, 32767); - std::vector> buffers(6); - float* pointers[6]; - for (auto channel = 0; channel < 6; ++channel) { - buffers[channel].resize(2000); - for (int sample = 0; sample < 2000; ++sample) { - buffers[channel][sample] = static_cast(dist(rng)) / (1 << 23); - } - pointers[channel] = buffers[channel].data(); - } - - writer->write(pointers, 6, 2000); + write(writer, rng, dist); writer->finalize(); dcp::SoundAsset check(path); @@ -115,3 +145,47 @@ BOOST_AUTO_TEST_CASE(sound_asset_writer_padding_test) } } } + + +BOOST_AUTO_TEST_CASE(sound_asset_writer_float_padding_test) +{ + auto path = boost::filesystem::path("build/test/sound_asset_writer_float_padding_test.mxf"); + + auto write = [](shared_ptr writer, boost::random::mt19937& rng, boost::random::uniform_int_distribution<>& dist) { + std::vector> buffers(6); + float* pointers[6]; + for (auto channel = 0; channel < 6; ++channel) { + buffers[channel].resize(2000); + for (int sample = 0; sample < 2000; ++sample) { + buffers[channel][sample] = static_cast(dist(rng)) / (1 << 23); + } + pointers[channel] = buffers[channel].data(); + } + + writer->write(pointers, 6, 2000); + }; + + padding_test(path, write); +} + + +BOOST_AUTO_TEST_CASE(sound_asset_writer_int_padding_test) +{ + auto path = boost::filesystem::path("build/test/sound_asset_writer_int_padding_test.mxf"); + + auto write = [](shared_ptr writer, boost::random::mt19937& rng, boost::random::uniform_int_distribution<>& dist) { + std::vector> buffers(6); + int32_t* pointers[6]; + for (auto channel = 0; channel < 6; ++channel) { + buffers[channel].resize(2000); + for (int sample = 0; sample < 2000; ++sample) { + buffers[channel][sample] = dist(rng); + } + pointers[channel] = buffers[channel].data(); + } + + writer->write(pointers, 6, 2000); + }; + + padding_test(path, write); +} -- 2.30.2