607 lines
12 KiB
Objective-C
607 lines
12 KiB
Objective-C
//
|
|
// MADFile.m
|
|
// Cog
|
|
//
|
|
// Created by Vincent Spader on 6/17/06.
|
|
// Copyright 2006 Vincent Spader. All rights reserved.
|
|
//
|
|
|
|
#import "MADDecoder.h"
|
|
#undef HAVE_CONFIG_H
|
|
#import <ID3Tag/id3tag.h>
|
|
|
|
@implementation MADDecoder
|
|
|
|
/*XING FUN*/
|
|
|
|
#define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')
|
|
#define INFO_MAGIC (('I' << 24) | ('n' << 16) | ('f' << 8) | 'o')
|
|
#define LAME_MAGIC (('L' << 24) | ('A' << 16) | ('M' << 8) | 'E')
|
|
|
|
struct xing
|
|
{
|
|
long flags; /* valid fields (see below) */
|
|
unsigned long frames; /* total number of frames */
|
|
unsigned long bytes; /* total number of bytes */
|
|
unsigned char toc[100]; /* 100-point seek table */
|
|
long scale; /* ?? */
|
|
};
|
|
|
|
struct lame
|
|
{
|
|
long flags;
|
|
};
|
|
|
|
enum
|
|
{
|
|
XING_FRAMES = 0x00000001L,
|
|
XING_BYTES = 0x00000002L,
|
|
XING_TOC = 0x00000004L,
|
|
XING_SCALE = 0x00000008L
|
|
};
|
|
|
|
int lame_parse(struct lame *lame, struct mad_bitptr *ptr, unsigned int bitlen)
|
|
{
|
|
unsigned long magic;
|
|
unsigned long garbage;
|
|
|
|
magic = mad_bit_read(ptr, 32); //4 bytes
|
|
|
|
if (magic != LAME_MAGIC)
|
|
return 0;
|
|
|
|
mad_bit_skip(ptr, 17*8); //17 bytes skipped
|
|
garbage = mad_bit_read(ptr, 24); //3 bytes
|
|
// _startPadding = (garbage >> 12) & 0x000FFF;
|
|
// _endPadding = garbage & 0x000FFF;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int xing_parse(struct xing *xing, struct mad_bitptr *ptr, unsigned int bitlen)
|
|
{
|
|
xing->flags = 0;
|
|
unsigned long magic;
|
|
|
|
if (bitlen < 64)
|
|
return 0;
|
|
|
|
magic = mad_bit_read(ptr, 32);
|
|
if (magic != INFO_MAGIC && magic != XING_MAGIC)
|
|
return 0;
|
|
|
|
xing->flags = mad_bit_read(ptr, 32);
|
|
bitlen -= 64;
|
|
|
|
if (xing->flags & XING_FRAMES) {
|
|
if (bitlen < 32)
|
|
return 0;
|
|
|
|
xing->frames = mad_bit_read(ptr, 32);
|
|
bitlen -= 32;
|
|
}
|
|
|
|
if (xing->flags & XING_BYTES) {
|
|
if (bitlen < 32)
|
|
return 0;
|
|
|
|
xing->bytes = mad_bit_read(ptr, 32);
|
|
bitlen -= 32;
|
|
}
|
|
|
|
if (xing->flags & XING_TOC) {
|
|
int i;
|
|
|
|
if (bitlen < 800)
|
|
return 0;
|
|
|
|
for (i = 0; i < 100; ++i)
|
|
xing->toc[i] = mad_bit_read(ptr, 8);
|
|
|
|
bitlen -= 800;
|
|
}
|
|
|
|
if (xing->flags & XING_SCALE) {
|
|
if (bitlen < 32)
|
|
return 0;
|
|
|
|
xing->scale = mad_bit_read(ptr, 32);
|
|
bitlen -= 32;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int parse_headers(struct xing *xing, struct lame *lame, struct mad_bitptr ptr, unsigned int bitlen)
|
|
{
|
|
xing->flags = 0;
|
|
lame->flags = 0;
|
|
|
|
if (xing_parse(xing, &ptr, bitlen))
|
|
{
|
|
lame_parse(lame, &ptr, bitlen);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
- (BOOL)scanFileFast:(BOOL)fast useXing:(BOOL)use_xing
|
|
{
|
|
const int BUFFER_SIZE = 16*1024;
|
|
const int N_AVERAGE_FRAMES = 10;
|
|
|
|
struct mad_stream stream;
|
|
struct mad_header header;
|
|
struct mad_frame frame; /* to read xing data */
|
|
struct xing xing;
|
|
struct lame lame;
|
|
int remainder = 0;
|
|
int data_used = 0;
|
|
int len = 0;
|
|
int tagsize = 0;
|
|
int frames = 0;
|
|
unsigned char buffer[BUFFER_SIZE];
|
|
BOOL has_xing = NO;
|
|
BOOL vbr = NO;
|
|
|
|
mad_stream_init (&stream);
|
|
mad_header_init (&header);
|
|
mad_frame_init (&frame);
|
|
|
|
bitrate = 0;
|
|
_duration = mad_timer_zero;
|
|
|
|
frames = 0;
|
|
|
|
[_source seek:0 whence:SEEK_END];
|
|
_fileSize = [_source tell];
|
|
[_source seek:0 whence:SEEK_SET];
|
|
|
|
BOOL done = NO;
|
|
|
|
while (!done)
|
|
{
|
|
remainder = stream.bufend - stream.next_frame;
|
|
|
|
memcpy (buffer, stream.this_frame, remainder);
|
|
len = [_source read:buffer + remainder amount:BUFFER_SIZE - remainder];
|
|
|
|
if (len <= 0)
|
|
break;
|
|
|
|
mad_stream_buffer (&stream, buffer, len + remainder);
|
|
|
|
while (1)
|
|
{
|
|
if (mad_header_decode (&header, &stream) == -1)
|
|
{
|
|
if (stream.error == MAD_ERROR_BUFLEN)
|
|
{
|
|
break;
|
|
}
|
|
if (!MAD_RECOVERABLE (stream.error))
|
|
{
|
|
break;
|
|
}
|
|
if (stream.error == MAD_ERROR_LOSTSYNC)
|
|
{
|
|
/* ignore LOSTSYNC due to ID3 tags */
|
|
tagsize = id3_tag_query (stream.this_frame,
|
|
stream.bufend -
|
|
stream.this_frame);
|
|
if (tagsize > 0)
|
|
{
|
|
mad_stream_skip (&stream, tagsize);
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
}
|
|
frames++;
|
|
|
|
mad_timer_add (&_duration, header.duration);
|
|
data_used += stream.next_frame - stream.this_frame;
|
|
if (frames == 1)
|
|
{
|
|
/* most of these *should* remain constant */
|
|
bitrate = header.bitrate;
|
|
frequency = header.samplerate;
|
|
channels = MAD_NCHANNELS(&header);
|
|
|
|
if (use_xing)
|
|
{
|
|
frame.header = header;
|
|
if (mad_frame_decode(&frame, &stream) == -1)
|
|
continue;
|
|
|
|
if (parse_headers(&xing, &lame, stream.anc_ptr, stream.anc_bitlen))
|
|
{
|
|
has_xing = YES;
|
|
vbr = YES;
|
|
|
|
frames = xing.frames;
|
|
mad_timer_multiply (&_duration, frames);
|
|
|
|
bitrate = 8.0 * xing.bytes / mad_timer_count(_duration, MAD_UNITS_SECONDS);
|
|
done = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
/* perhaps we have a VBR file */
|
|
if (bitrate != header.bitrate)
|
|
vbr = YES;
|
|
if (vbr)
|
|
bitrate += header.bitrate;
|
|
}
|
|
|
|
if ((!vbr || (vbr && !has_xing)) && fast && frames >= N_AVERAGE_FRAMES)
|
|
{
|
|
float frame_size = ((double)data_used) / N_AVERAGE_FRAMES;
|
|
frames = (_fileSize - tagsize) / frame_size;
|
|
|
|
_duration.seconds /= N_AVERAGE_FRAMES;
|
|
_duration.fraction /= N_AVERAGE_FRAMES;
|
|
mad_timer_multiply (&_duration, frames);
|
|
|
|
done = YES;
|
|
break;
|
|
}
|
|
}
|
|
if (stream.error != MAD_ERROR_BUFLEN)
|
|
break;
|
|
}
|
|
|
|
if (vbr && !has_xing)
|
|
bitrate = bitrate / frames;
|
|
|
|
mad_frame_finish (&frame);
|
|
mad_header_finish (&header);
|
|
mad_stream_finish (&stream);
|
|
|
|
length = mad_timer_count(_duration, MAD_UNITS_MILLISECONDS);
|
|
|
|
bitrate /= 1000;
|
|
|
|
bitsPerSample = 16;
|
|
|
|
[_source seek:0 whence:SEEK_SET];
|
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
return frames != 0;
|
|
}
|
|
|
|
|
|
- (BOOL)open:(id<CogSource>)source
|
|
{
|
|
[source retain];
|
|
[_source release];
|
|
_source = source;
|
|
|
|
/* First the structures used by libmad must be initialized. */
|
|
mad_stream_init(&_stream);
|
|
mad_frame_init(&_frame);
|
|
mad_synth_init(&_synth);
|
|
mad_timer_reset(&_timer);
|
|
|
|
_firstFrame = YES;
|
|
|
|
if ([_source seekable]) {
|
|
return [self scanFileFast:YES useXing:YES];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
/**
|
|
* Scale PCM data
|
|
*/
|
|
static inline signed int scale (mad_fixed_t sample)
|
|
{
|
|
BOOL hard_limit = YES;
|
|
// BOOL replaygain = NO;
|
|
/* replayGain by SamKR */
|
|
double scale = -1;
|
|
/* if (replaygain)
|
|
{
|
|
if (file_info->has_replaygain)
|
|
{
|
|
scale = file_info->replaygain_track_scale;
|
|
if (file_info->replaygain_album_scale != -1
|
|
&& (scale==-1 || ! xmmsmad_config.replaygain.track_mode))
|
|
{
|
|
scale = file_info->replaygain_album_scale;
|
|
}
|
|
}
|
|
if (scale == -1)
|
|
scale = xmmsmad_config.replaygain.default_scale;
|
|
}
|
|
*/
|
|
if (scale == -1)
|
|
scale = 1.0;
|
|
|
|
/* hard-limit (clipping-prevention) */
|
|
if (hard_limit)
|
|
{
|
|
/* convert to double before computation, to avoid mad_fixed_t wrapping */
|
|
double x = mad_f_todouble(sample) * scale;
|
|
static const double k = 0.5; // -6dBFS
|
|
if (x > k)
|
|
{
|
|
x = tanh((x - k) / (1-k)) * (1-k) + k;
|
|
}
|
|
else if(x < -k)
|
|
{
|
|
x = tanh((x + k) / (1-k)) * (1-k) - k;
|
|
}
|
|
sample = x * (MAD_F_ONE);
|
|
}
|
|
else
|
|
sample *= scale;
|
|
|
|
int n_bits_to_loose = MAD_F_FRACBITS + 1 - 16;
|
|
|
|
/* round */
|
|
/* add half of the bits_to_loose range to round */
|
|
sample += (1L << (n_bits_to_loose - 1));
|
|
|
|
/* clip */
|
|
/* make sure we are between -1 and 1 */
|
|
if (sample >= MAD_F_ONE)
|
|
{
|
|
sample = MAD_F_ONE - 1;
|
|
}
|
|
else if (sample < -MAD_F_ONE)
|
|
{
|
|
sample = -MAD_F_ONE;
|
|
}
|
|
|
|
/* quantize */
|
|
/*
|
|
* Turn our mad_fixed_t into an integer.
|
|
* Shift all but 16-bits of the fractional part
|
|
* off the right hand side and shift an extra place
|
|
* to get the sign bit.
|
|
*/
|
|
sample >>= n_bits_to_loose;
|
|
|
|
return sample;
|
|
}
|
|
|
|
|
|
- (void)writeOutput
|
|
{
|
|
unsigned int nsamples;
|
|
mad_fixed_t const *left_ch, *right_ch;
|
|
|
|
// if (_outputAvailable) {
|
|
// NSLog(@"Losing Output: %i", _outputAvailable);
|
|
// }
|
|
nsamples = _synth.pcm.length;
|
|
left_ch = _synth.pcm.samples[0];
|
|
right_ch = _synth.pcm.samples[1];
|
|
_outputAvailable = nsamples * channels * (bitsPerSample/8);
|
|
|
|
if (_outputBuffer)
|
|
free(_outputBuffer);
|
|
|
|
_outputBuffer = (unsigned char *) malloc (_outputAvailable * sizeof (char));
|
|
|
|
unsigned char *outputPtr = _outputBuffer;
|
|
|
|
int i;
|
|
for (i=0; i < nsamples; i++)
|
|
{
|
|
signed short sample;
|
|
/* output sample(s) in 16-bit signed little-endian PCM */
|
|
sample = scale(left_ch[i]);
|
|
*(outputPtr++) = sample>>8;
|
|
*(outputPtr++) = sample & 0xff;
|
|
|
|
if (channels == 2)
|
|
{
|
|
sample = scale(right_ch[i]);
|
|
*(outputPtr++) = sample>>8;
|
|
*(outputPtr++) = sample & 0xff;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (int)fillBuffer:(void *)buf ofSize:(UInt32)size
|
|
{
|
|
int remainder;
|
|
int len;
|
|
BOOL eof = NO;
|
|
int amountToCopy = size;
|
|
int amountRemaining = size;
|
|
|
|
if (amountToCopy > _outputAvailable)
|
|
amountToCopy = _outputAvailable;
|
|
|
|
if (amountToCopy) {
|
|
memcpy(buf, _outputBuffer, amountToCopy);
|
|
memmove(_outputBuffer, _outputBuffer + amountToCopy, _outputAvailable - amountToCopy);
|
|
|
|
amountRemaining -= amountToCopy;
|
|
_outputAvailable -= amountToCopy;
|
|
}
|
|
|
|
while (amountRemaining > 0 && !eof) {
|
|
if (_stream.buffer == NULL || _stream.error == MAD_ERROR_BUFLEN)
|
|
{
|
|
if (!_seekSkip)
|
|
{
|
|
remainder = _stream.bufend - _stream.next_frame;
|
|
if (remainder)
|
|
memmove(_inputBuffer, _stream.this_frame, remainder);
|
|
}
|
|
else
|
|
{
|
|
remainder = 0;
|
|
}
|
|
|
|
len = [_source read:_inputBuffer+remainder amount:INPUT_BUFFER_SIZE-remainder];
|
|
if (len <= 0)
|
|
{
|
|
eof = YES;
|
|
break;
|
|
}
|
|
|
|
len += remainder;
|
|
if (len < MAD_BUFFER_GUARD) {
|
|
int i;
|
|
for (i = len; i < MAD_BUFFER_GUARD; i++)
|
|
_inputBuffer[i] = 0;
|
|
len = MAD_BUFFER_GUARD;
|
|
}
|
|
|
|
mad_stream_buffer(&_stream, _inputBuffer, len);
|
|
_stream.error = 0;
|
|
|
|
if (_seekSkip)
|
|
{
|
|
int skip = 2;
|
|
do
|
|
{
|
|
if (mad_frame_decode (&_frame, &_stream) == 0)
|
|
{
|
|
mad_timer_add (&_timer, _frame.header.duration);
|
|
if (--skip == 0)
|
|
mad_synth_frame (&_synth, &_frame);
|
|
}
|
|
else if (!MAD_RECOVERABLE (_stream.error))
|
|
break;
|
|
} while (skip);
|
|
|
|
_seekSkip = NO;
|
|
}
|
|
|
|
}
|
|
if (mad_frame_decode(&_frame, &_stream) == -1) {
|
|
if (!MAD_RECOVERABLE (_stream.error))
|
|
{
|
|
if(_stream.error==MAD_ERROR_BUFLEN) {
|
|
continue;
|
|
}
|
|
|
|
eof = YES;
|
|
}
|
|
|
|
if (_stream.error == MAD_ERROR_LOSTSYNC)
|
|
{
|
|
// ignore LOSTSYNC due to ID3 tags
|
|
int tagsize = id3_tag_query (_stream.this_frame,
|
|
_stream.bufend -
|
|
_stream.this_frame);
|
|
if (tagsize > 0)
|
|
{
|
|
mad_stream_skip (&_stream, tagsize);
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
if (_firstFrame && ![_source seekable]) {
|
|
frequency = _frame.header.samplerate;
|
|
channels = MAD_NCHANNELS(&_frame.header);
|
|
bitsPerSample = 16;
|
|
|
|
NSLog(@"FORMAT CHANGED: %f", frequency);
|
|
[self willChangeValueForKey:@"properties"];
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
_firstFrame = NO;
|
|
}
|
|
|
|
mad_timer_add (&_timer, _frame.header.duration);
|
|
|
|
mad_synth_frame (&_synth, &_frame);
|
|
|
|
[self writeOutput];
|
|
amountToCopy = amountRemaining;
|
|
if (amountToCopy > _outputAvailable) {
|
|
amountToCopy = _outputAvailable;
|
|
}
|
|
if (amountRemaining < amountToCopy) {
|
|
amountToCopy = amountRemaining;
|
|
}
|
|
|
|
memcpy(((char *)buf)+(size-amountRemaining), _outputBuffer, amountToCopy);
|
|
memmove(_outputBuffer, _outputBuffer + amountToCopy, _outputAvailable - amountToCopy);
|
|
amountRemaining -= amountToCopy;
|
|
_outputAvailable -= amountToCopy;
|
|
}
|
|
|
|
return (size - amountRemaining);
|
|
}
|
|
|
|
- (void)close
|
|
{
|
|
[_source close];
|
|
|
|
mad_synth_finish(&_synth);
|
|
mad_frame_finish(&_frame);
|
|
mad_stream_finish(&_stream);
|
|
}
|
|
|
|
- (double)seekToTime:(double)milliseconds
|
|
{
|
|
unsigned long new_position;
|
|
unsigned long seconds = milliseconds/1000.0;
|
|
unsigned long total_seconds = mad_timer_count(_duration, MAD_UNITS_SECONDS);
|
|
|
|
if (seconds > total_seconds)
|
|
seconds = total_seconds;
|
|
|
|
mad_timer_set(&_timer, seconds, 0, 0);
|
|
new_position = ((double) seconds / (double) total_seconds) * _fileSize;
|
|
|
|
[_source seek:new_position whence:SEEK_SET];
|
|
mad_stream_sync(&_stream);
|
|
_stream.error = MAD_ERROR_BUFLEN;
|
|
_stream.sync = 0;
|
|
_outputAvailable = 0;
|
|
|
|
mad_frame_mute(&_frame);
|
|
mad_synth_mute(&_synth);
|
|
|
|
_seekSkip = YES;
|
|
|
|
NSLog(@"Seeking: %lf", seconds*1000.0);
|
|
|
|
return seconds*1000.0;
|
|
}
|
|
|
|
- (NSDictionary *)properties
|
|
{
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithInt:channels],@"channels",
|
|
[NSNumber numberWithInt:bitsPerSample],@"bitsPerSample",
|
|
[NSNumber numberWithFloat:frequency],@"sampleRate",
|
|
[NSNumber numberWithInt:bitrate],@"bitrate",
|
|
[NSNumber numberWithDouble:length],@"length",
|
|
[NSNumber numberWithBool:[_source seekable]], @"seekable",
|
|
@"big", @"endian",
|
|
nil];
|
|
}
|
|
|
|
|
|
+ (NSArray *)fileTypes
|
|
{
|
|
return [NSArray arrayWithObjects:@"mp3",nil];
|
|
}
|
|
|
|
@end
|
|
|