7

我使用 AVAudioPlayer 制作了自定义播放器。现在,我想获取添加到资源文件夹中的音频文件的详细信息,例如艺术家姓名、专辑名称等。

MPMusicPlayer 提供了获取详细信息的 API,但它使用 iPod 库并且它不从应用程序的沙箱中获取资源。所以,MPMusicPlayer 在那种情况下是行不通的。

那么,我们如何在 iPhone 中获取音频文件的详细信息。

4

2 回答 2

14

You can get this information through the AudioToolbox.framework. The AudioToolbox.framework is a C API, so I wrote an Objective-C wrapper for it:

ID3Tag .h:

@interface ID3Tag : NSObject <NSCoding> {
    NSString* title_;
    NSString* album_;
    NSString* artist_;
    NSNumber* trackNumber_;
    NSNumber* totalTracks_;
    NSString* genre_;
    NSString* year_;
    NSNumber* approxDuration_;
    NSString* composer_;
    NSString* tempo_;
    NSString* keySignature_;
    NSString* timeSignature_;
    NSString* lyricist_;
    NSString* recordedDate_;
    NSString* comments_;
    NSString* copyright_;
    NSString* sourceEncoder_;
    NSString* encodingApplication_;
    NSString* bitRate_;
    NSStream* sourceBitRate_;
    NSString* channelLayout_;
    NSString* isrc_;
    NSString* subtitle_;  
}

@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *album;
@property (nonatomic, retain) NSString *artist;
@property (nonatomic, retain) NSNumber *trackNumber;
@property (nonatomic, retain) NSNumber *totalTracks;
@property (nonatomic, retain) NSString *genre;
@property (nonatomic, retain) NSString *year;
@property (nonatomic, retain) NSNumber *approxDuration;
@property (nonatomic, retain) NSString *composer;
@property (nonatomic, retain) NSString *tempo;
@property (nonatomic, retain) NSString *keySignature;
@property (nonatomic, retain) NSString *timeSignature;
@property (nonatomic, retain) NSString *lyricist;
@property (nonatomic, retain) NSString *recordedDate;
@property (nonatomic, retain) NSString *comments;
@property (nonatomic, retain) NSString *copyright;
@property (nonatomic, retain) NSString *sourceEncoder;
@property (nonatomic, retain) NSString *encodingApplication;
@property (nonatomic, retain) NSString *bitRate;
@property (nonatomic, retain) NSStream *sourceBitRate;
@property (nonatomic, retain) NSString *channelLayout;
@property (nonatomic, retain) NSString *isrc;
@property (nonatomic, retain) NSString *subtitle;

@end

ID3TagParser.h

#import <Foundation/Foundation.h>
#import "ID3Tag.h"

@interface ID3Parser : NSObject {

}

- (ID3Tag*) parseAudioFileForID3Tag:(NSURL*) url;

@end

ID3TagParser.m

#import "ID3Parser.h"
#import <AudioToolbox/AudioToolbox.h>

@implementation ID3Parser

- (ID3Tag*) parseAudioFileForID3Tag:(NSURL*) url {
    if (url == nil) {
        return nil;
    }

    AudioFileID fileID  = nil;
    OSStatus err = noErr;

    err = AudioFileOpenURL( (CFURLRef) url, kAudioFileReadPermission, 0, &fileID );
    if( err != noErr ) {
        NSLog( @"AudioFileOpenURL failed" );
        return nil;
    } else {
        UInt32 id3DataSize = 0;
        char* rawID3Tag = NULL;

        //  Reads in the raw ID3 tag info
        err = AudioFileGetPropertyInfo(fileID, kAudioFilePropertyID3Tag, &id3DataSize, NULL);
        if(err != noErr) {
            return nil;
        }

        //  Allocate the raw tag data
        rawID3Tag = (char *) malloc(id3DataSize);

        if(rawID3Tag == NULL) {
            return nil;
        }

        err = AudioFileGetProperty(fileID, kAudioFilePropertyID3Tag, &id3DataSize, rawID3Tag);
        if(err != noErr) {
            return nil;
        }

        UInt32 id3TagSize = 0;
        UInt32 id3TagSizeLength = 0;
        err = AudioFormatGetProperty(kAudioFormatProperty_ID3TagSize, id3DataSize, rawID3Tag, &id3TagSizeLength, &id3TagSize);

        if(err != noErr) {
            switch(err) {
                case kAudioFormatUnspecifiedError:
                    NSLog(@"err: audio format unspecified error");
                    return nil;
                case kAudioFormatUnsupportedPropertyError:
                    NSLog(@"err: audio format unsupported property error");
                    return nil;
                case kAudioFormatBadPropertySizeError:
                    NSLog(@"err: audio format bad property size error"); 
                    return nil;
                case kAudioFormatBadSpecifierSizeError:
                    NSLog(@"err: audio format bad specifier size error"); 
                    return nil;
                case kAudioFormatUnsupportedDataFormatError:
                    NSLog(@"err: audio format unsupported data format error"); 
                    return nil;
                case kAudioFormatUnknownFormatError:
                    NSLog(@"err: audio format unknown format error");
                    return nil;
                default:
                    NSLog(@"err: some other audio format error"); 
                    return nil;
            }
        }

        CFDictionaryRef piDict = nil;
        UInt32 piDataSize = sizeof(piDict);

        //  Populates a CFDictionary with the ID3 tag properties
        err = AudioFileGetProperty(fileID, kAudioFilePropertyInfoDictionary, &piDataSize, &piDict);
        if(err != noErr) {
            NSLog(@"AudioFileGetProperty failed for property info dictionary");
            return nil;
        }

        //  Toll free bridge the CFDictionary so that we can interact with it via objc
        NSDictionary* nsDict = (NSDictionary*)piDict;

        ID3Tag* tag = [[[ID3Tag alloc] init] autorelease];

        tag.album = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Album]];
        tag.approxDuration = [NSNumber numberWithInt:[[nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_ApproximateDurationInSeconds]] intValue]];
        tag.artist = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Artist]];
        tag.bitRate = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_NominalBitRate]];
        tag.channelLayout = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_ChannelLayout]];
        tag.comments = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Comments]];
        tag.composer = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Composer]];
        tag.copyright = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Copyright]];
        tag.encodingApplication = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_EncodingApplication]];
        tag.genre = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Genre]];
        tag.isrc = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_ISRC]];
        tag.keySignature = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_KeySignature]];
        tag.lyricist = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Lyricist]];
        tag.recordedDate = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_RecordedDate]];
        tag.sourceBitRate = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_SourceBitDepth]];
        tag.sourceEncoder = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_SourceEncoder]];
        tag.subtitle = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_SubTitle]];
        tag.tempo = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Tempo]];
        tag.timeSignature = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_TimeSignature]];
        tag.title = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Title]];
        tag.year = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_Year]];

        /*  
         *  We're going to parse tracks differently so that we can perform queries on the data. This means we need to look
         *  for a '/' so that we can seperate out the track from the total tracks on the source compilation (if it's there).
         */
        NSString* tracks = [nsDict objectForKey:[NSString stringWithUTF8String: kAFInfoDictionary_TrackNumber]];

        int slashLocation = [tracks rangeOfString:@"/"].location;

        if (slashLocation == NSNotFound) {
            tag.trackNumber = [NSNumber numberWithInt:[tracks intValue]];
        } else {
            tag.trackNumber = [NSNumber numberWithInt:[[tracks substringToIndex:slashLocation] intValue]];
            tag.totalTracks = [NSNumber numberWithInt:[[tracks substringFromIndex:(slashLocation+1 < [tracks length] ? slashLocation+1 : 0 )] intValue]];
        }

        //  ALWAYS CLEAN UP!
        CFRelease(piDict);
        nsDict = nil;
        free(rawID3Tag);

        return tag;
    }
}

@end
于 2011-11-23T20:55:26.877 回答
8

我看到韦恩发布的标签阅读代码被复制粘贴太多了。如果使用kAudioFilePropertyInfoDictionary,则不需要 id3DataSize,不需要读取原始 ID3Tag 数据,并且 id3TagSizeLength id3TagSize 也不使用。只需打开文件并阅读标签:

- (NSDictionary *)id3TagsForURL:(NSURL *)resourceUrl
{
    AudioFileID fileID;
    OSStatus result = AudioFileOpenURL((CFURLRef)resourceUrl, kAudioFileReadPermission, 0, &fileID);

    if (result != noErr) {
        NSLog(@"Error reading tags: %li", result);
        return nil;
    }

    CFDictionaryRef piDict = nil;
    UInt32 piDataSize = sizeof(piDict);

    result = AudioFileGetProperty(fileID, kAudioFilePropertyInfoDictionary, &piDataSize, &piDict);
    if (result != noErr)
        NSLog(@"Error reading tags. AudioFileGetProperty failed");

    AudioFileClose(fileID);

    NSDictionary *tagsDictionary = [NSDictionary dictionaryWithDictionary:(NSDictionary*)piDict];
    CFRelease(piDict);

    return tagsDictionary;
}

字典键在 AudioFile.h 中定义,从 kAFInfoDictionary 开始。

但是,还有另一个标签读取键,它将返回不同的结果(使用其他键):

- (NSDictionary *)id3TagsForURL:(NSURL *)resourceUrl
{
    AudioFileID fileID;
    OSStatus result = AudioFileOpenURL((CFURLRef)resourceUrl, kAudioFileReadPermission, 0, &fileID);

    if (result != noErr) {
        return nil;
    }

    //read raw ID3Tag size
    UInt32 id3DataSize = 0;
    char *rawID3Tag = NULL;
    result = AudioFileGetPropertyInfo(fileID, kAudioFilePropertyID3Tag, &id3DataSize, NULL);
    if (result != noErr) {
        AudioFileClose(fileID);
        return nil;
    }

    rawID3Tag = (char *)malloc(id3DataSize);

    //read raw ID3Tag
    result = AudioFileGetProperty(fileID, kAudioFilePropertyID3Tag, &id3DataSize, rawID3Tag);
    if (result != noErr) {
        free(rawID3Tag);
        AudioFileClose(fileID);
        return nil;
    }

    CFDictionaryRef piDict = nil;
    UInt32 piDataSize = sizeof(piDict);

    //this key returns some other dictionary, which works also in iPod library
    result = AudioFormatGetProperty(kAudioFormatProperty_ID3TagToDictionary, id3DataSize, rawID3Tag, &piDataSize, &piDict);
    if (result != noErr) {
        return nil;
    }

    free(rawID3Tag);
    AudioFileClose(fileID);

    NSDictionary *tagsDictionary = [NSDictionary dictionaryWithDictionary:(NSDictionary*)piDict];
    CFRelease(piDict);

    return tagsDictionary;
}
于 2013-07-04T21:07:55.430 回答