/*============================================================================= CAAudioFile.cpp $Log: CAAudioFile.cpp,v $ Revision 1.59 2005/02/04 01:10:06 dwyatt [3988195] interpret dest SR of 0 as "copy the source SR" now only happens on decode/PCM-PCM. On encode, ExtAudioFileCreateNew now handles a SR of 0 by setting a placeholder SR on the file until the converter is created, at which time the real SR is copied from the converter to the file. Revision 1.58 2005/01/14 20:54:43 bills tweak errors Revision 1.57 2004/12/13 22:46:42 dwyatt [3913795] support changing PCM file's length with SetNumberFrames() Revision 1.56 2004/12/04 01:06:53 dwyatt don't set prime frames on converter on decode unless they're nonzero Revision 1.55 2004/12/04 01:04:17 dwyatt don't set prime frames on converter on decode unless they're nonzero Revision 1.54 2004/12/04 01:03:41 dwyatt don't set prime frames on converter on decode unless they're nonzero Revision 1.53 2004/12/03 23:36:06 luke fix miscopy Revision 1.52 2004/12/02 21:04:00 dwyatt [3865346] on decode: file packet table info -> converter prime info Revision 1.51 2004/12/01 23:54:02 dwyatt [3865346] set kAudioFilePropertyPacketTableInfo from kAudioConverterPrimeInfo on encode Revision 1.50 2004/11/23 14:33:04 dwyatt fix eofErr handling Revision 1.49 2004/11/10 22:32:38 dwyatt no vararg macros on windows Revision 1.48 2004/11/09 02:00:18 dwyatt file's packet count is now always determined lazily; buffers are now allocated just a little less lazily (before I/O) Revision 1.47 2004/10/15 17:10:34 dwyatt CAAudioFile rewrite to use ExtAudioFile (changeable via compile flag in CAAudioFile.h) Revision 1.46 2004/09/30 21:05:02 jcm10 make it build on Windows Revision 1.45 2004/08/24 00:28:45 bills fix warnings Revision 1.44 2004/08/03 19:23:32 dwyatt add warnings Revision 1.43 2004/08/03 19:16:46 asynth make defer optional Revision 1.42 2004/06/02 19:26:07 dwyatt add inForWriting argument to Wrap Revision 1.41 2004/05/28 18:13:44 dwyatt maintain frame mark when writing Revision 1.40 2004/05/28 01:08:22 bills have to deal with file being written Revision 1.39 2004/05/27 19:50:16 dwyatt [3657283] more cleanup/simplification of correlation between file and playback sample timelines; reinstate ability to wrap a newly-created file (more elegantly now); don't read more in the ReadCallback than is going to be pulled out the other end Revision 1.38 2004/05/26 01:52:31 dwyatt allow changing the file's data format after the file is created; clean up the state machine Revision 1.37 2004/05/26 01:01:41 dwyatt remove Wrap() for writable files; add support for client-owned I/O buffer; clean up debug code; fix maintenance of mFrameMark Revision 1.36 2004/05/21 22:24:50 dwyatt [3657283] sub-packet seeks Revision 1.35 2004/05/18 00:53:18 dwyatt clean up error codes Revision 1.34 2004/05/17 23:10:50 dwyatt clean up PrepareNew hack with a second sample rate Revision 1.33 2004/05/17 19:13:13 dwyatt back-port a couple Tiger items for pre-Tiger compiles Revision 1.32 2004/05/15 01:05:26 dwyatt use framework include to get to ExtendedAudioFile.h Revision 1.31 2004/05/15 00:06:33 dwyatt fix dataFormatChanging Revision 1.30 2004/05/14 23:09:56 dwyatt first cut at ExtendedAudioFile Revision 1.29 2004/05/06 21:20:33 dwyatt REALLY re-addin Revision 1.26 2004/03/12 03:18:20 dwyatt fix: mFileDataFormat.mSampleRate was 0 Revision 1.25 2004/03/05 20:41:48 dwyatt add compile warning Revision 1.24 2004/02/25 01:32:18 luke add comma Revision 1.23 2004/01/31 01:50:21 dwyatt conditionally compile profiling, add seek-to-packet accessors (not yet impl) Revision 1.22 2004/01/14 00:07:12 dwyatt make inclusion of CAChannelLayouts.h contingent on VERBOSE_CHANNELMAP Revision 1.21 2004/01/13 01:44:44 dwyatt moved from Source/Tests/AudioFileUtility/Utility/ Revision 1.20 2003/12/12 22:17:24 dwyatt add more logging code Revision 1.19 2003/12/04 01:07:57 dwyatt fix Revision 1.18 2003/12/04 00:59:02 dwyatt refactoring Revision 1.17 2003/10/09 23:21:00 dwyatt allow wrapping audio files that are open for writing Revision 1.16 2003/09/04 19:45:56 dwyatt don't fail if we can't set a file channel layout (WAVE can handle some but not all) Revision 1.15 2003/08/28 18:48:51 dwyatt conditionalize channel map debug printfs Revision 1.14 2003/08/28 02:11:30 asynth pass NULL for layout property size when getting info Revision 1.13 2003/08/27 23:41:03 dwyatt less spew, only set file channel layouts when they can accept them Revision 1.12 2003/08/24 23:31:30 dwyatt tolerate unsupported operation errors from setting the converter's channel layout Revision 1.11 2003/08/24 21:21:01 dwyatt always set the channel layouts on the converter Revision 1.10 2003/08/24 08:14:47 dwyatt tweaks to channel layout changes Revision 1.9 2003/08/23 04:55:53 dwyatt first whack at cleaning up channel layout handling Revision 1.8 2003/08/14 11:17:09 dwyatt - fix handling of implied output sample rate - only allocate packet descriptions when needed - fix bad packet count handling in WritePacketsFromCallback (QDesign) Revision 1.7 2003/08/08 20:01:38 bills magic cookies can return a propSize of zero Revision 1.6 2003/08/04 23:42:00 dwyatt more rigor around mFileMaxPacketSize Revision 1.5 2003/07/25 23:16:47 dwyatt use constant strings for operations in CAXException Revision 1.4 2003/07/15 17:35:16 dwyatt fix crash when no layout tag; add profiling Revision 1.3 2003/07/09 19:11:36 dwyatt support channel layouts Revision 1.2 2003/07/02 21:43:33 dwyatt more verbose debug code Revision 1.1 2003/06/23 23:07:37 dwyatt initial checkin created Wed Jun 11 2003, Doug Wyatt Copyright (c) 2003 Apple Computer, Inc. All Rights Reserved $NoKeywords: $ =============================================================================*/ #include "CAAudioFile.h" #if !CAAF_USE_EXTAUDIOFILE #include "CAXException.h" #include #include "CAHostTimeBase.h" #include "CADebugMacros.h" #if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) #include #else #include #endif #if DEBUG //#define VERBOSE_IO 1 //#define VERBOSE_CONVERTER 1 //#define VERBOSE_CHANNELMAP 1 //#define LOG_FUNCTION_ENTRIES 1 #endif #if LOG_FUNCTION_ENTRIES class FunctionLogger { public: FunctionLogger(const char *name, const char *fmt=NULL, ...) : mName(name) { Indent(); printf("-> %s ", name); if (fmt) { va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); } printf("\n"); ++sIndent; } ~FunctionLogger() { --sIndent; Indent(); printf("<- %s\n", mName); } static void Indent() { for (int i = sIndent; --i >= 0; ) { putchar(' '); putchar(' '); } } const char *mName; static int sIndent; }; int FunctionLogger::sIndent = 0; #define LOG_FUNCTION(name, format, ...) FunctionLogger _flog(name, format, ## __VA_ARGS__); #else #define LOG_FUNCTION(name, format, foo) #endif #if VERBOSE_CHANNELMAP //#include "CAChannelLayouts.h" #endif static const UInt32 kDefaultIOBufferSizeBytes = 0x10000; #if CAAUDIOFILE_PROFILE #define StartTiming(af, starttime) UInt64 starttime = af->mProfiling ? CAHostTimeBase::GetTheCurrentTime() : NULL #define ElapsedTime(af, starttime, counter) if (af->mProfiling) counter += (CAHostTimeBase::GetTheCurrentTime() - starttime) #else #define StartTiming(af, starttime) #define ElapsedTime(af, starttime, counter) #endif #define kNoMoreInputRightNow 'nein' // _______________________________________________________________________________________ // CAAudioFile::CAAudioFile() : mAudioFile(0), mUseCache(false), mFinishingEncoding(false), mMode(kClosed), mFileDataOffset(-1), mFramesToSkipFollowingSeek(0), mClientOwnsIOBuffer(false), mPacketDescs(NULL), mNumPacketDescs(0), mConverter(NULL), mMagicCookie(NULL), mWriteBufferList(NULL) #if CAAUDIOFILE_PROFILE , mProfiling(false), mTicksInConverter(0), mTicksInIO(0) #endif { mIOBufferList.mBuffers[0].mData = NULL; mIOBufferList.mBuffers[0].mDataByteSize = 0; mClientMaxPacketSize = 0; mIOBufferSizeBytes = kDefaultIOBufferSizeBytes; } // _______________________________________________________________________________________ // CAAudioFile::~CAAudioFile() { Close(); } // _______________________________________________________________________________________ // void CAAudioFile::Close() { LOG_FUNCTION("CAAudioFile::Close", NULL, NULL); if (mMode == kClosed) return; if (mMode == kWriting) FlushEncoder(); CloseConverter(); if (mAudioFile != 0 && mOwnOpenFile) { AudioFileClose(mAudioFile); mAudioFile = 0; } if (!mClientOwnsIOBuffer) { delete[] (Byte *)mIOBufferList.mBuffers[0].mData; mIOBufferList.mBuffers[0].mData = NULL; mIOBufferList.mBuffers[0].mDataByteSize = 0; } delete[] mPacketDescs; mPacketDescs = NULL; mNumPacketDescs = 0; delete[] mMagicCookie; mMagicCookie = NULL; delete mWriteBufferList; mWriteBufferList = NULL; mMode = kClosed; } // _______________________________________________________________________________________ // void CAAudioFile::CloseConverter() { if (mConverter) { #if VERBOSE_CONVERTER printf("CAAudioFile %p : CloseConverter\n", this); #endif AudioConverterDispose(mConverter); mConverter = NULL; } } // ======================================================================================= // _______________________________________________________________________________________ // void CAAudioFile::Open(const FSRef &fsref) { LOG_FUNCTION("CAAudioFile::Open", "%p", this); XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open"); mFSRef = fsref; XThrowIfError(AudioFileOpen(&mFSRef, fsRdPerm, 0, &mAudioFile), "open audio file"); mOwnOpenFile = true; mMode = kReading; GetExistingFileInfo(); } // _______________________________________________________________________________________ // void CAAudioFile::Wrap(AudioFileID fileID, bool forWriting) { LOG_FUNCTION("CAAudioFile::Wrap", "%p", this); XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open"); mAudioFile = fileID; mOwnOpenFile = false; mMode = forWriting ? kPreparingToWrite : kReading; GetExistingFileInfo(); if (forWriting) FileFormatChanged(); } // _______________________________________________________________________________________ // void CAAudioFile::CreateNew(const FSRef &parentDir, CFStringRef filename, AudioFileTypeID filetype, const AudioStreamBasicDescription &dataFormat, const AudioChannelLayout *layout) { LOG_FUNCTION("CAAudioFile::CreateNew", "%p", this); XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open"); mFileDataFormat = dataFormat; if (layout) { mFileChannelLayout = layout; #if VERBOSE_CHANNELMAP printf("PrepareNew passed channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag())); #endif } mMode = kPreparingToCreate; FileFormatChanged(&parentDir, filename, filetype); } // _______________________________________________________________________________________ // // called to create the file -- or update its format/channel layout/properties based on an encoder // setting change void CAAudioFile::FileFormatChanged(const FSRef *parentDir, CFStringRef filename, AudioFileTypeID filetype) { LOG_FUNCTION("CAAudioFile::FileFormatChanged", "%p", this); XThrowIf(mMode != kPreparingToCreate && mMode != kPreparingToWrite, kExtAudioFileError_InvalidOperationOrder, "new file not prepared"); UInt32 propertySize; OSStatus err; AudioStreamBasicDescription saveFileDataFormat = mFileDataFormat; #if VERBOSE_CONVERTER mFileDataFormat.PrintFormat(stdout, "", "Specified file data format"); #endif // Find out the actual format the converter will produce. This is necessary in // case the bitrate has forced a lower sample rate, which needs to be set correctly // in the stream description passed to AudioFileCreate. if (mConverter != NULL) { propertySize = sizeof(AudioStreamBasicDescription); Float64 origSampleRate = mFileDataFormat.mSampleRate; XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCurrentOutputStreamDescription, &propertySize, &mFileDataFormat), "get audio converter's output stream description"); // do the same for the channel layout being output by the converter #if VERBOSE_CONVERTER mFileDataFormat.PrintFormat(stdout, "", "Converter output"); #endif if (fiszero(mFileDataFormat.mSampleRate)) mFileDataFormat.mSampleRate = origSampleRate; err = AudioConverterGetPropertyInfo(mConverter, kAudioConverterOutputChannelLayout, &propertySize, NULL); if (err == noErr && propertySize > 0) { AudioChannelLayout *layout = static_cast(malloc(propertySize)); err = AudioConverterGetProperty(mConverter, kAudioConverterOutputChannelLayout, &propertySize, layout); if (err) { free(layout); XThrow(err, "couldn't get audio converter's output channel layout"); } mFileChannelLayout = layout; #if VERBOSE_CHANNELMAP printf("got new file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag())); #endif free(layout); } } // create the output file if (mMode == kPreparingToCreate) { CAStreamBasicDescription newFileDataFormat = mFileDataFormat; if (fiszero(newFileDataFormat.mSampleRate)) newFileDataFormat.mSampleRate = 44100; // just make something up for now #if VERBOSE_CONVERTER newFileDataFormat.PrintFormat(stdout, "", "Applied to new file"); #endif XThrowIfError(AudioFileCreate(parentDir, filename, filetype, &newFileDataFormat, 0, &mFSRef, &mAudioFile), "create audio file"); mMode = kPreparingToWrite; mOwnOpenFile = true; } else if (saveFileDataFormat != mFileDataFormat || fnotequal(saveFileDataFormat.mSampleRate, mFileDataFormat.mSampleRate)) { // second check must be explicit since operator== on ASBD treats SR of zero as "don't care" if (fiszero(mFileDataFormat.mSampleRate)) mFileDataFormat.mSampleRate = mClientDataFormat.mSampleRate; #if VERBOSE_CONVERTER newFileDataFormat.PrintFormat(stdout, "", "Applied to new file"); #endif XThrowIf(fiszero(mFileDataFormat.mSampleRate), kExtAudioFileError_InvalidDataFormat, "file's sample rate is 0"); XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyDataFormat, sizeof(AudioStreamBasicDescription), &mFileDataFormat), "couldn't update file's data format"); } UInt32 deferSizeUpdates = 1; err = AudioFileSetProperty(mAudioFile, kAudioFilePropertyDeferSizeUpdates, sizeof(UInt32), &deferSizeUpdates); #if DEBUG if (err) printf("WARNING: couldn't defer size updates to audio file\n"); #endif if (mConverter != NULL) { // encoder // get the magic cookie, if any, from the converter delete[] mMagicCookie; mMagicCookie = NULL; mMagicCookieSize = 0; err = AudioConverterGetPropertyInfo(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, NULL); // we can get a noErr result and also a propertySize == 0 // -- if the file format does support magic cookies, but this file doesn't have one. if (err == noErr && propertySize > 0) { mMagicCookie = new Byte[propertySize]; mMagicCookieSize = propertySize; XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, mMagicCookie), "get audio converter's magic cookie"); // now set the magic cookie on the output file UInt32 willEatTheCookie = false; // the converter wants to give us one; will the file take it? err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie); if (err == noErr && willEatTheCookie) XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyMagicCookieData, mMagicCookieSize, mMagicCookie), "set audio file's magic cookie"); } // get maximum packet size propertySize = sizeof(UInt32); XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterPropertyMaximumOutputPacketSize, &propertySize, &mFileMaxPacketSize), "get audio converter's maximum output packet size"); AllocateBuffers(true /* okToFail */); } else { InitFileMaxPacketSize(); } if (mFileChannelLayout.IsValid() && mFileChannelLayout.NumberChannels() > 2) { // don't bother tagging mono/stereo files UInt32 isWritable; err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, NULL, &isWritable); if (!err && isWritable) { #if VERBOSE_CHANNELMAP printf("writing file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag())); #endif err = AudioFileSetProperty(mAudioFile, kAudioFilePropertyChannelLayout, mFileChannelLayout.Size(), &mFileChannelLayout.Layout()); if (err) CAXException::Warning("could not set the file's channel layout", err); } else { #if VERBOSE_CHANNELMAP printf("file won't accept a channel layout\n"); #endif // forget that we have a channel layout, we can't store it in the file! mFileChannelLayout = CAAudioChannelLayout(); } } UpdateClientMaxPacketSize(); // also sets mFrame0Offset mPacketMark = 0; mFrameMark = 0; } // _______________________________________________________________________________________ // void CAAudioFile::InitFileMaxPacketSize() { LOG_FUNCTION("CAAudioFile::InitFileMaxPacketSize", "%p", this); UInt32 propertySize = sizeof(UInt32); OSStatus err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyMaximumPacketSize, &propertySize, &mFileMaxPacketSize); if (err) { // workaround for 3361377: not all file formats' maximum packet sizes are supported if (!mFileDataFormat.IsPCM()) XThrowIfError(err, "get audio file's maximum packet size"); mFileMaxPacketSize = mFileDataFormat.mBytesPerFrame; } AllocateBuffers(true /* okToFail */); } // _______________________________________________________________________________________ // SInt64 CAAudioFile::FileDataOffset() { if (mFileDataOffset < 0) { UInt32 propertySize = sizeof(SInt64); XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyDataOffset, &propertySize, &mFileDataOffset), "couldn't get file's data offset"); } return mFileDataOffset; } // _______________________________________________________________________________________ // SInt64 CAAudioFile::GetNumberFrames() const { AudioFilePacketTableInfo pti; UInt32 propertySize = sizeof(pti); OSStatus err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti); if (err == noErr) return pti.mNumberValidFrames; return mFileDataFormat.mFramesPerPacket * GetNumberPackets(); } // _______________________________________________________________________________________ // void CAAudioFile::SetNumberFrames(SInt64 nFrames) { XThrowIf(mFileDataFormat.mFramesPerPacket != 1, kExtAudioFileError_InvalidDataFormat, "SetNumberFrames only supported for PCM"); XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, sizeof(SInt64), &nFrames), "Couldn't set number of packets on audio file"); } // _______________________________________________________________________________________ // // call for existing file, NOT new one - from Open() or Wrap() void CAAudioFile::GetExistingFileInfo() { LOG_FUNCTION("CAAudioFile::GetExistingFileInfo", "%p", this); UInt32 propertySize; OSStatus err; // get mFileDataFormat propertySize = sizeof(AudioStreamBasicDescription); XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyDataFormat, &propertySize, &mFileDataFormat), "get audio file's data format"); // get mFileChannelLayout err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, &propertySize, NULL); if (err == noErr && propertySize > 0) { AudioChannelLayout *layout = static_cast(malloc(propertySize)); err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyChannelLayout, &propertySize, layout); if (err == noErr) { mFileChannelLayout = layout; #if VERBOSE_CHANNELMAP printf("existing file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag())); #endif } free(layout); XThrowIfError(err, "get audio file's channel layout"); } if (mMode != kReading) return; #if 0 // get mNumberPackets propertySize = sizeof(mNumberPackets); XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, &propertySize, &mNumberPackets), "get audio file's packet count"); #if VERBOSE_IO printf("CAAudioFile::GetExistingFileInfo: %qd packets\n", mNumberPackets); #endif #endif // get mMagicCookie err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyMagicCookieData, &propertySize, NULL); if (err == noErr && propertySize > 0) { mMagicCookie = new Byte[propertySize]; mMagicCookieSize = propertySize; XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyMagicCookieData, &propertySize, mMagicCookie), "get audio file's magic cookie"); } InitFileMaxPacketSize(); mPacketMark = 0; mFrameMark = 0; UpdateClientMaxPacketSize(); } // ======================================================================================= // _______________________________________________________________________________________ // void CAAudioFile::SetFileChannelLayout(const CAAudioChannelLayout &layout) { LOG_FUNCTION("CAAudioFile::SetFileChannelLayout", "%p", this); mFileChannelLayout = layout; #if VERBOSE_CHANNELMAP printf("file channel layout set explicitly: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag())); #endif FileFormatChanged(); } // _______________________________________________________________________________________ // void CAAudioFile::SetClientFormat(const CAStreamBasicDescription &dataFormat, const CAAudioChannelLayout *layout) { LOG_FUNCTION("CAAudioFile::SetClientFormat", "%p", this); XThrowIf(!dataFormat.IsPCM(), kExtAudioFileError_NonPCMClientFormat, "non-PCM client format on audio file"); bool dataFormatChanging = (mClientDataFormat.mFormatID == 0 || mClientDataFormat != dataFormat); if (dataFormatChanging) { CloseConverter(); if (mWriteBufferList) { delete mWriteBufferList; mWriteBufferList = NULL; } mClientDataFormat = dataFormat; } if (layout && layout->IsValid()) { XThrowIf(layout->NumberChannels() != mClientDataFormat.NumberChannels(), kExtAudioFileError_InvalidChannelMap, "inappropriate channel map"); mClientChannelLayout = *layout; } bool differentLayouts; if (mClientChannelLayout.IsValid()) { if (mFileChannelLayout.IsValid()) { differentLayouts = mClientChannelLayout.Tag() != mFileChannelLayout.Tag(); #if VERBOSE_CHANNELMAP printf("two valid layouts, %s\n", differentLayouts ? "different" : "same"); #endif } else { differentLayouts = false; #if VERBOSE_CHANNELMAP printf("valid client layout, unknown file layout\n"); #endif } } else { differentLayouts = false; #if VERBOSE_CHANNELMAP if (mFileChannelLayout.IsValid()) printf("valid file layout, unknown client layout\n"); else printf("two invalid layouts\n"); #endif } if (mClientDataFormat != mFileDataFormat || differentLayouts) { // We need an AudioConverter. if (mMode == kReading) { // file -> client (decode) //mFileDataFormat.PrintFormat( stdout, "", "File: "); //mClientDataFormat.PrintFormat(stdout, "", "Client: "); if (mConverter == NULL) XThrowIfError(AudioConverterNew(&mFileDataFormat, &mClientDataFormat, &mConverter), "create audio converter"); #if VERBOSE_CONVERTER printf("CAAudioFile %p -- created converter\n", this); CAShow(mConverter); #endif // set the magic cookie, if any (for decode) if (mMagicCookie) SetConverterProperty(kAudioConverterDecompressionMagicCookie, mMagicCookieSize, mMagicCookie, mFileDataFormat.IsPCM()); // we get cookies from some AIFF's but the converter barfs on them, // so we set canFail to true for PCM SetConverterChannelLayout(false, mFileChannelLayout); SetConverterChannelLayout(true, mClientChannelLayout); // propagate leading/trailing frame counts UInt32 propertySize; OSStatus err; AudioFilePacketTableInfo pti; propertySize = sizeof(pti); err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti); if (err == noErr && (pti.mPrimingFrames > 0 || pti.mRemainderFrames > 0)) { AudioConverterPrimeInfo primeInfo; primeInfo.leadingFrames = pti.mPrimingFrames; primeInfo.trailingFrames = pti.mRemainderFrames; XThrowIfError(AudioConverterSetProperty(mConverter, kAudioConverterPrimeInfo, sizeof(primeInfo), &primeInfo), "couldn't set prime info on converter"); } } else if (mMode == kPreparingToCreate || mMode == kPreparingToWrite) { // client -> file (encode) if (mConverter == NULL) XThrowIfError(AudioConverterNew(&mClientDataFormat, &mFileDataFormat, &mConverter), "create audio converter"); mWriteBufferList = CABufferList::New("", mClientDataFormat); SetConverterChannelLayout(false, mClientChannelLayout); SetConverterChannelLayout(true, mFileChannelLayout); if (mMode == kPreparingToWrite) FileFormatChanged(); } else XThrowIfError(kExtAudioFileError_InvalidOperationOrder, "audio file format not yet known"); } UpdateClientMaxPacketSize(); } // _______________________________________________________________________________________ // OSStatus CAAudioFile::SetConverterProperty( AudioConverterPropertyID inPropertyID, UInt32 inPropertyDataSize, const void* inPropertyData, bool inCanFail) { //LOG_FUNCTION("CAAudioFile::SetConverterProperty", "%p %-4.4s", this, (char *)&inPropertyID); OSStatus err = AudioConverterSetProperty(mConverter, inPropertyID, inPropertyDataSize, inPropertyData); if (!inCanFail) { XThrowIfError(err, "set audio converter property"); } UpdateClientMaxPacketSize(); if (mMode == kPreparingToWrite) FileFormatChanged(); return err; } // _______________________________________________________________________________________ // void CAAudioFile::SetConverterChannelLayout(bool output, const CAAudioChannelLayout &layout) { LOG_FUNCTION("CAAudioFile::SetConverterChannelLayout", "%p", this); OSStatus err; if (layout.IsValid()) { if (output) { err = AudioConverterSetProperty(mConverter, kAudioConverterOutputChannelLayout, layout.Size(), &layout.Layout()); XThrowIf(err && err != kAudioConverterErr_OperationNotSupported, err, "couldn't set converter's output channel layout"); } else { err = AudioConverterSetProperty(mConverter, kAudioConverterInputChannelLayout, layout.Size(), &layout.Layout()); XThrowIf(err && err != kAudioConverterErr_OperationNotSupported, err, "couldn't set converter's input channel layout"); } if (mMode == kPreparingToWrite) FileFormatChanged(); } } // _______________________________________________________________________________________ // CFArrayRef CAAudioFile::GetConverterConfig() { CFArrayRef plist; UInt32 propertySize = sizeof(plist); XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterPropertySettings, &propertySize, &plist), "get converter property settings"); return plist; } // _______________________________________________________________________________________ // void CAAudioFile::UpdateClientMaxPacketSize() { LOG_FUNCTION("CAAudioFile::UpdateClientMaxPacketSize", "%p", this); mFrame0Offset = 0; if (mConverter != NULL) { AudioConverterPropertyID property = (mMode == kReading) ? kAudioConverterPropertyMaximumOutputPacketSize : kAudioConverterPropertyMaximumInputPacketSize; UInt32 propertySize = sizeof(UInt32); XThrowIfError(AudioConverterGetProperty(mConverter, property, &propertySize, &mClientMaxPacketSize), "get audio converter's maximum packet size"); AudioConverterPrimeInfo primeInfo; propertySize = sizeof(primeInfo); OSStatus err = AudioConverterGetProperty(mConverter, kAudioConverterPrimeInfo, &propertySize, &primeInfo); if (err == noErr) mFrame0Offset = primeInfo.leadingFrames; #if VERBOSE_CONVERTER printf("kAudioConverterPrimeInfo: err = %ld, leadingFrames = %ld\n", err, mFrame0Offset); #endif } else { mClientMaxPacketSize = mFileMaxPacketSize; } } // _______________________________________________________________________________________ // Allocates: mIOBufferList, mIOBufferSizePackets, mPacketDescs // Dependent on: mFileMaxPacketSize, mIOBufferSizeBytes void CAAudioFile::AllocateBuffers(bool okToFail) { LOG_FUNCTION("CAAudioFile::AllocateBuffers", "%p", this); if (mFileMaxPacketSize == 0) { if (okToFail) return; XThrowIf(true, kExtAudioFileError_MaxPacketSizeUnknown, "file's maximum packet size is 0"); } UInt32 bufferSizeBytes = mIOBufferSizeBytes = std::max(mIOBufferSizeBytes, mFileMaxPacketSize); // must be big enough for at least one maximum size packet if (mIOBufferList.mBuffers[0].mDataByteSize != bufferSizeBytes) { mIOBufferList.mNumberBuffers = 1; mIOBufferList.mBuffers[0].mNumberChannels = mFileDataFormat.mChannelsPerFrame; if (!mClientOwnsIOBuffer) { //printf("reallocating I/O buffer\n"); delete[] (Byte *)mIOBufferList.mBuffers[0].mData; mIOBufferList.mBuffers[0].mData = new Byte[bufferSizeBytes]; } mIOBufferList.mBuffers[0].mDataByteSize = bufferSizeBytes; mIOBufferSizePackets = bufferSizeBytes / mFileMaxPacketSize; } UInt32 propertySize = sizeof(UInt32); UInt32 externallyFramed; XThrowIfError(AudioFormatGetProperty(kAudioFormatProperty_FormatIsExternallyFramed, sizeof(AudioStreamBasicDescription), &mFileDataFormat, &propertySize, &externallyFramed), "is format externally framed"); if (mNumPacketDescs != (externallyFramed ? mIOBufferSizePackets : 0)) { delete[] mPacketDescs; mPacketDescs = NULL; mNumPacketDescs = 0; if (externallyFramed) { //printf("reallocating packet descs\n"); mPacketDescs = new AudioStreamPacketDescription[mIOBufferSizePackets]; mNumPacketDescs = mIOBufferSizePackets; } } } // _______________________________________________________________________________________ // void CAAudioFile::SetIOBuffer(void *buf) { if (!mClientOwnsIOBuffer) delete[] (Byte *)mIOBufferList.mBuffers[0].mData; mIOBufferList.mBuffers[0].mData = buf; if (buf == NULL) { mClientOwnsIOBuffer = false; SetIOBufferSizeBytes(mIOBufferSizeBytes); } else { mClientOwnsIOBuffer = true; AllocateBuffers(); } // printf("CAAudioFile::SetIOBuffer %p: %p, 0x%lx bytes, mClientOwns = %d\n", this, mIOBufferList.mBuffers[0].mData, mIOBufferSizeBytes, mClientOwnsIOBuffer); } // =============================================================================== /* For Tiger: added kAudioFilePropertyPacketToFrame and kAudioFilePropertyFrameToPacket. You pass in an AudioFramePacketTranslation struct, with the appropriate field filled in, to AudioFileGetProperty. kAudioFilePropertyPacketToFrame = 'pkfr', // pass a AudioFramePacketTranslation with mPacket filled out and get mFrame back. mFrameOffsetInPacket is ignored. kAudioFilePropertyFrameToPacket = 'frpk', // pass a AudioFramePacketTranslation with mFrame filled out and get mPacket and mFrameOffsetInPacket back. struct AudioFramePacketTranslation { SInt64 mFrame; SInt64 mPacket; UInt32 mFrameOffsetInPacket; }; */ SInt64 CAAudioFile::PacketToFrame(SInt64 packet) const { AudioFramePacketTranslation trans; UInt32 propertySize; switch (mFileDataFormat.mFramesPerPacket) { case 1: return packet; case 0: trans.mPacket = packet; propertySize = sizeof(trans); XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketToFrame, &propertySize, &trans), "packet <-> frame translation unimplemented for format with variable frames/packet"); return trans.mFrame; } return packet * mFileDataFormat.mFramesPerPacket; } SInt64 CAAudioFile::FrameToPacket(SInt64 inFrame) const { AudioFramePacketTranslation trans; UInt32 propertySize; switch (mFileDataFormat.mFramesPerPacket) { case 1: return inFrame; case 0: trans.mFrame = inFrame; propertySize = sizeof(trans); XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyFrameToPacket, &propertySize, &trans), "packet <-> frame translation unimplemented for format with variable frames/packet"); return trans.mPacket; } return inFrame / mFileDataFormat.mFramesPerPacket; } // _______________________________________________________________________________________ // SInt64 CAAudioFile::Tell() const // frameNumber { return mFrameMark - mFrame0Offset; } void CAAudioFile::SeekToPacket(SInt64 packetNumber) { #if VERBOSE_IO printf("CAAudioFile::SeekToPacket: %qd\n", packetNumber); #endif XThrowIf(mMode != kReading || packetNumber < 0 /*|| packetNumber >= mNumberPackets*/ , kExtAudioFileError_InvalidSeek, "seek to packet in audio file"); if (mPacketMark == packetNumber) return; // already there! don't reset converter mPacketMark = packetNumber; mFrameMark = PacketToFrame(packetNumber) - mFrame0Offset; mFramesToSkipFollowingSeek = 0; if (mConverter) // must reset -- if we reached end of stream. converter will no longer work otherwise AudioConverterReset(mConverter); } /* Example: AAC, 1024 frames/packet, 2112 frame offset 2112 | Absolute frames: 0 1024 2048 | 3072 +---------+---------+--|------+---------+---------+ Packets: | 0 | 1 | | 2 | 3 | 4 | +---------+---------+--|------+---------+---------+ Client frames: -2112 -1088 -64 | 960 SeekToFrame, TellFrame | 0 * Offset between absolute and client frames is mFrame0Offset. *** mFrameMark is in client frames *** Examples: clientFrame 0 960 1000 1024 absoluteFrame 2112 3072 3112 3136 packet 0 0 0 1 tempFrameMark* -2112 -2112 -2112 -1088 mFramesToSkipFollowingSeek 2112 3072 3112 2112 */ void CAAudioFile::Seek(SInt64 clientFrame) { if (clientFrame == mFrameMark) return; // already there! don't reset converter //SInt64 absoluteFrame = clientFrame + mFrame0Offset; XThrowIf(mMode != kReading || clientFrame < 0 || !mClientDataFormat.IsPCM(), kExtAudioFileError_InvalidSeek, "seek to frame in audio file"); #if VERBOSE_IO SInt64 prevFrameMark = mFrameMark; #endif SInt64 packet; packet = FrameToPacket(clientFrame); if (packet < 0) packet = 0; SeekToPacket(packet); // this will have backed up mFrameMark to match the beginning of the packet mFramesToSkipFollowingSeek = std::max(UInt32(clientFrame - mFrameMark), UInt32(0)); mFrameMark = clientFrame; #if VERBOSE_IO printf("CAAudioFile::SeekToFrame: frame %qd (from %qd), packet %qd, skip %ld frames\n", mFrameMark, prevFrameMark, packet, mFramesToSkipFollowingSeek); #endif } // _______________________________________________________________________________________ // void CAAudioFile::Read(UInt32 &ioNumPackets, AudioBufferList *ioData) // May read fewer packets than requested if: // buffer is not big enough // file does not contain that many more packets // Note that eofErr is not fatal, just results in 0 packets returned // ioData's buffer sizes may be shortened { XThrowIf(mClientMaxPacketSize == 0, kExtAudioFileError_MaxPacketSizeUnknown, "client maximum packet size is 0"); if (mIOBufferList.mBuffers[0].mData == NULL) { #if DEBUG printf("warning: CAAudioFile::AllocateBuffers called from ReadPackets\n"); #endif AllocateBuffers(); } UInt32 bufferSizeBytes = ioData->mBuffers[0].mDataByteSize; UInt32 maxNumPackets = bufferSizeBytes / mClientMaxPacketSize; // older versions of AudioConverterFillComplexBuffer don't do this, so do our own sanity check UInt32 nPackets = std::min(ioNumPackets, maxNumPackets); mMaxPacketsToRead = ~0; if (mClientDataFormat.mFramesPerPacket == 1) { // PCM or equivalent while (mFramesToSkipFollowingSeek > 0) { UInt32 skipFrames = std::min(mFramesToSkipFollowingSeek, maxNumPackets); UInt32 framesPerPacket; if ((framesPerPacket=mFileDataFormat.mFramesPerPacket) > 0) mMaxPacketsToRead = (skipFrames + framesPerPacket - 1) / framesPerPacket; if (mConverter == NULL) { XThrowIfError(ReadInputProc(NULL, &skipFrames, ioData, NULL, this), "read audio file"); } else { StartTiming(this, fill); XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &skipFrames, ioData, NULL), "convert audio packets (pcm read)"); ElapsedTime(this, fill, mTicksInConverter); } if (skipFrames == 0) { // hit EOF ioNumPackets = 0; return; } #if VERBOSE_IO printf("CAAudioFile::ReadPackets: skipped %ld frames\n", skipFrames); #endif mFramesToSkipFollowingSeek -= skipFrames; // restore mDataByteSize for (int i = ioData->mNumberBuffers; --i >= 0 ; ) ioData->mBuffers[i].mDataByteSize = bufferSizeBytes; } } if (mFileDataFormat.mFramesPerPacket > 0) // don't read more packets than we are being asked to produce mMaxPacketsToRead = nPackets / mFileDataFormat.mFramesPerPacket + 1; if (mConverter == NULL) { XThrowIfError(ReadInputProc(NULL, &nPackets, ioData, NULL, this), "read audio file"); } else { StartTiming(this, fill); XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &nPackets, ioData, NULL), "convert audio packets (read)"); ElapsedTime(this, fill, mTicksInConverter); } ioNumPackets = nPackets; } // _______________________________________________________________________________________ // OSStatus CAAudioFile::ReadInputProc( AudioConverterRef inAudioConverter, UInt32* ioNumberDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription** outDataPacketDescription, void* inUserData) { CAAudioFile *This = static_cast(inUserData); #if 0 SInt64 remainingPacketsInFile = This->mNumberPackets - This->mPacketMark; if (remainingPacketsInFile <= 0) { *ioNumberDataPackets = 0; ioData->mBuffers[0].mDataByteSize = 0; if (outDataPacketDescription) *outDataPacketDescription = This->mPacketDescs; #if VERBOSE_IO printf("CAAudioFile::ReadInputProc: EOF\n"); #endif return noErr; // not eofErr; EOF is signified by 0 packets/0 bytes } #endif // determine how much to read AudioBufferList *readBuffer; UInt32 readPackets; if (inAudioConverter != NULL) { // getting called from converter, need to use our I/O buffer readBuffer = &This->mIOBufferList; readPackets = This->mIOBufferSizePackets; } else { // getting called directly from ReadPackets, use supplied buffer if (This->mFileMaxPacketSize == 0) return kExtAudioFileError_MaxPacketSizeUnknown; readBuffer = ioData; readPackets = std::min(*ioNumberDataPackets, readBuffer->mBuffers[0].mDataByteSize / This->mFileMaxPacketSize); // don't attempt to read more packets than will fit in the buffer } // don't try to read past EOF // if (readPackets > remainingPacketsInFile) // readPackets = remainingPacketsInFile; // don't read more packets than necessary to produce the requested amount of converted data if (readPackets > This->mMaxPacketsToRead) { #if VERBOSE_IO printf("CAAudioFile::ReadInputProc: limiting read to %ld packets (from %ld)\n", This->mMaxPacketsToRead, readPackets); #endif readPackets = This->mMaxPacketsToRead; } // read UInt32 bytesRead; OSStatus err; StartTiming(This, read); err = AudioFileReadPackets(This->mAudioFile, This->mUseCache, &bytesRead, This->mPacketDescs, This->mPacketMark, &readPackets, readBuffer->mBuffers[0].mData); ElapsedTime(This, read, This->mTicksInIO); if (err) { DebugMessageN1("Error %ld from AudioFileReadPackets!!!\n", err); return err; } #if VERBOSE_IO printf("CAAudioFile::ReadInputProc: read %ld packets (%qd-%qd), %ld bytes, err %ld\n", readPackets, This->mPacketMark, This->mPacketMark + readPackets, bytesRead, err); #if VERBOSE_IO >= 2 if (This->mPacketDescs) { for (UInt32 i = 0; i < readPackets; ++i) { printf(" read packet %qd : offset %qd, length %ld\n", This->mPacketMark + i, This->mPacketDescs[i].mStartOffset, This->mPacketDescs[i].mDataByteSize); } } printf(" read buffer:"); CAShowAudioBufferList(readBuffer, 0, 4); #endif #endif if (readPackets == 0) { *ioNumberDataPackets = 0; ioData->mBuffers[0].mDataByteSize = 0; return noErr; } if (outDataPacketDescription) *outDataPacketDescription = This->mPacketDescs; ioData->mBuffers[0].mDataByteSize = bytesRead; ioData->mBuffers[0].mData = readBuffer->mBuffers[0].mData; This->mPacketMark += readPackets; if (This->mFileDataFormat.mFramesPerPacket > 0) This->mFrameMark += readPackets * This->mFileDataFormat.mFramesPerPacket; else { for (UInt32 i = 0; i < readPackets; ++i) This->mFrameMark += This->mPacketDescs[i].mVariableFramesInPacket; } *ioNumberDataPackets = readPackets; return noErr; } // _______________________________________________________________________________________ // void CAAudioFile::Write(UInt32 numPackets, const AudioBufferList *data) { if (mIOBufferList.mBuffers[0].mData == NULL) { #if DEBUG printf("warning: CAAudioFile::AllocateBuffers called from WritePackets\n"); #endif AllocateBuffers(); } if (mMode == kPreparingToWrite) mMode = kWriting; else XThrowIf(mMode != kWriting, kExtAudioFileError_InvalidOperationOrder, "can't write to this file"); if (mConverter != NULL) { mWritePackets = numPackets; mWriteBufferList->SetFrom(data); WritePacketsFromCallback(WriteInputProc, this); } else { StartTiming(this, write); XThrowIfError(AudioFileWritePackets(mAudioFile, mUseCache, data->mBuffers[0].mDataByteSize, NULL, mPacketMark, &numPackets, data->mBuffers[0].mData), "write audio file"); ElapsedTime(this, write, mTicksInIO); #if VERBOSE_IO printf("CAAudioFile::WritePackets: wrote %ld packets at %qd, %ld bytes\n", numPackets, mPacketMark, data->mBuffers[0].mDataByteSize); #endif //mNumberPackets = mPacketMark += numPackets; if (mFileDataFormat.mFramesPerPacket > 0) mFrameMark += numPackets * mFileDataFormat.mFramesPerPacket; // else: shouldn't happen since we're only called when there's no converter } } // _______________________________________________________________________________________ // void CAAudioFile::FlushEncoder() { if (mConverter != NULL) { mFinishingEncoding = true; WritePacketsFromCallback(WriteInputProc, this); mFinishingEncoding = false; // get priming info from converter, set it on the file UInt32 propertySize; OSStatus err; AudioConverterPrimeInfo primeInfo; propertySize = sizeof(primeInfo); err = AudioConverterGetProperty(mConverter, kAudioConverterPrimeInfo, &propertySize, &primeInfo); if (err == noErr) { AudioFilePacketTableInfo pti; propertySize = sizeof(pti); err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti); if (err == noErr) { // printf("old packet table info: %qd valid, %ld priming, %ld remainder\n", pti.mNumberValidFrames, pti.mPrimingFrames, pti.mRemainderFrames); UInt64 totalFrames = pti.mNumberValidFrames + pti.mPrimingFrames + pti.mRemainderFrames; pti.mPrimingFrames = primeInfo.leadingFrames; pti.mRemainderFrames = primeInfo.trailingFrames; pti.mNumberValidFrames = totalFrames - pti.mPrimingFrames - pti.mRemainderFrames; // printf("new packet table info: %qd valid, %ld priming, %ld remainder\n", pti.mNumberValidFrames, pti.mPrimingFrames, pti.mRemainderFrames); XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, sizeof(pti), &pti), "couldn't set packet table info on audio file"); } } } } // _______________________________________________________________________________________ // OSStatus CAAudioFile::WriteInputProc( AudioConverterRef /*inAudioConverter*/, UInt32 * ioNumberDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription ** outDataPacketDescription, void* inUserData) { CAAudioFile *This = static_cast(inUserData); if (This->mFinishingEncoding) { *ioNumberDataPackets = 0; ioData->mBuffers[0].mDataByteSize = 0; ioData->mBuffers[0].mData = NULL; if (outDataPacketDescription) *outDataPacketDescription = NULL; return noErr; } UInt32 numPackets = This->mWritePackets; if (numPackets == 0) { return kNoMoreInputRightNow; } This->mWriteBufferList->ToAudioBufferList(ioData); This->mWriteBufferList->BytesConsumed(numPackets * This->mClientDataFormat.mBytesPerFrame); *ioNumberDataPackets = numPackets; if (outDataPacketDescription) *outDataPacketDescription = NULL; This->mWritePackets -= numPackets; return noErr; } // _______________________________________________________________________________________ // #if VERBOSE_IO static void hexdump(const void *addr, long len) { const Byte *p = (Byte *)addr; UInt32 offset = 0; if (len > 0x400) len = 0x400; while (len > 0) { int n = len > 16 ? 16 : len; printf("%08lX: ", offset); for (int i = 0; i < 16; ++i) if (i < n) printf("%02X ", p[i]); else printf(" "); for (int i = 0; i < 16; ++i) if (i < n) putchar(p[i] >= ' ' && p[i] < 127 ? p[i] : '.'); else putchar(' '); putchar('\n'); p += 16; len -= 16; offset += 16; } } #endif // _______________________________________________________________________________________ // void CAAudioFile::WritePacketsFromCallback( AudioConverterComplexInputDataProc inInputDataProc, void * inInputDataProcUserData) { while (true) { // keep writing until we exhaust the input (temporary stop), or produce no output (EOF) UInt32 numEncodedPackets = mIOBufferSizePackets; mIOBufferList.mBuffers[0].mDataByteSize = mIOBufferSizeBytes; StartTiming(this, fill); OSStatus err = AudioConverterFillComplexBuffer(mConverter, inInputDataProc, inInputDataProcUserData, &numEncodedPackets, &mIOBufferList, mPacketDescs); ElapsedTime(this, fill, mTicksInConverter); XThrowIf(err != 0 && err != kNoMoreInputRightNow, err, "convert audio packets (write)"); if (numEncodedPackets == 0) break; Byte *buf = (Byte *)mIOBufferList.mBuffers[0].mData; #if VERBOSE_IO printf("CAAudioFile::WritePacketsFromCallback: wrote %ld packets, %ld bytes\n", numEncodedPackets, mIOBufferList.mBuffers[0].mDataByteSize); if (mPacketDescs) { for (UInt32 i = 0; i < numEncodedPackets; ++i) { printf(" write packet %qd : offset %qd, length %ld\n", mPacketMark + i, mPacketDescs[i].mStartOffset, mPacketDescs[i].mDataByteSize); #if VERBOSE_IO >= 2 hexdump(buf + mPacketDescs[i].mStartOffset, mPacketDescs[i].mDataByteSize); #endif } } #endif StartTiming(this, write); XThrowIfError(AudioFileWritePackets(mAudioFile, mUseCache, mIOBufferList.mBuffers[0].mDataByteSize, mPacketDescs, mPacketMark, &numEncodedPackets, buf), "write audio file"); ElapsedTime(this, write, mTicksInIO); mPacketMark += numEncodedPackets; //mNumberPackets += numEncodedPackets; if (mFileDataFormat.mFramesPerPacket > 0) mFrameMark += numEncodedPackets * mFileDataFormat.mFramesPerPacket; else { for (UInt32 i = 0; i < numEncodedPackets; ++i) mFrameMark += mPacketDescs[i].mVariableFramesInPacket; } if (err == kNoMoreInputRightNow) break; } } #endif // !CAAF_USE_EXTAUDIOFILE