diff --git a/Sources/CSFBAudioEngine/Decoders/SFBCoreAudioDecoder.mm b/Sources/CSFBAudioEngine/Decoders/SFBCoreAudioDecoder.mm index 30ff1fd91..f2def4687 100644 --- a/Sources/CSFBAudioEngine/Decoders/SFBCoreAudioDecoder.mm +++ b/Sources/CSFBAudioEngine/Decoders/SFBCoreAudioDecoder.mm @@ -10,8 +10,8 @@ #import -#import -#import +#import +#import #import "SFBCoreAudioDecoder.h" @@ -71,8 +71,8 @@ SInt64 get_size_callback(void *inClientData) noexcept @interface SFBCoreAudioDecoder () { @private - SFB::AudioFileWrapper _af; - SFB::ExtAudioFileWrapper _eaf; + SFB::CAAudioFile _af; + SFB::CAExtAudioFile _eaf; } @end @@ -89,37 +89,26 @@ + (NSSet *)supportedPathExtensions static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - UInt32 size = 0; - auto result = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_ReadableTypes, 0, nullptr, &size); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "AudioFileGetGlobalInfoSize (kAudioFileGlobalInfo_ReadableTypes) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); - pathExtensions = [NSSet set]; - return; + try { + NSMutableSet *supportedPathExtensions = [NSMutableSet set]; + + auto readableTypes = SFB::CAAudioFile::ReadableTypes(); + for(const auto& type : readableTypes) { + try { + auto extensionsForType = SFB::CAAudioFile::ExtensionsForType(type); + [supportedPathExtensions addObjectsFromArray:(NSArray *)extensionsForType]; + } + catch(const std::exception& e) { + os_log_error(gSFBAudioDecoderLog, "SFB::CAAudioFile::ExtensionsForType failed for '%{public}.4s': %{public}s", SFBCStringForOSType(type), e.what()); + } + } + + pathExtensions = [supportedPathExtensions copy]; } - - auto readableTypesCount = size / sizeof(UInt32); - std::vector readableTypes(readableTypesCount); - - result = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ReadableTypes, 0, nullptr, &size, &readableTypes[0]); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "AudioFileGetGlobalInfo (kAudioFileGlobalInfo_ReadableTypes) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); + catch(const std::exception& e) { + os_log_error(gSFBAudioDecoderLog, "SFB::CAAudioFile::ReadableTypes failed: %{public}s", e.what()); pathExtensions = [NSSet set]; - return; - } - - NSMutableSet *supportedPathExtensions = [NSMutableSet set]; - for(UInt32 type : readableTypes) { - CFArrayRef extensionsForType = nil; - size = sizeof(extensionsForType); - result = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ExtensionsForType, sizeof(type), &type, &size, &extensionsForType); - - if(result == noErr) - [supportedPathExtensions addObjectsFromArray:(__bridge_transfer NSArray *)extensionsForType]; - else - os_log_error(gSFBAudioDecoderLog, "AudioFileGetGlobalInfo (kAudioFileGlobalInfo_ExtensionsForType) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); } - - pathExtensions = [supportedPathExtensions copy]; }); return pathExtensions; @@ -131,37 +120,26 @@ + (NSSet *)supportedMIMETypes static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - UInt32 size = 0; - auto result = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_ReadableTypes, 0, nullptr, &size); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "AudioFileGetGlobalInfoSize (kAudioFileGlobalInfo_ReadableTypes) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); - mimeTypes = [NSSet set]; - return; + try { + NSMutableSet *supportedMIMETypes = [NSMutableSet set]; + + auto readableTypes = SFB::CAAudioFile::ReadableTypes(); + for(const auto& type : readableTypes) { + try { + auto mimeTypesForType = SFB::CAAudioFile::MIMETypesForType(type); + [supportedMIMETypes addObjectsFromArray:(NSArray *)mimeTypesForType]; + } + catch(const std::exception& e) { + os_log_error(gSFBAudioDecoderLog, "SFB::CAAudioFile::MIMETypesForType failed for '%{public}.4s': %{public}s", SFBCStringForOSType(type), e.what()); + } + } + + mimeTypes = [supportedMIMETypes copy]; } - - auto readableTypesCount = size / sizeof(UInt32); - std::vector readableTypes(readableTypesCount); - - result = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_ReadableTypes, 0, nullptr, &size, &readableTypes[0]); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "AudioFileGetGlobalInfo (kAudioFileGlobalInfo_ReadableTypes) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); + catch(const std::exception& e) { + os_log_error(gSFBAudioDecoderLog, "SFB::CAAudioFile::ReadableTypes failed: %{public}s", e.what()); mimeTypes = [NSSet set]; - return; - } - - NSMutableSet *supportedMIMETypes = [NSMutableSet set]; - for(UInt32 type : readableTypes) { - CFArrayRef mimeTypesForType = nil; - size = sizeof(mimeTypesForType); - result = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_MIMETypesForType, sizeof(type), &type, &size, &mimeTypesForType); - - if(result == noErr) - [supportedMIMETypes addObjectsFromArray:(__bridge_transfer NSArray *)mimeTypesForType]; - else - os_log_error(gSFBAudioDecoderLog, "AudioFileGetGlobalInfo (kAudioFileGlobalInfo_MIMETypesForType) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); } - - mimeTypes = [supportedMIMETypes copy]; }); return mimeTypes; @@ -220,155 +198,100 @@ - (BOOL)openReturningError:(NSError **)error if(![super openReturningError:error]) return NO; - // Open the input file - AudioFileID audioFile; - auto result = AudioFileOpenWithCallbacks((__bridge void *)self, read_callback, nullptr, get_size_callback, nullptr, 0, &audioFile); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "AudioFileOpenWithCallbacks failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); + try { + // Open the input file + _af.OpenWithCallbacks((__bridge void *)self, read_callback, nullptr, get_size_callback, nullptr, 0); + _eaf.WrapAudioFileID(_af, false); - if(error) - *error = [NSError SFB_errorWithDomain:NSOSStatusErrorDomain - code:result - descriptionFormatStringForURL:NSLocalizedString(@"The format of the file “%@” was not recognized.", @"") - url:_inputSource.url - failureReason:NSLocalizedString(@"File Format Not Recognized", @"") - recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")]; + // Query file format + auto format = _eaf.FileDataFormat(); - return NO; - } - - auto af = SFB::AudioFileWrapper(audioFile); - - ExtAudioFileRef extAudioFile; - result = ExtAudioFileWrapAudioFileID(af, false, &extAudioFile); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileWrapAudioFileID failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); - - if(error) - *error = [NSError SFB_errorWithDomain:NSOSStatusErrorDomain - code:result - descriptionFormatStringForURL:NSLocalizedString(@"The format of the file “%@” was not recognized.", @"") - url:_inputSource.url - failureReason:NSLocalizedString(@"File Format Not Recognized", @"") - recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")]; - - return NO; - } - - auto eaf = SFB::ExtAudioFileWrapper(extAudioFile); - - // Query file format - AudioStreamBasicDescription format{}; - UInt32 dataSize = sizeof(format); - result = ExtAudioFileGetProperty(eaf, kExtAudioFileProperty_FileDataFormat, &dataSize, &format); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileGetProperty (kExtAudioFileProperty_FileDataFormat) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); - if(error) - *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:@{ NSURLErrorKey: _inputSource.url }]; - return NO; - } - - // Query channel layout - AVAudioChannelLayout *channelLayout = nil; - result = ExtAudioFileGetPropertyInfo(eaf, kExtAudioFileProperty_FileChannelLayout, &dataSize, nullptr); - if(result == noErr) { - AudioChannelLayout *layout = static_cast(malloc(dataSize)); - result = ExtAudioFileGetProperty(eaf, kExtAudioFileProperty_FileChannelLayout, &dataSize, layout); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileGetProperty (kExtAudioFileProperty_FileChannelLayout) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); - - free(layout); - - if(error) - *error = [NSError SFB_errorWithDomain:NSOSStatusErrorDomain - code:result - descriptionFormatStringForURL:NSLocalizedString(@"The format of the file “%@” was not recognized.", @"") - url:_inputSource.url - failureReason:NSLocalizedString(@"File Format Not Recognized", @"") - recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")]; - - return NO; - } - - channelLayout = [[AVAudioChannelLayout alloc] initWithLayout:layout]; - free(layout); + // Query channel layout + AVAudioChannelLayout *channelLayout = _eaf.FileChannelLayout(); // ExtAudioFile occasionally returns empty channel layouts; ignore them if(channelLayout.channelCount != format.mChannelsPerFrame) { os_log_error(gSFBAudioDecoderLog, "Channel count mismatch between AudioStreamBasicDescription (%u) and AVAudioChannelLayout (%u)", format.mChannelsPerFrame, channelLayout.channelCount); channelLayout = nil; } - } - else - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileGetPropertyInfo (kExtAudioFileProperty_FileChannelLayout) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); - _sourceFormat = [[AVAudioFormat alloc] initWithStreamDescription:&format channelLayout:channelLayout]; + _sourceFormat = [[AVAudioFormat alloc] initWithStreamDescription:&format channelLayout:channelLayout]; - // Tell the ExtAudioFile the format in which we'd like our data + // Tell the ExtAudioFile the format in which we'd like our data - // For Linear PCM formats leave the data untouched - if(format.mFormatID == kAudioFormatLinearPCM) - _processingFormat = [[AVAudioFormat alloc] initWithStreamDescription:&format channelLayout:channelLayout]; - // For Apple Lossless and FLAC convert to packed ints if possible, otherwise high-align - else if(format.mFormatID == kAudioFormatAppleLossless || format.mFormatID == kAudioFormatFLAC) { - AudioStreamBasicDescription asbd{}; + // For Linear PCM formats leave the data untouched + if(format.mFormatID == kAudioFormatLinearPCM) + _processingFormat = [[AVAudioFormat alloc] initWithStreamDescription:&format channelLayout:channelLayout]; + // For Apple Lossless and FLAC convert to packed ints if possible, otherwise high-align + else if(format.mFormatID == kAudioFormatAppleLossless || format.mFormatID == kAudioFormatFLAC) { + AudioStreamBasicDescription asbd{}; - asbd.mFormatID = kAudioFormatLinearPCM; + asbd.mFormatID = kAudioFormatLinearPCM; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-anon-enum-enum-conversion" - asbd.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger; + asbd.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger; #pragma clang diagnostic pop - asbd.mSampleRate = format.mSampleRate; - asbd.mChannelsPerFrame = format.mChannelsPerFrame; + asbd.mSampleRate = format.mSampleRate; + asbd.mChannelsPerFrame = format.mChannelsPerFrame; - if(format.mFormatFlags == kAppleLosslessFormatFlag_16BitSourceData) - asbd.mBitsPerChannel = 16; - else if(format.mFormatFlags == kAppleLosslessFormatFlag_20BitSourceData) - asbd.mBitsPerChannel = 20; - else if(format.mFormatFlags == kAppleLosslessFormatFlag_24BitSourceData) - asbd.mBitsPerChannel = 24; - else if(format.mFormatFlags == kAppleLosslessFormatFlag_32BitSourceData) - asbd.mBitsPerChannel = 32; + if(format.mFormatFlags == kAppleLosslessFormatFlag_16BitSourceData) + asbd.mBitsPerChannel = 16; + else if(format.mFormatFlags == kAppleLosslessFormatFlag_20BitSourceData) + asbd.mBitsPerChannel = 20; + else if(format.mFormatFlags == kAppleLosslessFormatFlag_24BitSourceData) + asbd.mBitsPerChannel = 24; + else if(format.mFormatFlags == kAppleLosslessFormatFlag_32BitSourceData) + asbd.mBitsPerChannel = 32; - asbd.mFormatFlags |= asbd.mBitsPerChannel % 8 ? kAudioFormatFlagIsAlignedHigh : kAudioFormatFlagIsPacked; + asbd.mFormatFlags |= asbd.mBitsPerChannel % 8 ? kAudioFormatFlagIsAlignedHigh : kAudioFormatFlagIsPacked; - asbd.mBytesPerPacket = ((asbd.mBitsPerChannel + 7) / 8) * asbd.mChannelsPerFrame; - asbd.mFramesPerPacket = 1; - asbd.mBytesPerFrame = asbd.mBytesPerPacket / asbd.mFramesPerPacket; + asbd.mBytesPerPacket = ((asbd.mBitsPerChannel + 7) / 8) * asbd.mChannelsPerFrame; + asbd.mFramesPerPacket = 1; + asbd.mBytesPerFrame = asbd.mBytesPerPacket / asbd.mFramesPerPacket; - _processingFormat = [[AVAudioFormat alloc] initWithStreamDescription:&asbd channelLayout:channelLayout]; - } - // For all other formats convert to the canonical Core Audio format - else { - if(channelLayout) - _processingFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:format.mSampleRate interleaved:NO channelLayout:channelLayout]; - else - _processingFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:format.mSampleRate channels:format.mChannelsPerFrame interleaved:NO]; - } + _processingFormat = [[AVAudioFormat alloc] initWithStreamDescription:&asbd channelLayout:channelLayout]; + } + // For all other formats convert to the canonical Core Audio format + else { + if(channelLayout) + _processingFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:format.mSampleRate interleaved:NO channelLayout:channelLayout]; + else + _processingFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:format.mSampleRate channels:format.mChannelsPerFrame interleaved:NO]; + } - // For audio with more than 2 channels AVAudioFormat requires a channel map. Since ExtAudioFile doesn't always return one, there is a - // chance that the initialization of _processingFormat failed. If that happened then attempting to set kExtAudioFileProperty_ClientDataFormat - // will segfault - if(!_processingFormat) { - if(error) - *error = [NSError SFB_errorWithDomain:SFBAudioDecoderErrorDomain - code:SFBAudioDecoderErrorCodeInvalidFormat - descriptionFormatStringForURL:NSLocalizedString(@"The format of the file “%@” was not recognized.", @"") - url:_inputSource.url - failureReason:NSLocalizedString(@"File Format Not Recognized", @"") - recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")]; + // For audio with more than 2 channels AVAudioFormat requires a channel map. Since ExtAudioFile doesn't always return one, there is a + // chance that the initialization of _processingFormat failed. If that happened then attempting to set kExtAudioFileProperty_ClientDataFormat + // will segfault + if(!_processingFormat) { + if(error) + *error = [NSError SFB_errorWithDomain:SFBAudioDecoderErrorDomain + code:SFBAudioDecoderErrorCodeInvalidFormat + descriptionFormatStringForURL:NSLocalizedString(@"The format of the file “%@” was not recognized.", @"") + url:_inputSource.url + failureReason:NSLocalizedString(@"File Format Not Recognized", @"") + recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")]; - return NO; + return NO; + } + + _eaf.SetClientDataFormat(*_processingFormat.streamDescription); + + return YES; } + catch(const std::system_error& e) { + os_log_error(gSFBAudioDecoderLog, "Error opening SFBCoreAudioDecoder: %{public}s", e.what()); - result = ExtAudioFileSetProperty(eaf, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), _processingFormat.streamDescription); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileSetProperty (kExtAudioFileProperty_ClientDataFormat) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); + try { + _af.Close(); + _eaf.Close(); + } + catch(...) + {} if(error) *error = [NSError SFB_errorWithDomain:NSOSStatusErrorDomain - code:result + code:e.code().value() descriptionFormatStringForURL:NSLocalizedString(@"The format of the file “%@” was not recognized.", @"") url:_inputSource.url failureReason:NSLocalizedString(@"File Format Not Recognized", @"") @@ -376,47 +299,49 @@ - (BOOL)openReturningError:(NSError **)error return NO; } - - _af = std::move(af); - _eaf = std::move(eaf); - - return YES; } - (BOOL)closeReturningError:(NSError **)error { - _eaf.reset(); - _af.reset(); + try { + _eaf.Close(); + _af.Close(); + } + catch(const std::system_error& e) { + os_log_error(gSFBAudioDecoderLog, "Error closing SFBCoreAudioDecoder: %{public}s", e.what()); + if(error) + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:e.code().value() userInfo:@{ NSURLErrorKey: _inputSource.url }]; + return NO; + } return [super closeReturningError:error]; } - (BOOL)isOpen { - return _eaf != nullptr; + return _eaf.IsValid(); } - (AVAudioFramePosition)framePosition { - SInt64 currentFrame; - auto result = ExtAudioFileTell(_eaf, ¤tFrame); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileTell failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); + try { + return _eaf.Tell(); + } + catch(const std::exception& e) { + os_log_error(gSFBAudioDecoderLog, "SFB::CAExtAudioFile::Tell failed: %{public}s", e.what()); return SFBUnknownFramePosition; } - return currentFrame; } - (AVAudioFramePosition)frameLength { - SInt64 frameLength; - UInt32 dataSize = sizeof(frameLength); - auto result = ExtAudioFileGetProperty(_eaf, kExtAudioFileProperty_FileLengthFrames, &dataSize, &frameLength); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileGetProperty (kExtAudioFileProperty_FileLengthFrames) failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); + try { + return _eaf.FrameLength(); + } + catch(const std::exception& e) { + os_log_error(gSFBAudioDecoderLog, "SFB::CAExtAudioFile::FrameLength failed: %{public}s", e.what()); return SFBUnknownFrameLength; } - return frameLength; } - (BOOL)decodeIntoBuffer:(AVAudioPCMBuffer *)buffer frameLength:(AVAudioFrameCount)frameLength error:(NSError **)error @@ -434,31 +359,33 @@ - (BOOL)decodeIntoBuffer:(AVAudioPCMBuffer *)buffer frameLength:(AVAudioFrameCou buffer.frameLength = buffer.frameCapacity; - auto result = ExtAudioFileRead(_eaf, &frameLength, buffer.mutableAudioBufferList); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileRead failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); + try { + _eaf.Read(frameLength, buffer.mutableAudioBufferList); + buffer.frameLength = frameLength; + return YES; + } + catch(const std::system_error& e) { + os_log_error(gSFBAudioDecoderLog, "SFB::CAExtAudioFile::Read failed: %{public}s", e.what()); buffer.frameLength = 0; if(error) - *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:@{ NSURLErrorKey: _inputSource.url }]; + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:e.code().value() userInfo:@{ NSURLErrorKey: _inputSource.url }]; return NO; } - - buffer.frameLength = frameLength; - - return YES; } - (BOOL)seekToFrame:(AVAudioFramePosition)frame error:(NSError **)error { NSParameterAssert(frame >= 0); - auto result = ExtAudioFileSeek(_eaf, frame); - if(result != noErr) { - os_log_error(gSFBAudioDecoderLog, "ExtAudioFileSeek failed: %d '%{public}.4s'", result, SFBCStringForOSType(result)); + try { + _eaf.Seek(frame); + return YES; + } + catch(const std::system_error& e) { + os_log_error(gSFBAudioDecoderLog, "SFB::CAExtAudioFile::Seek failed: %{public}s", e.what()); if(error) - *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:@{ NSURLErrorKey: _inputSource.url }]; + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:e.code().value() userInfo:@{ NSURLErrorKey: _inputSource.url }]; return NO; } - return YES; } @end