2017-10-06 09:48:52 -04:00
|
|
|
//
|
|
|
|
// Copyright (c) 2017 The Altra64 project contributors
|
2017-11-13 15:22:43 -05:00
|
|
|
// Portions (c) 2010 chillywilly (https://www.neoflash.com/forum/index.php?topic=6311.0)
|
2017-10-06 09:48:52 -04:00
|
|
|
// See LICENSE file in the project root for full license information.
|
|
|
|
//
|
|
|
|
|
2014-06-29 01:10:11 -04:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
2017-10-14 14:59:49 -04:00
|
|
|
#include <libdragon.h>
|
2014-06-29 01:10:11 -04:00
|
|
|
#include <mad.h>
|
2014-06-30 03:16:08 -04:00
|
|
|
#include "mp3.h"
|
2017-10-14 14:59:49 -04:00
|
|
|
#include "ff.h"
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
|
|
|
|
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 {
|
2017-11-13 15:22:43 -05:00
|
|
|
short left;
|
|
|
|
short right;
|
2014-06-29 01:10:11 -04:00
|
|
|
} 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;
|
2017-11-13 15:22:43 -05:00
|
|
|
static UINT readLen;
|
2014-06-29 01:10:11 -04:00
|
|
|
static int samplesRead;
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static FIL mp3File;
|
|
|
|
static FRESULT mp3Fd;
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static int mp3_seek(int offset, int whence)
|
|
|
|
{
|
|
|
|
DWORD offs = 0;
|
|
|
|
switch (whence)
|
|
|
|
{
|
|
|
|
case SEEK_SET:
|
|
|
|
offs = offset;
|
|
|
|
break;
|
|
|
|
case SEEK_CUR:
|
|
|
|
offs = mp3File.fptr + offset;
|
|
|
|
break;
|
|
|
|
case SEEK_END:
|
|
|
|
offs = f_size(&mp3File) + offset;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
f_lseek(&mp3File, offs);
|
|
|
|
return offs;
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static int mp3_size()
|
|
|
|
{
|
|
|
|
return f_size(&mp3File);
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static int mp3_read(unsigned char *ptr, int size)
|
2014-06-29 01:10:11 -04:00
|
|
|
{
|
2017-11-13 15:22:43 -05:00
|
|
|
UINT ts;
|
|
|
|
if (useReadBuffer)
|
|
|
|
{
|
|
|
|
int total = 0;
|
|
|
|
while (size)
|
|
|
|
{
|
|
|
|
if (!readLen)
|
|
|
|
{
|
|
|
|
f_read (
|
|
|
|
&mp3File, /* [IN] File object */
|
|
|
|
readBuffer, /* [OUT] Buffer to store read data */
|
|
|
|
INPUT_BUFFER_SIZE, /* [IN] Number of bytes to read */
|
|
|
|
&readLen /* [OUT] Number of bytes read */
|
|
|
|
);
|
|
|
|
readPos = 0;
|
|
|
|
if (readLen == 0)
|
|
|
|
return total; // EOF
|
|
|
|
}
|
|
|
|
int rlen = (size<readLen) ? size : readLen;
|
|
|
|
memcpy(ptr, readBuffer + readPos, rlen);
|
|
|
|
readPos += rlen;
|
|
|
|
readLen -= rlen;
|
|
|
|
ptr += rlen;
|
|
|
|
size -= rlen;
|
|
|
|
total += rlen;
|
|
|
|
}
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
f_read (
|
|
|
|
&mp3File, /* [IN] File object */
|
|
|
|
ptr, /* [OUT] Buffer to store read data */
|
|
|
|
size, /* [IN] Number of bytes to read */
|
|
|
|
&ts /* [OUT] Number of bytes read */
|
|
|
|
);
|
|
|
|
return ts;
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static int id3_tag_size(unsigned char const *buf, int remaining)
|
|
|
|
{
|
|
|
|
int size;
|
|
|
|
int exheadersize = 0;
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
if (remaining < 10)
|
|
|
|
return 0;
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
if (!strncmp((char*)buf, "ID3", 3) || !strncmp((char*)buf, "ea3", 3)) //skip past id3v2 header, which can cause a false sync to be found
|
|
|
|
{
|
2017-11-13 15:22:43 -05:00
|
|
|
unsigned int version = buf[3];
|
|
|
|
version = (size<<7) | buf[4];
|
|
|
|
unsigned int headerflags = buf[5];
|
2014-06-29 01:10:11 -04:00
|
|
|
//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;
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
|
|
|
|
if (headerflags & 0x20) //has extended header
|
|
|
|
{
|
|
|
|
exheadersize = buf[10];
|
|
|
|
exheadersize = (exheadersize<<7) | buf[11];
|
|
|
|
exheadersize = (exheadersize<<7) | buf[12];
|
|
|
|
exheadersize = (exheadersize<<7) | buf[13];
|
|
|
|
size += exheadersize;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (headerflags & 0x10) //has footer
|
2014-06-29 01:10:11 -04:00
|
|
|
size += 10;
|
|
|
|
}
|
2017-11-13 15:22:43 -05:00
|
|
|
return size;
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
//Seek next valid frame after ID3/EA3 header
|
|
|
|
//NOTE: adapted from Music prx 0.55 source
|
|
|
|
// credit goes to joek2100.
|
2017-11-13 15:22:43 -05:00
|
|
|
static int MP3_SkipHdr()
|
2014-06-29 01:10:11 -04:00
|
|
|
{
|
|
|
|
int offset = 0;
|
|
|
|
unsigned char buf[1024];
|
|
|
|
unsigned char *pBuffer;
|
|
|
|
int i;
|
|
|
|
int size = 0;
|
2017-11-13 15:22:43 -05:00
|
|
|
int exheadersize = 0;
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
offset = mp3_seek(0, SEEK_CUR);
|
|
|
|
mp3_read(buf, sizeof(buf));
|
2014-06-29 01:10:11 -04:00
|
|
|
if (!strncmp((char*)buf, "ID3", 3) || !strncmp((char*)buf, "ea3", 3)) //skip past id3v2 header, which can cause a false sync to be found
|
|
|
|
{
|
2017-11-13 15:22:43 -05:00
|
|
|
unsigned int version = buf[3];
|
|
|
|
version = (size<<7) | buf[4];
|
|
|
|
unsigned int headerflags = buf[5];
|
2014-06-29 01:10:11 -04:00
|
|
|
//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;
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
|
|
|
|
if (headerflags & 0x20) //has extended header
|
|
|
|
{
|
|
|
|
exheadersize = buf[10];
|
|
|
|
exheadersize = (exheadersize<<7) | buf[11];
|
|
|
|
exheadersize = (exheadersize<<7) | buf[12];
|
|
|
|
exheadersize = (exheadersize<<7) | buf[13];
|
|
|
|
size += exheadersize;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (headerflags & 0x10) //has footer
|
2014-06-29 01:10:11 -04:00
|
|
|
size += 10;
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
offset += size;
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
2017-11-13 15:22:43 -05:00
|
|
|
mp3_seek(offset, SEEK_SET);
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
//now seek for a sync
|
|
|
|
for ( ;; )
|
|
|
|
{
|
|
|
|
offset = mp3_seek(0, SEEK_CUR);
|
|
|
|
size = mp3_read(buf, sizeof(buf));
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2017-11-13 15:22:43 -05:00
|
|
|
mp3_seek((buf[4]<<8)+buf[5], SEEK_CUR);
|
2014-06-29 01:10:11 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
pBuffer = buf;
|
2017-11-13 15:22:43 -05:00
|
|
|
for( i = 0; i < size; i++)
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
//if this is a valid frame sync (0xe0 is for mpeg version 2.5,2+1)
|
2017-11-13 15:22:43 -05:00
|
|
|
if ( (pBuffer[i] == 0xff) && ((pBuffer[i+1] & 0xE0) == 0xE0) )
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
offset += i;
|
2017-11-13 15:22:43 -05:00
|
|
|
mp3_seek(offset, SEEK_SET);
|
2014-06-29 01:10:11 -04:00
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
}
|
2017-11-13 15:22:43 -05:00
|
|
|
//go back two bytes to catch any syncs that on the boundary
|
|
|
|
mp3_seek(-2, SEEK_CUR);
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static short convertSample(mad_fixed_t Fixed)
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
/* Clipping */
|
|
|
|
if (Fixed >= MAD_F_ONE)
|
2017-11-13 15:22:43 -05:00
|
|
|
return (32767);
|
2014-06-29 01:10:11 -04:00
|
|
|
if (Fixed <= -MAD_F_ONE)
|
2017-11-13 15:22:43 -05:00
|
|
|
return (-32768);
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
/* Conversion. */
|
2017-11-13 15:22:43 -05:00
|
|
|
Fixed = Fixed >> (MAD_F_FRACBITS - 15);
|
|
|
|
|
|
|
|
return ((short)Fixed);
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
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(bufferPos, want);
|
|
|
|
if (got <= 0)
|
|
|
|
return 1; // EOF
|
|
|
|
|
|
|
|
want -= got;
|
|
|
|
bufferPos += got;
|
|
|
|
}
|
|
|
|
return 0;
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
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? */
|
2014-06-29 01:10:11 -04:00
|
|
|
int tagsize = id3_tag_size(Stream.this_frame, Stream.bufend - Stream.this_frame);
|
2017-11-13 15:22:43 -05:00
|
|
|
if (tagsize > 0)
|
|
|
|
{
|
|
|
|
mad_stream_skip (&Stream, tagsize);
|
2014-06-29 01:10:11 -04:00
|
|
|
continue;
|
2017-11-13 15:22:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
mad_timer_add(&Timer, Frame.header.duration);
|
2017-11-13 15:22:43 -05:00
|
|
|
mad_synth_frame(&Synth, &Frame);
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static void convertLeftSamples(Sample* first, Sample* last, const mad_fixed_t* src)
|
|
|
|
{
|
|
|
|
for (Sample *dst = first; dst != last; ++dst)
|
|
|
|
dst->left = convertSample(*src++);
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static void convertRightSamples(Sample* first, Sample* last, const mad_fixed_t* src)
|
|
|
|
{
|
|
|
|
for (Sample *dst = first; dst != last; ++dst)
|
|
|
|
dst->right = convertSample(*src++);
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static void MP3_Callback(void *buffer, unsigned int samplesToWrite)
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
Sample *destination = (Sample*)buffer;
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
while (samplesToWrite > 0)
|
|
|
|
{
|
|
|
|
while (!eos && (Synth.pcm.length == 0))
|
|
|
|
decode();
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
if (eos)
|
|
|
|
{
|
|
|
|
// done
|
|
|
|
memset(destination, 0, samplesToWrite*4);
|
|
|
|
break;
|
|
|
|
}
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
unsigned int samplesAvailable = Synth.pcm.length - samplesRead;
|
2017-11-13 15:22:43 -05:00
|
|
|
if (samplesAvailable > samplesToWrite)
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
convertLeftSamples(destination, destination + samplesToWrite, &Synth.pcm.samples[0][samplesRead]);
|
|
|
|
convertRightSamples(destination, destination + samplesToWrite, &Synth.pcm.samples[1][samplesRead]);
|
|
|
|
|
|
|
|
samplesRead += samplesToWrite;
|
|
|
|
samplesToWrite = 0;
|
2017-11-13 15:22:43 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
convertLeftSamples(destination, destination + samplesAvailable, &Synth.pcm.samples[0][samplesRead]);
|
|
|
|
convertRightSamples(destination, destination + samplesAvailable, &Synth.pcm.samples[1][samplesRead]);
|
|
|
|
|
|
|
|
destination += samplesAvailable;
|
|
|
|
samplesToWrite -= samplesAvailable;
|
|
|
|
|
|
|
|
samplesRead = 0;
|
2017-11-13 15:22:43 -05:00
|
|
|
decode();
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static void MP3_Init()
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
/* First the structures used by libmad must be initialized. */
|
|
|
|
mad_stream_init(&Stream);
|
2017-11-13 15:22:43 -05:00
|
|
|
mad_header_init(&Header);
|
2014-06-29 01:10:11 -04:00
|
|
|
mad_frame_init(&Frame);
|
|
|
|
mad_synth_init(&Synth);
|
|
|
|
mad_timer_reset(&Timer);
|
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static void MP3_Exit()
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
mad_synth_finish(&Synth);
|
|
|
|
mad_header_finish(&Header);
|
|
|
|
mad_frame_finish(&Frame);
|
|
|
|
mad_stream_finish(&Stream);
|
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
static void MP3_GetInfo(long long *samples, int *rate)
|
|
|
|
{
|
|
|
|
unsigned long FrameCount = 0;
|
2014-06-29 01:10:11 -04:00
|
|
|
int bufferSize = 1024*512;
|
|
|
|
unsigned char *localBuffer;
|
2017-11-13 15:22:43 -05:00
|
|
|
long bytesread = 0;
|
2014-06-29 01:10:11 -04:00
|
|
|
double totalBitrate = 0.0;
|
|
|
|
double mediumBitrate = 0.0;
|
2017-11-13 15:22:43 -05:00
|
|
|
struct mad_stream stream;
|
|
|
|
struct mad_header header;
|
|
|
|
long size = mp3_size();
|
|
|
|
long count = size;
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
mad_stream_init (&stream);
|
|
|
|
mad_header_init (&header);
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
localBuffer = (unsigned char *)malloc(bufferSize);
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
for (int i=0; i<3; i++)
|
|
|
|
{
|
2014-06-29 01:10:11 -04:00
|
|
|
memset(localBuffer, 0, bufferSize);
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
if (count > bufferSize)
|
|
|
|
bytesread = mp3_read(localBuffer, bufferSize);
|
|
|
|
else
|
|
|
|
bytesread = mp3_read(localBuffer, count);
|
|
|
|
count -= bytesread;
|
|
|
|
if (!bytesread)
|
|
|
|
break; // ran out of data
|
|
|
|
|
|
|
|
mad_stream_buffer (&stream, localBuffer, bytesread);
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
{
|
|
|
|
if (mad_header_decode(&header, &stream) == -1)
|
|
|
|
{
|
|
|
|
if (stream.buffer == NULL || stream.error == MAD_ERROR_BUFLEN)
|
2014-06-29 01:10:11 -04:00
|
|
|
break;
|
2017-11-13 15:22:43 -05:00
|
|
|
else if (MAD_RECOVERABLE(stream.error))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (FrameCount++ == 0)
|
|
|
|
*rate = header.samplerate;
|
|
|
|
totalBitrate += header.bitrate;
|
|
|
|
}
|
|
|
|
}
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
mediumBitrate = totalBitrate / (double)FrameCount;
|
|
|
|
int secs = size * 8 / mediumBitrate;
|
2017-11-13 15:22:43 -05:00
|
|
|
*samples = *rate * secs;
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
mad_header_finish (&header);
|
|
|
|
mad_stream_finish (&stream);
|
2014-06-29 01:10:11 -04:00
|
|
|
|
|
|
|
if (localBuffer)
|
2017-11-13 15:22:43 -05:00
|
|
|
free(localBuffer);
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
mp3_seek(0, SEEK_SET);
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
void mp3_Start(char *fname, long long *samples, int *rate, int *channels)
|
|
|
|
{
|
|
|
|
|
|
|
|
mp3Fd = f_open(&mp3File, fname, FA_READ);
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
if (mp3Fd == FR_OK)
|
|
|
|
{
|
|
|
|
useReadBuffer = 0;
|
|
|
|
MP3_GetInfo(samples, rate);
|
|
|
|
*channels = 2;
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
MP3_Init();
|
|
|
|
MP3_SkipHdr();
|
|
|
|
eos = readLen = readPos = 0;
|
|
|
|
useReadBuffer = 1;
|
|
|
|
return;
|
|
|
|
}
|
2014-06-29 01:10:11 -04:00
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
*samples = 0;
|
|
|
|
return;
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
void mp3_Stop(void)
|
|
|
|
{
|
|
|
|
MP3_Exit();
|
|
|
|
if (mp3Fd == FR_OK)
|
|
|
|
{
|
|
|
|
f_close(&mp3File);
|
|
|
|
}
|
|
|
|
mp3Fd = FR_NO_FILE;
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|
|
|
|
|
2017-11-13 15:22:43 -05:00
|
|
|
int mp3_Update(char *buf, int bytes)
|
|
|
|
{
|
|
|
|
MP3_Callback(buf, bytes/4);
|
|
|
|
return eos ? 0 : 1;
|
2014-06-29 01:10:11 -04:00
|
|
|
}
|