2 Copyright (c) 2011-2015, Robert Scheler, Heiko Sparenberg Fraunhofer IIS,
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15 3. The name of the author may not be used to endorse or promote products
16 derived from this software without specific prior written permission.
18 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 /*! \file as-02-unwrap.cpp
31 \brief AS-02 file manipulation utility
33 This program extracts picture and sound from AS-02 files.
35 For more information about AS-02, please refer to the header file AS_02.h
36 For more information about asdcplib, please refer to the header file AS_DCP.h
39 #include <KM_fileio.h>
41 #include <WavFileWriter.h>
44 Result_t MD_to_PCM_ADesc(ASDCP::MXF::WaveAudioDescriptor* ADescObj, ASDCP::PCM::AudioDescriptor& ADesc);
47 using namespace ASDCP;
49 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
51 //------------------------------------------------------------------------------------------
53 // command line option parser class
55 static const char* PROGRAM_NAME = "as-02-unwrap"; // program name for messages
57 // Increment the iterator, test for an additional non-option command line argument.
58 // Causes the caller to return if there are no remaining arguments or if the next
59 // argument begins with '-'.
60 #define TEST_EXTRA_ARG(i,c) \
61 if ( ++i >= argc || argv[(i)][0] == '-' ) { \
62 fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
68 banner(FILE* stream = stdout)
72 Copyright (c) 2011-2015, Robert Scheler, Heiko Sparenberg Fraunhofer IIS, John Hurst\n\n\
73 asdcplib may be copied only under the terms of the license found at\n\
74 the top of every file in the asdcplib distribution kit.\n\n\
75 Specify the -h (help) option for further information about %s\n\n",
76 PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
81 usage(FILE* stream = stdout)
84 USAGE: %s [-h|-help] [-V]\n\
86 %s [-1|-2] [-b <buffer-size>] [-d <duration>]\n\
87 [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <size>] [-v] [-W]\n\
88 [-w] <input-file> [<file-prefix>]\n\n",
89 PROGRAM_NAME, PROGRAM_NAME);
93 -1 - Split Wave essence to mono WAV files during extract.\n\
94 Default is multichannel WAV\n\
95 -2 - Split Wave essence to stereo WAV files during extract.\n\
96 Default is multichannel WAV\n\
97 -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
98 Defaults to 4,194,304 (4MB)\n\
99 -d <duration> - Number of frames to process, default all\n\
100 -f <start-frame> - Starting frame number, default 0\n\
101 -h | -help - Show help\n\
102 -k <key-string> - Use key for ciphertext operations\n\
103 -m - verify HMAC values when reading\n\
104 -s <size> - Number of bytes to dump to output when -v is given\n\
105 -V - Show version information\n\
106 -v - Verbose, prints informative messages to stderr\n\
107 -W - Read input file only, do not write destination file\n\
108 -w <width> - Width of numeric element in a series of frame file names\n\
110 -z - Fail if j2c inputs have unequal parameters (default)\n\
111 -Z - Ignore unequal parameters in j2c inputs\n\
113 NOTES: o There is no option grouping, all options must be distinct arguments.\n\
114 o All option arguments must be separated from the option by whitespace.\n\n");
123 bool error_flag; // true if the given options are in error or not complete
124 bool key_flag; // true if an encryption key was given
125 bool read_hmac; // true if HMAC values are to be validated
126 bool split_wav; // true if PCM is to be extracted to stereo WAV files
127 bool mono_wav; // true if PCM is to be extracted to mono WAV files
128 bool verbose_flag; // true if the verbose option was selected
129 ui32_t fb_dump_size; // number of bytes of frame buffer to dump
130 bool no_write_flag; // true if no output files are to be written
131 bool version_flag; // true if the version display option was selected
132 bool help_flag; // true if the help display option was selected
133 bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
134 ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
135 ui32_t start_frame; // frame number to begin processing
136 ui32_t duration; // number of frames to be processed
137 bool duration_flag; // true if duration argument given
138 bool j2c_pedantic; // passed to JP2K::SequenceParser::OpenRead
139 ui32_t picture_rate; // fps of picture when wrapping PCM
140 ui32_t fb_size; // size of picture frame buffer
141 Rational edit_rate; // frame buffer size for reading clip-wrapped PCM
142 const char* file_prefix; // filename pre for files written by the extract mode
143 byte_t key_value[KeyLen]; // value of given encryption key (when key_flag is true)
144 byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
145 PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
146 const char* input_filename;
147 std::string prefix_buffer;
150 CommandOptions(int argc, const char** argv) :
151 error_flag(true), key_flag(false), read_hmac(false), split_wav(false),
152 mono_wav(false), verbose_flag(false), fb_dump_size(0), no_write_flag(false),
153 version_flag(false), help_flag(false), number_width(6),
154 start_frame(0), duration(0xffffffff), duration_flag(false), j2c_pedantic(true),
155 picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_prefix(0),
158 memset(key_value, 0, KeyLen);
159 memset(key_id_value, 0, UUIDlen);
161 for ( int i = 1; i < argc; ++i )
164 if ( (strcmp( argv[i], "-help") == 0) )
170 if ( argv[i][0] == '-'
171 && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
174 switch ( argv[i][1] )
176 case '1': mono_wav = true; break;
177 case '2': split_wav = true; break;
180 TEST_EXTRA_ARG(i, 'b');
181 fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
184 fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
189 TEST_EXTRA_ARG(i, 'd');
190 duration_flag = true;
191 duration = Kumu::xabs(strtol(argv[i], 0, 10));
195 TEST_EXTRA_ARG(i, 'f');
196 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
199 case 'h': help_flag = true; break;
200 case 'm': read_hmac = true; break;
203 TEST_EXTRA_ARG(i, 'p');
204 picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
208 TEST_EXTRA_ARG(i, 's');
209 fb_dump_size = Kumu::xabs(strtol(argv[i], 0, 10));
212 case 'V': version_flag = true; break;
213 case 'v': verbose_flag = true; break;
214 case 'W': no_write_flag = true; break;
217 TEST_EXTRA_ARG(i, 'w');
218 number_width = Kumu::xabs(strtol(argv[i], 0, 10));
221 case 'Z': j2c_pedantic = false; break;
222 case 'z': j2c_pedantic = true; break;
225 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
231 if ( argv[i][0] != '-' )
233 if ( input_filename == 0 )
235 input_filename = argv[i];
237 else if ( file_prefix == 0 )
239 file_prefix = argv[i];
244 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
250 if ( help_flag || version_flag )
253 if ( input_filename == 0 )
255 fputs("At least one filename argument is required.\n", stderr);
259 if ( file_prefix == 0 )
261 prefix_buffer = Kumu::PathSetExtension(input_filename, "") + "_";
262 file_prefix = prefix_buffer.c_str();
270 //------------------------------------------------------------------------------------------
274 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
275 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
276 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
279 read_JP2K_file(CommandOptions& Options)
281 AESDecContext* Context = 0;
282 HMACContext* HMAC = 0;
283 AS_02::JP2K::MXFReader Reader;
284 JP2K::FrameBuffer FrameBuffer(Options.fb_size);
285 ui32_t frame_count = 0;
287 Result_t result = Reader.OpenRead(Options.input_filename);
289 if ( ASDCP_SUCCESS(result) )
291 if ( Options.verbose_flag )
293 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
296 ASDCP::MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
297 ASDCP::MXF::CDCIEssenceDescriptor *cdci_descriptor = 0;
299 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_RGBAEssenceDescriptor),
300 reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor));
302 if ( KM_SUCCESS(result) )
304 assert(rgba_descriptor);
305 frame_count = rgba_descriptor->ContainerDuration;
307 if ( Options.verbose_flag )
309 rgba_descriptor->Dump();
314 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_CDCIEssenceDescriptor),
315 reinterpret_cast<MXF::InterchangeObject**>(&cdci_descriptor));
317 if ( KM_SUCCESS(result) )
319 assert(cdci_descriptor);
320 frame_count = cdci_descriptor->ContainerDuration;
322 if ( Options.verbose_flag )
324 cdci_descriptor->Dump();
329 fprintf(stderr, "File does not contain an essence descriptor.\n");
330 frame_count = Reader.AS02IndexReader().GetDuration();
334 if ( frame_count == 0 )
336 frame_count = Reader.AS02IndexReader().GetDuration();
339 if ( frame_count == 0 )
341 fprintf(stderr, "Unable to determine file duration.\n");
346 if ( ASDCP_SUCCESS(result) && Options.key_flag )
348 Context = new AESDecContext;
349 result = Context->InitKey(Options.key_value);
351 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
354 Reader.FillWriterInfo(Info);
358 HMAC = new HMACContext;
359 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
363 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
368 ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
369 if ( last_frame > frame_count )
370 last_frame = frame_count;
372 char name_format[64];
373 snprintf(name_format, 64, "%%s%%0%du.j2c", Options.number_width);
375 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
377 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
380 snprintf(filename, 1024, name_format, Options.file_prefix, i);
382 if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
384 printf("Frame %d, %d bytes", i, FrameBuffer.Size());
386 if ( ! Options.no_write_flag )
388 printf(" -> %s", filename);
394 if ( ASDCP_SUCCESS(result) && ( ! Options.no_write_flag ) )
396 Kumu::FileWriter OutFile;
398 result = OutFile.OpenWrite(filename);
400 if ( ASDCP_SUCCESS(result) )
401 result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
403 if ( ASDCP_SUCCESS(result) && Options.verbose_flag )
405 FrameBuffer.Dump(stderr, Options.fb_dump_size);
413 //------------------------------------------------------------------------------------------
416 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
417 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
418 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
421 read_PCM_file(CommandOptions& Options)
423 AESDecContext* Context = 0;
424 HMACContext* HMAC = 0;
425 AS_02::PCM::MXFReader Reader;
426 PCM::FrameBuffer FrameBuffer;
427 WavFileWriter OutWave;
428 ui32_t last_frame = 0;
429 ASDCP::MXF::WaveAudioDescriptor *wave_descriptor = 0;
431 if ( Options.edit_rate == Rational(0,0) ) // todo, make this available to the CLI
433 Options.edit_rate = EditRate_24;
436 Result_t result = Reader.OpenRead(Options.input_filename, Options.edit_rate);
438 if ( KM_SUCCESS(result) )
440 if ( Options.verbose_flag )
442 fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
445 ASDCP::MXF::InterchangeObject* tmp_obj = 0;
447 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_WaveAudioDescriptor), &tmp_obj);
449 if ( KM_SUCCESS(result) )
451 wave_descriptor = dynamic_cast<ASDCP::MXF::WaveAudioDescriptor*>(tmp_obj);
453 if ( wave_descriptor == 0 )
455 fprintf(stderr, "File does not contain an essence descriptor.\n");
459 if ( Options.verbose_flag )
461 wave_descriptor->Dump();
464 if ( wave_descriptor->ContainerDuration.get() == 0 )
466 fprintf(stderr, "ContainerDuration not set in file descriptor, attempting to use index duration.\n");
467 last_frame = Reader.AS02IndexReader().GetDuration();
471 last_frame = wave_descriptor->ContainerDuration;
474 if ( last_frame == 0 )
476 fprintf(stderr, "ContainerDuration not set in index, attempting to use Duration from SourceClip.\n");
477 result = Reader.OP1aHeader().GetMDObjectByType(DefaultCompositeDict().ul(MDD_SourceClip), &tmp_obj);
478 if ( KM_SUCCESS(result))
480 ASDCP::MXF::SourceClip *sourceClip = dynamic_cast<ASDCP::MXF::SourceClip*>(tmp_obj);
481 if ( ! sourceClip->Duration.empty() )
483 last_frame = sourceClip->Duration;
488 if ( last_frame == 0 )
490 fprintf(stderr, "Unable to determine file duration.\n");
494 assert(wave_descriptor);
495 FrameBuffer.Capacity(AS_02::MXF::CalcFrameBufferSize(*wave_descriptor, Options.edit_rate));
496 last_frame = AS_02::MXF::CalcFramesFromDurationInSamples(last_frame, *wave_descriptor, Options.edit_rate);
500 if ( ASDCP_SUCCESS(result) )
502 if ( Options.duration > 0 && Options.duration < last_frame )
503 last_frame = Options.duration;
505 if ( Options.start_frame > 0 )
507 if ( Options.start_frame > last_frame )
509 fprintf(stderr, "Start value greater than file duration.\n");
513 last_frame = Kumu::xmin(Options.start_frame + last_frame, last_frame);
516 last_frame = last_frame - Options.start_frame;
518 PCM::AudioDescriptor ADesc;
520 result = MD_to_PCM_ADesc(wave_descriptor, ADesc);
522 if ( ASDCP_SUCCESS(result) )
524 ADesc.ContainerDuration = last_frame;
525 ADesc.EditRate = Options.edit_rate;
527 result = OutWave.OpenWrite(ADesc, Options.file_prefix,
528 ( Options.split_wav ? WavFileWriter::ST_STEREO :
529 ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
533 if ( ASDCP_SUCCESS(result) && Options.key_flag )
535 Context = new AESDecContext;
536 result = Context->InitKey(Options.key_value);
538 if ( ASDCP_SUCCESS(result) && Options.read_hmac )
541 Reader.FillWriterInfo(Info);
545 HMAC = new HMACContext;
546 result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
550 fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
555 for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
557 result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
559 if ( ASDCP_SUCCESS(result) )
561 if ( Options.verbose_flag )
563 FrameBuffer.FrameNumber(i);
564 FrameBuffer.Dump(stderr, Options.fb_dump_size);
567 if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
569 fprintf(stderr, "Last frame is incomplete, padding with zeros.\n");
570 // actually, it has already been zeroed for us, we just need to recognize the appropriate size
571 FrameBuffer.Size(FrameBuffer.Capacity());
574 result = OutWave.WriteFrame(FrameBuffer);
584 main(int argc, const char** argv)
587 CommandOptions Options(argc, argv);
589 if ( Options.version_flag )
592 if ( Options.help_flag )
595 if ( Options.version_flag || Options.help_flag )
598 if ( Options.error_flag )
600 fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
604 EssenceType_t EssenceType;
605 Result_t result = ASDCP::EssenceType(Options.input_filename, EssenceType);
607 if ( ASDCP_SUCCESS(result) )
609 switch ( EssenceType )
611 case ESS_AS02_JPEG_2000:
612 result = read_JP2K_file(Options);
615 case ESS_AS02_PCM_24b_48k:
616 case ESS_AS02_PCM_24b_96k:
617 result = read_PCM_file(Options);
621 fprintf(stderr, "%s: Unknown file type, not AS-02 essence.\n", Options.input_filename);
626 if ( ASDCP_FAILURE(result) )
628 fputs("Program stopped on error.\n", stderr);
630 if ( result != RESULT_FAIL )
632 fputs(result, stderr);
644 // end as-02-unwrap.cpp