stereoscopic JP2K writing
authorjhurst <jhurst@cinecert.com>
Wed, 26 Sep 2007 18:31:20 +0000 (18:31 +0000)
committerjhurst <>
Wed, 26 Sep 2007 18:31:20 +0000 (18:31 +0000)
src/AS_DCP.h
src/AS_DCP_JP2K.cpp
src/AS_DCP_MXF.cpp
src/KLV.cpp
src/MXF.cpp
src/MXF.h
src/asdcp-test.cpp
src/klvwalk.cpp

index 9dc7f5b755e233103550e47949d644ce92041dcd..9878c814a9a532ad730c17234b46cc6b23a59982 100755 (executable)
@@ -34,16 +34,21 @@ D-Cinema packaging working group DC28.20.  The file format, labeled
 AS-DCP, is described in series of separate documents which include but
 may not be limited to:
 
- o AS-DCP Track File Specification
- o AS-DCP Track File Essence Encryption Specification
- o AS-DCP Operational Constraints Specification
+ o MXF Interop Track File Specification
+ o MXF Interop Track File Essence Encryption Specification
+ o MXF Interop Operational Constraints Specification
+ o SMPTE 429-3-2006 Track File Specification
+ o SMPTE 429-4-2006 JPEG 2000 for D-Cinema
+ o SMPTE 429-5-200X Timed Text Track File
+ o SMPTE 429-6-2006 Essence Encryption Specification
+ o SMPTE 429-10-2006 Stereoscopic Image Track File
  o SMPTE 330M - UMID
  o SMPTE 336M - KLV
  o SMPTE 377M - MXF
  o SMPTE 390M - OP-Atom
  o SMPTE 379M - Generic Container
  o SMPTE 381M - MPEG2 picture
- o SMPTE XXXM - JPEG 2000 picture
+ o SMPTE 422M - JPEG 2000 picture
  o SMPTE 382M - WAV/PCM sound
  o IETF RFC 2104 - HMAC/SHA1
  o NIST FIPS 197 - AES (Rijndael)
@@ -143,8 +148,8 @@ namespace ASDCP {
   // 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.
   const ui32_t VERSION_MAJOR = 1;
-  const ui32_t VERSION_APIMINOR = 1;
-  const ui32_t VERSION_IMPMINOR = 15;
+  const ui32_t VERSION_APIMINOR = 2;
+  const ui32_t VERSION_IMPMINOR = 16;
   const char* Version();
 
   // UUIDs are passed around as strings of UUIDlen bytes
@@ -190,6 +195,7 @@ namespace ASDCP {
   const Kumu::Result_t RESULT_CRYPT_INIT (-111, "Error initializing block cipher context.");
   const Kumu::Result_t RESULT_EMPTY_FB   (-112, "Empty frame buffer.");
   const Kumu::Result_t RESULT_KLV_CODING (-113, "KLV coding error.");
+  const Kumu::Result_t RESULT_SPHASE     (-114, "Stereoscopic phase mismatch.");
 
   //---------------------------------------------------------------------------------
   // file identification
@@ -203,6 +209,7 @@ namespace ASDCP {
     ESS_PCM_24b_48k, // the file contains one or more PCM audio pairs
     ESS_PCM_24b_96k, // the file contains one or more PCM audio pairs
     ESS_TIMED_TEXT,  // the file contains an XML timed text document and one or more resources
+    ESS_JPEG_2000_S, // the file contains one or more JPEG 2000 codestream pairs (stereoscopic)
   };
 
   // Determine the type of essence contained in the given MXF file. RESULT_OK
@@ -244,9 +251,10 @@ namespace ASDCP {
 
   // common edit rates, use these instead of hard coded constants
   const Rational EditRate_24(24,1);
-  const Rational EditRate_23_98(24000,1001);
+  const Rational EditRate_23_98(24000,1001); // Not a DCI-compliant value!
   const Rational EditRate_48(48,1);
   const Rational SampleRate_48k(48000,1);
+  const Rational SampleRate_96k(96000,1);
 
   // Non-reference counting container for internal member objects.
   // Please do not use this class for any other purpose.
@@ -1070,6 +1078,88 @@ namespace ASDCP {
          void     DumpHeaderMetadata(FILE* = 0) const;
          void     DumpIndex(FILE* = 0) const;
        };
+
+
+      // Stereoscopic Image support
+      //
+
+      enum StereoscopicPhase_t
+      {
+       SP_LEFT,
+       SP_RIGHT
+      };
+
+
+      class MXFSWriter
+       {
+         class h__SWriter;
+         mem_ptr<h__SWriter> m_Writer;
+         ASDCP_NO_COPY_CONSTRUCT(MXFSWriter);
+
+       public:
+         MXFSWriter();
+         virtual ~MXFSWriter();
+
+         // 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 WriterInfo&,
+                            const PictureDescriptor&, 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.
+         // Fails if the file is not open, is finalized, or an operating system
+         // error occurs. Frames must be written in the proper phase (L-R-L-R),
+         // RESULT_SPHASE will be returned if phase is reversed. The first frame
+         // written must be left eye.
+         Result_t WriteFrame(const FrameBuffer&, StereoscopicPhase_t phase,
+                             AESEncContext* = 0, HMACContext* = 0);
+
+         // Closes the MXF file, writing the index and revised header.  Returns
+         // RESULT_SPHASE if WriteFrame was called an odd number of times.
+         Result_t Finalize();
+       };
+
+      //
+      class MXFSReader
+       {
+         class h__SReader;
+         mem_ptr<h__SReader> m_Reader;
+         ASDCP_NO_COPY_CONSTRUCT(MXFSReader);
+
+       public:
+         MXFSReader();
+         virtual ~MXFSReader();
+
+         // Open the file for reading. The file must exist. Returns error if the
+         // operation cannot be completed.
+         Result_t OpenRead(const char* 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(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(WriterInfo&) const;
+
+         // Reads a frame of essence from the MXF file. If the optional AESEncContext
+         // argument is present, the essence is decrypted after reading. If the MXF
+         // file is encrypted and the AESDecContext argument is NULL, the frame buffer
+         // will contain the ciphertext frame data. If the HMACContext argument is
+         // not NULL, the HMAC will be calculated (if the file supports it).
+         // Returns RESULT_INIT if the file is not open, failure if the frame number is
+         // out of range, or if optional decrypt or HAMC operations fail.
+         Result_t ReadFrame(ui32_t frame_number, StereoscopicPhase_t phase,
+                            FrameBuffer&, AESDecContext* = 0, HMACContext* = 0) const;
+
+         // Print debugging information to stream
+         void     DumpHeaderMetadata(FILE* = 0) const;
+         void     DumpIndex(FILE* = 0) const;
+       };
     } // namespace JP2K
 } // namespace ASDCP
 
index c45f78518791cf47250d25fd6b0bd0c3f2aada2b..a0e84631a3a96875d2be8ca1b7f787608ea2d4cb 100755 (executable)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2004-2006, John Hurst
+Copyright (c) 2004-2007, John Hurst
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -35,6 +35,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 //------------------------------------------------------------------------------------------
 
 static std::string JP2K_PACKAGE_LABEL = "File Package: SMPTE 429-4 frame wrapping of JPEG 2000 codestreams";
+static std::string JP2K_S_PACKAGE_LABEL = "File Package: SMPTE 429-10 frame wrapping of stereoscopic JPEG 2000 codestreams";
 static std::string PICT_DEF_LABEL = "Picture Track";
 
 //
@@ -309,9 +310,10 @@ ASDCP::JP2K::MXFReader::DumpIndex(FILE* stream) const
 //------------------------------------------------------------------------------------------
 
 
+using namespace ASDCP::JP2K;
 
 //
-class ASDCP::JP2K::MXFWriter::h__Writer : public ASDCP::h__Writer
+class lh__Writer : public ASDCP::h__Writer
 {
   JPEG2000PictureSubDescriptor* m_EssenceSubDescriptor;
 
@@ -319,17 +321,18 @@ public:
   PictureDescriptor m_PDesc;
   byte_t            m_EssenceUL[SMPTE_UL_LENGTH];
 
-  ASDCP_NO_COPY_CONSTRUCT(h__Writer);
+  ASDCP_NO_COPY_CONSTRUCT(lh__Writer);
 
-  h__Writer() : m_EssenceSubDescriptor(0) {
+  lh__Writer() : m_EssenceSubDescriptor(0) {
     memset(m_EssenceUL, 0, SMPTE_UL_LENGTH);
   }
 
-  ~h__Writer(){}
+  ~lh__Writer(){}
 
   Result_t OpenWrite(const char*, ui32_t HeaderSize);
-  Result_t SetSourceStream(const PictureDescriptor&);
-  Result_t WriteFrame(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
+  Result_t SetSourceStream(const PictureDescriptor&, const std::string& label,
+                          ASDCP::Rational LocalEditRate = ASDCP::Rational(0,0));
+  Result_t WriteFrame(const JP2K::FrameBuffer&, bool add_index, AESEncContext*, HMACContext*);
   Result_t Finalize();
   Result_t JP2K_PDesc_to_MD(JP2K::PictureDescriptor& PDesc);
 };
@@ -337,7 +340,7 @@ public:
 
 //
 ASDCP::Result_t
-ASDCP::JP2K::MXFWriter::h__Writer::JP2K_PDesc_to_MD(JP2K::PictureDescriptor& PDesc)
+lh__Writer::JP2K_PDesc_to_MD(JP2K::PictureDescriptor& PDesc)
 {
   assert(m_EssenceDescriptor);
   assert(m_EssenceSubDescriptor);
@@ -394,7 +397,7 @@ ASDCP::JP2K::MXFWriter::h__Writer::JP2K_PDesc_to_MD(JP2K::PictureDescriptor& PDe
 // Open the file for writing. The file must not exist. Returns error if
 // the operation cannot be completed.
 ASDCP::Result_t
-ASDCP::JP2K::MXFWriter::h__Writer::OpenWrite(const char* filename, ui32_t HeaderSize)
+lh__Writer::OpenWrite(const char* filename, ui32_t HeaderSize)
 {
   if ( ! m_State.Test_BEGIN() )
     return RESULT_STATE;
@@ -423,18 +426,21 @@ ASDCP::JP2K::MXFWriter::h__Writer::OpenWrite(const char* filename, ui32_t Header
 
 // Automatically sets the MXF file's metadata from the first jpeg codestream stream.
 ASDCP::Result_t
-ASDCP::JP2K::MXFWriter::h__Writer::SetSourceStream(const PictureDescriptor& PDesc)
+lh__Writer::SetSourceStream(const PictureDescriptor& PDesc, const std::string& label, ASDCP::Rational LocalEditRate)
 {
   if ( ! m_State.Test_INIT() )
     return RESULT_STATE;
 
+  if ( LocalEditRate == ASDCP::Rational(0,0) )
+    LocalEditRate = m_PDesc.EditRate;
+
   m_PDesc = PDesc;
   Result_t result = JP2K_PDesc_to_MD(m_PDesc);
 
   if ( ASDCP_SUCCESS(result) )
-      result = WriteMXFHeader(JP2K_PACKAGE_LABEL, UL(Dict::ul(MDD_JPEG_2000Wrapping)),
+      result = WriteMXFHeader(label, UL(Dict::ul(MDD_JPEG_2000Wrapping)),
                              PICT_DEF_LABEL,     UL(Dict::ul(MDD_PictureDataDef)),
-                             m_PDesc.EditRate, 24 /* TCFrameRate */);
+                             LocalEditRate, 24 /* TCFrameRate */);
 
   if ( ASDCP_SUCCESS(result) )
     {
@@ -452,22 +458,23 @@ ASDCP::JP2K::MXFWriter::h__Writer::SetSourceStream(const PictureDescriptor& PDes
 // error occurs.
 //
 ASDCP::Result_t
-ASDCP::JP2K::MXFWriter::h__Writer::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* Ctx,
-                                              HMACContext* HMAC)
+lh__Writer::WriteFrame(const JP2K::FrameBuffer& FrameBuf, bool add_index,
+                      AESEncContext* Ctx, HMACContext* HMAC)
 {
   Result_t result = RESULT_OK;
 
   if ( m_State.Test_READY() )
     result = m_State.Goto_RUNNING(); // first time through
  
-  IndexTableSegment::IndexEntry Entry;
-  Entry.StreamOffset = m_StreamOffset;
+  ui64_t StreamOffset = m_StreamOffset;
 
   if ( ASDCP_SUCCESS(result) )
     result = WriteEKLVPacket(FrameBuf, m_EssenceUL, Ctx, HMAC);
 
-  if ( ASDCP_SUCCESS(result) )
+  if ( ASDCP_SUCCESS(result) && add_index )
     {  
+      IndexTableSegment::IndexEntry Entry;
+      Entry.StreamOffset = StreamOffset;
       m_FooterPart.PushIndexEntry(Entry);
       m_FramesWritten++;
     }
@@ -479,7 +486,7 @@ ASDCP::JP2K::MXFWriter::h__Writer::WriteFrame(const FrameBuffer& FrameBuf, AESEn
 // Closes the MXF file, writing the index and other closing information.
 //
 ASDCP::Result_t
-ASDCP::JP2K::MXFWriter::h__Writer::Finalize()
+lh__Writer::Finalize()
 {
   if ( ! m_State.Test_RUNNING() )
     return RESULT_STATE;
@@ -490,6 +497,12 @@ ASDCP::JP2K::MXFWriter::h__Writer::Finalize()
 }
 
 
+//
+class ASDCP::JP2K::MXFWriter::h__Writer : public lh__Writer
+{
+};
+
+
 //------------------------------------------------------------------------------------------
 
 
@@ -516,7 +529,7 @@ ASDCP::JP2K::MXFWriter::OpenWrite(const char* filename, const WriterInfo& Info,
   if ( ASDCP_SUCCESS(result) )
     {
       m_Writer->m_Info = Info;
-      result = m_Writer->SetSourceStream(PDesc);
+      result = m_Writer->SetSourceStream(PDesc, JP2K_PACKAGE_LABEL);
     }
 
   if ( ASDCP_FAILURE(result) )
@@ -536,7 +549,7 @@ ASDCP::JP2K::MXFWriter::WriteFrame(const FrameBuffer& FrameBuf, AESEncContext* C
   if ( m_Writer.empty() )
     return RESULT_INIT;
 
-  return m_Writer->WriteFrame(FrameBuf, Ctx, HMAC);
+  return m_Writer->WriteFrame(FrameBuf, true, Ctx, HMAC);
 }
 
 // Closes the MXF file, writing the index and other closing information.
@@ -550,6 +563,111 @@ ASDCP::JP2K::MXFWriter::Finalize()
 }
 
 
+//------------------------------------------------------------------------------------------
+//
+
+//
+class ASDCP::JP2K::MXFSWriter::h__SWriter : public lh__Writer
+{
+  StereoscopicPhase_t m_NextPhase;
+
+public:
+  h__SWriter() : m_NextPhase(SP_LEFT) {}
+
+  //
+  Result_t WriteFrame(const FrameBuffer& FrameBuf, StereoscopicPhase_t phase,
+                     AESEncContext* Ctx, HMACContext* HMAC)
+  {
+    if ( m_NextPhase != phase )
+      return RESULT_SPHASE;
+
+    if ( phase == SP_LEFT )
+      {
+       m_NextPhase = SP_RIGHT;
+       return lh__Writer::WriteFrame(FrameBuf, true, Ctx, HMAC);
+      }
+
+    m_NextPhase = SP_LEFT;
+    return lh__Writer::WriteFrame(FrameBuf, false, Ctx, HMAC);
+  }
+
+  //
+  Result_t Finalize()
+  {
+    if ( m_NextPhase != SP_LEFT )
+      return RESULT_SPHASE;
+
+    return lh__Writer::Finalize();
+  }
+};
+
+
+//
+ASDCP::JP2K::MXFSWriter::MXFSWriter()
+{
+}
+
+ASDCP::JP2K::MXFSWriter::~MXFSWriter()
+{
+}
+
+
+// Open the file for writing. The file must not exist. Returns error if
+// the operation cannot be completed.
+ASDCP::Result_t
+ASDCP::JP2K::MXFSWriter::OpenWrite(const char* filename, const WriterInfo& Info,
+                                  const PictureDescriptor& PDesc, ui32_t HeaderSize)
+{
+  m_Writer = new h__SWriter;
+  
+  if ( PDesc.EditRate != ASDCP::EditRate_24 )
+    {
+      DefaultLogSink().Error("Stereoscopic wrapping requires 24 fps input streams.\n");
+      return RESULT_FORMAT;
+    }
+
+  Result_t result = m_Writer->OpenWrite(filename, HeaderSize);
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      m_Writer->m_Info = Info;
+      PictureDescriptor TmpPDesc = PDesc;
+      TmpPDesc.EditRate = ASDCP::EditRate_48;
+
+      result = m_Writer->SetSourceStream(TmpPDesc, JP2K_S_PACKAGE_LABEL, ASDCP::EditRate_24);
+    }
+
+  if ( ASDCP_FAILURE(result) )
+    m_Writer.release();
+
+  return result;
+}
+
+
+// Writes a frame of essence to the MXF file. If the optional AESEncContext
+// argument is present, the essence is encrypted prior to writing.
+// Fails if the file is not open, is finalized, or an operating system
+// error occurs.
+ASDCP::Result_t
+ASDCP::JP2K::MXFSWriter::WriteFrame(const FrameBuffer& FrameBuf, StereoscopicPhase_t phase,
+                                   AESEncContext* Ctx, HMACContext* HMAC)
+{
+  if ( m_Writer.empty() )
+    return RESULT_INIT;
+
+  return m_Writer->WriteFrame(FrameBuf, phase, Ctx, HMAC);
+}
+
+// Closes the MXF file, writing the index and other closing information.
+ASDCP::Result_t
+ASDCP::JP2K::MXFSWriter::Finalize()
+{
+  if ( m_Writer.empty() )
+    return RESULT_INIT;
+
+  return m_Writer->Finalize();
+}
+
 //
 // end AS_DCP_JP2K.cpp
 //
index 247c2dadbca4411a1075fd027ccf079cd378163d..5afd293fe652b622b04a0dfa703cccf579a3f268 100755 (executable)
@@ -149,21 +149,12 @@ ASDCP::EssenceType(const char* filename, EssenceType_t& type)
       type = ESS_UNKNOWN;
       if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
        type = ESS_JPEG_2000;
-      else
-       {
-         if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
-           type = ESS_PCM_24b_48k;
-         else
-           {
-             if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
-               type = ESS_MPEG2_VES;
-             else
-               {
-                 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCTimedTextDescriptor))) )
-                   type = ESS_TIMED_TEXT;
-               }
-           }
-       }
+      else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
+       type = ESS_PCM_24b_48k;
+      else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
+       type = ESS_MPEG2_VES;
+      else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCTimedTextDescriptor))) )
+       type = ESS_TIMED_TEXT;
     }
 
   return result;
index 0e7d783989fa12d4bade3eaf4fc0c463b687586d..21aae248852f184717a757215c96a92d367bbffd 100755 (executable)
@@ -134,12 +134,12 @@ ASDCP::KLVPacket::Dump(FILE* stream, bool show_hex)
   if ( m_KeyStart != 0 )
     {
       assert(m_ValueStart);
-
-      for ( ui32_t i = 0; i < SMPTE_UL_LENGTH; i++ )
-       fprintf(stream, "%02x.", m_KeyStart[i]);
+      UL TmpUL(m_KeyStart);
+      char buf[64];
+      fprintf(stream, "%s", TmpUL.EncodeString(buf, 64));
 
       const MDDEntry* Entry = Dict::FindUL(m_KeyStart);
-      fprintf(stream, "\b  len: %7u (%s)\n", m_ValueLength, (Entry ? Entry->name : "Unknown"));
+      fprintf(stream, "  len: %7u (%s)\n", m_ValueLength, (Entry ? Entry->name : "Unknown"));
 
       if ( show_hex && m_ValueLength < 1000 )
        Kumu::hexdump(m_ValueStart, Kumu::xmin(m_ValueLength, (ui32_t)64), stream);
index fd0b709d7573c90825b6eb2111e321851128a58e..be5e58fa759ac9dac339266871d5ec10ebfa575e 100755 (executable)
@@ -164,8 +164,6 @@ ASDCP::MXF::RIP::Dump(FILE* stream)
 
   KLVFilePacket::Dump(stream, false);
   PairArray.Dump(stream, false);
-
-  fputs("==========================================================================\n", stream);
 }
 
 //------------------------------------------------------------------------------------------
@@ -391,8 +389,6 @@ ASDCP::MXF::Partition::Dump(FILE* stream)
   fprintf(stream, "  BodySID            = %u\n",  BodySID);
   fprintf(stream, "  OperationalPattern = %s\n",  OperationalPattern.EncodeString(identbuf, IdentBufferLen));
   fputs("Essence Containers:\n", stream); EssenceContainers.Dump(stream, false);
-
-  fputs("==========================================================================\n", stream);
 }
 
 
@@ -562,8 +558,6 @@ ASDCP::MXF::Primer::Dump(FILE* stream)
       const MDDEntry* Entry = Dict::FindUL((*i).UL.Value());
       fprintf(stream, "  %s %s\n", (*i).EncodeString(identbuf, IdentBufferLen), (Entry ? Entry->name : "Unknown"));
     }
-
-  fputs("==========================================================================\n", stream);
 }
 
 
@@ -925,16 +919,11 @@ ASDCP::MXF::OPAtomHeader::Dump(FILE* stream)
   if ( stream == 0 )
     stream = stderr;
 
-  if ( m_HasRIP )
-    m_RIP.Dump(stream);
-
   Partition::Dump(stream);
   m_Primer.Dump(stream);
 
   if ( m_Preface == 0 )
     fputs("No Preface loaded\n", stream);
-  else
-    m_Preface->Dump(stream);
 
   std::list<InterchangeObject*>::iterator i = m_PacketList->m_List.begin();
   for ( ; i != m_PacketList->m_List.end(); i++ )
index 05b04992b21a83f62661a36565f47dbb2e80ed3f..b43fc84dccc01081767d93e9ffeb7e726f11fd70 100755 (executable)
--- a/src/MXF.h
+++ b/src/MXF.h
@@ -301,7 +301,6 @@ namespace ASDCP
 
       //---------------------------------------------------------------------------------
       //
-      class h__PacketList; // See MXF.cpp
       class Identification;
       class SourcePackage;
 
index 4a1a58633b9f89d575bc410179279498806366b5..ba83858b40c3df2dd786680f49b76c0759448d67 100755 (executable)
@@ -123,10 +123,10 @@ void
 usage(FILE* stream = stdout)
 {
   fprintf(stream, "\
-USAGE: %s -c <output-file> [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
+USAGE: %s -c <output-file> [-3] [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
        [-f <start-frame>] [-j <key-id-string>] [-k <key-string>] [-L] [-M]\n\
        [-p <frame-rate>] [-R] [-s <num>] [-v] [-W]\n\
-       <input-file> [<input-file2> ...]\n\
+       <input-file> [<input-file-2> ...]\n\
 \n\
        %s [-h|-help] [-V]\n\
 \n\
@@ -145,7 +145,10 @@ USAGE: %s -c <output-file> [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
 
   fprintf(stream, "\
 Major modes:\n\
-  -c <output-file>  - Create AS-DCP track file from input(s)\n\
+  -3                - Create a stereoscopic image file. Expects two dir-\n\
+                      ectories of JP2K codestreams (directories must have\n\
+                      an equal number of frames; left eye is first).\n\
+  -c <output-file>  - Create an AS-DCP track file from input(s)\n\
   -g                - Generate a random 16 byte value to stdout\n\
   -G                - Perform GOP start lookup test on MXF+Interop MPEG file\n\
   -h | -help        - Show help\n\
@@ -238,6 +241,7 @@ public:
   bool   no_write_flag;  // true if no output files are to be written
   bool   version_flag;   // true if the version display option was selected
   bool   help_flag;      // true if the help display option was selected
+  bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
   ui32_t start_frame;    // frame number to begin processing
   ui32_t duration;       // number of frames to be processed
   bool   duration_flag;  // true if duration argument given
@@ -273,7 +277,7 @@ public:
     mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
     write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
     verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
-    no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
+    no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false), start_frame(0),
     duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
   {
@@ -297,6 +301,7 @@ public:
              {
              case '1': mono_wav = true; break;
              case '2': split_wav = true; break;
+             case '3': stereo_image_flag = true; break;
              case 'i': mode = MMT_INFO;        break;
              case 'G': mode = MMT_GOP_START; break;
              case 'W': no_write_flag = true; break;
@@ -687,6 +692,137 @@ gop_start_test(CommandOptions& Options)
 //------------------------------------------------------------------------------------------
 // JPEG 2000 essence
 
+// Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
+// Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
+//
+Result_t
+write_JP2K_S_file(CommandOptions& Options)
+{
+  AESEncContext*          Context = 0;
+  HMACContext*            HMAC = 0;
+  JP2K::MXFSWriter        Writer;
+  JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
+  JP2K::PictureDescriptor PDesc;
+  JP2K::SequenceParser    ParserLeft, ParserRight;
+  byte_t                  IV_buf[CBC_BLOCK_SIZE];
+  Kumu::FortunaRNG        RNG;
+
+  fprintf(stderr, "Hello, stereoscopic world!\n");
+
+  if ( Options.file_count != 2 )
+    {
+      fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
+      return RESULT_FAIL;
+    }
+
+  // set up essence parser
+  Result_t result = ParserLeft.OpenRead(Options.filenames[0]);
+
+  if ( ASDCP_SUCCESS(result) )
+    result = ParserRight.OpenRead(Options.filenames[1]);
+
+  // set up MXF writer
+  if ( ASDCP_SUCCESS(result) )
+    {
+      ParserLeft.FillPictureDescriptor(PDesc);
+      PDesc.EditRate = Options.PictureRate();
+
+      if ( Options.verbose_flag )
+       {
+         fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
+          fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
+         JP2K::PictureDescriptorDump(PDesc);
+       }
+    }
+
+  if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+    {
+      WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
+      Kumu::GenRandomUUID(Info.AssetUUID);
+
+      if ( Options.use_smpte_labels )
+       {
+         Info.LabelSetType = LS_MXF_SMPTE;
+         fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
+       }
+
+      // configure encryption
+      if( Options.key_flag )
+       {
+         Kumu::GenRandomUUID(Info.ContextID);
+         Info.EncryptedEssence = true;
+
+         if ( Options.key_id_flag )
+           memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+         else
+           RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
+
+         Context = new AESEncContext;
+         result = Context->InitKey(Options.key_value);
+
+         if ( ASDCP_SUCCESS(result) )
+           result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
+
+         if ( ASDCP_SUCCESS(result) && Options.write_hmac )
+           {
+             Info.UsesHMAC = true;
+             HMAC = new HMACContext;
+             result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
+           }
+       }
+
+      if ( ASDCP_SUCCESS(result) )
+       result = Writer.OpenWrite(Options.out_file, Info, PDesc);
+    }
+
+  if ( ASDCP_SUCCESS(result) )
+    {
+      ui32_t duration = 0;
+      result = ParserLeft.Reset();
+      if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
+
+      while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
+       {
+         result = ParserLeft.ReadFrame(FrameBuffer);
+
+         if ( ASDCP_SUCCESS(result) )
+           {
+             if ( Options.verbose_flag )
+               FrameBuffer.Dump(stderr, Options.fb_dump_size);
+                 
+             if ( Options.encrypt_header_flag )
+               FrameBuffer.PlaintextOffset(0);
+           }
+
+         if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+           result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
+
+         if ( ASDCP_SUCCESS(result) )
+           result = ParserRight.ReadFrame(FrameBuffer);
+
+         if ( ASDCP_SUCCESS(result) )
+           {
+             if ( Options.verbose_flag )
+               FrameBuffer.Dump(stderr, Options.fb_dump_size);
+                 
+             if ( Options.encrypt_header_flag )
+               FrameBuffer.PlaintextOffset(0);
+           }
+
+         if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+           result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
+       }
+
+      if ( result == RESULT_ENDOFFILE )
+       result = RESULT_OK;
+    }
+
+  if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+    result = Writer.Finalize();
+
+  return result;
+}
+
 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
 //
@@ -1422,6 +1558,11 @@ show_file_info(CommandOptions& Options)
       fputs("File essence type is JPEG 2000 pictures.\n", stdout);
       FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
     }
+  else if ( EssenceType == ESS_JPEG_2000_S )
+    {
+      fputs("File essence type is JPEG 2000 stereoscopic pictures.\n", stdout);
+      FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
+    }
 #ifdef ASDCP_WITH_TIMED_TEXT
   else if ( EssenceType == ESS_TIMED_TEXT )
     {
@@ -1612,7 +1753,12 @@ main(int argc, const char** argv)
              break;
 
            case ESS_JPEG_2000:
-             result = write_JP2K_file(Options);
+             if ( Options.stereo_image_flag )
+               result = write_JP2K_S_file(Options);
+
+             else
+               result = write_JP2K_file(Options);
+
              break;
 
            case ESS_PCM_24b_48k:
index bf9d9fb430721be2993ef1ad8d6d57465b216d09..26475dfc24020f4439a7f5347ab71e9a22eb3632 100755 (executable)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2005-2006, John Hurst
+Copyright (c) 2005-2007, John Hurst
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -65,7 +65,7 @@ banner(FILE* stream = stdout)
 {
   fprintf(stream, "\n\
 %s (asdcplib %s)\n\n\
-Copyright (c) 2005-2006 John Hurst\n\
+Copyright (c) 2005-2007 John Hurst\n\
 %s is part of the asdcplib DCP tools package.\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\
@@ -82,11 +82,10 @@ USAGE: %s [-r] [-v] <input-file> [<input-file2> ...]\n\
 \n\
        %s [-h|-help] [-V]\n\
 \n\
-  -h | -help  - Show help\n\
-  -r          - When KLV data is an OPAtom file, additionally\n\
-                display OPAtom headers\n\
-  -v          - Verbose. Prints informative messages to stderr\n\
-  -V          - Show version information\n\
+  -h | -help   - Show help\n\
+  -r           - When KLV data is an MXF OPAtom file, display OPAtom headers\n\
+  -v           - Verbose. Prints informative messages to stderr\n\
+  -V           - Show version information\n\
 \n\
   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
          o All option arguments must be separated from the option by whitespace.\n\
@@ -123,7 +122,6 @@ USAGE: %s [-r] [-v] <input-file> [<input-file2> ...]\n\
           {
             switch ( argv[i][1] )
               {
-
               case 'h': help_flag = true; break;
               case 'r': read_mxf_flag = true; break;
               case 'V': version_flag = true; break;
@@ -202,9 +200,10 @@ main(int argc, const char** argv)
          if ( ASDCP_SUCCESS(result) )
            result = Header.InitFromFile(Reader);
          
-         Header.Dump(stdout);
+         if ( ASDCP_SUCCESS(result) )
+           Header.Dump(stdout);
          
-         if ( ASDCP_SUCCESS(result) && Header.m_RIP.PairArray.size() > 3 )
+         if ( ASDCP_SUCCESS(result) && Header.m_RIP.PairArray.size() > 2 )
            {
              MXF::Array<MXF::RIP::Pair>::const_iterator pi = Header.m_RIP.PairArray.begin();
 
@@ -237,6 +236,9 @@ main(int argc, const char** argv)
              if ( ASDCP_SUCCESS(result) )
                Index.Dump(stdout);
            }
+
+         if ( ASDCP_SUCCESS(result) )
+           Header.m_RIP.Dump(stdout);
        }
       else // dump klv
        {