channel assignment
[asdcplib.git] / src / AS_DCP.h
index 5dafdaa5fbef63e0623340893b817731c9d22909..70367ce7eb0e7cd75275a87c2ad3b97a0eea5ec8 100755 (executable)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2003-2006, John Hurst
+Copyright (c) 2003-2009, John Hurst
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -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-200X 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)
@@ -60,6 +65,11 @@ The following use cases are supported by the library:
  o Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
  o Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
  o Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
+ o Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
+ o Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
+ o Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
+ o Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
+ o Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
  o Write one or more plaintext PCM audio streams to a plaintext ASDCP file
  o Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
  o Read one or more plaintext PCM audio streams from a plaintext ASDCP file
@@ -67,8 +77,9 @@ The following use cases are supported by the library:
  o Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
  o Read header metadata from an ASDCP file
 
-This project depends upon the following library:
+This project depends upon the following libraries:
  - OpenSSL http://www.openssl.org/
+ - Expat http://expat.sourceforge.net/ (optional)
 
 */
 
@@ -78,8 +89,11 @@ This project depends upon the following library:
 #include <KM_error.h>
 #include <stdio.h>
 #include <stdarg.h>
-#include <iostream>
 #include <math.h>
+#include <iosfwd>
+#include <string>
+#include <cstring>
+#include <list>
 
 //--------------------------------------------------------------------------------
 // common integer types
@@ -133,17 +147,8 @@ typedef unsigned int   ui32_t;
 // All library components are defined in the namespace ASDCP
 //
 namespace ASDCP {
-  // The version number consists of three segments: major, API minor, and
-  // implementation minor. Whenever a change is made to AS_DCP.h, the API minor
-  // version will increment. Changes made to the internal implementation will
-  // result in the incrementing of the implementation minor version.
-
-  // 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.
-  const ui32_t VERSION_MAJOR = 1;
-  const ui32_t VERSION_APIMINOR = 1;
-  const ui32_t VERSION_IMPMINOR = 9;
+  //
+  // The version number declaration and explanation have moved to ../configure.ac
   const char* Version();
 
   // UUIDs are passed around as strings of UUIDlen bytes
@@ -189,6 +194,8 @@ 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.");
+  const Kumu::Result_t RESULT_SFORMAT    (-115, "Rate mismatch, file may contain stereoscopic essence.");
 
   //---------------------------------------------------------------------------------
   // file identification
@@ -201,6 +208,8 @@ namespace ASDCP {
     ESS_JPEG_2000,   // the file contains one or more JPEG 2000 codestreams
     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
@@ -242,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.
@@ -329,6 +339,8 @@ namespace ASDCP {
     }
   };
 
+  // Print WriterInfo to std::ostream
+  std::ostream& operator << (std::ostream& strm, const WriterInfo& winfo);
   // Print WriterInfo to stream, stderr by default.
   void WriterInfoDump(const WriterInfo&, FILE* = 0);
 
@@ -407,7 +419,7 @@ namespace ASDCP {
       // Initializes HMAC context. The key argument must point to a binary
       // key that is CBC_KEY_SIZE bytes in length. Returns error if the key
       // argument is NULL.
-      Result_t InitKey(const byte_t* key, LabelSet_t = LS_MXF_INTEROP);
+      Result_t InitKey(const byte_t* key, LabelSet_t);
 
       // Reset internal state, allows repeated cycles of Update -> Finalize
       void Reset();
@@ -559,6 +571,8 @@ namespace ASDCP {
          ui32_t   ContainerDuration;       // 
       };
 
+      // Print VideoDescriptor to std::ostream
+      std::ostream& operator << (std::ostream& strm, const VideoDescriptor& vdesc);
       // Print VideoDescriptor to stream, stderr by default.
       void VideoDescriptorDump(const VideoDescriptor&, FILE* = 0);
 
@@ -735,9 +749,22 @@ namespace ASDCP {
        };
     } // namespace MPEG2
 
+  //---------------------------------------------------------------------------------
   //
   namespace PCM
     {
+      // The channel format will normally be CF_NONE. Unless you have read and understand
+      // SMPTE 429-2-2009 Annex A you should leave it as-is. If you want to label your channel
+      // format and it is one of the fomats given in 429-2, select the appropriate value
+      // from this enum and use it in the ChannelFormat element of the AudioDescriptor struct.
+      //
+      enum ChannelFormat_t {
+       CF_NONE,
+       CF_CFG_1, // 5.1 with optional HI/VI
+       CF_CFG_2, // 6.1 (5.1 + center surround) with optional HI/VI
+       CF_CFG_3, // 7.1 with optional HI/VI
+      };
+
       struct AudioDescriptor
        {
          Rational SampleRate;         // rate of frame wrapping
@@ -749,8 +776,11 @@ namespace ASDCP {
          ui32_t   AvgBps;             // 
          ui32_t   LinkedTrackID;      // 
          ui32_t   ContainerDuration;  // number of frames
+         ChannelFormat_t ChannelFormat; // audio channel arrangement
       };
 
+      // Print AudioDescriptor to std::ostream
+      std::ostream& operator << (std::ostream& strm, const AudioDescriptor& adesc);
       // Print debugging information to stream (stderr default)
       void   AudioDescriptorDump(const AudioDescriptor&, FILE* = 0);
 
@@ -885,18 +915,51 @@ namespace ASDCP {
        };
     } // namespace PCM
 
+  //---------------------------------------------------------------------------------
   //
   namespace JP2K
     {
       const ui32_t MaxComponents = 3;
-      const ui32_t DefaultCodingDataLength = 64;
+      const ui32_t MaxPrecincts = 32; // ISO 15444-1 Annex A.6.1
+      const ui32_t MaxDefaults = 256; // made up
+
+#pragma pack(1)
+      struct ImageComponent_t  // ISO 15444-1 Annex A.5.1
+      {
+       ui8_t Ssize;
+       ui8_t XRsize;
+       ui8_t YRsize;
+      };
+
+      struct CodingStyleDefault_t // ISO 15444-1 Annex A.6.1
+      {
+       ui8_t   Scod;
+
+       struct
+       {
+         ui8_t  ProgressionOrder;
+         ui8_t  NumberOfLayers[sizeof(ui16_t)];
+         ui8_t  MultiCompTransform;
+       } SGcod;
+       
+       struct
+       {
+         ui8_t  DecompositionLevels;
+         ui8_t  CodeblockWidth;
+         ui8_t  CodeblockHeight;
+         ui8_t  CodeblockStyle;
+         ui8_t  Transformation;
+         ui8_t  PrecinctSize[MaxPrecincts];
+       } SPcod;
+      };
 
-      struct ImageComponent
+      struct QuantizationDefault_t // ISO 15444-1 Annex A.6.4
       {
-       byte_t Ssize;
-       byte_t XRsize;
-       byte_t YRsize;
+       ui8_t  Sqcd;
+       ui8_t  SPqcd[MaxDefaults];
+       ui8_t  SPqcdLength;
       };
+#pragma pack()
 
       struct PictureDescriptor
       {
@@ -916,13 +979,13 @@ namespace ASDCP {
        ui32_t         XTOsize;
        ui32_t         YTOsize;
        ui16_t         Csize;
-       ImageComponent ImageComponents[MaxComponents];
-       byte_t         CodingStyle[DefaultCodingDataLength];
-       ui32_t         CodingStyleLength;
-       byte_t         QuantDefault[DefaultCodingDataLength];
-       ui32_t         QuantDefaultLength;
+       ImageComponent_t      ImageComponents[MaxComponents];
+       CodingStyleDefault_t  CodingStyleDefault;
+       QuantizationDefault_t QuantizationDefault;
       };
 
+      // Print debugging information to std::ostream
+      std::ostream& operator << (std::ostream& strm, const PictureDescriptor& pdesc);
       // Print debugging information to stream (stderr default)
       void   PictureDescriptorDump(const PictureDescriptor&, FILE* = 0);
 
@@ -964,6 +1027,10 @@ namespace ASDCP {
          Result_t FillPictureDescriptor(PictureDescriptor&) const;
        };
 
+      // Parses the data in the frame buffer to fill in the picture descriptor. Copies
+      // the offset of the image data into start_of_data. Returns error if the parser fails.
+      Result_t ParseMetadataIntoDesc(const FrameBuffer&, PictureDescriptor&, byte_t* start_of_data = 0);
+
       // An object which reads a sequence of files containing JPEG 2000 pictures.
       class SequenceParser
        {
@@ -1068,7 +1135,305 @@ namespace ASDCP {
          void     DumpHeaderMetadata(FILE* = 0) const;
          void     DumpIndex(FILE* = 0) const;
        };
+
+
+      // Stereoscopic Image support
+      //
+
+      enum StereoscopicPhase_t
+      {
+       SP_LEFT,
+       SP_RIGHT
+      };
+      
+      struct SFrameBuffer
+      {
+       JP2K::FrameBuffer Left;
+       JP2K::FrameBuffer Right;
+
+       SFrameBuffer(ui32_t size) {
+         Left.Capacity(size);
+         Right.Capacity(size);
+       }
+      };
+
+      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 pair of frames 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.
+         Result_t WriteFrame(const SFrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
+
+         // 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 pair of frames 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, SFrameBuffer&, AESDecContext* = 0, HMACContext* = 0) 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 TimedText
+    {
+      enum MIMEType_t { MT_BIN, MT_PNG, MT_OPENTYPE };
+
+      struct TimedTextResourceDescriptor
+      {
+       byte_t      ResourceID[UUIDlen];
+       MIMEType_t  Type;
+
+        TimedTextResourceDescriptor() : Type(MT_BIN) {}
+      };
+
+      typedef std::list<TimedTextResourceDescriptor> ResourceList_t;
+
+      struct TimedTextDescriptor
+      {
+       Rational       EditRate;                // 
+       ui32_t         ContainerDuration;
+       byte_t         AssetID[UUIDlen];
+       std::string    NamespaceName;
+       std::string    EncodingName;
+       ResourceList_t ResourceList;
+
+      TimedTextDescriptor() : ContainerDuration(0), EncodingName("UTF-8") {} // D-Cinema format is always UTF-8
+      };
+
+      // Print debugging information to std::ostream
+      std::ostream& operator << (std::ostream& strm, const TimedTextDescriptor& tinfo);
+      // Print debugging information to stream (stderr default)
+      void   DescriptorDump(const TimedTextDescriptor&, FILE* = 0);
+
+      //
+      class FrameBuffer : public ASDCP::FrameBuffer
+      {
+       ASDCP_NO_COPY_CONSTRUCT(FrameBuffer); // TODO: should have copy construct
+
+      protected:
+       byte_t      m_AssetID[UUIDlen];
+       std::string m_MIMEType;
+
+      public:
+       FrameBuffer() { memset(m_AssetID, 0, UUIDlen); }
+       FrameBuffer(ui32_t size) { Capacity(size); memset(m_AssetID, 0, UUIDlen); }
+       virtual ~FrameBuffer() {}
+        
+       inline const byte_t* AssetID() const { return m_AssetID; }
+       inline void          AssetID(const byte_t* buf) { memcpy(m_AssetID, buf, UUIDlen); }
+       inline const char*   MIMEType() const { return m_MIMEType.c_str(); }
+       inline void          MIMEType(const std::string& s) { m_MIMEType = s; }
+
+       // Print debugging information to stream (stderr default)
+       void Dump(FILE* = 0, ui32_t dump_bytes = 0) const;
+      };
+
+      //
+      class IResourceResolver
+      {
+      public:
+       virtual ~IResourceResolver() {}
+       virtual Result_t ResolveRID(const byte_t* uuid, FrameBuffer&) const = 0; // return data for RID
+      };
+
+      //
+      class DCSubtitleParser
+       {
+         class h__SubtitleParser;
+         mem_ptr<h__SubtitleParser> m_Parser;
+         ASDCP_NO_COPY_CONSTRUCT(DCSubtitleParser);
+
+       public:
+         DCSubtitleParser();
+         virtual ~DCSubtitleParser();
+
+         // Opens the XML file for reading, parse data to provide a complete
+         // set of stream metadata for the MXFWriter below.
+         Result_t OpenRead(const char* filename) const;
+
+         // Fill a TimedTextDescriptor struct with the values from the file's contents.
+         // Returns RESULT_INIT if the file is not open.
+         Result_t FillDescriptor(TimedTextDescriptor&) const;
+
+         // Reads the complete Timed Text Resource into the given string.
+         Result_t ReadTimedTextResource(std::string&) const;
+
+         // Reads the Ancillary Resource having the given ID. Fails if the buffer
+         // is too small or the resource does not exist. The optional Resolver
+         // argument can be provided which will be used to retrieve the resource
+         // having a particulat UUID. If a Resolver is not supplied, the default
+         // internal resolver will return the contents of the file having the UUID
+         // as the filename. The filename must exist in the same directory as the
+         // XML file opened with OpenRead().
+         Result_t ReadAncillaryResource(const byte_t* uuid, FrameBuffer&,
+                                        const IResourceResolver* Resolver = 0) const;
+       };
+
+      //
+      class MXFWriter
+       {
+         class h__Writer;
+         mem_ptr<h__Writer> m_Writer;
+         ASDCP_NO_COPY_CONSTRUCT(MXFWriter);
+
+       public:
+         MXFWriter();
+         virtual ~MXFWriter();
+
+         // 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 TimedTextDescriptor&, ui32_t HeaderSize = 16384);
+
+         // Writes the Timed-Text Resource to the MXF file. The file must be UTF-8
+         // encoded. 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.
+         // This method may only be called once, and it must be called before any
+         // call to WriteAncillaryResource(). RESULT_STATE will be returned if these
+         // conditions are not met.
+         Result_t WriteTimedTextResource(const std::string& XMLDoc, AESEncContext* = 0, HMACContext* = 0);
+
+         // Writes an Ancillary Resource 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. RESULT_STATE will be returned if the method is called before
+         // WriteTimedTextResource()
+         Result_t WriteAncillaryResource(const FrameBuffer&, AESEncContext* = 0, HMACContext* = 0);
+
+         // Closes the MXF file, writing the index and revised header.
+         Result_t Finalize();
+       };
+
+      //
+      class MXFReader
+       {
+         class h__Reader;
+         mem_ptr<h__Reader> m_Reader;
+         ASDCP_NO_COPY_CONSTRUCT(MXFReader);
+
+       public:
+         MXFReader();
+         virtual ~MXFReader();
+
+         // 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 a TimedTextDescriptor struct with the values from the file's header.
+         // Returns RESULT_INIT if the file is not open.
+         Result_t FillDescriptor(TimedTextDescriptor&) 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 the complete Timed Text Resource into the given string. Fails if the resource
+         // is encrypted and AESDecContext is NULL (use the following method to retrieve the
+         // raw ciphertet block).
+         Result_t ReadTimedTextResource(std::string&, AESDecContext* = 0, HMACContext* = 0) const;
+
+         // Reads the complete Timed Text Resource from the MXF file. If the optional AESEncContext
+         // argument is present, the resource 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 ReadTimedTextResource(FrameBuffer&, AESDecContext* = 0, HMACContext* = 0) const;
+
+         // Reads the timed-text resource having the given UUID from the MXF file. If the
+         // optional AESEncContext argument is present, the resource 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 ReadAncillaryResource(const byte_t* uuid, FrameBuffer&, AESDecContext* = 0, HMACContext* = 0) const;
+
+         // Print debugging information to stream
+         void     DumpHeaderMetadata(FILE* = 0) const;
+         void     DumpIndex(FILE* = 0) const;
+       };
+    } // namespace TimedText
+
+
 } // namespace ASDCP