Cog/Plugins/MAD/MADDecoder.m
Christopher Snowhill 7994929a80 Audio: Add full timestamp accounting to playback
Audio Chunks now have full timestamp accounting, including DSP playback
speed ratio for the one DSP that can change play ratio, Rubber Band.
Inputs which support looping and actually reporting the absolute play
position now do so.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2025-02-12 14:08:43 -08:00

902 lines
23 KiB
Objective-C

//
// MADFile.m
// Cog
//
// Created by Vincent Spader on 6/17/06.
// Copyright 2006 Vincent Spader. All rights reserved.
//
#import "MADDecoder.h"
#import "HTTPSource.h"
#import "Logging.h"
#import "id3tag.h"
#import <Accelerate/Accelerate.h>
#import "CVbriHeader.h"
@implementation MADDecoder
#define LAME_HEADER_SIZE ((8 * 5) + 4 + 4 + 8 + 32 + 16 + 16 + 4 + 4 + 8 + 12 + 12 + 8 + 8 + 2 + 3 + 11 + 32 + 32 + 32)
// From vbrheadersdk:
// ========================================
// A Xing header may be present in the ancillary
// data field of the first frame of an mp3 bitstream
// The Xing header (optionally) contains
// frames total number of audio frames in the bitstream
// bytes total number of bytes in the bitstream
// toc table of contents
// toc (table of contents) gives seek points
// for random access
// the ith entry determines the seek point for
// i-percent duration
// seek point in bytes = (toc[i]/256.0) * total_bitstream_bytes
// e.g. half duration seek point = (toc[50]/256.0) * total_bitstream_bytes
#define FRAMES_FLAG 0x0001
#define BYTES_FLAG 0x0002
#define TOC_FLAG 0x0004
#define VBR_SCALE_FLAG 0x0008
// Scan file quickly
- (void)bufferRefill:(struct mad_stream *)stream {
long bytesToRead, bytesRemaining;
if(NULL == stream->buffer || MAD_ERROR_BUFLEN == stream->error) {
if(stream->next_frame) {
bytesRemaining = stream->bufend - stream->next_frame;
memmove(_inputBuffer, stream->next_frame, bytesRemaining);
bytesToRead = INPUT_BUFFER_SIZE - bytesRemaining;
} else {
bytesToRead = INPUT_BUFFER_SIZE;
bytesRemaining = 0;
}
// Read raw bytes from the MP3 file
long bytesRead = [_source read:_inputBuffer + bytesRemaining amount:bytesToRead];
if(bytesRead == 0) {
memset(_inputBuffer + bytesRemaining + bytesRead, 0, MAD_BUFFER_GUARD);
bytesRead += MAD_BUFFER_GUARD;
inputEOF = YES;
}
mad_stream_buffer(stream, _inputBuffer, bytesRead + bytesRemaining);
stream->error = MAD_ERROR_NONE;
}
}
- (BOOL)scanFile {
struct mad_stream *stream;
struct mad_frame *frame;
stream = (struct mad_stream *) calloc(sizeof(struct mad_stream), 1);
frame = (struct mad_frame *) calloc(sizeof(struct mad_frame), 1);
if(!stream || !frame) {
ALog(@"Out of memory!");
return NO;
}
long framesDecoded = 0;
int samplesPerMPEGFrame = 0;
int id3_length = 0;
mad_stream_init(stream);
mad_frame_init(frame);
[_source seek:0 whence:SEEK_END];
_fileSize = [_source tell];
[_source seek:0 whence:SEEK_SET];
for(;;) {
[self bufferRefill:stream];
if(mad_frame_decode(frame, stream) == -1) {
if(MAD_RECOVERABLE(stream->error)) {
// Prevent ID3 tags from reporting recoverable frame errors
const uint8_t *buffer = stream->this_frame;
unsigned long buflen = stream->bufend - stream->this_frame;
if(8 <= buflen && 0x52 == buffer[0] && 0x49 == buffer[1] && 0x46 == buffer[2] &&
0x46 == buffer[3]) {
uint32_t riffLen = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24);
if(riffLen + 8 == _fileSize ||
riffLen + 8 + 1 == _fileSize) { // In case of padding
ALog(@"Renamed RIFF file found, dropping to next input handler.");
return NO;
}
}
else if(10 <= buflen && 0x49 == buffer[0] && 0x44 == buffer[1] && 0x33 == buffer[2]) {
id3_length = (((buffer[6] & 0x7F) << (3 * 7)) | ((buffer[7] & 0x7F) << (2 * 7)) |
((buffer[8] & 0x7F) << (1 * 7)) | ((buffer[9] & 0x7F) << (0 * 7)));
_foundID3v2 = YES;
// Add 10 bytes for ID3 header
id3_length += 10;
void *tagBuffer = malloc(id3_length);
if(!tagBuffer) goto error;
memcpy(tagBuffer, &buffer[0], MIN(buflen, id3_length));
long bufleft = id3_length - buflen;
long tagRead = MIN(buflen, id3_length);
while(bufleft > 0) {
stream->error = MAD_ERROR_BUFLEN;
stream->next_frame = NULL;
[self bufferRefill:stream];
buffer = stream->this_frame;
buflen = stream->bufend - stream->this_frame;
memcpy(tagBuffer + tagRead, buffer, MIN(buflen, bufleft));
tagRead += MIN(buflen, bufleft);
bufleft -= buflen;
}
if(bufleft < 0) {
mad_stream_skip(stream, buflen + bufleft);
}
struct id3_tag *tag = id3_tag_parse(tagBuffer, id3_length);
if(tag) {
for(size_t i = 0; i < tag->nframes; ++i) {
struct id3_frame *frame = tag->frames[i];
if(!strcmp(frame->id, "COMM")) {
union id3_field *field;
const id3_ucs4_t *description;
const id3_ucs4_t *value;
field = id3_frame_field(frame, 2);
description = id3_field_getstring(field);
field = id3_frame_field(frame, 3);
value = id3_field_getfullstring(field);
if(description && value) {
id3_utf8_t *description8 = id3_ucs4_utf8duplicate(description);
if(!strcmp((const char *)description8, "iTunSMPB")) {
id3_utf8_t *value8 = id3_ucs4_utf8duplicate(value);
uint32_t zero, start_pad, end_pad;
uint64_t last_eight_frames_offset;
int64_t temp_duration;
if(sscanf((const char *)value8, "%" PRIx32 " %" PRIx32 " %" PRIx32 " %" PRIx64 " %" PRIx32 " %" PRIx64, &zero, &start_pad, &end_pad, &temp_duration, &zero, &last_eight_frames_offset) == 6 &&
temp_duration >= 0 &&
start_pad <= (576 * 2 * 32) &&
end_pad <= (576 * 2 * 64) &&
(_fileSize && (last_eight_frames_offset < (_fileSize - id3_length)))) {
if(end_pad >= 528 + 1) {
_startPadding = start_pad + 528 + 1;
_endPadding = end_pad - (528 + 1);
// iTunes encodes the original length of the file here
totalFrames = temp_duration + _startPadding + _endPadding;
_foundiTunSMPB = YES;
}
}
free(value8);
}
free(description8);
}
}
}
id3_tag_delete(tag);
}
free(tagBuffer);
} else if(stream->error == MAD_ERROR_BADDATAPTR) {
goto framedecoded;
}
continue;
} else if(stream->error == MAD_ERROR_BUFLEN && inputEOF) {
break;
} else if(stream->error == MAD_ERROR_BUFLEN) {
continue;
} else {
// DLog(@"Unrecoverable error: %s", mad_stream_errorstr(&stream));
break;
}
}
framedecoded:
framesDecoded++;
if(framesDecoded == 1) {
sampleRate = frame->header.samplerate;
channels = MAD_NCHANNELS(&frame->header);
if(MAD_FLAG_LSF_EXT & frame->header.flags || MAD_FLAG_MPEG_2_5_EXT & frame->header.flags) {
switch(frame->header.layer) {
case MAD_LAYER_I:
samplesPerMPEGFrame = 384;
layer = 1;
break;
case MAD_LAYER_II:
samplesPerMPEGFrame = 1152;
layer = 2;
break;
case MAD_LAYER_III:
samplesPerMPEGFrame = 576;
layer = 3;
break;
}
} else {
switch(frame->header.layer) {
case MAD_LAYER_I:
samplesPerMPEGFrame = 384;
layer = 1;
break;
case MAD_LAYER_II:
samplesPerMPEGFrame = 1152;
layer = 2;
break;
case MAD_LAYER_III:
samplesPerMPEGFrame = 1152;
layer = 3;
break;
}
}
if(layer != 3) continue;
const size_t ancillaryBitsRemaining = (stream->next_frame - stream->this_frame) * 8;
static const int64_t xing_offtbl[2][2] = {{32, 17}, {17,9}};
const int64_t xing_offset = xing_offtbl[!!(MAD_FLAG_LSF_EXT & frame->header.flags || MAD_FLAG_MPEG_2_5_EXT & frame->header.flags)][channels == 1] + 4; // Plus MPEG header
size_t ancBitsRemainingXing = ancillaryBitsRemaining - xing_offset * 8;
if(ancBitsRemainingXing >= 32) {
const uint8_t *ptr = stream->this_frame + xing_offset;
struct mad_bitptr bitptr;
mad_bit_init(&bitptr, ptr);
uint32_t magic = (uint32_t)mad_bit_read(&bitptr, 32);
ancBitsRemainingXing -= 32;
if('Xing' == magic || 'Info' == magic) {
unsigned i;
uint32_t flags = 0, frames = 0, bytes = 0, vbrScale = 0;
if(32 > ancBitsRemainingXing)
continue;
flags = (uint32_t)mad_bit_read(&bitptr, 32);
ancBitsRemainingXing -= 32;
// 4 byte value containing total frames
if(FRAMES_FLAG & flags) {
if(32 > ancBitsRemainingXing)
continue;
frames = (uint32_t)mad_bit_read(&bitptr, 32);
ancBitsRemainingXing -= 32;
// Determine number of samples, discounting encoder delay and padding
// Our concept of a frame is the same as CoreAudio's- one sample across all channels
totalFrames = frames * samplesPerMPEGFrame;
// DLog(@"TOTAL READ FROM XING");
}
// 4 byte value containing total bytes
if(BYTES_FLAG & flags) {
if(32 > ancBitsRemainingXing)
continue;
bytes = (uint32_t)mad_bit_read(&bitptr, 32);
ancBitsRemainingXing -= 32;
}
// 100 bytes containing TOC information
if(TOC_FLAG & flags) {
if(8 * 100 > ancBitsRemainingXing)
continue;
for(i = 0; i < 100; ++i)
/*_xingTOC[i] = */ mad_bit_read(&bitptr, 8);
ancBitsRemainingXing -= (8 * 100);
}
// 4 byte value indicating encoded vbr scale
if(VBR_SCALE_FLAG & flags) {
if(32 > ancBitsRemainingXing)
continue;
vbrScale = (uint32_t)mad_bit_read(&bitptr, 32);
ancBitsRemainingXing -= 32;
}
framesDecoded = frames;
_foundXingHeader = YES;
// Loook for the LAME header next
// http://gabriel.mp3-tech.org/mp3infotag.html
if(32 > ancBitsRemainingXing)
continue;
magic = (uint32_t)mad_bit_read(&bitptr, 32);
ancBitsRemainingXing -= 32;
if('LAME' == magic || 'Lavf' == magic || 'Lavc' == magic) {
if(LAME_HEADER_SIZE > ancBitsRemainingXing)
continue;
/*unsigned char versionString [5 + 1];
memset(versionString, 0, 6);*/
for(i = 0; i < 5; ++i)
/*versionString[i] =*/mad_bit_read(&bitptr, 8);
/*uint8_t infoTagRevision =*/mad_bit_read(&bitptr, 4);
/*uint8_t vbrMethod =*/mad_bit_read(&bitptr, 4);
/*uint8_t lowpassFilterValue =*/mad_bit_read(&bitptr, 8);
/*float peakSignalAmplitude =*/mad_bit_read(&bitptr, 32);
/*uint16_t radioReplayGain =*/mad_bit_read(&bitptr, 16);
/*uint16_t audiophileReplayGain =*/mad_bit_read(&bitptr, 16);
/*uint8_t encodingFlags =*/mad_bit_read(&bitptr, 4);
/*uint8_t athType =*/mad_bit_read(&bitptr, 4);
/*uint8_t lameBitrate =*/mad_bit_read(&bitptr, 8);
_startPadding = mad_bit_read(&bitptr, 12);
_endPadding = mad_bit_read(&bitptr, 12);
_startPadding += 528 + 1; // MDCT/filterbank delay
_endPadding -= 528 + 1;
/*uint8_t misc =*/mad_bit_read(&bitptr, 8);
/*uint8_t mp3Gain =*/mad_bit_read(&bitptr, 8);
/*DLog(@"Gain: %i", mp3Gain);*/
/*uint8_t unused =*/mad_bit_read(&bitptr, 2);
/*uint8_t surroundInfo =*/mad_bit_read(&bitptr, 3);
/*uint16_t presetInfo =*/mad_bit_read(&bitptr, 11);
/*uint32_t musicGain =*/mad_bit_read(&bitptr, 32);
/*uint32_t musicCRC =*/mad_bit_read(&bitptr, 32);
/*uint32_t tagCRC =*/mad_bit_read(&bitptr, 32);
ancBitsRemainingXing -= LAME_HEADER_SIZE;
_foundLAMEHeader = YES;
break;
}
}
}
const size_t vbri_offset = 4 + 32;
size_t ancBitsRemainingVBRI = ancillaryBitsRemaining - vbri_offset * 8;
if(ancBitsRemainingVBRI >= 32) {
const uint8_t *ptr = stream->this_frame + vbri_offset;
struct mad_bitptr bitptr;
mad_bit_init(&bitptr, ptr);
uint32_t magic = (uint32_t)mad_bit_read(&bitptr, 32);
ancBitsRemainingVBRI -= 32;
if('VBRI' == magic) {
struct VbriHeader *vbri_header = 0;
if(readVbriHeader(&vbri_header, mad_bit_nextbyte(&bitptr), ancBitsRemainingVBRI / 8) == 0) {
uint32_t frames = VbriTotalFrames(vbri_header);
totalFrames = frames * samplesPerMPEGFrame;
_startPadding = 0;
_endPadding = 0;
_foundVBRIHeader = YES;
}
if(vbri_header) {
freeVbriHeader(vbri_header);
}
break;
}
}
} else if(_foundXingHeader || _foundiTunSMPB || _foundVBRIHeader) {
break;
} else if(framesDecoded > 1) {
break;
}
}
// Don't commit division by zero on bad files
if(stream->next_frame == stream->this_frame) {
goto error;
}
if(!_foundiTunSMPB && !_foundXingHeader && !_foundVBRIHeader) {
// Now do CBR estimation instead of full file scanning
size_t frameCount = (_fileSize - id3_length) / (stream->next_frame - stream->this_frame);
mad_timer_t duration = frame->header.duration;
mad_timer_multiply(&duration, frameCount);
totalFrames = mad_timer_count(duration, sampleRate);
}
bitrate = ((double)((_fileSize - id3_length) * 8) / 1000.0) * (sampleRate / (double)totalFrames);
mad_frame_finish(frame);
mad_stream_finish(stream);
free(frame);
free(stream);
[_source seek:0 whence:SEEK_SET];
inputEOF = NO;
DLog(@"Mad properties: %@", [self properties]);
return YES;
error:
if(frame) {
mad_frame_finish(frame);
free(frame);
}
if(stream) {
mad_stream_finish(stream);
free(stream);
}
return NO;
}
- (BOOL)open:(id<CogSource>)source {
_source = source;
/* First the structures used by libmad must be initialized. */
mad_stream_init(&_stream);
mad_frame_init(&_frame);
mad_synth_init(&_synth);
_firstFrame = YES;
_outputFrames = 0;
_startPadding = 0;
_endPadding = 0;
// DLog(@"OPEN: %i", _firstFrame);
seconds = 0.0;
inputEOF = NO;
genre = @"";
album = @"";
artist = @"";
title = @"";
if(![_source seekable]) {
// Decode the first frame to get the channels, samplerate, etc.
int r;
do {
r = [self decodeMPEGFrame];
DLog(@"Decoding first frame: %i", r);
} while(r == 0);
if(r != -1) {
metadataUpdateInterval = sampleRate;
metadataUpdateCount = 0;
}
return (r == -1 ? NO : YES);
}
framesToSkip = 0;
BOOL ret = [self scanFile];
if(_foundLAMEHeader || _foundiTunSMPB) {
framesToSkip = _startPadding;
}
metadataUpdateInterval = sampleRate;
metadataUpdateCount = 0;
return ret;
}
- (BOOL)writeOutput {
unsigned long startingSample = 0;
unsigned long sampleCount = _synth.pcm.length;
// DLog(@"Position: %li/%li", _framesDecoded, totalFrames);
// DLog(@"<%i, %i>", _startPadding, _endPadding);
if(framesToSkip > 0) {
startingSample = framesToSkip;
}
// DLog(@"Counts: %i, %i", startingSample, sampleCount);
if(_foundLAMEHeader || _foundiTunSMPB) {
// Past the end of the file.
if(totalFrames - _endPadding <= _framesDecoded) {
// DLog(@"End of file. Not writing.");
return YES;
}
// Clip this for the following calculation, so this doesn't underflow
// when seeking and skipping a lot of samples
unsigned long startingSampleClipped = MIN(startingSample, sampleCount);
// We are at the end of the file and need to read the last few frames
if(_framesDecoded + (sampleCount - startingSampleClipped) > totalFrames - _endPadding) {
// DLog(@"End of file. %li", totalFrames - _endPadding - _framesDecoded);
sampleCount = totalFrames - _endPadding - _framesDecoded + startingSample;
}
} else {
// Past the end of the file.
if([_source seekable] && totalFrames <= _framesDecoded) {
return YES;
}
}
// We haven't even gotten to the start yet
if(startingSample >= sampleCount) {
// DLog(@"Skipping entire sample");
_framesDecoded += sampleCount;
framesToSkip -= sampleCount;
return NO;
}
framesToSkip = 0;
// DLog(@"Revised: %i, %i", startingSample, sampleCount);
_framesDecoded += sampleCount;
if(_outputFrames > 0) {
DLog(@"LOSING FRAMES!");
}
_outputFrames = (sampleCount - startingSample);
if(_currentOutputFrames < _outputFrames) {
_outputBuffer = (float *)realloc(_outputBuffer, _outputFrames * channels * sizeof(float));
_currentOutputFrames = _outputFrames;
}
int ch;
// samples [0 ... n]
for(ch = 0; ch < channels; ch++) {
vDSP_vflt32(&_synth.pcm.samples[ch][startingSample], 1, &_outputBuffer[ch], channels, _outputFrames);
}
float scale = (float)MAD_F_ONE;
vDSP_vsdiv(&_outputBuffer[0], 1, &scale, &_outputBuffer[0], 1, _outputFrames * channels);
// Output to a file
// FILE *f = fopen("data.raw", "a");
// fwrite(_outputBuffer, channels * 2, _outputFrames, f);
// fclose(f);
return NO;
}
- (int)decodeMPEGFrame {
if(_stream.buffer == NULL || _stream.error == MAD_ERROR_BUFLEN) {
int inputToRead;
int inputRemaining;
if(_stream.next_frame != NULL) {
inputRemaining = (int)(_stream.bufend - _stream.next_frame);
memmove(_inputBuffer, _stream.next_frame, inputRemaining);
inputToRead = INPUT_BUFFER_SIZE - inputRemaining;
} else {
inputToRead = INPUT_BUFFER_SIZE;
inputRemaining = 0;
}
long inputRead = [_source read:_inputBuffer + inputRemaining amount:INPUT_BUFFER_SIZE - inputRemaining];
if(inputRead == 0) {
memset(_inputBuffer + inputRemaining + inputRead, 0, MAD_BUFFER_GUARD);
inputRead += MAD_BUFFER_GUARD;
inputEOF = YES;
}
mad_stream_buffer(&_stream, _inputBuffer, inputRead + inputRemaining);
_stream.error = MAD_ERROR_NONE;
// DLog(@"Read stream.");
}
BOOL skippingBadFrame = NO;
if(mad_frame_decode(&_frame, &_stream) == -1) {
if(_stream.error == MAD_ERROR_BADDATAPTR) {
skippingBadFrame = YES;
} else if(MAD_RECOVERABLE(_stream.error)) {
const uint8_t *buffer = _stream.this_frame;
unsigned long buflen = _stream.bufend - _stream.this_frame;
uint32_t id3_length = 0;
// No longer need ID3Tag framework
if(10 <= buflen && 0x49 == buffer[0] && 0x44 == buffer[1] && 0x33 == buffer[2]) {
id3_length = (((buffer[6] & 0x7F) << (3 * 7)) | ((buffer[7] & 0x7F) << (2 * 7)) |
((buffer[8] & 0x7F) << (1 * 7)) | ((buffer[9] & 0x7F) << (0 * 7)));
// Add 10 bytes for ID3 header
id3_length += 10;
mad_stream_skip(&_stream, id3_length);
}
DLog(@"recoverable error");
return 0;
} else if(MAD_ERROR_BUFLEN == _stream.error && inputEOF) {
DLog(@"EOF");
return -1;
} else if(MAD_ERROR_BUFLEN == _stream.error) {
// DLog(@"Bufferlen");
return 0;
} else {
// DLog(@"Unrecoverable stream error: %s", mad_stream_errorstr(&_stream));
return -1;
}
}
if(!_firstFrame || !(_foundXingHeader && _foundVBRIHeader)) {
signed long frameDuration = mad_timer_count(_frame.header.duration, sampleRate);
if((framesToSkip - 1152 * 4) >= frameDuration) {
framesToSkip -= frameDuration;
_framesDecoded += frameDuration;
return 0;
}
}
// DLog(@"Decoded buffer.");
if(!skippingBadFrame) {
mad_synth_frame(&_synth, &_frame);
}
// DLog(@"first frame: %i", _firstFrame);
if(_firstFrame) {
_firstFrame = NO;
if(![_source seekable]) {
sampleRate = _frame.header.samplerate;
channels = MAD_NCHANNELS(&_frame.header);
switch(_frame.header.layer) {
case MAD_LAYER_I:
layer = 1;
break;
case MAD_LAYER_II:
layer = 2;
break;
case MAD_LAYER_III:
layer = 3;
break;
default:
break;
}
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
}
// DLog(@"FIRST FRAME!!! %i %i", _foundXingHeader, _foundLAMEHeader);
if(_foundXingHeader || _foundVBRIHeader) {
// DLog(@"Skipping xing header.");
return 0;
}
} else if(skippingBadFrame) {
return 0;
}
return 1;
}
- (BOOL)syncFormat {
float _sampleRate = _frame.header.samplerate;
int _channels = MAD_NCHANNELS(&_frame.header);
int _layer = 3;
switch(_frame.header.layer) {
case MAD_LAYER_I:
_layer = 1;
break;
case MAD_LAYER_II:
_layer = 2;
break;
case MAD_LAYER_III:
_layer = 3;
break;
default:
break;
}
BOOL changed = (_sampleRate != sampleRate ||
_channels != channels ||
_layer != layer);
if(changed) {
sampleRate = _sampleRate;
channels = _channels;
layer = _layer;
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
}
return changed;
}
- (AudioChunk *)readAudio {
if(!_firstFrame)
[self syncFormat];
id audioChunkClass = NSClassFromString(@"AudioChunk");
AudioChunk *chunk = nil;
for(;;) {
long framesToCopy = _outputFrames;
if(framesToCopy) {
chunk = [[audioChunkClass alloc] initWithProperties:[self properties]];
[chunk setStreamTimestamp:seconds];
[chunk assignSamples:_outputBuffer frameCount:framesToCopy];
seconds += [chunk duration];
_outputFrames = 0;
break;
}
int r = [self decodeMPEGFrame];
// DLog(@"Decoding frame: %i", r);
if(r == 0) // Recoverable error.
continue;
else if(r == -1) // Unrecoverable error
break;
if([self writeOutput]) {
return nil;
}
// DLog(@"Wrote output");
[self syncFormat];
}
metadataUpdateCount += chunk ? [chunk frameCount] : 0;
if(metadataUpdateCount >= metadataUpdateInterval) {
metadataUpdateCount -= metadataUpdateInterval;
[self updateMetadata];
}
// DLog(@"Read: %i/%i", bytesRead, size);
return chunk;
}
- (void)close {
if(_source) {
_source = nil;
}
if(_outputBuffer) {
free(_outputBuffer);
_outputBuffer = NULL;
_currentOutputFrames = 0;
}
mad_synth_finish(&_synth);
mad_frame_finish(&_frame);
mad_stream_finish(&_stream);
}
- (long)seek:(long)frame {
if(frame == _framesDecoded) {
return frame;
}
if(frame > totalFrames)
frame = totalFrames;
framesToSkip = 0;
if(_foundLAMEHeader || _foundiTunSMPB) {
if(_framesDecoded < _startPadding) {
framesToSkip = _startPadding;
}
}
if(frame < _framesDecoded) {
_framesDecoded = 0;
seconds = 0.0;
_firstFrame = YES;
if(_foundLAMEHeader || _foundiTunSMPB)
framesToSkip = _startPadding;
[_source seek:0 whence:SEEK_SET];
mad_stream_buffer(&_stream, NULL, 0);
}
framesToSkip += frame - _framesDecoded;
seconds += (double)(frame - _framesDecoded) / sampleRate;
return frame;
}
- (void)updateMetadata {
NSString *_artist = artist;
NSString *_album = album;
NSString *_title = title;
NSString *_genre = genre;
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(![_artist isEqual:artist] ||
![_album isEqual:album] ||
![_title isEqual:title] ||
![_genre isEqual:genre]) {
artist = _artist;
album = _album;
title = _title;
genre = _genre;
if(![_source seekable]) {
[self willChangeValueForKey:@"metadata"];
[self didChangeValueForKey:@"metadata"];
}
}
}
- (NSDictionary *)properties {
if(layer < 1 || layer > 3) return nil;
const NSString *layers[3] = { @"MP1", @"MP2", @"MP3" };
return @{ @"channels": @(channels),
@"bitsPerSample": @(32),
@"sampleRate": @(sampleRate),
@"floatingPoint": @(YES),
@"bitrate": @(bitrate),
@"totalFrames": @(totalFrames - (_startPadding + _endPadding)),
@"seekable": @([_source seekable]),
@"codec": layers[layer - 1],
@"endian": @"host",
@"encoding": @"lossy" };
}
- (NSDictionary *)metadata {
return @{ @"artist": artist, @"album": album, @"title": title, @"genre": genre };
}
+ (NSArray *)fileTypes {
return @[@"mp3", @"m2a", @"mpa"];
}
+ (NSArray *)mimeTypes {
return @[@"audio/mpeg", @"audio/x-mp3"];
}
+ (NSArray *)fileTypeAssociations {
return @[@[@"MPEG Audio File", @"mp3.icns", @"mp3", @"m2a", @"mpa"]];
}
+ (float)priority {
return 2.0;
}
@end