Allow creation of ATMOS sync on channel 14.
authorCarl Hetherington <cth@carlh.net>
Fri, 10 Jul 2020 22:12:50 +0000 (00:12 +0200)
committerCarl Hetherington <cth@carlh.net>
Sat, 11 Jul 2020 21:45:52 +0000 (23:45 +0200)
12 files changed:
src/bitstream.cc [new file with mode: 0644]
src/bitstream.h [new file with mode: 0644]
src/fsk.cc [new file with mode: 0644]
src/fsk.h [new file with mode: 0644]
src/object.h
src/sound_asset.cc
src/sound_asset.h
src/sound_asset_writer.cc
src/sound_asset_writer.h
src/wscript
test/sync_test.cc [new file with mode: 0644]
test/wscript

diff --git a/src/bitstream.cc b/src/bitstream.cc
new file mode 100644 (file)
index 0000000..13d5ae4
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "bitstream.h"
+#include "dcp_assert.h"
+#include <iostream>
+#include <stdint.h>
+#include <string.h>
+
+
+using namespace dcp;
+
+
+void
+Bitstream::write_bit (bool bit)
+{
+       if (_crc) {
+               _crc->process_bit (bit);
+       }
+       _data.push_back (bit);
+}
+
+
+void
+Bitstream::write_from_byte (uint8_t byte, int bits)
+{
+       for (int i = bits - 1; i >= 0; --i) {
+               write_bit ((byte >> i) & 1);
+       }
+}
+
+
+void
+Bitstream::write_from_word (uint32_t word, int bits)
+{
+       for (int i = bits - 1; i >= 0; --i) {
+               write_bit ((word >> i) & 1);
+       }
+}
+
+
+void
+Bitstream::start_crc (uint16_t poly)
+{
+       DCP_ASSERT (!static_cast<bool>(_crc));
+       _crc = boost::crc_basic<16> (poly);
+}
+
+
+void
+Bitstream::write_crc ()
+{
+       DCP_ASSERT (static_cast<bool>(_crc));
+       uint16_t crc = _crc->checksum();
+       write_from_word (crc, 16);
+       _crc = boost::none;
+}
+
diff --git a/src/bitstream.h b/src/bitstream.h
new file mode 100644 (file)
index 0000000..1295f79
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include <boost/crc.hpp>
+#include <boost/optional.hpp>
+#include <stdint.h>
+#include <vector>
+
+
+namespace dcp {
+
+class Bitstream
+{
+public:
+       void start_crc (uint16_t poly);
+       void write_bit (bool bit);
+       void write_from_byte (uint8_t byte, int bits = 8);
+       void write_from_word (uint32_t word, int bits = 32);
+       void write_crc ();
+
+       std::vector<bool> get() const {
+               return _data;
+       }
+
+private:
+       std::vector<bool> _data;
+       boost::optional<boost::crc_basic<16> > _crc;
+};
+
+}
diff --git a/src/fsk.cc b/src/fsk.cc
new file mode 100644 (file)
index 0000000..4755e56
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "fsk.h"
+#include <iostream>
+
+
+using std::cout;
+using std::vector;
+using namespace dcp;
+
+
+FSK::FSK ()
+       : _data_position (0)
+       , _sample_position (0)
+       , _last_polarity (false)
+       , _last_bit (false)
+{
+
+}
+
+
+void
+FSK::set_data (vector<bool> data)
+{
+       _data = data;
+       _data_position = _sample_position = 0;
+}
+
+
+int32_t
+FSK::get ()
+{
+       static int const twenty_four_bit = 8388608; // 2^23
+
+       static int const lut[4][2] = {
+               // sample 0
+               {
+                       int( 0.03827 * twenty_four_bit), // 0
+                       int( 0.07071 * twenty_four_bit), // 1
+               },
+               // sample 1
+               {
+                       int( 0.09239 * twenty_four_bit), // 0
+                       int( 0.07071 * twenty_four_bit), // 1
+               },
+               // sample 2
+               {
+                       int( 0.09239 * twenty_four_bit), // 0
+                       int(-0.07071 * twenty_four_bit), // 1
+               },
+               // sample 3
+               {
+                       int( 0.03827 * twenty_four_bit), // 0
+                       int(-0.07071 * twenty_four_bit), // 1
+               }
+       };
+
+       /* The bit we are working on */
+       bool const bit = _data[_data_position];
+       /* Get the +ve version of the required sample */
+       int sample = lut[_sample_position][bit];
+
+       bool polarity = _last_polarity;
+       if (_sample_position == 0 && _last_bit == false) {
+               /* We're starting a new bit, and the last one was 0 so we need to flip
+                * the polarity we are using.
+                */
+               polarity = !polarity;
+       }
+
+       /* Obey the required polarity for this sample */
+       if (polarity == false) {
+               sample = -sample;
+       }
+
+       /* Get ready for next time */
+
+       _last_bit = bit;
+       _last_polarity = polarity;
+
+       ++_sample_position;
+       if (_sample_position == 4) {
+               _sample_position = 0;
+               ++_data_position;
+       }
+
+       return sample;
+}
+
diff --git a/src/fsk.h b/src/fsk.h
new file mode 100644 (file)
index 0000000..c73eb17
--- /dev/null
+++ b/src/fsk.h
@@ -0,0 +1,74 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include <stdint.h>
+#include <vector>
+
+
+namespace dcp {
+
+
+/** @class FSK
+ *  @brief Create frequency-shift-keyed samples for encoding synchronization signals.
+ *
+ *  An array of data is given to a FSK object using set_data(), and on calling get()
+ *  this data will be returned in a the D-Cinema FSK "format", sample by sample, starting
+ *  with the MSB of the first byte in the data array.
+ *
+ */
+class FSK
+{
+public:
+       FSK ();
+
+       void set_data (std::vector<bool> data);
+
+       /** @return the next sample as a 24-bit signed integer */
+       int32_t get ();
+
+private:
+       std::vector<bool> _data;
+       /** current offset into _data */
+       int _data_position;
+       /** current sample number of the current bit (0-3) */
+       int _sample_position;
+       /** polarity of the last bit to be written (false for -ve, true for +ve) */
+       bool _last_polarity;
+       /** value of the last bit to be written */
+       bool _last_bit;
+};
+
+}
+
+
index 0132a0db12c3cec2ba38999ac8a8248135b07418..aa0fe6b34c11e0a0fa08214f5babfb0f55135266 100644 (file)
@@ -47,6 +47,7 @@ struct write_interop_subtitle_test3;
 struct write_smpte_subtitle_test;
 struct write_smpte_subtitle_test2;
 struct write_smpte_subtitle_test3;
+struct sync_test2;
 
 namespace dcp {
 
@@ -72,6 +73,7 @@ protected:
        friend struct ::write_smpte_subtitle_test;
        friend struct ::write_smpte_subtitle_test2;
        friend struct ::write_smpte_subtitle_test3;
+       friend struct ::sync_test2;
 
        /** ID */
        std::string _id;
index c28a33feb07d2a58d5ce12a3e4203b27ee7e5e01..7f2bf5e3c45c29e08d0a4bdb4771b66df76dcccc 100644 (file)
@@ -193,10 +193,13 @@ SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHand
 }
 
 shared_ptr<SoundAssetWriter>
-SoundAsset::start_write (boost::filesystem::path file)
+SoundAsset::start_write (boost::filesystem::path file, bool atmos_sync)
 {
-       /* XXX: can't we use a shared_ptr here? */
-       return shared_ptr<SoundAssetWriter> (new SoundAssetWriter (this, file));
+       if (atmos_sync && _channels < 14) {
+               throw MiscError ("Insufficient channels to write ATMOS sync (there must be at least 14)");
+       }
+
+       return shared_ptr<SoundAssetWriter> (new SoundAssetWriter(this, file, atmos_sync));
 }
 
 shared_ptr<SoundAssetReader>
index 509ae559316bd800a9cde9da2e3f878bf54957e8..9656cf904d1758dbbc3596bb3b0a27f81752bd75 100644 (file)
@@ -58,7 +58,7 @@ public:
        explicit SoundAsset (boost::filesystem::path file);
        SoundAsset (Fraction edit_rate, int sampling_rate, int channels, Standard standard);
 
-       boost::shared_ptr<SoundAssetWriter> start_write (boost::filesystem::path file);
+       boost::shared_ptr<SoundAssetWriter> start_write (boost::filesystem::path file, bool atmos_sync = false);
        boost::shared_ptr<SoundAssetReader> start_read () const;
 
        bool equals (
index 8df4d911a0334377bbe01f7b71e71581cba0f648..ff1d02c23057fd5458aa5826ff1675a0884e8ed7 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
@@ -31,6 +31,7 @@
     files in the program, then also delete it here.
 */
 
+#include "bitstream.h"
 #include "sound_asset_writer.h"
 #include "sound_asset.h"
 #include "exceptions.h"
@@ -43,6 +44,8 @@
 using std::min;
 using std::max;
 using std::cout;
+using std::string;
+using std::vector;
 using namespace dcp;
 
 struct SoundAssetWriter::ASDCPState
@@ -53,12 +56,17 @@ struct SoundAssetWriter::ASDCPState
        ASDCP::PCM::AudioDescriptor desc;
 };
 
-SoundAssetWriter::SoundAssetWriter (SoundAsset* asset, boost::filesystem::path file)
+SoundAssetWriter::SoundAssetWriter (SoundAsset* asset, boost::filesystem::path file, bool sync)
        : AssetWriter (asset, file)
        , _state (new SoundAssetWriter::ASDCPState)
        , _asset (asset)
        , _frame_buffer_offset (0)
+       , _sync (sync)
+       , _sync_packet (0)
 {
+       DCP_ASSERT (!_sync || _asset->channels() >= 14);
+       DCP_ASSERT (!_sync || _asset->standard() == SMPTE);
+
        /* Derived from ASDCP::Wav::SimpleWaveHeader::FillADesc */
        _state->desc.EditRate = ASDCP::Rational (_asset->edit_rate().numerator, _asset->edit_rate().denominator);
        _state->desc.AudioSamplingRate = ASDCP::Rational (_asset->sampling_rate(), 1);
@@ -87,6 +95,10 @@ SoundAssetWriter::SoundAssetWriter (SoundAsset* asset, boost::filesystem::path f
        memset (_state->frame_buffer.Data(), 0, _state->frame_buffer.Capacity());
 
        _asset->fill_writer_info (&_state->writer_info, _asset->id());
+
+       if (_sync) {
+               _fsk.set_data (create_sync_packets());
+       }
 }
 
 void
@@ -115,14 +127,19 @@ SoundAssetWriter::write (float const * const * data, int frames)
 
                /* Write one sample per channel */
                for (int j = 0; j < ch; ++j) {
-                       /* 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;
+                       int32_t s = 0;
+                       if (j == 13 && _sync) {
+                               s = _fsk.get();
+                       } else {
+                               /* 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);
                        }
-                       int32_t const s = x * (1 << 23);
                        *out++ = (s & 0xff);
                        *out++ = (s & 0xff00) >> 8;
                        *out++ = (s & 0xff0000) >> 16;
@@ -149,6 +166,11 @@ SoundAssetWriter::write_current_frame ()
        }
 
        ++_frames_written;
+
+       if (_sync) {
+               /* We need a new set of sync packets for this frame */
+               _fsk.set_data (create_sync_packets());
+       }
 }
 
 bool
@@ -168,3 +190,87 @@ SoundAssetWriter::finalize ()
        _asset->_intrinsic_duration = _frames_written;
        return AssetWriter::finalize ();
 }
+
+
+/** Calculate and return the sync packets required for this edit unit (aka "frame") */
+vector<bool>
+SoundAssetWriter::create_sync_packets ()
+{
+       /* Parts of this code assumes 48kHz */
+       DCP_ASSERT (_asset->sampling_rate() == 48000);
+
+       /* Encoding of edit rate */
+       int edit_rate_code = 0;
+       /* How many 0 bits are used to pad the end of the packet */
+       int remaining_bits = 0;
+       /* How many packets in this edit unit (i.e. "frame") */
+       int packets = 0;
+       Fraction const edit_rate = _asset->edit_rate ();
+       if (edit_rate == Fraction(24, 1)) {
+               edit_rate_code = 0;
+               remaining_bits = 25;
+               packets = 4;
+       } else if (edit_rate == Fraction(25, 1)) {
+               edit_rate_code = 1;
+               remaining_bits = 20;
+               packets = 4;
+       } else if (edit_rate == Fraction(30, 1)) {
+               edit_rate_code = 2;
+               remaining_bits = 0;
+               packets = 4;
+       } else if (edit_rate == Fraction(48, 1)) {
+               edit_rate_code = 3;
+               remaining_bits = 25;
+               packets = 2;
+       } else if (edit_rate == Fraction(50, 1)) {
+               edit_rate_code = 4;
+               remaining_bits = 20;
+               packets = 2;
+       } else if (edit_rate == Fraction(60, 1)) {
+               edit_rate_code = 5;
+               remaining_bits = 0;
+               packets = 2;
+       } else if (edit_rate == Fraction(96, 1)) {
+               edit_rate_code = 6;
+               remaining_bits = 25;
+               packets = 1;
+       } else if (edit_rate == Fraction(100, 1)) {
+               edit_rate_code = 7;
+               remaining_bits = 20;
+               packets = 1;
+       } else if (edit_rate == Fraction(120, 1)) {
+               edit_rate_code = 8;
+               remaining_bits = 0;
+               packets = 1;
+       }
+
+       Bitstream bs;
+
+       Kumu::UUID id;
+       DCP_ASSERT (id.DecodeHex(_asset->id().c_str()));
+
+       for (int i = 0; i < packets; ++i) {
+               bs.write_from_byte (0x4d);
+               bs.write_from_byte (0x56);
+               bs.start_crc (0x1021);
+               bs.write_from_byte (edit_rate_code, 4);
+               bs.write_from_byte (0, 2);
+               bs.write_from_byte (_sync_packet, 2);
+               bs.write_from_byte (id.Value()[i * 4 + 0]);
+               bs.write_from_byte (id.Value()[i * 4 + 1]);
+               bs.write_from_byte (id.Value()[i * 4 + 2]);
+               bs.write_from_byte (id.Value()[i * 4 + 3]);
+               bs.write_from_word (_frames_written, 24);
+               bs.write_crc ();
+               bs.write_from_byte (0, 4);
+               bs.write_from_word (0, remaining_bits);
+
+               ++_sync_packet;
+               if (_sync_packet == 4) {
+                       _sync_packet = 0;
+               }
+       }
+
+       return bs.get();
+}
+
index b2de9e8eed4182a5034734751594f46ef821b75f..f1b1514d800830275b828b549ff5b244a16a5f2c 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
  */
 
 #include "asset_writer.h"
+#include "fsk.h"
 #include "types.h"
 #include "sound_frame.h"
 #include <boost/shared_ptr.hpp>
 #include <boost/filesystem.hpp>
+#include <boost/shared_array.hpp>
+
+
+struct sync_test1;
+
 
 namespace dcp {
 
@@ -62,10 +68,12 @@ public:
 
 private:
        friend class SoundAsset;
+       friend struct ::sync_test1;
 
-       SoundAssetWriter (SoundAsset *, boost::filesystem::path);
+       SoundAssetWriter (SoundAsset *, boost::filesystem::path, bool sync);
 
        void write_current_frame ();
+       std::vector<bool> create_sync_packets ();
 
        /* do this with an opaque pointer so we don't have to include
           ASDCP headers
@@ -76,6 +84,12 @@ private:
 
        SoundAsset* _asset;
        int _frame_buffer_offset;
+
+       /** true to ignore any signal passed to write() on channel 14 and instead write a sync track */
+       bool _sync;
+       /** index of the sync packet (0-3) which starts the next edit unit */
+       int _sync_packet;
+       FSK _fsk;
 };
 
 }
index 85543d9dcaf464eb96d4b8cefdb94c3dfd79255e..8dd856c6a060060ba7d12ac284f30481e4b50d7c 100644 (file)
@@ -39,6 +39,7 @@ def build(bld):
              asset_writer.cc
              atmos_asset.cc
              atmos_asset_writer.cc
+             bitstream.cc
              certificate_chain.cc
              certificate.cc
              chromaticity.cc
@@ -53,6 +54,7 @@ def build(bld):
              exceptions.cc
              file.cc
              font_asset.cc
+             fsk.cc
              gamma_transfer_function.cc
              identity_transfer_function.cc
              interop_load_font_node.cc
@@ -132,6 +134,7 @@ def build(bld):
               exceptions.h
               font_asset.h
               frame.h
+              fsk.h
               gamma_transfer_function.h
               identity_transfer_function.h
               interop_load_font_node.h
diff --git a/test/sync_test.cc b/test/sync_test.cc
new file mode 100644 (file)
index 0000000..ff3adf1
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    libdcp is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "sound_asset.h"
+#include "sound_asset_reader.h"
+#include "sound_asset_writer.h"
+#include "test.h"
+#include <boost/filesystem.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/test/unit_test.hpp>
+#include <vector>
+
+
+using std::vector;
+using boost::shared_array;
+using boost::shared_ptr;
+
+
+static const int sample_A = 0.038 * 8388608;
+static const int sample_B = 0.092 * 8388608;
+static const int sample_C = 0.071 * 8388608;
+
+static
+bool
+close (int reference, int sample)
+{
+       return abs (reference - sample) < 4096;
+}
+
+
+static
+bool
+close (int ref1, int ref2, int ref3, int ref4, int check[4])
+{
+       return close(ref1, check[0]) && close(ref2, check[1]) && close(ref3, check[2]) && close(ref4, check[3]);
+}
+
+
+static int const sync_channel = 13;
+static int const bytes_per_sample = 3;
+
+
+static int
+read_sync_sample (uint8_t const* data, int sample_index, int channels)
+{
+       uint8_t const* p = data + (sample_index * channels * bytes_per_sample) + sync_channel * bytes_per_sample;
+       return static_cast<int>((p[0] << 8) | (p[1] << 16) | (p[2] << 24)) / 256;
+}
+
+
+BOOST_AUTO_TEST_CASE (sync_test1)
+{
+       dcp::SoundAsset asset (private_test / "atmos_pcm.mxf");
+       shared_ptr<dcp::SoundAssetReader> reader = asset.start_read ();
+       shared_ptr<const dcp::SoundFrame> frame = reader->get_frame (0);
+
+       /* Read the samples from the first MXF frame of channel 14 and decode them to bits */
+       uint8_t const * data = frame->data ();
+       vector<bool> ref;
+       /* There's 2000 samples which contain 500 bits of data */
+       for (int i = 0; i < 500; ++i) {
+               int bit[4];
+               for (int j = 0; j < 4; ++j) {
+                       int sample_index = i * 4 + j;
+                       bit[j] = read_sync_sample (data, sample_index, asset.channels());
+               }
+
+               if (close(sample_A, sample_B, sample_B, sample_A, bit)) {
+                       ref.push_back (false);
+               } else if (close(-sample_A, -sample_B, -sample_B, -sample_A, bit)) {
+                       ref.push_back (false);
+               } else if (close(sample_C, sample_C, -sample_C, -sample_C, bit)) {
+                       ref.push_back (true);
+               } else if (close(-sample_C, -sample_C, sample_C, sample_C, bit)) {
+                       ref.push_back (true);
+               } else {
+                       BOOST_CHECK (false);
+               }
+       }
+
+       shared_ptr<dcp::SoundAssetWriter> writer = asset.start_write ("build/test/foo.mxf", true);
+
+       /* Compare the sync bits made by SoundAssetWriter to the "proper" ones in the MXF */
+       BOOST_CHECK (ref == writer->create_sync_packets());
+}
+
+
+BOOST_AUTO_TEST_CASE (sync_test2)
+{
+       /* Make a MXF with the same ID as atmos_pcm.mxf and write a frame of random stuff */
+       int const channels = 14;
+       dcp::SoundAsset asset (dcp::Fraction(24, 1), 48000, channels, dcp::SMPTE);
+       asset._id = "e004046e09234f90a4ae4355e7e83506";
+       boost::system::error_code ec;
+       boost::filesystem::remove ("build/test/foo.mxf", ec);
+       shared_ptr<dcp::SoundAssetWriter> writer = asset.start_write ("build/test/foo.mxf", true);
+
+       int const frames = 2000;
+       float** junk = new float*[channels];
+       for (int i = 0; i < channels; ++i) {
+               junk[i] = new float[frames];
+               for (int j = 0; j < frames; ++j) {
+                       junk[i][j] = float(rand()) / RAND_MAX;
+               }
+       }
+
+       writer->write (junk, frames);
+       for (int i = 0; i < channels; ++i) {
+               delete[] junk[i];
+       }
+       delete[] junk;
+       writer->finalize ();
+
+       /* Check that channel 14 on the first frame matches channel 14 on the reference */
+       dcp::SoundAsset ref (private_test / "atmos_pcm.mxf");
+       dcp::SoundAsset check ("build/test/foo.mxf");
+
+       shared_ptr<dcp::SoundAssetReader> ref_read = ref.start_read ();
+       shared_ptr<dcp::SoundAssetReader> check_read = check.start_read ();
+
+       shared_ptr<const dcp::SoundFrame> ref_frame = ref_read->get_frame(0);
+       uint8_t const* ref_data = ref_frame->data();
+       shared_ptr<const dcp::SoundFrame> check_frame = check_read->get_frame(0);
+       uint8_t const* check_data = check_frame->data();
+
+       for (int i = 0; i < frames; ++i) {
+               int ref_sample = read_sync_sample (ref_data, i, ref.channels());
+               int check_sample = read_sync_sample (check_data, i, check.channels());
+               BOOST_CHECK (abs(ref_sample - check_sample) < 2);
+       }
+}
+
index efd551b9adcffd432726fa4975c49d012b527226..8ea47108cba38f844340f87b4458ebe923b929fc 100644 (file)
@@ -95,6 +95,7 @@ def build(bld):
                  smpte_load_font_test.cc
                  smpte_subtitle_test.cc
                  sound_frame_test.cc
+                 sync_test.cc
                  test.cc
                  util_test.cc
                  utf8_test.cc