Beispiel #1
0
bool SFB::Audio::MP3Metadata::_WriteMetadata(CFErrorRef *error)
{
	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX))
		return false;

	std::unique_ptr<TagLib::FileStream> stream(new TagLib::FileStream((const char *)buf));
	if(!stream->isOpen()) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” could not be opened for writing."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Input/output error"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file may have been renamed, moved, deleted, or you may not have appropriate permissions."), ""));

			*error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
		}

		return false;
	}

	TagLib::MPEG::File file(stream.get(), TagLib::ID3v2::FrameFactory::instance(), false);
	if(!file.isValid()) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not an MPEG file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			*error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
		}
		
		return false;
	}

	// APE and ID3v1 tags are only written if present, but ID3v2 tags are always written

	auto APETag = file.APETag();
	if(APETag && !APETag->isEmpty())
		SetAPETagFromMetadata(*this, APETag);

	auto ID3v1Tag = file.ID3v1Tag();
	if(ID3v1Tag && !ID3v1Tag->isEmpty())
		SetID3v1TagFromMetadata(*this, ID3v1Tag);

	SetID3v2TagFromMetadata(*this, file.ID3v2Tag(true));

	if(!file.save()) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Unable to write metadata"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			*error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
		}
		
		return false;
	}

	return true;
}
Beispiel #2
0
bool SFB::Audio::MP3Metadata::_ReadMetadata(CFErrorRef *error)
{
	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX))
		return false;
	
	std::unique_ptr<TagLib::FileStream> stream(new TagLib::FileStream((const char *)buf, true));
	if(!stream->isOpen()) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” could not be opened for reading."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Input/output error"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file may have been renamed, moved, deleted, or you may not have appropriate permissions."), ""));

			*error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
		}

		return false;
	}

	TagLib::MPEG::File file(stream.get(), TagLib::ID3v2::FrameFactory::instance());
	if(!file.isValid()) {
		if(nullptr != error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not an MPEG file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			*error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
		}
		
		return false;
	}
	
	CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MP3"));

	if(file.audioProperties()) {
		auto properties = file.audioProperties();
		AddAudioPropertiesToDictionary(mMetadata, properties);

		// TODO: Is this too much information?
#if 0
		switch(properties->version()) {
			case TagLib::MPEG::Header::Version1:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-1 Layer I"));		break;
					case 2:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-1 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-1 Layer III"));	break;
				}
				break;
			case TagLib::MPEG::Header::Version2:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2 Layer I"));		break;
					case 2:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2 Layer III"));	break;
				}
				break;
			case TagLib::MPEG::Header::Version2_5:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2.5 Layer I"));	break;
					case 2:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2.5 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2.5 Layer III"));	break;
				}
				break;
		}
#endif

		if(properties->xingHeader() && properties->xingHeader()->totalFrames())
			AddIntToDictionary(mMetadata, kTotalFramesKey, (int)properties->xingHeader()->totalFrames());
	}

	if(file.APETag())
		AddAPETagToDictionary(mMetadata, mPictures, file.APETag());

	if(file.ID3v1Tag())
		AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag());

	if(file.ID3v2Tag())
		AddID3v2TagToDictionary(mMetadata, mPictures, file.ID3v2Tag());

	return true;
}
bool SFB::Audio::MusepackDecoder::_Open(CFErrorRef *error)
{
	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mInputSource->GetURL(), FALSE, buf, PATH_MAX))
		return false;

	mReader.read = read_callback;
	mReader.seek = seek_callback;
	mReader.tell = tell_callback;
	mReader.get_size = get_size_callback;
	mReader.canseek = canseek_callback;
	mReader.data = this;
	
	mDemux = mpc_demux_init(&mReader);
	if(nullptr == mDemux) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Musepack file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not a Musepack file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		mpc_reader_exit_stdio(&mReader);
		
		return false;
	}
	
	// Get input file information
	mpc_streaminfo streaminfo;
	mpc_demux_get_info(mDemux, &streaminfo);
	
	mTotalFrames				= mpc_streaminfo_get_length_samples(&streaminfo);
	
	// Canonical Core Audio format
	mFormat.mFormatID			= kAudioFormatLinearPCM;
	mFormat.mFormatFlags		= kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
	
	mFormat.mSampleRate			= streaminfo.sample_freq;
	mFormat.mChannelsPerFrame	= streaminfo.channels;
	mFormat.mBitsPerChannel		= 8 * sizeof(float);
	
	mFormat.mBytesPerPacket		= (mFormat.mBitsPerChannel / 8);
	mFormat.mFramesPerPacket	= 1;
	mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;
	
	mFormat.mReserved			= 0;
	
	// Set up the source format
	mSourceFormat.mFormatID				= 'MUSE';
	
	mSourceFormat.mSampleRate			= streaminfo.sample_freq;
	mSourceFormat.mChannelsPerFrame		= streaminfo.channels;
	
	mSourceFormat.mFramesPerPacket		= (1 << streaminfo.block_pwr);
	
	// Setup the channel layout
	switch(streaminfo.channels) {
		case 1:		mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Mono);			break;
		case 2:		mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo);		break;
		case 4:		mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Quadraphonic);	break;
	}
	
	// Allocate the buffer list
	if(!mBufferList.Allocate(mFormat, MPC_FRAME_LENGTH)) {
		if(error)
			*error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, nullptr);

		mpc_demux_exit(mDemux), mDemux = nullptr;
		mpc_reader_exit_stdio(&mReader);
		
		return false;
	}

	for(UInt32 i = 0; i < mBufferList->mNumberBuffers; ++i)
		mBufferList->mBuffers[i].mDataByteSize = 0;

	return true;
}
bool SFB::Audio::LibsndfileDecoder::_Open(CFErrorRef *error)
{
	// Set up the virtual IO function pointers
	SF_VIRTUAL_IO virtualIO;
	virtualIO.get_filelen	= my_sf_vio_get_filelen;
	virtualIO.seek			= my_sf_vio_seek;
	virtualIO.read			= my_sf_vio_read;
	virtualIO.write			= nullptr;
	virtualIO.tell			= my_sf_vio_tell;

	// Open the input file
	mFile = unique_SNDFILE_ptr(sf_open_virtual(&virtualIO, SFM_READ, &mFileInfo, this), sf_close);

	if(!mFile) {
		LOGGER_ERR("org.sbooth.AudioEngine.Decoder.Libsndfile", "sf_open_virtual failed: " << sf_error_number(sf_error(nullptr)));

		if(nullptr != error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The format of the file “%@” was not recognized."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("File Format Not Recognized"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		return false;
	}

	// Generate interleaved PCM output
	mFormat.mFormatID			= kAudioFormatLinearPCM;

	mFormat.mSampleRate			= mFileInfo.samplerate;
	mFormat.mChannelsPerFrame	= (UInt32)mFileInfo.channels;

	int subFormat = SF_FORMAT_SUBMASK & mFileInfo.format;

	// 8-bit PCM will be high-aligned in shorts
	if(SF_FORMAT_PCM_U8 == subFormat) {
		mFormat.mFormatFlags		= kAudioFormatFlagIsAlignedHigh;

		mFormat.mBitsPerChannel		= 8;

		mFormat.mBytesPerPacket		= sizeof(short) * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

		mReadMethod					= ReadMethod::Short;
	}
	else if(SF_FORMAT_PCM_S8 == subFormat) {
		mFormat.mFormatFlags		= kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsAlignedHigh;

		mFormat.mBitsPerChannel		= 8;

		mFormat.mBytesPerPacket		= sizeof(short) * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

		mReadMethod					= ReadMethod::Short;
	}
	// 16-bit PCM
	else if(SF_FORMAT_PCM_16 == subFormat) {
		mFormat.mFormatFlags		= kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;

		mFormat.mBitsPerChannel		= 16;

		mFormat.mBytesPerPacket		= (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

		mReadMethod					= ReadMethod::Short;
	}
	// 24-bit PCM will be high-aligned in ints
	else if(SF_FORMAT_PCM_24 == subFormat) {
		mFormat.mFormatFlags		= kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsAlignedHigh;

		mFormat.mBitsPerChannel		= 24;

		mFormat.mBytesPerPacket		= sizeof(int) * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

		mReadMethod					= ReadMethod::Int;
	}
	// 32-bit PCM
	else if(SF_FORMAT_PCM_32 == subFormat) {
		mFormat.mFormatFlags		= kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;

		mFormat.mBitsPerChannel		= 32;

		mFormat.mBytesPerPacket		= (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

		mReadMethod					= ReadMethod::Int;
	}
	// Floating point formats
	else if(SF_FORMAT_FLOAT == subFormat) {
		mFormat.mFormatFlags		= kAudioFormatFlagsNativeFloatPacked;

		mFormat.mBitsPerChannel		= 8 * sizeof(float);

		mFormat.mBytesPerPacket		= (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

		mReadMethod					= ReadMethod::Float;
	}
	else if(SF_FORMAT_DOUBLE == subFormat) {
		mFormat.mFormatFlags		= kAudioFormatFlagsNativeFloatPacked;

		mFormat.mBitsPerChannel		= 8 * sizeof(double);

		mFormat.mBytesPerPacket		= (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

		mReadMethod					= ReadMethod::Double;
	}
	// Everything else will be converted to 32-bit float
	else {
		mFormat.mFormatFlags		= kAudioFormatFlagsNativeFloatPacked;

		mFormat.mBitsPerChannel		= 8 * sizeof(float);

		mFormat.mBytesPerPacket		= (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

		mReadMethod					= ReadMethod::Float;
	}

	mFormat.mReserved			= 0;

	// Set up the source format
	mSourceFormat.mFormatID				= 'SNDF';

	mSourceFormat.mSampleRate			= mFileInfo.samplerate;
	mSourceFormat.mChannelsPerFrame		= (UInt32)mFileInfo.channels;

	switch(subFormat) {
		case SF_FORMAT_PCM_U8:
			mSourceFormat.mBitsPerChannel = 8;
			break;

		case SF_FORMAT_PCM_S8:
			mSourceFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger;
			mSourceFormat.mBitsPerChannel = 8;
			break;

		case SF_FORMAT_PCM_16:
			mSourceFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger;
			mSourceFormat.mBitsPerChannel = 16;
			break;

		case SF_FORMAT_PCM_24:
			mSourceFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger;
			mSourceFormat.mBitsPerChannel = 24;
			break;

		case SF_FORMAT_PCM_32:
			mSourceFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger;
			mSourceFormat.mBitsPerChannel = 32;
			break;

		case SF_FORMAT_FLOAT:
			mSourceFormat.mFormatFlags = kAudioFormatFlagIsFloat;
			mSourceFormat.mBitsPerChannel = 32;
			break;

		case SF_FORMAT_DOUBLE:
			mSourceFormat.mFormatFlags = kAudioFormatFlagIsFloat;
			mSourceFormat.mBitsPerChannel = 64;
			break;
	}

	return true;
}
bool SFB::Audio::OggSpeexDecoder::_Open(CFErrorRef *error)
{
	// Initialize Ogg data struct
	ogg_sync_init(&mOggSyncState);

	// Get the ogg buffer for writing
	char *data = ogg_sync_buffer(&mOggSyncState, READ_SIZE_BYTES);

	// Read bitstream from input file
	ssize_t bytesRead = (ssize_t)GetInputSource().Read(data, READ_SIZE_BYTES);
	if(-1 == bytesRead) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” could not be read."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Read error"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("Unable to read from the input file."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	// Tell the sync layer how many bytes were written to its internal buffer
	int result = ogg_sync_wrote(&mOggSyncState, bytesRead);
	if(-1 == result) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not an Ogg file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	// Turn the data we wrote into an ogg page
	result = ogg_sync_pageout(&mOggSyncState, &mOggPage);
	if(1 != result) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not an Ogg file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	// Initialize the stream and grab the serial number
	ogg_stream_init(&mOggStreamState, ogg_page_serialno(&mOggPage));

	// Get the first Ogg page
	result = ogg_stream_pagein(&mOggStreamState, &mOggPage);
	if(0 != result) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not an Ogg file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	// Get the first packet (should be the header) from the page
	ogg_packet op;
	result = ogg_stream_packetout(&mOggStreamState, &op);
	if(1 != result) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not an Ogg file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	if(op.bytes >= 5 && !memcmp(op.packet, "Speex", 5))
		mSpeexSerialNumber = mOggStreamState.serialno;

	++mOggPacketCount;

	// Convert the packet to the Speex header
	SpeexHeader *header = speex_packet_to_header((char *)op.packet, (int)op.bytes);
	if(nullptr == header) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg Speex file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not an Ogg Speex file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::FileFormatNotRecognizedError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		ogg_sync_destroy(&mOggSyncState);
		return false;
	}
	else if(SPEEX_NB_MODES <= header->mode) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The Speex mode in the file “%@” is not supported."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Unsupported Ogg Speex file mode"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("This file may have been encoded with a newer version of Speex."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::FileFormatNotSupportedError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		speex_header_free(header);
		header = nullptr;
		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	const SpeexMode *mode = speex_lib_get_mode(header->mode);
	if(mode->bitstream_version != header->mode_bitstream_version) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The Speex version in the file “%@” is not supported."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Unsupported Ogg Speex file version"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("This file was encoded with a different version of Speex."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::FileFormatNotSupportedError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		speex_header_free(header);
		header = nullptr;
		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	// Initialize the decoder
	mSpeexDecoder = speex_decoder_init(mode);
	if(nullptr== mSpeexDecoder) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("Unable to initialize the Speex decoder."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Error initializing Speex decoder"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("An unknown error occurred."), ""));

			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		speex_header_free(header);
		header = nullptr;
		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	speex_decoder_ctl(mSpeexDecoder, SPEEX_SET_SAMPLING_RATE, &header->rate);

	mSpeexFramesPerOggPacket = (0 == header->frames_per_packet ? 1 : header->frames_per_packet);
	mExtraSpeexHeaderCount = (UInt32)header->extra_headers;

	// Initialize the speex bit-packing data structure
	speex_bits_init(&mSpeexBits);

	// Initialize the stereo mode
	mSpeexStereoState = speex_stereo_state_init();

	if(2 == header->nb_channels) {
		SpeexCallback callback;
		callback.callback_id = SPEEX_INBAND_STEREO;
		callback.func = speex_std_stereo_request_handler;
		callback.data = mSpeexStereoState;
		speex_decoder_ctl(mSpeexDecoder, SPEEX_SET_HANDLER, &callback);
	}

	// Canonical Core Audio format
	mFormat.mFormatID			= kAudioFormatLinearPCM;
	mFormat.mFormatFlags		= kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;

	mFormat.mBitsPerChannel		= 8 * sizeof(float);
	mFormat.mSampleRate			= header->rate;
	mFormat.mChannelsPerFrame	= (UInt32)header->nb_channels;

	mFormat.mBytesPerPacket		= (mFormat.mBitsPerChannel / 8);
	mFormat.mFramesPerPacket	= 1;
	mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;

	mFormat.mReserved			= 0;

	// Set up the source format
	mSourceFormat.mFormatID				= 'SPEE';

	mSourceFormat.mSampleRate			= header->rate;
	mSourceFormat.mChannelsPerFrame		= (UInt32)header->nb_channels;

	switch(header->nb_channels) {
		case 1:		mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Mono);		break;
		case 2:		mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo);	break;
	}

	speex_header_free(header);
	header = nullptr;

	// Allocate the buffer list
	spx_int32_t speexFrameSize = 0;
	speex_decoder_ctl(mSpeexDecoder, SPEEX_GET_FRAME_SIZE, &speexFrameSize);

	if(!mBufferList.Allocate(mFormat, (UInt32)speexFrameSize)) {
		if(error)
			*error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, nullptr);

		speex_stereo_state_destroy(mSpeexStereoState);
		mSpeexStereoState = nullptr;
		speex_decoder_destroy(mSpeexDecoder);
		mSpeexDecoder = nullptr;
		speex_bits_destroy(&mSpeexBits);

		ogg_sync_destroy(&mOggSyncState);
		return false;
	}

	for(UInt32 i = 0; i < mBufferList->mNumberBuffers; ++i)
		mBufferList->mBuffers[i].mDataByteSize = 0;

	return true;
}
bool SFB::Audio::CoreAudioDecoder::_Open(CFErrorRef *error)
{
	// Open the input file
	OSStatus result = AudioFileOpenWithCallbacks(this, myAudioFile_ReadProc, nullptr, myAudioFile_GetSizeProc, nullptr, 0, &mAudioFile);

	if(noErr != result) {
		LOGGER_CRIT("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileOpenWithCallbacks failed: " << result);
		
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The format of the file “%@” was not recognized."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("File Format Not Recognized"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}
		
		return false;
	}
	
	result = ExtAudioFileWrapAudioFileID(mAudioFile, false, &mExtAudioFile);

	if(noErr != result) {
		LOGGER_CRIT("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileWrapAudioFileID failed: " << result);
		
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The format of the file “%@” was not recognized."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("File Format Not Recognized"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			*error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::InputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion);
		}

		result = AudioFileClose(mAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileClose failed: " << result);
		
		mAudioFile = nullptr;
		
		return false;
	}
	
	// Query file format
	UInt32 dataSize = sizeof(mSourceFormat);
	result = ExtAudioFileGetProperty(mExtAudioFile, kExtAudioFileProperty_FileDataFormat, &dataSize, &mSourceFormat);

	if(noErr != result) {
		LOGGER_CRIT("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileGetProperty (kExtAudioFileProperty_FileDataFormat) failed: " << result);
		
		result = ExtAudioFileDispose(mExtAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileDispose failed: " << result);
		
		result = AudioFileClose(mAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileClose failed: " << result);
		
		mAudioFile = nullptr;		
		mExtAudioFile = nullptr;
		
		return false;
	}
	
	// Tell the ExtAudioFile the format in which we'd like our data
	
	// For Linear PCM formats, leave the data untouched
	if(kAudioFormatLinearPCM == mSourceFormat.mFormatID)
		mFormat = mSourceFormat;
	// For Apple Lossless, convert to high-aligned signed ints in 32 bits
	else if(kAudioFormatAppleLossless == mSourceFormat.mFormatID) {
		mFormat.mFormatID			= kAudioFormatLinearPCM;
		mFormat.mFormatFlags		= kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsAlignedHigh;
		
		mFormat.mSampleRate			= mSourceFormat.mSampleRate;
		mFormat.mChannelsPerFrame	= mSourceFormat.mChannelsPerFrame;
		
		if(kAppleLosslessFormatFlag_16BitSourceData == mSourceFormat.mFormatFlags)
			mFormat.mBitsPerChannel	= 16;
		else if(kAppleLosslessFormatFlag_20BitSourceData == mSourceFormat.mFormatFlags)
			mFormat.mBitsPerChannel	= 20;
		else if(kAppleLosslessFormatFlag_24BitSourceData == mSourceFormat.mFormatFlags)
			mFormat.mBitsPerChannel	= 24;
		else if(kAppleLosslessFormatFlag_32BitSourceData == mSourceFormat.mFormatFlags)
			mFormat.mBitsPerChannel	= 32;
		
		mFormat.mBytesPerPacket		= 4 * mFormat.mChannelsPerFrame;
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;
		
		mFormat.mReserved			= 0;
		
	}
	// For all other formats convert to the canonical Core Audio format
	else {
		mFormat.mFormatID			= kAudioFormatLinearPCM;
		mFormat.mFormatFlags		= kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
		
		mFormat.mSampleRate			= mSourceFormat.mSampleRate;
		mFormat.mChannelsPerFrame	= mSourceFormat.mChannelsPerFrame;
		mFormat.mBitsPerChannel		= 32;
		
		mFormat.mBytesPerPacket		= (mFormat.mBitsPerChannel / 8);
		mFormat.mFramesPerPacket	= 1;
		mFormat.mBytesPerFrame		= mFormat.mBytesPerPacket * mFormat.mFramesPerPacket;
		
		mFormat.mReserved			= 0;
	}
	
	result = ExtAudioFileSetProperty(mExtAudioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(mFormat), &mFormat);

	if(noErr != result) {
		LOGGER_CRIT("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileSetProperty (kExtAudioFileProperty_ClientDataFormat) failed: " << result);
		
		result = ExtAudioFileDispose(mExtAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileDispose failed: " << result);
		
		result = AudioFileClose(mAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileClose failed: " << result);
		
		mAudioFile = nullptr;		
		mExtAudioFile = nullptr;
		
		return false;
	}
	
	// Setup the channel layout
	// There is a bug in EAF where if the underlying AF doesn't return a channel layout it returns an empty struct
//	result = ExtAudioFileGetPropertyInfo(mExtAudioFile, kExtAudioFileProperty_FileChannelLayout, &dataSize, nullptr);
	result = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, &dataSize, nullptr);
	if(noErr == result) {
		auto channelLayout = (AudioChannelLayout *)malloc(dataSize);
//		result = ExtAudioFileGetProperty(mExtAudioFile, kExtAudioFileProperty_FileChannelLayout, &dataSize, mChannelLayout);
		result = AudioFileGetProperty(mAudioFile, kAudioFilePropertyChannelLayout, &dataSize, channelLayout);

		if(noErr != result) {
//			LOGGER_ERR("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileGetProperty (kExtAudioFileProperty_FileChannelLayout) failed: " << result);
			LOGGER_ERR("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileGetProperty (kAudioFilePropertyChannelLayout) failed: " << result);
			
            free(channelLayout);

			result = ExtAudioFileDispose(mExtAudioFile);
			if(noErr != result)
				LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileDispose failed: " << result);
			
			result = AudioFileClose(mAudioFile);
			if(noErr != result)
				LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileClose failed: " << result);
			
			mAudioFile = nullptr;		
			mExtAudioFile = nullptr;
			
			return false;
		}

		mChannelLayout = channelLayout;

		free(channelLayout);
	}
	else
//		LOGGER_ERR("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileGetPropertyInfo (kExtAudioFileProperty_FileChannelLayout) failed: " << result);
		LOGGER_ERR("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileGetPropertyInfo (kAudioFilePropertyChannelLayout) failed: " << result);

	// Work around bugs in ExtAudioFile: http://lists.apple.com/archives/coreaudio-api/2009/Nov/msg00119.html
	// Synopsis: ExtAudioFileTell() and ExtAudioFileSeek() are broken for m4a files
	AudioFileID audioFile;
	dataSize = sizeof(audioFile);
	result = ExtAudioFileGetProperty(mExtAudioFile, kExtAudioFileProperty_AudioFile, &dataSize, &audioFile);
	
	if(noErr != result) {
		LOGGER_ERR("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileGetProperty (kExtAudioFileProperty_AudioFile) failed: " << result);
		
		result = ExtAudioFileDispose(mExtAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileDispose failed: " << result);
		
		result = AudioFileClose(mAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileClose failed: " << result);
		
		mAudioFile = nullptr;		
		mExtAudioFile = nullptr;
		
		return false;
	}
	
	AudioFileTypeID fileFormat;
	dataSize = sizeof(fileFormat);
	result = AudioFileGetProperty(audioFile, kAudioFilePropertyFileFormat, &dataSize, &fileFormat);

	if(noErr != result) {
		LOGGER_ERR("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileGetProperty (kAudioFilePropertyFileFormat) failed: " << result);
		
		result = ExtAudioFileDispose(mExtAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileDispose failed: " << result);
		
		result = AudioFileClose(mAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileClose failed: " << result);
		
		mAudioFile = nullptr;		
		mExtAudioFile = nullptr;
		
		return false;
	}
	
	if(kAudioFileM4AType == fileFormat || kAudioFileMPEG4Type == fileFormat || kAudioFileAAC_ADTSType == fileFormat)
		mUseM4AWorkarounds = true;
	
#if 0
	// This was supposed to determine if ExtAudioFile had been fixed, but even though
	// it passes on 10.6.2 things are not behaving properly
	SInt64 currentFrame = -1;	
	result = ExtAudioFileTell(mExtAudioFile, &currentFrame);

	if(noErr != result) {
		LOGGER_ERR("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileTell failed: " << result);
		
		result = ExtAudioFileDispose(mExtAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "ExtAudioFileDispose failed: " << result);
		
		result = AudioFileClose(mAudioFile);
		if(noErr != result)
			LOGGER_NOTICE("org.sbooth.AudioEngine.Decoder.CoreAudio", "AudioFileClose failed: " << result);
		
		mAudioFile = nullptr;		
		mExtAudioFile = nullptr;
		
		return false;
	}
	
	if(0 > currentFrame)
		mUseM4AWorkarounds = true;
#endif

	return true;
}