2 Copyright (c) 2003-2006, 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 asdcp-test.cpp
29 \brief AS-DCP file manipulation utility
31 This program provides command line access to the major features of the asdcplib
32 library, and serves as a library unit test which provides the functionality of
33 the supported use cases.
35 For more information about asdcplib, please refer to the header file AS_DCP.h
37 WARNING: While the asdcplib library attempts to provide a complete and secure
38 implementation of the cryptographic features of the AS-DCP file formats, this
39 unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
40 production environment without some modification.
42 In particular, this program uses weak IV generation and externally generated
43 plaintext keys. These shortcomings exist because cryptographic-quality
44 random number generation and key management are outside the scope of the
45 asdcplib library. Developers using asdcplib for commercial implementations
46 claiming SMPTE conformance are expected to provide proper implementations of
53 #include <KM_fileio.h>
55 #include <PCMParserList.h>
56 #include <WavFileWriter.h>
60 using namespace ASDCP;
62 const ui32_t FRAME_BUFFER_SIZE = 4*1024*1024;
64 //------------------------------------------------------------------------------------------
66 // command line option parser class
68 static const char* PACKAGE = "asdcp-test"; // program name for messages
69 const ui32_t MAX_IN_FILES = 16; // maximum number of input files handled by
70 // the command option parser
72 // local program identification info written to file headers
73 class MyInfo : public WriterInfo
78 static byte_t default_ProductUUID_Data[UUIDlen] =
79 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
80 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
82 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
83 CompanyName = "WidgetCo";
84 ProductName = "asdcp-test";
87 snprintf(s_buf, 128, "%u.%u.%u", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
88 ProductVersion = s_buf;
93 // Macros used to test command option data state.
95 // True if a major mode has already been selected.
96 #define TEST_MAJOR_MODE() ( info_flag||create_flag||extract_flag||genkey_flag||genid_flag||gop_start_flag )
98 // Causes the caller to return if a major mode has already been selected,
99 // otherwise sets the given flag.
100 #define TEST_SET_MAJOR_MODE(f) if ( TEST_MAJOR_MODE() ) \
102 fputs("Conflicting major mode, choose one of -(gcixG)).\n", stderr); \
107 // Increment the iterator, test for an additional non-option command line argument.
108 // Causes the caller to return if there are no remaining arguments or if the next
109 // argument begins with '-'.
110 #define TEST_EXTRA_ARG(i,c) if ( ++i >= argc || argv[(i)][0] == '-' ) \
112 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
117 banner(FILE* stream = stdout)
120 %s (asdcplib %s)\n\n\
121 Copyright (c) 2003-2006 John Hurst\n\n\
122 asdcplib may be copied only under the terms of the license found at\n\
123 the top of every file in the asdcplib distribution kit.\n\n\
124 Specify the -h (help) option for further information about %s\n\n",
125 PACKAGE, ASDCP::Version(), PACKAGE);
130 usage(FILE* stream = stdout)
133 USAGE: %s -c <output-file> [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
134 [-f <start-frame>] [-j <key-id-string>] [-k <key-string>] [-L] [-M]\n\
135 [-p <frame-rate>] [-R] [-s <num>] [-v] [-W]\n\
136 <input-file> [<input-file2> ...]\n\
138 %s [-h|-help] [-V]\n\
140 %s -i [-H] [-n] [-v] <input-file>\n\
144 %s -G [-v] <input-file>\n\
146 %s -x <file-prefix> [-b <buffer-size>] [-d <duration>]\n\
147 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S]\n\
148 [-v] [-W] <input-file>\n\
149 \n", PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE);
153 -c <output-file> - Create AS-DCP track file from input(s)\n\
154 -g - Generate a random 16 byte value to stdout\n\
155 -G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
156 -h | -help - Show help\n\
157 -i - Show file info\n\
158 -u - Generate a random UUID value to stdout\n\
159 -V - Show version information\n\
160 -x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
165 -e - Encrypt MPEG or JP2K headers (default)\n\
166 -E - Do not encrypt MPEG or JP2K headers\n\
167 -j <key-id-str> - Write key ID instead of creating a random value\n\
168 -k <key-string> - Use key for ciphertext operations\n\
169 -m - verify HMAC values when reading\n\
170 -M - Do not create HMAC values when writing\n\
174 Read/Write Options:\n\
175 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
176 Defaults to 4,194,304 (4MB)\n\
177 -d <duration> - Number of frames to process, default all\n\
178 -f <start-frame> - Starting frame number, default 0\n\
179 -L - Write SMPTE UL values instead of MXF Interop\n\
180 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
181 Use one of [23|24|48], 24 is default\n\
182 -R - Repeat the first frame over the entire file (picture\n\
183 essence only, requires -c, -d)\n\
184 -S - Split Wave essence to stereo WAV files during extract.\n\
185 Default is multichannel WAV\n\
186 -W - Read input file only, do not write source file\n\
191 -H - Show MXF header metadata, used with option -i\n\
192 -n - Show index, used with option -i\n\
195 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
196 stderr, used with option -v\n\
197 -v - Verbose, prints informative messages to stderr\n\
199 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
200 o All option arguments must be separated from the option by whitespace.\n\
201 o An argument of \"23\" to the -p option will be interpreted\n\
202 as 23000/1001 fps.\n\
213 bool error_flag; // true if the given options are in error or not complete
214 bool info_flag; // true if the file info mode was selected
215 bool create_flag; // true if the file create mode was selected
216 bool extract_flag; // true if the file extract mode was selected
217 bool genkey_flag; // true if we are to generate a new key value
218 bool genid_flag; // true if we are to generate a new UUID value
219 bool gop_start_flag; // true if we are to perform a GOP start lookup test
220 bool key_flag; // true if an encryption key was given
221 bool key_id_flag; // true if a key ID was given
222 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
223 bool write_hmac; // true if HMAC values are to be generated and written
224 bool read_hmac; // true if HMAC values are to be validated
225 bool split_wav; // true if PCM is to be extracted to stereo WAV files
226 bool verbose_flag; // true if the verbose option was selected
227 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
228 bool showindex_flag; // true if index is to be displayed
229 bool showheader_flag; // true if MXF file header is to be displayed
230 bool no_write_flag; // true if no output files are to be written
231 bool version_flag; // true if the version display option was selected
232 bool help_flag; // true if the help display option was selected
233 ui32_t start_frame; // frame number to begin processing
234 ui32_t duration; // number of frames to be processed
235 bool duration_flag; // true if duration argument given
236 bool do_repeat; // if true and -c -d, repeat first input frame
237 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
238 ui32_t picture_rate; // fps of picture when wrapping PCM
239 ui32_t fb_size; // size of picture frame buffer
240 ui32_t file_count; // number of elements in filenames[]
241 const char* file_root; // filename pre for files written by the extract mode
242 const char* out_file; // name of mxf file created by create mode
243 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
244 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
245 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
248 Rational PictureRate()
250 if ( picture_rate == 23 ) return EditRate_23_98;
251 if ( picture_rate == 48 ) return EditRate_48;
256 const char* szPictureRate()
258 if ( picture_rate == 23 ) return "23.976";
259 if ( picture_rate == 48 ) return "48";
264 CommandOptions(int argc, const char** argv) :
265 error_flag(true), info_flag(false), create_flag(false),
266 extract_flag(false), genkey_flag(false), genid_flag(false), gop_start_flag(false),
267 key_flag(false), key_id_flag(false), encrypt_header_flag(true),
268 write_hmac(true), read_hmac(false), split_wav(false),
269 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
270 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
271 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
272 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
274 memset(key_value, 0, KeyLen);
275 memset(key_id_value, 0, UUIDlen);
277 for ( int i = 1; i < argc; i++ )
280 if ( (strcmp( argv[i], "-help") == 0) )
286 if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
288 switch ( argv[i][1] )
290 case 'i': TEST_SET_MAJOR_MODE(info_flag); break;
291 case 'G': TEST_SET_MAJOR_MODE(gop_start_flag); break;
292 case 'W': no_write_flag = true; break;
293 case 'n': showindex_flag = true; break;
294 case 'H': showheader_flag = true; break;
295 case 'R': do_repeat = true; break;
296 case 'S': split_wav = true; break;
297 case 'V': version_flag = true; break;
298 case 'h': help_flag = true; break;
299 case 'v': verbose_flag = true; break;
300 case 'g': genkey_flag = true; break;
301 case 'u': genid_flag = true; break;
302 case 'e': encrypt_header_flag = true; break;
303 case 'E': encrypt_header_flag = false; break;
304 case 'M': write_hmac = false; break;
305 case 'm': read_hmac = true; break;
306 case 'L': use_smpte_labels = true; break;
309 TEST_SET_MAJOR_MODE(create_flag);
310 TEST_EXTRA_ARG(i, 'c');
315 TEST_SET_MAJOR_MODE(extract_flag);
316 TEST_EXTRA_ARG(i, 'x');
320 case 'k': key_flag = true;
321 TEST_EXTRA_ARG(i, 'k');
324 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
326 if ( length != KeyLen )
328 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", KeyLen, length);
334 case 'j': key_id_flag = true;
335 TEST_EXTRA_ARG(i, 'j');
338 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
340 if ( length != UUIDlen )
342 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", UUIDlen, length);
349 TEST_EXTRA_ARG(i, 'f');
350 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
354 TEST_EXTRA_ARG(i, 'd');
355 duration_flag = true;
356 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
360 TEST_EXTRA_ARG(i, 'p');
361 picture_rate = atoi(argv[i]);
365 TEST_EXTRA_ARG(i, 's');
366 fb_dump_size = atoi(argv[i]);
370 TEST_EXTRA_ARG(i, 'b');
371 fb_size = atoi(argv[i]);
374 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
379 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
386 if ( argv[i][0] != '-' )
388 filenames[file_count++] = argv[i];
392 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
396 if ( file_count >= MAX_IN_FILES )
398 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
404 if ( help_flag || version_flag )
407 if ( TEST_MAJOR_MODE() )
409 if ( ! genkey_flag && ! genid_flag && file_count == 0 )
411 fputs("Option requires at least one filename argument.\n", stderr);
416 if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
418 fputs("No operation selected (use one of -(gcixG) or -h for help).\n", stderr);
426 //------------------------------------------------------------------------------------------
429 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
430 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
433 write_MPEG2_file(CommandOptions& Options)
435 AESEncContext* Context = 0;
436 HMACContext* HMAC = 0;
437 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
438 MPEG2::Parser Parser;
439 MPEG2::MXFWriter Writer;
440 MPEG2::VideoDescriptor VDesc;
441 byte_t IV_buf[CBC_BLOCK_SIZE];
442 Kumu::FortunaRNG RNG;
444 // set up essence parser
445 Result_t result = Parser.OpenRead(Options.filenames[0]);
448 if ( ASDCP_SUCCESS(result) )
450 Parser.FillVideoDescriptor(VDesc);
452 if ( Options.verbose_flag )
454 fputs("MPEG-2 Pictures\n", stderr);
455 fputs("VideoDescriptor:\n", stderr);
456 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
457 MPEG2::VideoDescriptorDump(VDesc);
461 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
463 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
464 Kumu::GenRandomUUID(Info.AssetUUID);
466 if ( Options.use_smpte_labels )
468 Info.LabelSetType = LS_MXF_SMPTE;
469 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
472 // configure encryption
473 if( Options.key_flag )
475 Kumu::GenRandomUUID(Info.ContextID);
476 Info.EncryptedEssence = true;
478 if ( Options.key_id_flag )
479 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
481 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
483 Context = new AESEncContext;
484 result = Context->InitKey(Options.key_value);
486 if ( ASDCP_SUCCESS(result) )
487 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
489 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
491 Info.UsesHMAC = true;
492 HMAC = new HMACContext;
493 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
497 if ( ASDCP_SUCCESS(result) )
498 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
501 if ( ASDCP_SUCCESS(result) )
502 // loop through the frames
504 result = Parser.Reset();
507 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
509 if ( ! Options.do_repeat || duration == 1 )
511 result = Parser.ReadFrame(FrameBuffer);
513 if ( ASDCP_SUCCESS(result) )
515 if ( Options.verbose_flag )
516 FrameBuffer.Dump(stderr, Options.fb_dump_size);
518 if ( Options.encrypt_header_flag )
519 FrameBuffer.PlaintextOffset(0);
523 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
525 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
527 // The Writer class will forward the last block of ciphertext
528 // to the encryption context for use as the IV for the next
529 // frame. If you want to use non-sequitur IV values, un-comment
530 // the following line of code.
531 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
532 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
536 if ( result == RESULT_ENDOFFILE )
540 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
541 result = Writer.Finalize();
546 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
547 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
548 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
551 read_MPEG2_file(CommandOptions& Options)
553 AESDecContext* Context = 0;
554 HMACContext* HMAC = 0;
555 MPEG2::MXFReader Reader;
556 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
557 Kumu::FileWriter OutFile;
558 ui32_t frame_count = 0;
560 Result_t result = Reader.OpenRead(Options.filenames[0]);
562 if ( ASDCP_SUCCESS(result) )
564 MPEG2::VideoDescriptor VDesc;
565 Reader.FillVideoDescriptor(VDesc);
566 frame_count = VDesc.ContainerDuration;
568 if ( Options.verbose_flag )
570 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
571 MPEG2::VideoDescriptorDump(VDesc);
575 if ( ASDCP_SUCCESS(result) )
578 snprintf(filename, 256, "%s.ves", Options.file_root);
579 result = OutFile.OpenWrite(filename);
582 if ( ASDCP_SUCCESS(result) && Options.key_flag )
584 Context = new AESDecContext;
585 result = Context->InitKey(Options.key_value);
587 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
590 Reader.FillWriterInfo(Info);
594 HMAC = new HMACContext;
595 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
599 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
604 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
605 if ( last_frame > frame_count )
606 last_frame = frame_count;
608 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
610 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
612 if ( ASDCP_SUCCESS(result) )
614 if ( Options.verbose_flag )
615 FrameBuffer.Dump(stderr, Options.fb_dump_size);
617 ui32_t write_count = 0;
618 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
628 gop_start_test(CommandOptions& Options)
630 using namespace ASDCP::MPEG2;
633 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
634 ui32_t frame_count = 0;
636 Result_t result = Reader.OpenRead(Options.filenames[0]);
638 if ( ASDCP_SUCCESS(result) )
640 MPEG2::VideoDescriptor VDesc;
641 Reader.FillVideoDescriptor(VDesc);
642 frame_count = VDesc.ContainerDuration;
644 if ( Options.verbose_flag )
646 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
647 MPEG2::VideoDescriptorDump(VDesc);
651 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
652 if ( last_frame > frame_count )
653 last_frame = frame_count;
655 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
657 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
659 if ( ASDCP_SUCCESS(result) )
661 if ( Options.verbose_flag )
662 FrameBuffer.Dump(stderr, Options.fb_dump_size);
664 if ( FrameBuffer.FrameType() != FRAME_I )
665 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
667 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
674 //------------------------------------------------------------------------------------------
677 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
678 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
681 write_JP2K_file(CommandOptions& Options)
683 AESEncContext* Context = 0;
684 HMACContext* HMAC = 0;
685 JP2K::MXFWriter Writer;
686 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
687 JP2K::PictureDescriptor PDesc;
688 JP2K::SequenceParser Parser;
689 byte_t IV_buf[CBC_BLOCK_SIZE];
690 Kumu::FortunaRNG RNG;
692 // set up essence parser
693 Result_t result = Parser.OpenRead(Options.filenames[0]);
696 if ( ASDCP_SUCCESS(result) )
698 Parser.FillPictureDescriptor(PDesc);
699 PDesc.EditRate = Options.PictureRate();
701 if ( Options.verbose_flag )
703 fprintf(stderr, "JPEG 2000 pictures\n");
704 fputs("PictureDescriptor:\n", stderr);
705 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
706 JP2K::PictureDescriptorDump(PDesc);
710 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
712 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
713 Kumu::GenRandomUUID(Info.AssetUUID);
715 if ( Options.use_smpte_labels )
717 Info.LabelSetType = LS_MXF_SMPTE;
718 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
721 // configure encryption
722 if( Options.key_flag )
724 Kumu::GenRandomUUID(Info.ContextID);
725 Info.EncryptedEssence = true;
727 if ( Options.key_id_flag )
728 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
730 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
732 Context = new AESEncContext;
733 result = Context->InitKey(Options.key_value);
735 if ( ASDCP_SUCCESS(result) )
736 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
738 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
740 Info.UsesHMAC = true;
741 HMAC = new HMACContext;
742 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
746 if ( ASDCP_SUCCESS(result) )
747 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
750 if ( ASDCP_SUCCESS(result) )
753 result = Parser.Reset();
755 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
757 if ( ! Options.do_repeat || duration == 1 )
759 result = Parser.ReadFrame(FrameBuffer);
761 if ( ASDCP_SUCCESS(result) )
763 if ( Options.verbose_flag )
764 FrameBuffer.Dump(stderr, Options.fb_dump_size);
766 if ( Options.encrypt_header_flag )
767 FrameBuffer.PlaintextOffset(0);
771 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
773 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
775 // The Writer class will forward the last block of ciphertext
776 // to the encryption context for use as the IV for the next
777 // frame. If you want to use non-sequitur IV values, un-comment
778 // the following line of code.
779 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
780 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
784 if ( result == RESULT_ENDOFFILE )
788 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
789 result = Writer.Finalize();
794 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
795 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
796 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
799 read_JP2K_file(CommandOptions& Options)
801 AESDecContext* Context = 0;
802 HMACContext* HMAC = 0;
803 JP2K::MXFReader Reader;
804 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
805 ui32_t frame_count = 0;
807 Result_t result = Reader.OpenRead(Options.filenames[0]);
809 if ( ASDCP_SUCCESS(result) )
811 JP2K::PictureDescriptor PDesc;
812 Reader.FillPictureDescriptor(PDesc);
814 frame_count = PDesc.ContainerDuration;
816 if ( Options.verbose_flag )
818 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
819 JP2K::PictureDescriptorDump(PDesc);
823 if ( ASDCP_SUCCESS(result) && Options.key_flag )
825 Context = new AESDecContext;
826 result = Context->InitKey(Options.key_value);
828 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
831 Reader.FillWriterInfo(Info);
835 HMAC = new HMACContext;
836 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
840 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
845 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
846 if ( last_frame > frame_count )
847 last_frame = frame_count;
849 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
851 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
853 if ( ASDCP_SUCCESS(result) )
855 Kumu::FileWriter OutFile;
858 snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
859 result = OutFile.OpenWrite(filename);
861 if ( ASDCP_SUCCESS(result) )
862 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
864 if ( Options.verbose_flag )
865 FrameBuffer.Dump(stderr, Options.fb_dump_size);
872 //------------------------------------------------------------------------------------------
876 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
877 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
880 write_PCM_file(CommandOptions& Options)
882 AESEncContext* Context = 0;
883 HMACContext* HMAC = 0;
884 PCMParserList Parser;
885 PCM::MXFWriter Writer;
886 PCM::FrameBuffer FrameBuffer;
887 PCM::AudioDescriptor ADesc;
888 Rational PictureRate = Options.PictureRate();
889 byte_t IV_buf[CBC_BLOCK_SIZE];
890 Kumu::FortunaRNG RNG;
892 // set up essence parser
893 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
896 if ( ASDCP_SUCCESS(result) )
898 Parser.FillAudioDescriptor(ADesc);
900 ADesc.SampleRate = PictureRate;
901 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
903 if ( Options.verbose_flag )
905 fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
906 Options.szPictureRate(),
907 PCM::CalcSamplesPerFrame(ADesc));
908 fputs("AudioDescriptor:\n", stderr);
909 PCM::AudioDescriptorDump(ADesc);
913 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
915 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
916 Kumu::GenRandomUUID(Info.AssetUUID);
918 if ( Options.use_smpte_labels )
920 Info.LabelSetType = LS_MXF_SMPTE;
921 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
924 // configure encryption
925 if( Options.key_flag )
927 Kumu::GenRandomUUID(Info.ContextID);
928 Info.EncryptedEssence = true;
930 if ( Options.key_id_flag )
931 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
933 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
935 Context = new AESEncContext;
936 result = Context->InitKey(Options.key_value);
938 if ( ASDCP_SUCCESS(result) )
939 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
941 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
943 Info.UsesHMAC = true;
944 HMAC = new HMACContext;
945 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
949 if ( ASDCP_SUCCESS(result) )
950 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
953 if ( ASDCP_SUCCESS(result) )
955 result = Parser.Reset();
958 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
960 result = Parser.ReadFrame(FrameBuffer);
962 if ( ASDCP_SUCCESS(result) )
964 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
966 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
967 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
968 result = RESULT_ENDOFFILE;
972 if ( Options.verbose_flag )
973 FrameBuffer.Dump(stderr, Options.fb_dump_size);
975 if ( ! Options.no_write_flag )
977 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
979 // The Writer class will forward the last block of ciphertext
980 // to the encryption context for use as the IV for the next
981 // frame. If you want to use non-sequitur IV values, un-comment
982 // the following line of code.
983 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
984 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
989 if ( result == RESULT_ENDOFFILE )
993 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
994 result = Writer.Finalize();
999 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1000 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1001 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1004 read_PCM_file(CommandOptions& Options)
1006 AESDecContext* Context = 0;
1007 HMACContext* HMAC = 0;
1008 PCM::MXFReader Reader;
1009 PCM::FrameBuffer FrameBuffer;
1010 WavFileWriter OutWave;
1011 PCM::AudioDescriptor ADesc;
1012 ui32_t last_frame = 0;
1014 Result_t result = Reader.OpenRead(Options.filenames[0]);
1016 if ( ASDCP_SUCCESS(result) )
1018 Reader.FillAudioDescriptor(ADesc);
1020 if ( ADesc.SampleRate != EditRate_23_98
1021 && ADesc.SampleRate != EditRate_24
1022 && ADesc.SampleRate != EditRate_48 )
1023 ADesc.SampleRate = Options.PictureRate();
1025 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1027 if ( Options.verbose_flag )
1028 PCM::AudioDescriptorDump(ADesc);
1031 if ( ASDCP_SUCCESS(result) )
1033 last_frame = ADesc.ContainerDuration;
1035 if ( Options.duration > 0 && Options.duration < last_frame )
1036 last_frame = Options.duration;
1038 if ( Options.start_frame > 0 )
1040 if ( Options.start_frame > ADesc.ContainerDuration )
1042 fprintf(stderr, "Start value greater than file duration.\n");
1046 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1049 ADesc.ContainerDuration = last_frame - Options.start_frame;
1050 OutWave.OpenWrite(ADesc, Options.file_root, Options.split_wav);
1053 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1055 Context = new AESDecContext;
1056 result = Context->InitKey(Options.key_value);
1058 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1061 Reader.FillWriterInfo(Info);
1063 if ( Info.UsesHMAC )
1065 HMAC = new HMACContext;
1066 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1070 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1075 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1077 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1079 if ( ASDCP_SUCCESS(result) )
1081 if ( Options.verbose_flag )
1082 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1084 result = OutWave.WriteFrame(FrameBuffer);
1092 //------------------------------------------------------------------------------------------
1096 // These classes wrap the irregular names in the asdcplib API
1097 // so that I can use a template to simplify the implementation
1098 // of show_file_info()
1100 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1103 void FillDescriptor(MPEG2::MXFReader& Reader) {
1104 Reader.FillVideoDescriptor(*this);
1107 void Dump(FILE* stream) {
1108 MPEG2::VideoDescriptorDump(*this, stream);
1112 class MyPictureDescriptor : public JP2K::PictureDescriptor
1115 void FillDescriptor(JP2K::MXFReader& Reader) {
1116 Reader.FillPictureDescriptor(*this);
1119 void Dump(FILE* stream) {
1120 JP2K::PictureDescriptorDump(*this, stream);
1124 class MyAudioDescriptor : public PCM::AudioDescriptor
1127 void FillDescriptor(PCM::MXFReader& Reader) {
1128 Reader.FillAudioDescriptor(*this);
1131 void Dump(FILE* stream) {
1132 PCM::AudioDescriptorDump(*this, stream);
1137 // MSVC didn't like the function template, so now it's a static class method
1138 template<class ReaderT, class DescriptorT>
1139 class FileInfoWrapper
1142 static void file_info(CommandOptions& Options, FILE* stream = 0)
1147 if ( Options.verbose_flag || Options.showheader_flag )
1150 Result_t result = Reader.OpenRead(Options.filenames[0]);
1152 if ( ASDCP_SUCCESS(result) )
1154 if ( Options.showheader_flag )
1155 Reader.DumpHeaderMetadata(stream);
1158 Reader.FillWriterInfo(WI);
1159 WriterInfoDump(WI, stream);
1162 Desc.FillDescriptor(Reader);
1165 if ( Options.showindex_flag )
1166 Reader.DumpIndex(stream);
1168 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1170 Reader.DumpHeaderMetadata(stream);
1176 // Read header metadata from an ASDCP file
1179 show_file_info(CommandOptions& Options)
1181 EssenceType_t EssenceType;
1182 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1184 if ( ASDCP_FAILURE(result) )
1187 if ( EssenceType == ESS_MPEG2_VES )
1189 fputs("File essence type is MPEG2 video.\n", stdout);
1190 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1192 else if ( EssenceType == ESS_PCM_24b_48k )
1194 fputs("File essence type is PCM audio.\n", stdout);
1195 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1197 else if ( EssenceType == ESS_JPEG_2000 )
1199 fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1200 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1204 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1205 Kumu::FileReader Reader;
1206 MXF::OPAtomHeader TestHeader;
1208 result = Reader.OpenRead(Options.filenames[0]);
1210 if ( ASDCP_SUCCESS(result) )
1211 result = TestHeader.InitFromFile(Reader); // test UL and OP
1213 if ( ASDCP_SUCCESS(result) )
1215 TestHeader.Partition::Dump();
1217 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1220 fputs("File contains no Identification object.\n", stdout);
1222 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1225 fputs("File contains no SourcePackage object.\n", stdout);
1229 fputs("File is not MXF.\n", stdout);
1239 main(int argc, const char** argv)
1241 Result_t result = RESULT_OK;
1242 CommandOptions Options(argc, argv);
1244 if ( Options.version_flag )
1247 if ( Options.help_flag )
1250 if ( Options.version_flag || Options.help_flag )
1253 if ( Options.error_flag )
1255 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PACKAGE);
1259 if ( Options.info_flag )
1261 result = show_file_info(Options);
1263 else if ( Options.gop_start_flag )
1265 result = gop_start_test(Options);
1267 else if ( Options.genkey_flag )
1269 Kumu::FortunaRNG RNG;
1270 byte_t bin_buf[KeyLen];
1273 RNG.FillRandom(bin_buf, KeyLen);
1274 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
1276 else if ( Options.genid_flag )
1279 Kumu::GenRandomValue(TmpID);
1281 printf("%s\n", TmpID.EncodeHex(str_buf, 40));
1283 else if ( Options.extract_flag )
1285 EssenceType_t EssenceType;
1286 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1288 if ( ASDCP_SUCCESS(result) )
1290 switch ( EssenceType )
1293 result = read_MPEG2_file(Options);
1297 result = read_JP2K_file(Options);
1300 case ESS_PCM_24b_48k:
1301 result = read_PCM_file(Options);
1305 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1310 else if ( Options.create_flag )
1312 if ( Options.do_repeat && ! Options.duration_flag )
1314 fputs("Option -R requires -d <duration>\n", stderr);
1318 EssenceType_t EssenceType;
1319 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1321 if ( ASDCP_SUCCESS(result) )
1323 switch ( EssenceType )
1326 result = write_MPEG2_file(Options);
1330 result = write_JP2K_file(Options);
1333 case ESS_PCM_24b_48k:
1334 result = write_PCM_file(Options);
1338 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1339 Options.filenames[0]);
1345 if ( ASDCP_FAILURE(result) )
1347 fputs("Program stopped on error.\n", stderr);
1349 if ( result != RESULT_FAIL )
1351 fputs(result, stderr);
1352 fputc('\n', stderr);
1363 // end asdcp-test.cpp