/* Copyright: © Copyright 2005 Apple Computer, Inc. All rights reserved. Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Computer, Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*============================================================================= CAAudioFile.cpp =============================================================================*/ #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 #if VERBOSE_CHANNELMAP #include "CAChannelLayouts.h" // this is in Source/Tests/AudioFileTools/Utility #endif #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); if (sIndent == 0) printf("\n"); } 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 static const UInt32 kDefaultIOBufferSizeBytes = 0x10000; #if CAAUDIOFILE_PROFILE #define StartTiming(af, starttime) UInt64 starttime = af->mProfiling ? CAHostTimeBase::GetTheCurrentTime() : 0 #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), mTicksInReadInConverter(0), mTicksInIO(0), mInConverter(false) #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 from converter: %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 mFileDataFormat.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 (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]; XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, mMagicCookie), "get audio converter's magic cookie"); mMagicCookieSize = propertySize; // the converter lies and tell us the wrong size // 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) { #if VERBOSE_CONVERTER printf("Setting cookie on encoded file\n"); #endif 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 (write)\n"); #endif } } 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() - mFrame0Offset; } // _______________________________________________________________________________________ // 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): %s\n", mMode == kReading ? "read" : "write", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag())); #endif if (mMode != kReading) 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 if (mFileDataFormat.mBitsPerChannel == 0) { 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; /* ignore any error. better to play it at all than not. */ /*err = */AudioConverterSetProperty(mConverter, kAudioConverterPrimeInfo, sizeof(primeInfo), &primeInfo); //XThrowIfError(err, "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) { OSStatus err = noErr; //LOG_FUNCTION("ExtAudioFile::SetConverterProperty", "%p %-4.4s", this, (char *)&inPropertyID); if (inPropertyID == kAudioConverterPropertySettings && *(CFPropertyListRef *)inPropertyData == NULL) ; else { 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 VERBOSE_CHANNELMAP printf("Setting converter's %s channel layout: %s\n", output ? "output" : "input", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag())); #endif 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"); if (mFileDataFormat.mBitsPerChannel == 0) { 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 = ~0UL; 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 { #if CAAUDIOFILE_PROFILE mInConverter = true; #endif StartTiming(this, fill); XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &skipFrames, ioData, NULL), "convert audio packets (pcm read)"); ElapsedTime(this, fill, mTicksInConverter); #if CAAUDIOFILE_PROFILE mInConverter = false; #endif } if (skipFrames == 0) { // hit EOF ioNumPackets = 0; return; } mFrameMark += skipFrames; #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 { #if CAAUDIOFILE_PROFILE mInConverter = true; #endif StartTiming(this, fill); XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &nPackets, ioData, NULL), "convert audio packets (read)"); ElapsedTime(this, fill, mTicksInConverter); #if CAAUDIOFILE_PROFILE mInConverter = false; #endif } if (mClientDataFormat.mFramesPerPacket == 1) mFrameMark += nPackets; 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); StartTiming(This, readinconv); err = AudioFileReadPackets(This->mAudioFile, This->mUseCache, &bytesRead, This->mPacketDescs, This->mPacketMark, &readPackets, readBuffer->mBuffers[0].mData); #if CAAUDIOFILE_PROFILE if (This->mInConverter) ElapsedTime(This, readinconv, This->mTicksInReadInConverter); #endif 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->mClientDataFormat.mFramesPerPacket != 1) { // for PCM client formats we update in Read // but for non-PCM client format (weird case) we must update here/now 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 if (mFileDataFormat.mBitsPerChannel == 0) { 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; #if CAAUDIOFILE_PROFILE mInConverter = true; #endif StartTiming(this, fill); OSStatus err = AudioConverterFillComplexBuffer(mConverter, inInputDataProc, inInputDataProcUserData, &numEncodedPackets, &mIOBufferList, mPacketDescs); ElapsedTime(this, fill, mTicksInConverter); #if CAAUDIOFILE_PROFILE mInConverter = false; #endif 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