2 Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
8 1. Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14 derived from this software without specific prior written permission.
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 /*! \file as-02-wrap.cpp
29 \brief AS-02 file manipulation utility
31 This program wraps IMF essence (picture or sound) in to an AS-02 MXF file.
33 For more information about AS-02, please refer to the header file AS_02.h
34 For more information about asdcplib, please refer to the header file AS_DCP.h
37 #include <KM_fileio.h>
40 #include <PCMParserList.h>
43 using namespace ASDCP;
45 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
49 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
51 snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
55 //------------------------------------------------------------------------------------------
57 // command line option parser class
59 static const char* PROGRAM_NAME = "as-02-wrap"; // program name for messages
61 // local program identification info written to file headers
62 class MyInfo : public WriterInfo
67 static byte_t default_ProductUUID_Data[UUIDlen] =
68 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
69 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
71 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
72 CompanyName = "WidgetCo";
73 ProductName = "as-02-wrap";
74 ProductVersion = ASDCP::Version();
80 // Increment the iterator, test for an additional non-option command line argument.
81 // Causes the caller to return if there are no remaining arguments or if the next
82 // argument begins with '-'.
83 #define TEST_EXTRA_ARG(i,c) \
84 if ( ++i >= argc || argv[(i)][0] == '-' ) { \
85 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
91 banner(FILE* stream = stdout)
95 Copyright (c) 2011-2012, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
96 asdcplib may be copied only under the terms of the license found at\n\
97 the top of every file in the asdcplib distribution kit.\n\n\
98 Specify the -h (help) option for further information about %s\n\n",
99 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
104 usage(FILE* stream = stdout)
107 USAGE: %s [-h|-help] [-V]\n\
109 %s [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
110 [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
111 [-M] [-p <n>/<d>] [-v] [-W]\n\
112 [-z|-Z] <input-file>+ <output-file>\n\n",
113 PROGRAM_NAME, PROGRAM_NAME);
117 -C <UL> - Set ChannelAssignment UL value\n\
118 -h | -help - Show help\n\
119 -V - Show version information\n\
120 -e - Encrypt JP2K headers (default)\n\
121 -E - Do not encrypt JP2K headers\n\
122 -j <key-id-str> - Write key ID instead of creating a random value\n\
123 -k <key-string> - Use key for ciphertext operations\n\
124 -M - Do not create HMAC values when writing\n\
125 -a <UUID> - Specify the Asset ID of the file\n\
126 -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
127 Defaults to 4,194,304 (4MB)\n\
128 -d <duration> - Number of frames to process, default all\n\
129 -f <start-frame> - Starting frame number, default 0\n\
130 -p <n>/<d> - Edit Rate of the output file. 24/1 is the default\n\
131 -v - Verbose, prints informative messages to stderr\n\
132 -W - Read input file only, do not write source file\n\
133 -z - Fail if j2c inputs have unequal parameters (default)\n\
134 -Z - Ignore unequal parameters in j2c inputs\n\
136 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
137 o All option arguments must be separated from the option by whitespace.\n\n");
147 bool error_flag; // true if the given options are in error or not complete
148 bool key_flag; // true if an encryption key was given
149 bool asset_id_flag; // true if an asset ID was given
150 bool encrypt_header_flag; // true if j2c headers are to be encrypted
151 bool write_hmac; // true if HMAC values are to be generated and written
152 bool verbose_flag; // true if the verbose option was selected
153 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
154 bool no_write_flag; // true if no output files are to be written
155 bool version_flag; // true if the version display option was selected
156 bool help_flag; // true if the help display option was selected
157 ui32_t start_frame; // frame number to begin processing
158 ui32_t duration; // number of frames to be processed
159 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
160 Rational edit_rate; // edit rate of JP2K sequence
161 ui32_t fb_size; // size of picture frame buffer
162 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
163 bool key_id_flag; // true if a key ID was given
164 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
165 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
166 std::string out_file; //
167 bool show_ul_values; /// if true, dump the UL table before going tp work.
168 Kumu::PathList_t filenames; // list of filenames to be processed
169 UL channel_assignment;
171 //new attributes for AS-02 support
172 AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
173 ui32_t partition_space; //Shim parameter partition_spacing
176 CommandOptions(int argc, const char** argv) :
177 error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
178 encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
179 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
180 duration(0xffffffff), j2c_pedantic(true), fb_size(FRAME_BUFFER_SIZE),
181 show_ul_values(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60)
183 memset(key_value, 0, KeyLen);
184 memset(key_id_value, 0, UUIDlen);
186 for ( int i = 1; i < argc; i++ )
189 if ( (strcmp( argv[i], "-help") == 0) )
195 if ( argv[i][0] == '-'
196 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
199 switch ( argv[i][1] )
202 asset_id_flag = true;
203 TEST_EXTRA_ARG(i, 'a');
206 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
208 if ( length != UUIDlen )
210 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
217 TEST_EXTRA_ARG(i, 'b');
218 fb_size = abs(atoi(argv[i]));
221 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
226 TEST_EXTRA_ARG(i, 'U');
227 if ( ! channel_assignment.DecodeHex(argv[i]) )
229 fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
235 TEST_EXTRA_ARG(i, 'd');
236 duration = abs(atoi(argv[i]));
239 case 'E': encrypt_header_flag = false; break;
240 case 'e': encrypt_header_flag = true; break;
243 TEST_EXTRA_ARG(i, 'f');
244 start_frame = abs(atoi(argv[i]));
247 case 'h': help_flag = true; break;
249 case 'j': key_id_flag = true;
250 TEST_EXTRA_ARG(i, 'j');
253 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
255 if ( length != UUIDlen )
257 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
263 case 'k': key_flag = true;
264 TEST_EXTRA_ARG(i, 'k');
267 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
269 if ( length != KeyLen )
271 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
277 case 'M': write_hmac = false; break;
280 TEST_EXTRA_ARG(i, 'p');
281 /// TODO: VERY BROKEN, WANT RATIONAL
282 edit_rate.Numerator = abs(atoi(argv[i]));
283 edit_rate.Denominator = 1;
286 case 'V': version_flag = true; break;
287 case 'v': verbose_flag = true; break;
288 case 'W': no_write_flag = true; break;
289 case 'Z': j2c_pedantic = false; break;
290 case 'z': j2c_pedantic = true; break;
293 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
300 if ( argv[i][0] != '-' )
302 filenames.push_back(argv[i]);
306 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
312 if ( help_flag || version_flag )
315 if ( filenames.size() < 2 )
317 fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
321 out_file = filenames.back();
322 filenames.pop_back();
328 //------------------------------------------------------------------------------------------
331 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
332 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
335 write_JP2K_file(CommandOptions& Options)
337 AESEncContext* Context = 0;
338 HMACContext* HMAC = 0;
339 AS_02::JP2K::MXFWriter Writer;
340 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
341 JP2K::PictureDescriptor PDesc;
342 JP2K::SequenceParser Parser;
343 byte_t IV_buf[CBC_BLOCK_SIZE];
344 Kumu::FortunaRNG RNG;
346 // set up essence parser
347 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
350 if ( ASDCP_SUCCESS(result) )
352 Parser.FillPictureDescriptor(PDesc);
353 PDesc.EditRate = Options.edit_rate;
355 if ( Options.verbose_flag )
357 fprintf(stderr, "JPEG 2000 pictures\n");
358 fputs("PictureDescriptor:\n", stderr);
359 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
360 JP2K::PictureDescriptorDump(PDesc);
364 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
366 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
367 Info.LabelSetType = LS_MXF_SMPTE;
369 if ( Options.asset_id_flag )
370 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
372 Kumu::GenRandomUUID(Info.AssetUUID);
374 // configure encryption
375 if( Options.key_flag )
377 Kumu::GenRandomUUID(Info.ContextID);
378 Info.EncryptedEssence = true;
380 if ( Options.key_id_flag )
381 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
383 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
385 Context = new AESEncContext;
386 result = Context->InitKey(Options.key_value);
388 if ( ASDCP_SUCCESS(result) )
389 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
391 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
393 Info.UsesHMAC = true;
394 HMAC = new HMACContext;
395 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
399 if ( ASDCP_SUCCESS(result) )
400 result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc, Options.index_strategy, Options.partition_space);
403 if ( ASDCP_SUCCESS(result) )
406 result = Parser.Reset();
408 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
410 result = Parser.ReadFrame(FrameBuffer);
412 if ( ASDCP_SUCCESS(result) )
414 if ( Options.verbose_flag )
415 FrameBuffer.Dump(stderr, Options.fb_dump_size);
417 if ( Options.encrypt_header_flag )
418 FrameBuffer.PlaintextOffset(0);
421 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
423 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
425 // The Writer class will forward the last block of ciphertext
426 // to the encryption context for use as the IV for the next
427 // frame. If you want to use non-sequitur IV values, un-comment
428 // the following line of code.
429 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
430 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
434 if ( result == RESULT_ENDOFFILE )
438 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
439 result = Writer.Finalize();
444 //------------------------------------------------------------------------------------------
448 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
449 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
452 write_PCM_file(CommandOptions& Options)
454 AESEncContext* Context = 0;
455 HMACContext* HMAC = 0;
456 PCMParserList Parser;
457 PCM::MXFWriter Writer;
458 PCM::FrameBuffer FrameBuffer;
459 PCM::AudioDescriptor ADesc;
460 byte_t IV_buf[CBC_BLOCK_SIZE];
461 Kumu::FortunaRNG RNG;
463 // set up essence parser
464 Result_t result = Parser.OpenRead(Options.filenames, Rational(1, 1));
467 if ( ASDCP_SUCCESS(result) )
469 Parser.FillAudioDescriptor(ADesc);
471 ADesc.EditRate = Options.edit_rate;
472 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
474 if ( Options.verbose_flag )
477 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
478 ADesc.AudioSamplingRate.Quotient() / 1000.0,
479 RationalToString(Options.edit_rate, buf, 64),
480 PCM::CalcSamplesPerFrame(ADesc));
481 fputs("AudioDescriptor:\n", stderr);
482 PCM::AudioDescriptorDump(ADesc);
486 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
488 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
489 Info.LabelSetType = LS_MXF_SMPTE;
491 if ( Options.asset_id_flag )
492 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
494 Kumu::GenRandomUUID(Info.AssetUUID);
496 // configure encryption
497 if( Options.key_flag )
499 Kumu::GenRandomUUID(Info.ContextID);
500 Info.EncryptedEssence = true;
502 if ( Options.key_id_flag )
503 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
505 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
507 Context = new AESEncContext;
508 result = Context->InitKey(Options.key_value);
510 if ( ASDCP_SUCCESS(result) )
511 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
513 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
515 Info.UsesHMAC = true;
516 HMAC = new HMACContext;
517 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
521 if ( ASDCP_SUCCESS(result) )
522 result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
524 if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() )
526 MXF::WaveAudioDescriptor *descriptor = 0;
527 Writer.OP1aHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor),
528 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
529 descriptor->ChannelAssignment = Options.channel_assignment;
533 if ( ASDCP_SUCCESS(result) )
535 result = Parser.Reset();
538 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
540 result = Parser.ReadFrame(FrameBuffer);
542 if ( ASDCP_SUCCESS(result) )
544 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
546 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
547 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
548 result = RESULT_ENDOFFILE;
552 if ( Options.verbose_flag )
553 FrameBuffer.Dump(stderr, Options.fb_dump_size);
555 if ( ! Options.no_write_flag )
557 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
559 // The Writer class will forward the last block of ciphertext
560 // to the encryption context for use as the IV for the next
561 // frame. If you want to use non-sequitur IV values, un-comment
562 // the following line of code.
563 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
564 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
569 if ( result == RESULT_ENDOFFILE )
573 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
574 result = Writer.Finalize();
582 main(int argc, const char** argv)
584 Result_t result = RESULT_OK;
586 CommandOptions Options(argc, argv);
588 if ( Options.version_flag )
591 if ( Options.help_flag )
594 if ( Options.version_flag || Options.help_flag )
597 if ( Options.error_flag )
599 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
603 if ( Options.show_ul_values )
605 DefaultSMPTEDict().Dump(stdout);
608 EssenceType_t EssenceType;
609 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
611 if ( ASDCP_SUCCESS(result) )
613 switch ( EssenceType )
616 result = write_JP2K_file(Options);
619 case ESS_PCM_24b_48k:
620 case ESS_PCM_24b_96k:
621 result = write_PCM_file(Options);
625 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
626 Options.filenames.front().c_str());
631 if ( ASDCP_FAILURE(result) )
633 fputs("Program stopped on error.\n", stderr);
635 if ( result != RESULT_FAIL )
637 fputs(result, stderr);
649 // end as-02-wrap.cpp