2005-06-02 18:16:43 +00:00
|
|
|
//
|
2007-02-24 20:36:27 +00:00
|
|
|
// FlacDecoder.m
|
2005-06-02 18:16:43 +00:00
|
|
|
// zyVorbis
|
|
|
|
//
|
|
|
|
// Created by Vincent Spader on 1/25/05.
|
2005-07-02 21:02:06 +00:00
|
|
|
// Copyright 2005 Vincent Spader All rights reserved.
|
2005-06-02 18:16:43 +00:00
|
|
|
//
|
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
#import "FlacDecoder.h"
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
#import "Logging.h"
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2022-02-09 21:44:50 +00:00
|
|
|
#import "HTTPSource.h"
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2022-07-08 13:26:28 +00:00
|
|
|
#import "NSDictionary+Merge.h"
|
|
|
|
|
2022-02-12 15:16:59 +00:00
|
|
|
extern void grabbag__cuesheet_emit(NSString **out, const FLAC__StreamMetadata *cuesheet, const char *file_reference);
|
|
|
|
|
2022-02-09 21:44:50 +00:00
|
|
|
@implementation FlacDecoder
|
2022-02-07 10:06:51 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
FLAC__StreamDecoderReadStatus ReadCallback(const FLAC__StreamDecoder *decoder, FLAC__byte blockBuffer[], size_t *bytes, void *client_data) {
|
2016-05-05 20:05:39 +00:00
|
|
|
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
2022-02-07 05:49:27 +00:00
|
|
|
long bytesRead = [[flacDecoder source] read:blockBuffer amount:*bytes];
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
if(bytesRead < 0) {
|
2022-02-07 05:49:27 +00:00
|
|
|
*bytes = 0;
|
2007-03-04 21:32:03 +00:00
|
|
|
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
2022-02-07 05:49:27 +00:00
|
|
|
} else if(bytesRead == 0) {
|
|
|
|
*bytes = 0;
|
2007-03-04 21:32:03 +00:00
|
|
|
[flacDecoder setEndOfStream:YES];
|
|
|
|
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
2022-02-07 05:49:27 +00:00
|
|
|
} else {
|
|
|
|
*bytes = bytesRead;
|
2007-03-04 21:32:03 +00:00
|
|
|
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
FLAC__StreamDecoderSeekStatus SeekCallback(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) {
|
2016-05-05 20:05:39 +00:00
|
|
|
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-03-04 21:32:03 +00:00
|
|
|
if(![[flacDecoder source] seek:absolute_byte_offset whence:SEEK_SET])
|
|
|
|
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
|
|
|
|
else
|
|
|
|
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
FLAC__StreamDecoderTellStatus TellCallback(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) {
|
2016-05-05 20:05:39 +00:00
|
|
|
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
2007-03-04 21:32:03 +00:00
|
|
|
|
|
|
|
off_t pos;
|
|
|
|
if((pos = [[flacDecoder source] tell]) < 0)
|
|
|
|
return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
|
|
|
|
else {
|
|
|
|
*absolute_byte_offset = (FLAC__uint64)pos;
|
|
|
|
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
FLAC__bool EOFCallback(const FLAC__StreamDecoder *decoder, void *client_data) {
|
2016-05-05 20:05:39 +00:00
|
|
|
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-03-04 21:32:03 +00:00
|
|
|
return (FLAC__bool)[flacDecoder endOfStream];
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
FLAC__StreamDecoderLengthStatus LengthCallback(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) {
|
2016-05-05 20:05:39 +00:00
|
|
|
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
|
|
|
if([[flacDecoder source] seekable]) {
|
2007-03-04 21:32:03 +00:00
|
|
|
long currentPos = [[flacDecoder source] tell];
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-03-04 21:32:03 +00:00
|
|
|
[[flacDecoder source] seek:0 whence:SEEK_END];
|
|
|
|
*stream_length = [[flacDecoder source] tell];
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-03-04 21:32:03 +00:00
|
|
|
[[flacDecoder source] seek:currentPos whence:SEEK_SET];
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-03-04 21:32:03 +00:00
|
|
|
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
|
2022-02-07 05:49:27 +00:00
|
|
|
} else {
|
2007-03-04 21:32:03 +00:00
|
|
|
*stream_length = 0;
|
|
|
|
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
FLAC__StreamDecoderWriteStatus WriteCallback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const sampleblockBuffer[], void *client_data) {
|
2016-05-05 20:05:39 +00:00
|
|
|
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2022-02-09 21:44:50 +00:00
|
|
|
if(flacDecoder->abortFlag)
|
|
|
|
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
|
|
|
|
2022-02-08 03:18:45 +00:00
|
|
|
uint32_t channels = frame->header.channels;
|
|
|
|
uint32_t bitsPerSample = frame->header.bits_per_sample;
|
|
|
|
uint32_t frequency = frame->header.sample_rate;
|
|
|
|
|
|
|
|
if(channels != flacDecoder->channels ||
|
|
|
|
bitsPerSample != flacDecoder->bitsPerSample ||
|
|
|
|
frequency != flacDecoder->frequency) {
|
|
|
|
if(channels != flacDecoder->channels) {
|
|
|
|
flacDecoder->channelConfig = 0;
|
|
|
|
}
|
|
|
|
flacDecoder->channels = channels;
|
|
|
|
flacDecoder->bitsPerSample = bitsPerSample;
|
|
|
|
flacDecoder->frequency = frequency;
|
|
|
|
[flacDecoder willChangeValueForKey:@"properties"];
|
|
|
|
[flacDecoder didChangeValueForKey:@"properties"];
|
|
|
|
}
|
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
void *blockBuffer = [flacDecoder blockBuffer];
|
2007-02-27 23:56:52 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
int8_t *alias8;
|
2007-02-27 23:56:52 +00:00
|
|
|
int16_t *alias16;
|
|
|
|
int32_t *alias32;
|
|
|
|
int sample, channel;
|
2022-02-07 05:49:27 +00:00
|
|
|
int32_t audioSample;
|
|
|
|
|
|
|
|
switch(frame->header.bits_per_sample) {
|
|
|
|
case 8:
|
|
|
|
// Interleave the audio (no need for byte swapping)
|
|
|
|
alias8 = blockBuffer;
|
|
|
|
for(sample = 0; sample < frame->header.blocksize; ++sample) {
|
|
|
|
for(channel = 0; channel < frame->header.channels; ++channel) {
|
|
|
|
*alias8++ = (int8_t)sampleblockBuffer[channel][sample];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 16:
|
|
|
|
// Interleave the audio, converting to big endian byte order
|
|
|
|
alias16 = blockBuffer;
|
|
|
|
for(sample = 0; sample < frame->header.blocksize; ++sample) {
|
|
|
|
for(channel = 0; channel < frame->header.channels; ++channel) {
|
|
|
|
*alias16++ = (int16_t)OSSwapHostToBigInt16((int16_t)sampleblockBuffer[channel][sample]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 24:
|
|
|
|
// Interleave the audio (no need for byte swapping)
|
|
|
|
alias8 = blockBuffer;
|
|
|
|
for(sample = 0; sample < frame->header.blocksize; ++sample) {
|
|
|
|
for(channel = 0; channel < frame->header.channels; ++channel) {
|
|
|
|
audioSample = sampleblockBuffer[channel][sample];
|
|
|
|
*alias8++ = (int8_t)(audioSample >> 16);
|
|
|
|
*alias8++ = (int8_t)(audioSample >> 8);
|
|
|
|
*alias8++ = (int8_t)audioSample;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 32:
|
|
|
|
// Interleave the audio, converting to big endian byte order
|
|
|
|
alias32 = blockBuffer;
|
|
|
|
for(sample = 0; sample < frame->header.blocksize; ++sample) {
|
|
|
|
for(channel = 0; channel < frame->header.channels; ++channel) {
|
|
|
|
*alias32++ = OSSwapHostToBigInt32(sampleblockBuffer[channel][sample]);
|
|
|
|
}
|
|
|
|
}
|
2007-02-27 23:56:52 +00:00
|
|
|
default:
|
2022-02-07 05:49:27 +00:00
|
|
|
// Time for some nearest byte padding up to 32
|
|
|
|
alias8 = blockBuffer;
|
|
|
|
int sampleSize = frame->header.bits_per_sample;
|
|
|
|
int sampleBit;
|
|
|
|
for(sample = 0; sample < frame->header.blocksize; ++sample) {
|
|
|
|
for(channel = 0; channel < frame->header.channels; ++channel) {
|
|
|
|
int32_t sampleExtended = sampleblockBuffer[channel][sample];
|
|
|
|
for(sampleBit = sampleSize - 8; sampleBit >= -8; sampleBit -= 8) {
|
|
|
|
if(sampleBit >= 0)
|
|
|
|
*alias8++ = (uint8_t)((sampleExtended >> sampleBit) & 0xFF);
|
|
|
|
else
|
|
|
|
*alias8++ = (uint8_t)((sampleExtended << -sampleBit) & 0xFF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2007-02-27 23:56:52 +00:00
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
[flacDecoder setBlockBufferFrames:frame->header.blocksize];
|
2005-06-02 18:16:43 +00:00
|
|
|
|
|
|
|
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
|
|
|
}
|
|
|
|
|
2022-07-08 13:26:28 +00:00
|
|
|
static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
|
|
|
|
NSMutableArray *array = [dict valueForKey:tag];
|
|
|
|
if(!array) {
|
|
|
|
array = [[NSMutableArray alloc] init];
|
|
|
|
[dict setObject:array forKey:tag];
|
|
|
|
}
|
|
|
|
[array addObject:value];
|
|
|
|
}
|
|
|
|
|
2013-10-11 14:25:41 +00:00
|
|
|
// This callback is only called for STREAMINFO blocks
|
2022-02-07 05:49:27 +00:00
|
|
|
void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
|
|
|
|
// Some flacs observed in the wild have multiple STREAMINFO metadata blocks,
|
|
|
|
// of which only first one has sane values, so only use values from the first STREAMINFO
|
|
|
|
// to determine stream format (this seems to be consistent with flac spec: http://flac.sourceforge.net/format.html)
|
2016-05-05 20:05:39 +00:00
|
|
|
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2022-02-09 21:44:50 +00:00
|
|
|
if(!flacDecoder->hasStreamInfo && metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
|
2022-02-07 05:49:27 +00:00
|
|
|
flacDecoder->channels = metadata->data.stream_info.channels;
|
2022-02-08 03:18:45 +00:00
|
|
|
flacDecoder->channelConfig = 0;
|
2022-02-07 05:49:27 +00:00
|
|
|
flacDecoder->frequency = metadata->data.stream_info.sample_rate;
|
|
|
|
flacDecoder->bitsPerSample = metadata->data.stream_info.bits_per_sample;
|
|
|
|
|
|
|
|
flacDecoder->totalFrames = metadata->data.stream_info.total_samples;
|
|
|
|
|
|
|
|
flacDecoder->hasStreamInfo = YES;
|
|
|
|
}
|
2022-02-07 10:06:51 +00:00
|
|
|
|
2022-02-15 03:54:08 +00:00
|
|
|
if(metadata->type == FLAC__METADATA_TYPE_CUESHEET && !flacDecoder->cuesheetFound) {
|
|
|
|
flacDecoder->cuesheetFound = YES;
|
|
|
|
|
2022-02-12 15:16:59 +00:00
|
|
|
NSString *_cuesheet;
|
|
|
|
grabbag__cuesheet_emit(&_cuesheet, metadata, [[NSString stringWithFormat:@"\"%@\"", [[[flacDecoder->source url] path] lastPathComponent]] UTF8String]);
|
|
|
|
|
|
|
|
if(![_cuesheet isEqual:flacDecoder->cuesheet]) {
|
|
|
|
flacDecoder->cuesheet = _cuesheet;
|
|
|
|
if(![flacDecoder->source seekable]) {
|
|
|
|
[flacDecoder willChangeValueForKey:@"metadata"];
|
|
|
|
[flacDecoder didChangeValueForKey:@"metadata"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(metadata->type == FLAC__METADATA_TYPE_PICTURE) {
|
|
|
|
NSData *_albumArt = [NSData dataWithBytes:metadata->data.picture.data length:metadata->data.picture.data_length];
|
|
|
|
if(![_albumArt isEqual:flacDecoder->albumArt]) {
|
|
|
|
flacDecoder->albumArt = _albumArt;
|
|
|
|
if(![flacDecoder->source seekable]) {
|
|
|
|
[flacDecoder willChangeValueForKey:@"metadata"];
|
|
|
|
[flacDecoder didChangeValueForKey:@"metadata"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 10:06:51 +00:00
|
|
|
if(metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
|
2022-07-08 13:26:28 +00:00
|
|
|
NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init];
|
2022-02-12 15:16:59 +00:00
|
|
|
NSString *_cuesheet = flacDecoder->cuesheet;
|
2022-02-09 21:44:50 +00:00
|
|
|
const FLAC__StreamMetadata_VorbisComment *vorbis_comment = &metadata->data.vorbis_comment;
|
|
|
|
for(int i = 0; i < vorbis_comment->num_comments; ++i) {
|
2022-02-12 15:16:59 +00:00
|
|
|
char *_name;
|
|
|
|
char *_value;
|
|
|
|
if(FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(vorbis_comment->comments[i], &_name, &_value)) {
|
2022-07-05 20:44:44 +00:00
|
|
|
NSString *name = guess_encoding_of_string(_name);
|
|
|
|
NSString *value = guess_encoding_of_string(_value);
|
2022-02-12 15:16:59 +00:00
|
|
|
free(_name);
|
|
|
|
free(_value);
|
2022-02-09 21:44:50 +00:00
|
|
|
name = [name lowercaseString];
|
2022-07-08 13:26:28 +00:00
|
|
|
if([name isEqualToString:@"cuesheet"]) {
|
2022-02-12 15:16:59 +00:00
|
|
|
_cuesheet = value;
|
2022-02-15 03:54:08 +00:00
|
|
|
flacDecoder->cuesheetFound = YES;
|
2022-02-09 21:44:50 +00:00
|
|
|
} else if([name isEqualToString:@"waveformatextensible_channel_mask"]) {
|
|
|
|
if([value hasPrefix:@"0x"]) {
|
|
|
|
char *end;
|
|
|
|
const char *_value = [value UTF8String] + 2;
|
|
|
|
flacDecoder->channelConfig = (uint32_t)strtoul(_value, &end, 16);
|
|
|
|
}
|
2022-07-08 13:26:28 +00:00
|
|
|
} else {
|
|
|
|
setDictionary(_metaDict, name, value);
|
2022-02-09 21:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-08 13:26:28 +00:00
|
|
|
if(![_metaDict isEqualToDictionary:flacDecoder->metaDict]) {
|
|
|
|
flacDecoder->metaDict = _metaDict;
|
2022-02-12 15:16:59 +00:00
|
|
|
|
|
|
|
if(![flacDecoder->source seekable]) {
|
|
|
|
[flacDecoder willChangeValueForKey:@"metadata"];
|
|
|
|
[flacDecoder didChangeValueForKey:@"metadata"];
|
|
|
|
}
|
2022-02-09 21:44:50 +00:00
|
|
|
}
|
2022-02-07 10:06:51 +00:00
|
|
|
}
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
|
2022-02-09 21:44:50 +00:00
|
|
|
FlacDecoder *flacDecoder = (__bridge FlacDecoder *)client_data;
|
|
|
|
if(status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC)
|
|
|
|
flacDecoder->abortFlag = YES;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (BOOL)open:(id<CogSource>)s {
|
2007-03-04 21:32:03 +00:00
|
|
|
[self setSource:s];
|
2022-02-07 05:49:27 +00:00
|
|
|
[self setSize:0];
|
|
|
|
|
|
|
|
if([s seekable]) {
|
|
|
|
[s seek:0 whence:SEEK_END];
|
|
|
|
[self setSize:[s tell]];
|
|
|
|
[s seek:0 whence:SEEK_SET];
|
|
|
|
}
|
|
|
|
|
2022-02-09 21:44:50 +00:00
|
|
|
// Must peek at stream! HTTP reader supports seeking within its buffer
|
|
|
|
BOOL isOggFlac = NO;
|
|
|
|
uint8_t buffer[4];
|
|
|
|
[s read:buffer amount:4];
|
|
|
|
[s seek:0 whence:SEEK_SET];
|
|
|
|
if(memcmp(buffer, "OggS", 4) == 0) {
|
|
|
|
isOggFlac = YES;
|
|
|
|
}
|
|
|
|
|
2022-07-08 13:26:28 +00:00
|
|
|
metaDict = [NSDictionary dictionary];
|
|
|
|
icyMetaDict = [NSDictionary dictionary];
|
2022-02-12 15:16:59 +00:00
|
|
|
albumArt = [NSData data];
|
2022-02-15 03:54:08 +00:00
|
|
|
cuesheetFound = NO;
|
2022-02-12 15:16:59 +00:00
|
|
|
cuesheet = @"";
|
2022-02-09 21:44:50 +00:00
|
|
|
|
2007-03-04 21:32:03 +00:00
|
|
|
decoder = FLAC__stream_decoder_new();
|
2022-02-07 05:49:27 +00:00
|
|
|
if(decoder == NULL)
|
2005-06-02 18:16:43 +00:00
|
|
|
return NO;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2022-02-09 21:44:50 +00:00
|
|
|
if(![source seekable]) {
|
|
|
|
FLAC__stream_decoder_set_md5_checking(decoder, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
FLAC__stream_decoder_set_metadata_ignore_all(decoder);
|
|
|
|
FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO);
|
|
|
|
FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
2022-02-12 15:16:59 +00:00
|
|
|
FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_PICTURE);
|
|
|
|
FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_CUESHEET);
|
2022-02-09 21:44:50 +00:00
|
|
|
|
|
|
|
abortFlag = NO;
|
|
|
|
|
|
|
|
FLAC__StreamDecoderInitStatus ret;
|
|
|
|
|
|
|
|
if(isOggFlac) {
|
|
|
|
ret = FLAC__stream_decoder_init_ogg_stream(decoder,
|
|
|
|
ReadCallback,
|
|
|
|
([source seekable] ? SeekCallback : NULL),
|
|
|
|
([source seekable] ? TellCallback : NULL),
|
|
|
|
([source seekable] ? LengthCallback : NULL),
|
|
|
|
([source seekable] ? EOFCallback : NULL),
|
|
|
|
WriteCallback,
|
|
|
|
MetadataCallback,
|
|
|
|
ErrorCallback,
|
|
|
|
(__bridge void *)(self));
|
|
|
|
} else {
|
|
|
|
ret = FLAC__stream_decoder_init_stream(decoder,
|
|
|
|
ReadCallback,
|
|
|
|
([source seekable] ? SeekCallback : NULL),
|
|
|
|
([source seekable] ? TellCallback : NULL),
|
|
|
|
([source seekable] ? LengthCallback : NULL),
|
|
|
|
([source seekable] ? EOFCallback : NULL),
|
|
|
|
WriteCallback,
|
|
|
|
MetadataCallback,
|
|
|
|
ErrorCallback,
|
|
|
|
(__bridge void *)(self));
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ret != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
2005-06-02 18:16:43 +00:00
|
|
|
return NO;
|
2007-03-04 21:32:03 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-03-04 21:32:03 +00:00
|
|
|
FLAC__stream_decoder_process_until_end_of_metadata(decoder);
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2022-02-07 10:06:51 +00:00
|
|
|
if(hasStreamInfo) {
|
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
}
|
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
blockBuffer = malloc(SAMPLE_blockBuffer_SIZE);
|
2007-02-27 23:56:52 +00:00
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2022-07-10 22:14:47 +00:00
|
|
|
- (AudioChunk *)readAudio {
|
|
|
|
id audioChunkClass = NSClassFromString(@"AudioChunk");
|
|
|
|
AudioChunk *chunk = nil;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2022-07-14 07:01:59 +00:00
|
|
|
while (blockBufferFrames <= 0) {
|
|
|
|
if(FLAC__stream_decoder_get_state(decoder) == FLAC__STREAM_DECODER_END_OF_STREAM) {
|
|
|
|
return nil;
|
|
|
|
}
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2022-07-14 07:01:59 +00:00
|
|
|
if(!FLAC__stream_decoder_process_single(decoder)) {
|
|
|
|
return nil;
|
|
|
|
}
|
2022-07-10 22:14:47 +00:00
|
|
|
}
|
2007-11-24 20:16:27 +00:00
|
|
|
|
2022-07-10 22:14:47 +00:00
|
|
|
if(blockBufferFrames > 0) {
|
|
|
|
chunk = [[audioChunkClass alloc] initWithProperties:[self properties]];
|
|
|
|
[chunk assignSamples:blockBuffer frameCount:blockBufferFrames];
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2022-07-10 22:14:47 +00:00
|
|
|
blockBufferFrames = 0;
|
2022-02-07 05:49:27 +00:00
|
|
|
}
|
|
|
|
|
2022-02-09 23:04:49 +00:00
|
|
|
if(![source seekable]) {
|
|
|
|
Class sourceClass = [source class];
|
|
|
|
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
|
|
|
HTTPSource *httpSource = (HTTPSource *)source;
|
2022-07-08 13:26:28 +00:00
|
|
|
NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init];
|
2022-02-09 23:04:49 +00:00
|
|
|
if([httpSource hasMetadata]) {
|
|
|
|
NSDictionary *metadata = [httpSource metadata];
|
|
|
|
NSString *_genre = [metadata valueForKey:@"genre"];
|
|
|
|
NSString *_album = [metadata valueForKey:@"album"];
|
|
|
|
NSString *_artist = [metadata valueForKey:@"artist"];
|
|
|
|
NSString *_title = [metadata valueForKey:@"title"];
|
2022-07-08 13:26:28 +00:00
|
|
|
|
|
|
|
if(_genre && [_genre length]) {
|
|
|
|
setDictionary(_icyMetaDict, @"genre", _genre);
|
|
|
|
}
|
|
|
|
if(_album && [_album length]) {
|
|
|
|
setDictionary(_icyMetaDict, @"album", _album);
|
|
|
|
}
|
|
|
|
if(_artist && [_artist length]) {
|
|
|
|
setDictionary(_icyMetaDict, @"artist", _artist);
|
|
|
|
}
|
|
|
|
if(_title && [_title length]) {
|
|
|
|
setDictionary(_icyMetaDict, @"title", _title);
|
|
|
|
}
|
|
|
|
if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) {
|
|
|
|
icyMetaDict = _icyMetaDict;
|
2022-02-09 23:04:49 +00:00
|
|
|
[self willChangeValueForKey:@"metadata"];
|
|
|
|
[self didChangeValueForKey:@"metadata"];
|
|
|
|
}
|
2022-02-09 21:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-10 22:14:47 +00:00
|
|
|
return chunk;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)close {
|
|
|
|
if(decoder) {
|
2007-03-04 21:32:03 +00:00
|
|
|
FLAC__stream_decoder_finish(decoder);
|
|
|
|
FLAC__stream_decoder_delete(decoder);
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
if(blockBuffer) {
|
2007-11-24 20:16:27 +00:00
|
|
|
free(blockBuffer);
|
2007-02-27 23:56:52 +00:00
|
|
|
}
|
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
decoder = NULL;
|
2007-11-24 20:16:27 +00:00
|
|
|
blockBuffer = NULL;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)dealloc {
|
|
|
|
[self close];
|
2016-06-19 19:57:18 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (long)seek:(long)sample {
|
|
|
|
if(!FLAC__stream_decoder_seek_absolute(decoder, sample))
|
|
|
|
return -1;
|
|
|
|
|
2007-11-24 20:16:27 +00:00
|
|
|
return sample;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
// bs methods
|
|
|
|
- (char *)blockBuffer {
|
2007-11-24 20:16:27 +00:00
|
|
|
return blockBuffer;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
- (int)blockBufferFrames {
|
2007-11-24 20:16:27 +00:00
|
|
|
return blockBufferFrames;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setBlockBufferFrames:(int)frames {
|
2007-11-24 20:16:27 +00:00
|
|
|
blockBufferFrames = frames;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (FLAC__StreamDecoder *)decoder {
|
2005-06-02 18:16:43 +00:00
|
|
|
return decoder;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setSource:(id<CogSource>)s {
|
2007-03-04 21:32:03 +00:00
|
|
|
source = s;
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
- (id<CogSource>)source {
|
2007-03-04 21:32:03 +00:00
|
|
|
return source;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setEndOfStream:(BOOL)eos {
|
2007-03-04 21:32:03 +00:00
|
|
|
endOfStream = eos;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (BOOL)endOfStream {
|
2007-03-04 21:32:03 +00:00
|
|
|
return endOfStream;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setSize:(long)size {
|
|
|
|
fileSize = size;
|
2021-12-11 08:22:19 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (NSDictionary *)properties {
|
2022-06-17 13:39:02 +00:00
|
|
|
return @{ @"channels": @(channels),
|
|
|
|
@"channelConfig": @(channelConfig),
|
|
|
|
@"bitsPerSample": @(bitsPerSample),
|
|
|
|
@"sampleRate": @(frequency),
|
|
|
|
@"totalFrames": @(totalFrames),
|
|
|
|
@"seekable": @([source seekable]),
|
|
|
|
@"bitrate": @(fileSize ? (fileSize * 8 / ((totalFrames + (frequency / 2)) / frequency)) / 1000 : 0),
|
|
|
|
@"codec": @"FLAC",
|
|
|
|
@"endian": @"big",
|
|
|
|
@"encoding": @"lossless" };
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
|
2022-02-09 03:56:39 +00:00
|
|
|
- (NSDictionary *)metadata {
|
2022-07-08 13:26:28 +00:00
|
|
|
NSDictionary *dict1 = @{ @"albumArt": albumArt, @"cuesheet": cuesheet };
|
|
|
|
NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict];
|
|
|
|
NSDictionary *dict3 = [dict2 dictionaryByMergingWith:icyMetaDict];
|
|
|
|
return dict3;
|
2022-02-09 03:56:39 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
+ (NSArray *)fileTypes {
|
2022-01-19 02:12:57 +00:00
|
|
|
return @[@"flac"];
|
2007-10-14 18:39:58 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
+ (NSArray *)mimeTypes {
|
2022-02-09 21:44:50 +00:00
|
|
|
return @[@"audio/x-flac", @"application/ogg", @"audio/ogg"];
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
+ (float)priority {
|
|
|
|
return 2.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 17:54:11 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
+ (NSArray *)fileTypeAssociations {
|
|
|
|
return @[
|
|
|
|
@[@"FLAC Audio File", @"flac.icns", @"flac"]
|
|
|
|
];
|
2022-01-18 11:06:03 +00:00
|
|
|
}
|
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
@end
|