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|-1]\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 -1 - Split Wave essence to mono WAV files during extract.\n\
178 Default is multichannel WAV\n\
179 -W - Read input file only, do not write source file\n\
184 -H - Show MXF header metadata, used with option -i\n\
185 -n - Show index, used with option -i\n\
188 -s <num> - Number of bytes of frame buffer to be dumped as hex to\n\
189 stderr, used with option -v\n\
190 -v - Verbose, prints informative messages to stderr\n\
192 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
193 o All option arguments must be separated from the option by whitespace.\n\
194 o An argument of \"23\" to the -p option will be interpreted\n\
195 as 23000/1001 fps.\n\
221 bool error_flag; // true if the given options are in error or not complete
222 bool key_flag; // true if an encryption key was given
223 bool key_id_flag; // true if a key ID was given
224 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
225 bool write_hmac; // true if HMAC values are to be generated and written
226 bool read_hmac; // true if HMAC values are to be validated
227 bool split_wav; // true if PCM is to be extracted to stereo WAV files
228 bool mono_wav; // true if PCM is to be extracted to mono WAV files
229 bool verbose_flag; // true if the verbose option was selected
230 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
231 bool showindex_flag; // true if index is to be displayed
232 bool showheader_flag; // true if MXF file header is to be displayed
233 bool no_write_flag; // true if no output files are to be written
234 bool version_flag; // true if the version display option was selected
235 bool help_flag; // true if the help display option was selected
236 ui32_t start_frame; // frame number to begin processing
237 ui32_t duration; // number of frames to be processed
238 bool duration_flag; // true if duration argument given
239 bool do_repeat; // if true and -c -d, repeat first input frame
240 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
241 ui32_t picture_rate; // fps of picture when wrapping PCM
242 ui32_t fb_size; // size of picture frame buffer
243 ui32_t file_count; // number of elements in filenames[]
244 const char* file_root; // filename pre for files written by the extract mode
245 const char* out_file; // name of mxf file created by create mode
246 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
247 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
248 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
251 Rational PictureRate()
253 if ( picture_rate == 23 ) return EditRate_23_98;
254 if ( picture_rate == 48 ) return EditRate_48;
259 const char* szPictureRate()
261 if ( picture_rate == 23 ) return "23.976";
262 if ( picture_rate == 48 ) return "48";
267 CommandOptions(int argc, const char** argv) :
268 mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
269 write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
270 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
271 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
272 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
273 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
275 memset(key_value, 0, KeyLen);
276 memset(key_id_value, 0, UUIDlen);
278 for ( int i = 1; i < argc; i++ )
281 if ( (strcmp( argv[i], "-help") == 0) )
287 if ( argv[i][0] == '-'
288 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
291 switch ( argv[i][1] )
293 case '1': mono_wav = true; break;
294 case '2': split_wav = true; break;
295 case 'i': mode = MMT_INFO; break;
296 case 'G': mode = MMT_GOP_START; break;
297 case 'W': no_write_flag = true; break;
298 case 'n': showindex_flag = true; break;
299 case 'H': showheader_flag = true; break;
300 case 'R': do_repeat = true; break;
301 case 'S': split_wav = true; break;
302 case 'V': version_flag = true; break;
303 case 'h': help_flag = true; break;
304 case 'v': verbose_flag = true; break;
305 case 'g': mode = MMT_GEN_KEY; break;
306 case 'u': mode = MMT_GEN_ID; break;
307 case 'e': encrypt_header_flag = true; break;
308 case 'E': encrypt_header_flag = false; break;
309 case 'M': write_hmac = false; break;
310 case 'm': read_hmac = true; break;
311 case 'L': use_smpte_labels = true; break;
314 TEST_EXTRA_ARG(i, 'c');
320 TEST_EXTRA_ARG(i, 'x');
325 case 'k': key_flag = true;
326 TEST_EXTRA_ARG(i, 'k');
329 Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
331 if ( length != KeyLen )
333 fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", KeyLen, length);
339 case 'j': key_id_flag = true;
340 TEST_EXTRA_ARG(i, 'j');
343 Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
345 if ( length != UUIDlen )
347 fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", UUIDlen, length);
354 TEST_EXTRA_ARG(i, 'f');
355 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
359 TEST_EXTRA_ARG(i, 'd');
360 duration_flag = true;
361 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
365 TEST_EXTRA_ARG(i, 'p');
366 picture_rate = atoi(argv[i]);
370 TEST_EXTRA_ARG(i, 's');
371 fb_dump_size = atoi(argv[i]);
374 case 't': mode = MMT_DIGEST; break;
377 TEST_EXTRA_ARG(i, 'b');
378 fb_size = atoi(argv[i]);
381 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
386 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
393 if ( argv[i][0] != '-' )
395 filenames[file_count++] = argv[i];
399 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
403 if ( file_count >= MAX_IN_FILES )
405 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
411 if ( help_flag || version_flag )
414 if ( ( mode == MMT_INFO
415 || mode == MMT_CREATE
416 || mode == MMT_EXTRACT
417 || mode == MMT_GOP_START
418 || mode == MMT_DIGEST ) && file_count == 0 )
420 fputs("Option requires at least one filename argument.\n", stderr);
424 if ( mode == MMT_NONE && ! help_flag && ! version_flag )
426 fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
434 //------------------------------------------------------------------------------------------
437 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
438 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
441 write_MPEG2_file(CommandOptions& Options)
443 AESEncContext* Context = 0;
444 HMACContext* HMAC = 0;
445 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
446 MPEG2::Parser Parser;
447 MPEG2::MXFWriter Writer;
448 MPEG2::VideoDescriptor VDesc;
449 byte_t IV_buf[CBC_BLOCK_SIZE];
450 Kumu::FortunaRNG RNG;
452 // set up essence parser
453 Result_t result = Parser.OpenRead(Options.filenames[0]);
456 if ( ASDCP_SUCCESS(result) )
458 Parser.FillVideoDescriptor(VDesc);
460 if ( Options.verbose_flag )
462 fputs("MPEG-2 Pictures\n", stderr);
463 fputs("VideoDescriptor:\n", stderr);
464 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
465 MPEG2::VideoDescriptorDump(VDesc);
469 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
471 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
472 Kumu::GenRandomUUID(Info.AssetUUID);
474 if ( Options.use_smpte_labels )
476 Info.LabelSetType = LS_MXF_SMPTE;
477 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
480 // configure encryption
481 if( Options.key_flag )
483 Kumu::GenRandomUUID(Info.ContextID);
484 Info.EncryptedEssence = true;
486 if ( Options.key_id_flag )
487 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
489 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
491 Context = new AESEncContext;
492 result = Context->InitKey(Options.key_value);
494 if ( ASDCP_SUCCESS(result) )
495 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
497 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
499 Info.UsesHMAC = true;
500 HMAC = new HMACContext;
501 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
505 if ( ASDCP_SUCCESS(result) )
506 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
509 if ( ASDCP_SUCCESS(result) )
510 // loop through the frames
512 result = Parser.Reset();
515 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
517 if ( ! Options.do_repeat || duration == 1 )
519 result = Parser.ReadFrame(FrameBuffer);
521 if ( ASDCP_SUCCESS(result) )
523 if ( Options.verbose_flag )
524 FrameBuffer.Dump(stderr, Options.fb_dump_size);
526 if ( Options.encrypt_header_flag )
527 FrameBuffer.PlaintextOffset(0);
531 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
533 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
535 // The Writer class will forward the last block of ciphertext
536 // to the encryption context for use as the IV for the next
537 // frame. If you want to use non-sequitur IV values, un-comment
538 // the following line of code.
539 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
540 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
544 if ( result == RESULT_ENDOFFILE )
548 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
549 result = Writer.Finalize();
554 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
555 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
556 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
559 read_MPEG2_file(CommandOptions& Options)
561 AESDecContext* Context = 0;
562 HMACContext* HMAC = 0;
563 MPEG2::MXFReader Reader;
564 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
565 Kumu::FileWriter OutFile;
566 ui32_t frame_count = 0;
568 Result_t result = Reader.OpenRead(Options.filenames[0]);
570 if ( ASDCP_SUCCESS(result) )
572 MPEG2::VideoDescriptor VDesc;
573 Reader.FillVideoDescriptor(VDesc);
574 frame_count = VDesc.ContainerDuration;
576 if ( Options.verbose_flag )
578 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
579 MPEG2::VideoDescriptorDump(VDesc);
583 if ( ASDCP_SUCCESS(result) )
586 snprintf(filename, 256, "%s.ves", Options.file_root);
587 result = OutFile.OpenWrite(filename);
590 if ( ASDCP_SUCCESS(result) && Options.key_flag )
592 Context = new AESDecContext;
593 result = Context->InitKey(Options.key_value);
595 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
598 Reader.FillWriterInfo(Info);
602 HMAC = new HMACContext;
603 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
607 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
612 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
613 if ( last_frame > frame_count )
614 last_frame = frame_count;
616 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
618 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
620 if ( ASDCP_SUCCESS(result) )
622 if ( Options.verbose_flag )
623 FrameBuffer.Dump(stderr, Options.fb_dump_size);
625 ui32_t write_count = 0;
626 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
636 gop_start_test(CommandOptions& Options)
638 using namespace ASDCP::MPEG2;
641 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
642 ui32_t frame_count = 0;
644 Result_t result = Reader.OpenRead(Options.filenames[0]);
646 if ( ASDCP_SUCCESS(result) )
648 MPEG2::VideoDescriptor VDesc;
649 Reader.FillVideoDescriptor(VDesc);
650 frame_count = VDesc.ContainerDuration;
652 if ( Options.verbose_flag )
654 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
655 MPEG2::VideoDescriptorDump(VDesc);
659 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
660 if ( last_frame > frame_count )
661 last_frame = frame_count;
663 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
665 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
667 if ( ASDCP_SUCCESS(result) )
669 if ( Options.verbose_flag )
670 FrameBuffer.Dump(stderr, Options.fb_dump_size);
672 if ( FrameBuffer.FrameType() != FRAME_I )
673 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
675 fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
682 //------------------------------------------------------------------------------------------
685 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
686 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
689 write_JP2K_file(CommandOptions& Options)
691 AESEncContext* Context = 0;
692 HMACContext* HMAC = 0;
693 JP2K::MXFWriter Writer;
694 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
695 JP2K::PictureDescriptor PDesc;
696 JP2K::SequenceParser Parser;
697 byte_t IV_buf[CBC_BLOCK_SIZE];
698 Kumu::FortunaRNG RNG;
700 // set up essence parser
701 Result_t result = Parser.OpenRead(Options.filenames[0]);
704 if ( ASDCP_SUCCESS(result) )
706 Parser.FillPictureDescriptor(PDesc);
707 PDesc.EditRate = Options.PictureRate();
709 if ( Options.verbose_flag )
711 fprintf(stderr, "JPEG 2000 pictures\n");
712 fputs("PictureDescriptor:\n", stderr);
713 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
714 JP2K::PictureDescriptorDump(PDesc);
718 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
720 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
721 Kumu::GenRandomUUID(Info.AssetUUID);
723 if ( Options.use_smpte_labels )
725 Info.LabelSetType = LS_MXF_SMPTE;
726 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
729 // configure encryption
730 if( Options.key_flag )
732 Kumu::GenRandomUUID(Info.ContextID);
733 Info.EncryptedEssence = true;
735 if ( Options.key_id_flag )
736 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
738 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
740 Context = new AESEncContext;
741 result = Context->InitKey(Options.key_value);
743 if ( ASDCP_SUCCESS(result) )
744 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
746 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
748 Info.UsesHMAC = true;
749 HMAC = new HMACContext;
750 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
754 if ( ASDCP_SUCCESS(result) )
755 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
758 if ( ASDCP_SUCCESS(result) )
761 result = Parser.Reset();
763 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
765 if ( ! Options.do_repeat || duration == 1 )
767 result = Parser.ReadFrame(FrameBuffer);
769 if ( ASDCP_SUCCESS(result) )
771 if ( Options.verbose_flag )
772 FrameBuffer.Dump(stderr, Options.fb_dump_size);
774 if ( Options.encrypt_header_flag )
775 FrameBuffer.PlaintextOffset(0);
779 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
781 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
783 // The Writer class will forward the last block of ciphertext
784 // to the encryption context for use as the IV for the next
785 // frame. If you want to use non-sequitur IV values, un-comment
786 // the following line of code.
787 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
788 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
792 if ( result == RESULT_ENDOFFILE )
796 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
797 result = Writer.Finalize();
802 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
803 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
804 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
807 read_JP2K_file(CommandOptions& Options)
809 AESDecContext* Context = 0;
810 HMACContext* HMAC = 0;
811 JP2K::MXFReader Reader;
812 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
813 ui32_t frame_count = 0;
815 Result_t result = Reader.OpenRead(Options.filenames[0]);
817 if ( ASDCP_SUCCESS(result) )
819 JP2K::PictureDescriptor PDesc;
820 Reader.FillPictureDescriptor(PDesc);
822 frame_count = PDesc.ContainerDuration;
824 if ( Options.verbose_flag )
826 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
827 JP2K::PictureDescriptorDump(PDesc);
831 if ( ASDCP_SUCCESS(result) && Options.key_flag )
833 Context = new AESDecContext;
834 result = Context->InitKey(Options.key_value);
836 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
839 Reader.FillWriterInfo(Info);
843 HMAC = new HMACContext;
844 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
848 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
853 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
854 if ( last_frame > frame_count )
855 last_frame = frame_count;
857 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
859 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
861 if ( ASDCP_SUCCESS(result) )
863 Kumu::FileWriter OutFile;
866 snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
867 result = OutFile.OpenWrite(filename);
869 if ( ASDCP_SUCCESS(result) )
870 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
872 if ( Options.verbose_flag )
873 FrameBuffer.Dump(stderr, Options.fb_dump_size);
880 //------------------------------------------------------------------------------------------
884 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
885 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
888 write_PCM_file(CommandOptions& Options)
890 AESEncContext* Context = 0;
891 HMACContext* HMAC = 0;
892 PCMParserList Parser;
893 PCM::MXFWriter Writer;
894 PCM::FrameBuffer FrameBuffer;
895 PCM::AudioDescriptor ADesc;
896 Rational PictureRate = Options.PictureRate();
897 byte_t IV_buf[CBC_BLOCK_SIZE];
898 Kumu::FortunaRNG RNG;
900 // set up essence parser
901 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
904 if ( ASDCP_SUCCESS(result) )
906 Parser.FillAudioDescriptor(ADesc);
908 ADesc.SampleRate = PictureRate;
909 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
911 if ( Options.verbose_flag )
913 fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
914 Options.szPictureRate(),
915 PCM::CalcSamplesPerFrame(ADesc));
916 fputs("AudioDescriptor:\n", stderr);
917 PCM::AudioDescriptorDump(ADesc);
921 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
923 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
924 Kumu::GenRandomUUID(Info.AssetUUID);
926 if ( Options.use_smpte_labels )
928 Info.LabelSetType = LS_MXF_SMPTE;
929 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
932 // configure encryption
933 if( Options.key_flag )
935 Kumu::GenRandomUUID(Info.ContextID);
936 Info.EncryptedEssence = true;
938 if ( Options.key_id_flag )
939 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
941 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
943 Context = new AESEncContext;
944 result = Context->InitKey(Options.key_value);
946 if ( ASDCP_SUCCESS(result) )
947 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
949 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
951 Info.UsesHMAC = true;
952 HMAC = new HMACContext;
953 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
957 if ( ASDCP_SUCCESS(result) )
958 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
961 if ( ASDCP_SUCCESS(result) )
963 result = Parser.Reset();
966 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
968 result = Parser.ReadFrame(FrameBuffer);
970 if ( ASDCP_SUCCESS(result) )
972 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
974 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
975 fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
976 result = RESULT_ENDOFFILE;
980 if ( Options.verbose_flag )
981 FrameBuffer.Dump(stderr, Options.fb_dump_size);
983 if ( ! Options.no_write_flag )
985 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
987 // The Writer class will forward the last block of ciphertext
988 // to the encryption context for use as the IV for the next
989 // frame. If you want to use non-sequitur IV values, un-comment
990 // the following line of code.
991 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
992 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
997 if ( result == RESULT_ENDOFFILE )
1001 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1002 result = Writer.Finalize();
1007 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1008 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1009 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1012 read_PCM_file(CommandOptions& Options)
1014 AESDecContext* Context = 0;
1015 HMACContext* HMAC = 0;
1016 PCM::MXFReader Reader;
1017 PCM::FrameBuffer FrameBuffer;
1018 WavFileWriter OutWave;
1019 PCM::AudioDescriptor ADesc;
1020 ui32_t last_frame = 0;
1022 Result_t result = Reader.OpenRead(Options.filenames[0]);
1024 if ( ASDCP_SUCCESS(result) )
1026 Reader.FillAudioDescriptor(ADesc);
1028 if ( ADesc.SampleRate != EditRate_23_98
1029 && ADesc.SampleRate != EditRate_24
1030 && ADesc.SampleRate != EditRate_48 )
1031 ADesc.SampleRate = Options.PictureRate();
1033 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1035 if ( Options.verbose_flag )
1036 PCM::AudioDescriptorDump(ADesc);
1039 if ( ASDCP_SUCCESS(result) )
1041 last_frame = ADesc.ContainerDuration;
1043 if ( Options.duration > 0 && Options.duration < last_frame )
1044 last_frame = Options.duration;
1046 if ( Options.start_frame > 0 )
1048 if ( Options.start_frame > ADesc.ContainerDuration )
1050 fprintf(stderr, "Start value greater than file duration.\n");
1054 last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1057 ADesc.ContainerDuration = last_frame - Options.start_frame;
1058 OutWave.OpenWrite(ADesc, Options.file_root,
1059 ( Options.split_wav ? WavFileWriter::ST_STEREO :
1060 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1063 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1065 Context = new AESDecContext;
1066 result = Context->InitKey(Options.key_value);
1068 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1071 Reader.FillWriterInfo(Info);
1073 if ( Info.UsesHMAC )
1075 HMAC = new HMACContext;
1076 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1080 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1085 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1087 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1089 if ( ASDCP_SUCCESS(result) )
1091 if ( Options.verbose_flag )
1092 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1094 result = OutWave.WriteFrame(FrameBuffer);
1102 //------------------------------------------------------------------------------------------
1106 // These classes wrap the irregular names in the asdcplib API
1107 // so that I can use a template to simplify the implementation
1108 // of show_file_info()
1110 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1113 void FillDescriptor(MPEG2::MXFReader& Reader) {
1114 Reader.FillVideoDescriptor(*this);
1117 void Dump(FILE* stream) {
1118 MPEG2::VideoDescriptorDump(*this, stream);
1122 class MyPictureDescriptor : public JP2K::PictureDescriptor
1125 void FillDescriptor(JP2K::MXFReader& Reader) {
1126 Reader.FillPictureDescriptor(*this);
1129 void Dump(FILE* stream) {
1130 JP2K::PictureDescriptorDump(*this, stream);
1134 class MyAudioDescriptor : public PCM::AudioDescriptor
1137 void FillDescriptor(PCM::MXFReader& Reader) {
1138 Reader.FillAudioDescriptor(*this);
1141 void Dump(FILE* stream) {
1142 PCM::AudioDescriptorDump(*this, stream);
1147 // MSVC didn't like the function template, so now it's a static class method
1148 template<class ReaderT, class DescriptorT>
1149 class FileInfoWrapper
1152 static void file_info(CommandOptions& Options, FILE* stream = 0)
1157 if ( Options.verbose_flag || Options.showheader_flag )
1160 Result_t result = Reader.OpenRead(Options.filenames[0]);
1162 if ( ASDCP_SUCCESS(result) )
1164 if ( Options.showheader_flag )
1165 Reader.DumpHeaderMetadata(stream);
1168 Reader.FillWriterInfo(WI);
1169 WriterInfoDump(WI, stream);
1172 Desc.FillDescriptor(Reader);
1175 if ( Options.showindex_flag )
1176 Reader.DumpIndex(stream);
1178 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1180 Reader.DumpHeaderMetadata(stream);
1186 // Read header metadata from an ASDCP file
1189 show_file_info(CommandOptions& Options)
1191 EssenceType_t EssenceType;
1192 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1194 if ( ASDCP_FAILURE(result) )
1197 if ( EssenceType == ESS_MPEG2_VES )
1199 fputs("File essence type is MPEG2 video.\n", stdout);
1200 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1202 else if ( EssenceType == ESS_PCM_24b_48k )
1204 fputs("File essence type is PCM audio.\n", stdout);
1205 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1207 else if ( EssenceType == ESS_JPEG_2000 )
1209 fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1210 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1214 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1215 Kumu::FileReader Reader;
1216 MXF::OPAtomHeader TestHeader;
1218 result = Reader.OpenRead(Options.filenames[0]);
1220 if ( ASDCP_SUCCESS(result) )
1221 result = TestHeader.InitFromFile(Reader); // test UL and OP
1223 if ( ASDCP_SUCCESS(result) )
1225 TestHeader.Partition::Dump();
1227 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1230 fputs("File contains no Identification object.\n", stdout);
1232 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1235 fputs("File contains no SourcePackage object.\n", stdout);
1239 fputs("File is not MXF.\n", stdout);
1249 digest_file(const char* filename)
1251 using namespace Kumu;
1253 ASDCP_TEST_NULL_STR(filename);
1257 ByteString Buf(8192);
1259 Result_t result = Reader.OpenRead(filename);
1261 while ( ASDCP_SUCCESS(result) )
1263 ui32_t read_count = 0;
1264 result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1266 if ( result == RESULT_ENDOFFILE )
1272 if ( ASDCP_SUCCESS(result) )
1273 SHA1_Update(&Ctx, Buf.Data(), read_count);
1276 if ( ASDCP_SUCCESS(result) )
1278 const ui32_t sha_len = 20;
1279 byte_t bin_buf[sha_len];
1281 SHA1_Final(bin_buf, &Ctx);
1283 fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1291 main(int argc, const char** argv)
1293 Result_t result = RESULT_OK;
1294 CommandOptions Options(argc, argv);
1296 if ( Options.version_flag )
1299 if ( Options.help_flag )
1302 if ( Options.version_flag || Options.help_flag )
1305 if ( Options.error_flag )
1307 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PACKAGE);
1311 if ( Options.mode == MMT_INFO )
1313 result = show_file_info(Options);
1315 else if ( Options.mode == MMT_GOP_START )
1317 result = gop_start_test(Options);
1319 else if ( Options.mode == MMT_GEN_KEY )
1321 Kumu::FortunaRNG RNG;
1322 byte_t bin_buf[KeyLen];
1325 RNG.FillRandom(bin_buf, KeyLen);
1326 printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 40));
1328 else if ( Options.mode == MMT_GEN_ID )
1331 Kumu::GenRandomValue(TmpID);
1333 printf("%s\n", TmpID.EncodeHex(str_buf, 40));
1335 else if ( Options.mode == MMT_DIGEST )
1337 for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1338 result = digest_file(Options.filenames[i]);
1340 else if ( Options.mode == MMT_EXTRACT )
1342 EssenceType_t EssenceType;
1343 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1345 if ( ASDCP_SUCCESS(result) )
1347 switch ( EssenceType )
1350 result = read_MPEG2_file(Options);
1354 result = read_JP2K_file(Options);
1357 case ESS_PCM_24b_48k:
1358 result = read_PCM_file(Options);
1362 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1367 else if ( Options.mode == MMT_CREATE )
1369 if ( Options.do_repeat && ! Options.duration_flag )
1371 fputs("Option -R requires -d <duration>\n", stderr);
1375 EssenceType_t EssenceType;
1376 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1378 if ( ASDCP_SUCCESS(result) )
1380 switch ( EssenceType )
1383 result = write_MPEG2_file(Options);
1387 result = write_JP2K_file(Options);
1390 case ESS_PCM_24b_48k:
1391 result = write_PCM_file(Options);
1395 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1396 Options.filenames[0]);
1402 if ( ASDCP_FAILURE(result) )
1404 fputs("Program stopped on error.\n", stderr);
1406 if ( result != RESULT_FAIL )
1408 fputs(result, stderr);
1409 fputc('\n', stderr);
1420 // end asdcp-test.cpp