2 Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15 3. The name of the author may not be used to endorse or promote products
16 derived from this software without specific prior written permission.
18 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 /*! \file as-02-wrap.cpp
31 \brief AS-02 file manipulation utility
33 This program wraps IMF essence (picture or sound) in to an AS-02 MXF file.
35 For more information about AS-02, please refer to the header file AS_02.h
36 For more information about asdcplib, please refer to the header file AS_DCP.h
39 #include <KM_fileio.h>
42 #include <PCMParserList.h>
45 using namespace ASDCP;
47 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
48 const ASDCP::Dictionary *g_dict = 0;
52 RationalToString(const ASDCP::Rational& r, char* buf, const ui32_t& len)
54 snprintf(buf, len, "%d/%d", r.Numerator, r.Denominator);
60 //------------------------------------------------------------------------------------------
62 // command line option parser class
64 static const char* PROGRAM_NAME = "as-02-wrap"; // program name for messages
66 // local program identification info written to file headers
67 class MyInfo : public WriterInfo
72 static byte_t default_ProductUUID_Data[UUIDlen] =
73 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
74 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
76 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
77 CompanyName = "WidgetCo";
78 ProductName = "as-02-wrap";
79 ProductVersion = ASDCP::Version();
85 // Increment the iterator, test for an additional non-option command line argument.
86 // Causes the caller to return if there are no remaining arguments or if the next
87 // argument begins with '-'.
88 #define TEST_EXTRA_ARG(i,c) \
89 if ( ++i >= argc || argv[(i)][0] == '-' ) { \
90 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
96 banner(FILE* stream = stdout)
100 Copyright (c) 2011-2013, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
101 asdcplib may be copied only under the terms of the license found at\n\
102 the top of every file in the asdcplib distribution kit.\n\n\
103 Specify the -h (help) option for further information about %s\n\n",
104 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
109 usage(FILE* stream = stdout)
112 USAGE: %s [-h|-help] [-V]\n\
114 %s [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
115 [-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
116 [-M] [-m <expr>] [-r <n>/<d>] [-s <seconds>] [-v] [-W]\n\
117 [-z|-Z] <input-file>+ <output-file>\n\n",
118 PROGRAM_NAME, PROGRAM_NAME);
122 -C <UL> - Set ChannelAssignment UL value\n\
123 -h | -help - Show help\n\
124 -V - Show version information\n\
125 -e - Encrypt JP2K headers (default)\n\
126 -E - Do not encrypt JP2K headers\n\
127 -j <key-id-str> - Write key ID instead of creating a random value\n\
128 -k <key-string> - Use key for ciphertext operations\n\
129 -M - Do not create HMAC values when writing\n\
130 -m <expr> - Write MCA labels using <expr>. Example:\n\
131 51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
132 -a <UUID> - Specify the Asset ID of the file\n\
133 -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
134 Defaults to 4,194,304 (4MB)\n\
135 -d <duration> - Number of frames to process, default all\n\
136 -f <start-frame> - Starting frame number, default 0\n\
137 -r <n>/<d> - Edit Rate of the output file. 24/1 is the default\n\
138 -s <seconds> - Duration of a frame-wrapped partition (default 60)\n\
139 -v - Verbose, prints informative messages to stderr\n\
140 -W - Read input file only, do not write source file\n\
141 -z - Fail if j2c inputs have unequal parameters (default)\n\
142 -Z - Ignore unequal parameters in j2c inputs\n\
144 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
145 o All option arguments must be separated from the option by whitespace.\n\n");
149 static ASDCP::Rational
150 decode_rational(const char* str_rat)
153 ui32_t Num = atoi(str_rat);
156 const char* den_str = strrchr(str_rat, ' ');
158 Den = atoi(den_str+1);
160 return ASDCP::Rational(Num, Den);
169 bool error_flag; // true if the given options are in error or not complete
170 bool key_flag; // true if an encryption key was given
171 bool asset_id_flag; // true if an asset ID was given
172 bool encrypt_header_flag; // true if j2c headers are to be encrypted
173 bool write_hmac; // true if HMAC values are to be generated and written
174 bool verbose_flag; // true if the verbose option was selected
175 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
176 bool no_write_flag; // true if no output files are to be written
177 bool version_flag; // true if the version display option was selected
178 bool help_flag; // true if the help display option was selected
179 ui32_t start_frame; // frame number to begin processing
180 ui32_t duration; // number of frames to be processed
181 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
182 Rational edit_rate; // edit rate of JP2K sequence
183 ui32_t fb_size; // size of picture frame buffer
184 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
185 bool key_id_flag; // true if a key ID was given
186 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
187 byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
188 std::string out_file; //
189 bool show_ul_values_flag; /// if true, dump the UL table before going tp work.
190 Kumu::PathList_t filenames; // list of filenames to be processed
191 UL channel_assignment;
192 ASDCP::MXF::AS02_MCAConfigParser mca_config;
194 //new attributes for AS-02 support
195 AS_02::IndexStrategy_t index_strategy; //Shim parameter index_strategy_frame/clip
196 ui32_t partition_space; //Shim parameter partition_spacing
199 CommandOptions(int argc, const char** argv) :
200 error_flag(true), key_flag(false), key_id_flag(false), asset_id_flag(false),
201 encrypt_header_flag(true), write_hmac(true), verbose_flag(false), fb_dump_size(0),
202 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
203 duration(0xffffffff), j2c_pedantic(true), edit_rate(24,1), fb_size(FRAME_BUFFER_SIZE),
204 show_ul_values_flag(false), index_strategy(AS_02::IS_FOLLOW), partition_space(60),
207 memset(key_value, 0, KeyLen);
208 memset(key_id_value, 0, UUIDlen);
210 for ( int i = 1; i < argc; i++ )
213 if ( (strcmp( argv[i], "-help") == 0) )
219 if ( argv[i][0] == '-'
220 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
223 switch ( argv[i][1] )
226 asset_id_flag = true;
227 TEST_EXTRA_ARG(i, 'a');
230 Kumu::hex2bin(argv[i], asset_id_value, UUIDlen, &length);
232 if ( length != UUIDlen )
234 fprintf(stderr, "Unexpected asset ID length: %u, expecting %u characters.\n", length, UUIDlen);
241 TEST_EXTRA_ARG(i, 'b');
242 fb_size = abs(atoi(argv[i]));
245 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
250 TEST_EXTRA_ARG(i, 'U');
251 if ( ! channel_assignment.DecodeHex(argv[i]) )
253 fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
259 TEST_EXTRA_ARG(i, 'd');
260 duration = abs(atoi(argv[i]));
263 case 'E': encrypt_header_flag = false; break;
264 case 'e': encrypt_header_flag = true; break;
267 TEST_EXTRA_ARG(i, 'f');
268 start_frame = abs(atoi(argv[i]));
271 case 'h': help_flag = true; break;
273 case 'j': key_id_flag = true;
274 TEST_EXTRA_ARG(i, 'j');
277 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
279 if ( length != UUIDlen )
281 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
287 case 'k': key_flag = true;
288 TEST_EXTRA_ARG(i, 'k');
291 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
293 if ( length != KeyLen )
295 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
301 case 'M': write_hmac = false; break;
304 TEST_EXTRA_ARG(i, 'm');
305 if ( ! mca_config.DecodeString(argv[i]) )
312 TEST_EXTRA_ARG(i, 'r');
313 edit_rate = decode_rational(argv[i]);
317 TEST_EXTRA_ARG(i, 's');
318 partition_space = abs(atoi(argv[i]));
321 case 'u': show_ul_values_flag = true; break;
322 case 'V': version_flag = true; break;
323 case 'v': verbose_flag = true; break;
324 case 'W': no_write_flag = true; break;
325 case 'Z': j2c_pedantic = false; break;
326 case 'z': j2c_pedantic = true; break;
329 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
336 if ( argv[i][0] != '-' )
338 filenames.push_back(argv[i]);
342 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
348 if ( help_flag || version_flag )
351 if ( filenames.size() < 2 )
353 fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
357 out_file = filenames.back();
358 filenames.pop_back();
364 //------------------------------------------------------------------------------------------
368 Result_t JP2K_PDesc_to_MD(const ASDCP::JP2K::PictureDescriptor& PDesc,
369 const ASDCP::Dictionary& dict,
370 ASDCP::MXF::RGBAEssenceDescriptor *EssenceDescriptor,
371 ASDCP::MXF::JPEG2000PictureSubDescriptor *EssenceSubDescriptor);
373 Result_t PCM_ADesc_to_MD(ASDCP::PCM::AudioDescriptor& ADesc, ASDCP::MXF::WaveAudioDescriptor* ADescObj);
376 // Write one or more plaintext JPEG 2000 codestreams to a plaintext AS-02 file
377 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext AS-02 file
380 write_JP2K_file(CommandOptions& Options)
382 AESEncContext* Context = 0;
383 HMACContext* HMAC = 0;
384 AS_02::JP2K::MXFWriter Writer;
385 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
386 JP2K::SequenceParser Parser;
387 byte_t IV_buf[CBC_BLOCK_SIZE];
388 Kumu::FortunaRNG RNG;
389 ASDCP::MXF::RGBAEssenceDescriptor *essence_descriptor = 0;
390 ASDCP::MXF::InterchangeObject_list_t essence_sub_descriptors;
392 // set up essence parser
393 Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
396 if ( ASDCP_SUCCESS(result) )
398 ASDCP::JP2K::PictureDescriptor PDesc;
399 Parser.FillPictureDescriptor(PDesc);
400 PDesc.EditRate = Options.edit_rate;
402 if ( Options.verbose_flag )
404 fprintf(stderr, "JPEG 2000 pictures\n");
405 fputs("PictureDescriptor:\n", stderr);
406 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
407 JP2K::PictureDescriptorDump(PDesc);
410 // TODO: optionally set up CDCIEssenceDescriptor
411 essence_descriptor = new ASDCP::MXF::RGBAEssenceDescriptor(g_dict);
412 essence_sub_descriptors.push_back(new ASDCP::MXF::JPEG2000PictureSubDescriptor(g_dict));
414 result = ASDCP::JP2K_PDesc_to_MD(PDesc, *g_dict, essence_descriptor,
415 reinterpret_cast<ASDCP::MXF::JPEG2000PictureSubDescriptor*>(essence_sub_descriptors.back()));
417 /// TODO: set with magic or some such thing
418 essence_descriptor->ComponentMaxRef = 4095;
419 essence_descriptor->ComponentMinRef = 0;
422 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
424 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
425 Info.LabelSetType = LS_MXF_SMPTE;
427 if ( Options.asset_id_flag )
428 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
430 Kumu::GenRandomUUID(Info.AssetUUID);
432 // configure encryption
433 if( Options.key_flag )
435 Kumu::GenRandomUUID(Info.ContextID);
436 Info.EncryptedEssence = true;
438 if ( Options.key_id_flag )
439 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
441 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
443 Context = new AESEncContext;
444 result = Context->InitKey(Options.key_value);
446 if ( ASDCP_SUCCESS(result) )
447 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
449 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
451 Info.UsesHMAC = true;
452 HMAC = new HMACContext;
453 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
457 if ( ASDCP_SUCCESS(result) )
459 result = Writer.OpenWrite(Options.out_file, Info,
460 static_cast<ASDCP::MXF::FileDescriptor*>(essence_descriptor),
461 essence_sub_descriptors,
462 Options.edit_rate, 16384, Options.index_strategy, Options.partition_space);
463 // TODO: make 16384 part of CommandOptions
467 if ( ASDCP_SUCCESS(result) )
470 result = Parser.Reset();
472 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
474 result = Parser.ReadFrame(FrameBuffer);
476 if ( ASDCP_SUCCESS(result) )
478 if ( Options.verbose_flag )
479 FrameBuffer.Dump(stderr, Options.fb_dump_size);
481 if ( Options.encrypt_header_flag )
482 FrameBuffer.PlaintextOffset(0);
485 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
487 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
489 // The Writer class will forward the last block of ciphertext
490 // to the encryption context for use as the IV for the next
491 // frame. If you want to use non-sequitur IV values, un-comment
492 // the following line of code.
493 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
494 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
498 if ( result == RESULT_ENDOFFILE )
502 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
503 result = Writer.Finalize();
508 //------------------------------------------------------------------------------------------
512 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
513 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
516 write_PCM_file(CommandOptions& Options)
518 AESEncContext* Context = 0;
519 HMACContext* HMAC = 0;
520 PCMParserList Parser;
521 AS_02::PCM::MXFWriter Writer;
522 PCM::FrameBuffer FrameBuffer;
523 byte_t IV_buf[CBC_BLOCK_SIZE];
524 Kumu::FortunaRNG RNG;
525 ASDCP::MXF::WaveAudioDescriptor *essence_descriptor = 0;
527 // set up essence parser
528 Result_t result = Parser.OpenRead(Options.filenames, Options.edit_rate);
531 if ( ASDCP_SUCCESS(result) )
533 ASDCP::PCM::AudioDescriptor ADesc;
534 Parser.FillAudioDescriptor(ADesc);
536 ADesc.EditRate = Options.edit_rate;
537 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
539 if ( Options.verbose_flag )
542 fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
543 ADesc.AudioSamplingRate.Quotient() / 1000.0,
544 RationalToString(Options.edit_rate, buf, 64),
545 PCM::CalcSamplesPerFrame(ADesc));
546 fputs("AudioDescriptor:\n", stderr);
547 PCM::AudioDescriptorDump(ADesc);
550 essence_descriptor = new ASDCP::MXF::WaveAudioDescriptor(g_dict);
552 result = ASDCP::PCM_ADesc_to_MD(ADesc, essence_descriptor);
554 if ( Options.mca_config.empty() )
556 essence_descriptor->ChannelAssignment = Options.channel_assignment;
560 if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
562 fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
563 Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
567 // this is the d-cinema MCA label, what is the one for IMF?
568 essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_MCA);
572 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
574 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
575 Info.LabelSetType = LS_MXF_SMPTE;
577 if ( Options.asset_id_flag )
578 memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
580 Kumu::GenRandomUUID(Info.AssetUUID);
582 // configure encryption
583 if( Options.key_flag )
585 Kumu::GenRandomUUID(Info.ContextID);
586 Info.EncryptedEssence = true;
588 if ( Options.key_id_flag )
589 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
591 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
593 Context = new AESEncContext;
594 result = Context->InitKey(Options.key_value);
596 if ( ASDCP_SUCCESS(result) )
597 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
599 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
601 Info.UsesHMAC = true;
602 HMAC = new HMACContext;
603 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
607 if ( ASDCP_SUCCESS(result) )
609 result = Writer.OpenWrite(Options.out_file.c_str(), Info, essence_descriptor,
610 Options.mca_config, Options.edit_rate);
614 if ( ASDCP_SUCCESS(result) )
616 result = Parser.Reset();
619 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
621 result = Parser.ReadFrame(FrameBuffer);
623 if ( ASDCP_SUCCESS(result) )
625 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
627 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
628 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
629 result = RESULT_ENDOFFILE;
633 if ( Options.verbose_flag )
634 FrameBuffer.Dump(stderr, Options.fb_dump_size);
636 if ( ! Options.no_write_flag )
638 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
640 // The Writer class will forward the last block of ciphertext
641 // to the encryption context for use as the IV for the next
642 // frame. If you want to use non-sequitur IV values, un-comment
643 // the following line of code.
644 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
645 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
650 if ( result == RESULT_ENDOFFILE )
654 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
655 result = Writer.Finalize();
663 main(int argc, const char** argv)
665 Result_t result = RESULT_OK;
667 g_dict = &ASDCP::DefaultSMPTEDict();
669 CommandOptions Options(argc, argv);
671 if ( Options.version_flag )
674 if ( Options.help_flag )
677 if ( Options.show_ul_values_flag )
679 g_dict->Dump(stdout);
682 if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
685 if ( Options.error_flag )
687 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
691 EssenceType_t EssenceType;
692 result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
694 if ( ASDCP_SUCCESS(result) )
696 switch ( EssenceType )
699 result = write_JP2K_file(Options);
702 case ESS_PCM_24b_48k:
703 case ESS_PCM_24b_96k:
704 result = write_PCM_file(Options);
708 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
709 Options.filenames.front().c_str());
714 if ( ASDCP_FAILURE(result) )
716 fputs("Program stopped on error.\n", stderr);
718 if ( result != RESULT_FAIL )
720 fputs(result, stderr);
732 // end as-02-wrap.cpp