From ba6e57635ce6482fa9dcd6a824b579edb459b834 Mon Sep 17 00:00:00 2001 From: jhurst Date: Mon, 17 Jun 2013 17:55:54 +0000 Subject: [PATCH] tweezes --- README | 24 ++- configure.ac | 2 +- src/AS_02.h | 89 +++++--- src/AS_02_JP2K.cpp | 262 +++++++++++----------- src/AS_02_PCM.cpp | 454 ++++++++++++++++++++++----------------- src/AS_02_internal.h | 131 ++++++----- src/AS_DCP.h | 9 +- src/AS_DCP_JP2K.cpp | 4 +- src/AS_DCP_MPEG2.cpp | 4 +- src/AS_DCP_PCM.cpp | 12 +- src/AS_DCP_TimedText.cpp | 3 +- src/AS_DCP_internal.h | 86 +++++--- src/Index.cpp | 2 +- src/KM_log.h | 1 + src/MDD.cpp | 18 ++ src/MDD.h | 6 + src/MXF.cpp | 206 ++++++++++++++++++ src/MXF.h | 41 ++++ src/MXFTypes.h | 2 +- src/WavFileWriter.h | 3 +- src/as-02-unwrap.cpp | 138 ++++++++++-- src/as-02-wrap.cpp | 135 +++++++++--- src/asdcp-wrap.cpp | 98 +++++++-- src/h__02_Reader.cpp | 291 +++++++++++++++++++------ src/h__02_Writer.cpp | 237 ++++++++++++-------- src/h__Reader.cpp | 27 ++- src/h__Writer.cpp | 16 +- src/klvsplit.cpp | 27 +-- 28 files changed, 1595 insertions(+), 733 deletions(-) diff --git a/README b/README index 7def63f..f39fd19 100755 --- a/README +++ b/README @@ -139,6 +139,28 @@ command-line utilities all respond to -h. Change History +YYYY-MM-DD - IMF/AS-02 support, bug fixes + o Fixed missing return statement in ArchivableString::ArchiveLength + (thanks to both Kristof Provost and Franck Chopin) + o Fixed broken sample alignment in RF64Writer (thanks to Dolby) + o Fixed win32 build (thanks to Dolby) + o Massive refactoring of internals to allow easier implementation + of AS-02. Some API changes were made as well (note that + OPAtomHeader is now OP1aHeader and RIP is no longer part of the + OP1aHeader.) If you are using this project as a library (and + especially if you are keeping patches against it) PLEASE TAKE + TIME TO EVALUATE THIS RELEASE THOUROUGHLY BEFORE ADDING IT TO + YOUR RELEASE PATH. + o Final integration of Fraunhoffer IIS code contribution. AS-02 + files are now fully supported with some TODOs and one major + exception: LEAD indexes are not supported by the MXF writers + and interlace images are not yet supported. + o Added support for MCA labels (ST 428-12) to asdcp-wrap. Note + that this project is still in the early stages of interop testing + so errors are likely present and don't expect any server to + understand this feature. + + 2013-04-12 - Dolby Atmos support and more audio labels 1.11.49 o Significant code contribution from Dolby Laboratories to add support for generic data track files as proposed in ST 21DC @@ -150,7 +172,6 @@ Change History o Added ULs for ST 428-12 and Amd. 429-2 2013. Please check! - 2013-02-20 - bug fixes, enhancements 1.10.48 o Refactored internals of the AS-DCP file readers. While no changes in behavior are intended, users are cautioned to test @@ -187,6 +208,7 @@ Change History MCAAudioContentKind MCAAudioElementKind + 2012-08-07 - bug fix, 1.10.46 o Added missing zero-initializers to time values when parsing a timestamp string (in the case where the optional [Thh:mm.[:ss]] diff --git a/configure.ac b/configure.ac index 623ccc3..5968dd8 100644 --- a/configure.ac +++ b/configure.ac @@ -37,7 +37,7 @@ AC_PREREQ([2.59]) # For example, if asdcplib version 1.0.0 were modified to accomodate changes # in file format, and if no changes were made to AS_DCP.h, the new version would be # 1.0.1. If changes were also required in AS_DCP.h, the new version would be 1.1.1. -AC_INIT([asdcplib], [1.12.50a], [asdcplib@cinecert.com]) +AC_INIT([asdcplib], [1.12.50b], [asdcplib@cinecert.com]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([src/KM_error.h]) diff --git a/src/AS_02.h b/src/AS_02.h index e4be1b8..ff6b692 100644 --- a/src/AS_02.h +++ b/src/AS_02.h @@ -1,5 +1,7 @@ /* -Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst +Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, +John Hurst + All rights reserved. Redistribution and use in source and binary forms, with or without @@ -34,7 +36,7 @@ by the SMPTE Media and Packaging Technology Committee 35PM. The file format, labeled IMF Essence Component (AKA "AS-02" for historical reasons), is described in the following document: - o SMPTE 2067-5:201X (draft at this time) IMF Essence Component + o SMPTE 2067-5:2013 IMF Essence Component The following use cases are supported by the module: @@ -47,49 +49,46 @@ The following use cases are supported by the module: PCM audio streams o Read header metadata from an AS-02 file + +NOTE: ciphertext support for clip-wrapped PCM is not yet complete. */ #ifndef _AS_02_H_ #define _AS_02_H_ -#include "AS_DCP.h" -#include "MXF.h" - +#include "Metadata.h" -namespace ASDCP { - namespace MXF { - // #include to use this - class OPAtomHeader; - }; -}; namespace AS_02 { using Kumu::Result_t; + KM_DECLARE_RESULT(AS02_FORMAT, -116, "The file format is not proper OP-1a/AS-02."); + namespace MXF { // + // reads distributed index tables and provides a uniform lookup with + // translated StreamOffest values (that is, StreamOffest is adjusted + // to the actual file position class AS02IndexReader : public ASDCP::MXF::Partition { Kumu::ByteString m_IndexSegmentData; ui32_t m_Duration; + ui32_t m_BytesPerEditUnit; - // ui32_t m_BytesPerEditUnit; - - Result_t InitFromBuffer(const byte_t* p, ui32_t l); + Result_t InitFromBuffer(const byte_t* p, ui32_t l, const ui64_t& body_offset, const ui64_t& essence_container_offset); ASDCP_NO_COPY_CONSTRUCT(AS02IndexReader); AS02IndexReader(); public: const ASDCP::Dictionary*& m_Dict; - Kumu::fpos_t m_ECOffset; ASDCP::IPrimerLookup *m_Lookup; AS02IndexReader(const ASDCP::Dictionary*&); virtual ~AS02IndexReader(); - Result_t InitFromFile(const Kumu::FileReader& reader, const ASDCP::MXF::RIP& rip); + Result_t InitFromFile(const Kumu::FileReader& reader, const ASDCP::MXF::RIP& rip, const bool has_header_essence); ui32_t GetDuration() const; void Dump(FILE* = 0); Result_t GetMDObjectByID(const Kumu::UUID&, ASDCP::MXF::InterchangeObject** = 0); @@ -98,6 +97,27 @@ namespace AS_02 Result_t Lookup(ui32_t frame_num, ASDCP::MXF::IndexTableSegment::IndexEntry&) const; }; + + + // Returns size in bytes of a single sample of data described by ADesc + inline ui32_t CalcSampleSize(const ASDCP::MXF::WaveAudioDescriptor& d) + { + return (d.QuantizationBits / 8) * d.ChannelCount; + } + + // Returns number of samples per frame of data described by ADesc + inline ui32_t CalcSamplesPerFrame(const ASDCP::MXF::WaveAudioDescriptor& d, const ASDCP::Rational& edit_rate) + { + double tmpd = d.AudioSamplingRate.Quotient() / edit_rate.Quotient(); + return (ui32_t)ceil(tmpd); + } + + // Returns the size in bytes of a frame of data described by ADesc + inline ui32_t CalcFrameBufferSize(const ASDCP::MXF::WaveAudioDescriptor& d, const ASDCP::Rational& edit_rate) + { + return CalcSampleSize(d) * CalcSamplesPerFrame(d, edit_rate); + } + } // namespace MXF //--------------------------------------------------------------------------------- @@ -110,6 +130,7 @@ namespace AS_02 IS_LEAD, IS_FOLLOW, IS_FILE_SPECIFIC, + IS_MAX }; namespace JP2K @@ -133,11 +154,11 @@ namespace AS_02 // Open the file for writing. The file must not exist. Returns error if // the operation cannot be completed or if nonsensical data is discovered // in the essence descriptor. - Result_t OpenWrite(const char* filename, const ASDCP::WriterInfo&, - const ASDCP::JP2K::PictureDescriptor&, - const IndexStrategy_t& Strategy = IS_FOLLOW, - const ui32_t& PartitionSpace = 60, /* seconds per partition */ - const ui32_t& HeaderSize = 16384); + Result_t OpenWrite(const std::string& filename, const ASDCP::WriterInfo&, + ASDCP::MXF::FileDescriptor* essence_descriptor, + ASDCP::MXF::InterchangeObject_list_t& essence_sub_descriptor_list, + const ASDCP::Rational& edit_rate, const ui32_t& header_size = 16384, + const IndexStrategy_t& strategy = IS_FOLLOW, const ui32_t& partition_space = 10); // Writes a frame of essence to the MXF file. If the optional AESEncContext // argument is present, the essence is encrypted prior to writing. @@ -168,15 +189,11 @@ namespace AS_02 // Open the file for reading. The file must exist. Returns error if the // operation cannot be completed. - Result_t OpenRead(const char* filename) const; + Result_t OpenRead(const std::string& filename) const; // Returns RESULT_INIT if the file is not open. Result_t Close() const; - // Fill an AudioDescriptor struct with the values from the file's header. - // Returns RESULT_INIT if the file is not open. - Result_t FillPictureDescriptor(ASDCP::JP2K::PictureDescriptor&) const; - // Fill a WriterInfo struct with the values from the file's header. // Returns RESULT_INIT if the file is not open. Result_t FillWriterInfo(ASDCP::WriterInfo&) const; @@ -204,6 +221,14 @@ namespace AS_02 { // see AS_DCP.h for related data types + // An AS-02 PCM file is clip-wrapped, but the interface defined below mimics that used + // for frame-wrapped essence elsewhere in this library. The concept of frame rate + // therefore is only relevant to these classes and is not reflected in or affected by + // the contents of the MXF file. The frame rate that is set on the writer is only + // for compatibility with the existing parsers, samples are packed contiguously into + // the same clip-wrapped packet. Similarly, the edit rate must be set when initializing + // the reader to signal the number of samples to be read by each call to ReadFrame(); + // class MXFWriter { @@ -223,8 +248,10 @@ namespace AS_02 // Open the file for writing. The file must not exist. Returns error if // the operation cannot be completed or if nonsensical data is discovered // in the essence descriptor. - Result_t OpenWrite(const char* filename, const ASDCP::WriterInfo&, - const ASDCP::PCM::AudioDescriptor&, ui32_t HeaderSize = 16384); + Result_t OpenWrite(const std::string& filename, const ASDCP::WriterInfo&, + ASDCP::MXF::FileDescriptor* essence_descriptor, + ASDCP::MXF::InterchangeObject_list_t& essence_sub_descriptor_list, + const ASDCP::Rational& edit_rate, ui32_t HeaderSize = 16384); // Writes a frame of essence to the MXF file. If the optional AESEncContext // argument is present, the essence is encrypted prior to writing. @@ -255,15 +282,11 @@ namespace AS_02 // Open the file for reading. The file must exist. Returns error if the // operation cannot be completed. - Result_t OpenRead(const char* filename) const; + Result_t OpenRead(const std::string& filename, const ASDCP::Rational& EditRate); // Returns RESULT_INIT if the file is not open. Result_t Close() const; - // Fill an AudioDescriptor struct with the values from the file's header. - // Returns RESULT_INIT if the file is not open. - Result_t FillAudioDescriptor(ASDCP::PCM::AudioDescriptor&) const; - // Fill a WriterInfo struct with the values from the file's header. // Returns RESULT_INIT if the file is not open. Result_t FillWriterInfo(ASDCP::WriterInfo&) const; diff --git a/src/AS_02_JP2K.cpp b/src/AS_02_JP2K.cpp index aeb6dbc..79ea382 100644 --- a/src/AS_02_JP2K.cpp +++ b/src/AS_02_JP2K.cpp @@ -1,28 +1,30 @@ /* - Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst - 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. +Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, +John Hurst + +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. */ /*! \file AS_02_JP2K.cpp \version $Id$ @@ -50,60 +52,47 @@ static std::string PICT_DEF_LABEL = "Image Track"; class AS_02::JP2K::MXFReader::h__Reader : public AS_02::h__AS02Reader { - RGBAEssenceDescriptor* m_RGBAEssenceDescriptor; - CDCIEssenceDescriptor* m_CDCIEssenceDescriptor; - JPEG2000PictureSubDescriptor* m_EssenceSubDescriptor; - ASDCP::Rational m_EditRate; - ASDCP::Rational m_SampleRate; - EssenceType_t m_Format; - ASDCP_NO_COPY_CONSTRUCT(h__Reader); public: PictureDescriptor m_PDesc; // codestream parameter list h__Reader(const Dictionary& d) : - AS_02::h__AS02Reader(d), m_RGBAEssenceDescriptor(0), m_CDCIEssenceDescriptor(0), - m_EssenceSubDescriptor(0), m_Format(ESS_UNKNOWN) {} + AS_02::h__AS02Reader(d) {} virtual ~h__Reader() {} - Result_t OpenRead(const char*); + Result_t OpenRead(const std::string&); Result_t ReadFrame(ui32_t, ASDCP::JP2K::FrameBuffer&, AESDecContext*, HMACContext*); }; // Result_t -AS_02::JP2K::MXFReader::h__Reader::OpenRead(const char* filename) +AS_02::JP2K::MXFReader::h__Reader::OpenRead(const std::string& filename) { - Result_t result = OpenMXFRead(filename); + Result_t result = OpenMXFRead(filename.c_str()); - if( ASDCP_SUCCESS(result) ) + if( KM_SUCCESS(result) ) { InterchangeObject* tmp_iobj = 0; m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(CDCIEssenceDescriptor), &tmp_iobj); - m_CDCIEssenceDescriptor = static_cast(tmp_iobj); - if ( m_CDCIEssenceDescriptor == 0 ) + if ( tmp_iobj == 0 ) { m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor), &tmp_iobj); - m_RGBAEssenceDescriptor = static_cast(tmp_iobj); } - if ( m_CDCIEssenceDescriptor == 0 && m_RGBAEssenceDescriptor == 0 ) + if ( tmp_iobj == 0 ) { DefaultLogSink().Error("RGBAEssenceDescriptor nor CDCIEssenceDescriptor found.\n"); - return RESULT_FORMAT; } m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor), &tmp_iobj); - m_EssenceSubDescriptor = static_cast(tmp_iobj); - if ( m_EssenceSubDescriptor == 0 ) + if ( tmp_iobj == 0 ) { DefaultLogSink().Error("JPEG2000PictureSubDescriptor not found.\n"); - return RESULT_FORMAT; } std::list ObjectList; @@ -112,25 +101,7 @@ AS_02::JP2K::MXFReader::h__Reader::OpenRead(const char* filename) if ( ObjectList.empty() ) { DefaultLogSink().Error("MXF Metadata contains no Track Sets.\n"); - return RESULT_FORMAT; - } - - if ( m_CDCIEssenceDescriptor != 0 ) - { - m_EditRate = ((Track*)ObjectList.front())->EditRate; - m_SampleRate = m_CDCIEssenceDescriptor->SampleRate; - result = MD_to_JP2K_PDesc(*m_CDCIEssenceDescriptor, *m_EssenceSubDescriptor, m_EditRate, m_SampleRate, m_PDesc); - } - else if ( m_RGBAEssenceDescriptor != 0 ) - { - m_EditRate = ((Track*)ObjectList.front())->EditRate; - m_SampleRate = m_RGBAEssenceDescriptor->SampleRate; - result = MD_to_JP2K_PDesc(*m_RGBAEssenceDescriptor, *m_EssenceSubDescriptor, m_EditRate, m_SampleRate, m_PDesc); - } - - if ( m_PDesc.ContainerDuration == 0 ) - { - m_PDesc.ContainerDuration = m_IndexAccess.GetDuration(); + return RESULT_AS02_FORMAT; } } @@ -211,38 +182,35 @@ AS_02::JP2K::MXFReader::RIP() // Open the file for reading. The file must exist. Returns error if the // operation cannot be completed. Result_t -AS_02::JP2K::MXFReader::OpenRead(const char* filename) const +AS_02::JP2K::MXFReader::OpenRead(const std::string& filename) const { return m_Reader->OpenRead(filename); } // Result_t -AS_02::JP2K::MXFReader::ReadFrame(ui32_t FrameNum, ASDCP::JP2K::FrameBuffer& FrameBuf, - ASDCP::AESDecContext* Ctx, ASDCP::HMACContext* HMAC) const +AS_02::JP2K::MXFReader::Close() const { if ( m_Reader && m_Reader->m_File.IsOpen() ) - return m_Reader->ReadFrame(FrameNum, FrameBuf, Ctx, HMAC); + { + m_Reader->Close(); + return RESULT_OK; + } return RESULT_INIT; } - -// Fill the struct with the values from the file's header. -// Returns RESULT_INIT if the file is not open. +// Result_t -AS_02::JP2K::MXFReader::FillPictureDescriptor(PictureDescriptor& PDesc) const +AS_02::JP2K::MXFReader::ReadFrame(ui32_t FrameNum, ASDCP::JP2K::FrameBuffer& FrameBuf, + ASDCP::AESDecContext* Ctx, ASDCP::HMACContext* HMAC) const { if ( m_Reader && m_Reader->m_File.IsOpen() ) - { - PDesc = m_Reader->m_PDesc; - return RESULT_OK; - } + return m_Reader->ReadFrame(FrameNum, FrameBuf, Ctx, HMAC); return RESULT_INIT; } - // Fill the struct with the values from the file's header. // Returns RESULT_INIT if the file is not open. Result_t @@ -278,10 +246,11 @@ public: virtual ~h__Writer(){} - Result_t OpenWrite(const char*, EssenceType_t type, const AS_02::IndexStrategy_t& IndexStrategy, + Result_t OpenWrite(const std::string&, ASDCP::MXF::FileDescriptor* essence_descriptor, + ASDCP::MXF::InterchangeObject_list_t& essence_sub_descriptor_list, + const AS_02::IndexStrategy_t& IndexStrategy, const ui32_t& PartitionSpace, const ui32_t& HeaderSize); - Result_t SetSourceStream(const PictureDescriptor&, const std::string& label, - ASDCP::Rational LocalEditRate = ASDCP::Rational(0,0)); + Result_t SetSourceStream(const std::string& label, const ASDCP::Rational& edit_rate); Result_t WriteFrame(const ASDCP::JP2K::FrameBuffer&, ASDCP::AESEncContext*, ASDCP::HMACContext*); Result_t Finalize(); }; @@ -290,11 +259,16 @@ public: // Open the file for writing. The file must not exist. Returns error if // the operation cannot be completed. Result_t -AS_02::JP2K::MXFWriter::h__Writer::OpenWrite(const char* filename, EssenceType_t type, const AS_02::IndexStrategy_t& IndexStrategy, +AS_02::JP2K::MXFWriter::h__Writer::OpenWrite(const std::string& filename, + ASDCP::MXF::FileDescriptor* essence_descriptor, + ASDCP::MXF::InterchangeObject_list_t& essence_sub_descriptor_list, + const AS_02::IndexStrategy_t& IndexStrategy, const ui32_t& PartitionSpace_sec, const ui32_t& HeaderSize) { if ( ! m_State.Test_BEGIN() ) - return RESULT_STATE; + { + return RESULT_STATE; + } if ( m_IndexStrategy != AS_02::IS_FOLLOW ) { @@ -302,20 +276,39 @@ AS_02::JP2K::MXFWriter::h__Writer::OpenWrite(const char* filename, EssenceType_t return Kumu::RESULT_NOTIMPL; } - Result_t result = m_File.OpenWrite(filename); + Result_t result = m_File.OpenWrite(filename.c_str()); - if ( ASDCP_SUCCESS(result) ) + if ( KM_SUCCESS(result) ) { m_IndexStrategy = IndexStrategy; m_PartitionSpace = PartitionSpace_sec; // later converted to edit units by SetSourceStream() m_HeaderSize = HeaderSize; - m_EssenceDescriptor = new RGBAEssenceDescriptor(m_Dict); - m_EssenceSubDescriptor = new JPEG2000PictureSubDescriptor(m_Dict); - m_EssenceSubDescriptorList.push_back((InterchangeObject*)m_EssenceSubDescriptor); + if ( essence_descriptor->GetUL() != UL(m_Dict->ul(MDD_RGBAEssenceDescriptor)) + && essence_descriptor->GetUL() != UL(m_Dict->ul(MDD_CDCIEssenceDescriptor)) ) + { + DefaultLogSink().Error("Essence descriptor is not a RGBAEssenceDescriptor or CDCIEssenceDescriptor.\n"); + essence_descriptor->Dump(); + return RESULT_AS02_FORMAT; + } + + m_EssenceDescriptor = essence_descriptor; + + ASDCP::MXF::InterchangeObject_list_t::iterator i; + for ( i = essence_sub_descriptor_list.begin(); i != essence_sub_descriptor_list.end(); ++i ) + { + if ( (*i)->GetUL() != UL(m_Dict->ul(MDD_JPEG2000PictureSubDescriptor)) ) + { + DefaultLogSink().Error("Essence sub-descriptor is not a JPEG2000PictureSubDescriptor.\n"); + (*i)->Dump(); + } + + m_EssenceSubDescriptorList.push_back(*i); + GenRandomValue((*i)->InstanceUID); + m_EssenceDescriptor->SubDescriptors.push_back((*i)->InstanceUID); + *i = 0; // parent will only free the ones we don't keep + } - GenRandomValue(m_EssenceSubDescriptor->InstanceUID); - m_EssenceDescriptor->SubDescriptors.push_back(m_EssenceSubDescriptor->InstanceUID); result = m_State.Goto_INIT(); } @@ -324,38 +317,23 @@ AS_02::JP2K::MXFWriter::h__Writer::OpenWrite(const char* filename, EssenceType_t // Automatically sets the MXF file's metadata from the first jpeg codestream stream. Result_t -AS_02::JP2K::MXFWriter::h__Writer::SetSourceStream(const PictureDescriptor& PDesc, const std::string& label, ASDCP::Rational LocalEditRate) +AS_02::JP2K::MXFWriter::h__Writer::SetSourceStream(const std::string& label, const ASDCP::Rational& edit_rate) { assert(m_Dict); if ( ! m_State.Test_INIT() ) - return RESULT_STATE; - - if ( LocalEditRate == ASDCP::Rational(0,0) ) - LocalEditRate = PDesc.EditRate; - - m_PDesc = PDesc; - assert(m_Dict); - Result_t result = JP2K_PDesc_to_MD(m_PDesc, *m_Dict, - static_cast(m_EssenceDescriptor), - m_EssenceSubDescriptor); - - static_cast(m_EssenceDescriptor)->ComponentMaxRef = 4095; /// TODO: set with magic or some such thing - static_cast(m_EssenceDescriptor)->ComponentMinRef = 0; - - if ( ASDCP_SUCCESS(result) ) { - memcpy(m_EssenceUL, m_Dict->ul(MDD_JPEG2000Essence), SMPTE_UL_LENGTH); - m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container - result = m_State.Goto_READY(); + return RESULT_STATE; } - if ( ASDCP_SUCCESS(result) ) - { - ui32_t TCFrameRate = ( m_PDesc.EditRate == EditRate_23_98 ) ? 24 : m_PDesc.EditRate.Numerator; + memcpy(m_EssenceUL, m_Dict->ul(MDD_JPEG2000Essence), SMPTE_UL_LENGTH); + m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container + Result_t result = m_State.Goto_READY(); + if ( KM_SUCCESS(result) ) + { result = WriteAS02Header(label, UL(m_Dict->ul(MDD_JPEG_2000Wrapping)), PICT_DEF_LABEL, UL(m_EssenceUL), UL(m_Dict->ul(MDD_PictureDataDef)), - LocalEditRate, TCFrameRate); + edit_rate, derive_timecode_rate_from_edit_rate(edit_rate)); } return result; @@ -368,26 +346,27 @@ AS_02::JP2K::MXFWriter::h__Writer::SetSourceStream(const PictureDescriptor& PDes // Result_t AS_02::JP2K::MXFWriter::h__Writer::WriteFrame(const ASDCP::JP2K::FrameBuffer& FrameBuf, - AESEncContext* Ctx, HMACContext* HMAC) + AESEncContext* Ctx, HMACContext* HMAC) { + if ( FrameBuf.Size() == 0 ) + { + DefaultLogSink().Error("The frame buffer size is zero.\n"); + return RESULT_PARAM; + } + Result_t result = RESULT_OK; if ( m_State.Test_READY() ) - result = m_State.Goto_RUNNING(); // first time through - - ui64_t StreamOffset = m_StreamOffset; // m_StreamOffset will be changed by the call to WriteEKLVPacket - - if ( ASDCP_SUCCESS(result) ) - result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC); - - if ( ASDCP_SUCCESS(result) ) - { - IndexTableSegment::IndexEntry Entry; - Entry.StreamOffset = StreamOffset; - m_IndexWriter.PushIndexEntry(Entry); + { + result = m_State.Goto_RUNNING(); // first time through + } + + if ( KM_SUCCESS(result) ) + { + result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC); + m_FramesWritten++; } - m_FramesWritten++; return result; } @@ -401,8 +380,10 @@ AS_02::JP2K::MXFWriter::h__Writer::Finalize() Result_t result = m_State.Goto_FINAL(); - if ( ASDCP_SUCCESS(result) ) - result = WriteAS02Footer(); + if ( KM_SUCCESS(result) ) + { + result = WriteAS02Footer(); + } return result; } @@ -453,21 +434,28 @@ AS_02::JP2K::MXFWriter::RIP() // Open the file for writing. The file must not exist. Returns error if // the operation cannot be completed. Result_t -AS_02::JP2K::MXFWriter::OpenWrite(const char* filename, const ASDCP::WriterInfo& Info, - const ASDCP::JP2K::PictureDescriptor& PDesc, - const IndexStrategy_t& Strategy, - const ui32_t& PartitionSpace, - const ui32_t& HeaderSize) +AS_02::JP2K::MXFWriter::OpenWrite(const std::string& filename, const ASDCP::WriterInfo& Info, + ASDCP::MXF::FileDescriptor* essence_descriptor, + ASDCP::MXF::InterchangeObject_list_t& essence_sub_descriptor_list, + const ASDCP::Rational& edit_rate, const ui32_t& header_size, + const IndexStrategy_t& strategy, const ui32_t& partition_space) { + if ( essence_descriptor == 0 ) + { + DefaultLogSink().Error("Essence descriptor object required.\n"); + return RESULT_PARAM; + } + m_Writer = new AS_02::JP2K::MXFWriter::h__Writer(DefaultSMPTEDict()); m_Writer->m_Info = Info; - Result_t result = m_Writer->OpenWrite(filename, ASDCP::ESS_JPEG_2000, Strategy, PartitionSpace, HeaderSize); + Result_t result = m_Writer->OpenWrite(filename, essence_descriptor, essence_sub_descriptor_list, + strategy, partition_space, header_size); - if ( ASDCP_SUCCESS(result) ) - result = m_Writer->SetSourceStream(PDesc, JP2K_PACKAGE_LABEL); + if ( KM_SUCCESS(result) ) + result = m_Writer->SetSourceStream(JP2K_PACKAGE_LABEL, edit_rate); - if ( ASDCP_FAILURE(result) ) + if ( KM_FAILURE(result) ) m_Writer.release(); return result; diff --git a/src/AS_02_PCM.cpp b/src/AS_02_PCM.cpp index b371fcf..66de6e9 100644 --- a/src/AS_02_PCM.cpp +++ b/src/AS_02_PCM.cpp @@ -1,29 +1,30 @@ /* - Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst - 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. -*/ +Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, +John Hurst + +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. /*! \file AS_02_PCM.cpp \version $Id$ \brief AS-02 library, PCM essence reader and writer implementation @@ -40,141 +41,152 @@ static std::string PCM_PACKAGE_LABEL = "File Package: SMPTE 382M clip wrapping of wave audio"; static std::string SOUND_DEF_LABEL = "Sound Track"; -//this must be changed because the CBR_frame_size is only -// -static ui32_t -calc_CBR_frame_size(ASDCP::WriterInfo& Info, const ASDCP::PCM::AudioDescriptor& ADesc) -{ - ui32_t CBR_frame_size = 0; - - if ( Info.EncryptedEssence ) - { - CBR_frame_size = - //TODO: correct? - /*SMPTE_UL_LENGTH - + MXF_BER_LENGTH - + */klv_cryptinfo_size - + calc_esv_length(ASDCP::PCM::CalcFrameBufferSize(ADesc), 0) - + ( Info.UsesHMAC ? klv_intpack_size : (MXF_BER_LENGTH * 3) ); - } - else - { - CBR_frame_size = ASDCP::PCM::CalcFrameBufferSize(ADesc); - } - - return CBR_frame_size; -} - //------------------------------------------------------------------------------------------ class AS_02::PCM::MXFReader::h__Reader : public AS_02::h__AS02Reader { + ui64_t m_ClipEssenceBegin; + ui64_t m_SamplesPerFrame; + ui32_t m_ContainerDuration; + ASDCP_NO_COPY_CONSTRUCT(h__Reader); h__Reader(); public: - ASDCP::PCM::AudioDescriptor m_ADesc; - - h__Reader(const Dictionary& d) : AS_02::h__AS02Reader(d) {} + h__Reader(const Dictionary& d) : AS_02::h__AS02Reader(d), m_ClipEssenceBegin(0), + m_SamplesPerFrame(0), m_ContainerDuration(0) {} virtual ~h__Reader() {} - ASDCP::Result_t OpenRead(const char*); + ASDCP::Result_t OpenRead(const std::string&, const ASDCP::Rational& edit_rate); ASDCP::Result_t ReadFrame(ui32_t, ASDCP::PCM::FrameBuffer&, ASDCP::AESDecContext*, ASDCP::HMACContext*); - - - /// Result_t OpenMXFRead(const char* filename); - // positions file before reading - /// Result_t ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf, - /// const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC); - - // reads from current position - Result_t ReadEKLVPacket(ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf, - const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC); - - }; - +// TODO: This will ignore any body partitions past the first // // ASDCP::Result_t -AS_02::PCM::MXFReader::h__Reader::OpenRead(const char* filename) +AS_02::PCM::MXFReader::h__Reader::OpenRead(const std::string& filename, const ASDCP::Rational& edit_rate) { - Result_t result = OpenMXFRead(filename); + ASDCP::MXF::WaveAudioDescriptor* wave_descriptor = 0; + IndexTableSegment::IndexEntry tmp_entry; + Result_t result = OpenMXFRead(filename.c_str()); - if( ASDCP_SUCCESS(result) ) + if( KM_SUCCESS(result) ) { - InterchangeObject* Object = 0; - if ( ASDCP_SUCCESS(m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &Object)) ) + if ( KM_SUCCESS(m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), + reinterpret_cast(&wave_descriptor))) ) { - if ( Object == 0 ) + if ( wave_descriptor == 0 ) { DefaultLogSink().Error("WaveAudioDescriptor object not found.\n"); - return RESULT_FORMAT; + return RESULT_AS02_FORMAT; } - - result = MD_to_PCM_ADesc((ASDCP::MXF::WaveAudioDescriptor*)Object, m_ADesc); } } - // check for sample/frame rate sanity - if ( ASDCP_SUCCESS(result) - && m_ADesc.EditRate != EditRate_24 - && m_ADesc.EditRate != EditRate_25 - && m_ADesc.EditRate != EditRate_30 - && m_ADesc.EditRate != EditRate_48 - && m_ADesc.EditRate != EditRate_50 - && m_ADesc.EditRate != EditRate_60 - && m_ADesc.EditRate != EditRate_23_98 ) + if ( KM_SUCCESS(result) ) + result = m_IndexAccess.Lookup(0, tmp_entry); + + if ( KM_SUCCESS(result) ) + result = m_File.Seek(tmp_entry.StreamOffset); + + if ( KM_SUCCESS(result) ) { - DefaultLogSink().Error("PCM file EditRate is not a supported value: %d/%d\n", // lu - m_ADesc.EditRate.Numerator, m_ADesc.EditRate.Denominator); + assert(wave_descriptor); + KLReader reader; + result = reader.ReadKLFromFile(m_File); - // oh, they gave us the audio sampling rate instead, assume 24/1 - if ( m_ADesc.EditRate == SampleRate_48k ) + if ( KM_SUCCESS(result) ) { - DefaultLogSink().Warn("adjusting EditRate to 24/1\n"); - m_ADesc.EditRate = EditRate_24; - } - else - { - // or we just drop the hammer - return RESULT_FORMAT; + if ( ! UL(reader.Key()).MatchIgnoreStream(m_Dict->ul(MDD_WAVEssenceClip)) ) + { + const MDDEntry *entry = m_Dict->FindUL(reader.Key()); + + if ( entry == 0 ) + { + char buf[64]; + DefaultLogSink().Error("Essence wrapper key is not WAVEssenceClip: %s\n", UL(reader.Key()).EncodeString(buf, 64)); + } + else + { + DefaultLogSink().Error("Essence wrapper key is not WAVEssenceClip: %s\n", entry->name); + } + + return RESULT_AS02_FORMAT; + } + + if ( wave_descriptor->BlockAlign == 0 ) + { + DefaultLogSink().Error("EssenceDescriptor has corrupt BlockAlign value, unable to continue.\n"); + return RESULT_AS02_FORMAT; + } + + if ( reader.Length() % wave_descriptor->BlockAlign != 0 ) + { + DefaultLogSink().Error("Clip length is not an even multiple of BlockAlign, unable to continue.\n"); + return RESULT_AS02_FORMAT; + } + + m_ClipEssenceBegin = m_File.Tell(); + m_SamplesPerFrame = AS_02::MXF::CalcSamplesPerFrame(*wave_descriptor, edit_rate); + m_ContainerDuration = reader.Length() / m_SamplesPerFrame; } } - // TODO: test file for sane CBR index BytesPerEditUnit - return result; } - -// // ASDCP::Result_t AS_02::PCM::MXFReader::h__Reader::ReadFrame(ui32_t FrameNum, ASDCP::PCM::FrameBuffer& FrameBuf, ASDCP::AESDecContext* Ctx, ASDCP::HMACContext* HMAC) { if ( ! m_File.IsOpen() ) - return RESULT_INIT; + { + return RESULT_INIT; + } + + if ( FrameNum > m_ContainerDuration ) + { + return RESULT_RANGE; + } + + assert(m_ClipEssenceBegin); + Result_t result = RESULT_OK; + ui64_t position = m_ClipEssenceBegin + ( FrameNum * m_SamplesPerFrame ); - assert(m_Dict); - return ReadEKLVFrame(FrameNum, FrameBuf, m_Dict->ul(MDD_WAVEssence), Ctx, HMAC); + if ( m_File.Tell() != position ) + { + result = m_File.Seek(position); + } + + if ( KM_SUCCESS(result) ) + { + result = m_File.Read(FrameBuf.Data(), m_SamplesPerFrame); + } + + if ( KM_SUCCESS(result) ) + { + FrameBuf.Size(m_SamplesPerFrame); + } + + return result; } +//------------------------------------------------------------------------------------------ +// + + AS_02::PCM::MXFReader::MXFReader() { m_Reader = new h__Reader(DefaultCompositeDict()); } - AS_02::PCM::MXFReader::~MXFReader() { - if ( m_Reader && m_Reader->m_File.IsOpen() ) - m_Reader->Close(); } // Warning: direct manipulation of MXF structures can interfere @@ -225,9 +237,22 @@ AS_02::PCM::MXFReader::RIP() // Open the file for reading. The file must exist. Returns error if the // operation cannot be completed. ASDCP::Result_t -AS_02::PCM::MXFReader::OpenRead(const char* filename) const +AS_02::PCM::MXFReader::OpenRead(const std::string& filename, const ASDCP::Rational& edit_rate) { - return m_Reader->OpenRead(filename); + return m_Reader->OpenRead(filename, edit_rate); +} + +// +Result_t +AS_02::PCM::MXFReader::Close() const +{ + if ( m_Reader && m_Reader->m_File.IsOpen() ) + { + m_Reader->Close(); + return RESULT_OK; + } + + return RESULT_INIT; } // Reads a frame of essence from the MXF file. If the optional AESEncContext @@ -245,20 +270,6 @@ AS_02::PCM::MXFReader::ReadFrame(ui32_t FrameNum, ASDCP::PCM::FrameBuffer& Frame } -// Fill the struct with the values from the file's header. -// Returns RESULT_INIT if the file is not open. -ASDCP::Result_t -AS_02::PCM::MXFReader::FillAudioDescriptor(ASDCP::PCM::AudioDescriptor& ADesc) const -{ - if ( m_Reader && m_Reader->m_File.IsOpen() ) - { - ADesc = m_Reader->m_ADesc; - return RESULT_OK; - } - - return RESULT_INIT; -} - // Fill the struct with the values from the file's header. // Returns RESULT_INIT if the file is not open. ASDCP::Result_t @@ -300,58 +311,72 @@ class AS_02::PCM::MXFWriter::h__Writer : public AS_02::h__AS02Writer h__Writer(); public: - ASDCP::PCM::AudioDescriptor m_ADesc; - byte_t m_EssenceUL[SMPTE_UL_LENGTH]; - ui64_t m_KLV_start; + ASDCP::MXF::WaveAudioDescriptor *m_WaveAudioDescriptor; + byte_t m_EssenceUL[SMPTE_UL_LENGTH]; + ui32_t m_BytesPerFrame; + ui32_t m_SamplesPerFrame; - h__Writer(const Dictionary& d) : AS_02::h__AS02Writer(d), m_KLV_start(0){ + h__Writer(const Dictionary& d) : AS_02::h__AS02Writer(d), m_WaveAudioDescriptor(0), m_BytesPerFrame(0), m_SamplesPerFrame(0) + { memset(m_EssenceUL, 0, SMPTE_UL_LENGTH); - } virtual ~h__Writer(){} - Result_t OpenWrite(const char*, ui32_t HeaderSize); - Result_t SetSourceStream(const ASDCP::PCM::AudioDescriptor&); + Result_t OpenWrite(const std::string&, ASDCP::MXF::FileDescriptor* essence_descriptor, + ASDCP::MXF::InterchangeObject_list_t& essence_sub_descriptor_list, const ui32_t& header_size); + Result_t SetSourceStream(const ASDCP::Rational&); Result_t WriteFrame(const FrameBuffer&, ASDCP::AESEncContext* = 0, ASDCP::HMACContext* = 0); - Result_t Finalize(); - - //void AddSourceClip(const MXF::Rational& EditRate, ui32_t TCFrameRate, - // const std::string& TrackName, const UL& EssenceUL, - // const UL& DataDefinition, const std::string& PackageLabel); - //void AddDMSegment(const MXF::Rational& EditRate, ui32_t TCFrameRate, - // const std::string& TrackName, const UL& DataDefinition, - // const std::string& PackageLabel); - //void AddEssenceDescriptor(const UL& WrappingUL); - //Result_t CreateBodyPart(const MXF::Rational& EditRate, ui32_t BytesPerEditUnit = 0); - - ////new method to create BodyPartition for essence and index - //Result_t CreateBodyPartPair(); - ////new method to finalize BodyPartion(index) - //Result_t CompleteIndexBodyPart(); - - // reimplement these functions in AS_02_PCM to support modifications for AS-02 - // Result_t WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf, - // const byte_t* EssenceUL, AESEncContext* Ctx, HMACContext* HMAC); - // Result_t WriteAS02Footer(); - }; // Open the file for writing. The file must not exist. Returns error if // the operation cannot be completed. ASDCP::Result_t -AS_02::PCM::MXFWriter::h__Writer::OpenWrite(const char* filename, ui32_t HeaderSize) +AS_02::PCM::MXFWriter::h__Writer::OpenWrite(const std::string& filename, ASDCP::MXF::FileDescriptor* essence_descriptor, + ASDCP::MXF::InterchangeObject_list_t& essence_sub_descriptor_list, const ui32_t& header_size) { + assert(essence_descriptor); + + if ( essence_descriptor->GetUL() != UL(m_Dict->ul(MDD_WaveAudioDescriptor)) ) + { + DefaultLogSink().Error("Essence descriptor is not a WaveAudioDescriptor.\n"); + essence_descriptor->Dump(); + return RESULT_AS02_FORMAT; + } + + m_WaveAudioDescriptor = reinterpret_cast(essence_descriptor); + if ( ! m_State.Test_BEGIN() ) - return RESULT_STATE; + { + return RESULT_STATE; + } - Result_t result = m_File.OpenWrite(filename); + Result_t result = m_File.OpenWrite(filename.c_str()); - if ( ASDCP_SUCCESS(result) ) + if ( KM_SUCCESS(result) ) { - m_HeaderSize = HeaderSize; - m_EssenceDescriptor = new WaveAudioDescriptor(m_Dict); + m_HeaderSize = header_size; + m_EssenceDescriptor = essence_descriptor; + m_WaveAudioDescriptor->SampleRate = m_WaveAudioDescriptor->AudioSamplingRate; + + ASDCP::MXF::InterchangeObject_list_t::iterator i; + for ( i = essence_sub_descriptor_list.begin(); i != essence_sub_descriptor_list.end(); ++i ) + { + if ( (*i)->GetUL() != UL(m_Dict->ul(MDD_AudioChannelLabelSubDescriptor)) + && (*i)->GetUL() != UL(m_Dict->ul(MDD_SoundfieldGroupLabelSubDescriptor)) + && (*i)->GetUL() != UL(m_Dict->ul(MDD_GroupOfSoundfieldGroupsLabelSubDescriptor)) ) + { + DefaultLogSink().Error("Essence sub-descriptor is not an MCALabelSubDescriptor.\n"); + (*i)->Dump(); + } + + m_EssenceSubDescriptorList.push_back(*i); + GenRandomValue((*i)->InstanceUID); + m_EssenceDescriptor->SubDescriptors.push_back((*i)->InstanceUID); + *i = 0; // parent will only free the ones we don't keep + } + result = m_State.Goto_INIT(); } @@ -361,52 +386,31 @@ AS_02::PCM::MXFWriter::h__Writer::OpenWrite(const char* filename, ui32_t HeaderS // Automatically sets the MXF file's metadata from the WAV parser info. ASDCP::Result_t -AS_02::PCM::MXFWriter::h__Writer::SetSourceStream(const ASDCP::PCM::AudioDescriptor& ADesc) +AS_02::PCM::MXFWriter::h__Writer::SetSourceStream(const ASDCP::Rational& edit_rate) { if ( ! m_State.Test_INIT() ) - return RESULT_STATE; - - if ( ADesc.EditRate != EditRate_24 - && ADesc.EditRate != EditRate_25 - && ADesc.EditRate != EditRate_30 - && ADesc.EditRate != EditRate_48 - && ADesc.EditRate != EditRate_50 - && ADesc.EditRate != EditRate_60 - && ADesc.EditRate != EditRate_23_98 ) { - DefaultLogSink().Error("AudioDescriptor.EditRate is not a supported value: %d/%d\n", - ADesc.EditRate.Numerator, ADesc.EditRate.Denominator); - return RESULT_RAW_FORMAT; + return RESULT_STATE; } - if ( ADesc.AudioSamplingRate != SampleRate_48k && ADesc.AudioSamplingRate != SampleRate_96k ) - { - DefaultLogSink().Error("AudioDescriptor.AudioSamplingRate is not 48000/1 or 96000/1: %d/%d\n", - ADesc.AudioSamplingRate.Numerator, ADesc.AudioSamplingRate.Denominator); - return RESULT_RAW_FORMAT; - } + fprintf(stderr, "edit_rate=%d/%d\n", edit_rate.Numerator, edit_rate.Denominator); - assert(m_Dict); - m_ADesc = ADesc; + memcpy(m_EssenceUL, m_Dict->ul(MDD_WAVEssenceClip), SMPTE_UL_LENGTH); + m_EssenceUL[15] = 1; // set the stream identifier + Result_t result = m_State.Goto_READY(); - Result_t result = PCM_ADesc_to_MD(m_ADesc, (WaveAudioDescriptor*)m_EssenceDescriptor); - - if ( ASDCP_SUCCESS(result) ) + if ( KM_SUCCESS(result) ) { - memcpy(m_EssenceUL, m_Dict->ul(MDD_WAVEssence), SMPTE_UL_LENGTH); - //SMPTE 382M-2007: ByteNo. 15 - 02h - Wave Clip-Wrapped Element - m_EssenceUL[SMPTE_UL_LENGTH-2] = 2; // 02h - Wave Clip-Wrapped Element - m_EssenceUL[SMPTE_UL_LENGTH-1] = 1; // first (and only) essence container - result = m_State.Goto_READY(); - } + assert(m_WaveAudioDescriptor); + m_BytesPerFrame = AS_02::MXF::CalcFrameBufferSize(*m_WaveAudioDescriptor, edit_rate); + m_SamplesPerFrame = AS_02::MXF::CalcSamplesPerFrame(*m_WaveAudioDescriptor, edit_rate); + m_WaveAudioDescriptor->ContainerDuration = 0; - if ( ASDCP_SUCCESS(result) ) - { - ui32_t TCFrameRate = ( m_ADesc.EditRate == EditRate_23_98 ) ? 24 : m_ADesc.EditRate.Numerator; + fprintf(stderr, "m_BytesPerFrame=%d, m_SamplesPerFrame=%d\n", m_BytesPerFrame, m_SamplesPerFrame); result = WriteAS02Header(PCM_PACKAGE_LABEL, UL(m_Dict->ul(MDD_WAVWrapping)), SOUND_DEF_LABEL, UL(m_EssenceUL), UL(m_Dict->ul(MDD_SoundDataDef)), - m_ADesc.EditRate, TCFrameRate, calc_CBR_frame_size(m_Info, m_ADesc)); + m_EssenceDescriptor->SampleRate, derive_timecode_rate_from_edit_rate(edit_rate), m_BytesPerFrame); } return result; @@ -416,19 +420,42 @@ AS_02::PCM::MXFWriter::h__Writer::SetSourceStream(const ASDCP::PCM::AudioDescrip // // ASDCP::Result_t -AS_02::PCM::MXFWriter::h__Writer::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx, +AS_02::PCM::MXFWriter::h__Writer::WriteFrame(const FrameBuffer& frame_buf, AESEncContext* Ctx, HMACContext* HMAC) { + if ( frame_buf.Size() == 0 ) + { + DefaultLogSink().Error("The frame buffer size is zero.\n"); + return RESULT_PARAM; + } + + if ( frame_buf.Size() % m_BytesPerFrame != 0 ) + { + DefaultLogSink().Error("The frame buffer does not contain an integral number of sample sets.\n"); + return RESULT_AS02_FORMAT; + } + Result_t result = RESULT_OK; if ( m_State.Test_READY() ) - result = m_State.Goto_RUNNING(); // first time through + { + result = m_State.Goto_RUNNING(); // first time through + } - if ( ASDCP_SUCCESS(result) ) - result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC); + if ( KM_SUCCESS(result) && ! HasOpenClip() ) + { + result = StartClip(m_EssenceUL, Ctx, HMAC); + } - if ( ASDCP_SUCCESS(result) ) - m_FramesWritten++; + if ( KM_SUCCESS(result) ) + { + result = WriteClipBlock(frame_buf); + } + + if ( KM_SUCCESS(result) ) + { + m_FramesWritten++; + } return result; } @@ -443,7 +470,16 @@ AS_02::PCM::MXFWriter::h__Writer::Finalize() m_State.Goto_FINAL(); - return WriteAS02Footer(); + Result_t result = FinalizeClip(m_BytesPerFrame); + + if ( KM_SUCCESS(result) ) + { + fprintf(stderr, "m_FramesWritten=%d, m_SamplesPerFrame=%d\n", m_FramesWritten, m_SamplesPerFrame); + m_FramesWritten = m_FramesWritten * m_SamplesPerFrame; + WriteAS02Footer(); + } + + return result; } @@ -494,16 +530,30 @@ AS_02::PCM::MXFWriter::RIP() // Open the file for writing. The file must not exist. Returns error if // the operation cannot be completed. ASDCP::Result_t -AS_02::PCM::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info, - const ASDCP::PCM::AudioDescriptor& ADesc, ui32_t HeaderSize) +AS_02::PCM::MXFWriter::OpenWrite(const std::string& filename, const WriterInfo& Info, + ASDCP::MXF::FileDescriptor* essence_descriptor, + ASDCP::MXF::InterchangeObject_list_t& essence_sub_descriptor_list, + const ASDCP::Rational& edit_rate, ui32_t header_size) { + if ( essence_descriptor == 0 ) + { + DefaultLogSink().Error("Essence descriptor object required.\n"); + return RESULT_PARAM; + } + + if ( Info.EncryptedEssence ) + { + DefaultLogSink().Error("Encryption not supported for ST 382 clip-wrap.\n"); + return Kumu::RESULT_NOTIMPL; + } + m_Writer = new h__Writer(DefaultSMPTEDict()); m_Writer->m_Info = Info; - Result_t result = m_Writer->OpenWrite(filename, HeaderSize); + Result_t result = m_Writer->OpenWrite(filename, essence_descriptor, essence_sub_descriptor_list, header_size); - if ( ASDCP_SUCCESS(result) ) - result = m_Writer->SetSourceStream(ADesc); + if ( KM_SUCCESS(result) ) + result = m_Writer->SetSourceStream(edit_rate); if ( ASDCP_FAILURE(result) ) m_Writer.release(); diff --git a/src/AS_02_internal.h b/src/AS_02_internal.h index cf9c368..748e795 100644 --- a/src/AS_02_internal.h +++ b/src/AS_02_internal.h @@ -1,28 +1,30 @@ /* - Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst - 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. +Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, +John Hurst + +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. */ /*! \file AS_02_internal.h \version $Id: AS_02_internal.h *** @@ -50,34 +52,35 @@ namespace AS_02 void default_md_object_init(); - - // - class AS02IndexWriter : public ASDCP::MXF::Partition - { - ASDCP::MXF::IndexTableSegment* m_CurrentSegment; - ui32_t m_BytesPerEditUnit; - ASDCP::MXF::Rational m_EditRate; - - KM_NO_COPY_CONSTRUCT(AS02IndexWriter); - AS02IndexWriter(); - - public: - const ASDCP::Dictionary*& m_Dict; - Kumu::fpos_t m_ECOffset; - ASDCP::IPrimerLookup* m_Lookup; + namespace MXF + { + // + class AS02IndexWriter : public ASDCP::MXF::Partition + { + ASDCP::MXF::IndexTableSegment* m_CurrentSegment; + ui32_t m_BytesPerEditUnit; + ASDCP::MXF::Rational m_EditRate; + + KM_NO_COPY_CONSTRUCT(AS02IndexWriter); + AS02IndexWriter(); + + public: + const ASDCP::Dictionary*& m_Dict; + ASDCP::IPrimerLookup* m_Lookup; - AS02IndexWriter(const ASDCP::Dictionary*&); - virtual ~AS02IndexWriter(); + AS02IndexWriter(const ASDCP::Dictionary*&); + virtual ~AS02IndexWriter(); - Result_t WriteToFile(Kumu::FileWriter& Writer); - void ResetCBR(Kumu::fpos_t offset); - void Dump(FILE* = 0); + Result_t WriteToFile(Kumu::FileWriter& Writer); + void ResetCBR(Kumu::fpos_t offset); + void Dump(FILE* = 0); - ui32_t GetDuration() const; - void PushIndexEntry(const ASDCP::MXF::IndexTableSegment::IndexEntry&); - void SetIndexParamsCBR(ASDCP::IPrimerLookup* lookup, ui32_t size, const ASDCP::Rational& Rate); - void SetIndexParamsVBR(ASDCP::IPrimerLookup* lookup, const ASDCP::Rational& Rate, Kumu::fpos_t offset); - }; + ui32_t GetDuration() const; + void PushIndexEntry(const ASDCP::MXF::IndexTableSegment::IndexEntry&); + void SetIndexParamsCBR(ASDCP::IPrimerLookup* lookup, ui32_t size, const ASDCP::Rational& Rate); + void SetIndexParamsVBR(ASDCP::IPrimerLookup* lookup, const ASDCP::Rational& Rate, Kumu::fpos_t offset); + }; + } // class h__AS02Reader : public ASDCP::MXF::TrackFileReader @@ -86,20 +89,21 @@ namespace AS_02 h__AS02Reader(); public: - // Partition *m_pCurrentBodyPartition; - // ui64_t m_EssenceStart; - // std::vector m_BodyPartList; - // ui32_t m_start_pos; - h__AS02Reader(const ASDCP::Dictionary&); virtual ~h__AS02Reader(); Result_t OpenMXFRead(const char* filename); + // USE FRAME WRAPPING... Result_t ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL, ASDCP::AESDecContext* Ctx, ASDCP::HMACContext* HMAC); - Result_t LocateFrame(ui32_t FrameNum, Kumu::fpos_t& streamOffset, i8_t& temporalOffset, i8_t& keyFrameOffset); + // OR CLIP WRAPPING... + // clip wrapping is handled directly by the essence-specific classes + // Result_t ReadyClip(const ui32_t& FrameNum, const byte_t* EssenceUL, ASDCP::AESDecContext* Ctx, ASDCP::HMACContext* HMAC, ui64_t& position); + /// Result_t ReadClipBlock(ASDCP::FrameBuffer& FrameBuf, const ui32_t& read_size); + + // NOT BOTH! }; // @@ -109,9 +113,11 @@ namespace AS_02 h__AS02Writer(); public: - AS02IndexWriter m_IndexWriter; - ui32_t m_PartitionSpace; // edit units per partition - IndexStrategy_t m_IndexStrategy; //Shim parameter index_strategy_frame/clip + ui64_t m_ECStart; // offset of the first essence element + ui64_t m_ClipStart; // state variable for clip-wrap-in-progress + ui32_t m_PartitionSpace; // edit units per partition + AS_02::MXF::AS02IndexWriter m_IndexWriter; + IndexStrategy_t m_IndexStrategy; // per SMPTE ST 2067-5 h__AS02Writer(const Dictionary&); virtual ~h__AS02Writer(); @@ -122,9 +128,18 @@ namespace AS_02 const ASDCP::UL& DataDefinition, const ASDCP::Rational& EditRate, ui32_t TCFrameRate, ui32_t BytesPerEditUnit = 0); + // USE FRAME WRAPPING... Result_t WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf,const byte_t* EssenceUL, AESEncContext* Ctx, HMACContext* HMAC); + // OR CLIP WRAPPING... + bool HasOpenClip() const; + Result_t StartClip(const byte_t* EssenceUL, AESEncContext* Ctx, HMACContext* HMAC); + Result_t WriteClipBlock(const ASDCP::FrameBuffer& FrameBuf); + Result_t FinalizeClip(ui32_t bytes_per_frame); + + // NOT BOTH! + Result_t WriteAS02Footer(); }; diff --git a/src/AS_DCP.h b/src/AS_DCP.h index 4f6b097..1763d9c 100755 --- a/src/AS_DCP.h +++ b/src/AS_DCP.h @@ -66,6 +66,8 @@ The following use cases are supported by the library: JPEG 2000 stereoscopic codestream pairs PCM audio streams SMPTE 429-7 Timed Text XML with font and image resources + Proposed SMPTE Aux Data track file + Proposed Dolby (TM) Atmos track file o Read essence from a plaintext or ciphertext AS-DCP file: MPEG2 Video Elementary Stream @@ -73,6 +75,8 @@ The following use cases are supported by the library: JPEG 2000 stereoscopic codestream pairs PCM audio streams SMPTE 429-7 Timed Text XML with font and image resources + Proposed SMPTE Aux Data track file + Proposed Dolby (TM) Atmos track file o Read header metadata from an AS-DCP file @@ -213,6 +217,7 @@ namespace ASDCP { ESS_JPEG_2000_S, // the file contains one or more JPEG 2000 codestream pairs (stereoscopic) ESS_DCDATA_UNKNOWN, // the file contains one or more D-Cinema Data bytestreams ESS_DCDATA_DOLBY_ATMOS, // the file contains one or more DolbyATMOS bytestreams + ESS_MAX }; // Determine the type of essence contained in the given MXF file. RESULT_OK @@ -346,7 +351,8 @@ namespace ASDCP { { LS_MXF_UNKNOWN, LS_MXF_INTEROP, - LS_MXF_SMPTE + LS_MXF_SMPTE, + LS_MAX }; // @@ -842,6 +848,7 @@ namespace ASDCP { CF_CFG_3, // 7.1 (SDDS) with optional HI/VI CF_CFG_4, // Wild Track Format CF_CFG_5, // 7.1 DS with optional HI/VI + CF_MAX }; struct AudioDescriptor diff --git a/src/AS_DCP_JP2K.cpp b/src/AS_DCP_JP2K.cpp index 1aec48e..956a867 100755 --- a/src/AS_DCP_JP2K.cpp +++ b/src/AS_DCP_JP2K.cpp @@ -1167,11 +1167,9 @@ lh__Writer::SetSourceStream(const PictureDescriptor& PDesc, const std::string& l if ( ASDCP_SUCCESS(result) ) { - ui32_t TCFrameRate = ( m_PDesc.EditRate == EditRate_23_98 ) ? 24 : m_PDesc.EditRate.Numerator; - result = WriteASDCPHeader(label, UL(m_Dict->ul(MDD_JPEG_2000Wrapping)), PICT_DEF_LABEL, UL(m_EssenceUL), UL(m_Dict->ul(MDD_PictureDataDef)), - LocalEditRate, TCFrameRate); + LocalEditRate, derive_timecode_rate_from_edit_rate(m_PDesc.EditRate)); } return result; diff --git a/src/AS_DCP_MPEG2.cpp b/src/AS_DCP_MPEG2.cpp index 6dc34f2..b10c8b5 100755 --- a/src/AS_DCP_MPEG2.cpp +++ b/src/AS_DCP_MPEG2.cpp @@ -568,11 +568,9 @@ ASDCP::MPEG2::MXFWriter::h__Writer::SetSourceStream(const VideoDescriptor& VDesc if ( ASDCP_SUCCESS(result) ) { - ui32_t TCFrameRate = ( m_VDesc.EditRate == EditRate_23_98 ) ? 24 : m_VDesc.EditRate.Numerator; - result = WriteASDCPHeader(MPEG_PACKAGE_LABEL, UL(m_Dict->ul(MDD_MPEG2_VESWrapping)), PICT_DEF_LABEL, UL(m_EssenceUL), UL(m_Dict->ul(MDD_PictureDataDef)), - m_VDesc.EditRate, TCFrameRate); + m_VDesc.EditRate, derive_timecode_rate_from_edit_rate(m_VDesc.EditRate)); } return result; diff --git a/src/AS_DCP_PCM.cpp b/src/AS_DCP_PCM.cpp index c126f25..d683ed0 100755 --- a/src/AS_DCP_PCM.cpp +++ b/src/AS_DCP_PCM.cpp @@ -577,18 +577,10 @@ ASDCP::PCM::MXFWriter::h__Writer::SetSourceStream(const AudioDescriptor& ADesc) if ( ASDCP_SUCCESS(result) ) { - ui32_t TCFrameRate = m_ADesc.EditRate.Numerator; - - if ( m_ADesc.EditRate == EditRate_23_98 ) - TCFrameRate = 24; - else if ( m_ADesc.EditRate == EditRate_18 ) - TCFrameRate = 18; - else if ( m_ADesc.EditRate == EditRate_22 ) - TCFrameRate = 22; - result = WriteASDCPHeader(PCM_PACKAGE_LABEL, UL(m_Dict->ul(MDD_WAVWrapping)), SOUND_DEF_LABEL, UL(m_EssenceUL), UL(m_Dict->ul(MDD_SoundDataDef)), - m_ADesc.EditRate, TCFrameRate, calc_CBR_frame_size(m_Info, m_ADesc)); + m_ADesc.EditRate, derive_timecode_rate_from_edit_rate(m_ADesc.EditRate), + calc_CBR_frame_size(m_Info, m_ADesc)); } return result; diff --git a/src/AS_DCP_TimedText.cpp b/src/AS_DCP_TimedText.cpp index 56407ed..e77207f 100644 --- a/src/AS_DCP_TimedText.cpp +++ b/src/AS_DCP_TimedText.cpp @@ -577,7 +577,8 @@ ASDCP::TimedText::MXFWriter::h__Writer::SetSourceStream(ASDCP::TimedText::TimedT if ( ASDCP_SUCCESS(result) ) { InitHeader(); - AddDMSegment(m_TDesc.EditRate, 24, TIMED_TEXT_DEF_LABEL, + // timecode rate and essence rate are the same + AddDMSegment(m_TDesc.EditRate, m_TDesc.EditRate, derive_timecode_rate_from_edit_rate(m_TDesc.EditRate), TIMED_TEXT_DEF_LABEL, UL(m_Dict->ul(MDD_DataDataDef)), TIMED_TEXT_PACKAGE_LABEL); AddEssenceDescriptor(UL(m_Dict->ul(MDD_TimedTextWrapping))); diff --git a/src/AS_DCP_internal.h b/src/AS_DCP_internal.h index 6fa4fa1..df342bf 100755 --- a/src/AS_DCP_internal.h +++ b/src/AS_DCP_internal.h @@ -126,6 +126,8 @@ namespace ASDCP //------------------------------------------------------------------------------------------ // + ui32_t derive_timecode_rate_from_edit_rate(const ASDCP::Rational& edit_rate); + Result_t MD_to_WriterInfo(MXF::Identification*, WriterInfo&); Result_t MD_to_CryptoInfo(MXF::CryptographicContext*, WriterInfo&, const Dictionary&); @@ -158,7 +160,7 @@ namespace ASDCP void AddDMScrypt(Partition& HeaderPart, SourcePackage& Package, WriterInfo& Descr, const UL& WrappingUL, const Dictionary*& Dict); - Result_t Read_EKLV_Packet(Kumu::FileReader& File, const ASDCP::Dictionary& Dict, const MXF::OP1aHeader& HeaderPart, + Result_t Read_EKLV_Packet(Kumu::FileReader& File, const ASDCP::Dictionary& Dict, const ASDCP::WriterInfo& Info, Kumu::fpos_t& LastPosition, ASDCP::FrameBuffer& CtFrameBuf, ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC); @@ -168,7 +170,6 @@ namespace ASDCP ui64_t & StreamOffset, const ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL, AESEncContext* Ctx, HMACContext* HMAC); - // class KLReader : public ASDCP::KLVPacket { @@ -295,7 +296,8 @@ namespace ASDCP } // positions file before reading - Result_t ReadEKLVFrame(const ASDCP::MXF::Partition& CurrentPartition, + // allows external control of index offset + Result_t ReadEKLVFrame(const ui64_t& body_offset, ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC) { @@ -308,8 +310,8 @@ namespace ASDCP return RESULT_RANGE; } - // get frame position and go read the frame's key and length - Kumu::fpos_t FilePosition = CurrentPartition.BodyOffset + TmpEntry.StreamOffset; + // get relative frame position, apply offset and go read the frame's key and length + Kumu::fpos_t FilePosition = body_offset + TmpEntry.StreamOffset; Result_t result = RESULT_OK; if ( FilePosition != m_LastPosition ) @@ -324,17 +326,46 @@ namespace ASDCP return result; } + // positions file before reading + // assumes "processed" index entries have absolute positions + Result_t ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf, + const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC) + { + // look up frame index node + IndexTableSegment::IndexEntry TmpEntry; + + if ( KM_FAILURE(m_IndexAccess.Lookup(FrameNum, TmpEntry)) ) + { + DefaultLogSink().Error("Frame value out of range: %u\n", FrameNum); + return RESULT_RANGE; + } + + // get absolute frame position and go read the frame's key and length + Result_t result = RESULT_OK; + + if ( TmpEntry.StreamOffset != m_LastPosition ) + { + m_LastPosition = TmpEntry.StreamOffset; + result = m_File.Seek(TmpEntry.StreamOffset); + } + + if ( KM_SUCCESS(result) ) + result = ReadEKLVPacket(FrameNum, FrameNum + 1, FrameBuf, EssenceUL, Ctx, HMAC); + + return result; + } + // reads from current position Result_t ReadEKLVPacket(ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC) { assert(m_Dict); - return Read_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_LastPosition, m_CtFrameBuf, + return Read_EKLV_Packet(m_File, *m_Dict, m_Info, m_LastPosition, m_CtFrameBuf, FrameNum, SequenceNum, FrameBuf, EssenceUL, Ctx, HMAC); } // Get the position of a frame from a track file - Result_t LocateFrame(const ASDCP::MXF::Partition& CurrentPartition, + Result_t LocateFrame(const ui64_t& body_offset, ui32_t FrameNum, Kumu::fpos_t& streamOffset, i8_t& temporalOffset, i8_t& keyFrameOffset) { @@ -348,7 +379,7 @@ namespace ASDCP } // get frame position, temporal offset, and key frame ofset - streamOffset = CurrentPartition.BodyOffset + TmpEntry.StreamOffset; + streamOffset = body_offset + TmpEntry.StreamOffset; temporalOffset = TmpEntry.TemporalOffset; keyFrameOffset = TmpEntry.KeyFrameOffset; @@ -380,13 +411,13 @@ namespace ASDCP template TrackSet CreateTrackAndSequence(OP1aHeader& Header, PackageT& Package, const std::string TrackName, - const MXF::Rational& EditRate, const UL& Definition, ui32_t TrackID, const Dictionary*& Dict) + const MXF::Rational& clip_edit_rate, const UL& Definition, ui32_t TrackID, const Dictionary*& Dict) { TrackSet NewTrack; NewTrack.Track = new Track(Dict); Header.AddChildObject(NewTrack.Track); - NewTrack.Track->EditRate = EditRate; + NewTrack.Track->EditRate = clip_edit_rate; Package.Tracks.push_back(NewTrack.Track->InstanceUID); NewTrack.Track->TrackID = TrackID; NewTrack.Track->TrackName = TrackName.c_str(); @@ -403,17 +434,19 @@ namespace ASDCP template TrackSet CreateTimecodeTrack(OP1aHeader& Header, PackageT& Package, - const MXF::Rational& EditRate, ui32_t TCFrameRate, ui64_t TCStart, const Dictionary*& Dict) + const MXF::Rational& tc_edit_rate, ui32_t tc_frame_rate, ui64_t TCStart, const Dictionary*& Dict) { assert(Dict); UL TCUL(Dict->ul(MDD_TimecodeDataDef)); - TrackSet NewTrack = CreateTrackAndSequence(Header, Package, "Timecode Track", EditRate, TCUL, 1, Dict); + TrackSet NewTrack = + CreateTrackAndSequence(Header, Package, "Timecode Track", + tc_edit_rate, TCUL, 1, Dict); NewTrack.Clip = new TimecodeComponent(Dict); Header.AddChildObject(NewTrack.Clip); NewTrack.Sequence->StructuralComponents.push_back(NewTrack.Clip->InstanceUID); - NewTrack.Clip->RoundedTimecodeBase = TCFrameRate; + NewTrack.Clip->RoundedTimecodeBase = tc_frame_rate; NewTrack.Clip->StartTimecode = TCStart; NewTrack.Clip->DataDefinition = TCUL; @@ -428,6 +461,7 @@ namespace ASDCP ST_READY, // ready to write frames ST_RUNNING, // one or more frames written ST_FINAL, // index written, file closed + ST_MAX }; // implementation of h__WriterState class Goto_* methods @@ -540,7 +574,8 @@ namespace ASDCP } // - void AddSourceClip(const MXF::Rational& EditRate, ui32_t TCFrameRate, + void AddSourceClip(const MXF::Rational& clip_edit_rate, + const MXF::Rational& tc_edit_rate, ui32_t TCFrameRate, const std::string& TrackName, const UL& EssenceUL, const UL& DataDefinition, const std::string& PackageLabel) { @@ -571,13 +606,13 @@ namespace ASDCP TrackSet MPTCTrack = CreateTimecodeTrack(m_HeaderPart, *m_MaterialPackage, - EditRate, TCFrameRate, 0, m_Dict); + tc_edit_rate, TCFrameRate, 0, m_Dict); m_DurationUpdateList.push_back(&(MPTCTrack.Sequence->Duration)); m_DurationUpdateList.push_back(&(MPTCTrack.Clip->Duration)); TrackSet MPTrack = CreateTrackAndSequence(m_HeaderPart, *m_MaterialPackage, - TrackName, EditRate, DataDefinition, + TrackName, clip_edit_rate, DataDefinition, 2, m_Dict); m_DurationUpdateList.push_back(&(MPTrack.Sequence->Duration)); @@ -603,13 +638,13 @@ namespace ASDCP TrackSet FPTCTrack = CreateTimecodeTrack(m_HeaderPart, *m_FilePackage, - EditRate, TCFrameRate, + tc_edit_rate, TCFrameRate, ui64_C(3600) * TCFrameRate, m_Dict); m_DurationUpdateList.push_back(&(FPTCTrack.Sequence->Duration)); m_DurationUpdateList.push_back(&(FPTCTrack.Clip->Duration)); TrackSet FPTrack = CreateTrackAndSequence(m_HeaderPart, *m_FilePackage, - TrackName, EditRate, DataDefinition, + TrackName, clip_edit_rate, DataDefinition, 2, m_Dict); m_DurationUpdateList.push_back(&(FPTrack.Sequence->Duration)); @@ -630,7 +665,8 @@ namespace ASDCP } // - void AddDMSegment(const MXF::Rational& EditRate, ui32_t TCFrameRate, + void AddDMSegment(const MXF::Rational& clip_edit_rate, + const MXF::Rational& tc_edit_rate, ui32_t tc_frame_rate, const std::string& TrackName, const UL& DataDefinition, const std::string& PackageLabel) { @@ -661,13 +697,13 @@ namespace ASDCP TrackSet MPTCTrack = CreateTimecodeTrack(m_HeaderPart, *m_MaterialPackage, - EditRate, TCFrameRate, 0, m_Dict); + tc_edit_rate, tc_frame_rate, 0, m_Dict); m_DurationUpdateList.push_back(&(MPTCTrack.Sequence->Duration)); m_DurationUpdateList.push_back(&(MPTCTrack.Clip->Duration)); TrackSet MPTrack = CreateTrackAndSequence(m_HeaderPart, *m_MaterialPackage, - TrackName, EditRate, DataDefinition, + TrackName, clip_edit_rate, DataDefinition, 2, m_Dict); m_DurationUpdateList.push_back(&(MPTrack.Sequence->Duration)); @@ -693,14 +729,14 @@ namespace ASDCP TrackSet FPTCTrack = CreateTimecodeTrack(m_HeaderPart, *m_FilePackage, - EditRate, TCFrameRate, - ui64_C(3600) * TCFrameRate, m_Dict); + clip_edit_rate, tc_frame_rate, + ui64_C(3600) * tc_frame_rate, m_Dict); m_DurationUpdateList.push_back(&(FPTCTrack.Sequence->Duration)); m_DurationUpdateList.push_back(&(FPTCTrack.Clip->Duration)); TrackSet FPTrack = CreateTrackAndSequence(m_HeaderPart, *m_FilePackage, - TrackName, EditRate, DataDefinition, + TrackName, clip_edit_rate, DataDefinition, 2, m_Dict); m_DurationUpdateList.push_back(&(FPTrack.Sequence->Duration)); @@ -708,7 +744,7 @@ namespace ASDCP m_HeaderPart.AddChildObject(FPTrack.Clip); FPTrack.Sequence->StructuralComponents.push_back(FPTrack.Clip->InstanceUID); FPTrack.Clip->DataDefinition = DataDefinition; - FPTrack.Clip->EventComment = "D-Cinema Timed Text"; + FPTrack.Clip->EventComment = "ST 429-5 Timed Text"; m_DurationUpdateList.push_back(&(FPTrack.Clip->Duration)); m_EssenceDescriptor->LinkedTrackID = FPTrack.Track->TrackID; diff --git a/src/Index.cpp b/src/Index.cpp index 42bdce0..b7796bc 100755 --- a/src/Index.cpp +++ b/src/Index.cpp @@ -35,7 +35,7 @@ const ui32_t kl_length = ASDCP::SMPTE_UL_LENGTH + ASDCP::MXF_BER_LENGTH; // ASDCP::MXF::IndexTableSegment::IndexTableSegment(const Dictionary*& d) : - InterchangeObject(d), m_Dict(d), + InterchangeObject(d), m_Dict(d), RtFileOffset(0), RtEntryOffset(0), IndexStartPosition(0), IndexDuration(0), EditUnitByteCount(0), IndexSID(129), BodySID(1), SliceCount(0), PosTableCount(0) { diff --git a/src/KM_log.h b/src/KM_log.h index bc99cd1..9279f30 100755 --- a/src/KM_log.h +++ b/src/KM_log.h @@ -88,6 +88,7 @@ namespace Kumu LOG_NOTICE, // application user info LOG_ALERT, // application non-fatal or near-miss error LOG_CRIT, // application fatal error + LOG_MAX }; diff --git a/src/MDD.cpp b/src/MDD.cpp index 5afb75e..c60fc06 100644 --- a/src/MDD.cpp +++ b/src/MDD.cpp @@ -995,6 +995,24 @@ static const ASDCP::MDDEntry s_MDD_Table[] = { { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 319 0x03, 0x02, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00 }, {0}, false, "DCAudioChannel_VIN" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 320 + 0x03, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00 }, + {0}, false, "DCAudioSoundfield_51" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 321 + 0x03, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00 }, + {0}, false, "DCAudioSoundfield_71" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 322 + 0x03, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00 }, + {0}, false, "DCAudioSoundfield_SDS" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 323 + 0x03, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 }, + {0}, false, "DCAudioSoundfield_61" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, // 324 + 0x03, 0x02, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00 }, + {0}, false, "DCAudioSoundfield_M" }, + { { 0x06, 0x0e, 0x2b, 0x34, 0x01, 0x02, 0x01, 0x01, // 325 + 0x0d, 0x01, 0x03, 0x01, 0x16, 0x01, 0x02, 0x00 }, + {0}, false, "WAVEssenceClip" }, { {0}, {0}, false, 0 } }; diff --git a/src/MDD.h b/src/MDD.h index ab8482f..7cbe35e 100755 --- a/src/MDD.h +++ b/src/MDD.h @@ -355,6 +355,12 @@ namespace ASDCP { MDD_DCAudioChannel_Cs, // 317 MDD_DCAudioChannel_HI, // 318 MDD_DCAudioChannel_VIN, // 319 + MDD_DCAudioSoundfield_51, // 320 + MDD_DCAudioSoundfield_71, // 321 + MDD_DCAudioSoundfield_SDS, // 322 + MDD_DCAudioSoundfield_61, // 323 + MDD_DCAudioSoundfield_M, // 324 + MDD_WAVEssenceClip, // 325 MDD_Max }; // enum MDD_t diff --git a/src/MXF.cpp b/src/MXF.cpp index ad34095..6686e6a 100755 --- a/src/MXF.cpp +++ b/src/MXF.cpp @@ -781,6 +781,11 @@ ASDCP::MXF::OP1aHeader::InitFromBuffer(const byte_t* p, ui32_t l) if ( object->IsA(m_Dict->ul(MDD_KLVFill)) ) { delete object; + + if ( p > end_p ) + { + DefaultLogSink().Error("Fill item short read: %d.\n", p - end_p); + } } else if ( object->IsA(m_Dict->ul(MDD_Primer)) ) // TODO: only one primer should be found { @@ -1442,6 +1447,207 @@ ASDCP::MXF::CreateObject(const Dictionary*& Dict, const UL& label) } +//------------------------------------------------------------------------------------------ + + +ASDCP::MXF::MCAConfigParser::MCAConfigParser(const Dictionary*& d) : m_Dict(d), m_ChannelCount(0) +{ + label_map.insert(label_map_t::value_type("L", m_Dict->ul(MDD_DCAudioChannel_L))); + label_map.insert(label_map_t::value_type("R", m_Dict->ul(MDD_DCAudioChannel_R))); + label_map.insert(label_map_t::value_type("C", m_Dict->ul(MDD_DCAudioChannel_C))); + label_map.insert(label_map_t::value_type("LFE", m_Dict->ul(MDD_DCAudioChannel_LFE))); + label_map.insert(label_map_t::value_type("Ls", m_Dict->ul(MDD_DCAudioChannel_Ls))); + label_map.insert(label_map_t::value_type("Rs", m_Dict->ul(MDD_DCAudioChannel_Rs))); + label_map.insert(label_map_t::value_type("Lss", m_Dict->ul(MDD_DCAudioChannel_Lss))); + label_map.insert(label_map_t::value_type("Rss", m_Dict->ul(MDD_DCAudioChannel_Rss))); + label_map.insert(label_map_t::value_type("Lrs", m_Dict->ul(MDD_DCAudioChannel_Lrs))); + label_map.insert(label_map_t::value_type("Rrs", m_Dict->ul(MDD_DCAudioChannel_Rrs))); + label_map.insert(label_map_t::value_type("Lc", m_Dict->ul(MDD_DCAudioChannel_Lc))); + label_map.insert(label_map_t::value_type("Rc", m_Dict->ul(MDD_DCAudioChannel_Rc))); + label_map.insert(label_map_t::value_type("Cs", m_Dict->ul(MDD_DCAudioChannel_Cs))); + label_map.insert(label_map_t::value_type("HI", m_Dict->ul(MDD_DCAudioChannel_HI))); + label_map.insert(label_map_t::value_type("VIN", m_Dict->ul(MDD_DCAudioChannel_VIN))); + label_map.insert(label_map_t::value_type("51", m_Dict->ul(MDD_DCAudioSoundfield_51))); + label_map.insert(label_map_t::value_type("71", m_Dict->ul(MDD_DCAudioSoundfield_71))); + label_map.insert(label_map_t::value_type("SDS", m_Dict->ul(MDD_DCAudioSoundfield_SDS))); + label_map.insert(label_map_t::value_type("61", m_Dict->ul(MDD_DCAudioSoundfield_61))); + label_map.insert(label_map_t::value_type("M", m_Dict->ul(MDD_DCAudioSoundfield_M))); +} + +// +ui32_t +ASDCP::MXF::MCAConfigParser::ChannelCount() const +{ + return m_ChannelCount; +} + +// 51(L,R,C,LFE,Ls,Rs),HI,VIN +bool +ASDCP::MXF::MCAConfigParser::DecodeString(const std::string& s, const std::string& language) +{ + std::string symbol_buf; + m_ChannelCount = 0; + ASDCP::MXF::SoundfieldGroupLabelSubDescriptor *current_soundfield = 0; + std::string::const_iterator i; + + for ( i = s.begin(); i != s.end(); ++i ) + { + if ( *i == '(' ) + { + if ( current_soundfield != 0 ) + { + fprintf(stderr, "Encountered '(', already processing a soundfield group.\n"); + return false; + } + + if ( symbol_buf.empty() ) + { + fprintf(stderr, "Encountered '(', without leading soundfield group symbol.\n"); + return false; + } + + label_map_t::const_iterator i = label_map.find(symbol_buf); + + if ( i == label_map.end() ) + { + fprintf(stderr, "Unknown symbol: '%s'\n", symbol_buf.c_str()); + return false; + } + + if ( i->second.Value()[10] != 2 ) // magic depends on UL "Essence Facet" byte (see ST 428-12) + { + fprintf(stderr, "Not a soundfield group symbol: '%s'\n", symbol_buf.c_str()); + return false; + } + + current_soundfield = new ASDCP::MXF::SoundfieldGroupLabelSubDescriptor(m_Dict); + + GenRandomValue(current_soundfield->InstanceUID); + GenRandomValue(current_soundfield->MCALinkID); + current_soundfield->MCATagSymbol = "sg" + i->first; + current_soundfield->MCATagName = i->first; + current_soundfield->RFC5646SpokenLanguage = language; + current_soundfield->MCALabelDictionaryID = i->second; + push_back(reinterpret_cast(current_soundfield)); + symbol_buf.clear(); + } + else if ( *i == ')' ) + { + if ( current_soundfield == 0 ) + { + fprintf(stderr, "Encountered ')', not currently processing a soundfield group.\n"); + return false; + } + + if ( symbol_buf.empty() ) + { + fprintf(stderr, "Soundfield group description contains no channels.\n"); + return false; + } + + label_map_t::const_iterator i = label_map.find(symbol_buf); + + if ( i == label_map.end() ) + { + fprintf(stderr, "Unknown symbol: '%s'\n", symbol_buf.c_str()); + return false; + } + + ASDCP::MXF::AudioChannelLabelSubDescriptor *channel_descr = + new ASDCP::MXF::AudioChannelLabelSubDescriptor(m_Dict); + + GenRandomValue(channel_descr->InstanceUID); + assert(current_soundfield); + channel_descr->MCALinkID = current_soundfield->MCALinkID; + channel_descr->MCAChannelID = m_ChannelCount++; + channel_descr->MCATagSymbol = "ch" + i->first; + channel_descr->MCATagName = i->first; + channel_descr->RFC5646SpokenLanguage = language; + channel_descr->MCALabelDictionaryID = i->second; + push_back(reinterpret_cast(channel_descr)); + symbol_buf.clear(); + current_soundfield = 0; + } + else if ( *i == ',' ) + { + if ( ! symbol_buf.empty() ) + { + label_map_t::const_iterator i = label_map.find(symbol_buf); + + if ( i == label_map.end() ) + { + fprintf(stderr, "Unknown symbol: '%s'\n", symbol_buf.c_str()); + return false; + } + + if ( i->second.Value()[10] != 1 ) // magic depends on UL "Essence Facet" byte (see ST 428-12) + { + fprintf(stderr, "Not a channel symbol: '%s'\n", symbol_buf.c_str()); + return false; + } + + ASDCP::MXF::AudioChannelLabelSubDescriptor *channel_descr = + new ASDCP::MXF::AudioChannelLabelSubDescriptor(m_Dict); + + GenRandomValue(channel_descr->InstanceUID); + + if ( current_soundfield != 0 ) + { + channel_descr->MCALinkID = current_soundfield->MCALinkID; + } + + channel_descr->MCAChannelID = m_ChannelCount++; + channel_descr->MCATagSymbol = "ch" + i->first; + channel_descr->MCATagName = i->first; + channel_descr->RFC5646SpokenLanguage = language; + channel_descr->MCALabelDictionaryID = i->second; + push_back(reinterpret_cast(channel_descr)); + symbol_buf.clear(); + } + } + else if ( isalnum(*i) ) + { + symbol_buf += *i; + } + else if ( ! isspace(*i) ) + { + fprintf(stderr, "Unexpected character '%c'.\n", *i); + return false; + } + } + + if ( ! symbol_buf.empty() ) + { + label_map_t::const_iterator i = label_map.find(symbol_buf); + + if ( i == label_map.end() ) + { + fprintf(stderr, "Unknown symbol: '%s'\n", symbol_buf.c_str()); + return false; + } + + ASDCP::MXF::AudioChannelLabelSubDescriptor *channel_descr = + new ASDCP::MXF::AudioChannelLabelSubDescriptor(m_Dict); + + GenRandomValue(channel_descr->InstanceUID); + + if ( current_soundfield != 0 ) + { + channel_descr->MCALinkID = current_soundfield->MCALinkID; + } + + channel_descr->MCAChannelID = m_ChannelCount++; + channel_descr->MCATagSymbol = "ch" + i->first; + channel_descr->MCATagName = i->first; + channel_descr->RFC5646SpokenLanguage = language; + channel_descr->MCALabelDictionaryID = i->second; + push_back(reinterpret_cast(channel_descr)); + } + + return true; +} + + // // end MXF.cpp // diff --git a/src/MXF.h b/src/MXF.h index 50574c2..3b901ac 100755 --- a/src/MXF.h +++ b/src/MXF.h @@ -242,6 +242,9 @@ namespace ASDCP virtual void Dump(FILE* stream = 0); }; + // + typedef std::list InterchangeObject_list_t; + // class Preface : public InterchangeObject { @@ -321,6 +324,8 @@ namespace ASDCP }; const Dictionary*& m_Dict; + ui64_t RtFileOffset; // not part of the MXF structure: used to manage runtime index access + ui64_t RtEntryOffset; Rational IndexEditRate; ui64_t IndexStartPosition; @@ -410,6 +415,42 @@ namespace ASDCP virtual void SetIndexParamsVBR(IPrimerLookup* lookup, const Rational& Rate, Kumu::fpos_t offset); }; + //--------------------------------------------------------------------------------- + // + + // + inline std::string to_lower(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + return str; + } + + // ignore case when searching for audio labels + struct ci_comp + { + inline bool operator()(const std::string& a, const std::string& b) const { + return to_lower(a) < to_lower(b); + } + }; + + // + class MCAConfigParser : public InterchangeObject_list_t + { + typedef std::map label_map_t; + label_map_t label_map; + ui32_t m_ChannelCount; + + const Dictionary*& m_Dict; + + KM_NO_COPY_CONSTRUCT(MCAConfigParser); + MCAConfigParser(); + + public: + MCAConfigParser(const Dictionary*&); + bool DecodeString(const std::string& s, const std::string& language = "en"); + + // Valid only after a successful call to DecodeString + ui32_t ChannelCount() const; + }; } // namespace MXF } // namespace ASDCP diff --git a/src/MXFTypes.h b/src/MXFTypes.h index 82a5c68..05672d6 100755 --- a/src/MXFTypes.h +++ b/src/MXFTypes.h @@ -325,7 +325,7 @@ namespace ASDCP class VersionType : public Kumu::IArchive { public: - enum Release_t { RL_UNKNOWN, RL_RELEASE, RL_DEVELOPMENT, RL_PATCHED, RL_BETA, RL_PRIVATE }; + enum Release_t { RL_UNKNOWN, RL_RELEASE, RL_DEVELOPMENT, RL_PATCHED, RL_BETA, RL_PRIVATE, RL_MAX }; ui16_t Major; ui16_t Minor; ui16_t Patch; diff --git a/src/WavFileWriter.h b/src/WavFileWriter.h index 9611adc..6f507c8 100755 --- a/src/WavFileWriter.h +++ b/src/WavFileWriter.h @@ -98,7 +98,8 @@ class WavFileWriter enum SplitType_t { ST_NONE, // write all channels to a single WAV file ST_MONO, // write each channel a separate WAV file - ST_STEREO // write channel pairs to separate WAV files + ST_STEREO, // write channel pairs to separate WAV files + ST_MAX }; ASDCP::Result_t diff --git a/src/as-02-unwrap.cpp b/src/as-02-unwrap.cpp index 6e68507..42d4820 100755 --- a/src/as-02-unwrap.cpp +++ b/src/as-02-unwrap.cpp @@ -1,5 +1,7 @@ /* -Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst +Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, +John Hurst + All rights reserved. Redistribution and use in source and binary forms, with or without @@ -38,6 +40,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +namespace ASDCP { + Result_t MD_to_PCM_ADesc(ASDCP::MXF::WaveAudioDescriptor* ADescObj, ASDCP::PCM::AudioDescriptor& ADesc); +} + using namespace ASDCP; const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte; @@ -63,7 +69,7 @@ banner(FILE* stream = stdout) { fprintf(stream, "\n\ %s (asdcplib %s)\n\n\ -Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\ +Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\ asdcplib may be copied only under the terms of the license found at\n\ the top of every file in the asdcplib distribution kit.\n\n\ Specify the -h (help) option for further information about %s\n\n", @@ -132,6 +138,7 @@ public: bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead ui32_t picture_rate; // fps of picture when wrapping PCM ui32_t fb_size; // size of picture frame buffer + Rational edit_rate; // frame buffer size for reading clip-wrapped PCM const char* file_prefix; // filename pre for files written by the extract mode byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true) byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true) @@ -281,15 +288,58 @@ read_JP2K_file(CommandOptions& Options) if ( ASDCP_SUCCESS(result) ) { - JP2K::PictureDescriptor PDesc; - Reader.FillPictureDescriptor(PDesc); - - frame_count = PDesc.ContainerDuration; - if ( Options.verbose_flag ) { fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size); - JP2K::PictureDescriptorDump(PDesc); + } + + ASDCP::MXF::RGBAEssenceDescriptor *rgba_descriptor = 0; + ASDCP::MXF::CDCIEssenceDescriptor *cdci_descriptor = 0; + + result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor), + reinterpret_cast(&rgba_descriptor)); + + if ( KM_SUCCESS(result) ) + { + assert(rgba_descriptor); + frame_count = rgba_descriptor->ContainerDuration; + + if ( Options.verbose_flag ) + { + rgba_descriptor->Dump(); + } + } + else + { + result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor), + reinterpret_cast(&cdci_descriptor)); + + if ( KM_SUCCESS(result) ) + { + assert(cdci_descriptor); + frame_count = cdci_descriptor->ContainerDuration; + + if ( Options.verbose_flag ) + { + cdci_descriptor->Dump(); + } + } + else + { + fprintf(stderr, "File does not contain an essence descriptor.\n"); + frame_count = Reader.AS02IndexReader().GetDuration(); + } + } + + if ( frame_count == 0 ) + { + frame_count = Reader.AS02IndexReader().GetDuration(); + } + + if ( frame_count == 0 ) + { + fprintf(stderr, "Unable to determine file duration.\n"); + return RESULT_FAIL; } } @@ -360,43 +410,87 @@ read_PCM_file(CommandOptions& Options) AS_02::PCM::MXFReader Reader; PCM::FrameBuffer FrameBuffer; WavFileWriter OutWave; - PCM::AudioDescriptor ADesc; ui32_t last_frame = 0; + ASDCP::MXF::WaveAudioDescriptor *wave_descriptor = 0; - Result_t result = Reader.OpenRead(Options.input_filename); + if ( Options.edit_rate == Rational(0,0) ) // todo, make this available to the CLI + { + Options.edit_rate = EditRate_24; + } + + Result_t result = Reader.OpenRead(Options.input_filename, Options.edit_rate); if ( ASDCP_SUCCESS(result) ) { - Reader.FillAudioDescriptor(ADesc); - ADesc.EditRate = Rational(1, 1); - FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc)); + if ( Options.verbose_flag ) + { + fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size); + } + + result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor), + reinterpret_cast(&wave_descriptor)); + + if ( KM_SUCCESS(result) ) + { + assert(wave_descriptor); + last_frame = wave_descriptor->ContainerDuration; + + if ( Options.verbose_flag ) + { + wave_descriptor->Dump(); + } + } + else + { + fprintf(stderr, "File does not contain an essence descriptor.\n"); + last_frame = Reader.AS02IndexReader().GetDuration(); + } + + if ( last_frame == 0 ) + { + fprintf(stderr, "Unable to determine file duration.\n"); + return RESULT_FAIL; + } + + FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate)); if ( Options.verbose_flag ) - PCM::AudioDescriptorDump(ADesc); + { + wave_descriptor->Dump(); + } } if ( ASDCP_SUCCESS(result) ) { - last_frame = ADesc.ContainerDuration; - if ( Options.duration > 0 && Options.duration < last_frame ) last_frame = Options.duration; if ( Options.start_frame > 0 ) { - if ( Options.start_frame > ADesc.ContainerDuration ) + if ( Options.start_frame > last_frame ) { fprintf(stderr, "Start value greater than file duration.\n"); return RESULT_FAIL; } - last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration); + last_frame = Kumu::xmin(Options.start_frame + last_frame, last_frame); } - ADesc.ContainerDuration = last_frame - Options.start_frame; - OutWave.OpenWrite(ADesc, Options.file_prefix, - ( Options.split_wav ? WavFileWriter::ST_STEREO : - ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) )); + last_frame = last_frame - Options.start_frame; + + PCM::AudioDescriptor ADesc; + + result = MD_to_PCM_ADesc(wave_descriptor, ADesc); + + if ( ASDCP_SUCCESS(result) ) + { + ADesc.ContainerDuration = last_frame; + ADesc.EditRate = Options.edit_rate; + + result = OutWave.OpenWrite(ADesc, Options.file_prefix, + ( Options.split_wav ? WavFileWriter::ST_STEREO : + ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) )); + } } if ( ASDCP_SUCCESS(result) && Options.key_flag ) diff --git a/src/as-02-wrap.cpp b/src/as-02-wrap.cpp index 6f79012..b5cb138 100755 --- a/src/as-02-wrap.cpp +++ b/src/as-02-wrap.cpp @@ -1,5 +1,7 @@ /* -Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst +Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, +John Hurst + All rights reserved. Redistribution and use in source and binary forms, with or without @@ -43,6 +45,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace ASDCP; const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte; +const ASDCP::Dictionary *g_dict = 0; const char* @@ -52,6 +55,8 @@ RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len) return buf; } + + //------------------------------------------------------------------------------------------ // // command line option parser class @@ -92,7 +97,7 @@ banner(FILE* stream = stdout) { fprintf(stream, "\n\ %s (asdcplib %s)\n\n\ -Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\ +Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\ asdcplib may be copied only under the terms of the license found at\n\ the top of every file in the asdcplib distribution kit.\n\n\ Specify the -h (help) option for further information about %s\n\n", @@ -108,7 +113,7 @@ USAGE: %s [-h|-help] [-V]\n\ \n\ %s [-a ] [-b ] [-C
    ] [-d ]\n\ [-e|-E] [-f ] [-j ] [-k ]\n\ - [-M] [-p /] [-s ] [-v] [-W]\n\ + [-M] [-m ] [-r /] [-s ] [-v] [-W]\n\ [-z|-Z] + \n\n", PROGRAM_NAME, PROGRAM_NAME); @@ -122,12 +127,14 @@ Options:\n\ -j - Write key ID instead of creating a random value\n\ -k - Use key for ciphertext operations\n\ -M - Do not create HMAC values when writing\n\ + -m - Write MCA labels using . Example:\n\ + 51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\ -a - Specify the Asset ID of the file\n\ -b - Specify size in bytes of picture frame buffer\n\ Defaults to 4,194,304 (4MB)\n\ -d - Number of frames to process, default all\n\ -f - Starting frame number, default 0\n\ - -p / - Edit Rate of the output file. 24/1 is the default\n\ + -r / - Edit Rate of the output file. 24/1 is the default\n\ -s - Duration of a frame-wrapped partition (default 60)\n\ -v - Verbose, prints informative messages to stderr\n\ -W - Read input file only, do not write source file\n\ @@ -138,6 +145,20 @@ Options:\n\ o All option arguments must be separated from the option by whitespace.\n\n"); } +// +static ASDCP::Rational +decode_rational(const char* str_rat) +{ + assert(str_rat); + ui32_t Num = atoi(str_rat); + ui32_t Den = 0; + + const char* den_str = strrchr(str_rat, ' '); + if ( den_str != 0 ) + Den = atoi(den_str+1); + + return ASDCP::Rational(Num, Den); +} // // class CommandOptions @@ -165,9 +186,10 @@ public: byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true) byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true) std::string out_file; // - bool show_ul_values; /// if true, dump the UL table before going tp work. + bool show_ul_values_flag; /// if true, dump the UL table before going tp work. Kumu::PathList_t filenames; // list of filenames to be processed UL channel_assignment; + ASDCP::MXF::MCAConfigParser mca_config; //new attributes for AS-02 support AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip @@ -178,8 +200,9 @@ public: error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false), encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0), no_write_flag(false), version_flag(false), help_flag(false), start_frame(0), - duration(0xffffffff), j2c_pedantic(true), edit_rate(30,1), fb_size(FRAME_BUFFER_SIZE), - show_ul_values(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60) + duration(0xffffffff), j2c_pedantic(true), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE), + show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60), + mca_config(g_dict) { memset(key_value, 0, KeyLen); memset(key_id_value, 0, UUIDlen); @@ -277,11 +300,17 @@ public: case 'M': write_hmac = false; break; - case 'p': - TEST_EXTRA_ARG(i, 'p'); - /// TODO: VERY BROKEN, WANT RATIONAL - edit_rate.Numerator = abs(atoi(argv[i])); - edit_rate.Denominator = 1; + case 'm': + TEST_EXTRA_ARG(i, 'm'); + if ( ! mca_config.DecodeString(argv[i]) ) + { + return; + } + break; + + case 'r': + TEST_EXTRA_ARG(i, 'r'); + edit_rate = decode_rational(argv[i]); break; case 's': @@ -289,6 +318,7 @@ public: partition_space = abs(atoi(argv[i])); break; + case 'u': show_ul_values_flag = true; break; case 'V': version_flag = true; break; case 'v': verbose_flag = true; break; case 'W': no_write_flag = true; break; @@ -334,6 +364,15 @@ public: //------------------------------------------------------------------------------------------ // JPEG 2000 essence +namespace ASDCP { + Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc, + const ASDCP::Dictionary& dict, + ASDCP::MXF::RGBAEssenceDescriptor *EssenceDescriptor, + ASDCP::MXF::JPEG2000PictureSubDescriptor *EssenceSubDescriptor); + + Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj); +} + // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file // @@ -344,10 +383,11 @@ write_JP2K_file(CommandOptions& Options) HMACContext* HMAC = 0; AS_02::JP2K::MXFWriter Writer; JP2K::FrameBuffer FrameBuffer(Options.fb_size); - JP2K::PictureDescriptor PDesc; JP2K::SequenceParser Parser; byte_t IV_buf[CBC_BLOCK_SIZE]; Kumu::FortunaRNG RNG; + ASDCP::MXF::RGBAEssenceDescriptor *essence_descriptor = 0; + ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors; // set up essence parser Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic); @@ -355,6 +395,7 @@ write_JP2K_file(CommandOptions& Options) // set up MXF writer if ( ASDCP_SUCCESS(result) ) { + ASDCP::JP2K::PictureDescriptor PDesc; Parser.FillPictureDescriptor(PDesc); PDesc.EditRate = Options.edit_rate; @@ -365,6 +406,17 @@ write_JP2K_file(CommandOptions& Options) fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size); JP2K::PictureDescriptorDump(PDesc); } + + // TODO: optionally set up CDCIEssenceDescriptor + essence_descriptor = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict); + essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict)); + + result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict, essence_descriptor, + reinterpret_cast(essence_sub_descriptors.back())); + + /// TODO: set with magic or some such thing + essence_descriptor->ComponentMaxRef = 4095; + essence_descriptor->ComponentMinRef = 0; } if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) @@ -403,7 +455,13 @@ write_JP2K_file(CommandOptions& Options) } if ( ASDCP_SUCCESS(result) ) - result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc, Options.index_strategy, Options.partition_space); + { + result = Writer.OpenWrite(Options.out_file, Info, + static_cast(essence_descriptor), + essence_sub_descriptors, + Options.edit_rate, 16384, Options.index_strategy, Options.partition_space); + // TODO: make 16384 part of CommandOptions + } } if ( ASDCP_SUCCESS(result) ) @@ -462,16 +520,17 @@ write_PCM_file(CommandOptions& Options) PCMParserList Parser; AS_02::PCM::MXFWriter Writer; PCM::FrameBuffer FrameBuffer; - PCM::AudioDescriptor ADesc; byte_t IV_buf[CBC_BLOCK_SIZE]; Kumu::FortunaRNG RNG; + ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0; // set up essence parser - Result_t result = Parser.OpenRead(Options.filenames, Rational(1, 1)); + Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate); // set up MXF writer if ( ASDCP_SUCCESS(result) ) { + ASDCP::PCM::AudioDescriptor ADesc; Parser.FillAudioDescriptor(ADesc); ADesc.EditRate = Options.edit_rate; @@ -487,6 +546,27 @@ write_PCM_file(CommandOptions& Options) fputs("AudioDescriptor:\n", stderr); PCM::AudioDescriptorDump(ADesc); } + + essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict); + + result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor); + + if ( Options.mca_config.empty() ) + { + essence_descriptor->ChannelAssignment = Options.channel_assignment; + } + else + { + if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount ) + { + fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n", + Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount); + return RESULT_FAIL; + } + + // this is the d-cinema MCA label, what is the one for IMF? + essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_MCA); + } } if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) @@ -525,14 +605,9 @@ write_PCM_file(CommandOptions& Options) } if ( ASDCP_SUCCESS(result) ) - result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc); - - if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() ) { - MXF::WaveAudioDescriptor *descriptor = 0; - Writer.OP1aHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor), - reinterpret_cast(&descriptor)); - descriptor->ChannelAssignment = Options.channel_assignment; + result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor, + Options.mca_config, Options.edit_rate); } } @@ -589,6 +664,8 @@ main(int argc, const char** argv) { Result_t result = RESULT_OK; char str_buf[64]; + g_dict = &ASDCP::DefaultSMPTEDict(); + CommandOptions Options(argc, argv); if ( Options.version_flag ) @@ -597,7 +674,12 @@ main(int argc, const char** argv) if ( Options.help_flag ) usage(); - if ( Options.version_flag || Options.help_flag ) + if ( Options.show_ul_values_flag ) + { + g_dict->Dump(stdout); + } + + if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag ) return 0; if ( Options.error_flag ) @@ -606,11 +688,6 @@ main(int argc, const char** argv) return 3; } - if ( Options.show_ul_values ) - { - DefaultSMPTEDict().Dump(stdout); - } - EssenceType_t EssenceType; result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType); diff --git a/src/asdcp-wrap.cpp b/src/asdcp-wrap.cpp index 4439f7e..77e7845 100755 --- a/src/asdcp-wrap.cpp +++ b/src/asdcp-wrap.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2012, John Hurst +Copyright (c) 2003-2013, John Hurst All rights reserved. Redistribution and use in source and binary forms, with or without @@ -62,6 +62,8 @@ const byte_t P_HFR_UL_2K[16] = { 0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x03 }; +const ASDCP::Dictionary *g_dict = 0; + //------------------------------------------------------------------------------------------ // // command line option parser class @@ -118,8 +120,8 @@ USAGE: %s [-h|-help] [-V]\n\ \n\ %s [-3] [-a ] [-b ] [-C
      ] [-d ]\n\ [-e|-E] [-f ] [-j ] [-k ]\n\ - [-l