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 )
412 result = Parser.ReadFrame(FrameBuffer);
414 if ( ASDCP_SUCCESS(result) )
416 if ( Options.verbose_flag )
417 FrameBuffer.Dump(stderr, Options.fb_dump_size);
419 if ( Options.encrypt_header_flag )
420 FrameBuffer.PlaintextOffset(0);
424 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
426 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
428 // The Writer class will forward the last block of ciphertext
429 // to the encryption context for use as the IV for the next
430 // frame. If you want to use non-sequitur IV values, un-comment
431 // the following line of code.
432 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
433 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
437 if ( result == RESULT_ENDOFFILE )
441 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
442 result = Writer.Finalize();
447 //------------------------------------------------------------------------------------------
451 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
452 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
455 write_PCM_file(CommandOptions& Options)
457 AESEncContext* Context = 0;
458 HMACContext* HMAC = 0;
459 PCMParserList Parser;
460 PCM::MXFWriter Writer;
461 PCM::FrameBuffer FrameBuffer;
462 PCM::AudioDescriptor ADesc;
463 byte_t IV_buf[CBC_BLOCK_SIZE];
464 Kumu::FortunaRNG RNG;
466 // set up essence parser
467 Result_t result = Parser.OpenRead(Options.filenames, Rational(1, 1));
470 if ( ASDCP_SUCCESS(result) )
472 Parser.FillAudioDescriptor(ADesc);
474 ADesc.EditRate = Options.edit_rate;
475 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
477 if ( Options.verbose_flag )
480 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
481 ADesc.AudioSamplingRate.Quotient() / 1000.0,
482 RationalToString(Options.edit_rate, buf, 64),
483 PCM::CalcSamplesPerFrame(ADesc));
484 fputs("AudioDescriptor:\n", stderr);
485 PCM::AudioDescriptorDump(ADesc);
489 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
491 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
492 Info.LabelSetType = LS_MXF_SMPTE;
494 if ( Options.asset_id_flag )
495 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
497 Kumu::GenRandomUUID(Info.AssetUUID);
499 // configure encryption
500 if( Options.key_flag )
502 Kumu::GenRandomUUID(Info.ContextID);
503 Info.EncryptedEssence = true;
505 if ( Options.key_id_flag )
506 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
508 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
510 Context = new AESEncContext;
511 result = Context->InitKey(Options.key_value);
513 if ( ASDCP_SUCCESS(result) )
514 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
516 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
518 Info.UsesHMAC = true;
519 HMAC = new HMACContext;
520 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
524 if ( ASDCP_SUCCESS(result) )
525 result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
527 if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() )
529 MXF::WaveAudioDescriptor *descriptor = 0;
530 Writer.OPAtomHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor),
531 reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
532 descriptor->ChannelAssignment = Options.channel_assignment;
536 if ( ASDCP_SUCCESS(result) )
538 result = Parser.Reset();
541 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
543 result = Parser.ReadFrame(FrameBuffer);
545 if ( ASDCP_SUCCESS(result) )
547 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
549 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
550 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
551 result = RESULT_ENDOFFILE;
555 if ( Options.verbose_flag )
556 FrameBuffer.Dump(stderr, Options.fb_dump_size);
558 if ( ! Options.no_write_flag )
560 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
562 // The Writer class will forward the last block of ciphertext
563 // to the encryption context for use as the IV for the next
564 // frame. If you want to use non-sequitur IV values, un-comment
565 // the following line of code.
566 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
567 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
572 if ( result == RESULT_ENDOFFILE )
576 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
577 result = Writer.Finalize();
585 main(int argc, const char** argv)
587 Result_t result = RESULT_OK;
589 CommandOptions Options(argc, argv);
591 if ( Options.version_flag )
594 if ( Options.help_flag )
597 if ( Options.version_flag || Options.help_flag )
600 if ( Options.error_flag )
602 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
606 if ( Options.show_ul_values )
608 DefaultSMPTEDict().Dump(stdout);
611 EssenceType_t EssenceType;
612 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
614 if ( ASDCP_SUCCESS(result) )
616 switch ( EssenceType )
619 result = write_JP2K_file(Options);
622 case ESS_PCM_24b_48k:
623 case ESS_PCM_24b_96k:
624 result = write_PCM_file(Options);
628 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
629 Options.filenames.front().c_str());
634 if ( ASDCP_FAILURE(result) )
636 fputs("Program stopped on error.\n", stderr);
638 if ( result != RESULT_FAIL )
640 fputs(result, stderr);
652 // end as-02-wrap.cpp