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>
59 #include <openssl/sha.h>
61 using namespace ASDCP;
63 const ui32_t FRAME_BUFFER_SIZE = 4*1024*1024;
65 //------------------------------------------------------------------------------------------
67 // command line option parser class
69 static const char* PACKAGE = "asdcp-test"; // program name for messages
70 const ui32_t MAX_IN_FILES = 16; // maximum number of input files handled by
71 // the command option parser
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
79 static byte_t default_ProductUUID_Data[UUIDlen] =
80 { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
81 0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
83 memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84 CompanyName = "WidgetCo";
85 ProductName = "asdcp-test";
88 snprintf(s_buf, 128, "%u.%u.%u", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
89 ProductVersion = s_buf;
95 // Increment the iterator, test for an additional non-option command line argument.
96 // Causes the caller to return if there are no remaining arguments or if the next
97 // argument begins with '-'.
98 #define TEST_EXTRA_ARG(i,c) if ( ++i >= argc || argv[(i)][0] == '-' ) \
100 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
105 banner(FILE* stream = stdout)
108 %s (asdcplib %s)\n\n\
109 Copyright (c) 2003-2006 John Hurst\n\n\
110 asdcplib may be copied only under the terms of the license found at\n\
111 the top of every file in the asdcplib distribution kit.\n\n\
112 Specify the -h (help) option for further information about %s\n\n",
113 PACKAGE, ASDCP::Version(), PACKAGE);
118 usage(FILE* stream = stdout)
121 USAGE: %s -c <output-file> [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
122 [-f <start-frame>] [-j <key-id-string>] [-k <key-string>] [-L] [-M]\n\
123 [-p <frame-rate>] [-R] [-s <num>] [-v] [-W]\n\
124 <input-file> [<input-file2> ...]\n\
126 %s [-h|-help] [-V]\n\
128 %s -i [-H] [-n] [-v] <input-file>\n\
132 %s -G [-v] <input-file>\n\
134 %s -t <input-file>\n\
136 %s -x <file-prefix> [-b <buffer-size>] [-d <duration>]\n\
137 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S]\n\
138 [-v] [-W] <input-file>\n\
139 \n", PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE, PACKAGE);
143 -c <output-file> - Create AS-DCP track file from input(s)\n\
144 -g - Generate a random 16 byte value to stdout\n\
145 -G - Perform GOP start lookup test on MXF+Interop MPEG file\n\
146 -h | -help - Show help\n\
147 -i - Show file info\n\
148 -t - Calculate message digest of input file\n\
149 -u - Generate a random UUID value to stdout\n\
150 -V - Show version information\n\
151 -x <root-name> - Extract essence from AS-DCP file to named file(s)\n\
156 -e - Encrypt MPEG or JP2K headers (default)\n\
157 -E - Do not encrypt MPEG or JP2K headers\n\
158 -j <key-id-str> - Write key ID instead of creating a random value\n\
159 -k <key-string> - Use key for ciphertext operations\n\
160 -m - verify HMAC values when reading\n\
161 -M - Do not create HMAC values when writing\n\
165 Read/Write Options:\n\
166 -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
167 Defaults to 4,194,304 (4MB)\n\
168 -d <duration> - Number of frames to process, default all\n\
169 -f <start-frame> - Starting frame number, default 0\n\
170 -L - Write SMPTE UL values instead of MXF Interop\n\
171 -p <rate> - fps of picture when wrapping PCM or JP2K:\n\
172 Use one of [23|24|48], 24 is default\n\
173 -R - Repeat the first frame over the entire file (picture\n\
174 essence only, requires -c, -d)\n\
175 -S - Split Wave essence to stereo WAV files during extract.\n\
176 Default is multichannel WAV\n\
177 -W - Read input file only, do not write source file\n\
182 -H - Show MXF header metadata, used with option -i\n\
183 -n - Show index, used with option -i\n\
186 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
187 stderr, used with option -v\n\
188 -v - Verbose, prints informative messages to stderr\n\
190 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
191 o All option arguments must be separated from the option by whitespace.\n\
192 o An argument of \"23\" to the -p option will be interpreted\n\
193 as 23000/1001 fps.\n\
219 bool error_flag; // true if the given options are in error or not complete
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 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
266 write_hmac(true), read_hmac(false), split_wav(false),
267 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
268 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
269 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
270 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
272 memset(key_value, 0, KeyLen);
273 memset(key_id_value, 0, UUIDlen);
275 for ( int i = 1; i < argc; i++ )
278 if ( (strcmp( argv[i], "-help") == 0) )
284 if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
286 switch ( argv[i][1] )
288 case 'i': mode = MMT_INFO; break;
289 case 'G': mode = MMT_GOP_START; break;
290 case 'W': no_write_flag = true; break;
291 case 'n': showindex_flag = true; break;
292 case 'H': showheader_flag = true; break;
293 case 'R': do_repeat = true; break;
294 case 'S': split_wav = true; break;
295 case 'V': version_flag = true; break;
296 case 'h': help_flag = true; break;
297 case 'v': verbose_flag = true; break;
298 case 'g': mode = MMT_GEN_KEY; break;
299 case 'u': mode = MMT_GEN_ID; break;
300 case 'e': encrypt_header_flag = true; break;
301 case 'E': encrypt_header_flag = false; break;
302 case 'M': write_hmac = false; break;
303 case 'm': read_hmac = true; break;
304 case 'L': use_smpte_labels = true; break;
307 TEST_EXTRA_ARG(i, 'c');
313 TEST_EXTRA_ARG(i, 'x');
318 case 'k': key_flag = true;
319 TEST_EXTRA_ARG(i, 'k');
322 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
324 if ( length != KeyLen )
326 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", KeyLen, length);
332 case 'j': key_id_flag = true;
333 TEST_EXTRA_ARG(i, 'j');
336 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
338 if ( length != UUIDlen )
340 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", UUIDlen, length);
347 TEST_EXTRA_ARG(i, 'f');
348 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
352 TEST_EXTRA_ARG(i, 'd');
353 duration_flag = true;
354 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
358 TEST_EXTRA_ARG(i, 'p');
359 picture_rate = atoi(argv[i]);
363 TEST_EXTRA_ARG(i, 's');
364 fb_dump_size = atoi(argv[i]);
367 case 't': mode = MMT_DIGEST; break;
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 ( ( mode == MMT_INFO
408 || mode == MMT_CREATE
409 || mode == MMT_EXTRACT
410 || mode == MMT_GOP_START
411 || mode == MMT_DIGEST ) && file_count == 0 )
413 fputs("Option requires at least one filename argument.\n", stderr);
417 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
419 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
427 //------------------------------------------------------------------------------------------
430 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
431 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
434 write_MPEG2_file(CommandOptions& Options)
436 AESEncContext* Context = 0;
437 HMACContext* HMAC = 0;
438 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
439 MPEG2::Parser Parser;
440 MPEG2::MXFWriter Writer;
441 MPEG2::VideoDescriptor VDesc;
442 byte_t IV_buf[CBC_BLOCK_SIZE];
443 Kumu::FortunaRNG RNG;
445 // set up essence parser
446 Result_t result = Parser.OpenRead(Options.filenames[0]);
449 if ( ASDCP_SUCCESS(result) )
451 Parser.FillVideoDescriptor(VDesc);
453 if ( Options.verbose_flag )
455 fputs("MPEG-2 Pictures\n", stderr);
456 fputs("VideoDescriptor:\n", stderr);
457 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
458 MPEG2::VideoDescriptorDump(VDesc);
462 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
464 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
465 Kumu::GenRandomUUID(Info.AssetUUID);
467 if ( Options.use_smpte_labels )
469 Info.LabelSetType = LS_MXF_SMPTE;
470 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
473 // configure encryption
474 if( Options.key_flag )
476 Kumu::GenRandomUUID(Info.ContextID);
477 Info.EncryptedEssence = true;
479 if ( Options.key_id_flag )
480 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
482 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
484 Context = new AESEncContext;
485 result = Context->InitKey(Options.key_value);
487 if ( ASDCP_SUCCESS(result) )
488 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
490 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
492 Info.UsesHMAC = true;
493 HMAC = new HMACContext;
494 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
498 if ( ASDCP_SUCCESS(result) )
499 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
502 if ( ASDCP_SUCCESS(result) )
503 // loop through the frames
505 result = Parser.Reset();
508 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
510 if ( ! Options.do_repeat || duration == 1 )
512 result = Parser.ReadFrame(FrameBuffer);
514 if ( ASDCP_SUCCESS(result) )
516 if ( Options.verbose_flag )
517 FrameBuffer.Dump(stderr, Options.fb_dump_size);
519 if ( Options.encrypt_header_flag )
520 FrameBuffer.PlaintextOffset(0);
524 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
526 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
528 // The Writer class will forward the last block of ciphertext
529 // to the encryption context for use as the IV for the next
530 // frame. If you want to use non-sequitur IV values, un-comment
531 // the following line of code.
532 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
533 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
537 if ( result == RESULT_ENDOFFILE )
541 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
542 result = Writer.Finalize();
547 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
548 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
549 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
552 read_MPEG2_file(CommandOptions& Options)
554 AESDecContext* Context = 0;
555 HMACContext* HMAC = 0;
556 MPEG2::MXFReader Reader;
557 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
558 Kumu::FileWriter OutFile;
559 ui32_t frame_count = 0;
561 Result_t result = Reader.OpenRead(Options.filenames[0]);
563 if ( ASDCP_SUCCESS(result) )
565 MPEG2::VideoDescriptor VDesc;
566 Reader.FillVideoDescriptor(VDesc);
567 frame_count = VDesc.ContainerDuration;
569 if ( Options.verbose_flag )
571 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
572 MPEG2::VideoDescriptorDump(VDesc);
576 if ( ASDCP_SUCCESS(result) )
579 snprintf(filename, 256, "%s.ves", Options.file_root);
580 result = OutFile.OpenWrite(filename);
583 if ( ASDCP_SUCCESS(result) && Options.key_flag )
585 Context = new AESDecContext;
586 result = Context->InitKey(Options.key_value);
588 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
591 Reader.FillWriterInfo(Info);
595 HMAC = new HMACContext;
596 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
600 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
605 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
606 if ( last_frame > frame_count )
607 last_frame = frame_count;
609 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
611 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
613 if ( ASDCP_SUCCESS(result) )
615 if ( Options.verbose_flag )
616 FrameBuffer.Dump(stderr, Options.fb_dump_size);
618 ui32_t write_count = 0;
619 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
629 gop_start_test(CommandOptions& Options)
631 using namespace ASDCP::MPEG2;
634 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
635 ui32_t frame_count = 0;
637 Result_t result = Reader.OpenRead(Options.filenames[0]);
639 if ( ASDCP_SUCCESS(result) )
641 MPEG2::VideoDescriptor VDesc;
642 Reader.FillVideoDescriptor(VDesc);
643 frame_count = VDesc.ContainerDuration;
645 if ( Options.verbose_flag )
647 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
648 MPEG2::VideoDescriptorDump(VDesc);
652 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
653 if ( last_frame > frame_count )
654 last_frame = frame_count;
656 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
658 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
660 if ( ASDCP_SUCCESS(result) )
662 if ( Options.verbose_flag )
663 FrameBuffer.Dump(stderr, Options.fb_dump_size);
665 if ( FrameBuffer.FrameType() != FRAME_I )
666 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
668 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
675 //------------------------------------------------------------------------------------------
678 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
679 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
682 write_JP2K_file(CommandOptions& Options)
684 AESEncContext* Context = 0;
685 HMACContext* HMAC = 0;
686 JP2K::MXFWriter Writer;
687 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
688 JP2K::PictureDescriptor PDesc;
689 JP2K::SequenceParser Parser;
690 byte_t IV_buf[CBC_BLOCK_SIZE];
691 Kumu::FortunaRNG RNG;
693 // set up essence parser
694 Result_t result = Parser.OpenRead(Options.filenames[0]);
697 if ( ASDCP_SUCCESS(result) )
699 Parser.FillPictureDescriptor(PDesc);
700 PDesc.EditRate = Options.PictureRate();
702 if ( Options.verbose_flag )
704 fprintf(stderr, "JPEG 2000 pictures\n");
705 fputs("PictureDescriptor:\n", stderr);
706 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
707 JP2K::PictureDescriptorDump(PDesc);
711 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
713 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
714 Kumu::GenRandomUUID(Info.AssetUUID);
716 if ( Options.use_smpte_labels )
718 Info.LabelSetType = LS_MXF_SMPTE;
719 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
722 // configure encryption
723 if( Options.key_flag )
725 Kumu::GenRandomUUID(Info.ContextID);
726 Info.EncryptedEssence = true;
728 if ( Options.key_id_flag )
729 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
731 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
733 Context = new AESEncContext;
734 result = Context->InitKey(Options.key_value);
736 if ( ASDCP_SUCCESS(result) )
737 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
739 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
741 Info.UsesHMAC = true;
742 HMAC = new HMACContext;
743 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
747 if ( ASDCP_SUCCESS(result) )
748 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
751 if ( ASDCP_SUCCESS(result) )
754 result = Parser.Reset();
756 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
758 if ( ! Options.do_repeat || duration == 1 )
760 result = Parser.ReadFrame(FrameBuffer);
762 if ( ASDCP_SUCCESS(result) )
764 if ( Options.verbose_flag )
765 FrameBuffer.Dump(stderr, Options.fb_dump_size);
767 if ( Options.encrypt_header_flag )
768 FrameBuffer.PlaintextOffset(0);
772 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
774 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
776 // The Writer class will forward the last block of ciphertext
777 // to the encryption context for use as the IV for the next
778 // frame. If you want to use non-sequitur IV values, un-comment
779 // the following line of code.
780 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
781 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
785 if ( result == RESULT_ENDOFFILE )
789 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
790 result = Writer.Finalize();
795 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
796 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
797 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
800 read_JP2K_file(CommandOptions& Options)
802 AESDecContext* Context = 0;
803 HMACContext* HMAC = 0;
804 JP2K::MXFReader Reader;
805 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
806 ui32_t frame_count = 0;
808 Result_t result = Reader.OpenRead(Options.filenames[0]);
810 if ( ASDCP_SUCCESS(result) )
812 JP2K::PictureDescriptor PDesc;
813 Reader.FillPictureDescriptor(PDesc);
815 frame_count = PDesc.ContainerDuration;
817 if ( Options.verbose_flag )
819 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
820 JP2K::PictureDescriptorDump(PDesc);
824 if ( ASDCP_SUCCESS(result) && Options.key_flag )
826 Context = new AESDecContext;
827 result = Context->InitKey(Options.key_value);
829 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
832 Reader.FillWriterInfo(Info);
836 HMAC = new HMACContext;
837 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
841 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
846 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
847 if ( last_frame > frame_count )
848 last_frame = frame_count;
850 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
852 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
854 if ( ASDCP_SUCCESS(result) )
856 Kumu::FileWriter OutFile;
859 snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
860 result = OutFile.OpenWrite(filename);
862 if ( ASDCP_SUCCESS(result) )
863 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
865 if ( Options.verbose_flag )
866 FrameBuffer.Dump(stderr, Options.fb_dump_size);
873 //------------------------------------------------------------------------------------------
877 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
878 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
881 write_PCM_file(CommandOptions& Options)
883 AESEncContext* Context = 0;
884 HMACContext* HMAC = 0;
885 PCMParserList Parser;
886 PCM::MXFWriter Writer;
887 PCM::FrameBuffer FrameBuffer;
888 PCM::AudioDescriptor ADesc;
889 Rational PictureRate = Options.PictureRate();
890 byte_t IV_buf[CBC_BLOCK_SIZE];
891 Kumu::FortunaRNG RNG;
893 // set up essence parser
894 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
897 if ( ASDCP_SUCCESS(result) )
899 Parser.FillAudioDescriptor(ADesc);
901 ADesc.SampleRate = PictureRate;
902 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
904 if ( Options.verbose_flag )
906 fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
907 Options.szPictureRate(),
908 PCM::CalcSamplesPerFrame(ADesc));
909 fputs("AudioDescriptor:\n", stderr);
910 PCM::AudioDescriptorDump(ADesc);
914 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
916 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
917 Kumu::GenRandomUUID(Info.AssetUUID);
919 if ( Options.use_smpte_labels )
921 Info.LabelSetType = LS_MXF_SMPTE;
922 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
925 // configure encryption
926 if( Options.key_flag )
928 Kumu::GenRandomUUID(Info.ContextID);
929 Info.EncryptedEssence = true;
931 if ( Options.key_id_flag )
932 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
934 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
936 Context = new AESEncContext;
937 result = Context->InitKey(Options.key_value);
939 if ( ASDCP_SUCCESS(result) )
940 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
942 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
944 Info.UsesHMAC = true;
945 HMAC = new HMACContext;
946 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
950 if ( ASDCP_SUCCESS(result) )
951 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
954 if ( ASDCP_SUCCESS(result) )
956 result = Parser.Reset();
959 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
961 result = Parser.ReadFrame(FrameBuffer);
963 if ( ASDCP_SUCCESS(result) )
965 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
967 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
968 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
969 result = RESULT_ENDOFFILE;
973 if ( Options.verbose_flag )
974 FrameBuffer.Dump(stderr, Options.fb_dump_size);
976 if ( ! Options.no_write_flag )
978 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
980 // The Writer class will forward the last block of ciphertext
981 // to the encryption context for use as the IV for the next
982 // frame. If you want to use non-sequitur IV values, un-comment
983 // the following line of code.
984 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
985 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
990 if ( result == RESULT_ENDOFFILE )
994 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
995 result = Writer.Finalize();
1000 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1001 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1002 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1005 read_PCM_file(CommandOptions& Options)
1007 AESDecContext* Context = 0;
1008 HMACContext* HMAC = 0;
1009 PCM::MXFReader Reader;
1010 PCM::FrameBuffer FrameBuffer;
1011 WavFileWriter OutWave;
1012 PCM::AudioDescriptor ADesc;
1013 ui32_t last_frame = 0;
1015 Result_t result = Reader.OpenRead(Options.filenames[0]);
1017 if ( ASDCP_SUCCESS(result) )
1019 Reader.FillAudioDescriptor(ADesc);
1021 if ( ADesc.SampleRate != EditRate_23_98
1022 && ADesc.SampleRate != EditRate_24
1023 && ADesc.SampleRate != EditRate_48 )
1024 ADesc.SampleRate = Options.PictureRate();
1026 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1028 if ( Options.verbose_flag )
1029 PCM::AudioDescriptorDump(ADesc);
1032 if ( ASDCP_SUCCESS(result) )
1034 last_frame = ADesc.ContainerDuration;
1036 if ( Options.duration > 0 && Options.duration < last_frame )
1037 last_frame = Options.duration;
1039 if ( Options.start_frame > 0 )
1041 if ( Options.start_frame > ADesc.ContainerDuration )
1043 fprintf(stderr, "Start value greater than file duration.\n");
1047 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1050 ADesc.ContainerDuration = last_frame - Options.start_frame;
1051 OutWave.OpenWrite(ADesc, Options.file_root, Options.split_wav);
1054 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1056 Context = new AESDecContext;
1057 result = Context->InitKey(Options.key_value);
1059 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1062 Reader.FillWriterInfo(Info);
1064 if ( Info.UsesHMAC )
1066 HMAC = new HMACContext;
1067 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1071 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1076 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1078 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1080 if ( ASDCP_SUCCESS(result) )
1082 if ( Options.verbose_flag )
1083 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1085 result = OutWave.WriteFrame(FrameBuffer);
1093 //------------------------------------------------------------------------------------------
1097 // These classes wrap the irregular names in the asdcplib API
1098 // so that I can use a template to simplify the implementation
1099 // of show_file_info()
1101 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1104 void FillDescriptor(MPEG2::MXFReader& Reader) {
1105 Reader.FillVideoDescriptor(*this);
1108 void Dump(FILE* stream) {
1109 MPEG2::VideoDescriptorDump(*this, stream);
1113 class MyPictureDescriptor : public JP2K::PictureDescriptor
1116 void FillDescriptor(JP2K::MXFReader& Reader) {
1117 Reader.FillPictureDescriptor(*this);
1120 void Dump(FILE* stream) {
1121 JP2K::PictureDescriptorDump(*this, stream);
1125 class MyAudioDescriptor : public PCM::AudioDescriptor
1128 void FillDescriptor(PCM::MXFReader& Reader) {
1129 Reader.FillAudioDescriptor(*this);
1132 void Dump(FILE* stream) {
1133 PCM::AudioDescriptorDump(*this, stream);
1138 // MSVC didn't like the function template, so now it's a static class method
1139 template<class ReaderT, class DescriptorT>
1140 class FileInfoWrapper
1143 static void file_info(CommandOptions& Options, FILE* stream = 0)
1148 if ( Options.verbose_flag || Options.showheader_flag )
1151 Result_t result = Reader.OpenRead(Options.filenames[0]);
1153 if ( ASDCP_SUCCESS(result) )
1155 if ( Options.showheader_flag )
1156 Reader.DumpHeaderMetadata(stream);
1159 Reader.FillWriterInfo(WI);
1160 WriterInfoDump(WI, stream);
1163 Desc.FillDescriptor(Reader);
1166 if ( Options.showindex_flag )
1167 Reader.DumpIndex(stream);
1169 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1171 Reader.DumpHeaderMetadata(stream);
1177 // Read header metadata from an ASDCP file
1180 show_file_info(CommandOptions& Options)
1182 EssenceType_t EssenceType;
1183 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1185 if ( ASDCP_FAILURE(result) )
1188 if ( EssenceType == ESS_MPEG2_VES )
1190 fputs("File essence type is MPEG2 video.\n", stdout);
1191 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1193 else if ( EssenceType == ESS_PCM_24b_48k )
1195 fputs("File essence type is PCM audio.\n", stdout);
1196 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1198 else if ( EssenceType == ESS_JPEG_2000 )
1200 fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1201 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1205 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1206 Kumu::FileReader Reader;
1207 MXF::OPAtomHeader TestHeader;
1209 result = Reader.OpenRead(Options.filenames[0]);
1211 if ( ASDCP_SUCCESS(result) )
1212 result = TestHeader.InitFromFile(Reader); // test UL and OP
1214 if ( ASDCP_SUCCESS(result) )
1216 TestHeader.Partition::Dump();
1218 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1221 fputs("File contains no Identification object.\n", stdout);
1223 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1226 fputs("File contains no SourcePackage object.\n", stdout);
1230 fputs("File is not MXF.\n", stdout);
1240 digest_file(const char* filename)
1242 using namespace Kumu;
1244 ASDCP_TEST_NULL_STR(filename);
1248 ByteString Buf(8192);
1250 Result_t result = Reader.OpenRead(filename);
1252 while ( ASDCP_SUCCESS(result) )
1254 ui32_t read_count = 0;
1255 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1257 if ( result == RESULT_ENDOFFILE )
1263 if ( ASDCP_SUCCESS(result) )
1264 SHA1_Update(&Ctx, Buf.Data(), read_count);
1267 if ( ASDCP_SUCCESS(result) )
1269 const ui32_t sha_len = 20;
1270 byte_t bin_buf[sha_len];
1272 SHA1_Final(bin_buf, &Ctx);
1274 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1282 main(int argc, const char** argv)
1284 Result_t result = RESULT_OK;
1285 CommandOptions Options(argc, argv);
1287 if ( Options.version_flag )
1290 if ( Options.help_flag )
1293 if ( Options.version_flag || Options.help_flag )
1296 if ( Options.error_flag )
1298 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PACKAGE);
1302 if ( Options.mode == MMT_INFO )
1304 result = show_file_info(Options);
1306 else if ( Options.mode == MMT_GOP_START )
1308 result = gop_start_test(Options);
1310 else if ( Options.mode == MMT_GEN_KEY )
1312 Kumu::FortunaRNG RNG;
1313 byte_t bin_buf[KeyLen];
1316 RNG.FillRandom(bin_buf, KeyLen);
1317 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
1319 else if ( Options.mode == MMT_GEN_ID )
1322 Kumu::GenRandomValue(TmpID);
1324 printf("%s\n", TmpID.EncodeHex(str_buf, 40));
1326 else if ( Options.mode == MMT_DIGEST )
1328 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1329 result = digest_file(Options.filenames[i]);
1331 else if ( Options.mode == MMT_EXTRACT )
1333 EssenceType_t EssenceType;
1334 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1336 if ( ASDCP_SUCCESS(result) )
1338 switch ( EssenceType )
1341 result = read_MPEG2_file(Options);
1345 result = read_JP2K_file(Options);
1348 case ESS_PCM_24b_48k:
1349 result = read_PCM_file(Options);
1353 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1358 else if ( Options.mode == MMT_CREATE )
1360 if ( Options.do_repeat && ! Options.duration_flag )
1362 fputs("Option -R requires -d <duration>\n", stderr);
1366 EssenceType_t EssenceType;
1367 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1369 if ( ASDCP_SUCCESS(result) )
1371 switch ( EssenceType )
1374 result = write_MPEG2_file(Options);
1378 result = write_JP2K_file(Options);
1381 case ESS_PCM_24b_48k:
1382 result = write_PCM_file(Options);
1386 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1387 Options.filenames[0]);
1393 if ( ASDCP_FAILURE(result) )
1395 fputs("Program stopped on error.\n", stderr);
1397 if ( result != RESULT_FAIL )
1399 fputs(result, stderr);
1400 fputc('\n', stderr);
1411 // end asdcp-test.cpp