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