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
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 -L - Write SMPTE UL values instead of MXF Interop\n\
168 -R - Repeat the first frame over the entire file (picture essence only, requires -c, -d)\n\
169 -S - Split Wave essence to stereo WAV files during extract (default = multichannel WAV)\n\
170 -W - read input file only, do not write source file\n\
175 -H - show MXF header metadata, used with option -i\n\
176 -n - show index, used with option -i\n\
179 -s <number> - number of bytes of frame buffer to be dumped as hex to stderr (use with -v)\n\
180 -v - verbose, show extra detail during run\n\
182 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
183 o All option arguments must be separated from the option by whitespace.\n\
184 o An argument of \"23\" to the -p option will be interpreted as 23000/1001 fps.\n\
195 bool error_flag; // true if the given options are in error or not complete
196 bool info_flag; // true if the file info mode was selected
197 bool create_flag; // true if the file create mode was selected
198 bool extract_flag; // true if the file extract mode was selected
199 bool genkey_flag; // true if we are to generate a new key value
200 bool genid_flag; // true if we are to generate a new UUID value
201 bool gop_start_flag; // true if we are to perform a GOP start lookup test
202 bool key_flag; // true if an encryption key was given
203 bool key_id_flag; // true if a key ID was given
204 bool encrypt_header_flag; // true if mpeg headers are to be encrypted
205 bool write_hmac; // true if HMAC values are to be generated and written
206 bool read_hmac; // true if HMAC values are to be validated
207 bool split_wav; // true if PCM is to be extracted to stereo WAV files
208 bool verbose_flag; // true if the verbose option was selected
209 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
210 bool showindex_flag; // true if index is to be displayed
211 bool showheader_flag; // true if MXF file header is to be displayed
212 bool no_write_flag; // true if no output files are to be written
213 bool version_flag; // true if the version display option was selected
214 bool help_flag; // true if the help display option was selected
215 ui32_t start_frame; // frame number to begin processing
216 ui32_t duration; // number of frames to be processed
217 bool duration_flag; // true if duration argument given
218 bool do_repeat; // if true and -c -d, repeat first input frame
219 bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
220 ui32_t picture_rate; // fps of picture when wrapping PCM
221 ui32_t fb_size; // size of picture frame buffer
222 ui32_t file_count; // number of elements in filenames[]
223 const char* file_root; // filename pre for files written by the extract mode
224 const char* out_file; // name of mxf file created by create mode
225 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
226 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
227 const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
230 Rational PictureRate()
232 if ( picture_rate == 23 ) return EditRate_23_98;
233 if ( picture_rate == 48 ) return EditRate_48;
238 const char* szPictureRate()
240 if ( picture_rate == 23 ) return "23.976";
241 if ( picture_rate == 48 ) return "48";
246 CommandOptions(int argc, const char** argv) :
247 error_flag(true), info_flag(false), create_flag(false),
248 extract_flag(false), genkey_flag(false), genid_flag(false), gop_start_flag(false),
249 key_flag(false), key_id_flag(false), encrypt_header_flag(true),
250 write_hmac(true), read_hmac(false), split_wav(false),
251 verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
252 no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
253 duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
254 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
256 memset(key_value, 0, KeyLen);
257 memset(key_id_value, 0, UUIDlen);
259 for ( int i = 1; i < argc; i++ )
261 if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
263 switch ( argv[i][1] )
265 case 'i': TEST_SET_MAJOR_MODE(info_flag); break;
266 case 'G': TEST_SET_MAJOR_MODE(gop_start_flag); break;
267 case 'W': no_write_flag = true; break;
268 case 'n': showindex_flag = true; break;
269 case 'H': showheader_flag = true; break;
270 case 'R': do_repeat = true; break;
271 case 'S': split_wav = true; break;
272 case 'V': version_flag = true; break;
273 case 'h': help_flag = true; break;
274 case 'v': verbose_flag = true; break;
275 case 'g': genkey_flag = true; break;
276 case 'u': genid_flag = true; break;
277 case 'e': encrypt_header_flag = true; break;
278 case 'E': encrypt_header_flag = false; break;
279 case 'M': write_hmac = false; break;
280 case 'm': read_hmac = true; break;
281 case 'L': use_smpte_labels = true; break;
284 TEST_SET_MAJOR_MODE(create_flag);
285 TEST_EXTRA_ARG(i, 'c');
290 TEST_SET_MAJOR_MODE(extract_flag);
291 TEST_EXTRA_ARG(i, 'x');
295 case 'k': key_flag = true;
296 TEST_EXTRA_ARG(i, 'k');
299 hex2bin(argv[i], key_value, KeyLen, &length);
301 if ( length != KeyLen )
303 fprintf(stderr, "Unexpected key length: %lu, expecting %lu characters.\n", KeyLen, length);
309 case 'j': key_id_flag = true;
310 TEST_EXTRA_ARG(i, 'j');
313 hex2bin(argv[i], key_id_value, UUIDlen, &length);
315 if ( length != UUIDlen )
317 fprintf(stderr, "Unexpected key ID length: %lu, expecting %lu characters.\n", UUIDlen, length);
324 TEST_EXTRA_ARG(i, 'f');
325 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
329 TEST_EXTRA_ARG(i, 'd');
330 duration_flag = true;
331 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
335 TEST_EXTRA_ARG(i, 'p');
336 picture_rate = atoi(argv[i]);
340 TEST_EXTRA_ARG(i, 's');
341 fb_dump_size = atoi(argv[i]);
345 TEST_EXTRA_ARG(i, 'b');
346 fb_size = atoi(argv[i]);
349 fprintf(stderr, "Frame Buffer size: %lu bytes.\n", fb_size);
354 fprintf(stderr, "Unrecognized option: %c\n", argv[i][1]);
360 filenames[file_count++] = argv[i];
362 if ( file_count >= MAX_IN_FILES )
364 fprintf(stderr, "Filename lists exceeds maximum list size: %lu\n", MAX_IN_FILES);
370 if ( TEST_MAJOR_MODE() )
372 if ( ! genkey_flag && ! genid_flag && file_count == 0 )
374 fputs("Option requires at least one filename argument.\n", stderr);
379 if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
381 fputs("No operation selected (use one of -(gcixG) or -h for help).\n", stderr);
389 //------------------------------------------------------------------------------------------
392 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
393 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
396 write_MPEG2_file(CommandOptions& Options)
398 AESEncContext* Context = 0;
399 HMACContext* HMAC = 0;
400 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
401 MPEG2::Parser Parser;
402 MPEG2::MXFWriter Writer;
403 MPEG2::VideoDescriptor VDesc;
404 byte_t IV_buf[CBC_BLOCK_SIZE];
407 // set up essence parser
408 Result_t result = Parser.OpenRead(Options.filenames[0]);
411 if ( ASDCP_SUCCESS(result) )
413 Parser.FillVideoDescriptor(VDesc);
415 if ( Options.verbose_flag )
417 fputs("MPEG-2 Pictures\n", stderr);
418 fputs("VideoDescriptor:\n", stderr);
419 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
420 MPEG2::VideoDescriptorDump(VDesc);
424 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
426 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
427 GenRandomUUID(RNG, Info.AssetUUID);
429 if ( Options.use_smpte_labels )
431 Info.LabelSetType = LS_MXF_INTEROP;
432 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
435 // configure encryption
436 if( Options.key_flag )
438 GenRandomUUID(RNG, Info.ContextID);
439 Info.EncryptedEssence = true;
441 if ( Options.key_id_flag )
442 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
444 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
446 Context = new AESEncContext;
447 result = Context->InitKey(Options.key_value);
449 if ( ASDCP_SUCCESS(result) )
450 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
452 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
454 Info.UsesHMAC = true;
455 HMAC = new HMACContext;
456 result = HMAC->InitKey(Options.key_value);
460 if ( ASDCP_SUCCESS(result) )
461 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
464 if ( ASDCP_SUCCESS(result) )
465 // loop through the frames
467 result = Parser.Reset();
470 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
472 if ( ! Options.do_repeat || duration == 1 )
474 result = Parser.ReadFrame(FrameBuffer);
476 if ( ASDCP_SUCCESS(result) )
478 if ( Options.verbose_flag )
479 FrameBuffer.Dump(stderr, Options.fb_dump_size);
481 if ( Options.encrypt_header_flag )
482 FrameBuffer.PlaintextOffset(0);
486 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
488 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
490 // The Writer class will forward the last block of ciphertext
491 // to the encryption context for use as the IV for the next
492 // frame. If you want to use non-sequitur IV values, un-comment
493 // the following line of code.
494 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
495 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
499 if ( result == RESULT_ENDOFFILE )
503 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
504 result = Writer.Finalize();
509 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
510 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
511 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
514 read_MPEG2_file(CommandOptions& Options)
516 AESDecContext* Context = 0;
517 HMACContext* HMAC = 0;
518 MPEG2::MXFReader Reader;
519 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
521 ui32_t frame_count = 0;
523 Result_t result = Reader.OpenRead(Options.filenames[0]);
525 if ( ASDCP_SUCCESS(result) )
527 MPEG2::VideoDescriptor VDesc;
528 Reader.FillVideoDescriptor(VDesc);
529 frame_count = VDesc.ContainerDuration;
531 if ( Options.verbose_flag )
533 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
534 MPEG2::VideoDescriptorDump(VDesc);
538 if ( ASDCP_SUCCESS(result) )
541 snprintf(filename, 256, "%s.ves", Options.file_root);
542 result = OutFile.OpenWrite(filename);
545 if ( ASDCP_SUCCESS(result) && Options.key_flag )
547 Context = new AESDecContext;
548 result = Context->InitKey(Options.key_value);
550 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
553 Reader.FillWriterInfo(Info);
557 HMAC = new HMACContext;
558 result = HMAC->InitKey(Options.key_value);
562 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
567 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
568 if ( last_frame > frame_count )
569 last_frame = frame_count;
571 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
573 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
575 if ( ASDCP_SUCCESS(result) )
577 if ( Options.verbose_flag )
578 FrameBuffer.Dump(stderr, Options.fb_dump_size);
580 ui32_t write_count = 0;
581 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
591 gop_start_test(CommandOptions& Options)
593 using namespace ASDCP::MPEG2;
596 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
597 ui32_t frame_count = 0;
599 Result_t result = Reader.OpenRead(Options.filenames[0]);
601 if ( ASDCP_SUCCESS(result) )
603 MPEG2::VideoDescriptor VDesc;
604 Reader.FillVideoDescriptor(VDesc);
605 frame_count = VDesc.ContainerDuration;
607 if ( Options.verbose_flag )
609 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
610 MPEG2::VideoDescriptorDump(VDesc);
614 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
615 if ( last_frame > frame_count )
616 last_frame = frame_count;
618 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
620 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
622 if ( ASDCP_SUCCESS(result) )
624 if ( Options.verbose_flag )
625 FrameBuffer.Dump(stderr, Options.fb_dump_size);
627 if ( FrameBuffer.FrameType() != FRAME_I )
628 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
630 fprintf(stderr, "Requested frame %lu, got %lu\n", i, FrameBuffer.FrameNumber());
637 //------------------------------------------------------------------------------------------
640 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
641 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
644 write_JP2K_file(CommandOptions& Options)
646 AESEncContext* Context = 0;
647 HMACContext* HMAC = 0;
648 JP2K::MXFWriter Writer;
649 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
650 JP2K::PictureDescriptor PDesc;
651 JP2K::SequenceParser Parser;
652 byte_t IV_buf[CBC_BLOCK_SIZE];
655 // set up essence parser
656 Result_t result = Parser.OpenRead(Options.filenames[0]);
659 if ( ASDCP_SUCCESS(result) )
661 Parser.FillPictureDescriptor(PDesc);
662 PDesc.EditRate = Options.PictureRate();
664 if ( Options.verbose_flag )
666 fprintf(stderr, "JPEG 2000 pictures\n");
667 fputs("PictureDescriptor:\n", stderr);
668 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
669 JP2K::PictureDescriptorDump(PDesc);
673 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
675 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
676 GenRandomUUID(RNG, Info.AssetUUID);
678 if ( Options.use_smpte_labels )
680 Info.LabelSetType = LS_MXF_INTEROP;
681 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
684 // configure encryption
685 if( Options.key_flag )
687 GenRandomUUID(RNG, Info.ContextID);
688 Info.EncryptedEssence = true;
690 if ( Options.key_id_flag )
691 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
693 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
695 Context = new AESEncContext;
696 result = Context->InitKey(Options.key_value);
698 if ( ASDCP_SUCCESS(result) )
699 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
701 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
703 Info.UsesHMAC = true;
704 HMAC = new HMACContext;
705 result = HMAC->InitKey(Options.key_value);
709 if ( ASDCP_SUCCESS(result) )
710 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
713 if ( ASDCP_SUCCESS(result) )
716 result = Parser.Reset();
718 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
720 if ( ! Options.do_repeat || duration == 1 )
722 result = Parser.ReadFrame(FrameBuffer);
724 if ( ASDCP_SUCCESS(result) )
726 if ( Options.verbose_flag )
727 FrameBuffer.Dump(stderr, Options.fb_dump_size);
729 if ( Options.encrypt_header_flag )
730 FrameBuffer.PlaintextOffset(0);
734 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
736 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
738 // The Writer class will forward the last block of ciphertext
739 // to the encryption context for use as the IV for the next
740 // frame. If you want to use non-sequitur IV values, un-comment
741 // the following line of code.
742 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
743 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
747 if ( result == RESULT_ENDOFFILE )
751 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
752 result = Writer.Finalize();
757 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
758 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
759 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
762 read_JP2K_file(CommandOptions& Options)
764 AESDecContext* Context = 0;
765 HMACContext* HMAC = 0;
766 JP2K::MXFReader Reader;
767 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
768 ui32_t frame_count = 0;
770 Result_t result = Reader.OpenRead(Options.filenames[0]);
772 if ( ASDCP_SUCCESS(result) )
774 JP2K::PictureDescriptor PDesc;
775 Reader.FillPictureDescriptor(PDesc);
777 frame_count = PDesc.ContainerDuration;
779 if ( Options.verbose_flag )
781 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
782 JP2K::PictureDescriptorDump(PDesc);
786 if ( ASDCP_SUCCESS(result) && Options.key_flag )
788 Context = new AESDecContext;
789 result = Context->InitKey(Options.key_value);
791 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
794 Reader.FillWriterInfo(Info);
798 HMAC = new HMACContext;
799 result = HMAC->InitKey(Options.key_value);
803 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
808 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
809 if ( last_frame > frame_count )
810 last_frame = frame_count;
812 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
814 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
816 if ( ASDCP_SUCCESS(result) )
821 snprintf(filename, 256, "%s%06lu.j2c", Options.file_root, i);
822 result = OutFile.OpenWrite(filename);
824 if ( ASDCP_SUCCESS(result) )
825 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
827 if ( Options.verbose_flag )
828 FrameBuffer.Dump(stderr, Options.fb_dump_size);
835 //------------------------------------------------------------------------------------------
839 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
840 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
843 write_PCM_file(CommandOptions& Options)
845 AESEncContext* Context = 0;
846 HMACContext* HMAC = 0;
847 PCMParserList Parser;
848 PCM::MXFWriter Writer;
849 PCM::FrameBuffer FrameBuffer;
850 PCM::AudioDescriptor ADesc;
851 Rational PictureRate = Options.PictureRate();
852 byte_t IV_buf[CBC_BLOCK_SIZE];
855 // set up essence parser
856 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
859 if ( ASDCP_SUCCESS(result) )
861 Parser.FillAudioDescriptor(ADesc);
863 ADesc.SampleRate = PictureRate;
864 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
866 if ( Options.verbose_flag )
868 fprintf(stderr, "48Khz PCM Audio, %s fps (%lu spf)\n",
869 Options.szPictureRate(),
870 PCM::CalcSamplesPerFrame(ADesc));
871 fputs("AudioDescriptor:\n", stderr);
872 PCM::AudioDescriptorDump(ADesc);
876 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
878 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
879 GenRandomUUID(RNG, Info.AssetUUID);
881 if ( Options.use_smpte_labels )
883 Info.LabelSetType = LS_MXF_INTEROP;
884 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
887 // configure encryption
888 if( Options.key_flag )
890 GenRandomUUID(RNG, Info.ContextID);
891 Info.EncryptedEssence = true;
893 if ( Options.key_id_flag )
894 memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
896 RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
898 Context = new AESEncContext;
899 result = Context->InitKey(Options.key_value);
901 if ( ASDCP_SUCCESS(result) )
902 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
904 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
906 Info.UsesHMAC = true;
907 HMAC = new HMACContext;
908 result = HMAC->InitKey(Options.key_value);
912 if ( ASDCP_SUCCESS(result) )
913 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
916 if ( ASDCP_SUCCESS(result) )
918 result = Parser.Reset();
921 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
923 result = Parser.ReadFrame(FrameBuffer);
925 if ( ASDCP_SUCCESS(result) )
927 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
929 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
930 fprintf(stderr, "Expecting %lu bytes, got %lu.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
931 result = RESULT_ENDOFFILE;
935 if ( Options.verbose_flag )
936 FrameBuffer.Dump(stderr, Options.fb_dump_size);
938 if ( ! Options.no_write_flag )
940 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
942 // The Writer class will forward the last block of ciphertext
943 // to the encryption context for use as the IV for the next
944 // frame. If you want to use non-sequitur IV values, un-comment
945 // the following line of code.
946 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
947 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
952 if ( result == RESULT_ENDOFFILE )
956 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
957 result = Writer.Finalize();
962 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
963 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
964 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
967 read_PCM_file(CommandOptions& Options)
969 AESDecContext* Context = 0;
970 HMACContext* HMAC = 0;
971 PCM::MXFReader Reader;
972 PCM::FrameBuffer FrameBuffer;
973 WavFileWriter OutWave;
974 PCM::AudioDescriptor ADesc;
975 ui32_t last_frame = 0;
977 Result_t result = Reader.OpenRead(Options.filenames[0]);
979 if ( ASDCP_SUCCESS(result) )
981 Reader.FillAudioDescriptor(ADesc);
983 if ( ADesc.SampleRate != EditRate_23_98
984 && ADesc.SampleRate != EditRate_24
985 && ADesc.SampleRate != EditRate_48 )
986 ADesc.SampleRate = Options.PictureRate();
988 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
990 if ( Options.verbose_flag )
991 PCM::AudioDescriptorDump(ADesc);
994 if ( ASDCP_SUCCESS(result) )
996 last_frame = ADesc.ContainerDuration;
998 if ( Options.duration > 0 && Options.duration < last_frame )
999 last_frame = Options.duration;
1001 if ( Options.start_frame > 0 )
1003 if ( Options.start_frame > ADesc.ContainerDuration )
1005 fprintf(stderr, "Start value greater than file duration.\n");
1009 last_frame = xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1012 ADesc.ContainerDuration = last_frame - Options.start_frame;
1013 OutWave.OpenWrite(ADesc, Options.file_root, Options.split_wav);
1016 if ( ASDCP_SUCCESS(result) && Options.key_flag )
1018 Context = new AESDecContext;
1019 result = Context->InitKey(Options.key_value);
1021 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1024 Reader.FillWriterInfo(Info);
1026 if ( Info.UsesHMAC )
1028 HMAC = new HMACContext;
1029 result = HMAC->InitKey(Options.key_value);
1033 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1038 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1040 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1042 if ( ASDCP_SUCCESS(result) )
1044 if ( Options.verbose_flag )
1045 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1047 result = OutWave.WriteFrame(FrameBuffer);
1055 //------------------------------------------------------------------------------------------
1059 // These classes wrap the irregular names in the asdcplib API
1060 // so that I can use a template to simplify the implementation
1061 // of show_file_info()
1063 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1066 void FillDescriptor(MPEG2::MXFReader& Reader) {
1067 Reader.FillVideoDescriptor(*this);
1070 void Dump(FILE* stream) {
1071 MPEG2::VideoDescriptorDump(*this, stream);
1075 class MyPictureDescriptor : public JP2K::PictureDescriptor
1078 void FillDescriptor(JP2K::MXFReader& Reader) {
1079 Reader.FillPictureDescriptor(*this);
1082 void Dump(FILE* stream) {
1083 JP2K::PictureDescriptorDump(*this, stream);
1087 class MyAudioDescriptor : public PCM::AudioDescriptor
1090 void FillDescriptor(PCM::MXFReader& Reader) {
1091 Reader.FillAudioDescriptor(*this);
1094 void Dump(FILE* stream) {
1095 PCM::AudioDescriptorDump(*this, stream);
1100 // MSVC didn't like the function template, so now it's a static class method
1101 template<class ReaderT, class DescriptorT>
1102 class FileInfoWrapper
1105 static void file_info(CommandOptions& Options, FILE* stream = 0)
1110 if ( Options.verbose_flag || Options.showheader_flag )
1113 Result_t result = Reader.OpenRead(Options.filenames[0]);
1115 if ( ASDCP_SUCCESS(result) )
1117 if ( Options.showheader_flag )
1118 Reader.DumpHeaderMetadata(stream);
1121 Reader.FillWriterInfo(WI);
1122 WriterInfoDump(WI, stream);
1125 Desc.FillDescriptor(Reader);
1128 if ( Options.showindex_flag )
1129 Reader.DumpIndex(stream);
1131 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1133 Reader.DumpHeaderMetadata(stream);
1139 // Read header metadata from an ASDCP file
1142 show_file_info(CommandOptions& Options)
1144 EssenceType_t EssenceType;
1145 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1147 if ( ASDCP_FAILURE(result) )
1150 if ( EssenceType == ESS_MPEG2_VES )
1152 fputs("File essence type is MPEG2 video.\n", stdout);
1153 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1155 else if ( EssenceType == ESS_PCM_24b_48k )
1157 fputs("File essence type is PCM audio.\n", stdout);
1158 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1160 else if ( EssenceType == ESS_JPEG_2000 )
1162 fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1163 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1167 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1169 MXF::OPAtomHeader TestHeader;
1171 result = Reader.OpenRead(Options.filenames[0]);
1173 if ( ASDCP_SUCCESS(result) )
1174 result = TestHeader.InitFromFile(Reader); // test UL and OP
1176 if ( ASDCP_SUCCESS(result) )
1178 TestHeader.Partition::Dump();
1180 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1183 fputs("File contains no Identification object.\n", stdout);
1185 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1188 fputs("File contains no SourcePackage object.\n", stdout);
1192 fputs("File is not MXF.\n", stdout);
1202 main(int argc, const char** argv)
1204 Result_t result = RESULT_OK;
1205 CommandOptions Options(argc, argv);
1207 if ( Options.help_flag )
1213 if ( Options.error_flag )
1216 if ( Options.version_flag )
1219 if ( Options.info_flag )
1221 result = show_file_info(Options);
1223 else if ( Options.gop_start_flag )
1225 result = gop_start_test(Options);
1227 else if ( Options.genkey_flag )
1230 byte_t bin_buf[KeyLen];
1233 RNG.FillRandom(bin_buf, KeyLen);
1234 printf("%s\n", bin2hex(bin_buf, KeyLen, str_buf, 40));
1236 else if ( Options.genid_flag )
1239 byte_t bin_buf[KeyLen];
1242 GenRandomUUID(RNG, bin_buf);
1243 bin2hex(bin_buf, KeyLen, str_buf, 40);
1244 printf("%s\n", hyphenate_UUID(str_buf, 40));
1246 else if ( Options.extract_flag )
1248 EssenceType_t EssenceType;
1249 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1251 if ( ASDCP_SUCCESS(result) )
1253 switch ( EssenceType )
1256 result = read_MPEG2_file(Options);
1260 result = read_JP2K_file(Options);
1263 case ESS_PCM_24b_48k:
1264 result = read_PCM_file(Options);
1268 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1273 else if ( Options.create_flag )
1275 if ( Options.do_repeat && ! Options.duration_flag )
1277 fputs("Option -R requires -d <duration>\n", stderr);
1281 EssenceType_t EssenceType;
1282 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1284 if ( ASDCP_SUCCESS(result) )
1286 switch ( EssenceType )
1289 result = write_MPEG2_file(Options);
1293 result = write_JP2K_file(Options);
1296 case ESS_PCM_24b_48k:
1297 result = write_PCM_file(Options);
1301 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1302 Options.filenames[0]);
1308 if ( result != RESULT_OK )
1310 fputs("Program stopped on error.\n", stderr);
1312 if ( result != RESULT_FAIL )
1314 fputs(GetResultString(result), stderr);
1315 fputc('\n', stderr);
1326 // end asdcp-test.cpp