1 /* Copyright: � Copyright 2005 Apple Computer, Inc. All rights reserved. 2 3 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. 4 ("Apple") in consideration of your agreement to the following terms, and your 5 use, installation, modification or redistribution of this Apple software 6 constitutes acceptance of these terms. If you do not agree with these terms, 7 please do not use, install, modify or redistribute this Apple software. 8 9 In consideration of your agreement to abide by the following terms, and subject 10 to these terms, Apple grants you a personal, non-exclusive license, under Apple�s 11 copyrights in this original Apple software (the "Apple Software"), to use, 12 reproduce, modify and redistribute the Apple Software, with or without 13 modifications, in source and/or binary forms; provided that if you redistribute 14 the Apple Software in its entirety and without modifications, you must retain 15 this notice and the following text and disclaimers in all such redistributions of 16 the Apple Software. Neither the name, trademarks, service marks or logos of 17 Apple Computer, Inc. may be used to endorse or promote products derived from the 18 Apple Software without specific prior written permission from Apple. Except as 19 expressly stated in this notice, no other rights or licenses, express or implied, 20 are granted by Apple herein, including but not limited to any patent rights that 21 may be infringed by your derivative works or by other works in which the Apple 22 Software may be incorporated. 23 24 The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 25 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 26 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 27 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 28 COMBINATION WITH YOUR PRODUCTS. 29 30 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 31 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 32 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION 34 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT 35 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN 36 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 */ 38 /*============================================================================= 39 CAAudioFile.h 40 41 =============================================================================*/ 42 43 #ifndef __CAAudioFile_h__ 44 #define __CAAudioFile_h__ 45 46 #include <iostream> 47 #include <AvailabilityMacros.h> 48 49 #if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) 50 #include <AudioToolbox/AudioToolbox.h> 51 #else 52 #include <AudioToolbox.h> 53 #endif 54 55 #include "CAStreamBasicDescription.h" 56 #include "CABufferList.h" 57 #include "CAAudioChannelLayout.h" 58 #include "CAXException.h" 59 #include "CAMath.h" 60 61 #ifndef CAAF_USE_EXTAUDIOFILE 62 // option: use AudioToolbox/ExtAudioFile.h? Only available on Tiger. 63 #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_3 64 // we are building software that must be deployable on Panther or earlier 65 #define CAAF_USE_EXTAUDIOFILE 0 66 #else 67 // else we require Tiger and can use the API 68 #define CAAF_USE_EXTAUDIOFILE 1 69 #endif 70 #endif 71 72 #ifndef MAC_OS_X_VERSION_10_4 73 // we have pre-Tiger headers; add our own declarations 74 typedef UInt32 AudioFileTypeID; 75 enum { 76 kExtAudioFileError_InvalidProperty = -66561, 77 kExtAudioFileError_InvalidPropertySize = -66562, 78 kExtAudioFileError_NonPCMClientFormat = -66563, 79 kExtAudioFileError_InvalidChannelMap = -66564, // number of channels doesn't match format 80 kExtAudioFileError_InvalidOperationOrder = -66565, 81 kExtAudioFileError_InvalidDataFormat = -66566, 82 kExtAudioFileError_MaxPacketSizeUnknown = -66567, 83 kExtAudioFileError_InvalidSeek = -66568, // writing, or offset out of bounds 84 kExtAudioFileError_AsyncWriteTooLarge = -66569, 85 kExtAudioFileError_AsyncWriteBufferOverflow = -66570 // an async write could not be completed in time 86 }; 87 #else 88 #if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) 89 #include <AudioToolbox/ExtendedAudioFile.h> 90 #else 91 #include "ExtendedAudioFile.h" 92 #endif 93 #endif 94 95 // _______________________________________________________________________________________ 96 // Wrapper class for an AudioFile, supporting encode/decode to/from a PCM client format 97 class CAAudioFile { 98 public: 99 // implementation-independent helpers Open(const char * filePath)100 void Open(const char *filePath) { 101 FSRef fsref; 102 std::cerr << "Opening " << filePath << std::endl; 103 XThrowIfError(FSPathMakeRef((UInt8 *)filePath, &fsref, NULL), "locate audio file"); 104 Open(fsref); 105 } 106 HasConverter()107 bool HasConverter() const { return GetConverter() != NULL; } 108 GetDurationSeconds()109 double GetDurationSeconds() { 110 double sr = GetFileDataFormat().mSampleRate; 111 return fnonzero(sr) ? GetNumberFrames() / sr : 0.; 112 } 113 // will be 0 if the file's frames/packet is 0 (variable) 114 // or the file's sample rate is 0 (unknown) 115 116 #if CAAF_USE_EXTAUDIOFILE 117 public: CAAudioFile()118 CAAudioFile() : mExtAF(NULL) { std::cerr << "Constructing CAAudioFile\n"; } ~CAAudioFile()119 virtual ~CAAudioFile() { std::cerr << "Destroying CAAudiofile @ " << this << std::endl; if (mExtAF) Close(); } 120 Open(const FSRef & fsref)121 void Open(const FSRef &fsref) { 122 // open an existing file 123 XThrowIfError(ExtAudioFileOpen(&fsref, &mExtAF), "ExtAudioFileOpen failed"); 124 } 125 126 void CreateNew(const FSRef &inParentDir, CFStringRef inFileName, AudioFileTypeID inFileType, const AudioStreamBasicDescription &inStreamDesc, const AudioChannelLayout *inChannelLayout=NULL) { 127 XThrowIfError(ExtAudioFileCreateNew(&inParentDir, inFileName, inFileType, &inStreamDesc, inChannelLayout, &mExtAF), "ExtAudioFileCreateNew failed"); 128 } 129 Wrap(AudioFileID fileID,bool forWriting)130 void Wrap(AudioFileID fileID, bool forWriting) { 131 // use this to wrap an AudioFileID opened externally 132 XThrowIfError(ExtAudioFileWrapAudioFileID(fileID, forWriting, &mExtAF), "ExtAudioFileWrapAudioFileID failed"); 133 } 134 Close()135 void Close() { 136 std::cerr << "\tdisposeo of ext audio file @ " << mExtAF << std::endl; 137 XThrowIfError(ExtAudioFileDispose(mExtAF), "ExtAudioFileClose failed"); 138 mExtAF = NULL; 139 } 140 GetFileDataFormat()141 const CAStreamBasicDescription &GetFileDataFormat() { 142 UInt32 size = sizeof(mFileDataFormat); 143 XThrowIfError(ExtAudioFileGetProperty(mExtAF, kExtAudioFileProperty_FileDataFormat, &size, &mFileDataFormat), "Couldn't get file's data format"); 144 return mFileDataFormat; 145 } 146 GetFileChannelLayout()147 const CAAudioChannelLayout & GetFileChannelLayout() { 148 return FetchChannelLayout(mFileChannelLayout, kExtAudioFileProperty_FileChannelLayout); 149 } 150 SetFileChannelLayout(const CAAudioChannelLayout & layout)151 void SetFileChannelLayout(const CAAudioChannelLayout &layout) { 152 XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_FileChannelLayout, layout.Size(), &layout.Layout()), "Couldn't set file's channel layout"); 153 mFileChannelLayout = layout; 154 } 155 GetClientDataFormat()156 const CAStreamBasicDescription &GetClientDataFormat() { 157 UInt32 size = sizeof(mClientDataFormat); 158 XThrowIfError(ExtAudioFileGetProperty(mExtAF, kExtAudioFileProperty_ClientDataFormat, &size, &mClientDataFormat), "Couldn't get client data format"); 159 return mClientDataFormat; 160 } 161 GetClientChannelLayout()162 const CAAudioChannelLayout & GetClientChannelLayout() { 163 return FetchChannelLayout(mClientChannelLayout, kExtAudioFileProperty_ClientChannelLayout); 164 } 165 166 void SetClientFormat(const CAStreamBasicDescription &dataFormat, const CAAudioChannelLayout *layout=NULL) { 167 XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_ClientDataFormat, sizeof(dataFormat), &dataFormat), "Couldn't set client format"); 168 if (layout) 169 SetClientChannelLayout(*layout); 170 } 171 SetClientChannelLayout(const CAAudioChannelLayout & layout)172 void SetClientChannelLayout(const CAAudioChannelLayout &layout) { 173 XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_ClientChannelLayout, layout.Size(), &layout.Layout()), "Couldn't set client channel layout"); 174 } 175 GetConverter()176 AudioConverterRef GetConverter() const { 177 UInt32 size = sizeof(AudioConverterRef); 178 AudioConverterRef converter; 179 XThrowIfError(ExtAudioFileGetProperty(mExtAF, kExtAudioFileProperty_AudioConverter, &size, &converter), "Couldn't get file's AudioConverter"); 180 return converter; 181 } 182 183 OSStatus SetConverterProperty(AudioConverterPropertyID inPropertyID, UInt32 inPropertyDataSize, const void *inPropertyData, bool inCanFail=false) 184 { 185 OSStatus err = AudioConverterSetProperty(GetConverter(), inPropertyID, inPropertyDataSize, inPropertyData); 186 if (!inCanFail) 187 XThrowIfError(err, "Couldn't set audio converter property"); 188 if (!err) { 189 // must tell the file that we have changed the converter; a NULL converter config is sufficient 190 CFPropertyListRef config = NULL; 191 XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_ConverterConfig, sizeof(CFPropertyListRef), &config), "couldn't signal the file that the converter has changed"); 192 } 193 return err; 194 } 195 GetNumberFrames()196 SInt64 GetNumberFrames() { 197 SInt64 length; 198 UInt32 size = sizeof(SInt64); 199 XThrowIfError(ExtAudioFileGetProperty(mExtAF, kExtAudioFileProperty_FileLengthFrames, &size, &length), "Couldn't get file's length"); 200 return length; 201 } 202 SetNumberFrames(SInt64 length)203 void SetNumberFrames(SInt64 length) { 204 XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_FileLengthFrames, sizeof(SInt64), &length), "Couldn't set file's length"); 205 } 206 Seek(SInt64 pos)207 void Seek(SInt64 pos) { 208 XThrowIfError(ExtAudioFileSeek(mExtAF, pos), "Couldn't seek in audio file"); 209 } 210 Tell()211 SInt64 Tell() { 212 SInt64 pos; 213 XThrowIfError(ExtAudioFileTell(mExtAF, &pos), "Couldn't get file's mark"); 214 return pos; 215 } 216 Read(UInt32 & ioFrames,AudioBufferList * ioData)217 void Read(UInt32 &ioFrames, AudioBufferList *ioData) { 218 XThrowIfError(ExtAudioFileRead(mExtAF, &ioFrames, ioData), "Couldn't read audio file"); 219 } 220 Write(UInt32 inFrames,const AudioBufferList * inData)221 void Write(UInt32 inFrames, const AudioBufferList *inData) { 222 XThrowIfError(ExtAudioFileWrite(mExtAF, inFrames, inData), "Couldn't write audio file"); 223 } 224 SetIOBufferSizeBytes(UInt32 bufferSizeBytes)225 void SetIOBufferSizeBytes(UInt32 bufferSizeBytes) { 226 XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_IOBufferSizeBytes, sizeof(UInt32), &bufferSizeBytes), "Couldn't set audio file's I/O buffer size"); 227 } 228 229 private: FetchChannelLayout(CAAudioChannelLayout & layoutObj,ExtAudioFilePropertyID propID)230 const CAAudioChannelLayout & FetchChannelLayout(CAAudioChannelLayout &layoutObj, ExtAudioFilePropertyID propID) { 231 UInt32 size; 232 XThrowIfError(ExtAudioFileGetPropertyInfo(mExtAF, propID, &size, NULL), "Couldn't get info about channel layout"); 233 AudioChannelLayout *layout = (AudioChannelLayout *)malloc(size); 234 OSStatus err = ExtAudioFileGetProperty(mExtAF, propID, &size, layout); 235 if (err) { 236 free(layout); 237 XThrowIfError(err, "Couldn't get channel layout"); 238 } 239 layoutObj = layout; 240 free(layout); 241 return layoutObj; 242 } 243 244 245 private: 246 ExtAudioFileRef mExtAF; 247 248 CAStreamBasicDescription mFileDataFormat; 249 CAAudioChannelLayout mFileChannelLayout; 250 251 CAStreamBasicDescription mClientDataFormat; 252 CAAudioChannelLayout mClientChannelLayout; 253 #endif 254 255 #if !CAAF_USE_EXTAUDIOFILE 256 CAAudioFile(); 257 virtual ~CAAudioFile(); 258 259 // --- second-stage initializers --- 260 // Use exactly one of the following: 261 // - Open 262 // - PrepareNew followed by Create 263 // - Wrap 264 265 void Open(const FSRef &fsref); 266 // open an existing file 267 268 void CreateNew(const FSRef &inParentDir, CFStringRef inFileName, AudioFileTypeID inFileType, const AudioStreamBasicDescription &inStreamDesc, const AudioChannelLayout *inChannelLayout=NULL); 269 270 void Wrap(AudioFileID fileID, bool forWriting); 271 // use this to wrap an AudioFileID opened externally 272 273 // --- 274 275 void Close(); 276 // In case you want to close the file before the destructor executes 277 278 // --- Data formats --- 279 280 // Allow specifying the file's channel layout. Must be called before SetClientFormat. 281 // When writing, the specified channel layout is written to the file (if the file format supports 282 // the channel layout). When reading, the specified layout overrides the one read from the file, 283 // if any. 284 void SetFileChannelLayout(const CAAudioChannelLayout &layout); 285 286 // This specifies the data format which the client will use for reading/writing the file, 287 // which may be different from the file's format. An AudioConverter is created if necessary. 288 // The client format must be linear PCM. 289 void SetClientFormat(const CAStreamBasicDescription &dataFormat, const CAAudioChannelLayout *layout=NULL); SetClientDataFormat(const CAStreamBasicDescription & dataFormat)290 void SetClientDataFormat(const CAStreamBasicDescription &dataFormat) { SetClientFormat(dataFormat, NULL); } SetClientChannelLayout(const CAAudioChannelLayout & layout)291 void SetClientChannelLayout(const CAAudioChannelLayout &layout) { SetClientFormat(mClientDataFormat, &layout); } 292 293 // Wrapping the underlying converter, if there is one 294 OSStatus SetConverterProperty(AudioConverterPropertyID inPropertyID, 295 UInt32 inPropertyDataSize, 296 const void * inPropertyData, 297 bool inCanFail = false); SetConverterConfig(CFArrayRef config)298 void SetConverterConfig(CFArrayRef config) { 299 SetConverterProperty(kAudioConverterPropertySettings, sizeof(config), &config); } 300 CFArrayRef GetConverterConfig(); 301 302 // --- I/O --- 303 // All I/O is sequential, but you can seek to an arbitrary position when reading. 304 // SeekToPacket and TellPacket's packet numbers are in the file's data format, not the client's. 305 // However, ReadPackets/WritePackets use packet counts in the client data format. 306 307 void Read(UInt32 &ioNumFrames, AudioBufferList *ioData); 308 void Write(UInt32 numFrames, const AudioBufferList *data); 309 310 // These can fail for files without a constant mFramesPerPacket 311 void Seek(SInt64 frameNumber); 312 SInt64 Tell() const; // frameNumber 313 314 // --- Accessors --- 315 // note: client parameters only valid if SetClientFormat has been called GetAudioFileID()316 AudioFileID GetAudioFileID() const { return mAudioFile; } GetFileDataFormat()317 const CAStreamBasicDescription &GetFileDataFormat() const { return mFileDataFormat; } GetClientDataFormat()318 const CAStreamBasicDescription &GetClientDataFormat() const { return mClientDataFormat; } GetFileChannelLayout()319 const CAAudioChannelLayout & GetFileChannelLayout() const { return mFileChannelLayout; } GetClientChannelLayout()320 const CAAudioChannelLayout & GetClientChannelLayout() const { return mClientChannelLayout; } GetConverter()321 AudioConverterRef GetConverter() const { return mConverter; } 322 GetFileMaxPacketSize()323 UInt32 GetFileMaxPacketSize() const { return mFileMaxPacketSize; } GetClientMaxPacketSize()324 UInt32 GetClientMaxPacketSize() const { return mClientMaxPacketSize; } GetNumberPackets()325 SInt64 GetNumberPackets() const { 326 SInt64 npackets; 327 UInt32 propertySize = sizeof(npackets); 328 XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, &propertySize, &npackets), "get audio file's packet count"); 329 return npackets; 330 } 331 SInt64 GetNumberFrames() const; 332 // will be 0 if the file's frames/packet is 0 (variable) 333 void SetNumberFrames(SInt64 length); // should only be set on a PCM file 334 335 // --- Tunable performance parameters --- SetUseCache(bool b)336 void SetUseCache(bool b) { mUseCache = b; } SetIOBufferSizeBytes(UInt32 bufferSizeBytes)337 void SetIOBufferSizeBytes(UInt32 bufferSizeBytes) { mIOBufferSizeBytes = bufferSizeBytes; } GetIOBufferSizeBytes()338 UInt32 GetIOBufferSizeBytes() { return mIOBufferSizeBytes; } GetIOBuffer()339 void * GetIOBuffer() { return mIOBufferList.mBuffers[0].mData; } 340 void SetIOBuffer(void *buf); 341 342 // -- Profiling --- 343 #if CAAUDIOFILE_PROFILE EnableProfiling(bool b)344 void EnableProfiling(bool b) { mProfiling = b; } TicksInConverter()345 UInt64 TicksInConverter() const { return (mTicksInConverter > 0) ? (mTicksInConverter - mTicksInReadInConverter) : 0; } TicksInIO()346 UInt64 TicksInIO() const { return mTicksInIO; } 347 #endif 348 349 // _______________________________________________________________________________________ 350 private: 351 SInt64 FileDataOffset(); 352 void SeekToPacket(SInt64 packetNumber); TellPacket()353 SInt64 TellPacket() const { return mPacketMark; } // will be imprecise if SeekToFrame was called 354 355 void SetConverterChannelLayout(bool output, const CAAudioChannelLayout &layout); 356 void WritePacketsFromCallback( 357 AudioConverterComplexInputDataProc inInputDataProc, 358 void * inInputDataProcUserData); 359 // will use I/O buffer size 360 void InitFileMaxPacketSize(); 361 void FileFormatChanged(const FSRef *parentDir=0, CFStringRef filename=0, AudioFileTypeID filetype=0); 362 363 void GetExistingFileInfo(); 364 void FlushEncoder(); 365 void CloseConverter(); 366 void UpdateClientMaxPacketSize(); 367 void AllocateBuffers(bool okToFail=false); 368 SInt64 PacketToFrame(SInt64 packet) const; 369 SInt64 FrameToPacket(SInt64 inFrame) const; 370 371 static OSStatus ReadInputProc( AudioConverterRef inAudioConverter, 372 UInt32* ioNumberDataPackets, 373 AudioBufferList* ioData, 374 AudioStreamPacketDescription** outDataPacketDescription, 375 void* inUserData); 376 377 static OSStatus WriteInputProc( AudioConverterRef inAudioConverter, 378 UInt32* ioNumberDataPackets, 379 AudioBufferList* ioData, 380 AudioStreamPacketDescription** outDataPacketDescription, 381 void* inUserData); 382 // _______________________________________________________________________________________ 383 private: 384 385 // the file 386 FSRef mFSRef; 387 AudioFileID mAudioFile; 388 bool mOwnOpenFile; 389 bool mUseCache; 390 bool mFinishingEncoding; 391 enum { kClosed, kReading, kPreparingToCreate, kPreparingToWrite, kWriting } mMode; 392 393 // SInt64 mNumberPackets; // in file's format 394 SInt64 mFileDataOffset; 395 SInt64 mPacketMark; // in file's format 396 SInt64 mFrameMark; // this may be offset from the start of the file 397 // by the codec's latency; i.e. our frame 0 could 398 // lie at frame 2112 of a decoded AAC file 399 SInt32 mFrame0Offset; 400 UInt32 mFramesToSkipFollowingSeek; 401 402 // buffers 403 UInt32 mIOBufferSizeBytes; 404 UInt32 mIOBufferSizePackets; 405 AudioBufferList mIOBufferList; // only one buffer -- USE ACCESSOR so it can be lazily initialized 406 bool mClientOwnsIOBuffer; 407 AudioStreamPacketDescription *mPacketDescs; 408 UInt32 mNumPacketDescs; 409 410 // formats/conversion 411 AudioConverterRef mConverter; 412 CAStreamBasicDescription mFileDataFormat; 413 CAStreamBasicDescription mClientDataFormat; 414 CAAudioChannelLayout mFileChannelLayout; 415 CAAudioChannelLayout mClientChannelLayout; 416 UInt32 mFileMaxPacketSize; 417 UInt32 mClientMaxPacketSize; 418 419 // cookie 420 Byte * mMagicCookie; 421 UInt32 mMagicCookieSize; 422 423 // for ReadPackets 424 UInt32 mMaxPacketsToRead; 425 426 // for WritePackets 427 UInt32 mWritePackets; 428 CABufferList * mWriteBufferList; 429 430 #if CAAUDIOFILE_PROFILE 431 // performance 432 bool mProfiling; 433 UInt64 mTicksInConverter; 434 UInt64 mTicksInReadInConverter; 435 UInt64 mTicksInIO; 436 bool mInConverter; 437 #endif 438 439 #endif // CAAF_USE_EXTAUDIOFILE 440 }; 441 442 #endif // __CAAudioFile_h__ 443