bool OggSpeexMetadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; // TODO: Use unique_ptr once the switch to C++11 STL is made std::auto_ptr<TagLib::FileStream> stream(new TagLib::FileStream(reinterpret_cast<const char *>(buf), true)); if(!stream->isOpen()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” could not be opened for reading."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Input/output error"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file may have been renamed, moved, deleted, or you may not have appropriate permissions."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } TagLib::Ogg::Speex::File file(stream.get()); if(!file.isValid()) { if(nullptr != error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg Speex file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an Ogg Speex file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("Ogg Speex")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) { std::vector<AttachedPicture *> pictures; AddXiphCommentToDictionary(mMetadata, pictures, file.tag()); for(auto picture : pictures) AddSavedPicture(picture); } return true; }
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; }
bool MP3Metadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf)); TagLib::MPEG::File file(stream, TagLib::ID3v2::FrameFactory::instance(), false); if(!file.isValid()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an MPEG file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unable to write metadata"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } MergeChangedMetadataIntoMetadata(); return true; }
bool SFB::Audio::WAVEMetadata::_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::RIFF::WAV::File file(stream.get(), false); if(!file.isValid()) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid WAVE file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not a WAVE 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; } // An Info tag is only written if present, but ID3v2 tags are always written // TODO: Should other field names from the Info tag be handled? if(file.InfoTag()) SetTagFromMetadata(*this, file.InfoTag()); SetID3v2TagFromMetadata(*this, file.ID3v2Tag()); if(!file.save()) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid WAVE 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; }
bool SFB::Audio::TrueAudioMetadata::_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::TrueAudio::File file(stream.get()); if(!file.isValid()) { if(nullptr != error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid True Audio file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not a True Audio 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("True Audio")); if(file.audioProperties()) { auto properties = file.audioProperties(); AddAudioPropertiesToDictionary(mMetadata, properties); if(properties->bitsPerSample()) AddIntToDictionary(mMetadata, kBitsPerChannelKey, properties->bitsPerSample()); if(properties->sampleFrames()) AddIntToDictionary(mMetadata, kTotalFramesKey, (int)properties->sampleFrames()); } // Add all tags that are present if(file.ID3v1Tag()) AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag()); if(file.ID3v2Tag()) AddID3v2TagToDictionary(mMetadata, mPictures, file.ID3v2Tag()); return true; }
bool MonkeysAudioMetadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf)); TagLib::APE::File file(stream, false); if(!file.isValid()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Monkey's Audio file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a Monkey's Audio file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } // Although both ID3v1 and APE tags are read, only APE tags are written if(file.APETag()) SetAPETagFromMetadata(*this, file.APETag()); if(!file.save()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Monkey's Audio file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unable to write metadata"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } MergeChangedMetadataIntoMetadata(); return true; }
bool SFB::Audio::OggFLACMetadata::_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::Ogg::FLAC::File file(stream.get(), false); if(!file.isValid()) { 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(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion); } return false; } SetXiphCommentFromMetadata(*this, file.tag()); if(!file.save()) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg 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; }
bool OggVorbisMetadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf)); TagLib::Ogg::Vorbis::File file(stream, false); if(!file.isValid()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg Vorbis file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an Ogg Vorbis file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } SetXiphCommentFromMetadata(*this, file.tag()); if(!file.save()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg Vorbis file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unable to write metadata"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } MergeChangedMetadataIntoMetadata(); return true; }
bool SFB::Audio::OggVorbisMetadata::_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::Ogg::Vorbis::File file(stream.get()); if(!file.isValid()) { if(nullptr != error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg Vorbis file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not an Ogg Vorbis 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("Ogg Vorbis")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddXiphCommentToDictionary(mMetadata, mPictures, file.tag()); return true; }
SFB::Audio::Metadata::unique_ptr SFB::Audio::Metadata::CreateMetadataForURL(CFURLRef url, CFErrorRef *error) { if(nullptr == url) return nullptr; // If this is a file URL, use the extension-based resolvers SFB::CFString scheme = CFURLCopyScheme(url); // If there is no scheme the URL is invalid if(!scheme) { if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, EINVAL, nullptr); return nullptr; } if(kCFCompareEqualTo == CFStringCompare(CFSTR("file"), scheme, kCFCompareCaseInsensitive)) { // Verify the file exists SInt32 errorCode = noErr; SFB::CFBoolean fileExists = (CFBooleanRef)CFURLCreatePropertyFromResource(kCFAllocatorDefault, url, kCFURLFileExists, &errorCode); if(fileExists) { if(CFBooleanGetValue(fileExists)) { SFB::CFString pathExtension = CFURLCopyPathExtension(url); if(pathExtension) { // Some extensions (.oga for example) support multiple audio codecs (Vorbis, FLAC, Speex) for(auto subclassInfo : sRegisteredSubclasses) { if(subclassInfo.mHandlesFilesWithExtension(pathExtension)) { unique_ptr metadata(subclassInfo.mCreateMetadata(url)); if(metadata->ReadMetadata(error)) return metadata; } } } } else { LOGGER_WARNING("org.sbooth.AudioEngine.Metadata", "The requested URL doesn't exist"); if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” does not exist."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("File not found"), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file may exist on removable media or may have been deleted."), ""); *error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, url, failureReason, recoverySuggestion); } } } else LOGGER_WARNING("org.sbooth.AudioEngine.Metadata", "CFURLCreatePropertyFromResource failed: " << errorCode); } return nullptr; }
bool WavPackMetadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, FALSE, buf, PATH_MAX)) return false; auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true); TagLib::WavPack::File file(stream); if(!file.isValid()) { if(nullptr != error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid WavPack file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a WavPack file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("WavPack")); if(file.audioProperties()) { auto properties = file.audioProperties(); AddAudioPropertiesToDictionary(mMetadata, properties); if(properties->bitsPerSample()) AddIntToDictionary(mMetadata, kPropertiesBitsPerChannelKey, properties->bitsPerSample()); if(properties->sampleFrames()) AddIntToDictionary(mMetadata, kPropertiesTotalFramesKey, properties->sampleFrames()); } if(file.ID3v1Tag()) AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag()); if(file.APETag()) { std::vector<AttachedPicture *> pictures; AddAPETagToDictionary(mMetadata, pictures, file.APETag()); for(auto picture : pictures) AddSavedPicture(picture); } return true; }
bool SFB::Audio::MonkeysAudioDecoder::_Open(CFErrorRef *error) { auto ioInterface = std::unique_ptr<APEIOInterface>(new APEIOInterface(GetInputSource())); auto decompressor = std::unique_ptr<APE::IAPEDecompress>(CreateIAPEDecompressEx(ioInterface.get(), nullptr)); if(!decompressor) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Monkey's Audio file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not a Monkey's Audio 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); } return false; } mDecompressor = std::move(decompressor); mIOInterface = std::move(ioInterface); // The file format mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; mFormat.mBitsPerChannel = (UInt32)mDecompressor->GetInfo(APE::APE_INFO_BITS_PER_SAMPLE); mFormat.mSampleRate = mDecompressor->GetInfo(APE::APE_INFO_SAMPLE_RATE); mFormat.mChannelsPerFrame = (UInt32)mDecompressor->GetInfo(APE::APE_INFO_CHANNELS); mFormat.mBytesPerPacket = (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame; mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; // Set up the source format mSourceFormat.mFormatID = 'APE '; mSourceFormat.mSampleRate = mFormat.mSampleRate; mSourceFormat.mChannelsPerFrame = mFormat.mChannelsPerFrame; mSourceFormat.mBitsPerChannel = mFormat.mBitsPerChannel; switch(mFormat.mChannelsPerFrame) { case 1: mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Mono); break; case 2: mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo); break; case 4: mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Quadraphonic); break; } return true; }
bool SFB::Audio::MODDecoder::_Open(CFErrorRef *error) { dfs.open = nullptr; dfs.skip = skip_callback; dfs.getc = getc_callback; dfs.getnc = getnc_callback; dfs.close = close_callback; df = unique_DUMBFILE_ptr(dumbfile_open_ex(this, &dfs), dumbfile_close); if(!df) { return false; } SFB::CFString pathExtension = CFURLCopyPathExtension(GetURL()); if(nullptr == pathExtension) return false; // Attempt to create the appropriate decoder based on the file's extension if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("it"), kCFCompareCaseInsensitive)) duh = unique_DUH_ptr(dumb_read_it(df.get()), unload_duh); else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("xm"), kCFCompareCaseInsensitive)) duh = unique_DUH_ptr(dumb_read_xm(df.get()), unload_duh); else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("s3m"), kCFCompareCaseInsensitive)) duh = unique_DUH_ptr(dumb_read_s3m(df.get()), unload_duh); else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("mod"), kCFCompareCaseInsensitive)) duh = unique_DUH_ptr(dumb_read_mod(df.get()), unload_duh); if(!duh) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MOD file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not a MOD 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); } return false; } // NB: This must change if the sample rate changes because it is based on 65536 Hz mTotalFrames = duh_get_length(duh.get()); dsr = unique_DUH_SIGRENDERER_ptr(duh_start_sigrenderer(duh.get(), 0, 2, 0), duh_end_sigrenderer); if(!dsr) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MOD file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not a MOD 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); } return false; } // Generate interleaved 2 channel 44.1 16-bit output mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; mFormat.mSampleRate = DUMB_SAMPLE_RATE; mFormat.mChannelsPerFrame = DUMB_CHANNELS; mFormat.mBitsPerChannel = DUMB_BIT_DEPTH; mFormat.mBytesPerPacket = (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame; mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; // Set up the source format mSourceFormat.mFormatID = 'MOD '; mSourceFormat.mSampleRate = DUMB_SAMPLE_RATE; mSourceFormat.mChannelsPerFrame = DUMB_CHANNELS; // Setup the channel layout mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo); return true; }
bool SFB::Audio::TrueAudioDecoder::_Open(CFErrorRef *error) { mCallbacks = unique_callback_wrapper_ptr(new TTA_io_callback_wrapper); mCallbacks->iocb.read = read_callback; mCallbacks->iocb.write = nullptr; mCallbacks->iocb.seek = seek_callback; mCallbacks->decoder = this; TTA_info streamInfo; try { mDecoder = unique_tta_ptr(new tta::tta_decoder((TTA_io_callback *)mCallbacks.get())); mDecoder->init_get_info(&streamInfo, 0); } catch(tta::tta_exception e) { LOGGER_CRIT("org.sbooth.AudioEngine.Decoder.TrueAudio", "Error creating True Audio decoder: " << e.code()); } if(!mDecoder) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid True Audio file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not a True Audio 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); } return false; } mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger; mFormat.mSampleRate = streamInfo.sps; mFormat.mChannelsPerFrame = streamInfo.nch; mFormat.mBitsPerChannel = streamInfo.bps; mFormat.mBytesPerPacket = ((streamInfo.bps + 7) / 8) * mFormat.mChannelsPerFrame; mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; // Support 4 to 32 bits per sample (True Audio may support more or less, but the documentation didn't say) switch(mFormat.mBitsPerChannel) { case 8: case 16: case 24: case 32: mFormat.mFormatFlags |= kAudioFormatFlagIsPacked; break; case 4 ... 7: case 9 ... 15: case 17 ... 23: case 25 ... 31: // Align high because Apple's AudioConverter doesn't handle low alignment mFormat.mFormatFlags |= kAudioFormatFlagIsAlignedHigh; break; default: { LOGGER_CRIT("org.sbooth.AudioEngine.Decoder.TrueAudio", "Unsupported bit depth: " << mFormat.mBitsPerChannel) if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a supported True Audio file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Bit depth not supported"), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's bit depth is not supported."), ""); *error = CreateErrorForURL(Decoder::ErrorDomain, Decoder::FileFormatNotSupportedError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); } return false; } } // Set up the source format mSourceFormat.mFormatID = 'TTA '; mSourceFormat.mSampleRate = streamInfo.sps; mSourceFormat.mChannelsPerFrame = streamInfo.nch; mSourceFormat.mBitsPerChannel = streamInfo.bps; // Setup the channel layout switch(streamInfo.nch) { case 1: mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Mono); break; case 2: mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo); break; case 4: mChannelLayout = ChannelLayout::ChannelLayoutWithTag(kAudioChannelLayoutTag_Quadraphonic); break; } mTotalFrames = streamInfo.samples; return true; }
bool FLACMetadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true); TagLib::FLAC::File file(stream, TagLib::ID3v2::FrameFactory::instance()); if(!file.isValid()) { if(nullptr != error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a FLAC file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("FLAC")); if(file.audioProperties()) { auto properties = file.audioProperties(); AddAudioPropertiesToDictionary(mMetadata, properties); if(properties->sampleWidth()) AddIntToDictionary(mMetadata, kPropertiesBitsPerChannelKey, properties->sampleWidth()); if(properties->sampleFrames()) AddLongLongToDictionary(mMetadata, kPropertiesTotalFramesKey, properties->sampleFrames()); } // Add all tags that are present if(file.ID3v1Tag()) AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag()); if(file.ID3v2Tag()) { std::vector<AttachedPicture *> pictures; AddID3v2TagToDictionary(mMetadata, pictures, file.ID3v2Tag()); for(auto picture : pictures) AddSavedPicture(picture); } if(file.xiphComment()) { std::vector<AttachedPicture *> pictures; AddXiphCommentToDictionary(mMetadata, pictures, file.xiphComment()); for(auto picture : pictures) AddSavedPicture(picture); } // Add album art for(auto iter : file.pictureList()) { CFDataRef data = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(iter->data().data()), iter->data().size()); CFStringRef description = nullptr; if(!iter->description().isNull()) description = CFStringCreateWithCString(kCFAllocatorDefault, iter->description().toCString(true), kCFStringEncodingUTF8); AttachedPicture *picture = new AttachedPicture(data, static_cast<AttachedPicture::Type>(iter->type()), description); AddSavedPicture(picture); if(data) CFRelease(data), data = nullptr; if(description) CFRelease(description), description = nullptr; } return true; }
bool FLACMetadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf)); TagLib::FLAC::File file(stream, TagLib::ID3v2::FrameFactory::instance(), false); if(!file.isValid()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a FLAC file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } SetXiphCommentFromMetadata(*this, file.xiphComment(), false); // Remove existing cover art file.removePictures(); // Add album art for(auto attachedPicture : GetAttachedPictures()) { CGImageSourceRef imageSource = CGImageSourceCreateWithData(attachedPicture->GetData(), nullptr); if(nullptr == imageSource) { LOGGER_ERR("org.sbooth.AudioEngine.AudioMetadata.FLAC", "Skipping album art (unable to create image)"); continue; } TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture; picture->setData(TagLib::ByteVector((const char *)CFDataGetBytePtr(attachedPicture->GetData()), (TagLib::uint)CFDataGetLength(attachedPicture->GetData()))); picture->setType(static_cast<TagLib::FLAC::Picture::Type>(attachedPicture->GetType())); if(attachedPicture->GetDescription()) picture->setDescription(TagLib::StringFromCFString(attachedPicture->GetDescription())); // Convert the image's UTI into a MIME type CFStringRef mimeType = UTTypeCopyPreferredTagWithClass(CGImageSourceGetType(imageSource), kUTTagClassMIMEType); if(mimeType) { picture->setMimeType(TagLib::StringFromCFString(mimeType)); CFRelease(mimeType), mimeType = nullptr; } // Flesh out the height, width, and depth CFDictionaryRef imagePropertiesDictionary = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nullptr); if(imagePropertiesDictionary) { CFNumberRef imageWidth = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyPixelWidth); CFNumberRef imageHeight = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyPixelHeight); CFNumberRef imageDepth = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyDepth); int height, width, depth; // Ignore numeric conversion errors CFNumberGetValue(imageWidth, kCFNumberIntType, &width); CFNumberGetValue(imageHeight, kCFNumberIntType, &height); CFNumberGetValue(imageDepth, kCFNumberIntType, &depth); picture->setHeight(height); picture->setWidth(width); picture->setColorDepth(depth); CFRelease(imagePropertiesDictionary), imagePropertiesDictionary = nullptr; } file.addPicture(picture); CFRelease(imageSource), imageSource = nullptr; } if(!file.save()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unable to write metadata"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } MergeChangedMetadataIntoMetadata(); return true; }
SFB::Audio::Decoder::unique_ptr SFB::Audio::Decoder::CreateDecoderForInputSource(InputSource::unique_ptr inputSource, CFStringRef mimeType, CFErrorRef *error) { if(!inputSource) return nullptr; // Open the input source if it isn't already if(AutomaticallyOpenDecoders() && !inputSource->IsOpen() && !inputSource->Open(error)) return nullptr; #if 0 // If the input is an instance of HTTPInputSource, use the MIME type from the server // This code is disabled because most HTTP servers don't send the correct MIME types HTTPInputSource *httpInputSource = dynamic_cast<HTTPInputSource *>(inputSource); bool releaseMIMEType = false; if(!mimeType && httpInputSource && httpInputSource->IsOpen()) { mimeType = httpInputSource->CopyContentMIMEType(); if(mimeType) releaseMIMEType = true; } #endif // The MIME type takes precedence over the file extension if(mimeType) { for(auto subclassInfo : sRegisteredSubclasses) { if(subclassInfo.mHandlesMIMEType(mimeType)) { unique_ptr decoder(subclassInfo.mCreateDecoder(std::move(inputSource))); if(!AutomaticallyOpenDecoders()) return decoder; else { if(decoder->Open(error)) return decoder; // Take back the input source for reuse if opening fails else inputSource = std::move(decoder->mInputSource); } } } #if 0 if(releaseMIMEType) CFRelease(mimeType), mimeType = nullptr; #endif } // If no MIME type was specified, use the extension-based resolvers CFURLRef inputURL = inputSource->GetURL(); if(!inputURL) return nullptr; SFB::CFString pathExtension = CFURLCopyPathExtension(inputURL); if(!pathExtension) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The type of the file “%@” could not be determined."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Unknown file type"), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may be missing or may not match the file's type."), ""); *error = CreateErrorForURL(InputSource::ErrorDomain, InputSource::FileNotFoundError, description, inputURL, failureReason, recoverySuggestion); } return nullptr; } // TODO: Some extensions (.oga for example) support multiple audio codecs (Vorbis, FLAC, Speex) // and if openDecoder is false the wrong decoder type may be returned, since the file isn't analyzed // until Open() is called for(auto subclassInfo : sRegisteredSubclasses) { if(subclassInfo.mHandlesFilesWithExtension(pathExtension)) { unique_ptr decoder(subclassInfo.mCreateDecoder(std::move(inputSource))); if(!AutomaticallyOpenDecoders()) return decoder; else { if(decoder->Open(error)) return decoder; // Take back the input source for reuse if opening fails else inputSource = std::move(decoder->mInputSource); } } } return nullptr; }
bool MP3Metadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true); TagLib::MPEG::File file(stream, TagLib::ID3v2::FrameFactory::instance()); if(!file.isValid()) { if(nullptr != error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an MPEG file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, 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, kPropertiesFormatNameKey, CFSTR("MPEG-1 Layer I")); break; case 2: CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-1 Layer II")); break; case 3: CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-1 Layer III")); break; } break; case TagLib::MPEG::Header::Version2: switch(properties->layer()) { case 1: CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2 Layer I")); break; case 2: CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2 Layer II")); break; case 3: CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2 Layer III")); break; } break; case TagLib::MPEG::Header::Version2_5: switch(properties->layer()) { case 1: CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2.5 Layer I")); break; case 2: CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2.5 Layer II")); break; case 3: CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2.5 Layer III")); break; } break; } #endif if(properties->xingHeader() && properties->xingHeader()->totalFrames()) AddIntToDictionary(mMetadata, kPropertiesTotalFramesKey, properties->xingHeader()->totalFrames()); } if(file.APETag()) { std::vector<AttachedPicture *> pictures; AddAPETagToDictionary(mMetadata, pictures, file.APETag()); for(auto picture : pictures) AddSavedPicture(picture); } if(file.ID3v1Tag()) AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag()); if(file.ID3v2Tag()) { std::vector<AttachedPicture *> pictures; AddID3v2TagToDictionary(mMetadata, pictures, file.ID3v2Tag()); for(auto picture : pictures) AddSavedPicture(picture); } 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::MODMetadata::_ReadMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; SFB::CFString pathExtension = CFURLCopyPathExtension(mURL); if(!pathExtension) return false; bool fileIsValid = false; if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("it"), kCFCompareCaseInsensitive)) { 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::IT::File file(stream.get()); if(file.isValid()) { fileIsValid = true; CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MOD (Impulse Tracker)")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddTagToDictionary(mMetadata, file.tag()); } } else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("xm"), kCFCompareCaseInsensitive)) { 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::XM::File file(stream.get()); if(file.isValid()) { fileIsValid = true; CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MOD (Extended Module)")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddTagToDictionary(mMetadata, file.tag()); } } else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("s3m"), kCFCompareCaseInsensitive)) { 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::S3M::File file(stream.get()); if(file.isValid()) { fileIsValid = true; CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MOD (ScreamTracker III)")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddTagToDictionary(mMetadata, file.tag()); } } else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("mod"), kCFCompareCaseInsensitive)) { 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::Mod::File file(stream.get()); if(file.isValid()) { fileIsValid = true; CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MOD (Protracker)")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddTagToDictionary(mMetadata, file.tag()); } } if(!fileIsValid) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MOD file."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not a MOD 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; } return true; }
bool TrueAudioDecoder::Open(CFErrorRef *error) { if(IsOpen()) { LOGGER_WARNING("org.sbooth.AudioEngine.AudioDecoder.TrueAudio", "Open() called on an AudioDecoder that is already open"); return true; } // Ensure the input source is open if(!mInputSource->IsOpen() && !mInputSource->Open(error)) return false; mCallbacks = new TTA_io_callback_wrapper; mCallbacks->iocb.read = read_callback; mCallbacks->iocb.write = nullptr; mCallbacks->iocb.seek = seek_callback; mCallbacks->decoder = this; TTA_info streamInfo; try { mDecoder = new tta::tta_decoder((TTA_io_callback *)mCallbacks); mDecoder->init_get_info(&streamInfo, 0); } catch(tta::tta_exception e) { LOGGER_WARNING("org.sbooth.AudioEngine.AudioDecoder.TrueAudio", "Error creating True Audio decoder: " << e.code()); if(mDecoder) delete mDecoder, mDecoder = nullptr; } if(nullptr == mDecoder) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid True Audio file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a True Audio file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger; mFormat.mSampleRate = streamInfo.sps; mFormat.mChannelsPerFrame = streamInfo.nch; mFormat.mBitsPerChannel = streamInfo.bps; mFormat.mBytesPerPacket = ((streamInfo.bps + 7) / 8) * mFormat.mChannelsPerFrame; mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; // Support 4 to 32 bits per sample (True Audio may support more or less, but the documentation didn't say) switch(mFormat.mBitsPerChannel) { case 8: case 16: case 24: case 32: mFormat.mFormatFlags |= kAudioFormatFlagIsPacked; break; case 4 ... 7: case 9 ... 15: case 17 ... 23: case 25 ... 31: // Align high because Apple's AudioConverter doesn't handle low alignment mFormat.mFormatFlags |= kAudioFormatFlagIsAlignedHigh; break; default: { LOGGER_ERR("org.sbooth.AudioEngine.AudioDecoder.TrueAudio", "Unsupported bit depth: " << mFormat.mBitsPerChannel) if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a supported True Audio file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Bit depth not supported"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's bit depth is not supported."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderFileFormatNotSupportedError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } delete mDecoder, mDecoder = nullptr; delete mCallbacks, mCallbacks = nullptr; return false; } } // Set up the source format mSourceFormat.mFormatID = 'TTA '; mSourceFormat.mSampleRate = streamInfo.sps; mSourceFormat.mChannelsPerFrame = streamInfo.nch; mSourceFormat.mBitsPerChannel = streamInfo.bps; // Setup the channel layout mChannelLayout = CreateChannelLayoutWithTag(streamInfo.nch); mTotalFrames = streamInfo.samples; mIsOpen = true; return true; }
bool OggSpeexDecoder::Open(CFErrorRef *error) { if(IsOpen()) { LOGGER_WARNING("org.sbooth.AudioEngine.AudioDecoder.OggSpeex", "Open() called on an AudioDecoder that is already open"); return true; } // Ensure the input source is open if(!mInputSource->IsOpen() && !mInputSource->Open(error)) return false; // 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 = GetInputSource()->Read(data, READ_SIZE_BYTES); if(-1 == bytesRead) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” could not be read."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Read error"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("Unable to read from the input file."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an Ogg file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an Ogg file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an Ogg file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an Ogg file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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, static_cast<int>(op.bytes)); if(nullptr == header) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Ogg Speex file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an Ogg Speex file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderFileFormatNotRecognizedError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } ogg_sync_destroy(&mOggSyncState); return false; } else if(SPEEX_NB_MODES <= header->mode) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The Speex mode in the file “%@” is not supported."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unsupported Ogg Speex file mode"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("This file may have been encoded with a newer version of Speex."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderFileFormatNotSupportedError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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) { CFStringRef description = CFCopyLocalizedString(CFSTR("The Speex version in the file “%@” is not supported."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unsupported Ogg Speex file version"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("This file was encoded with a different version of Speex."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderFileFormatNotSupportedError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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) { CFStringRef description = CFCopyLocalizedString(CFSTR("Unable to initialize the Speex decoder."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Error initializing Speex decoder"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("An unknown error occurred."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } 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 = 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 = 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 = header->nb_channels; switch(header->nb_channels) { case 1: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Mono); break; case 2: mChannelLayout = CreateChannelLayoutWithTag(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); mBufferList = AllocateABL(mFormat, speexFrameSize); if(nullptr == mBufferList) { if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, nullptr); speex_header_free(header), header = 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; mIsOpen = true; return true; }
bool MODMetadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; CFStringRef pathExtension = CFURLCopyPathExtension(mURL); if(nullptr == pathExtension) return false; bool fileIsValid = false; if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("it"), kCFCompareCaseInsensitive)) { auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true); TagLib::IT::File file(stream); if(file.isValid()) { fileIsValid = true; CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MOD (Impulse Tracker)")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddTagToDictionary(mMetadata, file.tag()); } } else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("xm"), kCFCompareCaseInsensitive)) { auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true); TagLib::XM::File file(stream); if(file.isValid()) { fileIsValid = true; CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MOD (Extended Module)")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddTagToDictionary(mMetadata, file.tag()); } } else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("s3m"), kCFCompareCaseInsensitive)) { auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true); TagLib::S3M::File file(stream); if(file.isValid()) { fileIsValid = true; CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MOD (ScreamTracker III)")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddTagToDictionary(mMetadata, file.tag()); } } else if(kCFCompareEqualTo == CFStringCompare(pathExtension, CFSTR("mod"), kCFCompareCaseInsensitive)) { auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true); TagLib::Mod::File file(stream); if(file.isValid()) { fileIsValid = true; CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MOD (Protracker)")); if(file.audioProperties()) AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(file.tag()) AddTagToDictionary(mMetadata, file.tag()); } } CFRelease(pathExtension), pathExtension = nullptr; if(!fileIsValid) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MOD file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a MOD file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } return true; }
AudioDecoder * AudioDecoder::CreateDecoderForInputSource(InputSource *inputSource, CFStringRef mimeType, CFErrorRef *error) { if(nullptr == inputSource) return nullptr; AudioDecoder *decoder = nullptr; // Open the input source if it isn't already if(AutomaticallyOpenDecoders() && !inputSource->IsOpen() && !inputSource->Open(error)) return nullptr; // As a factory this class has knowledge of its subclasses // It would be possible (and perhaps preferable) to switch to a generic // plugin interface at a later date #if 0 // If the input is an instance of HTTPInputSource, use the MIME type from the server // This code is disabled because most HTTP servers don't send the correct MIME types HTTPInputSource *httpInputSource = dynamic_cast<HTTPInputSource *>(inputSource); bool releaseMIMEType = false; if(!mimeType && httpInputSource && httpInputSource->IsOpen()) { mimeType = httpInputSource->CopyContentMIMEType(); if(mimeType) releaseMIMEType = true; } #endif // The MIME type takes precedence over the file extension if(mimeType) { if(FLACDecoder::HandlesMIMEType(mimeType)) { decoder = new FLACDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && WavPackDecoder::HandlesMIMEType(mimeType)) { decoder = new WavPackDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && MPEGDecoder::HandlesMIMEType(mimeType)) { decoder = new MPEGDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && OggVorbisDecoder::HandlesMIMEType(mimeType)) { decoder = new OggVorbisDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && OggSpeexDecoder::HandlesMIMEType(mimeType)) { decoder = new OggSpeexDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } #if !TARGET_OS_IPHONE if(nullptr == decoder && MusepackDecoder::HandlesMIMEType(mimeType)) { decoder = new MusepackDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && MonkeysAudioDecoder::HandlesMIMEType(mimeType)) { decoder = new MonkeysAudioDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && MODDecoder::HandlesMIMEType(mimeType)) { decoder = new MODDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && TrueAudioDecoder::HandlesMIMEType(mimeType)) { decoder = new TrueAudioDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && LibsndfileDecoder::HandlesMIMEType(mimeType)) { decoder = new LibsndfileDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } #endif if(nullptr == decoder && CoreAudioDecoder::HandlesMIMEType(mimeType)) { decoder = new CoreAudioDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } #if 0 if(releaseMIMEType) CFRelease(mimeType), mimeType = nullptr; #endif if(decoder) return decoder; } // If no MIME type was specified, use the extension-based resolvers CFURLRef inputURL = inputSource->GetURL(); if(!inputURL) return nullptr; // Determining the extension isn't as simple as using CFURLCopyPathExtension (wouldn't that be nice?), // because although the behavior on Lion works like one would expect, on Snow Leopard it returns // a number that I believe is part of the inode number, but is definitely NOT the extension CFStringRef pathExtension = nullptr; #if !TARGET_OS_IPHONE CFURLRef filePathURL = CFURLCreateFilePathURL(kCFAllocatorDefault, inputURL, nullptr); if(filePathURL) { pathExtension = CFURLCopyPathExtension(filePathURL); CFRelease(filePathURL), filePathURL = nullptr; } else #endif pathExtension = CFURLCopyPathExtension(inputURL); if(!pathExtension) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The type of the file “%@” could not be determined."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unknown file type"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may be missing or may not match the file's type."), ""); *error = CreateErrorForURL(InputSourceErrorDomain, InputSourceFileNotFoundError, description, inputURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return nullptr; } // TODO: Some extensions (.oga for example) support multiple audio codecs (Vorbis, FLAC, Speex) // and if openDecoder is false the wrong decoder type may be returned, since the file isn't analyzed // until Open() is called if(FLACDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new FLACDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && WavPackDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new WavPackDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && MPEGDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new MPEGDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && OggVorbisDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new OggVorbisDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && OggSpeexDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new OggSpeexDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } #if !TARGET_OS_IPHONE if(nullptr == decoder && MusepackDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new MusepackDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && MonkeysAudioDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new MonkeysAudioDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && MODDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new MODDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && TrueAudioDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new TrueAudioDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } if(nullptr == decoder && LibsndfileDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new LibsndfileDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } #endif if(nullptr == decoder && CoreAudioDecoder::HandlesFilesWithExtension(pathExtension)) { decoder = new CoreAudioDecoder(inputSource); if(AutomaticallyOpenDecoders() && !decoder->Open(error)) { decoder->mInputSource = nullptr; delete decoder, decoder = nullptr; } } CFRelease(pathExtension), pathExtension = nullptr; return decoder; }
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::ReplayGainAnalyzer::AnalyzeURL(CFURLRef url, CFErrorRef *error) { if(nullptr == url) return false; auto decoder = Decoder::CreateDecoderForURL(url, error); if(!decoder || !decoder->Open(error)) return false; AudioStreamBasicDescription inputFormat = decoder->GetFormat(); // Higher sampling rates aren't natively supported but are handled via resampling int32_t decoderSampleRate = (int32_t)inputFormat.mSampleRate; bool validSampleRate = EvenMultipleSampleRateIsSupported(decoderSampleRate); if(!validSampleRate) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” does not contain audio at a supported sample rate."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Only sample rates of 8.0 KHz, 11.025 KHz, 12.0 KHz, 16.0 KHz, 22.05 KHz, 24.0 KHz, 32.0 KHz, 44.1 KHz, 48 KHz and multiples are supported."), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(ReplayGainAnalyzer::ErrorDomain, ReplayGainAnalyzer::FileFormatNotSupportedError, description, url, failureReason, recoverySuggestion); } return false; } Float64 replayGainSampleRate = GetBestReplayGainSampleRateForSampleRate(decoderSampleRate); if(!(1 == inputFormat.mChannelsPerFrame || 2 == inputFormat.mChannelsPerFrame)) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” does not contain mono or stereo audio."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Only mono or stereo files supported"), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(ReplayGainAnalyzer::ErrorDomain, ReplayGainAnalyzer::FileFormatNotSupportedError, description, url, failureReason, recoverySuggestion); } return false; } AudioStreamBasicDescription outputFormat = { .mFormatID = kAudioFormatLinearPCM, .mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved, .mReserved = 0, .mSampleRate = replayGainSampleRate, .mChannelsPerFrame = inputFormat.mChannelsPerFrame, .mBitsPerChannel = 32, .mBytesPerPacket = 4, .mBytesPerFrame = 4, .mFramesPerPacket = 1 }; if(!SetSampleRate((int32_t)outputFormat.mSampleRate)) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” does not contain audio at a supported sample rate."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Only sample rates of 8.0 KHz, 11.025 KHz, 12.0 KHz, 16.0 KHz, 22.05 KHz, 24.0 KHz, 32.0 KHz, 44.1 KHz, 48 KHz and multiples are supported."), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(ReplayGainAnalyzer::ErrorDomain, ReplayGainAnalyzer::FileFormatNotSupportedError, description, url, failureReason, recoverySuggestion); } return false; } // Converter takes ownership of decoder Converter converter(std::move(decoder), outputFormat); if(!converter.Open(error)) return false; const UInt32 bufferSizeFrames = 512; BufferList outputBuffer(outputFormat, bufferSizeFrames); bool isStereo = (2 == outputFormat.mChannelsPerFrame); for(;;) { UInt32 frameCount = converter.ConvertAudio(outputBuffer, bufferSizeFrames); if(0 == frameCount) break; // Find the peak sample magnitude float lpeak, rpeak; vDSP_maxmgv((const float *)outputBuffer->mBuffers[0].mData, 1, &lpeak, frameCount); if(isStereo) { vDSP_maxmgv((const float *)outputBuffer->mBuffers[1].mData, 1, &rpeak, frameCount); priv->trackPeak = std::max(priv->trackPeak, std::max(lpeak, rpeak)); } else priv->trackPeak = std::max(priv->trackPeak, lpeak); // The replay gain analyzer expects 16-bit sample size passed as floats const float scale = 1u << 15; vDSP_vsmul((const float *)outputBuffer->mBuffers[0].mData, 1, &scale, (float *)outputBuffer->mBuffers[0].mData, 1, frameCount); if(isStereo) { vDSP_vsmul((const float *)outputBuffer->mBuffers[1].mData, 1, &scale, (float *)outputBuffer->mBuffers[1].mData, 1, frameCount); AnalyzeSamples((const float *)outputBuffer->mBuffers[0].mData, (const float *)outputBuffer->mBuffers[1].mData, frameCount, true); } else AnalyzeSamples((const float *)outputBuffer->mBuffers[0].mData, nullptr, frameCount, false); } priv->albumPeak = std::max(priv->albumPeak, priv->trackPeak); return true; } bool SFB::Audio::ReplayGainAnalyzer::GetTrackGain(float& trackGain) { if(!analyzeResult(priv->A, sizeof(priv->A) / sizeof(*(priv->A)), trackGain)) return false; for(uint32_t i = 0; i < sizeof(priv->A) / sizeof(*(priv->A)); ++i) { priv->B[i] += priv->A[i]; priv->A[i] = 0; } priv->Zero(); priv->totsamp = 0; priv->lsum = priv->rsum = 0.; return true; } bool SFB::Audio::ReplayGainAnalyzer::GetTrackPeak(float& trackPeak) { trackPeak = priv->trackPeak; priv->trackPeak = 0.; 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 MonkeysAudioDecoder::Open(CFErrorRef *error) { if(IsOpen()) { LOGGER_WARNING("org.sbooth.AudioEngine.AudioDecoder.MonkeysAudio", "Open() called on an AudioDecoder that is already open"); return true; } // Ensure the input source is open if(!mInputSource->IsOpen() && !mInputSource->Open(error)) return false; mIOInterface = new APEIOInterface(GetInputSource()); int errorCode; mDecompressor = CreateIAPEDecompressEx(mIOInterface, &errorCode); if(nullptr == mDecompressor) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Monkey's Audio file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a Monkey's Audio file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } // The file format mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; mFormat.mBitsPerChannel = static_cast<UInt32>(mDecompressor->GetInfo(APE_INFO_BITS_PER_SAMPLE)); mFormat.mSampleRate = mDecompressor->GetInfo(APE_INFO_SAMPLE_RATE); mFormat.mChannelsPerFrame = static_cast<UInt32>(mDecompressor->GetInfo(APE_INFO_CHANNELS)); mFormat.mBytesPerPacket = (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame; mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; // Set up the source format mSourceFormat.mFormatID = 'APE '; mSourceFormat.mSampleRate = mFormat.mSampleRate; mSourceFormat.mChannelsPerFrame = mFormat.mChannelsPerFrame; switch(mFormat.mChannelsPerFrame) { case 1: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Mono); break; case 2: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo); break; case 4: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Quadraphonic); break; } mIsOpen = true; return true; }
bool TrueAudioMetadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; // TODO: Use unique_ptr once the switch to C++11 STL is made std::auto_ptr<TagLib::FileStream> stream(new TagLib::FileStream(reinterpret_cast<const char *>(buf))); if(!stream->isOpen()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” could not be opened for writing."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Input/output error"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file may have been renamed, moved, deleted, or you may not have appropriate permissions."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } TagLib::TrueAudio::File file(stream.get(), false); if(!file.isValid()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid True Audio file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a True Audio file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } // ID3v1 tags are only written if present, but ID3v2 tags are always written if(file.ID3v1Tag()) SetID3v1TagFromMetadata(*this, file.ID3v1Tag()); SetID3v2TagFromMetadata(*this, file.ID3v2Tag(true)); if(!file.save()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid True Audio file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unable to write metadata"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } MergeChangedMetadataIntoMetadata(); return true; }
bool WavPackDecoder::Open(CFErrorRef *error) { if(IsOpen()) { LOGGER_WARNING("org.sbooth.AudioEngine.AudioDecoder.WavPack", "Open() called on an AudioDecoder that is already open"); return true; } // Ensure the input source is open if(!mInputSource->IsOpen() && !mInputSource->Open(error)) return false; mStreamReader.read_bytes = read_bytes_callback; mStreamReader.get_pos = get_pos_callback; mStreamReader.set_pos_abs = set_pos_abs_callback; mStreamReader.set_pos_rel = set_pos_rel_callback; mStreamReader.push_back_byte = push_back_byte_callback; mStreamReader.get_length = get_length_callback; mStreamReader.can_seek = can_seek_callback; char errorBuf [80]; // Setup converter mWPC = WavpackOpenFileInputEx(&mStreamReader, this, nullptr, errorBuf, OPEN_WVC | OPEN_NORMALIZE, 0); if(nullptr == mWPC) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid WavPack file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a WavPack file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioDecoderErrorDomain, AudioDecoderInputOutputError, description, mInputSource->GetURL(), failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } // Floating-point and lossy files will be handed off in the canonical Core Audio format int mode = WavpackGetMode(mWPC); if(MODE_FLOAT & mode || !(MODE_LOSSLESS & mode)) { // Canonical Core Audio format mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; mFormat.mSampleRate = WavpackGetSampleRate(mWPC); mFormat.mChannelsPerFrame = WavpackGetNumChannels(mWPC); mFormat.mBitsPerChannel = 8 * sizeof(float); mFormat.mBytesPerPacket = (mFormat.mBitsPerChannel / 8); mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; } else { mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsNonInterleaved; // Don't set kAudioFormatFlagIsAlignedHigh for 32-bit integer files mFormat.mFormatFlags |= (32 == WavpackGetBitsPerSample(mWPC) ? kAudioFormatFlagIsPacked : kAudioFormatFlagIsAlignedHigh); mFormat.mSampleRate = WavpackGetSampleRate(mWPC); mFormat.mChannelsPerFrame = WavpackGetNumChannels(mWPC); mFormat.mBitsPerChannel = WavpackGetBitsPerSample(mWPC); mFormat.mBytesPerPacket = sizeof(int32_t); mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; } mTotalFrames = WavpackGetNumSamples(mWPC); // Set up the source format mSourceFormat.mFormatID = 'WVPK'; mSourceFormat.mSampleRate = WavpackGetSampleRate(mWPC); mSourceFormat.mChannelsPerFrame = WavpackGetNumChannels(mWPC); mSourceFormat.mBitsPerChannel = WavpackGetBitsPerSample(mWPC); // Setup the channel layout switch(mFormat.mChannelsPerFrame) { case 1: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Mono); break; case 2: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo); break; case 4: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Quadraphonic); break; } mBuffer = static_cast<int32_t *>(calloc(BUFFER_SIZE_FRAMES * mFormat.mChannelsPerFrame, sizeof(int32_t))); if(nullptr == mBuffer) { if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, nullptr); return false; } mIsOpen = true; return true; }