diff options
| author | Carl Hetherington <cth@carlh.net> | 2016-01-04 14:48:18 +0000 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2016-01-04 14:48:18 +0000 |
| commit | 086b83c4132189120b7f1685cb39efba56d71c2c (patch) | |
| tree | d42475c7e133c4ad078eb8da3acd0ee2b6206942 /asdcplib/src/asdcp-wrap.cpp | |
| parent | 5c92b6dbf3bc7d905938ad9972aec5f0ee106935 (diff) | |
Copy asdcplib 2.5.11 into the tree.
Diffstat (limited to 'asdcplib/src/asdcp-wrap.cpp')
| -rwxr-xr-x | asdcplib/src/asdcp-wrap.cpp | 756 |
1 files changed, 670 insertions, 86 deletions
diff --git a/asdcplib/src/asdcp-wrap.cpp b/asdcplib/src/asdcp-wrap.cpp index e1182aeb..8e683e18 100755 --- a/asdcplib/src/asdcp-wrap.cpp +++ b/asdcplib/src/asdcp-wrap.cpp @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2012, John Hurst +Copyright (c) 2003-2015, John Hurst All rights reserved. Redistribution and use in source and binary forms, with or without @@ -25,10 +25,10 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*! \file asdcp-wrap.cpp - \version $Id: asdcp-wrap.cpp,v 1.5 2012/03/07 18:47:02 mikey Exp $ + \version $Id: asdcp-wrap.cpp,v 1.23 2015/10/14 16:48:22 jhurst Exp $ \brief AS-DCP file manipulation utility - This program wraps d-cinema essence (picture, sound or text) in t an AS-DCP + This program wraps d-cinema essence (picture, sound or text) into an AS-DCP MXF file. For more information about asdcplib, please refer to the header file AS_DCP.h @@ -48,6 +48,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <KM_fileio.h> #include <KM_prng.h> +#include <AtmosSyncChannel_Mixer.h> #include <AS_DCP.h> #include <PCMParserList.h> #include <Metadata.h> @@ -56,6 +57,13 @@ using namespace ASDCP; const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte; +const byte_t P_HFR_UL_2K[16] = { + 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, + 0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x03 +}; + +const ASDCP::Dictionary *g_dict = 0; + //------------------------------------------------------------------------------------------ // // command line option parser class @@ -71,7 +79,7 @@ public: static byte_t default_ProductUUID_Data[UUIDlen] = { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22, 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 }; - + memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen); CompanyName = "WidgetCo"; ProductName = "asdcp-wrap"; @@ -91,12 +99,21 @@ public: } // +static void +create_random_uuid(byte_t* uuidbuf) +{ + Kumu::UUID tmp_id; + GenRandomValue(tmp_id); + memcpy(uuidbuf, tmp_id.Value(), tmp_id.Size()); +} + +// void banner(FILE* stream = stdout) { fprintf(stream, "\n\ %s (asdcplib %s)\n\n\ -Copyright (c) 2003-2012 John Hurst\n\n\ +Copyright (c) 2003-2015 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", @@ -112,16 +129,17 @@ USAGE: %s [-h|-help] [-V]\n\ \n\ %s [-3] [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\ [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\ - [-l <label>] [-L] [-M] [-p <frame-rate>] [-s <num>] [-v] [-W]\n\ - [-z|-Z] <input-file>+ <output-file>\n\n", + [-l <label>] [-L] [-M] [-m <expr>] [-p <frame-rate>] [-s] [-v]\n\ + [-W] [-z|-Z] <input-file>+ <output-file>\n\n", PROGRAM_NAME, PROGRAM_NAME); fprintf(stream, "\ Options:\n\ -3 - Create a stereoscopic image file. Expects two\n\ directories of JP2K codestreams (directories must have\n\ - an equal number of frames; left eye is first).\n\ - -C <UL> - Set ChannelAssignment UL value\n\ + an equal number of frames; the left eye is first)\n\ + -A <UL> - Set DataEssenceCoding UL value in an Aux Data file\n\ + -C <UL> - Set ChannelAssignment UL value in a PCM file\n\ -h | -help - Show help\n\ -V - Show version information\n\ -e - Encrypt MPEG or JP2K headers (default)\n\ @@ -129,8 +147,12 @@ Options:\n\ -j <key-id-str> - Write key ID instead of creating a random value\n\ -k <key-string> - Use key for ciphertext operations\n\ -M - Do not create HMAC values when writing\n\ - -a <UUID> - Specify the Asset ID of a file (with -c)\n\ - -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\ + -m <expr> - Write MCA labels using <expr>. Example:\n\ + 51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\ + Note: The symbol '-' may be used for an unlabeled\n\ + channel, but not within a soundfield.\n\ + -a <UUID> - Specify the Asset ID of the file\n\ + -b <buffer-size> - Specify size in bytes of picture frame buffer\n\ Defaults to 4,194,304 (4MB)\n\ -d <duration> - Number of frames to process, default all\n\ -f <start-frame> - Starting frame number, default 0\n\ @@ -139,10 +161,17 @@ Options:\n\ '7.1DS', 'WTF'\n\ Default is no label (valid for Interop only).\n\ -L - Write SMPTE UL values instead of MXF Interop\n\ + -P <UL> - Set PictureEssenceCoding UL value in a JP2K file\n\ -p <rate> - fps of picture when wrapping PCM or JP2K:\n\ Use one of [23|24|25|30|48|50|60], 24 is default\n\ + -s - Insert a Dolby Atmos synchronization channel when\n\ + wrapping PCM. This implies a -L option(SMPTE ULs) and \n\ + will overide -C and -l options with Configuration 4 \n\ + Channel Assigment and no format label respectively. \n\ -v - Verbose, prints informative messages to stderr\n\ - -W - Read input file only, do not write source file\n\ + -w - When writing 377-4 MCA labels, use the WTF Channel\n\ + assignment label instead of the standard MCA label\n\ + -W - Read input file only, do not write output file\n\ -z - Fail if j2c inputs have unequal parameters (default)\n\ -Z - Ignore unequal parameters in j2c inputs\n\ \n\ @@ -162,7 +191,7 @@ decode_channel_fmt(const std::string& label_name) else if ( label_name == "6.1" ) return PCM::CF_CFG_2; - + else if ( label_name == "7.1" ) return PCM::CF_CFG_3; @@ -189,18 +218,13 @@ public: bool asset_id_flag; // true if an asset ID was given bool encrypt_header_flag; // true if mpeg headers are to be encrypted bool write_hmac; // true if HMAC values are to be generated and written - /// bool read_hmac; // true if HMAC values are to be validated - /// bool split_wav; // true if PCM is to be extracted to stereo WAV files - /// bool mono_wav; // true if PCM is to be extracted to mono WAV files bool verbose_flag; // true if the verbose option was selected ui32_t fb_dump_size; // number of bytes of frame buffer to dump - /// bool showindex_flag; // true if index is to be displayed - /// bool showheader_flag; // true if MXF file header is to be displayed 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 number_width; // number of digits in a serialized filename (for JPEG extract) + bool write_partial_pcm_flag; // if true, write the last frame of PCM input even when it is incomplete ui32_t start_frame; // frame number to begin processing ui32_t duration; // number of frames to be processed bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values @@ -213,13 +237,25 @@ public: byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true) PCM::ChannelFormat_t channel_fmt; // audio channel arrangement 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 to work. Kumu::PathList_t filenames; // list of filenames to be processed UL channel_assignment; + UL picture_coding; + UL aux_data_coding; + bool dolby_atmos_sync_flag; // if true, insert a Dolby Atmos Synchronization channel. + ui32_t ffoa; // first frame of action for atmos wrapping + ui32_t max_channel_count; // max channel count for atmos wrapping + ui32_t max_object_count; // max object count for atmos wrapping + bool use_interop_sound_wtf; // make true to force WTF assignment label instead of MCA + ASDCP::MXF::ASDCP_MCAConfigParser mca_config; // Rational PictureRate() { + if ( picture_rate == 16 ) return EditRate_16; + if ( picture_rate == 18 ) return EditRate_18; + if ( picture_rate == 20 ) return EditRate_20; + if ( picture_rate == 22 ) return EditRate_22; if ( picture_rate == 23 ) return EditRate_23_98; if ( picture_rate == 24 ) return EditRate_24; if ( picture_rate == 25 ) return EditRate_25; @@ -236,6 +272,10 @@ public: // const char* szPictureRate() { + if ( picture_rate == 16 ) return "16"; + if ( picture_rate == 18 ) return "18.182"; + if ( picture_rate == 20 ) return "20"; + if ( picture_rate == 22 ) return "21.818"; if ( picture_rate == 23 ) return "23.976"; if ( picture_rate == 24 ) return "24"; if ( picture_rate == 25 ) return "25"; @@ -255,11 +295,15 @@ public: encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0), no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false), - start_frame(0), + write_partial_pcm_flag(false), start_frame(0), duration(0xffffffff), use_smpte_labels(false), j2c_pedantic(true), fb_size(FRAME_BUFFER_SIZE), channel_fmt(PCM::CF_NONE), - show_ul_values(false) + ffoa(0), max_channel_count(10), max_object_count(118), // hard-coded sample atmos properties + dolby_atmos_sync_flag(false), + show_ul_values_flag(false), + mca_config(g_dict), + use_interop_sound_wtf(false) { memset(key_value, 0, KeyLen); memset(key_id_value, 0, UUIDlen); @@ -272,7 +316,7 @@ public: help_flag = true; continue; } - + if ( argv[i][0] == '-' && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) ) && argv[i][2] == 0 ) @@ -281,6 +325,15 @@ public: { case '3': stereo_image_flag = true; break; + case 'A': + TEST_EXTRA_ARG(i, 'A'); + if ( ! aux_data_coding.DecodeHex(argv[i]) ) + { + fprintf(stderr, "Error decoding UL value: %s\n", argv[i]); + return; + } + break; + case 'a': asset_id_flag = true; TEST_EXTRA_ARG(i, 'a'); @@ -298,7 +351,7 @@ public: case 'b': TEST_EXTRA_ARG(i, 'b'); - fb_size = abs(atoi(argv[i])); + fb_size = Kumu::xabs(strtol(argv[i], 0, 10)); if ( verbose_flag ) fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size); @@ -306,7 +359,7 @@ public: break; case 'C': - TEST_EXTRA_ARG(i, 'U'); + TEST_EXTRA_ARG(i, 'C'); if ( ! channel_assignment.DecodeHex(argv[i]) ) { fprintf(stderr, "Error decoding UL value: %s\n", argv[i]); @@ -316,7 +369,7 @@ public: case 'd': TEST_EXTRA_ARG(i, 'd'); - duration = abs(atoi(argv[i])); + duration = Kumu::xabs(strtol(argv[i], 0, 10)); break; case 'E': encrypt_header_flag = false; break; @@ -324,9 +377,10 @@ public: case 'f': TEST_EXTRA_ARG(i, 'f'); - start_frame = abs(atoi(argv[i])); + start_frame = Kumu::xabs(strtol(argv[i], 0, 10)); break; + case 'g': write_partial_pcm_flag = true; break; case 'h': help_flag = true; break; case 'j': key_id_flag = true; @@ -365,13 +419,33 @@ public: case 'L': use_smpte_labels = true; break; case 'M': write_hmac = false; break; + case 'm': + TEST_EXTRA_ARG(i, 'm'); + if ( ! mca_config.DecodeString(argv[i]) ) + { + return; + } + break; + + case 'P': + TEST_EXTRA_ARG(i, 'P'); + if ( ! picture_coding.DecodeHex(argv[i]) ) + { + fprintf(stderr, "Error decoding UL value: %s\n", argv[i]); + return; + } + break; + case 'p': TEST_EXTRA_ARG(i, 'p'); - picture_rate = abs(atoi(argv[i])); + picture_rate = Kumu::xabs(strtol(argv[i], 0, 10)); break; + case 's': dolby_atmos_sync_flag = true; break; + case 'u': show_ul_values_flag = true; break; case 'V': version_flag = true; break; case 'v': verbose_flag = true; break; + case 'w': use_interop_sound_wtf = true; break; case 'W': no_write_flag = true; break; case 'Z': j2c_pedantic = false; break; case 'z': j2c_pedantic = true; break; @@ -398,7 +472,7 @@ public: if ( help_flag || version_flag ) return; - + if ( filenames.size() < 2 ) { fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr); @@ -430,7 +504,7 @@ write_MPEG2_file(CommandOptions& Options) Kumu::FortunaRNG RNG; // set up essence parser - Result_t result = Parser.OpenRead(Options.filenames.front().c_str()); + Result_t result = Parser.OpenRead(Options.filenames.front()); // set up MXF writer if ( ASDCP_SUCCESS(result) ) @@ -467,9 +541,13 @@ write_MPEG2_file(CommandOptions& Options) Info.EncryptedEssence = true; if ( Options.key_id_flag ) - memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + { + memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + } else - RNG.FillRandom(Info.CryptographicKeyID, UUIDlen); + { + create_random_uuid(Info.CryptographicKeyID); + } Context = new AESEncContext; result = Context->InitKey(Options.key_value); @@ -486,7 +564,7 @@ write_MPEG2_file(CommandOptions& Options) } if ( ASDCP_SUCCESS(result) ) - result = Writer.OpenWrite(Options.out_file.c_str(), Info, VDesc); + result = Writer.OpenWrite(Options.out_file, Info, VDesc); } if ( ASDCP_SUCCESS(result) ) @@ -497,19 +575,16 @@ write_MPEG2_file(CommandOptions& Options) while ( ASDCP_SUCCESS(result) && duration++ < Options.duration ) { - if ( duration == 1 ) - { result = Parser.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 ) { @@ -536,6 +611,35 @@ write_MPEG2_file(CommandOptions& Options) //------------------------------------------------------------------------------------------ + +// return false if an error is discovered +bool +check_phfr_params(CommandOptions& Options, JP2K::PictureDescriptor& PDesc) +{ + Rational rate = Options.PictureRate(); + if ( rate != EditRate_96 && rate != EditRate_100 && rate != EditRate_120 ) + return true; + + if ( PDesc.StoredWidth > 2048 ) + { + fprintf(stderr, "P-HFR files currently limited to 2K.\n"); + return false; + } + + if ( ! Options.use_smpte_labels ) + { + fprintf(stderr, "P-HFR files must be written using SMPTE labels. Use option '-L'.\n"); + return false; + } + + // do not set the label if the user has already done so + if ( ! Options.picture_coding.HasValue() ) + Options.picture_coding = UL(P_HFR_UL_2K); + + return true; +} + +//------------------------------------------------------------------------------------------ // JPEG 2000 essence // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file @@ -560,12 +664,12 @@ write_JP2K_S_file(CommandOptions& Options) } // set up essence parser - Result_t result = ParserLeft.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic); + Result_t result = ParserLeft.OpenRead(Options.filenames.front(), Options.j2c_pedantic); if ( ASDCP_SUCCESS(result) ) { Options.filenames.pop_front(); - result = ParserRight.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic); + result = ParserRight.OpenRead(Options.filenames.front(), Options.j2c_pedantic); } // set up MXF writer @@ -582,6 +686,9 @@ write_JP2K_S_file(CommandOptions& Options) } } + if ( ! check_phfr_params(Options, PDesc) ) + return RESULT_FAIL; + if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) { WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here @@ -603,9 +710,13 @@ write_JP2K_S_file(CommandOptions& Options) Info.EncryptedEssence = true; if ( Options.key_id_flag ) - memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + { + memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + } else - RNG.FillRandom(Info.CryptographicKeyID, UUIDlen); + { + create_random_uuid(Info.CryptographicKeyID); + } Context = new AESEncContext; result = Context->InitKey(Options.key_value); @@ -622,7 +733,15 @@ write_JP2K_S_file(CommandOptions& Options) } if ( ASDCP_SUCCESS(result) ) - result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc); + result = Writer.OpenWrite(Options.out_file, Info, PDesc); + + if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() ) + { + MXF::RGBAEssenceDescriptor *descriptor = 0; + Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_RGBAEssenceDescriptor), + reinterpret_cast<MXF::InterchangeObject**>(&descriptor)); + descriptor->PictureEssenceCoding = Options.picture_coding; + } } if ( ASDCP_SUCCESS(result) ) @@ -639,7 +758,7 @@ write_JP2K_S_file(CommandOptions& Options) { if ( Options.verbose_flag ) FrameBuffer.Dump(stderr, Options.fb_dump_size); - + if ( Options.encrypt_header_flag ) FrameBuffer.PlaintextOffset(0); } @@ -654,7 +773,7 @@ write_JP2K_S_file(CommandOptions& Options) { if ( Options.verbose_flag ) FrameBuffer.Dump(stderr, Options.fb_dump_size); - + if ( Options.encrypt_header_flag ) FrameBuffer.PlaintextOffset(0); } @@ -689,7 +808,7 @@ write_JP2K_file(CommandOptions& Options) Kumu::FortunaRNG RNG; // set up essence parser - Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic); + Result_t result = Parser.OpenRead(Options.filenames.front(), Options.j2c_pedantic); // set up MXF writer if ( ASDCP_SUCCESS(result) ) @@ -706,6 +825,9 @@ write_JP2K_file(CommandOptions& Options) } } + if ( ! check_phfr_params(Options, PDesc) ) + return RESULT_FAIL; + if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) { WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here @@ -727,9 +849,13 @@ write_JP2K_file(CommandOptions& Options) Info.EncryptedEssence = true; if ( Options.key_id_flag ) - memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + { + memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + } else - RNG.FillRandom(Info.CryptographicKeyID, UUIDlen); + { + create_random_uuid(Info.CryptographicKeyID); + } Context = new AESEncContext; result = Context->InitKey(Options.key_value); @@ -746,7 +872,15 @@ write_JP2K_file(CommandOptions& Options) } if ( ASDCP_SUCCESS(result) ) - result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc); + result = Writer.OpenWrite(Options.out_file, Info, PDesc); + + if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() ) + { + MXF::RGBAEssenceDescriptor *descriptor = 0; + Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_RGBAEssenceDescriptor), + reinterpret_cast<MXF::InterchangeObject**>(&descriptor)); + descriptor->PictureEssenceCoding = Options.picture_coding; + } } if ( ASDCP_SUCCESS(result) ) @@ -756,19 +890,16 @@ write_JP2K_file(CommandOptions& Options) while ( ASDCP_SUCCESS(result) && duration++ < Options.duration ) { - if ( duration == 1 ) - { result = Parser.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 ) { @@ -825,9 +956,9 @@ write_PCM_file(CommandOptions& Options) FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc)); ADesc.ChannelFormat = Options.channel_fmt; - if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE) + if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE && Options.mca_config.empty() ) { - fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n"); + fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see options -C, -l and -m)\n"); } if ( Options.verbose_flag ) @@ -862,9 +993,13 @@ write_PCM_file(CommandOptions& Options) Info.EncryptedEssence = true; if ( Options.key_id_flag ) - memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + { + memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + } else - RNG.FillRandom(Info.CryptographicKeyID, UUIDlen); + { + create_random_uuid(Info.CryptographicKeyID); + } Context = new AESEncContext; result = Context->InitKey(Options.key_value); @@ -881,14 +1016,60 @@ write_PCM_file(CommandOptions& Options) } if ( ASDCP_SUCCESS(result) ) - result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc); + result = Writer.OpenWrite(Options.out_file, Info, ADesc); - if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() ) + if ( ASDCP_SUCCESS(result) + && ( Options.channel_assignment.HasValue() + || ! Options.mca_config.empty() ) ) { - MXF::WaveAudioDescriptor *descriptor = 0; - Writer.OPAtomHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor), - reinterpret_cast<MXF::InterchangeObject**>(&descriptor)); - descriptor->ChannelAssignment = Options.channel_assignment; + MXF::WaveAudioDescriptor *essence_descriptor = 0; + Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_WaveAudioDescriptor), + reinterpret_cast<MXF::InterchangeObject**>(&essence_descriptor)); + assert(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; + } + + if ( Options.channel_assignment.HasValue() ) + { + essence_descriptor->ChannelAssignment = Options.channel_assignment; + } + else if ( Options.use_interop_sound_wtf ) + { + essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_4_WTF); + } + else + { + essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_MCA); + } + + // add descriptors to the essence_descriptor and header + ASDCP::MXF::InterchangeObject_list_t::iterator i; + for ( i = Options.mca_config.begin(); i != Options.mca_config.end(); ++i ) + { + if ( (*i)->GetUL() != UL(g_dict->ul(MDD_AudioChannelLabelSubDescriptor)) + && (*i)->GetUL() != UL(g_dict->ul(MDD_SoundfieldGroupLabelSubDescriptor)) + && (*i)->GetUL() != UL(g_dict->ul(MDD_GroupOfSoundfieldGroupsLabelSubDescriptor)) ) + { + fprintf(stderr, "Essence sub-descriptor is not an MCALabelSubDescriptor.\n"); + (*i)->Dump(); + } + + Writer.OP1aHeader().AddChildObject(*i); + essence_descriptor->SubDescriptors.push_back((*i)->InstanceUID); + *i = 0; // parent will only free the ones we don't keep + } + } } } @@ -907,8 +1088,12 @@ write_PCM_file(CommandOptions& Options) { fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n"); fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size()); - result = RESULT_ENDOFFILE; - continue; + + if ( Options.write_partial_pcm_flag ) + { + result = RESULT_ENDOFFILE; + continue; + } } if ( Options.verbose_flag ) @@ -938,6 +1123,139 @@ write_PCM_file(CommandOptions& Options) return result; } +// Mix one or more plaintext PCM audio streams with a Dolby Atmos Synchronization channel and write them to a plaintext ASDCP file +// Mix one or more plaintext PCM audio streams with a Dolby Atmos Synchronization channel and write them to a ciphertext ASDCP file +// +Result_t +write_PCM_with_ATMOS_sync_file(CommandOptions& Options) +{ + AESEncContext* Context = 0; + HMACContext* HMAC = 0; + PCM::MXFWriter Writer; + PCM::FrameBuffer FrameBuffer; + PCM::AudioDescriptor ADesc; + Rational PictureRate = Options.PictureRate(); + byte_t IV_buf[CBC_BLOCK_SIZE]; + Kumu::FortunaRNG RNG; + + WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here + if ( Options.asset_id_flag ) + memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen); + else + Kumu::GenRandomUUID(Info.AssetUUID); + AtmosSyncChannelMixer Mixer(Info.AssetUUID); + + // set up essence parser + Result_t result = Mixer.OpenRead(Options.filenames, PictureRate); + + // set up MXF writer + if ( ASDCP_SUCCESS(result) ) + { + Mixer.FillAudioDescriptor(ADesc); + + ADesc.EditRate = PictureRate; + FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc)); + ADesc.ChannelFormat = PCM::CF_CFG_4; + + if ( Options.verbose_flag ) + { + fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n", + ADesc.AudioSamplingRate.Quotient() / 1000.0, + Options.szPictureRate(), + PCM::CalcSamplesPerFrame(ADesc)); + fputs("AudioDescriptor:\n", stderr); + PCM::AudioDescriptorDump(ADesc); + } + } + + if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) + { + 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 + { + create_random_uuid(Info.CryptographicKeyID); + } + + 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, ADesc); + } + + if ( ASDCP_SUCCESS(result) ) + { + result = Mixer.Reset(); + ui32_t duration = 0; + + while ( ASDCP_SUCCESS(result) && duration++ < Options.duration ) + { + result = Mixer.ReadFrame(FrameBuffer); + + if ( ASDCP_SUCCESS(result) ) + { + if ( FrameBuffer.Size() != FrameBuffer.Capacity() ) + { + fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n"); + fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size()); + + if ( Options.write_partial_pcm_flag ) + { + result = RESULT_ENDOFFILE; + continue; + } + } + + if ( Options.verbose_flag ) + FrameBuffer.Dump(stderr, Options.fb_dump_size); + + if ( ! Options.no_write_flag ) + { + result = Writer.WriteFrame(FrameBuffer, Context, HMAC); + + // The Writer class will forward the last block of ciphertext + // to the encryption context for use as the IV for the next + // frame. If you want to use non-sequitur IV values, un-comment + // the following line of code. + // if ( ASDCP_SUCCESS(result) && Options.key_flag ) + // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE)); + } + } + } + + if ( result == RESULT_ENDOFFILE ) + result = RESULT_OK; + } + + if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) + result = Writer.Finalize(); + + return result; +} + //------------------------------------------------------------------------------------------ // TimedText essence @@ -959,7 +1277,7 @@ write_timed_text_file(CommandOptions& Options) Kumu::FortunaRNG RNG; // set up essence parser - Result_t result = Parser.OpenRead(Options.filenames.front().c_str()); + Result_t result = Parser.OpenRead(Options.filenames.front()); // set up MXF writer if ( ASDCP_SUCCESS(result) ) @@ -982,11 +1300,9 @@ write_timed_text_file(CommandOptions& Options) else Kumu::GenRandomUUID(Info.AssetUUID); - if ( Options.use_smpte_labels ) - { - Info.LabelSetType = LS_MXF_SMPTE; - fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n"); - } + // 428-7 IN 429-5 always uses SMPTE labels + Info.LabelSetType = LS_MXF_SMPTE; + fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n"); // configure encryption if( Options.key_flag ) @@ -995,9 +1311,13 @@ write_timed_text_file(CommandOptions& Options) Info.EncryptedEssence = true; if ( Options.key_id_flag ) - memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + { + memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen); + } else - RNG.FillRandom(Info.CryptographicKeyID, UUIDlen); + { + create_random_uuid(Info.CryptographicKeyID); + } Context = new AESEncContext; result = Context->InitKey(Options.key_value); @@ -1014,7 +1334,7 @@ write_timed_text_file(CommandOptions& Options) } if ( ASDCP_SUCCESS(result) ) - result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc); + result = Writer.OpenWrite(Options.out_file, Info, TDesc); } if ( ASDCP_FAILURE(result) ) @@ -1040,7 +1360,7 @@ write_timed_text_file(CommandOptions& Options) if ( ! Options.no_write_flag ) { result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC); - + // The Writer class will forward the last block of ciphertext // to the encryption context for use as the IV for the next // frame. If you want to use non-sequitur IV values, un-comment @@ -1060,12 +1380,253 @@ write_timed_text_file(CommandOptions& Options) return result; } +// Write one or more plaintext Dolby ATMOS bytestreams to a plaintext ASDCP file +// Write one or more plaintext Dolby ATMOS bytestreams to a ciphertext ASDCP file +// +Result_t +write_dolby_atmos_file(CommandOptions& Options) +{ + AESEncContext* Context = 0; + HMACContext* HMAC = 0; + ATMOS::MXFWriter Writer; + DCData::FrameBuffer FrameBuffer(Options.fb_size); + ATMOS::AtmosDescriptor ADesc; + DCData::SequenceParser Parser; + byte_t IV_buf[CBC_BLOCK_SIZE]; + Kumu::FortunaRNG RNG; + + // set up essence parser + Result_t result = Parser.OpenRead(Options.filenames.front()); + + // set up MXF writer + if ( ASDCP_SUCCESS(result) ) + { + Parser.FillDCDataDescriptor(ADesc); + ADesc.EditRate = Options.PictureRate(); + // TODO: fill AtmosDescriptor + ADesc.FirstFrame = Options.ffoa; + ADesc.MaxChannelCount = Options.max_channel_count; + ADesc.MaxObjectCount = Options.max_object_count; + Kumu::GenRandomUUID(ADesc.AtmosID); + ADesc.AtmosVersion = 1; + if ( Options.verbose_flag ) + { + fprintf(stderr, "Dolby ATMOS Data\n"); + fputs("AtmosDescriptor:\n", stderr); + fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size); + ATMOS::AtmosDescriptorDump(ADesc); + } + } + + if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) + { + WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here + if ( Options.asset_id_flag ) + memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen); + else + Kumu::GenRandomUUID(Info.AssetUUID); + + Info.LabelSetType = LS_MXF_SMPTE; + + // 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 + { + create_random_uuid(Info.CryptographicKeyID); + } + + 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, ADesc); + } + + if ( ASDCP_SUCCESS(result) ) + { + ui32_t duration = 0; + result = Parser.Reset(); + + while ( ASDCP_SUCCESS(result) && duration++ < Options.duration ) + { + result = Parser.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, Context, HMAC); + + // The Writer class will forward the last block of ciphertext + // to the encryption context for use as the IV for the next + // frame. If you want to use non-sequitur IV values, un-comment + // the following line of code. + // if ( ASDCP_SUCCESS(result) && Options.key_flag ) + // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE)); + } + } + + 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 Aux Data (ST 429-14) bytestreams to a plaintext ASDCP file +// Write one or more plaintext Aux Data (ST 429-14) bytestreams to a ciphertext ASDCP file +// +Result_t +write_aux_data_file(CommandOptions& Options) +{ + AESEncContext* Context = 0; + HMACContext* HMAC = 0; + DCData::MXFWriter Writer; + DCData::FrameBuffer FrameBuffer(Options.fb_size); + DCData::DCDataDescriptor DDesc; + DCData::SequenceParser Parser; + byte_t IV_buf[CBC_BLOCK_SIZE]; + Kumu::FortunaRNG RNG; + + // set up essence parser + Result_t result = Parser.OpenRead(Options.filenames.front()); + + // set up MXF writer + if ( ASDCP_SUCCESS(result) ) + { + Parser.FillDCDataDescriptor(DDesc); + memcpy(DDesc.DataEssenceCoding, Options.aux_data_coding.Value(), Options.aux_data_coding.Size()); + DDesc.EditRate = Options.PictureRate(); + + if ( Options.verbose_flag ) + { + fprintf(stderr, "Aux Data\n"); + fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size); + DCData::DCDataDescriptorDump(DDesc); + } + } + + if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) + { + WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here + if ( Options.asset_id_flag ) + memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen); + else + Kumu::GenRandomUUID(Info.AssetUUID); + + Info.LabelSetType = LS_MXF_SMPTE; + + // 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 + { + create_random_uuid(Info.CryptographicKeyID); + } + + 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, DDesc); + } + + if ( ASDCP_SUCCESS(result) ) + { + ui32_t duration = 0; + result = Parser.Reset(); + + while ( ASDCP_SUCCESS(result) && duration++ < Options.duration ) + { + result = Parser.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, Context, HMAC); + + // The Writer class will forward the last block of ciphertext + // to the encryption context for use as the IV for the next + // frame. If you want to use non-sequitur IV values, un-comment + // the following line of code. + // if ( ASDCP_SUCCESS(result) && Options.key_flag ) + // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE)); + } + } + + if ( result == RESULT_ENDOFFILE ) + result = RESULT_OK; + } + + if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag ) + result = Writer.Finalize(); + + return result; +} + // int 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 ) @@ -1074,7 +1635,15 @@ main(int argc, const char** argv) if ( Options.help_flag ) usage(); - if ( Options.version_flag || Options.help_flag ) + if ( Options.show_ul_values_flag ) + { + if ( Options.use_smpte_labels ) + DefaultSMPTEDict().Dump(stdout); + else + DefaultInteropDict().Dump(stdout); + } + + if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag ) return 0; if ( Options.error_flag ) @@ -1083,16 +1652,8 @@ main(int argc, const char** argv) return 3; } - if ( Options.show_ul_values ) - { - if ( Options.use_smpte_labels ) - DefaultSMPTEDict().Dump(stdout); - else - DefaultInteropDict().Dump(stdout); - } - EssenceType_t EssenceType; - result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType); + result = ASDCP::RawEssenceType(Options.filenames.front(), EssenceType); if ( ASDCP_SUCCESS(result) ) { @@ -1115,13 +1676,36 @@ main(int argc, const char** argv) case ESS_PCM_24b_48k: case ESS_PCM_24b_96k: - result = write_PCM_file(Options); + if ( Options.dolby_atmos_sync_flag ) + { + result = write_PCM_with_ATMOS_sync_file(Options); + } + else + { + result = write_PCM_file(Options); + } break; - + case ESS_TIMED_TEXT: result = write_timed_text_file(Options); break; + case ESS_DCDATA_DOLBY_ATMOS: + result = write_dolby_atmos_file(Options); + break; + + case ESS_DCDATA_UNKNOWN: + if ( ! Options.aux_data_coding.HasValue() ) + { + fprintf(stderr, "Option \"-A <UL>\" is required for Aux Data essence.\n"); + return 3; + } + else + { + result = write_aux_data_file(Options); + } + break; + default: fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n", Options.filenames.front().c_str()); |
