Well, I can tell you that I've grown to really dislike the Core Foundation API.

As you've found, AVAudioPlayer makes it really easy to play a sound. Getting information about the sound, though, is a bit more sticky. You've probably noticed that AVAudioPlayer doesn't support this at all, so we have to look elsewhere.
The
Audio Toolbox Framework Reference provides over 200 pages of Core Foundation-based API.
To recap (since we'll be using a little bit of that code), we can set up the AVAudioPlayer with:
- Code: Select all
//assume 'player' is an instance variable declared as AVAudioPlayer * player,
//with typical @property and @synthesize already done.
NSString * soundFilePath = [[NSBundle mainBundle] pathForResource:@"LadyOfRome" ofType:@"mp3"];
// we get an autoreleased NSURL ptr because we're going to use it further down this routine as well,
// and we don't want to forget the release
NSURL * fileURL = [NSURL fileURLWithPath: soundFilePath];
AVAudioPlayer * audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
[self setPlayer: audioPlayer];
[audioPlayer release];
[player setDelegate: self];
[player prepareToPlay];
So far, so good. Cocoa rocks.

Now we get to the CF-based code. It doesn't start off too badly.
- Code: Select all
AudioFileID fileID = nil;
OSStatus err = noErr;
err = AudioFileOpenURL( (CFURLRef) fileURL, kAudioFileReadPermission, 0, &fileID );
if( err != noErr ) {
NSLog( @"AudioFileOpenURL failed" );
}
Some CF types are "toll-free bridged" to their Cocoa counterparts, meaning we can just cast one to use the other. This comes in really handy above, where we just cast our NSURL pointer to its CF counterpart.
That AudioFileID is going to be used for several other calls. All routines return an OSStatus code indicating whether or not an error was encountered. Let's get cracking with the ID3 info.
We get the raw ID3 data in two steps. First we tell it what we want, and we get back the amount of memory we need to allocate ourselves. Then we allocate the memory and ask for the actual data.
- Code: Select all
UInt32 id3DataSize = 0;
char * rawID3Tag = NULL;
err = AudioFileGetPropertyInfo( fileID, kAudioFilePropertyID3Tag, &id3DataSize, NULL );
if( err != noErr ) {
NSLog( @"AudioFileGetPropertyInfo failed for ID3 tag" );
}
NSLog( @"id3 data size is %d bytes", id3DataSize );
rawID3Tag = (char *) malloc( id3DataSize );
if( rawID3Tag == NULL ) {
NSLog( @"could not allocate %d bytes of memory for ID3 tag", id3DataSize );
}
err = AudioFileGetProperty( fileID, kAudioFilePropertyID3Tag, &id3DataSize, rawID3Tag );
if( err != noErr ) {
NSLog( @"AudioFileGetProperty failed for ID3 tag" );
}
NSLog( @"read %d bytes of ID3 info", id3DataSize );
To prove to ourselves that we really do have the raw ID3 data, we can display the first 100 bytes of data, substituting a dot (.) for each unprintable character:
- Code: Select all
int ilim = 100;
if( ilim > id3DataSize ) {
ilim = id3DataSize;
}
for( int i=0; i < ilim; i++ ) {
if( rawID3Tag[i] < 32 ) printf( "." );
else printf( "%c", rawID3Tag[i] );
}
printf( "\n" );
Running this on my machine (and with my .mp3

) yields:
- Code: Select all
id3 data size is 334444 bytes
read 334444 bytes of ID3 info
ID3.....4bTT2.. .Lady Of Rome, Sister Of Brazil.TP1....Al Di Meola.TAL....Elegant Gypsy.TRK....5/6.T
So far, so good. And then...failure.
AudioFormatGetProperty looks like it has a property which interprets the raw ID3 tag, but doggone if I couldn't get anything reasonable out of this routine. There are two ID3-related properties. One is supposed to return the ID3 tag size, and the other a dictionary with the values parsed out. Even just getting the tag size failed:
- Code: Select all
UInt32 id3TagSize = 0;
UInt32 id3TagSizeLength = 0;
err = AudioFormatGetProperty( kAudioFormatProperty_ID3TagSize,
id3DataSize,
rawID3Tag,
&id3TagSizeLength,
&id3TagSize
);
if( err != noErr ) {
NSLog( @"AudioFormatGetProperty failed for ID3 tag size" );
switch( err ) {
case kAudioFormatUnspecifiedError:
NSLog( @"err: audio format unspecified error" );
break;
case kAudioFormatUnsupportedPropertyError:
NSLog( @"err: audio format unsupported property error" );
break;
case kAudioFormatBadPropertySizeError:
NSLog( @"err: audio format bad property size error" );
break;
case kAudioFormatBadSpecifierSizeError:
NSLog( @"err: audio format bad specifier size error" );
break;
case kAudioFormatUnsupportedDataFormatError:
NSLog( @"err: audio format unsupported data format error" );
break;
case kAudioFormatUnknownFormatError:
NSLog( @"err: audio format unknown format error" );
break;
default:
NSLog( @"err: some other audio format error" );
break;
}
}
NSLog( @"id3 tag size is %d bytes", id3TagSize );
For the longest time, this returned the supposedly-impossible "some other audio format error". Now it returns "unsupported property error." As far as ID3-specific info is concerned, I don't know where to go from here.
However, all is not lost.
The Audio File Services which worked for us above can return its own property info dictionary:
- Code: Select all
CFDictionaryRef piDict = nil;
UInt32 piDataSize = sizeof( piDict );
err = AudioFileGetProperty( fileID, kAudioFilePropertyInfoDictionary, &piDataSize, &piDict );
if( err != noErr ) {
NSLog( @"AudioFileGetProperty failed for property info dictionary" );
}
We can dump this dictionary either with Cocoa (nice) or with CF (ugly, but shows the types):
- Code: Select all
NSLog( @"property info: %@", (NSDictionary*)piDict );
CFShow( piDict );
For me, this shows:
- Code: Select all
property info: {
album = "Elegant Gypsy";
"approximate duration in seconds" = 105;
artist = "Al Di Meola";
"encoding application" = "iTunes v4.1";
genre = Jazz;
title = "Lady Of Rome, Sister Of Brazil";
"track number" = "5/6";
year = 1977;
}
<CFDictionary 0x553e80 [0xa06f11a0]>{type = mutable, count = 8, capacity = 12, pairs = (
1 : <CFString 0x349afea4 [0xa06f11a0]>{contents = "track number"} = <CFString 0x53c4f0 [0xa06f11a0]>{contents = "5/6"}
5 : <CFString 0x349afeb4 [0xa06f11a0]>{contents = "year"} = <CFString 0x540ad0 [0xa06f11a0]>{contents = "1977"}
8 : <CFString 0x349afe74 [0xa06f11a0]>{contents = "artist"} = <CFString 0x553980 [0xa06f11a0]>{contents = "Al Di Meola"}
9 : <CFString 0x349aff14 [0xa06f11a0]>{contents = "genre"} = <CFString 0x554040 [0xa06f11a0]>{contents = "Jazz"}
10 : <CFString 0x349afe64 [0xa06f11a0]>{contents = "album"} = <CFString 0x553fe0 [0xa06f11a0]>{contents = "Elegant Gypsy"}
11 : <CFString 0x349afee4 [0xa06f11a0]>{contents = "title"} = <CFString 0x553b30 [0xa06f11a0]>{contents = "Lady Of Rome, Sister Of Brazil"}
12 : <CFString 0x349aff04 [0xa06f11a0]>{contents = "encoding application"} = <CFString 0x5537a0 [0xa06f11a0]>{contents = "iTunes v4.1"}
13 : <CFString 0x349b0274 [0xa06f11a0]>{contents = "approximate duration in seconds"} = <CFString 0x553db0 [0xa06f11a0]>{contents = "105"}
)}
Accessing the properties using the Apple-supplied keys (look for the kAFInfoDictionary prefix) it's a pain either way:
- Code: Select all
// Cocoa
NSString * genre = [(NSDictionary*)piDict objectForKey:
[NSString stringWithUTF8String: kAFInfoDictionary_Genre]
];
// CF
// it sure would've been nice if they had DOCUMENTED that CFSTR needed to be used
NSString * genreString = (NSString *)CFDictionaryGetValue( piDict, CFSTR( kAFInfoDictionary_Genre ));
In the end, we need to release the property info dictionary, as well as to free the ID3 area we created.
- Code: Select all
CFRelease( piDict );
free( rawID3Tag );
Anyway, that should get you started.
(Updated 3 April to address some grammatical errors.)