/*=============================================================================
	CAAudioFile.cpp
	
	$Log: CAAudioFile.cpp,v $
	Revision 1.59  2005/02/04 01:10:06  dwyatt
	[3988195] interpret dest SR of 0 as "copy the source SR" now only happens on decode/PCM-PCM. On encode, ExtAudioFileCreateNew now handles a SR of 0 by setting a placeholder SR on the file until the converter is created, at which time the real SR is copied from the converter to the file.
	
	Revision 1.58  2005/01/14 20:54:43  bills
	tweak errors
	
	Revision 1.57  2004/12/13 22:46:42  dwyatt
	[3913795] support changing PCM file's length with SetNumberFrames()
	
	Revision 1.56  2004/12/04 01:06:53  dwyatt
	don't set prime frames on converter on decode unless they're nonzero
	
	Revision 1.55  2004/12/04 01:04:17  dwyatt
	don't set prime frames on converter on decode unless they're nonzero
	
	Revision 1.54  2004/12/04 01:03:41  dwyatt
	don't set prime frames on converter on decode unless they're nonzero
	
	Revision 1.53  2004/12/03 23:36:06  luke
	fix miscopy
	
	Revision 1.52  2004/12/02 21:04:00  dwyatt
	[3865346] on decode: file packet table info -> converter prime info
	
	Revision 1.51  2004/12/01 23:54:02  dwyatt
	[3865346] set kAudioFilePropertyPacketTableInfo from kAudioConverterPrimeInfo on encode
	
	Revision 1.50  2004/11/23 14:33:04  dwyatt
	fix eofErr handling
	
	Revision 1.49  2004/11/10 22:32:38  dwyatt
	no vararg macros on windows
	
	Revision 1.48  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.47  2004/10/15 17:10:34  dwyatt
	CAAudioFile rewrite to use ExtAudioFile (changeable via compile flag in CAAudioFile.h)
	
	Revision 1.46  2004/09/30 21:05:02  jcm10
	make it build on Windows
	
	Revision 1.45  2004/08/24 00:28:45  bills
	fix warnings
	
	Revision 1.44  2004/08/03 19:23:32  dwyatt
	add warnings
	
	Revision 1.43  2004/08/03 19:16:46  asynth
	make defer optional
	
	Revision 1.42  2004/06/02 19:26:07  dwyatt
	add inForWriting argument to Wrap
	
	Revision 1.41  2004/05/28 18:13:44  dwyatt
	maintain frame mark when writing
	
	Revision 1.40  2004/05/28 01:08:22  bills
	have to deal with file being written
	
	Revision 1.39  2004/05/27 19:50:16  dwyatt
	[3657283] more cleanup/simplification of correlation between file and playback sample timelines;
	reinstate ability to wrap a newly-created file (more elegantly now);
	don't read more in the ReadCallback than is going to be pulled out the other end
	
	Revision 1.38  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.37  2004/05/26 01:01:41  dwyatt
	remove Wrap() for writable files; add support for client-owned I/O buffer; clean up debug code; fix maintenance of mFrameMark
	
	Revision 1.36  2004/05/21 22:24:50  dwyatt
	[3657283] sub-packet seeks
	
	Revision 1.35  2004/05/18 00:53:18  dwyatt
	clean up error codes
	
	Revision 1.34  2004/05/17 23:10:50  dwyatt
	clean up PrepareNew hack with a second sample rate
	
	Revision 1.33  2004/05/17 19:13:13  dwyatt
	back-port a couple Tiger items for pre-Tiger compiles
	
	Revision 1.32  2004/05/15 01:05:26  dwyatt
	use framework include to get to ExtendedAudioFile.h
	
	Revision 1.31  2004/05/15 00:06:33  dwyatt
	fix dataFormatChanging
	
	Revision 1.30  2004/05/14 23:09:56  dwyatt
	first cut at ExtendedAudioFile
	
	Revision 1.29  2004/05/06 21:20:33  dwyatt
	REALLY re-addin
	
	Revision 1.26  2004/03/12 03:18:20  dwyatt
	fix: mFileDataFormat.mSampleRate was 0
	
	Revision 1.25  2004/03/05 20:41:48  dwyatt
	add compile warning
	
	Revision 1.24  2004/02/25 01:32:18  luke
	add comma
	
	Revision 1.23  2004/01/31 01:50:21  dwyatt
	conditionally compile profiling, add seek-to-packet accessors (not yet impl)
	
	Revision 1.22  2004/01/14 00:07:12  dwyatt
	make inclusion of CAChannelLayouts.h contingent on VERBOSE_CHANNELMAP
	
	Revision 1.21  2004/01/13 01:44:44  dwyatt
	moved from Source/Tests/AudioFileUtility/Utility/
	
	Revision 1.20  2003/12/12 22:17:24  dwyatt
	add more logging code
	
	Revision 1.19  2003/12/04 01:07:57  dwyatt
	fix
	
	Revision 1.18  2003/12/04 00:59:02  dwyatt
	refactoring
	
	Revision 1.17  2003/10/09 23:21:00  dwyatt
	allow wrapping audio files that are open for writing
	
	Revision 1.16  2003/09/04 19:45:56  dwyatt
	don't fail if we can't set a file channel layout (WAVE can handle some but not all)
	
	Revision 1.15  2003/08/28 18:48:51  dwyatt
	conditionalize channel map debug printfs
	
	Revision 1.14  2003/08/28 02:11:30  asynth
	pass NULL for layout property size when getting info
	
	Revision 1.13  2003/08/27 23:41:03  dwyatt
	less spew, only set file channel layouts when they can accept them
	
	Revision 1.12  2003/08/24 23:31:30  dwyatt
	tolerate unsupported operation errors from setting the converter's channel layout
	
	Revision 1.11  2003/08/24 21:21:01  dwyatt
	always set the channel layouts on the converter
	
	Revision 1.10  2003/08/24 08:14:47  dwyatt
	tweaks to channel layout changes
	
	Revision 1.9  2003/08/23 04:55:53  dwyatt
	first whack at cleaning up channel layout handling
	
	Revision 1.8  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.7  2003/08/08 20:01:38  bills
	magic cookies can return a propSize of zero
	
	Revision 1.6  2003/08/04 23:42:00  dwyatt
	more rigor around mFileMaxPacketSize
	
	Revision 1.5  2003/07/25 23:16:47  dwyatt
	use constant strings for operations in CAXException
	
	Revision 1.4  2003/07/15 17:35:16  dwyatt
	fix crash when no layout tag; add profiling
	
	Revision 1.3  2003/07/09 19:11:36  dwyatt
	support channel layouts
	
	Revision 1.2  2003/07/02 21:43:33  dwyatt
	more verbose debug code
	
	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: $
=============================================================================*/

#include "CAAudioFile.h"

#if !CAAF_USE_EXTAUDIOFILE

#include "CAXException.h"
#include <algorithm>
#include "CAHostTimeBase.h"
#include "CADebugMacros.h"

#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
	#include <AudioToolbox/AudioToolbox.h>
#else
	#include <AudioFormat.h>
#endif

#if DEBUG
	//#define VERBOSE_IO 1
	//#define VERBOSE_CONVERTER 1
	//#define VERBOSE_CHANNELMAP 1
	//#define LOG_FUNCTION_ENTRIES 1
#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);
		}
		
		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

#if VERBOSE_CHANNELMAP
	//#include "CAChannelLayouts.h"
#endif

static const UInt32 kDefaultIOBufferSizeBytes = 0x10000;

#if CAAUDIOFILE_PROFILE
	#define StartTiming(af, starttime) UInt64 starttime = af->mProfiling ? CAHostTimeBase::GetTheCurrentTime() : NULL
	#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),
	mTicksInIO(0)
#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<AudioChannelLayout *>(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: %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
		newFileDataFormat.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 DEBUG
	if (err)
		printf("WARNING: couldn't defer size updates to audio file\n");
#endif

	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];
			mMagicCookieSize = propertySize;
			XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, mMagicCookie), "get audio converter's magic cookie");
			// 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)
				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\n");
#endif
			// forget that we have a channel layout, we can't store it in the file!
			mFileChannelLayout = CAAudioChannelLayout();
		}
	}
	
	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();
}

// _______________________________________________________________________________________
//
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<AudioChannelLayout *>(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\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
#endif
	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
			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;
				XThrowIfError(AudioConverterSetProperty(mConverter, kAudioConverterPrimeInfo, sizeof(primeInfo), &primeInfo), "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)
{
	//LOG_FUNCTION("CAAudioFile::SetConverterProperty", "%p %-4.4s", this, (char *)&inPropertyID);
	OSStatus 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 (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");
		
		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 = ~0;
	
	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 {
				StartTiming(this, fill);
				XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &skipFrames, ioData, NULL), "convert audio packets (pcm read)");
				ElapsedTime(this, fill, mTicksInConverter);
			}
			if (skipFrames == 0) {	// hit EOF
				ioNumPackets = 0;
				return;
			}
#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 {
		StartTiming(this, fill);
		XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &nPackets, ioData, NULL), "convert audio packets (read)");
		ElapsedTime(this, fill, mTicksInConverter);
	}
	
	ioNumPackets = nPackets;
}

// _______________________________________________________________________________________
//
OSStatus CAAudioFile::ReadInputProc(	AudioConverterRef				inAudioConverter,
										UInt32*							ioNumberDataPackets,
										AudioBufferList*				ioData,
										AudioStreamPacketDescription**	outDataPacketDescription,
										void*							inUserData)
{
	CAAudioFile *This = static_cast<CAAudioFile *>(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);
	err = AudioFileReadPackets(This->mAudioFile, This->mUseCache, &bytesRead, This->mPacketDescs, This->mPacketMark, &readPackets, readBuffer->mBuffers[0].mData);
	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->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
		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<CAAudioFile *>(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;
		StartTiming(this, fill);
		OSStatus err = AudioConverterFillComplexBuffer(mConverter, inInputDataProc, inInputDataProcUserData, 
					&numEncodedPackets, &mIOBufferList, mPacketDescs);
		ElapsedTime(this, fill, mTicksInConverter);
		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
