mirror of https://github.com/parasyte/alt64
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
399 lines
10 KiB
399 lines
10 KiB
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <stdint.h> |
|
#include <mad.h> |
|
#include "fat.h" |
|
#include "mp3.h" |
|
|
|
#include <libdragon.h> |
|
|
|
static struct mad_stream Stream; |
|
static struct mad_header Header; |
|
static struct mad_frame Frame; |
|
static struct mad_synth Synth; |
|
static mad_timer_t Timer; |
|
|
|
typedef struct { |
|
short left; |
|
short right; |
|
} Sample; |
|
|
|
static int eos; |
|
|
|
#define INPUT_BUFFER_SIZE 2048 |
|
static unsigned char fileBuffer[INPUT_BUFFER_SIZE]; |
|
static unsigned char readBuffer[INPUT_BUFFER_SIZE]; |
|
static int useReadBuffer; |
|
static int readPos; |
|
static unsigned int readLen; |
|
static int samplesRead; |
|
|
|
char * mp3File; |
|
char * mp3Fd; |
|
long mp3File_fptr=0; |
|
int mp3File_fsize; |
|
|
|
extern int gBrowser; |
|
extern char path[1024]; |
|
|
|
extern void c2wstrcpy(void *dst, void *src); |
|
extern void c2wstrcat(void *dst, void *src); |
|
|
|
static int mp3_seek(char* fd, int offset, int whence) { |
|
//todo filesize and mp3File_fptr; |
|
long offs = 0; |
|
// libff routine |
|
switch (whence) |
|
{ |
|
case SEEK_SET: |
|
offs = offset; |
|
break; |
|
case SEEK_CUR: |
|
offs = mp3File_fptr + offset; |
|
break; |
|
case SEEK_END: |
|
offs = mp3File_fsize + offset; |
|
break; |
|
} |
|
//f_lseek(&mp3File, offs); |
|
mp3File_fptr=offs; |
|
|
|
return offs; |
|
} |
|
|
|
static int mp3_size(char* fd) { |
|
FatRecord rec_tmpf; |
|
u8 resp=0; |
|
resp = fatOpenFileByeName(fd, 0); //err if not found ^^ |
|
|
|
int fsize = file.sec_available*512; //fsize in bytes |
|
mp3File_fsize = fsize; |
|
//todo filesize |
|
return mp3File_fsize; |
|
} |
|
|
|
static void _f_read(char* fname, unsigned char *readBuffer, int size){ |
|
/* |
|
FatRecord rec_tmpf; |
|
u8 resp=0; |
|
resp = fatOpenFileByeName(fname, 0); //err if not found ^^ |
|
|
|
int fsize = file.sec_available*512; //fsize in bytes |
|
mp3File_fsize = fsize; |
|
|
|
//injecting in buffer... slow but working :/ |
|
if(file.sec_available*512>=size){ |
|
resp = fatReadPartialFile(readBuffer, size/512, mp3File_fptr); |
|
//resp = fatReadFile(readBuffer+mp3File_fptr, size/512);//file.sec_available); |
|
mp3File_fptr+=size; |
|
|
|
} |
|
//dma_write_s(buffer, 0xb0000000, fsize); |
|
*/ |
|
} |
|
|
|
static int mp3_read(char* fd, unsigned char *ptr, int size) |
|
{ |
|
int ts=size; |
|
_f_read(fd, ptr, size); |
|
return ts; |
|
} |
|
|
|
static int id3_tag_size(unsigned char const *buf, int remaining) { |
|
int size; |
|
|
|
if (remaining < 10) |
|
return 0; |
|
|
|
if (!strncmp((char*)buf, "ID3", 3) || !strncmp((char*)buf, "ea3", 3)) //skip past id3v2 header, which can cause a false sync to be found |
|
{ |
|
//get the real size from the syncsafe int |
|
size = buf[6]; |
|
size = (size<<7) | buf[7]; |
|
size = (size<<7) | buf[8]; |
|
size = (size<<7) | buf[9]; |
|
|
|
size += 10; |
|
|
|
if (buf[5] & 0x10) //has footer |
|
size += 10; |
|
} |
|
return size; |
|
} |
|
|
|
//Seek next valid frame after ID3/EA3 header |
|
//NOTE: adapted from Music prx 0.55 source |
|
// credit goes to joek2100. |
|
static int MP3_SkipHdr(char* fd) |
|
{ |
|
int offset = 0; |
|
unsigned char buf[1024]; |
|
unsigned char *pBuffer; |
|
int i; |
|
int size = 0; |
|
|
|
offset = mp3_seek(fd, 0, SEEK_CUR); |
|
mp3_read(fd, buf, sizeof(buf)); |
|
if (!strncmp((char*)buf, "ID3", 3) || !strncmp((char*)buf, "ea3", 3)) //skip past id3v2 header, which can cause a false sync to be found |
|
{ |
|
//get the real size from the syncsafe int |
|
size = buf[6]; |
|
size = (size<<7) | buf[7]; |
|
size = (size<<7) | buf[8]; |
|
size = (size<<7) | buf[9]; |
|
|
|
size += 10; |
|
|
|
if (buf[5] & 0x10) //has footer |
|
size += 10; |
|
|
|
offset += size; |
|
} |
|
mp3_seek(fd, offset, SEEK_SET); |
|
|
|
//now seek for a sync |
|
while(1) { |
|
offset = mp3_seek(fd, 0, SEEK_CUR); |
|
size = mp3_read(fd, buf, sizeof(buf)); |
|
|
|
if (size <= 2)//at end of file |
|
return -1; |
|
|
|
if (!strncmp((char*)buf, "EA3", 3)) //oma mp3 files have non-safe ints in the EA3 header |
|
{ |
|
mp3_seek(fd, (buf[4]<<8)+buf[5], SEEK_CUR); |
|
continue; |
|
} |
|
|
|
pBuffer = buf; |
|
for( i = 0; i < size; i++) { |
|
//if this is a valid frame sync (0xe0 is for mpeg version 2.5,2+1) |
|
if ( (pBuffer[i] == 0xff) && ((pBuffer[i+1] & 0xE0) == 0xE0) ) { |
|
offset += i; |
|
mp3_seek(fd, offset, SEEK_SET); |
|
return offset; |
|
} |
|
} |
|
//go back two bytes to catch any syncs that on the boundary |
|
mp3_seek(fd, -2, SEEK_CUR); |
|
} |
|
} |
|
|
|
static short convertSample(mad_fixed_t Fixed) { |
|
/* Clipping */ |
|
if (Fixed >= MAD_F_ONE) |
|
return (32767); |
|
if (Fixed <= -MAD_F_ONE) |
|
return (-32768); |
|
|
|
/* Conversion. */ |
|
Fixed = Fixed >> (MAD_F_FRACBITS - 15); |
|
|
|
return ((short)Fixed); |
|
} |
|
|
|
static int fillFileBuffer() { |
|
int leftOver = Stream.bufend - Stream.next_frame; |
|
int want = INPUT_BUFFER_SIZE - leftOver; |
|
|
|
// move left-over bytes |
|
if (leftOver > 0) |
|
memmove(fileBuffer, fileBuffer + want, leftOver); |
|
|
|
// fill remainder of buffer |
|
unsigned char* bufferPos = fileBuffer + leftOver; |
|
while (want > 0) { |
|
int got = mp3_read(mp3Fd, bufferPos, want); |
|
if (got <= 0) |
|
return 1; // EOF |
|
|
|
want -= got; |
|
bufferPos += got; |
|
} |
|
return 0; |
|
} |
|
|
|
static void decode() { |
|
while (mad_frame_decode(&Frame, &Stream) == -1) { |
|
if ((Stream.error == MAD_ERROR_BUFLEN) || (Stream.error == MAD_ERROR_BUFPTR)) { |
|
if (fillFileBuffer()) { |
|
eos = 1; |
|
break; |
|
} |
|
mad_stream_buffer(&Stream, fileBuffer, INPUT_BUFFER_SIZE); |
|
} |
|
else if (Stream.error == MAD_ERROR_LOSTSYNC) { |
|
/* LOSTSYNC - due to ID3 tags? */ |
|
int tagsize = id3_tag_size(Stream.this_frame, Stream.bufend - Stream.this_frame); |
|
if (tagsize > 0) { |
|
mad_stream_skip (&Stream, tagsize); |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
mad_timer_add(&Timer, Frame.header.duration); |
|
mad_synth_frame(&Synth, &Frame); |
|
} |
|
|
|
static void convertLeftSamples(Sample* first, Sample* last, const mad_fixed_t* src) { |
|
for (Sample *dst = first; dst != last; ++dst) |
|
dst->left = convertSample(*src++); |
|
} |
|
|
|
static void convertRightSamples(Sample* first, Sample* last, const mad_fixed_t* src) { |
|
for (Sample *dst = first; dst != last; ++dst) |
|
dst->right = convertSample(*src++); |
|
} |
|
|
|
static void MP3_Callback(void *buffer, unsigned int samplesToWrite) { |
|
Sample *destination = (Sample*)buffer; |
|
|
|
while (samplesToWrite > 0) { |
|
while (!eos && (Synth.pcm.length == 0)) |
|
decode(); |
|
|
|
if (eos) { |
|
// done |
|
memset(destination, 0, samplesToWrite*4); |
|
break; |
|
} |
|
|
|
unsigned int samplesAvailable = Synth.pcm.length - samplesRead; |
|
if (samplesAvailable > samplesToWrite) { |
|
convertLeftSamples(destination, destination + samplesToWrite, &Synth.pcm.samples[0][samplesRead]); |
|
convertRightSamples(destination, destination + samplesToWrite, &Synth.pcm.samples[1][samplesRead]); |
|
|
|
samplesRead += samplesToWrite; |
|
samplesToWrite = 0; |
|
} |
|
else { |
|
convertLeftSamples(destination, destination + samplesAvailable, &Synth.pcm.samples[0][samplesRead]); |
|
convertRightSamples(destination, destination + samplesAvailable, &Synth.pcm.samples[1][samplesRead]); |
|
|
|
destination += samplesAvailable; |
|
samplesToWrite -= samplesAvailable; |
|
|
|
samplesRead = 0; |
|
decode(); |
|
} |
|
} |
|
} |
|
|
|
static void MP3_Init() { |
|
/* First the structures used by libmad must be initialized. */ |
|
mad_stream_init(&Stream); |
|
mad_header_init(&Header); |
|
mad_frame_init(&Frame); |
|
mad_synth_init(&Synth); |
|
mad_timer_reset(&Timer); |
|
} |
|
|
|
static void MP3_Exit() { |
|
mad_synth_finish(&Synth); |
|
mad_header_finish(&Header); |
|
mad_frame_finish(&Frame); |
|
mad_stream_finish(&Stream); |
|
} |
|
|
|
static void MP3_GetInfo(long long *samples, int *rate) { |
|
unsigned long FrameCount = 0; |
|
int bufferSize = 1024*512; |
|
unsigned char *localBuffer; |
|
long red = 0; |
|
double totalBitrate = 0.0; |
|
double mediumBitrate = 0.0; |
|
struct mad_stream stream; |
|
struct mad_header header; |
|
int size = mp3_size(mp3Fd); |
|
long count = size; |
|
|
|
mad_stream_init (&stream); |
|
mad_header_init (&header); |
|
|
|
localBuffer = (unsigned char *)malloc(bufferSize); |
|
|
|
for (int i=0; i<3; i++) { |
|
memset(localBuffer, 0, bufferSize); |
|
|
|
if (count > bufferSize) |
|
red = mp3_read(mp3Fd, localBuffer, bufferSize); |
|
else |
|
red = mp3_read(mp3Fd, localBuffer, count); |
|
count -= red; |
|
if (!red) |
|
break; // ran out of data |
|
|
|
mad_stream_buffer (&stream, localBuffer, red); |
|
|
|
while (1) { |
|
if (mad_header_decode(&header, &stream) == -1) { |
|
if (stream.buffer == NULL || stream.error == MAD_ERROR_BUFLEN) { |
|
break; |
|
} |
|
else if (MAD_RECOVERABLE(stream.error)) { |
|
continue; |
|
} |
|
else { |
|
break; |
|
} |
|
} |
|
if (FrameCount++ == 0) |
|
*rate = header.samplerate; |
|
totalBitrate += header.bitrate; |
|
} |
|
} |
|
|
|
mediumBitrate = totalBitrate / (double)FrameCount; |
|
int secs = size * 8 / mediumBitrate; |
|
*samples = *rate * secs; |
|
|
|
mad_header_finish (&header); |
|
mad_stream_finish (&stream); |
|
|
|
if (localBuffer) |
|
free(localBuffer); |
|
|
|
mp3_seek(mp3Fd, 0, SEEK_SET); |
|
} |
|
|
|
|
|
void start_mp3(char *fname, long long *samples, int *rate, int *channels) { |
|
sprintf(mp3Fd, "%s", fname); |
|
|
|
//if (mp3Fd[0]!=0) |
|
//{ |
|
useReadBuffer = 0; |
|
MP3_GetInfo(samples, rate); |
|
*channels = 2; |
|
|
|
MP3_Init(); |
|
MP3_SkipHdr(mp3Fd); |
|
eos = readLen = readPos = 0; |
|
useReadBuffer = 1; |
|
return; |
|
//} |
|
|
|
//*samples = 0; |
|
} |
|
|
|
void stop_mp3(void) { |
|
MP3_Exit(); |
|
mp3File_fptr=0; |
|
/* |
|
if (mp3Fd > 0) |
|
{ |
|
if (gBrowser) |
|
f_close(&mp3File); |
|
else |
|
dfs_close(mp3Fd); |
|
} |
|
mp3Fd = -1; |
|
*/ |
|
} |
|
|
|
int update_mp3(char *buf, int bytes) { |
|
MP3_Callback(buf, bytes/4); |
|
return eos ? 0 : 1; |
|
}
|
|
|