2 Copyright (c) 2003-2005, 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
54 #include <PCMParserList.h>
55 #include <WavFileWriter.h>
56 #include <hex_utils.h>
57 #include <AS_DCP_UUID.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, "%lu.%lu.%lu", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
89 ProductVersion = s_buf;
94 // Macros used to test command option data state.
96 // True if a major mode has already been selected.
97 #define TEST_MAJOR_MODE() ( info_flag||create_flag||extract_flag||genkey_flag||genid_flag||gop_start_flag )
99 // Causes the caller to return if a major mode has already been selected,
100 // otherwise sets the given flag.
101 #define TEST_SET_MAJOR_MODE(f) if ( TEST_MAJOR_MODE() ) \
103 fputs("Conflicting major mode, choose one of -(gcixG)).\n", stderr); \
108 // Increment the iterator, test for an additional non-option command line argument.
109 // Causes the caller to return if there are no remaining arguments or if the next
110 // argument begins with '-'.
111 #define TEST_EXTRA_ARG(i,c) if ( ++i >= argc || argv[(i)][0] == '-' ) \
113 fprintf(stderr, "Argument not found for option %c.\n", (c)); \
118 banner(FILE* stream = stderr)
121 %s (asdcplib %s)\n\n\
122 Copyright (c) 2003-2005 John Hurst\n\n\
123 asdcplib may be copied only under the terms of the license found at\n\
124 the top of every file in the asdcplib distribution kit.\n\n\
125 Specify the -h (help) option for further information about %s\n\n",
126 PACKAGE, ASDCP::Version(), PACKAGE);
131 usage(FILE* stream = stderr)
134 USAGE: %s [-i [-H, -n]|-c <filename> [-p <rate>, -e, -M, -R]|-x <root-name> [-m][-S]|-g|-u|-G|-V|-h]\n\
135 [-k <key-string>] [-j <key-id-string>] [-f <start-frame-num>] [-d <duration>]\n\
136 [-b <buf-size>] [-W] [-v [-s]] [<filename>, ...]\n\
141 -i - show file info\n\
142 -c <filename> - create AS-DCP file from input(s)\n\
143 -x <root-name> - extract essence from AS-DCP file to named file(s)\n\
144 -g - generate a random 16 byte value to stdout\n\
145 -u - generate a random UUID value to stdout\n\
146 -G - Perform GOP start lookup test on MPEG file\n\
153 -j <key-id-str> - write key ID instead of creating a random value\n\
154 -k <key-string> - use key for ciphertext operations\n\
155 -e - encrypt MPEG or JP2K headers (default)\n\
156 -E - do not encrypt MPEG or JP2K headers\n\
157 -M - do not create HMAC values when writing\n\
158 -m - verify HMAC values when reading\n\
162 Read/Write Options:\n\
163 -b <buf-size> - Size (in bytes) of the picture frame buffer, default: 2097152 (2MB)\n\
164 -f <frame-num> - starting frame number, default 0\n\
165 -d <duration> - number of frames to process, default all\n\
166 -p <rate> - fps of picture when wrapping PCM or JP2K:, use one of [23|24|48], 24 is default\n\
167 -R - Repeat the first frame over the entire file (picture essence only, requires -c, -d)\n\
168 -S - Split Wave essence to stereo WAV files during extract (default = multichannel WAV)\n\
169 -W - read input file only, do not write source file\n\
174 -H - show MXF header metadata, used with option -i\n\
175 -n - show index, used with option -i\n\
178 -s <number> - number of bytes of frame buffer to be dumped as hex to stderr (use with -v)\n\
179 -v - verbose, show extra detail during run\n\
181 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
182 o All option arguments must be separated from the option by whitespace.\n\
183 o An argument of \"23\" to the -p option will be interpreted as 23000/1001 fps.\n\
194 bool error_flag; // true if the given options are in error or not complete
195 bool info_flag; // true if the file info mode was selected
196 bool create_flag; // true if the file create mode was selected
197 bool extract_flag; // true if the file extract mode was selected
198 bool genkey_flag; // true if we are to generate a new key value
199 bool genid_flag; // true if we are to generate a new UUID value
200 bool gop_start_flag; // true if we are to perform a GOP start lookup test
201 bool key_flag; // true if an encryption key was given
202 bool key_id_flag; // true if a key ID was given
203 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
204 bool write_hmac; // true if HMAC values are to be generated and written
205 bool read_hmac; // true if HMAC values are to be validated
206 bool split_wav; // true if PCM is to be extracted to stereo WAV files
207 bool verbose_flag; // true if the verbose option was selected
208 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
209 bool showindex_flag; // true if index is to be displayed
210 bool showheader_flag; // true if MXF file header is to be displayed
211 bool no_write_flag; // true if no output files are to be written
212 bool version_flag; // true if the version display option was selected
213 bool help_flag; // true if the help display option was selected
214 ui32_t start_frame; // frame number to begin processing
215 ui32_t duration; // number of frames to be processed
216 bool duration_flag; // true if duration argument given
217 bool do_repeat; // if true and -c -d, repeat first input frame
218 ui32_t picture_rate; // fps of picture when wrapping PCM
219 ui32_t fb_size; // size of picture frame buffer
220 ui32_t file_count; // number of elements in filenames[]
221 const char* file_root; // filename pre for files written by the extract mode
222 const char* out_file; // name of mxf file created by create mode
223 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
224 byte_t key_id_value[KeyIDlen];// value of given key ID (when key_id_flag is true)
225 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
228 Rational PictureRate()
230 if ( picture_rate == 23 ) return EditRate_23_98;
231 if ( picture_rate == 48 ) return EditRate_48;
236 const char* szPictureRate()
238 if ( picture_rate == 23 ) return "23.976";
239 if ( picture_rate == 48 ) return "48";
244 CommandOptions(int argc, const char** argv) :
245 error_flag(true), info_flag(false), create_flag(false),
246 extract_flag(false), genkey_flag(false), genid_flag(false), gop_start_flag(false),
247 key_flag(false), encrypt_header_flag(true), write_hmac(true), read_hmac(false), split_wav(false),
248 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
249 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
250 duration(0xffffffff), duration_flag(false), do_repeat(false), picture_rate(24),
251 fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
253 memset(key_value, 0, KeyLen);
254 memset(key_id_value, 0, KeyIDlen);
256 for ( int i = 1; i < argc; i++ )
258 if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
260 switch ( argv[i][1] )
262 case 'i': TEST_SET_MAJOR_MODE(info_flag); break;
263 case 'G': TEST_SET_MAJOR_MODE(gop_start_flag); break;
264 case 'W': no_write_flag = true; break;
265 case 'n': showindex_flag = true; break;
266 case 'H': showheader_flag = true; break;
267 case 'R': do_repeat = true; break;
268 case 'S': split_wav = true; break;
269 case 'V': version_flag = true; break;
270 case 'h': help_flag = true; break;
271 case 'v': verbose_flag = true; break;
273 #ifdef ASDCP_WITHOUT_OPENSSL
274 fputs("Program compiled without encryption support.\n", stderr);
282 #ifdef ASDCP_WITHOUT_OPENSSL
283 fputs("Program compiled without encryption support.\n", stderr);
290 case 'e': encrypt_header_flag = true; break;
291 case 'E': encrypt_header_flag = false; break;
292 case 'M': write_hmac = false; break;
293 case 'm': read_hmac = true; break;
296 TEST_SET_MAJOR_MODE(create_flag);
297 TEST_EXTRA_ARG(i, 'c');
302 TEST_SET_MAJOR_MODE(extract_flag);
303 TEST_EXTRA_ARG(i, 'x');
307 case 'k': key_flag = true;
308 #ifdef ASDCP_WITHOUT_OPENSSL
309 fputs("Program compiled without encryption support.\n", stderr);
312 TEST_EXTRA_ARG(i, 'k');
315 hex2bin(argv[i], key_value, KeyLen, &length);
317 if ( length != KeyLen )
319 fprintf(stderr, "Unexpected key length: %lu, expecting %lu characters.\n", KeyLen, length);
326 case 'j': key_id_flag = true;
327 #ifdef ASDCP_WITHOUT_OPENSSL
328 fputs("Program compiled without encryption support.\n", stderr);
331 TEST_EXTRA_ARG(i, 'j');
334 hex2bin(argv[i], key_id_value, KeyIDlen, &length);
336 if ( length != KeyIDlen )
338 fprintf(stderr, "Unexpected key ID length: %lu, expecting %lu characters.\n", KeyIDlen, length);
346 TEST_EXTRA_ARG(i, 'f');
347 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
351 TEST_EXTRA_ARG(i, 'd');
352 duration_flag = true;
353 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
357 TEST_EXTRA_ARG(i, 'p');
358 picture_rate = atoi(argv[i]);
362 TEST_EXTRA_ARG(i, 's');
363 fb_dump_size = atoi(argv[i]);
367 TEST_EXTRA_ARG(i, 'b');
368 fb_size = atoi(argv[i]);
371 fprintf(stderr, "Frame Buffer size: %lu bytes.\n", fb_size);
376 fprintf(stderr, "Unrecognized option: %c\n", argv[i][1]);
382 filenames[file_count++] = argv[i];
384 if ( file_count >= MAX_IN_FILES )
386 fprintf(stderr, "Filename lists exceeds maximum list size: %lu\n", MAX_IN_FILES);
392 if ( TEST_MAJOR_MODE() )
394 if ( ! genkey_flag && ! genid_flag && file_count == 0 )
396 fputs("Option requires at least one filename argument.\n", stderr);
401 if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
403 fputs("No operation selected (use one of -(gcixG) or -h for help).\n", stderr);
411 //------------------------------------------------------------------------------------------
414 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
415 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
418 write_MPEG2_file(CommandOptions& Options)
420 AESEncContext* Context = 0;
421 HMACContext* HMAC = 0;
422 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
423 MPEG2::Parser Parser;
424 MPEG2::MXFWriter Writer;
425 MPEG2::VideoDescriptor VDesc;
427 #ifndef ASDCP_WITHOUT_OPENSSL
428 byte_t IV_buf[CBC_BLOCK_SIZE];
432 // set up essence parser
433 Result_t result = Parser.OpenRead(Options.filenames[0]);
436 if ( ASDCP_SUCCESS(result) )
438 Parser.FillVideoDescriptor(VDesc);
440 if ( Options.verbose_flag )
442 fputs("MPEG-2 Pictures\n", stderr);
443 fputs("VideoDescriptor:\n", stderr);
444 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
445 MPEG2::VideoDescriptorDump(VDesc);
449 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
451 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
452 #ifndef ASDCP_WITHOUT_OPENSSL
453 GenRandomUUID(RNG, Info.AssetUUID);
455 // configure encryption
456 if( Options.key_flag )
458 GenRandomUUID(RNG, Info.ContextID);
459 Info.EncryptedEssence = true;
461 if ( Options.key_id_flag )
462 memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
464 RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
466 Context = new AESEncContext;
467 result = Context->InitKey(Options.key_value);
469 if ( ASDCP_SUCCESS(result) )
470 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
472 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
474 Info.UsesHMAC = true;
475 HMAC = new HMACContext;
476 result = HMAC->InitKey(Options.key_value);
479 #endif // ASDCP_WITHOUT_OPENSSL
481 if ( ASDCP_SUCCESS(result) )
482 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
485 if ( ASDCP_SUCCESS(result) )
486 // loop through the frames
488 result = Parser.Reset();
491 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
493 if ( ! Options.do_repeat || duration == 1 )
495 result = Parser.ReadFrame(FrameBuffer);
497 if ( ASDCP_SUCCESS(result) )
499 if ( Options.verbose_flag )
500 FrameBuffer.Dump(stderr, Options.fb_dump_size);
502 if ( Options.encrypt_header_flag )
503 FrameBuffer.PlaintextOffset(0);
507 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
509 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
511 #ifndef ASDCP_WITHOUT_OPENSSL
512 // The Writer class will forward the last block of ciphertext
513 // to the encryption context for use as the IV for the next
514 // frame. If you want to use non-sequitur IV values, un-comment
515 // the following line of code.
516 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
517 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
522 if ( result == RESULT_ENDOFFILE )
526 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
527 result = Writer.Finalize();
533 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
534 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
535 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
538 read_MPEG2_file(CommandOptions& Options)
540 AESDecContext* Context = 0;
541 HMACContext* HMAC = 0;
542 MPEG2::MXFReader Reader;
543 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
545 ui32_t frame_count = 0;
547 Result_t result = Reader.OpenRead(Options.filenames[0]);
549 if ( ASDCP_SUCCESS(result) )
551 MPEG2::VideoDescriptor VDesc;
552 Reader.FillVideoDescriptor(VDesc);
553 frame_count = VDesc.ContainerDuration;
555 if ( Options.verbose_flag )
557 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
558 MPEG2::VideoDescriptorDump(VDesc);
562 if ( ASDCP_SUCCESS(result) )
565 snprintf(filename, 256, "%s.ves", Options.file_root);
566 result = OutFile.OpenWrite(filename);
569 if ( ASDCP_SUCCESS(result) && Options.key_flag )
571 Context = new AESDecContext;
572 result = Context->InitKey(Options.key_value);
574 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
577 Reader.FillWriterInfo(Info);
581 HMAC = new HMACContext;
582 result = HMAC->InitKey(Options.key_value);
586 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
591 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
592 if ( last_frame > frame_count )
593 last_frame = frame_count;
595 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
597 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
599 if ( ASDCP_SUCCESS(result) )
601 if ( Options.verbose_flag )
602 FrameBuffer.Dump(stderr, Options.fb_dump_size);
604 ui32_t write_count = 0;
605 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
615 gop_start_test(CommandOptions& Options)
617 using namespace ASDCP::MPEG2;
620 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
621 ui32_t frame_count = 0;
623 Result_t result = Reader.OpenRead(Options.filenames[0]);
625 if ( ASDCP_SUCCESS(result) )
627 MPEG2::VideoDescriptor VDesc;
628 Reader.FillVideoDescriptor(VDesc);
629 frame_count = VDesc.ContainerDuration;
631 if ( Options.verbose_flag )
633 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
634 MPEG2::VideoDescriptorDump(VDesc);
638 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
639 if ( last_frame > frame_count )
640 last_frame = frame_count;
642 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
644 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
646 if ( ASDCP_SUCCESS(result) )
648 if ( Options.verbose_flag )
649 FrameBuffer.Dump(stderr, Options.fb_dump_size);
651 if ( FrameBuffer.FrameType() != FRAME_I )
652 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
654 fprintf(stderr, "Requested frame %lu, got %lu\n", i, FrameBuffer.FrameNumber());
662 //------------------------------------------------------------------------------------------
665 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
666 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
669 write_JP2K_file(CommandOptions& Options)
671 AESEncContext* Context = 0;
672 HMACContext* HMAC = 0;
673 JP2K::MXFWriter Writer;
674 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
675 JP2K::PictureDescriptor PDesc;
676 JP2K::SequenceParser Parser;
678 #ifndef ASDCP_WITHOUT_OPENSSL
679 byte_t IV_buf[CBC_BLOCK_SIZE];
683 // set up essence parser
684 Result_t result = Parser.OpenRead(Options.filenames[0]);
687 if ( ASDCP_SUCCESS(result) )
689 Parser.FillPictureDescriptor(PDesc);
690 PDesc.EditRate = Options.PictureRate();
692 if ( Options.verbose_flag )
694 fprintf(stderr, "JPEG 2000 pictures\n");
695 fputs("PictureDescriptor:\n", stderr);
696 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
697 JP2K::PictureDescriptorDump(PDesc);
701 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
703 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
704 #ifndef ASDCP_WITHOUT_OPENSSL
705 GenRandomUUID(RNG, Info.AssetUUID);
707 // configure encryption
708 if( Options.key_flag )
710 GenRandomUUID(RNG, Info.ContextID);
711 Info.EncryptedEssence = true;
713 if ( Options.key_id_flag )
714 memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
716 RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
718 Context = new AESEncContext;
719 result = Context->InitKey(Options.key_value);
721 if ( ASDCP_SUCCESS(result) )
722 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
724 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
726 Info.UsesHMAC = true;
727 HMAC = new HMACContext;
728 result = HMAC->InitKey(Options.key_value);
731 #endif // ASDCP_WITHOUT_OPENSSL
733 if ( ASDCP_SUCCESS(result) )
734 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
737 if ( ASDCP_SUCCESS(result) )
740 result = Parser.Reset();
742 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
744 if ( ! Options.do_repeat || duration == 1 )
746 result = Parser.ReadFrame(FrameBuffer);
748 if ( ASDCP_SUCCESS(result) )
750 if ( Options.verbose_flag )
751 FrameBuffer.Dump(stderr, Options.fb_dump_size);
753 if ( Options.encrypt_header_flag )
754 FrameBuffer.PlaintextOffset(0);
758 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
760 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
761 #ifndef ASDCP_WITHOUT_OPENSSL
762 // The Writer class will forward the last block of ciphertext
763 // to the encryption context for use as the IV for the next
764 // frame. If you want to use non-sequitur IV values, un-comment
765 // the following line of code.
766 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
767 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
772 if ( result == RESULT_ENDOFFILE )
776 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
777 result = Writer.Finalize();
783 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
784 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
785 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
788 read_JP2K_file(CommandOptions& Options)
790 AESDecContext* Context = 0;
791 HMACContext* HMAC = 0;
792 JP2K::MXFReader Reader;
793 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
794 ui32_t frame_count = 0;
796 Result_t result = Reader.OpenRead(Options.filenames[0]);
798 if ( ASDCP_SUCCESS(result) )
800 JP2K::PictureDescriptor PDesc;
801 Reader.FillPictureDescriptor(PDesc);
803 frame_count = PDesc.ContainerDuration;
805 if ( Options.verbose_flag )
807 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
808 JP2K::PictureDescriptorDump(PDesc);
812 if ( ASDCP_SUCCESS(result) && Options.key_flag )
814 Context = new AESDecContext;
815 result = Context->InitKey(Options.key_value);
817 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
820 Reader.FillWriterInfo(Info);
824 HMAC = new HMACContext;
825 result = HMAC->InitKey(Options.key_value);
829 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
834 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
835 if ( last_frame > frame_count )
836 last_frame = frame_count;
838 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
840 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
842 if ( ASDCP_SUCCESS(result) )
847 snprintf(filename, 256, "%s%06lu.j2c", Options.file_root, i);
848 result = OutFile.OpenWrite(filename);
850 if ( ASDCP_SUCCESS(result) )
851 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
853 if ( Options.verbose_flag )
854 FrameBuffer.Dump(stderr, Options.fb_dump_size);
862 //------------------------------------------------------------------------------------------
866 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
867 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
870 write_PCM_file(CommandOptions& Options)
872 AESEncContext* Context = 0;
873 HMACContext* HMAC = 0;
874 PCMParserList Parser;
875 PCM::MXFWriter Writer;
876 PCM::FrameBuffer FrameBuffer;
877 PCM::AudioDescriptor ADesc;
878 Rational PictureRate = Options.PictureRate();
880 #ifndef ASDCP_WITHOUT_OPENSSL
881 byte_t IV_buf[CBC_BLOCK_SIZE];
885 // set up essence parser
886 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
889 if ( ASDCP_SUCCESS(result) )
891 Parser.FillAudioDescriptor(ADesc);
893 ADesc.SampleRate = PictureRate;
894 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
896 if ( Options.verbose_flag )
898 fprintf(stderr, "48Khz PCM Audio, %s fps (%lu spf)\n",
899 Options.szPictureRate(),
900 PCM::CalcSamplesPerFrame(ADesc));
901 fputs("AudioDescriptor:\n", stderr);
902 PCM::AudioDescriptorDump(ADesc);
906 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
908 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
909 #ifndef ASDCP_WITHOUT_OPENSSL
910 GenRandomUUID(RNG, Info.AssetUUID);
912 // configure encryption
913 if( Options.key_flag )
915 GenRandomUUID(RNG, Info.ContextID);
916 Info.EncryptedEssence = true;
918 if ( Options.key_id_flag )
919 memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
921 RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
923 Context = new AESEncContext;
924 result = Context->InitKey(Options.key_value);
926 if ( ASDCP_SUCCESS(result) )
927 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
929 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
931 Info.UsesHMAC = true;
932 HMAC = new HMACContext;
933 result = HMAC->InitKey(Options.key_value);
936 #endif // ASDCP_WITHOUT_OPENSSL
938 if ( ASDCP_SUCCESS(result) )
939 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
942 if ( ASDCP_SUCCESS(result) )
944 result = Parser.Reset();
947 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
949 result = Parser.ReadFrame(FrameBuffer);
951 if ( ASDCP_SUCCESS(result) )
953 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
955 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
956 fprintf(stderr, "Expecting %lu bytes, got %lu.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
957 result = RESULT_ENDOFFILE;
961 if ( Options.verbose_flag )
962 FrameBuffer.Dump(stderr, Options.fb_dump_size);
964 if ( ! Options.no_write_flag )
966 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
968 #ifndef ASDCP_WITHOUT_OPENSSL
969 // The Writer class will forward the last block of ciphertext
970 // to the encryption context for use as the IV for the next
971 // frame. If you want to use non-sequitur IV values, un-comment
972 // the following line of code.
973 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
974 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
980 if ( result == RESULT_ENDOFFILE )
984 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
985 result = Writer.Finalize();
991 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
992 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
993 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
996 read_PCM_file(CommandOptions& Options)
998 AESDecContext* Context = 0;
999 HMACContext* HMAC = 0;
1000 PCM::MXFReader Reader;
1001 PCM::FrameBuffer FrameBuffer;
1002 WavFileWriter OutWave;
1003 PCM::AudioDescriptor ADesc;
1004 ui32_t last_frame = 0;
1006 Result_t result = Reader.OpenRead(Options.filenames[0]);
1008 if ( ASDCP_SUCCESS(result) )
1010 Reader.FillAudioDescriptor(ADesc);
1012 if ( ADesc.SampleRate != EditRate_23_98
1013 && ADesc.SampleRate != EditRate_24
1014 && ADesc.SampleRate != EditRate_48 )
1015 ADesc.SampleRate = Options.PictureRate();
1017 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1019 if ( Options.verbose_flag )
1020 PCM::AudioDescriptorDump(ADesc);
1023 if ( ASDCP_SUCCESS(result) )
1025 last_frame = ADesc.ContainerDuration;
1027 if ( Options.duration > 0 && Options.duration < last_frame )
1028 last_frame = Options.duration;
1030 if ( Options.start_frame > 0 )
1032 if ( Options.start_frame > ADesc.ContainerDuration )
1034 fprintf(stderr, "Start value greater than file duration.\n");
1038 last_frame = xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1041 ADesc.ContainerDuration = last_frame - Options.start_frame;
1042 OutWave.OpenWrite(ADesc, Options.file_root, Options.split_wav);
1045 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1047 Context = new AESDecContext;
1048 result = Context->InitKey(Options.key_value);
1050 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1053 Reader.FillWriterInfo(Info);
1055 if ( Info.UsesHMAC )
1057 HMAC = new HMACContext;
1058 result = HMAC->InitKey(Options.key_value);
1062 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1067 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1069 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1071 if ( ASDCP_SUCCESS(result) )
1073 if ( Options.verbose_flag )
1074 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1076 result = OutWave.WriteFrame(FrameBuffer);
1084 //------------------------------------------------------------------------------------------
1088 // These classes wrap the irregular names in the asdcplib API
1089 // so that I can use a template to simplify the implementation
1090 // of show_file_info()
1092 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1095 void FillDescriptor(MPEG2::MXFReader& Reader) {
1096 Reader.FillVideoDescriptor(*this);
1099 void Dump(FILE* stream) {
1100 MPEG2::VideoDescriptorDump(*this, stream);
1104 class MyPictureDescriptor : public JP2K::PictureDescriptor
1107 void FillDescriptor(JP2K::MXFReader& Reader) {
1108 Reader.FillPictureDescriptor(*this);
1111 void Dump(FILE* stream) {
1112 JP2K::PictureDescriptorDump(*this, stream);
1116 class MyAudioDescriptor : public PCM::AudioDescriptor
1119 void FillDescriptor(PCM::MXFReader& Reader) {
1120 Reader.FillAudioDescriptor(*this);
1123 void Dump(FILE* stream) {
1124 PCM::AudioDescriptorDump(*this, stream);
1129 // MSVC didn't like the function template, so now it's a static class method
1130 template<class ReaderT, class DescriptorT>
1131 class FileInfoWrapper
1134 static void file_info(CommandOptions& Options, FILE* stream = 0)
1139 if ( Options.verbose_flag || Options.showheader_flag )
1142 Result_t result = Reader.OpenRead(Options.filenames[0]);
1144 if ( ASDCP_SUCCESS(result) )
1146 if ( Options.showheader_flag )
1147 Reader.DumpHeaderMetadata(stream);
1150 Reader.FillWriterInfo(WI);
1151 WriterInfoDump(WI, stream);
1154 Desc.FillDescriptor(Reader);
1157 if ( Options.showindex_flag )
1158 Reader.DumpIndex(stream);
1160 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1162 Reader.DumpHeaderMetadata(stream);
1168 // Read header metadata from an ASDCP file
1171 show_file_info(CommandOptions& Options)
1173 EssenceType_t EssenceType;
1174 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1176 if ( ASDCP_FAILURE(result) )
1179 if ( EssenceType == ESS_MPEG2_VES )
1181 fputs("File essence type is MPEG2 video.\n", stdout);
1182 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1184 else if ( EssenceType == ESS_PCM_24b_48k )
1186 fputs("File essence type is PCM audio.\n", stdout);
1187 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1189 else if ( EssenceType == ESS_JPEG_2000 )
1191 fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1192 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1196 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1198 MXF::OPAtomHeader TestHeader;
1200 result = Reader.OpenRead(Options.filenames[0]);
1202 if ( ASDCP_SUCCESS(result) )
1203 result = TestHeader.InitFromFile(Reader); // test UL and OP
1205 if ( ASDCP_SUCCESS(result) )
1207 TestHeader.Partition::Dump();
1209 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1212 fputs("File contains no Identification object.\n", stdout);
1214 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1217 fputs("File contains no SourcePackage object.\n", stdout);
1221 fputs("File is not MXF.\n", stdout);
1231 main(int argc, const char** argv)
1233 Result_t result = RESULT_OK;
1234 CommandOptions Options(argc, argv);
1236 if ( Options.help_flag )
1242 if ( Options.error_flag )
1245 if ( Options.version_flag )
1248 if ( Options.info_flag )
1250 result = show_file_info(Options);
1252 else if ( Options.gop_start_flag )
1254 result = gop_start_test(Options);
1256 #ifndef ASDCP_WITHOUT_OPENSSL
1257 else if ( Options.genkey_flag )
1260 byte_t bin_buf[KeyLen];
1263 RNG.FillRandom(bin_buf, KeyLen);
1264 printf("%s\n", bin2hex(bin_buf, KeyLen, str_buf, 40));
1266 else if ( Options.genid_flag )
1269 byte_t bin_buf[KeyLen];
1272 GenRandomUUID(RNG, bin_buf);
1273 bin2hex(bin_buf, KeyLen, str_buf, 40);
1274 printf("%s\n", hyphenate_UUID(str_buf, 40));
1276 #endif // ASDCP_WITHOUT_OPENSSL
1277 else if ( Options.extract_flag )
1279 EssenceType_t EssenceType;
1280 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1282 fprintf(stderr, "ATTENTION! Expecting SMPTE Universal Labels\n");
1284 if ( ASDCP_SUCCESS(result) )
1286 switch ( EssenceType )
1289 result = read_MPEG2_file(Options);
1293 result = read_JP2K_file(Options);
1296 case ESS_PCM_24b_48k:
1297 result = read_PCM_file(Options);
1301 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1306 else if ( Options.create_flag )
1308 fprintf(stderr, "ATTENTION! This version of asdcplib does not support writing MXF files.\n");
1311 if ( Options.do_repeat && ! Options.duration_flag )
1313 fputs("Option -R requires -d <duration>\n", stderr);
1317 EssenceType_t EssenceType;
1318 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1320 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1322 if ( ASDCP_SUCCESS(result) )
1324 switch ( EssenceType )
1327 result = write_MPEG2_file(Options);
1331 result = write_JP2K_file(Options);
1334 case ESS_PCM_24b_48k:
1335 result = write_PCM_file(Options);
1339 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1340 Options.filenames[0]);
1347 if ( result != RESULT_OK )
1349 fputs("Program stopped on error.\n", stderr);
1351 if ( result != RESULT_FAIL )
1353 fputs(GetResultString(result), stderr);
1354 fputc('\n', stderr);
1365 // end asdcp-test.cpp