bugfix in indexing
[asdcplib.git] / src / asdcp-test.cpp
1 /*
2 Copyright (c) 2003-2005, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
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.
15
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.
26 */
27 /*! \file    asdcp-test.cpp
28     \version $Id$       
29     \brief   AS-DCP file manipulation utility
30
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.
34
35   For more information about asdcplib, please refer to the header file AS_DCP.h
36
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.
41
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
47   these features.
48 */
49
50 #include <iostream>
51 #include <assert.h>
52
53 #include <FileIO.h>
54 #include <PCMParserList.h>
55 #include <WavFileWriter.h>
56 #include <hex_utils.h>
57 #include <AS_DCP_UUID.h>
58 #include <MXF.h>
59 #include <Metadata.h>
60
61 using namespace ASDCP;
62
63 const ui32_t FRAME_BUFFER_SIZE = 4*1024*1024;
64
65 //------------------------------------------------------------------------------------------
66 //
67 // command line option parser class
68
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
72
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
75 {
76 public:
77   MyInfo()
78   {
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 };
82       
83       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84       CompanyName = "WidgetCo";
85       ProductName = "asdcp-test";
86
87       char s_buf[128];
88       snprintf(s_buf, 128, "%lu.%lu.%lu", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
89       ProductVersion = s_buf;
90   }
91 } s_MyInfo;
92
93
94 // Macros used to test command option data state.
95
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 )
98
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() ) \
102                                  { \
103                                    fputs("Conflicting major mode, choose one of -(gcixG)).\n", stderr); \
104                                    return; \
105                                  } \
106                                  (f) = true;
107
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] == '-' ) \
112                                  { \
113                                    fprintf(stderr, "Argument not found for option %c.\n", (c)); \
114                                    return; \
115                                  }
116 //
117 void
118 banner(FILE* stream = stderr)
119 {
120   fprintf(stream, "\n\
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);
127 }
128
129 //
130 void
131 usage(FILE* stream = stderr)
132 {
133   fprintf(stream, "\
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\
137 \n", PACKAGE);
138
139   fprintf(stream, "\
140 Major modes:\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\
147   -V              - show version\n\
148   -h              - show help\n\
149 \n");
150
151   fprintf(stream, "\
152 Security Options:\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\
159 \n");
160
161   fprintf(stream, "\
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\
170 \n");
171
172   fprintf(stream, "\
173 Info Options:\n\
174   -H              - show MXF header metadata, used with option -i\n\
175   -n              - show index, used with option -i\n\
176 \n\
177 Other Options:\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\
180 \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\
184 \n");
185 }
186
187 //
188 //
189 class CommandOptions
190 {
191   CommandOptions();
192
193 public:
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
226
227   //
228   Rational PictureRate()
229   {
230     if ( picture_rate == 23 ) return EditRate_23_98;
231     if ( picture_rate == 48 ) return EditRate_48;
232     return EditRate_24;
233   }
234
235   //
236   const char* szPictureRate()
237   {
238     if ( picture_rate == 23 ) return "23.976";
239     if ( picture_rate == 48 ) return "48";
240     return "24";
241   }
242
243   //
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)
252   {
253     memset(key_value, 0, KeyLen);
254     memset(key_id_value, 0, KeyIDlen);
255
256     for ( int i = 1; i < argc; i++ )
257       {
258         if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
259           {
260             switch ( argv[i][1] )
261               {
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':
273 #ifdef ASDCP_WITHOUT_OPENSSL
274                 fputs("Program compiled without encryption support.\n", stderr);
275                 return;
276 #else
277                 genkey_flag = true;
278 #endif
279                 break;
280
281               case 'u':
282 #ifdef ASDCP_WITHOUT_OPENSSL
283                 fputs("Program compiled without encryption support.\n", stderr);
284                 return;
285 #else
286                 genid_flag = true;
287 #endif
288                 break;
289
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;
294
295               case 'c':
296                 TEST_SET_MAJOR_MODE(create_flag);
297                 TEST_EXTRA_ARG(i, 'c');
298                 out_file = argv[i];
299                 break;
300
301               case 'x':
302                 TEST_SET_MAJOR_MODE(extract_flag);
303                 TEST_EXTRA_ARG(i, 'x');
304                 file_root = argv[i];
305                 break;
306
307               case 'k': key_flag = true;
308 #ifdef ASDCP_WITHOUT_OPENSSL
309                 fputs("Program compiled without encryption support.\n", stderr);
310                 return;
311 #else
312                 TEST_EXTRA_ARG(i, 'k');
313                 {
314                   ui32_t length;
315                   hex2bin(argv[i], key_value, KeyLen, &length);
316
317                   if ( length != KeyLen )
318                     {
319                       fprintf(stderr, "Unexpected key length: %lu, expecting %lu characters.\n", KeyLen, length);
320                       return;
321                     }
322                 }
323 #endif
324                 break;
325
326               case 'j': key_id_flag = true;
327 #ifdef ASDCP_WITHOUT_OPENSSL
328                 fputs("Program compiled without encryption support.\n", stderr);
329                 return;
330 #else
331                 TEST_EXTRA_ARG(i, 'j');
332                 {
333                   ui32_t length;
334                   hex2bin(argv[i], key_id_value, KeyIDlen, &length);
335
336                   if ( length != KeyIDlen )
337                     {
338                       fprintf(stderr, "Unexpected key ID length: %lu, expecting %lu characters.\n", KeyIDlen, length);
339                       return;
340                     }
341                 }
342 #endif
343                 break;
344
345               case 'f':
346                 TEST_EXTRA_ARG(i, 'f');
347                 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
348                 break;
349
350               case 'd':
351                 TEST_EXTRA_ARG(i, 'd');
352                 duration_flag = true;
353                 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
354                 break;
355
356               case 'p':
357                 TEST_EXTRA_ARG(i, 'p');
358                 picture_rate = atoi(argv[i]);
359                 break;
360
361               case 's':
362                 TEST_EXTRA_ARG(i, 's');
363                 fb_dump_size = atoi(argv[i]);
364                 break;
365
366               case 'b':
367                 TEST_EXTRA_ARG(i, 'b');
368                 fb_size = atoi(argv[i]);
369
370                 if ( verbose_flag )
371                   fprintf(stderr, "Frame Buffer size: %lu bytes.\n", fb_size);
372
373                 break;
374
375               default:
376                 fprintf(stderr, "Unrecognized option: %c\n", argv[i][1]);
377                 return;
378               }
379           }
380         else
381           {
382             filenames[file_count++] = argv[i];
383
384             if ( file_count >= MAX_IN_FILES )
385               {
386                 fprintf(stderr, "Filename lists exceeds maximum list size: %lu\n", MAX_IN_FILES);
387                 return;
388               }
389           }
390       }
391
392     if ( TEST_MAJOR_MODE() )
393       {
394         if ( ! genkey_flag && ! genid_flag && file_count == 0 )
395           {
396             fputs("Option requires at least one filename argument.\n", stderr);
397             return;
398           }
399       }
400
401     if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
402       {
403         fputs("No operation selected (use one of -(gcixG) or -h for help).\n", stderr);
404         return;
405       }
406
407     error_flag = false;
408   }
409 };
410
411 //------------------------------------------------------------------------------------------
412 // MPEG2 essence
413 #if 0
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
416 //
417 Result_t
418 write_MPEG2_file(CommandOptions& Options)
419 {
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;
426
427 #ifndef ASDCP_WITHOUT_OPENSSL
428   byte_t             IV_buf[CBC_BLOCK_SIZE];
429   FortunaRNG         RNG;
430 #endif
431
432   // set up essence parser
433   Result_t result = Parser.OpenRead(Options.filenames[0]);
434
435   // set up MXF writer
436   if ( ASDCP_SUCCESS(result) )
437     {
438       Parser.FillVideoDescriptor(VDesc);
439
440       if ( Options.verbose_flag )
441         {
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);
446         }
447     }
448
449   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
450     {
451       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
452 #ifndef ASDCP_WITHOUT_OPENSSL
453       GenRandomUUID(RNG, Info.AssetUUID);
454
455       // configure encryption
456       if( Options.key_flag )
457         {
458           GenRandomUUID(RNG, Info.ContextID);
459           Info.EncryptedEssence = true;
460
461           if ( Options.key_id_flag )
462             memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
463           else
464             RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
465
466           Context = new AESEncContext;
467           result = Context->InitKey(Options.key_value);
468
469           if ( ASDCP_SUCCESS(result) )
470             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
471
472           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
473             {
474               Info.UsesHMAC = true;
475               HMAC = new HMACContext;
476               result = HMAC->InitKey(Options.key_value);
477             }
478         }
479 #endif // ASDCP_WITHOUT_OPENSSL
480
481       if ( ASDCP_SUCCESS(result) )
482         result = Writer.OpenWrite(Options.out_file, Info, VDesc);
483     }
484
485   if ( ASDCP_SUCCESS(result) )
486     // loop through the frames
487     {
488       result = Parser.Reset();
489       ui32_t duration = 0;
490
491       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
492         {
493           if ( ! Options.do_repeat || duration == 1 )
494             {
495               result = Parser.ReadFrame(FrameBuffer);
496
497               if ( ASDCP_SUCCESS(result) )
498                 {
499                   if ( Options.verbose_flag )
500                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
501                   
502                   if ( Options.encrypt_header_flag )
503                     FrameBuffer.PlaintextOffset(0);
504                 }
505             }
506
507           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
508             {
509               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
510
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));
518 #endif
519             }
520         }
521
522       if ( result == RESULT_ENDOFFILE )
523         result = RESULT_OK;
524     }
525
526   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
527     result = Writer.Finalize();
528
529   return result;
530 }
531 #endif
532
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
536 //
537 Result_t
538 read_MPEG2_file(CommandOptions& Options)
539 {
540   AESDecContext*     Context = 0;
541   HMACContext*       HMAC = 0;
542   MPEG2::MXFReader   Reader;
543   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
544   FileWriter         OutFile;
545   ui32_t             frame_count = 0;
546
547   Result_t result = Reader.OpenRead(Options.filenames[0]);
548
549   if ( ASDCP_SUCCESS(result) )
550     {
551       MPEG2::VideoDescriptor VDesc;
552       Reader.FillVideoDescriptor(VDesc);
553       frame_count = VDesc.ContainerDuration;
554
555       if ( Options.verbose_flag )
556         {
557           fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
558           MPEG2::VideoDescriptorDump(VDesc);
559         }
560     }
561
562   if ( ASDCP_SUCCESS(result) )
563     {
564       char filename[256];
565       snprintf(filename, 256, "%s.ves", Options.file_root);
566       result = OutFile.OpenWrite(filename);
567     }
568
569   if ( ASDCP_SUCCESS(result) && Options.key_flag )
570     {
571       Context = new AESDecContext;
572       result = Context->InitKey(Options.key_value);
573
574       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
575         {
576           WriterInfo Info;
577           Reader.FillWriterInfo(Info);
578
579           if ( Info.UsesHMAC )
580             {
581               HMAC = new HMACContext;
582               result = HMAC->InitKey(Options.key_value);
583             }
584           else
585             {
586               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
587             }
588         }
589     }
590
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;
594
595   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
596     {
597       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
598
599       if ( ASDCP_SUCCESS(result) )
600         {
601           if ( Options.verbose_flag )
602             FrameBuffer.Dump(stderr, Options.fb_dump_size);
603
604           ui32_t write_count = 0;
605           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
606         }
607     }
608
609   return result;
610 }
611
612
613 //
614 Result_t
615 gop_start_test(CommandOptions& Options)
616 {
617   using namespace ASDCP::MPEG2;
618
619   MXFReader   Reader;
620   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
621   ui32_t      frame_count = 0;
622
623   Result_t result = Reader.OpenRead(Options.filenames[0]);
624
625   if ( ASDCP_SUCCESS(result) )
626     {
627       MPEG2::VideoDescriptor VDesc;
628       Reader.FillVideoDescriptor(VDesc);
629       frame_count = VDesc.ContainerDuration;
630
631       if ( Options.verbose_flag )
632         {
633           fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
634           MPEG2::VideoDescriptorDump(VDesc);
635         }
636     }
637
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;
641
642   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
643     {
644       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
645
646       if ( ASDCP_SUCCESS(result) )
647         {
648           if ( Options.verbose_flag )
649             FrameBuffer.Dump(stderr, Options.fb_dump_size);
650
651           if ( FrameBuffer.FrameType() != FRAME_I )
652             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
653
654           fprintf(stderr, "Requested frame %lu, got %lu\n", i, FrameBuffer.FrameNumber());
655         }
656     }
657
658   return result;
659 }
660
661 #if 0
662 //------------------------------------------------------------------------------------------
663 // JPEG 2000 essence
664
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
667 //
668 Result_t
669 write_JP2K_file(CommandOptions& Options)
670 {
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;
677
678 #ifndef ASDCP_WITHOUT_OPENSSL
679   byte_t           IV_buf[CBC_BLOCK_SIZE];
680   FortunaRNG       RNG;
681 #endif
682
683   // set up essence parser
684   Result_t result = Parser.OpenRead(Options.filenames[0]);
685
686   // set up MXF writer
687   if ( ASDCP_SUCCESS(result) )
688     {
689       Parser.FillPictureDescriptor(PDesc);
690       PDesc.EditRate = Options.PictureRate();
691
692       if ( Options.verbose_flag )
693         {
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);
698         }
699     }
700
701   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
702     {
703       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
704 #ifndef ASDCP_WITHOUT_OPENSSL
705       GenRandomUUID(RNG, Info.AssetUUID);
706
707       // configure encryption
708       if( Options.key_flag )
709         {
710           GenRandomUUID(RNG, Info.ContextID);
711           Info.EncryptedEssence = true;
712
713           if ( Options.key_id_flag )
714             memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
715           else
716             RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
717
718           Context = new AESEncContext;
719           result = Context->InitKey(Options.key_value);
720
721           if ( ASDCP_SUCCESS(result) )
722             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
723
724           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
725             {
726               Info.UsesHMAC = true;
727               HMAC = new HMACContext;
728               result = HMAC->InitKey(Options.key_value);
729             }
730         }
731 #endif // ASDCP_WITHOUT_OPENSSL
732
733       if ( ASDCP_SUCCESS(result) )
734         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
735     }
736
737   if ( ASDCP_SUCCESS(result) )
738     {
739       ui32_t duration = 0;
740       result = Parser.Reset();
741
742       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
743         {
744           if ( ! Options.do_repeat || duration == 1 )
745             {
746               result = Parser.ReadFrame(FrameBuffer);
747
748               if ( ASDCP_SUCCESS(result) )
749                 {
750                   if ( Options.verbose_flag )
751                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
752                   
753                   if ( Options.encrypt_header_flag )
754                     FrameBuffer.PlaintextOffset(0);
755                 }
756             }
757
758           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
759             {
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));
768 #endif
769             }
770         }
771
772       if ( result == RESULT_ENDOFFILE )
773         result = RESULT_OK;
774     }
775
776   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
777     result = Writer.Finalize();
778
779   return result;
780 }
781 #endif
782
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
786 //
787 Result_t
788 read_JP2K_file(CommandOptions& Options)
789 {
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;
795
796   Result_t result = Reader.OpenRead(Options.filenames[0]);
797
798   if ( ASDCP_SUCCESS(result) )
799     {
800       JP2K::PictureDescriptor PDesc;
801       Reader.FillPictureDescriptor(PDesc);
802
803       frame_count = PDesc.ContainerDuration;
804
805       if ( Options.verbose_flag )
806         {
807           fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
808           JP2K::PictureDescriptorDump(PDesc);
809         }
810     }
811
812   if ( ASDCP_SUCCESS(result) && Options.key_flag )
813     {
814       Context = new AESDecContext;
815       result = Context->InitKey(Options.key_value);
816
817       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
818         {
819           WriterInfo Info;
820           Reader.FillWriterInfo(Info);
821
822           if ( Info.UsesHMAC )
823             {
824               HMAC = new HMACContext;
825               result = HMAC->InitKey(Options.key_value);
826             }
827           else
828             {
829               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
830             }
831         }
832     }
833
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;
837
838   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
839     {
840       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
841
842       if ( ASDCP_SUCCESS(result) )
843         {
844           FileWriter OutFile;
845           char filename[256];
846           ui32_t write_count;
847           snprintf(filename, 256, "%s%06lu.j2c", Options.file_root, i);
848           result = OutFile.OpenWrite(filename);
849
850           if ( ASDCP_SUCCESS(result) )
851             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
852
853           if ( Options.verbose_flag )
854             FrameBuffer.Dump(stderr, Options.fb_dump_size);
855         }
856     }
857
858   return result;
859 }
860
861 #if 0
862 //------------------------------------------------------------------------------------------
863 // PCM essence
864
865
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
868 //
869 Result_t
870 write_PCM_file(CommandOptions& Options)
871 {
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();
879
880 #ifndef ASDCP_WITHOUT_OPENSSL
881   byte_t           IV_buf[CBC_BLOCK_SIZE];
882   FortunaRNG       RNG;
883 #endif
884
885   // set up essence parser
886   Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
887
888   // set up MXF writer
889   if ( ASDCP_SUCCESS(result) )
890     {
891       Parser.FillAudioDescriptor(ADesc);
892
893       ADesc.SampleRate = PictureRate;
894       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
895
896       if ( Options.verbose_flag )
897         {
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);
903         }
904     }
905
906   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
907     {
908       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
909 #ifndef ASDCP_WITHOUT_OPENSSL
910       GenRandomUUID(RNG, Info.AssetUUID);
911
912       // configure encryption
913       if( Options.key_flag )
914         {
915           GenRandomUUID(RNG, Info.ContextID);
916           Info.EncryptedEssence = true;
917
918           if ( Options.key_id_flag )
919             memcpy(Info.CryptographicKeyID, Options.key_id_value, KeyIDlen);
920           else
921             RNG.FillRandom(Info.CryptographicKeyID, KeyIDlen);
922
923           Context = new AESEncContext;
924           result = Context->InitKey(Options.key_value);
925
926           if ( ASDCP_SUCCESS(result) )
927             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
928
929           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
930             {
931               Info.UsesHMAC = true;
932               HMAC = new HMACContext;
933               result = HMAC->InitKey(Options.key_value);
934             }
935         }
936 #endif // ASDCP_WITHOUT_OPENSSL
937
938       if ( ASDCP_SUCCESS(result) )
939         result = Writer.OpenWrite(Options.out_file, Info, ADesc);
940     }
941
942   if ( ASDCP_SUCCESS(result) )
943     {
944       result = Parser.Reset();
945       ui32_t duration = 0;
946
947       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
948         {
949           result = Parser.ReadFrame(FrameBuffer);
950
951           if ( ASDCP_SUCCESS(result) )
952             {
953               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
954                 {
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;
958                   continue;
959                 }
960
961               if ( Options.verbose_flag )
962                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
963
964               if ( ! Options.no_write_flag )
965                 {
966                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
967
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));
975 #endif
976                 }
977             }
978         }
979
980       if ( result == RESULT_ENDOFFILE )
981         result = RESULT_OK;
982     }
983
984   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
985     result = Writer.Finalize();
986
987   return result;
988 }
989 #endif
990
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
994 //
995 Result_t
996 read_PCM_file(CommandOptions& Options)
997 {
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;
1005
1006   Result_t result = Reader.OpenRead(Options.filenames[0]);
1007
1008   if ( ASDCP_SUCCESS(result) )
1009     {
1010       Reader.FillAudioDescriptor(ADesc);
1011
1012       if ( ADesc.SampleRate != EditRate_23_98
1013            && ADesc.SampleRate != EditRate_24
1014            && ADesc.SampleRate != EditRate_48 )
1015         ADesc.SampleRate = Options.PictureRate();
1016
1017       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1018
1019       if ( Options.verbose_flag )
1020         PCM::AudioDescriptorDump(ADesc);
1021     }
1022
1023   if ( ASDCP_SUCCESS(result) )
1024     {
1025       last_frame = ADesc.ContainerDuration;
1026
1027       if ( Options.duration > 0 && Options.duration < last_frame )
1028         last_frame = Options.duration;
1029
1030       if ( Options.start_frame > 0 )
1031         {
1032           if ( Options.start_frame > ADesc.ContainerDuration )
1033             {
1034               fprintf(stderr, "Start value greater than file duration.\n");
1035               return RESULT_FAIL;
1036             }
1037
1038           last_frame = xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1039         }
1040
1041       ADesc.ContainerDuration = last_frame - Options.start_frame;
1042       OutWave.OpenWrite(ADesc, Options.file_root, Options.split_wav);
1043     }
1044
1045   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1046     {
1047       Context = new AESDecContext;
1048       result = Context->InitKey(Options.key_value);
1049
1050       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1051         {
1052           WriterInfo Info;
1053           Reader.FillWriterInfo(Info);
1054
1055           if ( Info.UsesHMAC )
1056             {
1057               HMAC = new HMACContext;
1058               result = HMAC->InitKey(Options.key_value);
1059             }
1060           else
1061             {
1062               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1063             }
1064         }
1065     }
1066
1067   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1068     {
1069       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1070
1071       if ( ASDCP_SUCCESS(result) )
1072         {
1073           if ( Options.verbose_flag )
1074             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1075
1076           result = OutWave.WriteFrame(FrameBuffer);
1077         }
1078     }
1079
1080   return result;
1081 }
1082
1083
1084 //------------------------------------------------------------------------------------------
1085 //
1086
1087 //
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()
1091
1092 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1093 {
1094  public:
1095   void FillDescriptor(MPEG2::MXFReader& Reader) {
1096     Reader.FillVideoDescriptor(*this);
1097   }
1098
1099   void Dump(FILE* stream) {
1100     MPEG2::VideoDescriptorDump(*this, stream);
1101   }
1102 };
1103
1104 class MyPictureDescriptor : public JP2K::PictureDescriptor
1105 {
1106  public:
1107   void FillDescriptor(JP2K::MXFReader& Reader) {
1108     Reader.FillPictureDescriptor(*this);
1109   }
1110
1111   void Dump(FILE* stream) {
1112     JP2K::PictureDescriptorDump(*this, stream);
1113   }
1114 };
1115
1116 class MyAudioDescriptor : public PCM::AudioDescriptor
1117 {
1118  public:
1119   void FillDescriptor(PCM::MXFReader& Reader) {
1120     Reader.FillAudioDescriptor(*this);
1121   }
1122
1123   void Dump(FILE* stream) {
1124     PCM::AudioDescriptorDump(*this, stream);
1125   }
1126 };
1127
1128
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
1132 {
1133 public:
1134   static void file_info(CommandOptions& Options, FILE* stream = 0)
1135   {
1136     if ( stream == 0 )
1137       stream = stdout;
1138
1139     if ( Options.verbose_flag || Options.showheader_flag )
1140       {
1141         ReaderT     Reader;
1142         Result_t result = Reader.OpenRead(Options.filenames[0]);
1143
1144         if ( ASDCP_SUCCESS(result) )
1145           {
1146             if ( Options.showheader_flag )
1147               Reader.DumpHeaderMetadata(stream);
1148
1149             WriterInfo WI;
1150             Reader.FillWriterInfo(WI);
1151             WriterInfoDump(WI, stream);
1152
1153             DescriptorT Desc;
1154             Desc.FillDescriptor(Reader);
1155             Desc.Dump(stream);
1156
1157             if ( Options.showindex_flag )
1158               Reader.DumpIndex(stream);
1159           }
1160         else if ( result == RESULT_FORMAT && Options.showheader_flag )
1161           {
1162             Reader.DumpHeaderMetadata(stream);
1163           }
1164       }
1165   }
1166 };
1167
1168 // Read header metadata from an ASDCP file
1169 //
1170 Result_t
1171 show_file_info(CommandOptions& Options)
1172 {
1173   EssenceType_t EssenceType;
1174   Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1175
1176   if ( ASDCP_FAILURE(result) )
1177     return result;
1178
1179   if ( EssenceType == ESS_MPEG2_VES )
1180     {
1181       fputs("File essence type is MPEG2 video.\n", stdout);
1182       FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1183     }
1184   else if ( EssenceType == ESS_PCM_24b_48k )
1185     {
1186       fputs("File essence type is PCM audio.\n", stdout);
1187       FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1188     }
1189   else if ( EssenceType == ESS_JPEG_2000 )
1190     {
1191       fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1192       FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1193     }
1194   else
1195     {
1196       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1197       FileReader   Reader;
1198       MXF::OPAtomHeader TestHeader;
1199
1200       result = Reader.OpenRead(Options.filenames[0]);
1201
1202       if ( ASDCP_SUCCESS(result) )
1203         result = TestHeader.InitFromFile(Reader); // test UL and OP
1204
1205       if ( ASDCP_SUCCESS(result) )
1206         {
1207           TestHeader.Partition::Dump();
1208
1209           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1210             ID->Dump();
1211           else
1212             fputs("File contains no Identification object.\n", stdout);
1213
1214           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1215             SP->Dump();
1216           else
1217             fputs("File contains no SourcePackage object.\n", stdout);
1218         }
1219       else
1220         {
1221           fputs("File is not MXF.\n", stdout);
1222         }
1223     }
1224
1225   return result;
1226 }
1227
1228
1229 //
1230 int
1231 main(int argc, const char** argv)
1232 {
1233   Result_t result = RESULT_OK;
1234   CommandOptions Options(argc, argv);
1235
1236   if ( Options.help_flag )
1237     {
1238       usage();
1239       return 0;
1240     }
1241
1242   if ( Options.error_flag )
1243     return 3;
1244
1245   if ( Options.version_flag )
1246     banner();
1247
1248   if ( Options.info_flag )
1249     {
1250       result = show_file_info(Options);
1251     }
1252   else if ( Options.gop_start_flag )
1253     {
1254       result = gop_start_test(Options);
1255     }
1256 #ifndef ASDCP_WITHOUT_OPENSSL
1257   else if ( Options.genkey_flag )
1258     {
1259       FortunaRNG RNG;
1260       byte_t bin_buf[KeyLen];
1261       char   str_buf[40];
1262
1263       RNG.FillRandom(bin_buf, KeyLen);
1264       printf("%s\n", bin2hex(bin_buf, KeyLen, str_buf, 40));
1265     }
1266   else if ( Options.genid_flag )
1267     {
1268       FortunaRNG RNG;
1269       byte_t bin_buf[KeyLen];
1270       char   str_buf[40];
1271
1272       GenRandomUUID(RNG, bin_buf);
1273       bin2hex(bin_buf, KeyLen, str_buf, 40);
1274       printf("%s\n", hyphenate_UUID(str_buf, 40));
1275     }
1276 #endif // ASDCP_WITHOUT_OPENSSL
1277   else if ( Options.extract_flag )
1278     {
1279       EssenceType_t EssenceType;
1280       result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1281 #ifdef SMPTE_LABELS
1282       fprintf(stderr, "ATTENTION! Expecting SMPTE Universal Labels\n");
1283 #endif
1284       if ( ASDCP_SUCCESS(result) )
1285         {
1286           switch ( EssenceType )
1287             {
1288             case ESS_MPEG2_VES:
1289               result = read_MPEG2_file(Options);
1290               break;
1291
1292             case ESS_JPEG_2000:
1293               result = read_JP2K_file(Options);
1294               break;
1295
1296             case ESS_PCM_24b_48k:
1297               result = read_PCM_file(Options);
1298               break;
1299
1300             default:
1301               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1302               return 5;
1303             }
1304         }
1305     }
1306   else if ( Options.create_flag )
1307     {
1308       fprintf(stderr, "ATTENTION! This version of asdcplib does not support writing MXF files.\n");
1309
1310 #if 0
1311       if ( Options.do_repeat && ! Options.duration_flag )
1312         {
1313           fputs("Option -R requires -d <duration>\n", stderr);
1314           return RESULT_FAIL;
1315         }
1316
1317       EssenceType_t EssenceType;
1318       result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1319 #ifdef SMPTE_LABELS
1320       fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1321 #endif
1322       if ( ASDCP_SUCCESS(result) )
1323         {
1324           switch ( EssenceType )
1325             {
1326             case ESS_MPEG2_VES:
1327               result = write_MPEG2_file(Options);
1328               break;
1329
1330             case ESS_JPEG_2000:
1331               result = write_JP2K_file(Options);
1332               break;
1333
1334             case ESS_PCM_24b_48k:
1335               result = write_PCM_file(Options);
1336               break;
1337
1338             default:
1339               fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1340                       Options.filenames[0]);
1341               return 5;
1342             }
1343         }
1344 #endif
1345     }
1346
1347   if ( result != RESULT_OK )
1348     {
1349       fputs("Program stopped on error.\n", stderr);
1350
1351       if ( result != RESULT_FAIL )
1352         {
1353           fputs(GetResultString(result), stderr);
1354           fputc('\n', stderr);
1355         }
1356
1357       return 1;
1358     }
1359
1360   return 0;
1361 }
1362
1363
1364 //
1365 // end asdcp-test.cpp
1366 //