diff options
| author | John Hurst <jhurst@cinecert.com> | 2020-07-07 10:48:29 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-07-07 10:48:29 -0700 |
| commit | 3562c679bc8fd59ac91541bba2144a778bfdbf02 (patch) | |
| tree | e7a53012d45461876839fdc480aee939ab6d8cb1 /src | |
| parent | dc74dc00c2f225344f14c39969bd6b66664b4fd4 (diff) | |
| parent | f421cbb767e0437458332200e3f8537064feaaa8 (diff) | |
Merge pull request #45 from cinecert/iab-labels
Add support for IAB Track Files (SMPTE ST 2067-201)
Diffstat (limited to 'src')
| -rw-r--r-- | src/AS_02_IAB.cpp | 618 | ||||
| -rw-r--r-- | src/AS_02_IAB.h | 239 | ||||
| -rw-r--r-- | src/AS_02_internal.h | 5 | ||||
| -rwxr-xr-x | src/AS_DCP.h | 1 | ||||
| -rwxr-xr-x | src/AS_DCP_MXF.cpp | 4 | ||||
| -rwxr-xr-x | src/AS_DCP_internal.h | 21 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | src/MDD.cpp | 21 | ||||
| -rwxr-xr-x | src/MDD.h | 7 | ||||
| -rwxr-xr-x | src/MXF.cpp | 6 | ||||
| -rw-r--r-- | src/Makefile.am | 2 | ||||
| -rwxr-xr-x | src/Metadata.cpp | 146 | ||||
| -rwxr-xr-x | src/Metadata.h | 44 |
13 files changed, 1114 insertions, 6 deletions
diff --git a/src/AS_02_IAB.cpp b/src/AS_02_IAB.cpp new file mode 100644 index 0000000..cbee6e2 --- /dev/null +++ b/src/AS_02_IAB.cpp @@ -0,0 +1,618 @@ +/* +Copyright (c) 2011-2020, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, +John Hurst, Pierre-Anthony Lemieux + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "AS_02_internal.h" +#include "AS_02_IAB.h" + +#include <iostream> +#include <iomanip> +#include <stdexcept> + +namespace Kumu { + class RuntimeError : public std::runtime_error { + Kumu::Result_t m_Result; + RuntimeError(); + public: + RuntimeError(const Kumu::Result_t& result) : std::runtime_error(result.Message()), m_Result(result) {} + Kumu::Result_t GetResult() { return this->m_Result; } + ~RuntimeError() throw() {} + }; +} + +//------------------------------------------------------------------------------------------ + + +AS_02::IAB::MXFWriter::MXFWriter() : m_ClipStart(0), m_State(ST_BEGIN) { +} + +AS_02::IAB::MXFWriter::~MXFWriter() {} + +const ASDCP::MXF::OP1aHeader& +AS_02::IAB::MXFWriter::OP1aHeader() const { + if (this->m_State == ST_BEGIN) { + throw Kumu::RuntimeError(Kumu::RESULT_INIT); + } + + return this->m_Writer->m_HeaderPart; +} + +const ASDCP::MXF::RIP& +AS_02::IAB::MXFWriter::RIP() const { + if (this->m_State == ST_BEGIN) { + throw Kumu::RuntimeError(Kumu::RESULT_INIT); + } + + return this->m_Writer->m_RIP; +} + +Kumu::Result_t +AS_02::IAB::MXFWriter::OpenWrite( + const std::string& filename, + const ASDCP::WriterInfo& Info, + const ASDCP::MXF::IABSoundfieldLabelSubDescriptor& sub, + const std::vector<ASDCP::UL>& conformsToSpecs, + const ASDCP::Rational& edit_rate, + const ASDCP::Rational& sample_rate) { + + /* are we already running */ + + if (this->m_State != ST_BEGIN) { + return Kumu::RESULT_STATE; + } + + Result_t result = Kumu::RESULT_OK; + + /* initialize the writer */ + + this->m_Writer = new AS_02::IAB::MXFWriter::h__Writer(DefaultSMPTEDict()); + + this->m_Writer->m_Info = Info; + + this->m_Writer->m_HeaderSize = 16 * 1024; + + try { + + /* open the file */ + + result = this->m_Writer->m_File.OpenWrite(filename.c_str()); + + if (result.Failure()) { + throw Kumu::RuntimeError(result); + } + + /* initialize IAB descriptor */ + + ASDCP::MXF::IABEssenceDescriptor* desc = new ASDCP::MXF::IABEssenceDescriptor(this->m_Writer->m_Dict); + + GenRandomValue(desc->InstanceUID); /* TODO: remove */ + desc->SampleRate = edit_rate; + desc->AudioSamplingRate = sample_rate; + desc->ChannelCount = 0; + desc->SoundEssenceCoding = this->m_Writer->m_Dict->ul(MDD_ImmersiveAudioCoding); + desc->QuantizationBits = 24; + + this->m_Writer->m_EssenceDescriptor = desc; + + /* copy and add the IAB subdescriptor */ + + ASDCP::MXF::IABSoundfieldLabelSubDescriptor* subdesc = new ASDCP::MXF::IABSoundfieldLabelSubDescriptor(sub); + + GenRandomValue(subdesc->InstanceUID); /* TODO: remove */ + subdesc->MCATagName = "IAB"; + subdesc->MCATagSymbol = "IAB"; + subdesc->MCALabelDictionaryID = this->m_Writer->m_Dict->ul(MDD_IABSoundfield); + GenRandomValue(subdesc->MCALinkID); + + this->m_Writer->m_EssenceSubDescriptorList.push_back(subdesc); + this->m_Writer->m_EssenceDescriptor->SubDescriptors.push_back(subdesc->InstanceUID); + + /* initialize the index write */ + + this->m_Writer->m_IndexWriter.SetEditRate(edit_rate); + + /* Essence Element UL */ + + byte_t element_ul_bytes[ASDCP::SMPTE_UL_LENGTH]; + + const ASDCP::MDDEntry& element_ul_entry = this->m_Writer->m_Dict->Type(MDD_IMF_IABEssenceClipWrappedElement); + + std::copy(element_ul_entry.ul, element_ul_entry.ul + ASDCP::SMPTE_UL_LENGTH, element_ul_bytes); + + /* only a single track */ + + element_ul_bytes[15] = 1; + + /* write the file header*/ + /* WriteAS02Header() takes ownership of desc and subdesc */ + + result = this->m_Writer->WriteAS02Header( + "Clip wrapping of IA bitstreams as specified in SMPTE ST 2067-201", + UL(this->m_Writer->m_Dict->ul(MDD_IMF_IABEssenceClipWrappedContainer)), + "IA Bitstream", + UL(element_ul_bytes), + UL(this->m_Writer->m_Dict->ul(MDD_SoundDataDef)), + edit_rate, + derive_timecode_rate_from_edit_rate(edit_rate), + &conformsToSpecs + ); + + if (result.Failure()) { + throw Kumu::RuntimeError(result); + } + + /* start the clip */ + + this->m_ClipStart = this->m_Writer->m_File.Tell(); + + byte_t clip_buffer[24] = { 0 }; + + memcpy(clip_buffer, this->m_Writer->m_Dict->ul(MDD_IMF_IABEssenceClipWrappedElement), 16); + + if (!Kumu::write_BER(clip_buffer + 16, 0, 8)) { + throw Kumu::RuntimeError(Kumu::RESULT_FAIL); + } + + this->m_Writer->m_StreamOffset = 24; + + result = this->m_Writer->m_File.Write(clip_buffer, 24); + + if (result.Failure()) { + throw Kumu::RuntimeError(result); + } + + this->m_State = ST_READY; + + } catch (Kumu::RuntimeError e) { + + this->Reset(); + + return e.GetResult(); + + } + + return result; + +} + +Result_t +AS_02::IAB::MXFWriter::WriteFrame(const ui8_t* frame, ui32_t sz) { + + /* are we running */ + + if (this->m_State == ST_BEGIN) { + return Kumu::RESULT_INIT; + } + + Result_t result = Kumu::RESULT_OK; + + /* update the index */ + + IndexTableSegment::IndexEntry Entry; + + Entry.StreamOffset = this->m_Writer->m_StreamOffset; + + this->m_Writer->m_IndexWriter.PushIndexEntry(Entry); + + try { + + /* write the frame */ + + result = this->m_Writer->m_File.Write(frame, sz); + + if (result.Failure()) { + throw Kumu::RuntimeError(result); + } + + /* increment the frame counter */ + + this->m_Writer->m_FramesWritten++; + + /* increment stream offset */ + + this->m_Writer->m_StreamOffset += sz; + + /* we are running now */ + + this->m_State = ST_RUNNING; + + } catch (Kumu::RuntimeError e) { + + this->Reset(); + + return e.GetResult(); + + } + + return result; +} + +Result_t +AS_02::IAB::MXFWriter::Finalize() { + + /* are we running */ + + if (this->m_State == ST_BEGIN) { + return Kumu::RESULT_INIT; + } + + Result_t result = RESULT_OK; + + try { + + /* write clip length */ + + ui64_t current_position = this->m_Writer->m_File.Tell(); + + result = this->m_Writer->m_File.Seek(m_ClipStart + 16); + + byte_t clip_buffer[8] = { 0 }; + + ui64_t size = static_cast<ui64_t>(this->m_Writer->m_StreamOffset); + + bool check = Kumu::write_BER(clip_buffer, size, 8); + + if (!check) { + throw Kumu::RuntimeError(Kumu::RESULT_FAIL); + } + + result = this->m_Writer->m_File.Write(clip_buffer, 8); + + if (result.Failure()) { + throw Kumu::RuntimeError(result); + } + + result = this->m_Writer->m_File.Seek(current_position); + + if (result.Failure()) { + throw Kumu::RuntimeError(result); + } + + /* write footer */ + + result = this->m_Writer->WriteAS02Footer(); + + if (result.Failure()) { + throw Kumu::RuntimeError(result); + } + + } catch (Kumu::RuntimeError e) { + + /* nothing to do since we are about to reset */ + + result = e.GetResult(); + + } + + /* we are ready to start again */ + + this->Reset(); + + return result; +} + +void +AS_02::IAB::MXFWriter::Reset() { + this->m_Writer.set(NULL); + this->m_State = ST_BEGIN; +} + + +//------------------------------------------------------------------------------------------ + + +AS_02::IAB::MXFReader::MXFReader() : m_State(ST_READER_BEGIN) {} + +AS_02::IAB::MXFReader::~MXFReader() {} + +ASDCP::MXF::OP1aHeader& +AS_02::IAB::MXFReader::OP1aHeader() const { + if (this->m_State == ST_READER_BEGIN) { + throw Kumu::RuntimeError(Kumu::RESULT_INIT); + } + + return this->m_Reader->m_HeaderPart; +} + +const ASDCP::MXF::RIP& +AS_02::IAB::MXFReader::RIP() const { + if (this->m_State == ST_READER_BEGIN) { + throw Kumu::RuntimeError(Kumu::RESULT_INIT); + } + + return this->m_Reader->m_RIP; +} + +Result_t +AS_02::IAB::MXFReader::OpenRead(const std::string& filename) { + + /* are we already running */ + + if (this->m_State != ST_READER_BEGIN) { + return Kumu::RESULT_STATE; + } + + Result_t result = Kumu::RESULT_OK; + + /* initialize the writer */ + + this->m_Reader = new h__Reader(DefaultCompositeDict()); + + try { + + result = this->m_Reader->OpenMXFRead(filename); + + if (result.Failure()) { + throw Kumu::RuntimeError(result); + } + + InterchangeObject* tmp_iobj = 0; + + this->m_Reader->m_HeaderPart.GetMDObjectByType( + this->m_Reader->m_Dict->Type(MDD_IABEssenceDescriptor).ul, + &tmp_iobj + ); + + if (!tmp_iobj) { + throw Kumu::RuntimeError(Kumu::RESULT_FAIL); + } + + this->m_Reader->m_HeaderPart.GetMDObjectByType( + this->m_Reader->m_Dict->Type(MDD_IABSoundfieldLabelSubDescriptor).ul, + &tmp_iobj + ); + + if (!tmp_iobj) { + throw Kumu::RuntimeError(Kumu::RESULT_FAIL); + } + + std::list<InterchangeObject*> ObjectList; + + this->m_Reader->m_HeaderPart.GetMDObjectsByType( + this->m_Reader->m_Dict->Type(MDD_Track).ul, + ObjectList + ); + + if (ObjectList.empty()) { + throw Kumu::RuntimeError(Kumu::RESULT_FAIL); + } + + /* invalidate current frame */ + + this->m_CurrentFrameIndex = -1; + + /* we are ready */ + + this->m_State = ST_READER_READY; + + } catch (Kumu::RuntimeError e) { + + this->Reset(); + + return e.GetResult(); + } + + return RESULT_OK; +} + + +Result_t +AS_02::IAB::MXFReader::Close() { + + /* are we already running */ + + if (this->m_State == ST_READER_BEGIN) { + return Kumu::RESULT_INIT; + } + + this->Reset(); + + return Kumu::RESULT_OK; +} + + +Result_t AS_02::IAB::MXFReader::GetFrameCount(ui32_t& frameCount) const { + + /* are we already running */ + + if (this->m_State == ST_READER_BEGIN) { + return Kumu::RESULT_INIT; + } + + frameCount = m_Reader->m_IndexAccess.GetDuration(); + + return Kumu::RESULT_OK; +} + +Result_t +AS_02::IAB::MXFReader::ReadFrame(ui32_t frame_number, AS_02::IAB::MXFReader::Frame& frame) { + + /* are we already running */ + + if (this->m_State == ST_READER_BEGIN) { + return Kumu::RESULT_INIT; + } + + Result_t result = RESULT_OK; + + /* have we already read the frame */ + + if (frame_number != this->m_CurrentFrameIndex) { + + try { + + // look up frame index node + IndexTableSegment::IndexEntry index_entry; + + result = this->m_Reader->m_IndexAccess.Lookup(frame_number, index_entry); + + if (result.Failure()) { + DefaultLogSink().Error("Frame value out of range: %u\n", frame_number); + throw Kumu::RuntimeError(result); + } + + result = this->m_Reader->m_File.Seek(index_entry.StreamOffset); + + if (result.Failure()) { + DefaultLogSink().Error("Cannot seek to stream offset: %u\n", index_entry.StreamOffset); + throw Kumu::RuntimeError(result); + } + + /* read the preamble info */ + + const int preambleTLLen = 5; + const int frameTLLen = 5; + + ui32_t buffer_offset = 0; + + this->m_CurrentFrameBuffer.resize(preambleTLLen); + + result = this->m_Reader->m_File.Read(&this->m_CurrentFrameBuffer[buffer_offset], preambleTLLen); + + if (result.Failure()) { + DefaultLogSink().Error("Error reading IA Frame preamble\n", index_entry.StreamOffset); + throw Kumu::RuntimeError(result); + } + + ui32_t preambleLen = ((ui32_t)this->m_CurrentFrameBuffer[1 + buffer_offset] << 24) + + ((ui32_t)this->m_CurrentFrameBuffer[2 + buffer_offset] << 16) + + ((ui32_t)this->m_CurrentFrameBuffer[3 + buffer_offset] << 8) + + (ui32_t)this->m_CurrentFrameBuffer[4 + buffer_offset]; + + buffer_offset += preambleTLLen; + + /* read the preamble*/ + + if (preambleLen > 0) { + + this->m_CurrentFrameBuffer.resize(preambleTLLen + preambleLen); + + result = this->m_Reader->m_File.Read(&this->m_CurrentFrameBuffer[buffer_offset], preambleLen); + + if (result.Failure()) { + DefaultLogSink().Error("Error reading IA Frame preamble\n", index_entry.StreamOffset); + throw Kumu::RuntimeError(result); + } + + buffer_offset += preambleLen; + + } + + /* read the IA Frame info */ + + this->m_CurrentFrameBuffer.resize(preambleTLLen + preambleLen + frameTLLen); + + result = this->m_Reader->m_File.Read(&this->m_CurrentFrameBuffer[buffer_offset], frameTLLen); + + if (result.Failure()) { + DefaultLogSink().Error("Error reading IA Frame data\n", index_entry.StreamOffset); + throw Kumu::RuntimeError(result); + } + + ui32_t frameLen = ((ui32_t)this->m_CurrentFrameBuffer[buffer_offset + 1] << 24) + + ((ui32_t)this->m_CurrentFrameBuffer[buffer_offset + 2] << 16) + + ((ui32_t)this->m_CurrentFrameBuffer[buffer_offset + 3] << 8) + + (ui32_t)this->m_CurrentFrameBuffer[buffer_offset + 4]; + + buffer_offset += frameTLLen; + + /* read the IA Frame */ + + if (frameLen > 0) { + + this->m_CurrentFrameBuffer.resize(preambleTLLen + preambleLen + frameTLLen + frameLen); + + result = this->m_Reader->m_File.Read(&this->m_CurrentFrameBuffer[buffer_offset], frameLen); + + if (result.Failure()) { + DefaultLogSink().Error("Error reading IA Frame data\n", index_entry.StreamOffset); + throw Kumu::RuntimeError(result); + } + + buffer_offset += frameLen; + + } + + /* update current frame */ + + this->m_CurrentFrameIndex = frame_number; + + } catch (Kumu::RuntimeError e) { + + this->Reset(); + + return e.GetResult(); + + } + + } + + frame = std::pair<size_t, const ui8_t*>(this->m_CurrentFrameBuffer.size(), &this->m_CurrentFrameBuffer[0]); + + this->m_State = ST_READER_RUNNING; + + return result; +} + +Result_t +AS_02::IAB::MXFReader::FillWriterInfo(WriterInfo& Info) const { + /* are we already running */ + + if (this->m_State == ST_READER_BEGIN) { + return Kumu::RESULT_FAIL; + } + + Info = m_Reader->m_Info; + + return Kumu::RESULT_OK; +} + +void +AS_02::IAB::MXFReader::DumpHeaderMetadata(FILE* stream) const { + if (this->m_State != ST_READER_BEGIN) { + this->m_Reader->m_HeaderPart.Dump(stream); + } +} + + +void +AS_02::IAB::MXFReader::DumpIndex(FILE* stream) const { + if (this->m_State != ST_READER_BEGIN) { + this->m_Reader->m_IndexAccess.Dump(stream); + } +} + +void +AS_02::IAB::MXFReader::Reset() { + this->m_Reader.set(NULL); + this->m_State = ST_READER_BEGIN; +} + +// +// end AS_02_IAB.cpp +// diff --git a/src/AS_02_IAB.h b/src/AS_02_IAB.h new file mode 100644 index 0000000..57fb77e --- /dev/null +++ b/src/AS_02_IAB.h @@ -0,0 +1,239 @@ +/* +Copyright (c), Bjoern Stresing, Patrick Bichiou, Wolfgang Ruppel, +John Hurst, Pierre-Anthony Lemieux + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * Reader and writer classes for IAB Track Files, as defined in SMPTE ST 2067-201 + * + * @file + */ + +#ifndef AS_02_IAB_h__ +#define AS_02_IAB_h__ + +#include "AS_02.h" +#include "AS_02_internal.h" +#include "Metadata.h" + +namespace AS_02 { + + namespace IAB { + + /** + * Writes IAB Track Files as specified in ST SMPTE 2067-201 + * + */ + class MXFWriter { + + typedef h__AS02Writer<AS_02::MXF::AS02IndexWriterVBR> h__Writer; + + ASDCP::mem_ptr<h__Writer> m_Writer; + ui64_t m_ClipStart; + WriterState_t m_State; + + void Reset(); + + ASDCP_NO_COPY_CONSTRUCT(MXFWriter); + + public: + MXFWriter(); + virtual ~MXFWriter(); + + /** + * Must be preceded by a succesful OpenWrite() call followed by zero or more WriteFrame() calls + * + * Warning: direct manipulation of MXF structures can interfere + * with the normal operation of the wrapper. Caveat emptor! + * + * @return Header of the Track File + * + * @throws std::runtime_error if the Track File is not open + */ + virtual const ASDCP::MXF::OP1aHeader& OP1aHeader() const; + + /** + * Must be preceded by a succesful OpenWrite() call followed by zero or more WriteFrame() calls + * + * Warning: direct manipulation of MXF structures can interfere + * with the normal operation of the wrapper. Caveat emptor! + * + * @return RIP of the Track File + * + * @throws std::runtime_error if the Track File is not open + */ + virtual const ASDCP::MXF::RIP& RIP() const; + + /** + * Creates and prepares an IAB Track File for writing. + * + * Must be called following instantiation or after Finalize() call + * + * @param filename Path to the file. The file must no exist. + * @param Info MXF file metadata to be written. + * @param sub IAB Soundfield Subdescritor items to be written. MCATagName, MCATagSymbol, MCALabelDictionaryID, MCALinkID are ignored. + * @param conformsToSpecs Value of the ConformsToSpecifications preface item + * @param edit_rate Frame rate of the IA Bitstream + * @param sampling_rate Sampling rate of the audio essence within the IA Bitstream + * + * @return RESULT_OK indicates that frames are ready to be written, + * otherwise the reader is reset and the file is left is an undermined state. + */ + Result_t OpenWrite( + const std::string& filename, + const ASDCP::WriterInfo& Info, + const ASDCP::MXF::IABSoundfieldLabelSubDescriptor& sub, + const std::vector<ASDCP::UL>& conformsToSpecs, + const ASDCP::Rational& edit_rate, + const ASDCP::Rational& sampling_rate = ASDCP::SampleRate_48k + ); + + /** + * Writes a single frame. + * + * Must be preceded by a succesful OpenWrite() call followed by zero or more WriteFrame() calls + * + * @param frame Pointer to a complete IA Frame + * @param sz Size in bytes of the IA Frame + * @return RESULT_OK indicates that the frame is written and additional frames can be written, + * otherwise the reader is reset and the file is left is an undermined state. + */ + Result_t WriteFrame(const ui8_t* frame, ui32_t sz); + + /** + * Writes the Track File footer and closes the file. + * + * Must be preceded by a succesful OpenWrite() call followed by zero or more WriteFrame() calls + * + * @return RESULT_OK indicates that the frame is written and additional frames can be written, + * otherwise the reader is reset and the file is left is an undermined state. + */ + Result_t Finalize(); + }; + + /** + * Reads IAB Track Files as specified in ST SMPTE 2067-201 + * + */ + class MXFReader { + + typedef h__AS02Reader h__Reader; + + ASDCP::mem_ptr<h__Reader> m_Reader; + + i64_t m_CurrentFrameIndex; + std::vector<ui8_t> m_CurrentFrameBuffer; + ReaderState_t m_State; + + void Reset(); + + ASDCP_NO_COPY_CONSTRUCT(MXFReader); + + public: + + /* typedefs*/ + + typedef std::pair<size_t, const ui8_t*> Frame; + + /* methods */ + + MXFReader(); + virtual ~MXFReader(); + + /** + * Warning: direct manipulation of MXF structures can interfere + * with the normal operation of the wrapper. Caveat emptor! + * + * @return Header of the Track File + * + * @throws std::runtime_error if the Track File is not open + */ + virtual ASDCP::MXF::OP1aHeader& OP1aHeader() const; + + /** + * Warning: direct manipulation of MXF structures can interfere + * with the normal operation of the wrapper. Caveat emptor! + * + * @return RIP of the Track File + * + * @throws std::runtime_error if the Track File is not open + */ + virtual const ASDCP::MXF::RIP& RIP() const; + + /** + * Creates and prepares an IAB Track File for reading. + * + * @param filename Path to the file. The file must no exist. + * + * @return RESULT_OK indicates that frames are ready to be read, + * otherwise the reader is reset + */ + Result_t OpenRead(const std::string& filename); + + /** + * Closes the IAB Track File. + * + * @return RESULT_OK indicates that the Track File was successfully closed. + */ + Result_t Close(); + + /** + * Fill a WriterInfo struct with the values from the Track File's header. + * + * @param writer_info Struct to be filled + * @return RESULT_OK indicates that writer_info was successfully filled. + */ + Result_t FillWriterInfo(ASDCP::WriterInfo& writer_info) const; + + /** + * Reads an IA Frame. + * + * @param frame_number Index of the frame to be read. Must be in the range [0, GetFrameCount()). + * @param frame Frame data. Must not be modified. Remains valid until the next call to ReadFrame(). + * @return RESULT_OK indicates that more frames are ready to be read, + * otherwise the file is closed and the reader reset + */ + Result_t ReadFrame(ui32_t frame_number, Frame& frame); + + /** + * Returns the number of IA Frame in the Track File. + * + * @param frameCount Number of IA Frames + * @return RESULT_OK unless the file is not open + */ + Result_t GetFrameCount(ui32_t& frameCount) const; + + // Print debugging information to stream + void DumpHeaderMetadata(FILE* = 0) const; + void DumpIndex(FILE* = 0) const; + }; + + } //namespace IAB + +} // namespace AS_02 + +#endif // AS_02_IAB_h__ diff --git a/src/AS_02_internal.h b/src/AS_02_internal.h index e6038c3..b5d8282 100644 --- a/src/AS_02_internal.h +++ b/src/AS_02_internal.h @@ -164,7 +164,8 @@ namespace AS_02 // all the above for a single source clip Result_t WriteAS02Header(const std::string& PackageLabel, const ASDCP::UL& WrappingUL, const std::string& TrackName, const ASDCP::UL& EssenceUL, - const ASDCP::UL& DataDefinition, const ASDCP::Rational& EditRate) + const ASDCP::UL& DataDefinition, const ASDCP::Rational& EditRate, + const std::vector<ASDCP::UL>* conformsToSpecifications = NULL) { if ( EditRate.Numerator == 0 || EditRate.Denominator == 0 ) { @@ -172,7 +173,7 @@ namespace AS_02 return RESULT_PARAM; } - InitHeader(MXFVersion_2011); + InitHeader(MXFVersion_2011, conformsToSpecifications); AddSourceClip(EditRate, EditRate/*TODO: for a moment*/, 0 /*no timecode track*/, TrackName, EssenceUL, DataDefinition, PackageLabel); AddEssenceDescriptor(WrappingUL); diff --git a/src/AS_DCP.h b/src/AS_DCP.h index f39c9c8..f089f06 100755 --- a/src/AS_DCP.h +++ b/src/AS_DCP.h @@ -223,6 +223,7 @@ namespace ASDCP { ESS_AS02_TIMED_TEXT, // the file contains a TTML document and zero or more resources ESS_AS02_ISXD, // the file contains an ISXD document stream (per SMPTE RDD 47) ESS_AS02_ACES, // the file contains two or more ACES codestreams (per SMPTE ST 2067-50) + ESS_AS02_IAB, // the file contains an IAB stream (per SMPTE ST 2067-201) ESS_MAX }; diff --git a/src/AS_DCP_MXF.cpp b/src/AS_DCP_MXF.cpp index d3606fe..c478f2f 100755 --- a/src/AS_DCP_MXF.cpp +++ b/src/AS_DCP_MXF.cpp @@ -277,6 +277,10 @@ ASDCP::EssenceType(const std::string& filename, EssenceType_t& type) { type = ESS_AS02_ACES; } + else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(IABEssenceDescriptor))) ) + { + type = ESS_AS02_IAB; + } } } else diff --git a/src/AS_DCP_internal.h b/src/AS_DCP_internal.h index a684ba5..7162368 100755 --- a/src/AS_DCP_internal.h +++ b/src/AS_DCP_internal.h @@ -624,7 +624,7 @@ namespace ASDCP const MXF::RIP& GetRIP() const { return m_RIP; } - void InitHeader(const MXFVersion& mxf_ver) + void InitHeader(const MXFVersion& mxf_ver, const std::vector<ASDCP::UL>* conformsToSpecifications = NULL) { assert(m_Dict); assert(m_EssenceDescriptor); @@ -633,6 +633,18 @@ namespace ASDCP m_HeaderPart.m_Preface = new Preface(m_Dict); m_HeaderPart.AddChildObject(m_HeaderPart.m_Preface); + // add conformsToSpecifications, if it exists + + if (conformsToSpecifications && conformsToSpecifications->size() > 0) { + + m_HeaderPart.m_Preface->ConformsToSpecifications.set_has_value(); + + m_HeaderPart.m_Preface->ConformsToSpecifications.get().insert( + conformsToSpecifications->begin(), + conformsToSpecifications->end() + ); + } + // Set the Operational Pattern label -- we're just starting and have no RIP or index, // so we tell the world by using OP1a m_HeaderPart.m_Preface->OperationalPattern = UL(m_Dict->ul(MDD_OP1a)); @@ -881,6 +893,13 @@ namespace ASDCP //------------------------------------------------------------------------------------------ // +// state machine for mxf reader + enum ReaderState_t { + ST_READER_BEGIN, // waiting for Open() + ST_READER_READY, // ready to read frames + ST_READER_RUNNING, // one or more frames read + }; + // class h__ASDCPReader : public MXF::TrackFileReader<OP1aHeader, OPAtomIndexFooter> { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 842f44a..2787e46 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,13 +37,13 @@ set(asdcp_src ${asdcp_src} Wav.h WavFileWriter.h MXF.h Metadata.h JP2K.h AS_DCP. # ----------as02----------
# source
-set(as02_src h__02_Reader.cpp h__02_Writer.cpp AS_02_ISXD.cpp AS_02_JP2K.cpp AS_02_PCM.cpp ST2052_TextParser.cpp AS_02_TimedText.cpp AS_02_ACES.cpp ACES_Codestream_Parser.cpp ACES_Sequence_Parser.cpp ACES.cpp)
+set(as02_src h__02_Reader.cpp h__02_Writer.cpp AS_02_ISXD.cpp AS_02_JP2K.cpp AS_02_PCM.cpp ST2052_TextParser.cpp AS_02_TimedText.cpp AS_02_ACES.cpp ACES_Codestream_Parser.cpp ACES_Sequence_Parser.cpp ACES.cpp AS_02_IAB.cpp)
# header for deployment (install target)
-set(as02_deploy_header AS_02.h Metadata.h MXF.h MXFTypes.h KLV.h MDD.h AS_02_ACES.h ACES.h)
+set(as02_deploy_header AS_02.h Metadata.h MXF.h MXFTypes.h KLV.h MDD.h AS_02_ACES.h ACES.h AS_02_IAB.h AS_02_internal.h)
# header
-set(as02_src ${as02_src} AS_02.h AS_02_internal.h AS_02_ACES.h ACES.h)
+set(as02_src ${as02_src} AS_02.h AS_02_internal.h AS_02_ACES.h ACES.h AS_02_IAB.h)
include_directories("${PROJECT_SOURCE_DIR}/src" "${OpenSSLLib_include_DIR}" "${XercescppLib_include_DIR}")
diff --git a/src/MDD.cpp b/src/MDD.cpp index 6db1bec..772b96d 100644 --- a/src/MDD.cpp +++ b/src/MDD.cpp @@ -1639,6 +1639,27 @@ static const ASDCP::MDDEntry s_MDD_Table[] = { { { 0x06, 0x0e, 0x2b, 0x34, 01, 0x01, 0x01, 0x0e, 06, 0x01, 0x01, 0x02, 06, 0x00, 0x00, 0x00 }, {0}, false, "TimedTextDescriptor_ZPositionInUse" }, // 528 + { { 0x06, 0x0e, 0x2b, 0x34, 0x02, 0x53, 0x01, 0x01, // 529 + 0x0d, 0x01, 0x01, 0x01, 0x01, 0x01, 0x7b, 0x00 }, + {0}, false, "IABEssenceDescriptor" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x02, 0x53, 0x01, 0x01, // 530 + 0x0d, 0x01, 0x01, 0x01, 0x01, 0x01, 0x7c, 0x00 }, + {0}, false, "IABSoundfieldLabelSubDescriptor" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 531 + 0x01, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00, 0x00 }, + {0}, false, "IMF_IABTrackFileLevel0" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 532 + 0x0d, 0x01, 0x03, 0x01, 0x02, 0x1d, 0x01, 0x01 }, + {0}, false, "IMF_IABEssenceClipWrappedContainer" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 533 + 0x03, 0x02, 0x02, 0x21, 0x00, 0x00, 0x00, 0x00 }, + {0}, false, "IABSoundfield" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x01, 0x02, 0x01, 0x01, // 534 + 0x0d, 0x01, 0x03, 0x01, 0x16, 0x7f, 0x0d, 0x7f }, + {0}, false, "IMF_IABEssenceClipWrappedElement" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x05, // 535 + 0x0e, 0x09, 0x06, 0x04, 0x00, 0x00, 0x00, 0x00 }, + {0}, false, "ImmersiveAudioCoding" }, { {0}, {0}, false, 0 }, }; @@ -564,6 +564,13 @@ namespace ASDCP { MDD_TimedTextDescriptor_DisplayType, // 526 MDD_TimedTextDescriptor_IntrinsicPictureResolution, // 527 MDD_TimedTextDescriptor_ZPositionInUse, // 528 + MDD_IABEssenceDescriptor, // 529 + MDD_IABSoundfieldLabelSubDescriptor, // 530 + MDD_IMF_IABTrackFileLevel0, // 531 + MDD_IMF_IABEssenceClipWrappedContainer, // 532 + MDD_IABSoundfield, // 533 + MDD_IMF_IABEssenceClipWrappedElement, // 534 + MDD_ImmersiveAudioCoding, // 535 MDD_Max }; // enum MDD_t diff --git a/src/MXF.cpp b/src/MXF.cpp index af406ee..5cab95e 100755 --- a/src/MXF.cpp +++ b/src/MXF.cpp @@ -1742,6 +1742,12 @@ ASDCP::MXF::decode_mca_string(const std::string& s, const mca_label_map_t& label return false; } + if ( ! ul_is_an_mca_channel(i->second.ul) ) + { + DefaultLogSink().Error("Not a channel symbol: '%s'\n", symbol_buf.c_str()); + return false; + } + ASDCP::MXF::AudioChannelLabelSubDescriptor *channel_descr = new ASDCP::MXF::AudioChannelLabelSubDescriptor(dict); GenRandomValue(channel_descr->MCALinkID); diff --git a/src/Makefile.am b/src/Makefile.am index 74c0b11..7768b39 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -143,6 +143,7 @@ libas02_la_SOURCES = \ AS_02_internal.h \ ACES.h \ AS_02_ACES.h \ + AS_02_IAB.h \ h__02_Reader.cpp \ h__02_Writer.cpp \ AS_02_JP2K.cpp \ @@ -151,6 +152,7 @@ libas02_la_SOURCES = \ ST2052_TextParser.cpp \ AS_02_TimedText.cpp \ ACES.cpp \ + AS_02_IAB.cpp \ ACES_Codestream_Parser.cpp \ ACES_Sequence_Parser.cpp \ AS_02_ACES.cpp diff --git a/src/Metadata.cpp b/src/Metadata.cpp index adaac11..9ca3d66 100755 --- a/src/Metadata.cpp +++ b/src/Metadata.cpp @@ -87,6 +87,8 @@ static InterchangeObject* GenericStreamTextBasedSet_Factory(const Dictionary*& D static InterchangeObject* ISXDDataEssenceDescriptor_Factory(const Dictionary*& Dict) { return new ISXDDataEssenceDescriptor(Dict); } static InterchangeObject* PHDRMetadataTrackSubDescriptor_Factory(const Dictionary*& Dict) { return new PHDRMetadataTrackSubDescriptor(Dict); } static InterchangeObject* PIMFDynamicMetadataDescriptor_Factory(const Dictionary*& Dict) { return new PIMFDynamicMetadataDescriptor(Dict); } +static InterchangeObject* IABEssenceDescriptor_Factory(const Dictionary*& Dict) { return new IABEssenceDescriptor(Dict); } +static InterchangeObject* IABSoundfieldLabelSubDescriptor_Factory(const Dictionary*& Dict) { return new IABSoundfieldLabelSubDescriptor(Dict); } void @@ -140,6 +142,8 @@ ASDCP::MXF::Metadata_InitTypes(const Dictionary*& Dict) SetObjectFactory(Dict->ul(MDD_ISXDDataEssenceDescriptor), ISXDDataEssenceDescriptor_Factory); SetObjectFactory(Dict->ul(MDD_PHDRMetadataTrackSubDescriptor), PHDRMetadataTrackSubDescriptor_Factory); SetObjectFactory(Dict->ul(MDD_PIMFDynamicMetadataDescriptor), PIMFDynamicMetadataDescriptor_Factory); + SetObjectFactory(Dict->ul(MDD_IABEssenceDescriptor), IABEssenceDescriptor_Factory); + SetObjectFactory(Dict->ul(MDD_IABSoundfieldLabelSubDescriptor), IABSoundfieldLabelSubDescriptor_Factory); } //------------------------------------------------------------------------------------------ @@ -4851,6 +4855,148 @@ PIMFDynamicMetadataDescriptor::WriteToBuffer(ASDCP::FrameBuffer& Buffer) return InterchangeObject::WriteToBuffer(Buffer); } +//------------------------------------------------------------------------------------------ +// IABEssenceDescriptor + +// + +IABEssenceDescriptor::IABEssenceDescriptor(const Dictionary*& d) : GenericSoundEssenceDescriptor(d), m_Dict(d) +{ + assert(m_Dict); + m_UL = m_Dict->ul(MDD_IABEssenceDescriptor); +} + +IABEssenceDescriptor::IABEssenceDescriptor(const IABEssenceDescriptor& rhs) : GenericSoundEssenceDescriptor(rhs.m_Dict), m_Dict(rhs.m_Dict) +{ + assert(m_Dict); + m_UL = m_Dict->ul(MDD_IABEssenceDescriptor); + Copy(rhs); +} + + +// +ASDCP::Result_t +IABEssenceDescriptor::InitFromTLVSet(TLVReader& TLVSet) +{ + assert(m_Dict); + Result_t result = GenericSoundEssenceDescriptor::InitFromTLVSet(TLVSet); + return result; +} + +// +ASDCP::Result_t +IABEssenceDescriptor::WriteToTLVSet(TLVWriter& TLVSet) +{ + assert(m_Dict); + Result_t result = GenericSoundEssenceDescriptor::WriteToTLVSet(TLVSet); + return result; +} + +// +void +IABEssenceDescriptor::Copy(const IABEssenceDescriptor& rhs) +{ + GenericSoundEssenceDescriptor::Copy(rhs); +} + +// +void +IABEssenceDescriptor::Dump(FILE* stream) +{ + char identbuf[IdentBufferLen]; + *identbuf = 0; + + if ( stream == 0 ) + stream = stderr; + + GenericSoundEssenceDescriptor::Dump(stream); +} + +// +ASDCP::Result_t +IABEssenceDescriptor::InitFromBuffer(const byte_t* p, ui32_t l) +{ + return InterchangeObject::InitFromBuffer(p, l); +} + +// +ASDCP::Result_t +IABEssenceDescriptor::WriteToBuffer(ASDCP::FrameBuffer& Buffer) +{ + return InterchangeObject::WriteToBuffer(Buffer); +} + +//------------------------------------------------------------------------------------------ +// IABSoundfieldLabelSubDescriptor + +// + +IABSoundfieldLabelSubDescriptor::IABSoundfieldLabelSubDescriptor(const Dictionary*& d) : MCALabelSubDescriptor(d), m_Dict(d) +{ + assert(m_Dict); + m_UL = m_Dict->ul(MDD_IABSoundfieldLabelSubDescriptor); +} + +IABSoundfieldLabelSubDescriptor::IABSoundfieldLabelSubDescriptor(const IABSoundfieldLabelSubDescriptor& rhs) : MCALabelSubDescriptor(rhs.m_Dict), m_Dict(rhs.m_Dict) +{ + assert(m_Dict); + m_UL = m_Dict->ul(MDD_IABSoundfieldLabelSubDescriptor); + Copy(rhs); +} + + +// +ASDCP::Result_t +IABSoundfieldLabelSubDescriptor::InitFromTLVSet(TLVReader& TLVSet) +{ + assert(m_Dict); + Result_t result = MCALabelSubDescriptor::InitFromTLVSet(TLVSet); + return result; +} + +// +ASDCP::Result_t +IABSoundfieldLabelSubDescriptor::WriteToTLVSet(TLVWriter& TLVSet) +{ + assert(m_Dict); + Result_t result = MCALabelSubDescriptor::WriteToTLVSet(TLVSet); + return result; +} + +// +void +IABSoundfieldLabelSubDescriptor::Copy(const IABSoundfieldLabelSubDescriptor& rhs) +{ + MCALabelSubDescriptor::Copy(rhs); +} + +// +void +IABSoundfieldLabelSubDescriptor::Dump(FILE* stream) +{ + char identbuf[IdentBufferLen]; + *identbuf = 0; + + if ( stream == 0 ) + stream = stderr; + + MCALabelSubDescriptor::Dump(stream); +} + +// +ASDCP::Result_t +IABSoundfieldLabelSubDescriptor::InitFromBuffer(const byte_t* p, ui32_t l) +{ + return InterchangeObject::InitFromBuffer(p, l); +} + +// +ASDCP::Result_t +IABSoundfieldLabelSubDescriptor::WriteToBuffer(ASDCP::FrameBuffer& Buffer) +{ + return InterchangeObject::WriteToBuffer(Buffer); +} + // // end Metadata.cpp // diff --git a/src/Metadata.h b/src/Metadata.h index c624784..ab11034 100755 --- a/src/Metadata.h +++ b/src/Metadata.h @@ -1297,6 +1297,50 @@ namespace ASDCP virtual Result_t WriteToBuffer(ASDCP::FrameBuffer&); }; + // + class IABEssenceDescriptor : public GenericSoundEssenceDescriptor + { + IABEssenceDescriptor(); + + public: + const Dictionary*& m_Dict; + + IABEssenceDescriptor(const Dictionary*& d); + IABEssenceDescriptor(const IABEssenceDescriptor& rhs); + virtual ~IABEssenceDescriptor() {} + + const IABEssenceDescriptor& operator=(const IABEssenceDescriptor& rhs) { Copy(rhs); return *this; } + virtual void Copy(const IABEssenceDescriptor& rhs); + virtual const char* HasName() { return "IABEssenceDescriptor"; } + virtual Result_t InitFromTLVSet(TLVReader& TLVSet); + virtual Result_t WriteToTLVSet(TLVWriter& TLVSet); + virtual void Dump(FILE* = 0); + virtual Result_t InitFromBuffer(const byte_t* p, ui32_t l); + virtual Result_t WriteToBuffer(ASDCP::FrameBuffer&); + }; + + // + class IABSoundfieldLabelSubDescriptor : public MCALabelSubDescriptor + { + IABSoundfieldLabelSubDescriptor(); + + public: + const Dictionary*& m_Dict; + + IABSoundfieldLabelSubDescriptor(const Dictionary*& d); + IABSoundfieldLabelSubDescriptor(const IABSoundfieldLabelSubDescriptor& rhs); + virtual ~IABSoundfieldLabelSubDescriptor() {} + + const IABSoundfieldLabelSubDescriptor& operator=(const IABSoundfieldLabelSubDescriptor& rhs) { Copy(rhs); return *this; } + virtual void Copy(const IABSoundfieldLabelSubDescriptor& rhs); + virtual const char* HasName() { return "IABSoundfieldLabelSubDescriptor"; } + virtual Result_t InitFromTLVSet(TLVReader& TLVSet); + virtual Result_t WriteToTLVSet(TLVWriter& TLVSet); + virtual void Dump(FILE* = 0); + virtual Result_t InitFromBuffer(const byte_t* p, ui32_t l); + virtual Result_t WriteToBuffer(ASDCP::FrameBuffer&); + }; + } // namespace MXF } // namespace ASDCP |
