Fix Object ref not being written: this prevented GenericStreamTextBasedSet to be...
[asdcplib.git] / src / wavesplit.cpp
1 /*
2 Copyright (c) 2005-2009, 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    wavesplit.cpp
28     \version $Id$
29     \brief   WAV file splitter
30 */
31
32 #include <AS_DCP.h>
33 #include <WavFileWriter.h>
34 #include <assert.h>
35
36 using namespace ASDCP;
37
38 //------------------------------------------------------------------------------------------
39 //
40 // command line option parser class
41
42 static const char* PROGRAM_NAME = "wavesplit";    // program name for messages
43
44 // Macros used to test command option data state.
45
46 // True if a major mode has already been selected.
47 #define TEST_MAJOR_MODE()     ( create_flag || info_flag )
48
49 // Causes the caller to return if a major mode has already been selected,
50 // otherwise sets the given flag.
51 #define TEST_SET_MAJOR_MODE(f) if ( TEST_MAJOR_MODE() ) \
52                                  { \
53                                    fputs("Conflicting major mode, choose one of -(ic).\n", stderr); \
54                                    return; \
55                                  } \
56                                  (f) = true;
57
58 // Increment the iterator, test for an additional non-option command line argument.
59 // Causes the caller to return if there are no remaining arguments or if the next
60 // argument begins with '-'.
61 #define TEST_EXTRA_ARG(i,c)    if ( ++i >= argc || argv[(i)][0] == '-' ) \
62                                  { \
63                                    fprintf(stderr, "Argument not found for option %c.\n", (c)); \
64                                    return; \
65                                  }
66 //
67 void
68 banner(FILE* stream = stderr)
69 {
70   fprintf(stream, "\n\
71 %s (asdcplib %s)\n\n\
72 Copyright (c) 2005-2009 John Hurst\n\n\
73 %s is part of asdcplib.\n\
74 asdcplib may be copied only under the terms of the license found at\n\
75 the top of every file in the asdcplib distribution kit.\n\n\
76 Specify the -h (help) option for further information about %s\n\n",
77           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME, PROGRAM_NAME);
78 }
79
80 //
81 void
82 usage(FILE* stream = stderr)
83 {
84   fprintf(stream, "\
85 USAGE: %s [-v] -V\n\
86        %s [-v] -c <root-name> [-d <duration>] [-f <start-frame>] <filename>\n\
87        %s [-v] -i <filename>\n\
88 \n\
89 Major modes:\n\
90   -c <root-name>  - Create a WAV file for each channel in the input file\n\
91                     (default is two-channel files)\n\
92   -d <duration>   - Number of frames to process, default all\n\
93   -f <frame-num>  - Starting frame number, default 0\n\
94   -h              - Show help\n\
95   -i              - Show input file metadata (no output created)\n\
96   -V              - Show version\n\
97   -v              - Print extra info while processing\n\
98 \n\
99   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
100          o All option arguments must be separated from the option by whitespace.\n\
101 \n", PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
102 }
103
104 //
105 //
106 class CommandOptions
107 {
108   CommandOptions();
109
110 public:
111   bool   error_flag;     // true if the given options are in error or not complete
112   bool   create_flag;    // true if the file create mode was selected
113   bool   info_flag;      // true if the file info mode was selected
114   bool   version_flag;   // true if the version display option was selected
115   bool   help_flag;      // true if the help display option was selected
116   bool   verbose_flag;   // true for extra info during procesing
117   ui32_t start_frame;    // frame number to begin processing
118   ui32_t duration;       // number of frames to be processed
119   const char* file_root; // filename prefix for files written by the extract mode
120   const char* filename;  // filename to be processed
121
122   CommandOptions(int argc, const char** argv) :
123     error_flag(true), create_flag(false), info_flag(false),
124     version_flag(false), help_flag(false), start_frame(0),
125     duration(0xffffffff), file_root(0), filename(0)
126   {
127     for ( int i = 1; i < argc; i++ )
128       {
129         if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
130           {
131             switch ( argv[i][1] )
132               {
133               case 'c':
134                 TEST_SET_MAJOR_MODE(create_flag);
135                 TEST_EXTRA_ARG(i, 'c');
136                 file_root = argv[i];
137                 break;
138
139               case 'd':
140                 TEST_EXTRA_ARG(i, 'd');
141                 duration = Kumu::xabs(strtol(argv[i], 0, 10));
142                 break;
143
144               case 'f':
145                 TEST_EXTRA_ARG(i, 'f');
146                 start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
147                 break;
148
149               case 'h': help_flag = true; break;
150               case 'i': TEST_SET_MAJOR_MODE(info_flag); break;
151               case 'V': version_flag = true; break;
152
153               default:
154                 fprintf(stderr, "Unrecognized option: %c\n", argv[i][1]);
155                 return;
156               }
157           }
158         else
159           {
160             if ( filename )
161               {
162                 fprintf(stderr, "Unexpected extra filename.\n");
163                 return;
164               }
165
166             filename = argv[i];
167           }
168       }
169
170     if ( TEST_MAJOR_MODE() )
171       {
172         if ( filename == 0 )
173           {
174             fputs("Input filename required.\n", stderr);
175             return;
176           }
177       }
178
179     if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
180       {
181         fputs("No operation selected (use one of -(ic) or -h for help).\n", stderr);
182         return;
183       }
184
185     error_flag = false;
186   }
187 };
188
189 //
190 Result_t
191 wav_file_info(CommandOptions& Options)
192 {
193   PCM::AudioDescriptor ADesc;
194   Rational         PictureRate = EditRate_24;
195   PCM::WAVParser Parser;
196
197   // set up essence parser
198   Result_t result = Parser.OpenRead(Options.filename, PictureRate);
199
200   if ( ASDCP_SUCCESS(result) )
201     {
202       Parser.FillAudioDescriptor(ADesc);
203       ADesc.EditRate = PictureRate;
204       fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n", "24",
205               PCM::CalcSamplesPerFrame(ADesc));
206       fputs("AudioDescriptor:\n", stderr);
207       PCM::AudioDescriptorDump(ADesc);
208     }
209
210   return result;
211 }
212
213 //
214 void
215 split_buffer(ui32_t sample_size, PCM::FrameBuffer& FrameBuffer,
216              PCM::FrameBuffer& L_FrameBuffer, PCM::FrameBuffer& R_FrameBuffer)
217 {
218   assert((FrameBuffer.Size() % 2) == 0);
219   byte_t* p = FrameBuffer.Data();
220   byte_t* end_p = p + FrameBuffer.Size();
221   byte_t* lp = L_FrameBuffer.Data();
222   byte_t* rp = R_FrameBuffer.Data();
223
224   for ( ; p < end_p; )
225     {
226       memcpy(lp, p, sample_size);
227       lp += sample_size;
228       p += sample_size;
229       memcpy(rp, p, sample_size);
230       rp += sample_size;
231       p += sample_size;
232     }
233
234   L_FrameBuffer.Size(L_FrameBuffer.Capacity());
235   R_FrameBuffer.Size(R_FrameBuffer.Capacity());
236 }
237
238 //
239 Result_t
240 split_wav_file(CommandOptions& Options)
241 {
242   PCM::FrameBuffer FrameBuffer;
243   PCM::FrameBuffer L_FrameBuffer;
244   PCM::FrameBuffer R_FrameBuffer;
245   PCM::AudioDescriptor ADesc;
246   Rational         PictureRate = EditRate_24;
247   PCM::WAVParser Parser;
248
249   // set up essence parser
250   Result_t result = Parser.OpenRead(Options.filename, PictureRate);
251
252   if ( ASDCP_SUCCESS(result) )
253     {
254       Parser.FillAudioDescriptor(ADesc);
255
256       ADesc.EditRate = PictureRate;
257       ui32_t fb_size = PCM::CalcFrameBufferSize(ADesc);
258       assert((fb_size % 2) == 0);
259       FrameBuffer.Capacity(fb_size);
260       L_FrameBuffer.Capacity(fb_size/2);
261       R_FrameBuffer.Capacity(fb_size/2);
262
263       if ( Options.verbose_flag )
264         {
265           fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n", "24",
266                   PCM::CalcSamplesPerFrame(ADesc));
267           fputs("AudioDescriptor:\n", stderr);
268           PCM::AudioDescriptorDump(ADesc);
269         }
270
271       ADesc.ChannelCount = 1;
272     }
273
274   // set up output files
275   Kumu::FileWriter L_OutFile;
276   Kumu::FileWriter R_OutFile;
277
278   if ( ASDCP_SUCCESS(result) )
279     {
280       char filename[256];
281       snprintf(filename, 256, "%s_l.wav", Options.file_root);
282       result = L_OutFile.OpenWrite(filename);
283
284       if ( ASDCP_SUCCESS(result) )
285         {
286           snprintf(filename, 256, "%s_r.wav", Options.file_root);
287           result = R_OutFile.OpenWrite(filename);
288         }
289     }
290
291
292   if ( ASDCP_SUCCESS(result) )
293     {
294       Wav::SimpleWaveHeader WavHeader(ADesc);
295       result = WavHeader.WriteToFile(L_OutFile);
296
297       if ( ASDCP_SUCCESS(result) )
298         result = WavHeader.WriteToFile(R_OutFile);
299     }
300
301   if ( ASDCP_SUCCESS(result) )
302     {
303       ui32_t write_count = 0;
304       ui32_t duration = 0;
305
306       while ( ASDCP_SUCCESS(result) && (duration++ < Options.duration) )
307         {
308           result = Parser.ReadFrame(FrameBuffer);
309
310           if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
311             {
312               fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
313               fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
314               result = RESULT_ENDOFFILE;
315               continue;
316             }
317
318           if ( Options.verbose_flag )
319             FrameBuffer.Dump(stderr);
320
321           if ( ASDCP_SUCCESS(result) )
322             {
323               split_buffer(PCM::CalcSampleSize(ADesc), FrameBuffer, L_FrameBuffer, R_FrameBuffer);
324               result = L_OutFile.Write(L_FrameBuffer.Data(), L_FrameBuffer.Size(), &write_count);
325
326               if ( ASDCP_SUCCESS(result) )
327                 result = R_OutFile.Write(R_FrameBuffer.Data(), R_FrameBuffer.Size(), &write_count);
328             }
329         }
330
331       if ( result == RESULT_ENDOFFILE )
332         result = RESULT_OK;
333
334       if ( ASDCP_SUCCESS(result) )
335         {
336           ADesc.ContainerDuration = duration;
337           Wav::SimpleWaveHeader WavHeader(ADesc);
338           L_OutFile.Seek();
339
340           if ( ASDCP_SUCCESS(result) )
341             result = R_OutFile.Seek();
342
343           if ( ASDCP_SUCCESS(result) )
344             result = WavHeader.WriteToFile(L_OutFile);
345
346           if ( ASDCP_SUCCESS(result) )
347             result = WavHeader.WriteToFile(R_OutFile);
348         }
349     }
350
351   return RESULT_OK;
352 }
353
354
355 //
356 int
357 main(int argc, const char** argv)
358 {
359   Result_t result = RESULT_OK;
360   CommandOptions Options(argc, argv);
361
362   if ( Options.help_flag )
363     {
364       usage();
365       return 0;
366     }
367
368   if ( Options.error_flag )
369     return 3;
370
371   if ( Options.version_flag )
372     banner();
373
374   if ( Options.info_flag )
375     result = wav_file_info(Options);
376
377   else if ( Options.create_flag )
378     result = split_wav_file(Options);
379
380   if ( result != RESULT_OK )
381     {
382       fputs("Program stopped on error.\n", stderr);
383
384       if ( result != RESULT_FAIL )
385         {
386           fputs(result, stderr);
387           fputc('\n', stderr);
388         }
389
390       return 1;
391     }
392
393   return 0;
394 }
395
396
397 //