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;
272 case 'g': genkey_flag = true; break;
273 case 'u': genid_flag = true; break;
274 case 'e': encrypt_header_flag = true; break;
275 case 'E': encrypt_header_flag = false; break;
276 case 'M': write_hmac = false; break;
277 case 'm': read_hmac = true; break;
280 TEST_SET_MAJOR_MODE(create_flag);
281 TEST_EXTRA_ARG(i, 'c');
286 TEST_SET_MAJOR_MODE(extract_flag);
287 TEST_EXTRA_ARG(i, 'x');
291 case 'k': key_flag = true;
292 TEST_EXTRA_ARG(i, 'k');
295 hex2bin(argv[i], key_value, KeyLen, &length);
297 if ( length != KeyLen )
299 fprintf(stderr, "Unexpected key length: %lu, expecting %lu characters.\n", KeyLen, length);
305 case 'j': key_id_flag = true;
306 TEST_EXTRA_ARG(i, 'j');
309 hex2bin(argv[i], key_id_value, KeyIDlen, &length);
311 if ( length != KeyIDlen )
313 fprintf(stderr, "Unexpected key ID length: %lu, expecting %lu characters.\n", KeyIDlen, length);
320 TEST_EXTRA_ARG(i, 'f');
321 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
325 TEST_EXTRA_ARG(i, 'd');
326 duration_flag = true;
327 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
331 TEST_EXTRA_ARG(i, 'p');
332 picture_rate = atoi(argv[i]);
336 TEST_EXTRA_ARG(i, 's');
337 fb_dump_size = atoi(argv[i]);
341 TEST_EXTRA_ARG(i, 'b');
342 fb_size = atoi(argv[i]);
345 fprintf(stderr, "Frame Buffer size: %lu bytes.\n", fb_size);
350 fprintf(stderr, "Unrecognized option: %c\n", argv[i][1]);
356 filenames[file_count++] = argv[i];
358 if ( file_count >= MAX_IN_FILES )
360 fprintf(stderr, "Filename lists exceeds maximum list size: %lu\n", MAX_IN_FILES);
366 if ( TEST_MAJOR_MODE() )
368 if ( ! genkey_flag && ! genid_flag && file_count == 0 )
370 fputs("Option requires at least one filename argument.\n", stderr);
375 if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
377 fputs("No operation selected (use one of -(gcixG) or -h for help).\n", stderr);
385 //------------------------------------------------------------------------------------------
388 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
389 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
392 write_MPEG2_file(CommandOptions& Options)
394 AESEncContext* Context = 0;
395 HMACContext* HMAC = 0;
396 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
397 MPEG2::Parser Parser;
398 MPEG2::MXFWriter Writer;
399 MPEG2::VideoDescriptor VDesc;
400 byte_t IV_buf[CBC_BLOCK_SIZE];
403 // set up essence parser
404 Result_t result = Parser.OpenRead(Options.filenames[0]);
407 if ( ASDCP_SUCCESS(result) )
409 Parser.FillVideoDescriptor(VDesc);
411 if ( Options.verbose_flag )
413 fputs("MPEG-2 Pictures\n", stderr);
414 fputs("VideoDescriptor:\n", stderr);
415 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
416 MPEG2::VideoDescriptorDump(VDesc);
420 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
422 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
423 GenRandomUUID(RNG, Info.AssetUUID);
425 // configure encryption
426 if( Options.key_flag )
428 GenRandomUUID(RNG, Info.ContextID);
429 Info.EncryptedEssence = true;
431 if ( Options.key_id_flag )
432 memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
434 RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
436 Context = new AESEncContext;
437 result = Context->InitKey(Options.key_value);
439 if ( ASDCP_SUCCESS(result) )
440 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
442 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
444 Info.UsesHMAC = true;
445 HMAC = new HMACContext;
446 result = HMAC->InitKey(Options.key_value);
450 if ( ASDCP_SUCCESS(result) )
451 result = Writer.OpenWrite(Options.out_file, Info, VDesc);
454 if ( ASDCP_SUCCESS(result) )
455 // loop through the frames
457 result = Parser.Reset();
460 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
462 if ( ! Options.do_repeat || duration == 1 )
464 result = Parser.ReadFrame(FrameBuffer);
466 if ( ASDCP_SUCCESS(result) )
468 if ( Options.verbose_flag )
469 FrameBuffer.Dump(stderr, Options.fb_dump_size);
471 if ( Options.encrypt_header_flag )
472 FrameBuffer.PlaintextOffset(0);
476 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
478 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
480 // The Writer class will forward the last block of ciphertext
481 // to the encryption context for use as the IV for the next
482 // frame. If you want to use non-sequitur IV values, un-comment
483 // the following line of code.
484 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
485 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
489 if ( result == RESULT_ENDOFFILE )
493 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
494 result = Writer.Finalize();
499 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
500 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
501 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
504 read_MPEG2_file(CommandOptions& Options)
506 AESDecContext* Context = 0;
507 HMACContext* HMAC = 0;
508 MPEG2::MXFReader Reader;
509 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
511 ui32_t frame_count = 0;
513 Result_t result = Reader.OpenRead(Options.filenames[0]);
515 if ( ASDCP_SUCCESS(result) )
517 MPEG2::VideoDescriptor VDesc;
518 Reader.FillVideoDescriptor(VDesc);
519 frame_count = VDesc.ContainerDuration;
521 if ( Options.verbose_flag )
523 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
524 MPEG2::VideoDescriptorDump(VDesc);
528 if ( ASDCP_SUCCESS(result) )
531 snprintf(filename, 256, "%s.ves", Options.file_root);
532 result = OutFile.OpenWrite(filename);
535 if ( ASDCP_SUCCESS(result) && Options.key_flag )
537 Context = new AESDecContext;
538 result = Context->InitKey(Options.key_value);
540 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
543 Reader.FillWriterInfo(Info);
547 HMAC = new HMACContext;
548 result = HMAC->InitKey(Options.key_value);
552 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
557 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
558 if ( last_frame > frame_count )
559 last_frame = frame_count;
561 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
563 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
565 if ( ASDCP_SUCCESS(result) )
567 if ( Options.verbose_flag )
568 FrameBuffer.Dump(stderr, Options.fb_dump_size);
570 ui32_t write_count = 0;
571 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
581 gop_start_test(CommandOptions& Options)
583 using namespace ASDCP::MPEG2;
586 MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
587 ui32_t frame_count = 0;
589 Result_t result = Reader.OpenRead(Options.filenames[0]);
591 if ( ASDCP_SUCCESS(result) )
593 MPEG2::VideoDescriptor VDesc;
594 Reader.FillVideoDescriptor(VDesc);
595 frame_count = VDesc.ContainerDuration;
597 if ( Options.verbose_flag )
599 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
600 MPEG2::VideoDescriptorDump(VDesc);
604 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
605 if ( last_frame > frame_count )
606 last_frame = frame_count;
608 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
610 result = Reader.ReadFrameGOPStart(i, FrameBuffer);
612 if ( ASDCP_SUCCESS(result) )
614 if ( Options.verbose_flag )
615 FrameBuffer.Dump(stderr, Options.fb_dump_size);
617 if ( FrameBuffer.FrameType() != FRAME_I )
618 fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
620 fprintf(stderr, "Requested frame %lu, got %lu\n", i, FrameBuffer.FrameNumber());
627 //------------------------------------------------------------------------------------------
630 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
631 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
634 write_JP2K_file(CommandOptions& Options)
636 AESEncContext* Context = 0;
637 HMACContext* HMAC = 0;
638 JP2K::MXFWriter Writer;
639 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
640 JP2K::PictureDescriptor PDesc;
641 JP2K::SequenceParser Parser;
642 byte_t IV_buf[CBC_BLOCK_SIZE];
645 // set up essence parser
646 Result_t result = Parser.OpenRead(Options.filenames[0]);
649 if ( ASDCP_SUCCESS(result) )
651 Parser.FillPictureDescriptor(PDesc);
652 PDesc.EditRate = Options.PictureRate();
654 if ( Options.verbose_flag )
656 fprintf(stderr, "JPEG 2000 pictures\n");
657 fputs("PictureDescriptor:\n", stderr);
658 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
659 JP2K::PictureDescriptorDump(PDesc);
663 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
665 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
666 GenRandomUUID(RNG, Info.AssetUUID);
668 // configure encryption
669 if( Options.key_flag )
671 GenRandomUUID(RNG, Info.ContextID);
672 Info.EncryptedEssence = true;
674 if ( Options.key_id_flag )
675 memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
677 RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
679 Context = new AESEncContext;
680 result = Context->InitKey(Options.key_value);
682 if ( ASDCP_SUCCESS(result) )
683 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
685 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
687 Info.UsesHMAC = true;
688 HMAC = new HMACContext;
689 result = HMAC->InitKey(Options.key_value);
693 if ( ASDCP_SUCCESS(result) )
694 result = Writer.OpenWrite(Options.out_file, Info, PDesc);
697 if ( ASDCP_SUCCESS(result) )
700 result = Parser.Reset();
702 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
704 if ( ! Options.do_repeat || duration == 1 )
706 result = Parser.ReadFrame(FrameBuffer);
708 if ( ASDCP_SUCCESS(result) )
710 if ( Options.verbose_flag )
711 FrameBuffer.Dump(stderr, Options.fb_dump_size);
713 if ( Options.encrypt_header_flag )
714 FrameBuffer.PlaintextOffset(0);
718 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
720 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
722 // The Writer class will forward the last block of ciphertext
723 // to the encryption context for use as the IV for the next
724 // frame. If you want to use non-sequitur IV values, un-comment
725 // the following line of code.
726 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
727 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
731 if ( result == RESULT_ENDOFFILE )
735 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
736 result = Writer.Finalize();
741 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
742 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
743 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
746 read_JP2K_file(CommandOptions& Options)
748 AESDecContext* Context = 0;
749 HMACContext* HMAC = 0;
750 JP2K::MXFReader Reader;
751 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
752 ui32_t frame_count = 0;
754 Result_t result = Reader.OpenRead(Options.filenames[0]);
756 if ( ASDCP_SUCCESS(result) )
758 JP2K::PictureDescriptor PDesc;
759 Reader.FillPictureDescriptor(PDesc);
761 frame_count = PDesc.ContainerDuration;
763 if ( Options.verbose_flag )
765 fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
766 JP2K::PictureDescriptorDump(PDesc);
770 if ( ASDCP_SUCCESS(result) && Options.key_flag )
772 Context = new AESDecContext;
773 result = Context->InitKey(Options.key_value);
775 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
778 Reader.FillWriterInfo(Info);
782 HMAC = new HMACContext;
783 result = HMAC->InitKey(Options.key_value);
787 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
792 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
793 if ( last_frame > frame_count )
794 last_frame = frame_count;
796 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
798 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
800 if ( ASDCP_SUCCESS(result) )
805 snprintf(filename, 256, "%s%06lu.j2c", Options.file_root, i);
806 result = OutFile.OpenWrite(filename);
808 if ( ASDCP_SUCCESS(result) )
809 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
811 if ( Options.verbose_flag )
812 FrameBuffer.Dump(stderr, Options.fb_dump_size);
819 //------------------------------------------------------------------------------------------
823 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
824 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
827 write_PCM_file(CommandOptions& Options)
829 AESEncContext* Context = 0;
830 HMACContext* HMAC = 0;
831 PCMParserList Parser;
832 PCM::MXFWriter Writer;
833 PCM::FrameBuffer FrameBuffer;
834 PCM::AudioDescriptor ADesc;
835 Rational PictureRate = Options.PictureRate();
836 byte_t IV_buf[CBC_BLOCK_SIZE];
839 // set up essence parser
840 Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
843 if ( ASDCP_SUCCESS(result) )
845 Parser.FillAudioDescriptor(ADesc);
847 ADesc.SampleRate = PictureRate;
848 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
850 if ( Options.verbose_flag )
852 fprintf(stderr, "48Khz PCM Audio, %s fps (%lu spf)\n",
853 Options.szPictureRate(),
854 PCM::CalcSamplesPerFrame(ADesc));
855 fputs("AudioDescriptor:\n", stderr);
856 PCM::AudioDescriptorDump(ADesc);
860 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
862 WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
863 GenRandomUUID(RNG, Info.AssetUUID);
865 // configure encryption
866 if( Options.key_flag )
868 GenRandomUUID(RNG, Info.ContextID);
869 Info.EncryptedEssence = true;
871 if ( Options.key_id_flag )
872 memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
874 RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
876 Context = new AESEncContext;
877 result = Context->InitKey(Options.key_value);
879 if ( ASDCP_SUCCESS(result) )
880 result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
882 if ( ASDCP_SUCCESS(result) && Options.write_hmac )
884 Info.UsesHMAC = true;
885 HMAC = new HMACContext;
886 result = HMAC->InitKey(Options.key_value);
890 if ( ASDCP_SUCCESS(result) )
891 result = Writer.OpenWrite(Options.out_file, Info, ADesc);
894 if ( ASDCP_SUCCESS(result) )
896 result = Parser.Reset();
899 while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
901 result = Parser.ReadFrame(FrameBuffer);
903 if ( ASDCP_SUCCESS(result) )
905 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
907 fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
908 fprintf(stderr, "Expecting %lu bytes, got %lu.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
909 result = RESULT_ENDOFFILE;
913 if ( Options.verbose_flag )
914 FrameBuffer.Dump(stderr, Options.fb_dump_size);
916 if ( ! Options.no_write_flag )
918 result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
920 // The Writer class will forward the last block of ciphertext
921 // to the encryption context for use as the IV for the next
922 // frame. If you want to use non-sequitur IV values, un-comment
923 // the following line of code.
924 // if ( ASDCP_SUCCESS(result) && Options.key_flag )
925 // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
930 if ( result == RESULT_ENDOFFILE )
934 if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
935 result = Writer.Finalize();
940 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
941 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
942 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
945 read_PCM_file(CommandOptions& Options)
947 AESDecContext* Context = 0;
948 HMACContext* HMAC = 0;
949 PCM::MXFReader Reader;
950 PCM::FrameBuffer FrameBuffer;
951 WavFileWriter OutWave;
952 PCM::AudioDescriptor ADesc;
953 ui32_t last_frame = 0;
955 Result_t result = Reader.OpenRead(Options.filenames[0]);
957 if ( ASDCP_SUCCESS(result) )
959 Reader.FillAudioDescriptor(ADesc);
961 if ( ADesc.SampleRate != EditRate_23_98
962 && ADesc.SampleRate != EditRate_24
963 && ADesc.SampleRate != EditRate_48 )
964 ADesc.SampleRate = Options.PictureRate();
966 FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
968 if ( Options.verbose_flag )
969 PCM::AudioDescriptorDump(ADesc);
972 if ( ASDCP_SUCCESS(result) )
974 last_frame = ADesc.ContainerDuration;
976 if ( Options.duration > 0 && Options.duration < last_frame )
977 last_frame = Options.duration;
979 if ( Options.start_frame > 0 )
981 if ( Options.start_frame > ADesc.ContainerDuration )
983 fprintf(stderr, "Start value greater than file duration.\n");
987 last_frame = xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
990 ADesc.ContainerDuration = last_frame - Options.start_frame;
991 OutWave.OpenWrite(ADesc, Options.file_root, Options.split_wav);
994 if ( ASDCP_SUCCESS(result) && Options.key_flag )
996 Context = new AESDecContext;
997 result = Context->InitKey(Options.key_value);
999 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1002 Reader.FillWriterInfo(Info);
1004 if ( Info.UsesHMAC )
1006 HMAC = new HMACContext;
1007 result = HMAC->InitKey(Options.key_value);
1011 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1016 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1018 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1020 if ( ASDCP_SUCCESS(result) )
1022 if ( Options.verbose_flag )
1023 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1025 result = OutWave.WriteFrame(FrameBuffer);
1033 //------------------------------------------------------------------------------------------
1037 // These classes wrap the irregular names in the asdcplib API
1038 // so that I can use a template to simplify the implementation
1039 // of show_file_info()
1041 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1044 void FillDescriptor(MPEG2::MXFReader& Reader) {
1045 Reader.FillVideoDescriptor(*this);
1048 void Dump(FILE* stream) {
1049 MPEG2::VideoDescriptorDump(*this, stream);
1053 class MyPictureDescriptor : public JP2K::PictureDescriptor
1056 void FillDescriptor(JP2K::MXFReader& Reader) {
1057 Reader.FillPictureDescriptor(*this);
1060 void Dump(FILE* stream) {
1061 JP2K::PictureDescriptorDump(*this, stream);
1065 class MyAudioDescriptor : public PCM::AudioDescriptor
1068 void FillDescriptor(PCM::MXFReader& Reader) {
1069 Reader.FillAudioDescriptor(*this);
1072 void Dump(FILE* stream) {
1073 PCM::AudioDescriptorDump(*this, stream);
1078 // MSVC didn't like the function template, so now it's a static class method
1079 template<class ReaderT, class DescriptorT>
1080 class FileInfoWrapper
1083 static void file_info(CommandOptions& Options, FILE* stream = 0)
1088 if ( Options.verbose_flag || Options.showheader_flag )
1091 Result_t result = Reader.OpenRead(Options.filenames[0]);
1093 if ( ASDCP_SUCCESS(result) )
1095 if ( Options.showheader_flag )
1096 Reader.DumpHeaderMetadata(stream);
1099 Reader.FillWriterInfo(WI);
1100 WriterInfoDump(WI, stream);
1103 Desc.FillDescriptor(Reader);
1106 if ( Options.showindex_flag )
1107 Reader.DumpIndex(stream);
1109 else if ( result == RESULT_FORMAT && Options.showheader_flag )
1111 Reader.DumpHeaderMetadata(stream);
1117 // Read header metadata from an ASDCP file
1120 show_file_info(CommandOptions& Options)
1122 EssenceType_t EssenceType;
1123 Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1125 if ( ASDCP_FAILURE(result) )
1128 if ( EssenceType == ESS_MPEG2_VES )
1130 fputs("File essence type is MPEG2 video.\n", stdout);
1131 FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1133 else if ( EssenceType == ESS_PCM_24b_48k )
1135 fputs("File essence type is PCM audio.\n", stdout);
1136 FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1138 else if ( EssenceType == ESS_JPEG_2000 )
1140 fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1141 FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1145 fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1147 MXF::OPAtomHeader TestHeader;
1149 result = Reader.OpenRead(Options.filenames[0]);
1151 if ( ASDCP_SUCCESS(result) )
1152 result = TestHeader.InitFromFile(Reader); // test UL and OP
1154 if ( ASDCP_SUCCESS(result) )
1156 TestHeader.Partition::Dump();
1158 if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1161 fputs("File contains no Identification object.\n", stdout);
1163 if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1166 fputs("File contains no SourcePackage object.\n", stdout);
1170 fputs("File is not MXF.\n", stdout);
1180 main(int argc, const char** argv)
1182 Result_t result = RESULT_OK;
1183 CommandOptions Options(argc, argv);
1185 if ( Options.help_flag )
1191 if ( Options.error_flag )
1194 if ( Options.version_flag )
1197 if ( Options.info_flag )
1199 result = show_file_info(Options);
1201 else if ( Options.gop_start_flag )
1203 result = gop_start_test(Options);
1205 else if ( Options.genkey_flag )
1208 byte_t bin_buf[KeyLen];
1211 RNG.FillRandom(bin_buf, KeyLen);
1212 printf("%s\n", bin2hex(bin_buf, KeyLen, str_buf, 40));
1214 else if ( Options.genid_flag )
1217 byte_t bin_buf[KeyLen];
1220 GenRandomUUID(RNG, bin_buf);
1221 bin2hex(bin_buf, KeyLen, str_buf, 40);
1222 printf("%s\n", hyphenate_UUID(str_buf, 40));
1224 else if ( Options.extract_flag )
1226 EssenceType_t EssenceType;
1227 result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1229 fprintf(stderr, "ATTENTION! Expecting SMPTE Universal Labels\n");
1231 if ( ASDCP_SUCCESS(result) )
1233 switch ( EssenceType )
1236 result = read_MPEG2_file(Options);
1240 result = read_JP2K_file(Options);
1243 case ESS_PCM_24b_48k:
1244 result = read_PCM_file(Options);
1248 fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1253 else if ( Options.create_flag )
1255 fprintf(stderr, "ATTENTION! This version of asdcplib does not support writing MXF files.\n");
1257 if ( Options.do_repeat && ! Options.duration_flag )
1259 fputs("Option -R requires -d <duration>\n", stderr);
1263 EssenceType_t EssenceType;
1264 result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1266 fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1268 if ( ASDCP_SUCCESS(result) )
1270 switch ( EssenceType )
1273 result = write_MPEG2_file(Options);
1277 result = write_JP2K_file(Options);
1280 case ESS_PCM_24b_48k:
1281 result = write_PCM_file(Options);
1285 fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1286 Options.filenames[0]);
1292 if ( result != RESULT_OK )
1294 fputs("Program stopped on error.\n", stderr);
1296 if ( result != RESULT_FAIL )
1298 fputs(GetResultString(result), stderr);
1299 fputc('\n', stderr);
1310 // end asdcp-test.cpp