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 ()
{
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();
+}
+
#include "asset_writer.h"
+#include "dcp_assert.h"
#include "fsk.h"
#include "types.h"
+#include "sound_asset.h"
#include "sound_frame.h"
#include <memory>
#include <boost/filesystem.hpp>
namespace dcp {
+namespace sound_asset_writer {
+
+template <typename T>
+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;
*/
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 <class T>
+ 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 ();
#include <boost/filesystem.hpp>
#include <boost/random.hpp>
#include <boost/test/unit_test.hpp>
+#include <functional>
-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<void (shared_ptr<dcp::SoundAssetWriter>, 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<std::vector<float>> 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<float>(dist(rng)) / (1 << 23);
- }
- pointers[channel] = buffers[channel].data();
- }
-
- writer->write(pointers, 6, 2000);
+ write(writer, rng, dist);
writer->finalize();
dcp::SoundAsset check(path);
}
-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<dcp::SoundAssetWriter> writer, boost::random::mt19937& rng, boost::random::uniform_int_distribution<>& dist) {
+ std::vector<std::vector<float>> 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<float>(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<dcp::SoundAssetWriter> writer, boost::random::mt19937& rng, boost::random::uniform_int_distribution<>& dist) {
+ std::vector<std::vector<int32_t>> 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<void (shared_ptr<dcp::SoundAssetWriter>, 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<std::vector<float>> 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<float>(dist(rng)) / (1 << 23);
- }
- pointers[channel] = buffers[channel].data();
- }
-
- writer->write(pointers, 6, 2000);
+ write(writer, rng, dist);
writer->finalize();
dcp::SoundAsset check(path);
}
}
}
+
+
+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<dcp::SoundAssetWriter> writer, boost::random::mt19937& rng, boost::random::uniform_int_distribution<>& dist) {
+ std::vector<std::vector<float>> 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<float>(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<dcp::SoundAssetWriter> writer, boost::random::mt19937& rng, boost::random::uniform_int_distribution<>& dist) {
+ std::vector<std::vector<int32_t>> 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);
+}