Synced string array in sfdb_ui.cc with ImportMode enum.
[ardour.git] / libs / appleutility / CAAudioFile.cpp
1 /*      Copyright:      � Copyright 2005 Apple Computer, Inc. All rights reserved.
2
3         Disclaimer:     IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
4                         ("Apple") in consideration of your agreement to the following terms, and your
5                         use, installation, modification or redistribution of this Apple software
6                         constitutes acceptance of these terms.  If you do not agree with these terms,
7                         please do not use, install, modify or redistribute this Apple software.
8
9                         In consideration of your agreement to abide by the following terms, and subject
10                         to these terms, Apple grants you a personal, non-exclusive license, under Apple�s
11                         copyrights in this original Apple software (the "Apple Software"), to use,
12                         reproduce, modify and redistribute the Apple Software, with or without
13                         modifications, in source and/or binary forms; provided that if you redistribute
14                         the Apple Software in its entirety and without modifications, you must retain
15                         this notice and the following text and disclaimers in all such redistributions of
16                         the Apple Software.  Neither the name, trademarks, service marks or logos of
17                         Apple Computer, Inc. may be used to endorse or promote products derived from the
18                         Apple Software without specific prior written permission from Apple.  Except as
19                         expressly stated in this notice, no other rights or licenses, express or implied,
20                         are granted by Apple herein, including but not limited to any patent rights that
21                         may be infringed by your derivative works or by other works in which the Apple
22                         Software may be incorporated.
23
24                         The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
25                         WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
26                         WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27                         PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
28                         COMBINATION WITH YOUR PRODUCTS.
29
30                         IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
31                         CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
32                         GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33                         ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
34                         OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
35                         (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
36                         ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38 /*=============================================================================
39         CAAudioFile.cpp
40         
41 =============================================================================*/
42
43 #include "CAAudioFile.h"
44
45 #if !CAAF_USE_EXTAUDIOFILE
46
47 #include "CAXException.h"
48 #include <algorithm>
49 #include "CAHostTimeBase.h"
50 #include "CADebugMacros.h"
51
52 #if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
53         #include <AudioToolbox/AudioToolbox.h>
54 #else
55         #include <AudioFormat.h>
56 #endif
57
58 #if DEBUG
59         //#define VERBOSE_IO 1
60         //#define VERBOSE_CONVERTER 1
61         //#define VERBOSE_CHANNELMAP 1
62         //#define LOG_FUNCTION_ENTRIES 1
63
64         #if VERBOSE_CHANNELMAP
65                 #include "CAChannelLayouts.h"   // this is in Source/Tests/AudioFileTools/Utility
66         #endif
67 #endif
68
69 #if LOG_FUNCTION_ENTRIES
70         class FunctionLogger {
71         public:
72                 FunctionLogger(const char *name, const char *fmt=NULL, ...) : mName(name) {
73                         Indent();
74                         printf("-> %s ", name);
75                         if (fmt) {
76                                 va_list args;
77                                 va_start(args, fmt);
78                                 vprintf(fmt, args);
79                                 va_end(args);
80                         }
81                         printf("\n");
82                         ++sIndent;
83                 }
84                 ~FunctionLogger() {
85                         --sIndent;
86                         Indent();
87                         printf("<- %s\n", mName);
88                         if (sIndent == 0)
89                                 printf("\n");
90                 }
91                 
92                 static void     Indent() {
93                         for (int i = sIndent; --i >= 0; ) {
94                                 putchar(' '); putchar(' ');
95                         }
96                 }
97                 
98                 const char *mName;
99                 static int sIndent;
100         };
101         int FunctionLogger::sIndent = 0;
102
103         #define LOG_FUNCTION(name, format, ...) FunctionLogger _flog(name, format, ## __VA_ARGS__);
104 #else
105         #define LOG_FUNCTION(name, format, foo)
106 #endif
107
108 static const UInt32 kDefaultIOBufferSizeBytes = 0x10000;
109
110 #if CAAUDIOFILE_PROFILE
111         #define StartTiming(af, starttime) UInt64 starttime = af->mProfiling ? CAHostTimeBase::GetTheCurrentTime() : 0
112         #define ElapsedTime(af, starttime, counter) if (af->mProfiling) counter += (CAHostTimeBase::GetTheCurrentTime() - starttime)
113 #else
114         #define StartTiming(af, starttime)
115         #define ElapsedTime(af, starttime, counter)
116 #endif
117
118 #define kNoMoreInputRightNow 'nein'
119
120 // _______________________________________________________________________________________
121 //
122 CAAudioFile::CAAudioFile() :
123         mAudioFile(0),
124         mUseCache(false),
125         mFinishingEncoding(false),
126         mMode(kClosed),
127         mFileDataOffset(-1),
128         mFramesToSkipFollowingSeek(0),
129         
130         mClientOwnsIOBuffer(false),
131         mPacketDescs(NULL),
132         mNumPacketDescs(0),
133         mConverter(NULL),
134         mMagicCookie(NULL),
135         mWriteBufferList(NULL)
136 #if CAAUDIOFILE_PROFILE
137     ,
138         mProfiling(false),
139         mTicksInConverter(0),
140         mTicksInReadInConverter(0),
141         mTicksInIO(0),
142         mInConverter(false)
143 #endif
144 {
145         mIOBufferList.mBuffers[0].mData = NULL;
146         mIOBufferList.mBuffers[0].mDataByteSize = 0;
147         mClientMaxPacketSize = 0;
148         mIOBufferSizeBytes = kDefaultIOBufferSizeBytes;
149 }
150
151 // _______________________________________________________________________________________
152 //
153 CAAudioFile::~CAAudioFile()
154 {
155         Close();
156 }
157
158 // _______________________________________________________________________________________
159 //
160 void    CAAudioFile::Close()
161 {
162         LOG_FUNCTION("CAAudioFile::Close", NULL, NULL);
163         if (mMode == kClosed)
164                 return;
165         if (mMode == kWriting)
166                 FlushEncoder();
167         CloseConverter();
168         if (mAudioFile != 0 && mOwnOpenFile) {
169                 AudioFileClose(mAudioFile);
170                 mAudioFile = 0;
171         }
172         if (!mClientOwnsIOBuffer) {
173                 delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
174                 mIOBufferList.mBuffers[0].mData = NULL;
175                 mIOBufferList.mBuffers[0].mDataByteSize = 0;
176         }
177         delete[] mPacketDescs;  mPacketDescs = NULL;    mNumPacketDescs = 0;
178         delete[] mMagicCookie;  mMagicCookie = NULL;
179         delete mWriteBufferList;        mWriteBufferList = NULL;
180         mMode = kClosed;
181 }
182
183 // _______________________________________________________________________________________
184 //
185 void    CAAudioFile::CloseConverter()
186 {
187         if (mConverter) {
188 #if VERBOSE_CONVERTER
189                 printf("CAAudioFile %p : CloseConverter\n", this);
190 #endif
191                 AudioConverterDispose(mConverter);
192                 mConverter = NULL;
193         }
194 }
195
196 // =======================================================================================
197
198 // _______________________________________________________________________________________
199 //
200 void    CAAudioFile::Open(const FSRef &fsref)
201 {
202         LOG_FUNCTION("CAAudioFile::Open", "%p", this);
203         XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
204         mFSRef = fsref;
205         XThrowIfError(AudioFileOpen(&mFSRef, fsRdPerm, 0, &mAudioFile), "open audio file");
206         mOwnOpenFile = true;
207         mMode = kReading;
208         GetExistingFileInfo();
209 }
210
211 // _______________________________________________________________________________________
212 //
213 void    CAAudioFile::Wrap(AudioFileID fileID, bool forWriting)
214 {
215         LOG_FUNCTION("CAAudioFile::Wrap", "%p", this);
216         XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
217
218         mAudioFile = fileID;
219         mOwnOpenFile = false;
220         mMode = forWriting ? kPreparingToWrite : kReading;
221         GetExistingFileInfo();
222         if (forWriting)
223                 FileFormatChanged();
224 }
225
226 // _______________________________________________________________________________________
227 //
228 void    CAAudioFile::CreateNew(const FSRef &parentDir, CFStringRef filename, AudioFileTypeID filetype, const AudioStreamBasicDescription &dataFormat, const AudioChannelLayout *layout)
229 {
230         LOG_FUNCTION("CAAudioFile::CreateNew", "%p", this);
231         XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
232         
233         mFileDataFormat = dataFormat;
234         if (layout) {
235                 mFileChannelLayout = layout;
236 #if VERBOSE_CHANNELMAP
237                 printf("PrepareNew passed channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
238 #endif
239         }
240         mMode = kPreparingToCreate;
241         FileFormatChanged(&parentDir, filename, filetype);
242 }
243
244 // _______________________________________________________________________________________
245 //
246 // called to create the file -- or update its format/channel layout/properties based on an encoder 
247 // setting change
248 void    CAAudioFile::FileFormatChanged(const FSRef *parentDir, CFStringRef filename, AudioFileTypeID filetype)
249 {
250         LOG_FUNCTION("CAAudioFile::FileFormatChanged", "%p", this);
251         XThrowIf(mMode != kPreparingToCreate && mMode != kPreparingToWrite, kExtAudioFileError_InvalidOperationOrder, "new file not prepared");
252         
253         UInt32 propertySize;
254         OSStatus err;
255         AudioStreamBasicDescription saveFileDataFormat = mFileDataFormat;
256         
257 #if VERBOSE_CONVERTER
258         mFileDataFormat.PrintFormat(stdout, "", "Specified file data format");
259 #endif
260         
261         // Find out the actual format the converter will produce. This is necessary in
262         // case the bitrate has forced a lower sample rate, which needs to be set correctly
263         // in the stream description passed to AudioFileCreate.
264         if (mConverter != NULL) {
265                 propertySize = sizeof(AudioStreamBasicDescription);
266                 Float64 origSampleRate = mFileDataFormat.mSampleRate;
267                 XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCurrentOutputStreamDescription, &propertySize, &mFileDataFormat), "get audio converter's output stream description");
268                 // do the same for the channel layout being output by the converter
269 #if VERBOSE_CONVERTER
270                 mFileDataFormat.PrintFormat(stdout, "", "Converter output");
271 #endif
272                 if (fiszero(mFileDataFormat.mSampleRate))
273                         mFileDataFormat.mSampleRate = origSampleRate;
274                 err = AudioConverterGetPropertyInfo(mConverter, kAudioConverterOutputChannelLayout, &propertySize, NULL);
275                 if (err == noErr && propertySize > 0) {
276                         AudioChannelLayout *layout = static_cast<AudioChannelLayout *>(malloc(propertySize));
277                         err = AudioConverterGetProperty(mConverter, kAudioConverterOutputChannelLayout, &propertySize, layout);
278                         if (err) {
279                                 free(layout);
280                                 XThrow(err, "couldn't get audio converter's output channel layout");
281                         }
282                         mFileChannelLayout = layout;
283 #if VERBOSE_CHANNELMAP
284                         printf("got new file's channel layout from converter: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
285 #endif
286                         free(layout);
287                 }
288         }
289         
290         // create the output file
291         if (mMode == kPreparingToCreate) {
292                 CAStreamBasicDescription newFileDataFormat = mFileDataFormat;
293                 if (fiszero(newFileDataFormat.mSampleRate))
294                         newFileDataFormat.mSampleRate = 44100;  // just make something up for now
295 #if VERBOSE_CONVERTER
296                 newFileDataFormat.PrintFormat(stdout, "", "Applied to new file");
297 #endif
298                 XThrowIfError(AudioFileCreate(parentDir, filename, filetype, &newFileDataFormat, 0, &mFSRef, &mAudioFile), "create audio file");
299                 mMode = kPreparingToWrite;
300                 mOwnOpenFile = true;
301         } else if (saveFileDataFormat != mFileDataFormat || fnotequal(saveFileDataFormat.mSampleRate, mFileDataFormat.mSampleRate)) {
302                 // second check must be explicit since operator== on ASBD treats SR of zero as "don't care"
303                 if (fiszero(mFileDataFormat.mSampleRate))
304                         mFileDataFormat.mSampleRate = mClientDataFormat.mSampleRate;
305 #if VERBOSE_CONVERTER
306                 mFileDataFormat.PrintFormat(stdout, "", "Applied to new file");
307 #endif
308                 XThrowIf(fiszero(mFileDataFormat.mSampleRate), kExtAudioFileError_InvalidDataFormat, "file's sample rate is 0");
309                 XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyDataFormat, sizeof(AudioStreamBasicDescription), &mFileDataFormat), "couldn't update file's data format");
310         }
311
312         UInt32 deferSizeUpdates = 1;
313         err = AudioFileSetProperty(mAudioFile, kAudioFilePropertyDeferSizeUpdates, sizeof(UInt32), &deferSizeUpdates);
314
315         if (mConverter != NULL) {
316                 // encoder
317                 // get the magic cookie, if any, from the converter             
318                 delete[] mMagicCookie;  mMagicCookie = NULL;
319                 mMagicCookieSize = 0;
320
321                 err = AudioConverterGetPropertyInfo(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, NULL);
322                 
323                 // we can get a noErr result and also a propertySize == 0
324                 // -- if the file format does support magic cookies, but this file doesn't have one.
325                 if (err == noErr && propertySize > 0) {
326                         mMagicCookie = new Byte[propertySize];
327                         XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, mMagicCookie), "get audio converter's magic cookie");
328                         mMagicCookieSize = propertySize;        // the converter lies and tell us the wrong size
329                         // now set the magic cookie on the output file
330                         UInt32 willEatTheCookie = false;
331                         // the converter wants to give us one; will the file take it?
332                         err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyMagicCookieData,
333                                         NULL, &willEatTheCookie);
334                         if (err == noErr && willEatTheCookie) {
335 #if VERBOSE_CONVERTER
336                                 printf("Setting cookie on encoded file\n");
337 #endif
338                                 XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyMagicCookieData, mMagicCookieSize, mMagicCookie), "set audio file's magic cookie");
339                         }
340                 }
341                 
342                 // get maximum packet size
343                 propertySize = sizeof(UInt32);
344                 XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterPropertyMaximumOutputPacketSize, &propertySize, &mFileMaxPacketSize), "get audio converter's maximum output packet size");
345
346                 AllocateBuffers(true /* okToFail */);
347         } else {
348                 InitFileMaxPacketSize();
349         }
350         
351         if (mFileChannelLayout.IsValid() && mFileChannelLayout.NumberChannels() > 2) {
352                 // don't bother tagging mono/stereo files
353                 UInt32 isWritable;
354                 err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, NULL, &isWritable);
355                 if (!err && isWritable) {
356 #if VERBOSE_CHANNELMAP
357                         printf("writing file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
358 #endif
359                         err = AudioFileSetProperty(mAudioFile, kAudioFilePropertyChannelLayout, 
360                                 mFileChannelLayout.Size(), &mFileChannelLayout.Layout());
361                         if (err)
362                                 CAXException::Warning("could not set the file's channel layout", err);
363                 } else {
364 #if VERBOSE_CHANNELMAP
365                         printf("file won't accept a channel layout (write)\n");
366 #endif
367                 }
368         }
369         
370         UpdateClientMaxPacketSize();    // also sets mFrame0Offset
371         mPacketMark = 0;
372         mFrameMark = 0;
373 }
374
375 // _______________________________________________________________________________________
376 //
377 void    CAAudioFile::InitFileMaxPacketSize()
378 {
379         LOG_FUNCTION("CAAudioFile::InitFileMaxPacketSize", "%p", this);
380         UInt32 propertySize = sizeof(UInt32);
381         OSStatus err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyMaximumPacketSize, 
382                 &propertySize, &mFileMaxPacketSize);
383         if (err) {
384                 // workaround for 3361377: not all file formats' maximum packet sizes are supported
385                 if (!mFileDataFormat.IsPCM())
386                         XThrowIfError(err, "get audio file's maximum packet size");
387                 mFileMaxPacketSize = mFileDataFormat.mBytesPerFrame;
388         }
389         AllocateBuffers(true /* okToFail */);
390 }
391
392
393 // _______________________________________________________________________________________
394 //
395 SInt64  CAAudioFile::FileDataOffset()
396 {
397         if (mFileDataOffset < 0) {
398                 UInt32 propertySize = sizeof(SInt64);
399                 XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyDataOffset, &propertySize, &mFileDataOffset), "couldn't get file's data offset");
400         }
401         return mFileDataOffset;
402 }
403
404 // _______________________________________________________________________________________
405 //
406 SInt64  CAAudioFile::GetNumberFrames() const
407 {
408         AudioFilePacketTableInfo pti;
409         UInt32 propertySize = sizeof(pti);
410         OSStatus err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
411         if (err == noErr)
412                 return pti.mNumberValidFrames;
413         return mFileDataFormat.mFramesPerPacket * GetNumberPackets() - mFrame0Offset;
414 }
415
416 // _______________________________________________________________________________________
417 //
418 void    CAAudioFile::SetNumberFrames(SInt64 nFrames)
419 {
420         XThrowIf(mFileDataFormat.mFramesPerPacket != 1, kExtAudioFileError_InvalidDataFormat, "SetNumberFrames only supported for PCM");
421         XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, sizeof(SInt64), &nFrames), "Couldn't set number of packets on audio file");
422 }
423
424 // _______________________________________________________________________________________
425 //
426 // call for existing file, NOT new one - from Open() or Wrap()
427 void    CAAudioFile::GetExistingFileInfo()
428 {
429         LOG_FUNCTION("CAAudioFile::GetExistingFileInfo", "%p", this);
430         UInt32 propertySize;
431         OSStatus err;
432         
433         // get mFileDataFormat
434         propertySize = sizeof(AudioStreamBasicDescription);
435         XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyDataFormat, &propertySize, &mFileDataFormat), "get audio file's data format");
436         
437         // get mFileChannelLayout
438         err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, &propertySize, NULL);
439         if (err == noErr && propertySize > 0) {
440                 AudioChannelLayout *layout = static_cast<AudioChannelLayout *>(malloc(propertySize));
441                 err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyChannelLayout, &propertySize, layout);
442                 if (err == noErr) {
443                         mFileChannelLayout = layout;
444 #if VERBOSE_CHANNELMAP
445                         printf("existing file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
446 #endif
447                 }
448                 free(layout);
449                 XThrowIfError(err, "get audio file's channel layout");
450         }
451         if (mMode != kReading)
452                 return;
453         
454 #if 0
455         // get mNumberPackets
456         propertySize = sizeof(mNumberPackets);
457         XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, &propertySize, &mNumberPackets), "get audio file's packet count");
458 #if VERBOSE_IO
459         printf("CAAudioFile::GetExistingFileInfo: %qd packets\n", mNumberPackets);
460 #endif
461 #endif
462         
463         // get mMagicCookie
464         err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyMagicCookieData, &propertySize, NULL);
465         if (err == noErr && propertySize > 0) {
466                 mMagicCookie = new Byte[propertySize];
467                 mMagicCookieSize = propertySize;
468                 XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyMagicCookieData, &propertySize, mMagicCookie), "get audio file's magic cookie");
469         }
470         InitFileMaxPacketSize();
471         mPacketMark = 0;
472         mFrameMark = 0;
473         
474         UpdateClientMaxPacketSize();
475 }
476
477 // =======================================================================================
478
479 // _______________________________________________________________________________________
480 //
481 void    CAAudioFile::SetFileChannelLayout(const CAAudioChannelLayout &layout)
482 {
483         LOG_FUNCTION("CAAudioFile::SetFileChannelLayout", "%p", this);
484         mFileChannelLayout = layout;
485 #if VERBOSE_CHANNELMAP
486         printf("file channel layout set explicitly (%s): %s\n", mMode == kReading ? "read" : "write", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
487 #endif
488         if (mMode != kReading)
489                 FileFormatChanged();
490 }
491
492 // _______________________________________________________________________________________
493 //
494 void    CAAudioFile::SetClientFormat(const CAStreamBasicDescription &dataFormat, const CAAudioChannelLayout *layout)
495 {
496         LOG_FUNCTION("CAAudioFile::SetClientFormat", "%p", this);
497         XThrowIf(!dataFormat.IsPCM(), kExtAudioFileError_NonPCMClientFormat, "non-PCM client format on audio file");
498         
499         bool dataFormatChanging = (mClientDataFormat.mFormatID == 0 || mClientDataFormat != dataFormat);
500         
501         if (dataFormatChanging) {
502                 CloseConverter();
503                 if (mWriteBufferList) {
504                         delete mWriteBufferList;
505                         mWriteBufferList = NULL;
506                 }
507                 mClientDataFormat = dataFormat;
508         }
509         
510         if (layout && layout->IsValid()) {
511                 XThrowIf(layout->NumberChannels() != mClientDataFormat.NumberChannels(), kExtAudioFileError_InvalidChannelMap, "inappropriate channel map");
512                 mClientChannelLayout = *layout;
513         }
514         
515         bool differentLayouts;
516         if (mClientChannelLayout.IsValid()) {
517                 if (mFileChannelLayout.IsValid()) {
518                         differentLayouts = mClientChannelLayout.Tag() != mFileChannelLayout.Tag();
519 #if VERBOSE_CHANNELMAP
520                         printf("two valid layouts, %s\n", differentLayouts ? "different" : "same");
521 #endif
522                 } else {
523                         differentLayouts = false;
524 #if VERBOSE_CHANNELMAP
525                         printf("valid client layout, unknown file layout\n");
526 #endif
527                 }
528         } else {
529                 differentLayouts = false;
530 #if VERBOSE_CHANNELMAP
531                 if (mFileChannelLayout.IsValid())
532                         printf("valid file layout, unknown client layout\n");
533                 else
534                         printf("two invalid layouts\n");
535 #endif
536         }
537         
538         if (mClientDataFormat != mFileDataFormat || differentLayouts) {
539                 // We need an AudioConverter.
540                 if (mMode == kReading) {
541                         // file -> client (decode)
542 //mFileDataFormat.PrintFormat(  stdout, "", "File:   ");
543 //mClientDataFormat.PrintFormat(stdout, "", "Client: ");
544
545                         if (mConverter == NULL)
546                                 XThrowIfError(AudioConverterNew(&mFileDataFormat, &mClientDataFormat, &mConverter),
547                                 "create audio converter");
548                         
549 #if VERBOSE_CONVERTER
550                         printf("CAAudioFile %p -- created converter\n", this);
551                         CAShow(mConverter);
552 #endif
553                         // set the magic cookie, if any (for decode)
554                         if (mMagicCookie)
555                                 SetConverterProperty(kAudioConverterDecompressionMagicCookie, mMagicCookieSize, mMagicCookie, mFileDataFormat.IsPCM());
556                                         // we get cookies from some AIFF's but the converter barfs on them,
557                                         // so we set canFail to true for PCM
558
559                         SetConverterChannelLayout(false, mFileChannelLayout);
560                         SetConverterChannelLayout(true, mClientChannelLayout);
561                         
562                         // propagate leading/trailing frame counts
563                         if (mFileDataFormat.mBitsPerChannel == 0) {
564                                 UInt32 propertySize;
565                                 OSStatus err;
566                                 AudioFilePacketTableInfo pti;
567                                 propertySize = sizeof(pti);
568                                 err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
569                                 if (err == noErr && (pti.mPrimingFrames > 0 || pti.mRemainderFrames > 0)) {
570                                         AudioConverterPrimeInfo primeInfo;
571                                         primeInfo.leadingFrames = pti.mPrimingFrames;
572                                         primeInfo.trailingFrames = pti.mRemainderFrames;
573                                         /* ignore any error. better to play it at all than not. */
574                                         /*err = */AudioConverterSetProperty(mConverter, kAudioConverterPrimeInfo, sizeof(primeInfo), &primeInfo);
575                                         //XThrowIfError(err, "couldn't set prime info on converter");
576                                 }
577                         }
578                 } else if (mMode == kPreparingToCreate || mMode == kPreparingToWrite) {
579                         // client -> file (encode)
580                         if (mConverter == NULL)
581                                 XThrowIfError(AudioConverterNew(&mClientDataFormat, &mFileDataFormat, &mConverter), "create audio converter");
582                         mWriteBufferList = CABufferList::New("", mClientDataFormat);
583                         SetConverterChannelLayout(false, mClientChannelLayout);
584                         SetConverterChannelLayout(true, mFileChannelLayout);
585                         if (mMode == kPreparingToWrite)
586                                 FileFormatChanged();
587                 } else
588                         XThrowIfError(kExtAudioFileError_InvalidOperationOrder, "audio file format not yet known");
589         }
590         UpdateClientMaxPacketSize();
591 }
592
593 // _______________________________________________________________________________________
594 //
595 OSStatus        CAAudioFile::SetConverterProperty(      
596                                                                                         AudioConverterPropertyID        inPropertyID,
597                                                                                         UInt32                                          inPropertyDataSize,
598                                                                                         const void*                                     inPropertyData,
599                                                                                         bool                                            inCanFail)
600 {
601         OSStatus err = noErr;
602         //LOG_FUNCTION("ExtAudioFile::SetConverterProperty", "%p %-4.4s", this, (char *)&inPropertyID);
603         if (inPropertyID == kAudioConverterPropertySettings && *(CFPropertyListRef *)inPropertyData == NULL)
604                 ;
605         else {
606                 err = AudioConverterSetProperty(mConverter, inPropertyID, inPropertyDataSize, inPropertyData);
607                 if (!inCanFail) {
608                         XThrowIfError(err, "set audio converter property");
609                 }
610         }
611         UpdateClientMaxPacketSize();
612         if (mMode == kPreparingToWrite)
613                 FileFormatChanged();
614         return err;
615 }
616
617 // _______________________________________________________________________________________
618 //
619 void    CAAudioFile::SetConverterChannelLayout(bool output, const CAAudioChannelLayout &layout)
620 {
621         LOG_FUNCTION("CAAudioFile::SetConverterChannelLayout", "%p", this);
622         OSStatus err;
623         
624         if (layout.IsValid()) {
625 #if VERBOSE_CHANNELMAP
626                 printf("Setting converter's %s channel layout: %s\n", output ? "output" : "input",
627                         CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
628 #endif
629                 if (output) {
630                         err = AudioConverterSetProperty(mConverter, kAudioConverterOutputChannelLayout,
631                                 layout.Size(), &layout.Layout());
632                         XThrowIf(err && err != kAudioConverterErr_OperationNotSupported, err, "couldn't set converter's output channel layout");
633                 } else {
634                         err = AudioConverterSetProperty(mConverter, kAudioConverterInputChannelLayout,
635                                 layout.Size(), &layout.Layout());
636                         XThrowIf(err && err != kAudioConverterErr_OperationNotSupported, err, "couldn't set converter's input channel layout");
637                 }
638                 if (mMode == kPreparingToWrite)
639                         FileFormatChanged();
640         }
641 }
642
643 // _______________________________________________________________________________________
644 //
645 CFArrayRef  CAAudioFile::GetConverterConfig()
646 {
647         CFArrayRef plist;
648         UInt32 propertySize = sizeof(plist);
649         XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterPropertySettings, &propertySize, &plist), "get converter property settings");
650         return plist;
651 }
652
653 // _______________________________________________________________________________________
654 //
655 void    CAAudioFile::UpdateClientMaxPacketSize()
656 {
657         LOG_FUNCTION("CAAudioFile::UpdateClientMaxPacketSize", "%p", this);
658         mFrame0Offset = 0;
659         if (mConverter != NULL) {
660                 AudioConverterPropertyID property = (mMode == kReading) ? 
661                         kAudioConverterPropertyMaximumOutputPacketSize :
662                         kAudioConverterPropertyMaximumInputPacketSize;
663                         
664                 UInt32 propertySize = sizeof(UInt32);
665                 XThrowIfError(AudioConverterGetProperty(mConverter, property, &propertySize, &mClientMaxPacketSize),
666                         "get audio converter's maximum packet size");
667                 
668                 if (mFileDataFormat.mBitsPerChannel == 0) {
669                         AudioConverterPrimeInfo primeInfo;
670                         propertySize = sizeof(primeInfo);
671                         OSStatus err = AudioConverterGetProperty(mConverter, kAudioConverterPrimeInfo, &propertySize, &primeInfo);
672                         if (err == noErr)
673                                 mFrame0Offset = primeInfo.leadingFrames;
674 #if VERBOSE_CONVERTER
675                         printf("kAudioConverterPrimeInfo: err = %ld, leadingFrames = %ld\n", err, mFrame0Offset);
676 #endif
677                 }
678         } else {
679                 mClientMaxPacketSize = mFileMaxPacketSize;
680         }
681 }
682
683 // _______________________________________________________________________________________
684 //      Allocates: mIOBufferList, mIOBufferSizePackets, mPacketDescs
685 //      Dependent on: mFileMaxPacketSize, mIOBufferSizeBytes
686 void    CAAudioFile::AllocateBuffers(bool okToFail)
687 {
688         LOG_FUNCTION("CAAudioFile::AllocateBuffers", "%p", this);
689         if (mFileMaxPacketSize == 0) {
690                 if (okToFail)
691                         return;
692                 XThrowIf(true, kExtAudioFileError_MaxPacketSizeUnknown, "file's maximum packet size is 0");
693         }
694         UInt32 bufferSizeBytes = mIOBufferSizeBytes = std::max(mIOBufferSizeBytes, mFileMaxPacketSize);
695                 // must be big enough for at least one maximum size packet
696         
697         if (mIOBufferList.mBuffers[0].mDataByteSize != bufferSizeBytes) {
698                 mIOBufferList.mNumberBuffers = 1;
699                 mIOBufferList.mBuffers[0].mNumberChannels = mFileDataFormat.mChannelsPerFrame;
700                 if (!mClientOwnsIOBuffer) {
701                         //printf("reallocating I/O buffer\n");
702                         delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
703                         mIOBufferList.mBuffers[0].mData = new Byte[bufferSizeBytes];
704                 }
705                 mIOBufferList.mBuffers[0].mDataByteSize = bufferSizeBytes;
706                 mIOBufferSizePackets = bufferSizeBytes / mFileMaxPacketSize;
707         }
708         
709         UInt32 propertySize = sizeof(UInt32);
710         UInt32 externallyFramed;
711         XThrowIfError(AudioFormatGetProperty(kAudioFormatProperty_FormatIsExternallyFramed,
712                         sizeof(AudioStreamBasicDescription), &mFileDataFormat, &propertySize, &externallyFramed),
713                         "is format externally framed");
714         if (mNumPacketDescs != (externallyFramed ? mIOBufferSizePackets : 0)) {
715                 delete[] mPacketDescs;
716                 mPacketDescs = NULL;
717                 mNumPacketDescs = 0;
718
719                 if (externallyFramed) {
720                         //printf("reallocating packet descs\n");
721                         mPacketDescs = new AudioStreamPacketDescription[mIOBufferSizePackets];
722                         mNumPacketDescs = mIOBufferSizePackets;
723                 }
724         }
725 }
726
727 // _______________________________________________________________________________________
728 //
729 void    CAAudioFile::SetIOBuffer(void *buf)
730 {
731         if (!mClientOwnsIOBuffer)
732                 delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
733         mIOBufferList.mBuffers[0].mData = buf;
734
735         if (buf == NULL) {
736                 mClientOwnsIOBuffer = false;
737                 SetIOBufferSizeBytes(mIOBufferSizeBytes);
738         } else {
739                 mClientOwnsIOBuffer = true;
740                 AllocateBuffers();
741         }
742 //      printf("CAAudioFile::SetIOBuffer %p: %p, 0x%lx bytes, mClientOwns = %d\n", this, mIOBufferList.mBuffers[0].mData, mIOBufferSizeBytes, mClientOwnsIOBuffer);
743 }
744
745 // ===============================================================================
746
747 /*
748 For Tiger:
749 added kAudioFilePropertyPacketToFrame and kAudioFilePropertyFrameToPacket.
750 You pass in an AudioFramePacketTranslation struct, with the appropriate field filled in, to AudioFileGetProperty.
751
752         kAudioFilePropertyPacketToFrame                 =       'pkfr',
753                 // pass a AudioFramePacketTranslation with mPacket filled out and get mFrame back. mFrameOffsetInPacket is ignored.
754         kAudioFilePropertyFrameToPacket                 =       'frpk',
755                 // pass a AudioFramePacketTranslation with mFrame filled out and get mPacket and mFrameOffsetInPacket back.
756
757 struct AudioFramePacketTranslation
758 {
759         SInt64 mFrame;
760         SInt64 mPacket;
761         UInt32 mFrameOffsetInPacket;
762 };
763 */
764
765 SInt64  CAAudioFile::PacketToFrame(SInt64 packet) const
766 {
767         AudioFramePacketTranslation trans;
768         UInt32 propertySize;
769         
770         switch (mFileDataFormat.mFramesPerPacket) {
771         case 1:
772                 return packet;
773         case 0:
774                 trans.mPacket = packet;
775                 propertySize = sizeof(trans);
776                 XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketToFrame, &propertySize, &trans),
777                         "packet <-> frame translation unimplemented for format with variable frames/packet");
778                 return trans.mFrame;
779         }
780         return packet * mFileDataFormat.mFramesPerPacket;
781 }
782
783 SInt64  CAAudioFile::FrameToPacket(SInt64 inFrame) const
784 {
785         AudioFramePacketTranslation trans;
786         UInt32 propertySize;
787         
788         switch (mFileDataFormat.mFramesPerPacket) {
789         case 1:
790                 return inFrame;
791         case 0:
792                 trans.mFrame = inFrame;
793                 propertySize = sizeof(trans);
794                 XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyFrameToPacket, &propertySize, &trans),
795                         "packet <-> frame translation unimplemented for format with variable frames/packet");
796                 return trans.mPacket;
797         }
798         return inFrame / mFileDataFormat.mFramesPerPacket;
799 }
800
801 // _______________________________________________________________________________________
802 //
803
804 SInt64  CAAudioFile::Tell() const       // frameNumber
805 {
806         return mFrameMark - mFrame0Offset;
807 }
808
809 void    CAAudioFile::SeekToPacket(SInt64 packetNumber)
810 {
811 #if VERBOSE_IO
812         printf("CAAudioFile::SeekToPacket: %qd\n", packetNumber);
813 #endif
814         XThrowIf(mMode != kReading || packetNumber < 0 /*|| packetNumber >= mNumberPackets*/ , kExtAudioFileError_InvalidSeek, "seek to packet in audio file");
815         if (mPacketMark == packetNumber)
816                 return; // already there! don't reset converter
817         mPacketMark = packetNumber;
818         
819         mFrameMark = PacketToFrame(packetNumber) - mFrame0Offset;
820         mFramesToSkipFollowingSeek = 0;
821         if (mConverter)
822                 // must reset -- if we reached end of stream. converter will no longer work otherwise
823                 AudioConverterReset(mConverter);
824 }
825
826 /*
827         Example: AAC, 1024 frames/packet, 2112 frame offset
828         
829                                            2112
830                                              |
831     Absolute frames:  0       1024      2048 |    3072
832                       +---------+---------+--|------+---------+---------+
833     Packets:          |    0    |    1    |  | 2    |    3    |    4    |
834                       +---------+---------+--|------+---------+---------+
835     Client frames:  -2112   -1088       -64  |     960                                          SeekToFrame, TellFrame
836                                              |
837                                              0
838
839         *   Offset between absolute and client frames is mFrame0Offset.
840         *** mFrameMark is in client frames ***
841         
842         Examples:
843                 clientFrame                                     0               960             1000    1024
844                 absoluteFrame                           2112    3072    3112    3136
845                 packet                                          0               0               0               1
846                 tempFrameMark*                          -2112   -2112   -2112   -1088
847                 mFramesToSkipFollowingSeek      2112    3072    3112    2112
848 */
849 void    CAAudioFile::Seek(SInt64 clientFrame)
850 {
851         if (clientFrame == mFrameMark)
852                 return; // already there! don't reset converter
853
854         //SInt64 absoluteFrame = clientFrame + mFrame0Offset;
855         XThrowIf(mMode != kReading || clientFrame < 0 || !mClientDataFormat.IsPCM(), kExtAudioFileError_InvalidSeek, "seek to frame in audio file");
856
857 #if VERBOSE_IO
858         SInt64 prevFrameMark = mFrameMark;
859 #endif
860         
861         SInt64 packet;
862         packet = FrameToPacket(clientFrame);
863         if (packet < 0)
864                 packet = 0;
865         SeekToPacket(packet);
866         // this will have backed up mFrameMark to match the beginning of the packet
867         mFramesToSkipFollowingSeek = std::max(UInt32(clientFrame - mFrameMark), UInt32(0));
868         mFrameMark = clientFrame;
869         
870 #if VERBOSE_IO
871         printf("CAAudioFile::SeekToFrame: frame %qd (from %qd), packet %qd, skip %ld frames\n", mFrameMark, prevFrameMark, packet, mFramesToSkipFollowingSeek);
872 #endif
873 }
874
875 // _______________________________________________________________________________________
876 //
877 void    CAAudioFile::Read(UInt32 &ioNumPackets, AudioBufferList *ioData)
878                         // May read fewer packets than requested if:
879                         //              buffer is not big enough
880                         //              file does not contain that many more packets
881                         // Note that eofErr is not fatal, just results in 0 packets returned
882                         // ioData's buffer sizes may be shortened
883 {
884         XThrowIf(mClientMaxPacketSize == 0, kExtAudioFileError_MaxPacketSizeUnknown, "client maximum packet size is 0");
885         if (mIOBufferList.mBuffers[0].mData == NULL) {
886 #if DEBUG
887                 printf("warning: CAAudioFile::AllocateBuffers called from ReadPackets\n");
888 #endif
889                 AllocateBuffers();
890         }
891         UInt32 bufferSizeBytes = ioData->mBuffers[0].mDataByteSize;
892         UInt32 maxNumPackets = bufferSizeBytes / mClientMaxPacketSize;  
893         // older versions of AudioConverterFillComplexBuffer don't do this, so do our own sanity check
894         UInt32 nPackets = std::min(ioNumPackets, maxNumPackets);
895         
896         mMaxPacketsToRead = ~0UL;
897         
898         if (mClientDataFormat.mFramesPerPacket == 1) {  // PCM or equivalent
899                 while (mFramesToSkipFollowingSeek > 0) {
900                         UInt32 skipFrames = std::min(mFramesToSkipFollowingSeek, maxNumPackets);
901                         UInt32 framesPerPacket;
902                         if ((framesPerPacket=mFileDataFormat.mFramesPerPacket) > 0)
903                                 mMaxPacketsToRead = (skipFrames + framesPerPacket - 1) / framesPerPacket;
904
905                         if (mConverter == NULL) {
906                                 XThrowIfError(ReadInputProc(NULL, &skipFrames, ioData, NULL, this), "read audio file");
907                         } else {
908 #if CAAUDIOFILE_PROFILE
909                                 mInConverter = true;
910 #endif
911                                 StartTiming(this, fill);
912                                 XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &skipFrames, ioData, NULL), "convert audio packets (pcm read)");
913                                 ElapsedTime(this, fill, mTicksInConverter);
914 #if CAAUDIOFILE_PROFILE
915                                 mInConverter = false;
916 #endif
917                         }
918                         if (skipFrames == 0) {  // hit EOF
919                                 ioNumPackets = 0;
920                                 return;
921                         }
922                         mFrameMark += skipFrames;
923 #if VERBOSE_IO
924                         printf("CAAudioFile::ReadPackets: skipped %ld frames\n", skipFrames);
925 #endif
926
927                         mFramesToSkipFollowingSeek -= skipFrames;
928
929                         // restore mDataByteSize
930                         for (int i = ioData->mNumberBuffers; --i >= 0 ; )
931                                 ioData->mBuffers[i].mDataByteSize = bufferSizeBytes;
932                 }
933         }
934         
935         if (mFileDataFormat.mFramesPerPacket > 0)
936                 // don't read more packets than we are being asked to produce
937                 mMaxPacketsToRead = nPackets / mFileDataFormat.mFramesPerPacket + 1;
938         if (mConverter == NULL) {
939                 XThrowIfError(ReadInputProc(NULL, &nPackets, ioData, NULL, this), "read audio file");
940         } else {
941 #if CAAUDIOFILE_PROFILE
942                 mInConverter = true;
943 #endif
944                 StartTiming(this, fill);
945                 XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &nPackets, ioData, NULL), "convert audio packets (read)");
946                 ElapsedTime(this, fill, mTicksInConverter);
947 #if CAAUDIOFILE_PROFILE
948                 mInConverter = false;
949 #endif
950         }
951         if (mClientDataFormat.mFramesPerPacket == 1)
952                 mFrameMark += nPackets;
953         
954         ioNumPackets = nPackets;
955 }
956
957 // _______________________________________________________________________________________
958 //
959 OSStatus CAAudioFile::ReadInputProc(    AudioConverterRef                               inAudioConverter,
960                                                                                 UInt32*                                                 ioNumberDataPackets,
961                                                                                 AudioBufferList*                                ioData,
962                                                                                 AudioStreamPacketDescription**  outDataPacketDescription,
963                                                                                 void*                                                   inUserData)
964 {
965         CAAudioFile *This = static_cast<CAAudioFile *>(inUserData);
966
967 #if 0
968         SInt64 remainingPacketsInFile = This->mNumberPackets - This->mPacketMark;
969         if (remainingPacketsInFile <= 0) {
970                 *ioNumberDataPackets = 0;
971                 ioData->mBuffers[0].mDataByteSize = 0;
972                 if (outDataPacketDescription)
973                         *outDataPacketDescription = This->mPacketDescs;
974 #if VERBOSE_IO
975                 printf("CAAudioFile::ReadInputProc: EOF\n");
976 #endif
977                 return noErr;   // not eofErr; EOF is signified by 0 packets/0 bytes
978         }
979 #endif
980         
981         // determine how much to read
982         AudioBufferList *readBuffer;
983         UInt32 readPackets;
984         if (inAudioConverter != NULL) {
985                 // getting called from converter, need to use our I/O buffer
986                 readBuffer = &This->mIOBufferList;
987                 readPackets = This->mIOBufferSizePackets;
988         } else {
989                 // getting called directly from ReadPackets, use supplied buffer
990                 if (This->mFileMaxPacketSize == 0)
991                         return kExtAudioFileError_MaxPacketSizeUnknown;
992                 readBuffer = ioData;
993                 readPackets = std::min(*ioNumberDataPackets, readBuffer->mBuffers[0].mDataByteSize / This->mFileMaxPacketSize);
994                         // don't attempt to read more packets than will fit in the buffer
995         }
996         // don't try to read past EOF
997 //      if (readPackets > remainingPacketsInFile)
998 //              readPackets = remainingPacketsInFile;
999         // don't read more packets than necessary to produce the requested amount of converted data
1000         if (readPackets > This->mMaxPacketsToRead) {
1001 #if VERBOSE_IO
1002                 printf("CAAudioFile::ReadInputProc: limiting read to %ld packets (from %ld)\n", This->mMaxPacketsToRead, readPackets);
1003 #endif
1004                 readPackets = This->mMaxPacketsToRead;
1005         }
1006         
1007         // read
1008         UInt32 bytesRead;
1009         OSStatus err;
1010         
1011         StartTiming(This, read);
1012         StartTiming(This, readinconv);
1013         err = AudioFileReadPackets(This->mAudioFile, This->mUseCache, &bytesRead, This->mPacketDescs, This->mPacketMark, &readPackets, readBuffer->mBuffers[0].mData);
1014 #if CAAUDIOFILE_PROFILE
1015         if (This->mInConverter) ElapsedTime(This, readinconv, This->mTicksInReadInConverter);
1016 #endif
1017         ElapsedTime(This, read, This->mTicksInIO);
1018
1019         if (err) {
1020                 DebugMessageN1("Error %ld from AudioFileReadPackets!!!\n", err);
1021                 return err;
1022         }
1023         
1024 #if VERBOSE_IO
1025         printf("CAAudioFile::ReadInputProc: read %ld packets (%qd-%qd), %ld bytes, err %ld\n", readPackets, This->mPacketMark, This->mPacketMark + readPackets, bytesRead, err);
1026 #if VERBOSE_IO >= 2
1027         if (This->mPacketDescs) {
1028                 for (UInt32 i = 0; i < readPackets; ++i) {
1029                         printf("  read packet %qd : offset %qd, length %ld\n", This->mPacketMark + i, This->mPacketDescs[i].mStartOffset, This->mPacketDescs[i].mDataByteSize);
1030                 }
1031         }
1032         printf("  read buffer:"); CAShowAudioBufferList(readBuffer, 0, 4);
1033 #endif
1034 #endif
1035         if (readPackets == 0) {
1036                 *ioNumberDataPackets = 0;
1037                 ioData->mBuffers[0].mDataByteSize = 0;
1038                 return noErr;
1039         }
1040
1041         if (outDataPacketDescription)
1042                 *outDataPacketDescription = This->mPacketDescs;
1043         ioData->mBuffers[0].mDataByteSize = bytesRead;
1044         ioData->mBuffers[0].mData = readBuffer->mBuffers[0].mData;
1045
1046         This->mPacketMark += readPackets;
1047         if (This->mClientDataFormat.mFramesPerPacket != 1) {    // for PCM client formats we update in Read
1048                 // but for non-PCM client format (weird case) we must update here/now
1049                 if (This->mFileDataFormat.mFramesPerPacket > 0)
1050                         This->mFrameMark += readPackets * This->mFileDataFormat.mFramesPerPacket;
1051                 else {
1052                         for (UInt32 i = 0; i < readPackets; ++i)
1053                                 This->mFrameMark += This->mPacketDescs[i].mVariableFramesInPacket;
1054                 }
1055         }
1056         *ioNumberDataPackets = readPackets;
1057         return noErr;
1058 }
1059
1060 // _______________________________________________________________________________________
1061 //
1062 void    CAAudioFile::Write(UInt32 numPackets, const AudioBufferList *data)
1063 {
1064         if (mIOBufferList.mBuffers[0].mData == NULL) {
1065 #if DEBUG
1066                 printf("warning: CAAudioFile::AllocateBuffers called from WritePackets\n");
1067 #endif
1068                 AllocateBuffers();
1069         }
1070
1071         if (mMode == kPreparingToWrite)
1072                 mMode = kWriting;
1073         else
1074                 XThrowIf(mMode != kWriting, kExtAudioFileError_InvalidOperationOrder, "can't write to this file");
1075         if (mConverter != NULL) {
1076                 mWritePackets = numPackets;
1077                 mWriteBufferList->SetFrom(data);
1078                 WritePacketsFromCallback(WriteInputProc, this);
1079         } else {
1080                 StartTiming(this, write);
1081                 XThrowIfError(AudioFileWritePackets(mAudioFile, mUseCache, data->mBuffers[0].mDataByteSize, 
1082                                                 NULL, mPacketMark, &numPackets, data->mBuffers[0].mData),
1083                                                 "write audio file");
1084                 ElapsedTime(this, write, mTicksInIO);
1085 #if VERBOSE_IO
1086                 printf("CAAudioFile::WritePackets: wrote %ld packets at %qd, %ld bytes\n", numPackets, mPacketMark, data->mBuffers[0].mDataByteSize);
1087 #endif
1088                 //mNumberPackets = 
1089                 mPacketMark += numPackets;
1090                 if (mFileDataFormat.mFramesPerPacket > 0)
1091                         mFrameMark += numPackets * mFileDataFormat.mFramesPerPacket;
1092                 // else: shouldn't happen since we're only called when there's no converter
1093         }
1094 }
1095
1096 // _______________________________________________________________________________________
1097 //
1098 void    CAAudioFile::FlushEncoder()
1099 {
1100         if (mConverter != NULL) {
1101                 mFinishingEncoding = true;
1102                 WritePacketsFromCallback(WriteInputProc, this);
1103                 mFinishingEncoding = false;
1104
1105                 // get priming info from converter, set it on the file
1106                 if (mFileDataFormat.mBitsPerChannel == 0) {
1107                         UInt32 propertySize;
1108                         OSStatus err;
1109                         AudioConverterPrimeInfo primeInfo;
1110                         propertySize = sizeof(primeInfo);
1111         
1112                         err = AudioConverterGetProperty(mConverter, kAudioConverterPrimeInfo, &propertySize, &primeInfo);
1113                         if (err == noErr) {
1114                                 AudioFilePacketTableInfo pti;
1115                                 propertySize = sizeof(pti);
1116                                 err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
1117                                 if (err == noErr) {
1118 //printf("old packet table info: %qd valid, %ld priming, %ld remainder\n", pti.mNumberValidFrames, pti.mPrimingFrames, pti.mRemainderFrames);
1119                                         UInt64 totalFrames = pti.mNumberValidFrames + pti.mPrimingFrames + pti.mRemainderFrames;
1120                                         pti.mPrimingFrames = primeInfo.leadingFrames;
1121                                         pti.mRemainderFrames = primeInfo.trailingFrames;
1122                                         pti.mNumberValidFrames = totalFrames - pti.mPrimingFrames - pti.mRemainderFrames;
1123 //printf("new packet table info: %qd valid, %ld priming, %ld remainder\n", pti.mNumberValidFrames, pti.mPrimingFrames, pti.mRemainderFrames);
1124                                         XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, sizeof(pti), &pti), "couldn't set packet table info on audio file");
1125                                 }
1126                         }
1127                 }
1128         }
1129 }
1130
1131 // _______________________________________________________________________________________
1132 //
1133 OSStatus CAAudioFile::WriteInputProc(   AudioConverterRef                               /*inAudioConverter*/,
1134                                                                                 UInt32 *                                                ioNumberDataPackets,
1135                                                                                 AudioBufferList*                                ioData,
1136                                                                                 AudioStreamPacketDescription ** outDataPacketDescription,
1137                                                                                 void*                                                   inUserData)
1138 {
1139         CAAudioFile *This = static_cast<CAAudioFile *>(inUserData);
1140         if (This->mFinishingEncoding) {
1141                 *ioNumberDataPackets = 0;
1142                 ioData->mBuffers[0].mDataByteSize = 0;
1143                 ioData->mBuffers[0].mData = NULL;
1144                 if (outDataPacketDescription)
1145                         *outDataPacketDescription = NULL;
1146                 return noErr;
1147         }
1148         UInt32 numPackets = This->mWritePackets;
1149         if (numPackets == 0) {
1150                 return kNoMoreInputRightNow;
1151         }
1152         This->mWriteBufferList->ToAudioBufferList(ioData);
1153         This->mWriteBufferList->BytesConsumed(numPackets * This->mClientDataFormat.mBytesPerFrame);
1154         *ioNumberDataPackets = numPackets;
1155         if (outDataPacketDescription)
1156                 *outDataPacketDescription = NULL;
1157         This->mWritePackets -= numPackets;
1158         return noErr;
1159 }
1160
1161 // _______________________________________________________________________________________
1162 //
1163 #if VERBOSE_IO
1164 static void     hexdump(const void *addr, long len)
1165 {
1166         const Byte *p = (Byte *)addr;
1167         UInt32 offset = 0;
1168         
1169         if (len > 0x400) len = 0x400;
1170         
1171         while (len > 0) {
1172                 int n = len > 16 ? 16 : len;
1173                 printf("%08lX:  ", offset);
1174                 for (int i = 0; i < 16; ++i)
1175                         if (i < n)
1176                                 printf("%02X ", p[i]);
1177                         else printf("   ");
1178                 for (int i = 0; i < 16; ++i)
1179                         if (i < n)
1180                                 putchar(p[i] >= ' ' && p[i] < 127 ? p[i] : '.');
1181                         else putchar(' ');
1182                 putchar('\n');
1183                 p += 16;
1184                 len -= 16;
1185                 offset += 16;
1186         }
1187 }
1188 #endif
1189
1190 // _______________________________________________________________________________________
1191 //
1192 void    CAAudioFile::WritePacketsFromCallback(
1193                                                                 AudioConverterComplexInputDataProc      inInputDataProc,
1194                                                                 void *                                                          inInputDataProcUserData)
1195 {
1196         while (true) {
1197                 // keep writing until we exhaust the input (temporary stop), or produce no output (EOF)
1198                 UInt32 numEncodedPackets = mIOBufferSizePackets;
1199                 mIOBufferList.mBuffers[0].mDataByteSize = mIOBufferSizeBytes;
1200 #if CAAUDIOFILE_PROFILE
1201                 mInConverter = true;
1202 #endif
1203                 StartTiming(this, fill);
1204                 OSStatus err = AudioConverterFillComplexBuffer(mConverter, inInputDataProc, inInputDataProcUserData, 
1205                                         &numEncodedPackets, &mIOBufferList, mPacketDescs);
1206                 ElapsedTime(this, fill, mTicksInConverter);
1207 #if CAAUDIOFILE_PROFILE
1208                 mInConverter = false;
1209 #endif
1210                 XThrowIf(err != 0 && err != kNoMoreInputRightNow, err, "convert audio packets (write)");
1211                 if (numEncodedPackets == 0)
1212                         break;
1213                 Byte *buf = (Byte *)mIOBufferList.mBuffers[0].mData;
1214 #if VERBOSE_IO
1215                 printf("CAAudioFile::WritePacketsFromCallback: wrote %ld packets, %ld bytes\n", numEncodedPackets, mIOBufferList.mBuffers[0].mDataByteSize);
1216                 if (mPacketDescs) {
1217                         for (UInt32 i = 0; i < numEncodedPackets; ++i) {
1218                                 printf("  write packet %qd : offset %qd, length %ld\n", mPacketMark + i, mPacketDescs[i].mStartOffset, mPacketDescs[i].mDataByteSize);
1219 #if VERBOSE_IO >= 2
1220                                 hexdump(buf + mPacketDescs[i].mStartOffset, mPacketDescs[i].mDataByteSize);
1221 #endif
1222                         }
1223                 }
1224 #endif
1225                 StartTiming(this, write);
1226                 XThrowIfError(AudioFileWritePackets(mAudioFile, mUseCache, mIOBufferList.mBuffers[0].mDataByteSize, mPacketDescs, mPacketMark, &numEncodedPackets, buf), "write audio file");
1227                 ElapsedTime(this, write, mTicksInIO);
1228                 mPacketMark += numEncodedPackets;
1229                 //mNumberPackets += numEncodedPackets;
1230                 if (mFileDataFormat.mFramesPerPacket > 0)
1231                         mFrameMark += numEncodedPackets * mFileDataFormat.mFramesPerPacket;
1232                 else {
1233                         for (UInt32 i = 0; i < numEncodedPackets; ++i)
1234                                 mFrameMark += mPacketDescs[i].mVariableFramesInPacket;
1235                 }
1236                 if (err == kNoMoreInputRightNow)
1237                         break;
1238         }
1239 }
1240
1241 #endif // !CAAF_USE_EXTAUDIOFILE