/*============================================================================= CAAudioFile.h $Log: CAAudioFile.h,v $ Revision 1.35 2005/02/02 01:10:05 dwyatt [3984623] define CAAF_USE_EXTAUDIOFILE according to deployment settings Revision 1.34 2004/12/13 22:46:42 dwyatt [3913795] support changing PCM file's length with SetNumberFrames() Revision 1.33 2004/12/02 21:04:00 dwyatt [3865346] on decode: file packet table info -> converter prime info Revision 1.32 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.31 2004/10/25 20:04:17 dwyatt fix: SetIOBufferSizeBytes Revision 1.30 2004/10/21 01:31:33 jcm10 flat headers Revision 1.29 2004/10/15 21:25:46 dwyatt add SetClientChannelLayout Revision 1.28 2004/10/15 17:10:34 dwyatt CAAudioFile rewrite to use ExtAudioFile (changeable via compile flag in CAAudioFile.h) Revision 1.27 2004/09/30 21:05:03 jcm10 make it build on Windows Revision 1.26 2004/06/02 19:26:07 dwyatt add inForWriting argument to Wrap Revision 1.25 2004/05/27 19:48:03 dwyatt [3657283] more cleanup/simplification of correlation between file and playback sample timelines Revision 1.24 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.23 2004/05/26 00:44:19 dwyatt remove Wrap() for writable files, add support for client-owned I/O buffer Revision 1.22 2004/05/21 22:22:29 dwyatt [3657283] sub-packet seeks Revision 1.21 2004/05/17 23:10:50 dwyatt clean up PrepareNew hack with a second sample rate Revision 1.20 2004/05/17 19:13:13 dwyatt back-port a couple Tiger items for pre-Tiger compiles Revision 1.19 2004/05/14 23:09:56 dwyatt first cut at ExtendedAudioFile Revision 1.18 2004/05/13 22:49:03 dwyatt add GetIOBufferSizeBytes accessor Revision 1.17 2004/05/06 21:20:33 dwyatt REALLY re-addin Revision 1.14 2004/05/06 02:00:54 dwyatt add GetNumberFrames, GetDurationSeconds Revision 1.13 2004/03/05 20:41:38 dwyatt fix profiling (separation of I/O and codec time) Revision 1.12 2004/01/31 01:50:21 dwyatt conditionally compile profiling, add seek-to-packet accessors (not yet impl) Revision 1.11 2004/01/13 01:44:34 dwyatt moved from Source/Tests/AudioFileUtility/Utility/ Revision 1.10 2003/12/04 00:59:02 dwyatt refactoring Revision 1.9 2003/10/09 23:21:00 dwyatt allow wrapping audio files that are open for writing Revision 1.8 2003/08/24 08:14:47 dwyatt tweaks to channel layout changes Revision 1.7 2003/08/23 04:58:02 dwyatt first whack at cleaning up channel layouts Revision 1.6 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.5 2003/08/04 23:42:00 dwyatt more rigor around mFileMaxPacketSize Revision 1.4 2003/07/25 23:29:12 dwyatt use constant strings for operations in CAXException Revision 1.3 2003/07/15 17:35:26 dwyatt add profiling Revision 1.2 2003/07/09 19:11:36 dwyatt support channel layouts 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: $ =============================================================================*/ #ifndef __CAAudioFile_h__ #define __CAAudioFile_h__ #include #if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) #include #else #include #endif #include "CAStreamBasicDescription.h" #include "CABufferList.h" #include "CAAudioChannelLayout.h" #include "CAXException.h" #include "CAMath.h" #ifndef CAAF_USE_EXTAUDIOFILE // option: use AudioToolbox/ExtAudioFile.h? Only available on Tiger. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_3 // we are building software that must be deployable on Panther or earlier #define CAAF_USE_EXTAUDIOFILE 0 #else // else we require Tiger and can use the API #define CAAF_USE_EXTAUDIOFILE 1 #endif #endif #ifndef MAC_OS_X_VERSION_10_4 // we have pre-Tiger headers; add our own declarations typedef UInt32 AudioFileTypeID; enum { kExtAudioFileError_InvalidProperty = -66561, kExtAudioFileError_InvalidPropertySize = -66562, kExtAudioFileError_NonPCMClientFormat = -66563, kExtAudioFileError_InvalidChannelMap = -66564, // number of channels doesn't match format kExtAudioFileError_InvalidOperationOrder = -66565, kExtAudioFileError_InvalidDataFormat = -66566, kExtAudioFileError_MaxPacketSizeUnknown = -66567, kExtAudioFileError_InvalidSeek = -66568, // writing, or offset out of bounds kExtAudioFileError_AsyncWriteTooLarge = -66569, kExtAudioFileError_AsyncWriteBufferOverflow = -66570 // an async write could not be completed in time }; #else #if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) #include #else #include "ExtendedAudioFile.h" #endif #endif // _______________________________________________________________________________________ // Wrapper class for an AudioFile, supporting encode/decode to/from a PCM client format class CAAudioFile { public: // implementation-independent helpers void Open(const char *filePath) { FSRef fsref; XThrowIfError(FSPathMakeRef((UInt8 *)filePath, &fsref, NULL), "locate audio file"); Open(fsref); } bool HasConverter() const { return GetConverter() != NULL; } double GetDurationSeconds() { double sr = GetFileDataFormat().mSampleRate; return fnonzero(sr) ? GetNumberFrames() / sr : 0.; } // will be 0 if the file's frames/packet is 0 (variable) // or the file's sample rate is 0 (unknown) #if CAAF_USE_EXTAUDIOFILE public: CAAudioFile() : mExtAF(NULL) { } virtual ~CAAudioFile() { if (mExtAF) Close(); } void Open(const FSRef &fsref) { // open an existing file XThrowIfError(ExtAudioFileOpen(&fsref, &mExtAF), "ExtAudioFileOpen failed"); } void CreateNew(const FSRef &inParentDir, CFStringRef inFileName, AudioFileTypeID inFileType, const AudioStreamBasicDescription &inStreamDesc, const AudioChannelLayout *inChannelLayout=NULL) { XThrowIfError(ExtAudioFileCreateNew(&inParentDir, inFileName, inFileType, &inStreamDesc, inChannelLayout, &mExtAF), "ExtAudioFileCreateNew failed"); } void Wrap(AudioFileID fileID, bool forWriting) { // use this to wrap an AudioFileID opened externally XThrowIfError(ExtAudioFileWrapAudioFileID(fileID, forWriting, &mExtAF), "ExtAudioFileWrapAudioFileID failed"); } void Close() { XThrowIfError(ExtAudioFileDispose(mExtAF), "ExtAudioFileClose failed"); mExtAF = NULL; } const CAStreamBasicDescription &GetFileDataFormat() { UInt32 size = sizeof(mFileDataFormat); XThrowIfError(ExtAudioFileGetProperty(mExtAF, kExtAudioFileProperty_FileDataFormat, &size, &mFileDataFormat), "Couldn't get file's data format"); return mFileDataFormat; } const CAAudioChannelLayout & GetFileChannelLayout() { return FetchChannelLayout(mFileChannelLayout, kExtAudioFileProperty_FileChannelLayout); } void SetFileChannelLayout(const CAAudioChannelLayout &layout) { XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_FileChannelLayout, layout.Size(), &layout.Layout()), "Couldn't set file's channel layout"); mFileChannelLayout = layout; } const CAStreamBasicDescription &GetClientDataFormat() { UInt32 size = sizeof(mClientDataFormat); XThrowIfError(ExtAudioFileGetProperty(mExtAF, kExtAudioFileProperty_ClientDataFormat, &size, &mClientDataFormat), "Couldn't get client data format"); return mClientDataFormat; } const CAAudioChannelLayout & GetClientChannelLayout() { return FetchChannelLayout(mClientChannelLayout, kExtAudioFileProperty_ClientChannelLayout); } void SetClientFormat(const CAStreamBasicDescription &dataFormat, const CAAudioChannelLayout *layout=NULL) { XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_ClientDataFormat, sizeof(dataFormat), &dataFormat), "Couldn't set client format"); if (layout) SetClientChannelLayout(*layout); } void SetClientChannelLayout(const CAAudioChannelLayout &layout) { XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_ClientChannelLayout, layout.Size(), &layout.Layout()), "Couldn't set client channel layout"); } AudioConverterRef GetConverter() const { UInt32 size = sizeof(AudioConverterRef); AudioConverterRef converter; XThrowIfError(ExtAudioFileGetProperty(mExtAF, kExtAudioFileProperty_AudioConverter, &size, &converter), "Couldn't get file's AudioConverter"); return converter; } OSStatus SetConverterProperty(AudioConverterPropertyID inPropertyID, UInt32 inPropertyDataSize, const void *inPropertyData, bool inCanFail=false) { OSStatus err = AudioConverterSetProperty(GetConverter(), inPropertyID, inPropertyDataSize, inPropertyData); if (!inCanFail) XThrowIfError(err, "Couldn't set audio converter property"); return err; } SInt64 GetNumberFrames() { SInt64 length; UInt32 size = sizeof(SInt64); XThrowIfError(ExtAudioFileGetProperty(mExtAF, kExtAudioFileProperty_FileLengthFrames, &size, &length), "Couldn't get file's length"); return length; } void SetNumberFrames(SInt64 length) { XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_FileLengthFrames, sizeof(SInt64), &length), "Couldn't set file's length"); } void Seek(SInt64 pos) { XThrowIfError(ExtAudioFileSeek(mExtAF, pos), "Couldn't seek in audio file"); } SInt64 Tell() { SInt64 pos; XThrowIfError(ExtAudioFileTell(mExtAF, &pos), "Couldn't get file's mark"); return pos; } void Read(UInt32 &ioFrames, AudioBufferList *ioData) { XThrowIfError(ExtAudioFileRead(mExtAF, &ioFrames, ioData), "Couldn't read audio file"); } void Write(UInt32 inFrames, const AudioBufferList *inData) { XThrowIfError(ExtAudioFileWrite(mExtAF, inFrames, inData), "Couldn't write audio file"); } void SetIOBufferSizeBytes(UInt32 bufferSizeBytes) { XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_IOBufferSizeBytes, sizeof(UInt32), &bufferSizeBytes), "Couldn't set audio file's I/O buffer size"); } private: const CAAudioChannelLayout & FetchChannelLayout(CAAudioChannelLayout &layoutObj, ExtAudioFilePropertyID propID) { UInt32 size; XThrowIfError(ExtAudioFileGetPropertyInfo(mExtAF, propID, &size, NULL), "Couldn't get info about channel layout"); AudioChannelLayout *layout = (AudioChannelLayout *)malloc(size); OSStatus err = ExtAudioFileGetProperty(mExtAF, propID, &size, layout); if (err) { free(layout); XThrowIfError(err, "Couldn't get channel layout"); } layoutObj = layout; free(layout); return layoutObj; } private: ExtAudioFileRef mExtAF; CAStreamBasicDescription mFileDataFormat; CAAudioChannelLayout mFileChannelLayout; CAStreamBasicDescription mClientDataFormat; CAAudioChannelLayout mClientChannelLayout; #endif #if !CAAF_USE_EXTAUDIOFILE CAAudioFile(); virtual ~CAAudioFile(); // --- second-stage initializers --- // Use exactly one of the following: // - Open // - PrepareNew followed by Create // - Wrap void Open(const FSRef &fsref); // open an existing file void CreateNew(const FSRef &inParentDir, CFStringRef inFileName, AudioFileTypeID inFileType, const AudioStreamBasicDescription &inStreamDesc, const AudioChannelLayout *inChannelLayout=NULL); void Wrap(AudioFileID fileID, bool forWriting); // use this to wrap an AudioFileID opened externally // --- void Close(); // In case you want to close the file before the destructor executes // --- Data formats --- // Allow specifying the file's channel layout. Must be called before SetClientFormat. // When writing, the specified channel layout is written to the file (if the file format supports // the channel layout). When reading, the specified layout overrides the one read from the file, // if any. void SetFileChannelLayout(const CAAudioChannelLayout &layout); // This specifies the data format which the client will use for reading/writing the file, // which may be different from the file's format. An AudioConverter is created if necessary. // The client format must be linear PCM. void SetClientFormat(const CAStreamBasicDescription &dataFormat, const CAAudioChannelLayout *layout=NULL); void SetClientDataFormat(const CAStreamBasicDescription &dataFormat) { SetClientFormat(dataFormat, NULL); } void SetClientChannelLayout(const CAAudioChannelLayout &layout) { SetClientFormat(mClientDataFormat, &layout); } // Wrapping the underlying converter, if there is one OSStatus SetConverterProperty(AudioConverterPropertyID inPropertyID, UInt32 inPropertyDataSize, const void * inPropertyData, bool inCanFail = false); void SetConverterConfig(CFArrayRef config) { SetConverterProperty(kAudioConverterPropertySettings, sizeof(config), &config); } CFArrayRef GetConverterConfig(); // --- I/O --- // All I/O is sequential, but you can seek to an arbitrary position when reading. // SeekToPacket and TellPacket's packet numbers are in the file's data format, not the client's. // However, ReadPackets/WritePackets use packet counts in the client data format. void Read(UInt32 &ioNumFrames, AudioBufferList *ioData); void Write(UInt32 numFrames, const AudioBufferList *data); // These can fail for files without a constant mFramesPerPacket void Seek(SInt64 frameNumber); SInt64 Tell() const; // frameNumber // --- Accessors --- // note: client parameters only valid if SetClientFormat has been called AudioFileID GetAudioFileID() const { return mAudioFile; } const CAStreamBasicDescription &GetFileDataFormat() const { return mFileDataFormat; } const CAStreamBasicDescription &GetClientDataFormat() const { return mClientDataFormat; } const CAAudioChannelLayout & GetFileChannelLayout() const { return mFileChannelLayout; } const CAAudioChannelLayout & GetClientChannelLayout() const { return mClientChannelLayout; } AudioConverterRef GetConverter() const { return mConverter; } UInt32 GetFileMaxPacketSize() const { return mFileMaxPacketSize; } UInt32 GetClientMaxPacketSize() const { return mClientMaxPacketSize; } SInt64 GetNumberPackets() const { SInt64 npackets; UInt32 propertySize = sizeof(npackets); XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, &propertySize, &npackets), "get audio file's packet count"); return npackets; } SInt64 GetNumberFrames() const; // will be 0 if the file's frames/packet is 0 (variable) void SetNumberFrames(SInt64 length); // should only be set on a PCM file // --- Tunable performance parameters --- void SetUseCache(bool b) { mUseCache = b; } void SetIOBufferSizeBytes(UInt32 bufferSizeBytes) { mIOBufferSizeBytes = bufferSizeBytes; } UInt32 GetIOBufferSizeBytes() { return mIOBufferSizeBytes; } void * GetIOBuffer() { return mIOBufferList.mBuffers[0].mData; } void SetIOBuffer(void *buf); // -- Profiling --- #if CAAUDIOFILE_PROFILE void EnableProfiling(bool b) { mProfiling = b; } UInt64 TicksInConverter() const { return (mTicksInConverter > 0) ? (mTicksInConverter - mTicksInIO) : 0; } UInt64 TicksInIO() const { return mTicksInIO; } #endif // _______________________________________________________________________________________ private: SInt64 FileDataOffset(); void SeekToPacket(SInt64 packetNumber); SInt64 TellPacket() const { return mPacketMark; } // will be imprecise if SeekToFrame was called void SetConverterChannelLayout(bool output, const CAAudioChannelLayout &layout); void WritePacketsFromCallback( AudioConverterComplexInputDataProc inInputDataProc, void * inInputDataProcUserData); // will use I/O buffer size void InitFileMaxPacketSize(); void FileFormatChanged(const FSRef *parentDir=0, CFStringRef filename=0, AudioFileTypeID filetype=0); void GetExistingFileInfo(); void FlushEncoder(); void CloseConverter(); void UpdateClientMaxPacketSize(); void AllocateBuffers(bool okToFail=false); SInt64 PacketToFrame(SInt64 packet) const; SInt64 FrameToPacket(SInt64 inFrame) const; static OSStatus ReadInputProc( AudioConverterRef inAudioConverter, UInt32* ioNumberDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription** outDataPacketDescription, void* inUserData); static OSStatus WriteInputProc( AudioConverterRef inAudioConverter, UInt32* ioNumberDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription** outDataPacketDescription, void* inUserData); // _______________________________________________________________________________________ private: // the file FSRef mFSRef; AudioFileID mAudioFile; bool mOwnOpenFile; bool mUseCache; bool mFinishingEncoding; enum { kClosed, kReading, kPreparingToCreate, kPreparingToWrite, kWriting } mMode; // SInt64 mNumberPackets; // in file's format SInt64 mFileDataOffset; SInt64 mPacketMark; // in file's format SInt64 mFrameMark; // this may be offset from the start of the file // by the codec's latency; i.e. our frame 0 could // lie at frame 2112 of a decoded AAC file SInt32 mFrame0Offset; UInt32 mFramesToSkipFollowingSeek; // buffers UInt32 mIOBufferSizeBytes; UInt32 mIOBufferSizePackets; AudioBufferList mIOBufferList; // only one buffer -- USE ACCESSOR so it can be lazily initialized bool mClientOwnsIOBuffer; AudioStreamPacketDescription *mPacketDescs; UInt32 mNumPacketDescs; // formats/conversion AudioConverterRef mConverter; CAStreamBasicDescription mFileDataFormat; CAStreamBasicDescription mClientDataFormat; CAAudioChannelLayout mFileChannelLayout; CAAudioChannelLayout mClientChannelLayout; UInt32 mFileMaxPacketSize; UInt32 mClientMaxPacketSize; // cookie Byte * mMagicCookie; UInt32 mMagicCookieSize; // for ReadPackets UInt32 mMaxPacketsToRead; // for WritePackets UInt32 mWritePackets; CABufferList * mWriteBufferList; #if CAAUDIOFILE_PROFILE // performance bool mProfiling; UInt64 mTicksInConverter; UInt64 mTicksInIO; #endif #endif // CAAF_USE_EXTAUDIOFILE }; #endif // __CAAudioFile_h__