/*=============================================================================
	CAAudioFile.h
	
	$Log: CAAudioFile.h,v $
	Revision 1.35.2.1  2005/04/21 19:28:35  dwyatt
	bring over a few things from TOT for the benefit of test tools
	
	Revision 1.36  2005/03/12 19:05:07  dwyatt
	support kExtAudioFileProperty_ConverterConfig of NULL so that converter bitrate changes (etc) get applied to encoded file format
	
	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 <AvailabilityMacros.h>

#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
	#include <AudioToolbox/AudioToolbox.h>
#else
	#include <AudioToolbox.h>
#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 <AudioToolbox/ExtendedAudioFile.h>
	#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");
		if (!err) {
			// must tell the file that we have changed the converter; a NULL converter config is sufficient
			CFPropertyListRef config = NULL;
			XThrowIfError(ExtAudioFileSetProperty(mExtAF, kExtAudioFileProperty_ConverterConfig, sizeof(CFPropertyListRef), &config), "couldn't signal the file that the converter has changed");
		}
		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__
