diff --git a/src/CoreAudio/AudioToolbox/AudioFile.cpp b/src/CoreAudio/AudioToolbox/AudioFile.cpp index 6d34d85d1..0b155ebfd 100644 --- a/src/CoreAudio/AudioToolbox/AudioFile.cpp +++ b/src/CoreAudio/AudioToolbox/AudioFile.cpp @@ -19,6 +19,7 @@ along with Darling. If not, see . #include #include +#include "AudioFileFormatManager.h" OSStatus AudioFileCreateWithURL (CFURLRef inFileRef, AudioFileTypeID inFileType, @@ -193,7 +194,34 @@ OSStatus AudioFileGetGlobalInfoSize( AudioFilePropertyID inPropertyID, void * _Nullable inSpecifier, UInt32 *outDataSize) { - return unimpErr; + return AudioFileGetGlobalInfo(inPropertyID, inSpecifierSize, inSpecifier, outDataSize, nullptr); +} + +static void writeUInt32Set(const std::set& set, void *outPropertyData) +{ + if (outPropertyData) + { + UInt32* out = static_cast(outPropertyData); + int i = 0; + for (UInt32 v : set) + out[i++] = v; + } +} + +template +CFArrayRef stringContainerToArray(const ContainerType& t) +{ + std::unique_ptr ptrs(new CFStringRef[t.size()]); + int i = 0; + + for (const std::string& str : t) + ptrs[i++] = CFStringCreateWithCString(nullptr, str.c_str(), kCFStringEncodingUTF8); + + CFArrayRef rv = CFArrayCreate(nullptr, (const void**) ptrs.get(), t.size(), &kCFTypeArrayCallBacks); + + for (int i = 0; i < t.size(); i++) + CFRelease(ptrs[i]); + return rv; } OSStatus AudioFileGetGlobalInfo( AudioFilePropertyID inPropertyID, @@ -202,6 +230,142 @@ OSStatus AudioFileGetGlobalInfo( AudioFilePropertyID inPropertyID, UInt32 *ioDataSize, void *outPropertyData) { + switch (inPropertyID) + { + case kAudioFileGlobalInfo_ReadableTypes: + case kAudioFileGlobalInfo_WritableTypes: + { + std::set types = AudioFileFormatManager::instance()->types(inPropertyID == kAudioFileGlobalInfo_WritableTypes); + + *ioDataSize = types.size() * sizeof(UInt32); + writeUInt32Set(types, outPropertyData); + + return noErr; + } + case kAudioFileGlobalInfo_FileTypeName: + { + const UInt32* fileType = static_cast(inSpecifier); + if (inSpecifierSize != sizeof(*fileType)) + return kAudioFileBadPropertySizeError; + + const AudioFileFormatManager::ComponentInfo* ci; + + ci = AudioFileFormatManager::instance()->fileType(*fileType); + if (!ci) + return kAudioFileUnsupportedFileTypeError; + + CFStringRef* out = static_cast(outPropertyData); + *ioDataSize = sizeof(*out); + + if (out) + *out = CFStringCreateWithCString(nullptr, ci->name.c_str(), kCFStringEncodingUTF8); + + return noErr; + } + case kAudioFileGlobalInfo_AvailableStreamDescriptionsForFormat: + { + const AudioFileTypeAndFormatID* spec = static_cast(inSpecifier); + if (inSpecifierSize != sizeof(*spec)) + return kAudioFileBadPropertySizeError; + break; + } + case kAudioFileGlobalInfo_AvailableFormatIDs: + { + const UInt32* fileType = static_cast(inSpecifier); + if (inSpecifierSize != sizeof(*fileType)) + return kAudioFileBadPropertySizeError; + + auto set = AudioFileFormatManager::instance()->availableFormatIDs(*fileType); + *ioDataSize = set.size() * sizeof(UInt32); + + writeUInt32Set(set, outPropertyData); + + return noErr; + } + case kAudioFileGlobalInfo_AllHFSTypeCodes: + case kAudioFileGlobalInfo_HFSTypeCodesForType: + case kAudioFileGlobalInfo_TypesForHFSTypeCode: + *ioDataSize = 0; + return noErr; + case kAudioFileGlobalInfo_AllExtensions: + case kAudioFileGlobalInfo_AllUTIs: + case kAudioFileGlobalInfo_AllMIMETypes: + { + *ioDataSize = sizeof(CFArrayRef); + + if (outPropertyData) + { + std::set set; + AudioFileFormatManager* mgr = AudioFileFormatManager::instance(); + + if (inPropertyID == kAudioFileGlobalInfo_AllExtensions) + set = mgr->allExtensions(); + else if (inPropertyID == kAudioFileGlobalInfo_AllUTIs) + set = mgr->allUTIs(); + else if (inPropertyID == kAudioFileGlobalInfo_AllMIMETypes) + set = mgr->allMIMEs(); + + *((CFArrayRef*)outPropertyData) = stringContainerToArray(set); + } + + return noErr; + } + case kAudioFileGlobalInfo_ExtensionsForType: + case kAudioFileGlobalInfo_UTIsForType: + case kAudioFileGlobalInfo_MIMETypesForType: + { + const UInt32* fileType = static_cast(inSpecifier); + if (inSpecifierSize != sizeof(*fileType)) + return kAudioFileBadPropertySizeError; + + *ioDataSize = sizeof(CFArrayRef); + + if (outPropertyData) + { + const std::vector* vector; + const AudioFileFormatManager::ComponentInfo* ci; + + ci = AudioFileFormatManager::instance()->fileType(*fileType); + if (!ci) + return kAudioFileUnsupportedFileTypeError; + + if (inPropertyID == kAudioFileGlobalInfo_ExtensionsForType) + vector = &ci->extensions; + else if (inPropertyID == kAudioFileGlobalInfo_UTIsForType) + vector = &ci->utis; + else if (inPropertyID == kAudioFileGlobalInfo_MIMETypesForType) + vector = &ci->mimeTypes; + + *((CFArrayRef*)outPropertyData) = stringContainerToArray(*vector); + } + + return noErr; + } + case kAudioFileGlobalInfo_TypesForMIMEType: + case kAudioFileGlobalInfo_TypesForUTI: + case kAudioFileGlobalInfo_TypesForExtension: + { + CFStringRef str = static_cast(inSpecifier); + if (inSpecifierSize != sizeof(str)) + return kAudioFileBadPropertySizeError; + + AudioFileFormatManager* mgr = AudioFileFormatManager::instance(); + const char* cstr = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); + std::set set; + + if (inPropertyID == kAudioFileGlobalInfo_TypesForMIMEType) + set = mgr->typesForMIME(cstr); + else if (inPropertyID == kAudioFileGlobalInfo_TypesForUTI) + set = mgr->typesForUTI(cstr); + else if (inPropertyID == kAudioFileGlobalInfo_TypesForExtension) + set = mgr->typesForExtension(cstr); + + *ioDataSize = set.size() * sizeof(UInt32); + writeUInt32Set(set, outPropertyData); + + return noErr; + } + } return unimpErr; } diff --git a/src/CoreAudio/AudioToolbox/AudioFileFormatManager.cpp b/src/CoreAudio/AudioToolbox/AudioFileFormatManager.cpp index 4b1cb5f54..d6ff9a3fd 100644 --- a/src/CoreAudio/AudioToolbox/AudioFileFormatManager.cpp +++ b/src/CoreAudio/AudioToolbox/AudioFileFormatManager.cpp @@ -44,15 +44,126 @@ void AudioFileFormatManager::buildDatabase() } } +static std::vector cfArrayToVector(CFArrayRef array) +{ + std::vector rv; + const CFIndex count = CFArrayGetCount(array); + + rv.reserve(count); + + for (CFIndex i = 0; i < count; i++) + { + CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(array, i); + rv.push_back(CFStringGetCStringPtr(str, kCFStringEncodingUTF8)); + } + + return rv; +} + void AudioFileFormatManager::analyzeAudioFileComponent(AudioFileComponent component) { - /* - kAudioFileComponent_FileTypeName = 'ftnm', - kAudioFileComponent_UTIsForType = 'futi', - kAudioFileComponent_MIMETypesForType = 'fmim', - kAudioFileComponent_ExtensionsForType = 'fext', - kAudioFileComponent_AvailableFormatIDs = 'fmid', - */ + ComponentInfo ci; + UInt32 propSize; + + ::AudioComponentGetDescription(AudioComponent(component), &ci.acd); + + ci.fileType = ci.acd.componentSubType; // TODO: Is this correct? + + { + CFStringRef name = nullptr; + propSize = sizeof(name); + + if (::AudioFileComponentGetGlobalInfo(component, kAudioFileComponent_FileTypeName, 0, nullptr, &propSize, &name) == noErr && name) + { + ci.name = CFStringGetCStringPtr(name, kCFStringEncodingUTF8); + CFRelease(name); + } + } + { + UInt32 can; + + propSize = sizeof(can); + ci.canRead = ci.canWrite = false; + + if (::AudioFileComponentGetGlobalInfo(component, kAudioFileComponent_CanRead, 0, nullptr, &propSize, &can) == noErr && can) + ci.canRead = true; + if (::AudioFileComponentGetGlobalInfo(component, kAudioFileComponent_CanWrite, 0, nullptr, &propSize, &can) == noErr && can) + ci.canWrite = true; + } + + CFArrayRef array = nullptr; + if (::AudioFileComponentGetGlobalInfo(component, kAudioFileComponent_UTIsForType, 0, nullptr, &propSize, &array) == noErr && array) + { + ci.utis = cfArrayToVector(array); + CFRelease(array); + } + + array = nullptr; + if (::AudioFileComponentGetGlobalInfo(component, kAudioFileComponent_MIMETypesForType, 0, nullptr, &propSize, &array) == noErr && array) + { + ci.mimeTypes = cfArrayToVector(array); + CFRelease(array); + } + + array = nullptr; + if (::AudioFileComponentGetGlobalInfo(component, kAudioFileComponent_ExtensionsForType, 0, nullptr, &propSize, &array) == noErr && array) + { + ci.extensions = cfArrayToVector(array); + CFRelease(array); + } + + if (::AudioFileComponentGetGlobalInfoSize(component, kAudioFileComponent_AvailableFormatIDs, 0, nullptr, &propSize) == noErr) + { + const size_t formatCount = propSize / sizeof(UInt32); + std::unique_ptr formatIds(new UInt32[formatCount]); + + if (::AudioFileComponentGetGlobalInfo(component, kAudioFileComponent_AvailableFormatIDs, 0, nullptr, &propSize, formatIds.get()) == noErr) + { + for (size_t i = 0; i < formatCount; i++) + { + UInt32 format = formatIds[i]; + std::vector asbds; + + if (::AudioFileComponentGetGlobalInfoSize(component, kAudioFileComponent_AvailableStreamDescriptionsForFormat, sizeof(UInt32), &format, &propSize) == noErr) + { + asbds.resize(propSize / sizeof(AudioStreamBasicDescription)); + + if (::AudioFileComponentGetGlobalInfo(component, kAudioFileComponent_AvailableStreamDescriptionsForFormat, sizeof(UInt32), &format, &propSize, asbds.data()) != noErr) + asbds.resize(0); + + ci.formatDescriptions.emplace(format, std::move(asbds)); + } + else + ci.formatDescriptions.emplace(format, asbds); + } + } + } + + registerComponent(ci); +} + +void AudioFileFormatManager::addToMap(std::unordered_map>& map, const std::string& key, ComponentInfo* ci) +{ + auto it = map.find(key); + if (it == map.end()) + it = map.emplace(key, std::vector()).first; + it->second.push_back(ci); +} + +void AudioFileFormatManager::registerComponent(const ComponentInfo& ci) +{ + m_components.push_back(ci); + + // Add format into various maps + ComponentInfo* pci = &m_components.back(); + m_fileTypeMap.emplace(ci.fileType, pci); + + for (const std::string& uti : ci.utis) + addToMap(m_utiMap, uti, pci); + for (const std::string& mime : ci.mimeTypes) + addToMap(m_mimeMap, mime, pci); + for (const std::string& ext : ci.extensions) + addToMap(m_extensionMap, ext, pci); } AudioFileFormatManager* AudioFileFormatManager::instance() @@ -60,3 +171,85 @@ AudioFileFormatManager* AudioFileFormatManager::instance() static AudioFileFormatManager inst; return &inst; } + +std::set AudioFileFormatManager::availableFormatIDs(UInt32 fileType) const +{ + std::set rv; + + auto it = m_fileTypeMap.find(fileType); + if (it == m_fileTypeMap.end()) + return rv; + + const ComponentInfo* ci = it->second; + + for (auto const& [key, value] : ci->formatDescriptions) + rv.insert(key); + return rv; +} + +const AudioFileFormatManager::ComponentInfo* AudioFileFormatManager::fileType(UInt32 fileType) const +{ + auto it = m_fileTypeMap.find(fileType); + if (it == m_fileTypeMap.end()) + return nullptr; + return it->second; +} + +std::set AudioFileFormatManager::types(bool writableTypes) const +{ + std::set rv; + + for (const ComponentInfo& ci : m_components) + { + if (writableTypes && ci.canWrite) + rv.insert(ci.fileType); + else if (!writableTypes && ci.canRead) + rv.insert(ci.fileType); + } + + return rv; +} + +std::set AudioFileFormatManager::findTypes(const std::unordered_map>& map, const char* key) +{ + std::set rv; + + auto it = map.find(key); + if (it == map.end()) + return rv; + + for (const ComponentInfo* ci : it->second) + rv.insert(ci->fileType); + + return rv; +} + +std::set AudioFileFormatManager::typesForMIME(const char* mime) const +{ + return findTypes(m_mimeMap, mime); +} + +std::set AudioFileFormatManager::typesForUTI(const char* uti) const +{ + return findTypes(m_utiMap, uti); +} + +std::set AudioFileFormatManager::typesForExtension(const char* ext) const +{ + return findTypes(m_extensionMap, ext); +} + +std::set AudioFileFormatManager::allMIMEs() const +{ + return allKeys(m_mimeMap); +} + +std::set AudioFileFormatManager::allUTIs() const +{ + return allKeys(m_utiMap); +} + +std::set AudioFileFormatManager::allExtensions() const +{ + return allKeys(m_extensionMap); +} diff --git a/src/CoreAudio/AudioToolbox/AudioFileFormatManager.h b/src/CoreAudio/AudioToolbox/AudioFileFormatManager.h index 596d89702..f937f6fa9 100644 --- a/src/CoreAudio/AudioToolbox/AudioFileFormatManager.h +++ b/src/CoreAudio/AudioToolbox/AudioFileFormatManager.h @@ -22,15 +22,60 @@ along with Darling. If not, see . #define _AUDIO_FILE_FORMAT_MANAGER_H #include #include +#include +#include +#include +#include +#include class __attribute__((visibility("hidden"))) AudioFileFormatManager { +public: + static AudioFileFormatManager* instance(); + + std::set availableFormatIDs(UInt32 fileType) const; + + struct ComponentInfo + { + AudioComponentDescription acd; + UInt32 fileType; // container type + std::string name; + std::vector utis, extensions, mimeTypes; + // codec type + std::unordered_map> formatDescriptions; + bool canRead, canWrite; + }; + + const ComponentInfo* fileType(UInt32 type) const; + std::set types(bool writableTypes) const; + + std::set typesForMIME(const char* mime) const; + std::set typesForUTI(const char* uti) const; + std::set typesForExtension(const char* ext) const; + + std::set allMIMEs() const; + std::set allUTIs() const; + std::set allExtensions() const; private: AudioFileFormatManager(); void buildDatabase(); void analyzeAudioFileComponent(AudioFileComponent component); -public: - static AudioFileFormatManager* instance(); + void registerComponent(const ComponentInfo& ci); + static void addToMap(std::unordered_map>& map, const std::string& key, ComponentInfo* ci); + static std::set findTypes(const std::unordered_map>& map, const char* key); + + template + std::set allKeys(const std::unordered_map& map) const + { + std::set rv; + for (auto const& [k, v] : map) + rv.insert(k); + return rv; + } +private: + std::list m_components; + std::unordered_map m_fileTypeMap; + std::unordered_map> m_mimeMap, m_utiMap, m_extensionMap; }; #endif