2005-06-02 14:16:43 -04:00
|
|
|
//
|
|
|
|
// VorbisFile.m
|
|
|
|
// zyVorbis
|
|
|
|
//
|
|
|
|
// Created by Vincent Spader on 1/22/05.
|
2005-07-02 17:02:06 -04:00
|
|
|
// Copyright 2005 Vincent Spader All rights reserved.
|
2005-06-02 14:16:43 -04:00
|
|
|
//
|
|
|
|
|
2007-02-24 17:36:27 -03:00
|
|
|
#import "VorbisDecoder.h"
|
2005-06-02 14:16:43 -04:00
|
|
|
|
2013-10-11 09:03:55 -03:00
|
|
|
#import "Logging.h"
|
2005-06-02 14:16:43 -04:00
|
|
|
|
2022-02-09 18:44:50 -03:00
|
|
|
#import "HTTPSource.h"
|
|
|
|
|
2007-02-24 17:36:27 -03:00
|
|
|
@implementation VorbisDecoder
|
2005-06-02 14:16:43 -04:00
|
|
|
|
2021-12-28 05:54:28 -03:00
|
|
|
static const int MAXCHANNELS = 8;
|
|
|
|
static const int chmap[MAXCHANNELS][MAXCHANNELS] = {
|
2022-02-07 02:49:27 -03:00
|
|
|
{
|
|
|
|
0,
|
|
|
|
}, // mono
|
|
|
|
{
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
}, // l, r
|
|
|
|
{
|
|
|
|
0,
|
|
|
|
2,
|
|
|
|
1,
|
|
|
|
}, // l, c, r -> l, r, c
|
|
|
|
{
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
2,
|
|
|
|
3,
|
|
|
|
}, // l, r, bl, br
|
|
|
|
{
|
|
|
|
0,
|
|
|
|
2,
|
|
|
|
1,
|
|
|
|
3,
|
|
|
|
4,
|
|
|
|
}, // l, c, r, bl, br -> l, r, c, bl, br
|
|
|
|
{ 0, 2, 1, 5, 3, 4 }, // l, c, r, bl, br, lfe -> l, r, c, lfe, bl, br
|
|
|
|
{ 0, 2, 1, 6, 5, 3, 4 }, // l, c, r, sl, sr, bc, lfe -> l, r, c, lfe, bc, sl, sr
|
|
|
|
{ 0, 2, 1, 7, 5, 6, 3, 4 } // l, c, r, sl, sr, bl, br, lfe -> l, r, c, lfe, bl, br, sl, sr
|
2021-12-28 05:54:28 -03:00
|
|
|
};
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
size_t sourceRead(void *buf, size_t size, size_t nmemb, void *datasource) {
|
2016-05-05 17:05:39 -03:00
|
|
|
id source = (__bridge id)datasource;
|
2007-03-01 22:36:52 -03:00
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
return [source read:buf amount:(size * nmemb)];
|
2007-03-01 22:36:52 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
int sourceSeek(void *datasource, ogg_int64_t offset, int whence) {
|
2016-05-05 17:05:39 -03:00
|
|
|
id source = (__bridge id)datasource;
|
2007-03-01 22:36:52 -03:00
|
|
|
return ([source seek:offset whence:whence] ? 0 : -1);
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
int sourceClose(void *datasource) {
|
2007-03-01 22:36:52 -03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
long sourceTell(void *datasource) {
|
2016-05-05 17:05:39 -03:00
|
|
|
id source = (__bridge id)datasource;
|
2007-03-01 22:36:52 -03:00
|
|
|
|
|
|
|
return [source tell];
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (BOOL)open:(id<CogSource>)s {
|
2016-05-05 17:05:39 -03:00
|
|
|
source = s;
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2007-03-01 22:36:52 -03:00
|
|
|
ov_callbacks callbacks = {
|
2022-02-07 02:49:27 -03:00
|
|
|
.read_func = sourceRead,
|
|
|
|
.seek_func = sourceSeek,
|
|
|
|
.close_func = sourceClose,
|
|
|
|
.tell_func = sourceTell
|
2007-03-01 22:36:52 -03:00
|
|
|
};
|
2022-02-07 02:49:27 -03:00
|
|
|
|
|
|
|
if(ov_open_callbacks((__bridge void *)(source), &vorbisRef, NULL, 0, callbacks) != 0) {
|
2013-10-11 09:03:55 -03:00
|
|
|
DLog(@"FAILED TO OPEN VORBIS FILE");
|
2005-06-02 14:16:43 -04:00
|
|
|
return NO;
|
2007-03-01 22:36:52 -03:00
|
|
|
}
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2005-06-02 14:16:43 -04:00
|
|
|
vorbis_info *vi;
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2005-06-02 14:16:43 -04:00
|
|
|
vi = ov_info(&vorbisRef, -1);
|
2022-02-07 02:49:27 -03:00
|
|
|
|
|
|
|
bitrate = (vi->bitrate_nominal / 1000.0);
|
2007-02-24 17:36:27 -03:00
|
|
|
channels = vi->channels;
|
2005-06-02 14:16:43 -04:00
|
|
|
frequency = vi->rate;
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2007-03-01 22:36:52 -03:00
|
|
|
seekable = ov_seekable(&vorbisRef);
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2007-11-24 17:16:27 -03:00
|
|
|
totalFrames = ov_pcm_total(&vorbisRef, -1);
|
2005-06-02 14:16:43 -04:00
|
|
|
|
2007-03-03 14:19:37 -03:00
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
|
|
[self didChangeValueForKey:@"properties"];
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2022-02-09 18:44:50 -03:00
|
|
|
genre = @"";
|
|
|
|
album = @"";
|
|
|
|
artist = @"";
|
|
|
|
title = @"";
|
|
|
|
[self updateMetadata];
|
|
|
|
|
2005-06-02 14:16:43 -04:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2022-02-09 18:44:50 -03:00
|
|
|
- (void)updateMetadata {
|
2022-02-09 20:04:49 -03:00
|
|
|
if([source seekable]) return;
|
|
|
|
|
2022-02-09 18:44:50 -03:00
|
|
|
const vorbis_comment *comment = ov_comment(&vorbisRef, -1);
|
|
|
|
|
|
|
|
if(comment) {
|
|
|
|
uint8_t nullByte = '\0';
|
|
|
|
NSString *_genre = genre;
|
|
|
|
NSString *_album = album;
|
|
|
|
NSString *_artist = artist;
|
|
|
|
NSString *_title = title;
|
|
|
|
for(int i = 0; i < comment->comments; ++i) {
|
|
|
|
NSMutableData *commentField = [NSMutableData dataWithBytes:comment->user_comments[i] length:comment->comment_lengths[i]];
|
|
|
|
[commentField appendBytes:&nullByte length:1];
|
|
|
|
NSString *commentString = [NSString stringWithUTF8String:[commentField bytes]];
|
|
|
|
NSArray *splitFields = [commentString componentsSeparatedByString:@"="];
|
|
|
|
if([splitFields count] == 2) {
|
|
|
|
NSString *name = [splitFields objectAtIndex:0];
|
|
|
|
NSString *value = [splitFields objectAtIndex:1];
|
|
|
|
name = [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
|
|
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
|
|
name = [name lowercaseString];
|
|
|
|
if([name isEqualToString:@"genre"]) {
|
|
|
|
_genre = value;
|
|
|
|
} else if([name isEqualToString:@"album"]) {
|
|
|
|
_album = value;
|
|
|
|
} else if([name isEqualToString:@"artist"]) {
|
|
|
|
_artist = value;
|
|
|
|
} else if([name isEqualToString:@"title"]) {
|
|
|
|
_title = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(![_genre isEqual:genre] ||
|
|
|
|
![_album isEqual:album] ||
|
|
|
|
![_artist isEqual:artist] ||
|
|
|
|
![_title isEqual:title]) {
|
|
|
|
genre = _genre;
|
|
|
|
album = _album;
|
|
|
|
artist = _artist;
|
|
|
|
title = _title;
|
|
|
|
[self willChangeValueForKey:@"metadata"];
|
|
|
|
[self didChangeValueForKey:@"metadata"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateIcyMetadata {
|
2022-02-09 20:04:49 -03:00
|
|
|
if([source seekable]) return;
|
|
|
|
|
2022-02-09 18:44:50 -03:00
|
|
|
NSString *_genre = genre;
|
|
|
|
NSString *_album = album;
|
|
|
|
NSString *_artist = artist;
|
|
|
|
NSString *_title = title;
|
|
|
|
|
|
|
|
Class sourceClass = [source class];
|
|
|
|
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
|
|
|
HTTPSource *httpSource = (HTTPSource *)source;
|
|
|
|
if([httpSource hasMetadata]) {
|
|
|
|
NSDictionary *metadata = [httpSource metadata];
|
|
|
|
_genre = [metadata valueForKey:@"genre"];
|
|
|
|
_album = [metadata valueForKey:@"album"];
|
|
|
|
_artist = [metadata valueForKey:@"artist"];
|
|
|
|
_title = [metadata valueForKey:@"title"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(![_genre isEqual:genre] ||
|
|
|
|
![_album isEqual:album] ||
|
|
|
|
![_artist isEqual:artist] ||
|
|
|
|
![_title isEqual:title]) {
|
|
|
|
genre = _genre;
|
|
|
|
album = _album;
|
|
|
|
artist = _artist;
|
|
|
|
title = _title;
|
|
|
|
[self willChangeValueForKey:@"metadata"];
|
|
|
|
[self didChangeValueForKey:@"metadata"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames {
|
2005-06-02 14:16:43 -04:00
|
|
|
int numread;
|
2007-03-03 20:05:15 -03:00
|
|
|
int total = 0;
|
2022-02-07 02:49:27 -03:00
|
|
|
|
|
|
|
if(currentSection != lastSection) {
|
2007-03-03 19:55:26 -03:00
|
|
|
vorbis_info *vi;
|
|
|
|
vi = ov_info(&vorbisRef, -1);
|
2022-02-07 02:49:27 -03:00
|
|
|
|
|
|
|
bitrate = (vi->bitrate_nominal / 1000.0);
|
2007-03-03 19:55:26 -03:00
|
|
|
channels = vi->channels;
|
|
|
|
frequency = vi->rate;
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2007-03-03 19:55:26 -03:00
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
|
|
[self didChangeValueForKey:@"properties"];
|
2022-02-09 18:44:50 -03:00
|
|
|
|
|
|
|
[self updateMetadata];
|
2007-03-03 19:55:26 -03:00
|
|
|
}
|
2022-02-07 02:49:27 -03:00
|
|
|
|
|
|
|
do {
|
2007-03-03 20:05:15 -03:00
|
|
|
lastSection = currentSection;
|
2022-02-07 02:49:27 -03:00
|
|
|
float **pcm;
|
|
|
|
numread = (int)ov_read_float(&vorbisRef, &pcm, frames - total, ¤tSection);
|
|
|
|
if(numread > 0) {
|
|
|
|
if(channels <= MAXCHANNELS) {
|
|
|
|
for(int i = 0; i < channels; i++) {
|
|
|
|
for(int j = 0; j < numread; j++) {
|
|
|
|
((float *)buf)[(total + j) * channels + i] = pcm[chmap[channels - 1][i]][j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for(int i = 0; i < channels; i++) {
|
|
|
|
for(int j = 0; j < numread; j++) {
|
|
|
|
((float *)buf)[(total + j) * channels + i] = pcm[i][j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-03-03 20:05:15 -03:00
|
|
|
total += numread;
|
2007-03-03 19:55:26 -03:00
|
|
|
}
|
2022-02-07 02:49:27 -03:00
|
|
|
|
|
|
|
if(currentSection != lastSection) {
|
2007-03-03 20:05:15 -03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
} while(total != frames && numread != 0);
|
|
|
|
|
2022-02-09 18:44:50 -03:00
|
|
|
[self updateIcyMetadata];
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
return total;
|
2005-06-02 14:16:43 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)close {
|
2005-06-02 14:16:43 -04:00
|
|
|
ov_clear(&vorbisRef);
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)dealloc {
|
|
|
|
[self close];
|
2016-06-19 15:57:18 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (long)seek:(long)frame {
|
2007-11-24 17:16:27 -03:00
|
|
|
ov_pcm_seek(&vorbisRef, frame);
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2007-11-24 17:16:27 -03:00
|
|
|
return frame;
|
2005-06-02 14:16:43 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (NSDictionary *)properties {
|
2022-02-09 00:42:03 -03:00
|
|
|
return @{@"channels": [NSNumber numberWithInt:channels],
|
|
|
|
@"bitsPerSample": [NSNumber numberWithInt:32],
|
|
|
|
@"floatingPoint": [NSNumber numberWithBool:YES],
|
|
|
|
@"sampleRate": [NSNumber numberWithFloat:frequency],
|
|
|
|
@"totalFrames": [NSNumber numberWithDouble:totalFrames],
|
|
|
|
@"bitrate": [NSNumber numberWithInt:bitrate],
|
|
|
|
@"seekable": [NSNumber numberWithBool:([source seekable] && seekable)],
|
|
|
|
@"codec": @"Ogg Vorbis",
|
|
|
|
@"endian": @"host",
|
|
|
|
@"encoding": @"lossy"};
|
2007-02-24 17:36:27 -03:00
|
|
|
}
|
|
|
|
|
2022-02-09 00:56:39 -03:00
|
|
|
- (NSDictionary *)metadata {
|
2022-02-09 18:44:50 -03:00
|
|
|
return @{ @"genre": genre, @"album": album, @"artist": artist, @"title": title };
|
2022-02-09 00:56:39 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
+ (NSArray *)fileTypes {
|
2022-01-18 23:12:57 -03:00
|
|
|
return @[@"ogg"];
|
2007-02-24 17:36:27 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
+ (NSArray *)mimeTypes {
|
2022-02-09 18:44:50 -03:00
|
|
|
return @[@"application/ogg", @"application/x-ogg", @"audio/ogg", @"audio/x-vorbis+ogg"];
|
2007-10-14 15:39:58 -03:00
|
|
|
}
|
2007-02-24 17:36:27 -03:00
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
+ (float)priority {
|
|
|
|
return 1.0;
|
Implemented support for multiple decoders per file name extension, with a floating point priority control per interface. In the event that more than one input is registered to a given extension, and we match that extension, it will be passed off to an instance of the multi-decoder wrapper, which will try opening the file with all of the decoders in order of priority, until either one of them accepts it, or all of them have failed. This paves the way for adding a VGMSTREAM input, so I can give it a very low priority, since it has several formats that are verified by file name extension only. All current inputs have been given a priority of 1.0, except for CoreAudio, which was given a priority of 0.5, because it contains an MP3 and AC3 decoders that I'd rather not use if I don't have to.
2013-10-21 14:54:11 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
+ (NSArray *)fileTypeAssociations {
|
|
|
|
return @[
|
|
|
|
@[@"Ogg Vorbis File", @"ogg.icns", @"ogg"]
|
|
|
|
];
|
2022-01-18 08:06:03 -03:00
|
|
|
}
|
|
|
|
|
2005-06-02 14:16:43 -04:00
|
|
|
@end
|