 |
forums.ps2dev.org Homebrew PS2, PSP & PS3 Development Discussions
|
| View previous topic :: View next topic |
| Author |
Message |
AyAn4m1
Joined: 26 Sep 2005 Posts: 15
|
Posted: Sat Feb 25, 2006 6:40 am Post subject: MP3 Buffered Playback |
|
|
| My goal was to get an mp3 to playback using libmad. This was fairly simple to implement, but my code was reading the entire mp3 into memory. This obviously didn't work out for larger files, so I thought I could adapt my code to buffer the mp3 in chunks. I rewrote the code so that if the input buffer was emptied, it would call sceIoRead and read ahead the next X bytes, X being the length of the input buffer. Playback works, but there is a skip whenever sceIoRead is called. I have tried calling sceIoRead on another thread, decoding the audio in an audio callback, decoding the audio in a thread and writing to the pcm buffer with a callback, not using a callback at all, using sceIoRead, fread, and bstdfile. None of these make any difference; there is still a skip or crackle whenever the memory stick is accessed. I don't know what I'm doing wrong, but I have seen applications that manage to buffer an MP3 without skipping on playback. Any advice? |
|
| Back to top |
|
 |
Jim

Joined: 02 Jul 2005 Posts: 487 Location: Sydney
|
Posted: Sat Feb 25, 2006 4:51 pm Post subject: |
|
|
Try double buffering - fill one buffer and play the other, swapping buffers when the playback hits the end.
Jim _________________ http://www.dbfinteractive.com |
|
| Back to top |
|
 |
Blue
Joined: 25 Feb 2006 Posts: 5
|
Posted: Sat Feb 25, 2006 9:21 pm Post subject: |
|
|
I'm having the same problem, I've tried double buffering, triple buffering, using two threads and a stack buffer in between, calling the sceouput function myself and tweaking buffer sizes, but i still get crackles when the PSP is reading from the MS, or missaligned frames. I'm starting to think that the read action is blocking certain other threads, or causes a performance drop.
My buffer is always at 90% full, an the play thread just reads from the buffer and feeds it to the pcm buffer. When i see the MS light blink I can hear the crackles appear in the music. I've tried using stdio functions, buffered reads (bstdfile) and even Async reads. |
|
| Back to top |
|
 |
.: Smerity :.
Joined: 04 Feb 2006 Posts: 9
|
Posted: Mon Feb 27, 2006 6:53 pm Post subject: |
|
|
I too have had this problem.
Getting buffered Mp3s to work is quite difficult for me, and I haven't achieved it yet.
In my attempts, I had a look at PSPRadio's source, as they read from streams from both the Memory Stick and the net (streaming radio)
I had a look at PSPRadio's source, and they use bstdfile (a buffered interface for fread) and have modified their input to libmad appropriately. It was quite lost on me though, first because I'm not confident in C++ (I write in C mainly), and second because I'm just not that much of a great coder
So yeah, if any of you guys can get this solved, I'd greatly appreciate it.
To note, the SVN repository for PSPRadio is here -
http://svn.berlios.de/wsvn/pspradio/?sc=0
And all the code related to the Mp3 streaming is in \SharedLib\PSPApp |
|
| Back to top |
|
 |
AyAn4m1
Joined: 26 Sep 2005 Posts: 15
|
Posted: Fri Mar 03, 2006 7:09 am Post subject: |
|
|
| Yeah, I've tried double buffering already, and it just doesn't work... it seems like a lot of people are having this problem, and noone has a real solution. For the record Smerity, both myself and Blue have tried using bstdfile and it makes no difference. I don't know how pspradio did it, but does anyone have an answer? |
|
| Back to top |
|
 |
RCON
Joined: 03 Aug 2005 Posts: 16
|
Posted: Sat Mar 04, 2006 7:55 am Post subject: |
|
|
I attempted to rebuild the audio engine for PSP Rhythm using double buffers and that didn't work either. What I had to do is use the audio callback to time my audio engine so it would put sample in exactly when it needed to.
Maybe you need to try something like that. What does the data look like when you read it from the memory stick? if it is uncompressed already in your buffer then using the audio callback should work.
-Louie |
|
| Back to top |
|
 |
PeterM
Joined: 31 Dec 2005 Posts: 125 Location: Edinburgh, UK
|
Posted: Sat Mar 04, 2006 1:15 pm Post subject: |
|
|
Here's the code I used when I tried streaming an mp3 from the memory stick. It works, but it's pretty slow (maybe about 25% of the CPU time? I never benchmarked).
Read the code from the bottom of the file to the top (high level to low level) and it'll be easier to follow.
| Code: | #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/unistd.h>
#include <pspaudiolib.h>
#include <pspdebug.h>
#include <pspkernel.h>
#include <pspmoduleinfo.h>
#include <psppower.h>
#include <pspctrl.h>
#include "mad.h"
PSP_MODULE_INFO("Test", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
namespace test
{
namespace main
{
static int exitCallback(int arg1, int arg2, void* common)
{
// Exit.
sceKernelExitGame();
return 0;
}
static int powerCallback(int unknown, int powerInfo, void* common)
{
return 0;
}
static int callbackThread(SceSize args, void *argp)
{
// Register the exit callback.
const SceUID exitCallbackID = sceKernelCreateCallback("exitCallback", exitCallback, NULL);
sceKernelRegisterExitCallback(exitCallbackID);
// Register the power callback.
const SceUID powerCallbackID = sceKernelCreateCallback("powerCallback", powerCallback, NULL);
scePowerRegisterCallback(0, powerCallbackID);
// Sleep and handle callbacks.
sceKernelSleepThreadCB();
return 0;
}
static int setUpCallbackThread(void)
{
const int thid = sceKernelCreateThread("callbackThread", callbackThread, 0x11, 0xFA0, 0, 0);
if (thid >= 0)
sceKernelStartThread(thid, 0, 0);
return thid;
}
struct Sample
{
short left;
short right;
};
static SceUID file = 0;
static unsigned int fileSize = 1;
static unsigned int filePos = 0;
static mad_stream stream;
static mad_frame frame;
static mad_synth synth;
static unsigned char fileBuffer[2048];
static unsigned int samplesRead;
static void fillFileBuffer()
{
// Open the file if it's not open.
if (file <= 0)
{
char cd[1024];
memset(cd, 0, sizeof(cd));
getcwd(cd, sizeof(cd) - 1);
char fileName[1024];
memset(fileName, 0, sizeof(fileName));
snprintf(fileName, sizeof(fileName) - 1, "%s/%s", cd, "01.mp3");
pspDebugScreenPrintf("Opening %s... ", fileName);
file = sceIoOpen(fileName, PSP_O_RDONLY, 777);
if (file <= 0)
{
pspDebugScreenPrintf("Failed (%d).\n", file);
return;
}
else
{
pspDebugScreenPrintf("OK (%d).\n", file);
}
// Get the size.
fileSize = sceIoLseek(file, 0, SEEK_END);
sceIoLseek(file, 0, SEEK_SET);
}
// Find out how much to keep and how much to fill.
const unsigned int bytesToKeep = stream.bufend - stream.next_frame;
unsigned int bytesToFill = sizeof(fileBuffer) - bytesToKeep;
pspDebugScreenPrintf("bytesToFill = %u, bytesToKeep = %u.\n", bytesToFill, bytesToKeep);
// Want to keep any bytes?
if (bytesToKeep)
{
// Copy the tail to the head.
memmove(fileBuffer, fileBuffer + sizeof(fileBuffer) - bytesToKeep, bytesToKeep);
}
// Read into the rest of the file buffer.
unsigned char* bufferPos = fileBuffer + bytesToKeep;
while (bytesToFill > 0)
{
// Read some.
pspDebugScreenPrintf("Reading %u bytes...\n", bytesToFill);
const unsigned int bytesRead = sceIoRead(file, bufferPos, bytesToFill);
// EOF?
if (bytesRead == 0)
{
pspDebugScreenPrintf("End of file.\n");
sceIoLseek(file, 0, SEEK_SET);
filePos = 0;
continue;
}
// Adjust where we're writing to.
bytesToFill -= bytesRead;
bufferPos += bytesRead;
filePos += bytesRead;
pspDebugScreenPrintf("Read %u bytes from the file, %u left to fill.\n", bytesRead, bytesToFill);
pspDebugScreenPrintf("%u%%.\n", filePos * 100 / fileSize);
}
}
static void decode()
{
// While we need to fill the buffer...
while (
(mad_frame_decode(&frame, &stream) == -1) &&
((stream.error == MAD_ERROR_BUFLEN) || (stream.error == MAD_ERROR_BUFPTR))
)
{
// Fill up the remainder of the file buffer.
fillFileBuffer();
// Give new buffer to the stream.
mad_stream_buffer(&stream, fileBuffer, sizeof(fileBuffer));
}
// Synth the frame.
mad_synth_frame(&synth, &frame);
}
static inline short convertSample(mad_fixed_t sample)
{
/* round */
sample += (1L << (MAD_F_FRACBITS - 16));
/* clip */
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
/* quantize */
return sample >> (MAD_F_FRACBITS + 1 - 16);
}
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 fillOutputBuffer(void* buffer, unsigned int samplesToWrite, void* userData)
{
// Where are we writing to?
Sample* destination = static_cast<Sample*> (buffer);
// While we've got samples to write...
while (samplesToWrite > 0)
{
// Enough samples available?
const unsigned int samplesAvailable = synth.pcm.length - samplesRead;
if (samplesAvailable > samplesToWrite)
{
// Write samplesToWrite samples.
convertLeftSamples(destination, destination + samplesToWrite, &synth.pcm.samples[0][samplesRead]);
convertRightSamples(destination, destination + samplesToWrite, &synth.pcm.samples[1][samplesRead]);
// We're still using the same PCM data.
samplesRead += samplesToWrite;
// Done.
samplesToWrite = 0;
}
else
{
// Write samplesAvailable samples.
convertLeftSamples(destination, destination + samplesAvailable, &synth.pcm.samples[0][samplesRead]);
convertRightSamples(destination, destination + samplesAvailable, &synth.pcm.samples[1][samplesRead]);
// We need more PCM data.
samplesRead = 0;
decode();
// We've still got more to write.
destination += samplesAvailable;
samplesToWrite -= samplesAvailable;
}
}
}
}
}
using namespace test;
using namespace test::main;
int main(int argc, char *argv[])
{
// Set up the callback thread.
setUpCallbackThread();
// Initialise the debug screen.
pspDebugScreenInit();
// Set up MAD.
pspDebugScreenPrintf("Setting up MAD... ");
mad_stream_init(&stream);
mad_frame_init(&frame);
mad_synth_init(&synth);
pspDebugScreenPrintf("OK.\n");
// Initialise the audio system.
pspAudioInit();
// Set the channel callback.
pspDebugScreenPrintf("Decoding...\n");
pspAudioSetChannelCallback(0, fillOutputBuffer, 0);
// Wait for a button press.
SceCtrlData pad;
memset(&pad, 0, sizeof(pad));
while (pad.Buttons != 0)
{
sceCtrlReadBufferPositive(&pad, 1);
}
while (pad.Buttons == 0)
{
sceCtrlReadBufferPositive(&pad, 1);
}
while (pad.Buttons != 0)
{
sceCtrlReadBufferPositive(&pad, 1);
}
// Shut down audio.
pspAudioEnd();
// Shut down MAD.
mad_synth_finish(&synth);
mad_frame_finish(&frame);
mad_stream_finish(&stream);
// Quit.
sceKernelExitGame();
return 0;
} |
Useful links to the MAD mailing list archives:
http://www.mars.org/mailman/public/mad-dev/2000-April/000007.html
http://www.mars.org/mailman/public/mad-dev/2000-September/000091.html
Side note: MAD seems to be compiled without optimization - maybe MAD's Makefile should have -O3 in the CFLAGS?
Hope this helps yas,
Pete |
|
| Back to top |
|
 |
AyAn4m1
Joined: 26 Sep 2005 Posts: 15
|
Posted: Sun Mar 05, 2006 1:24 am Post subject: |
|
|
| PeterM, thank you SO much for your help... it works perfectly now. It was a problem in the way the functions were being called (the order) but to anyone else with this problem, look at Peter's code and you should have no trouble getting it to work. Thanks again! |
|
| Back to top |
|
 |
PeterM
Joined: 31 Dec 2005 Posts: 125 Location: Edinburgh, UK
|
Posted: Sun Mar 05, 2006 1:29 am Post subject: |
|
|
| Cool, I'm glad it works. |
|
| Back to top |
|
 |
shifty
Joined: 16 Jun 2005 Posts: 32 Location: MIT
|
Posted: Thu Aug 31, 2006 7:14 am Post subject: peterm's libmad mp3 code and latency |
|
|
hi all!
I'm using a variation on peterm's code and it's working very nicely,
except that there seems to be a huge amount of latency between
the file starting to play and actually hearing the sound. Counting samples
into the file, I found that for the first 7 seconds of the playback, mad_synth_frame is returning empty buffers. And it feels like that long, too.
Is that usual behavior? Anyone else seeing it? Is there some variable in the libmad code that could shorten that? Or how about a variable that is set when the synth if finally decoding valid data? i've been searching through the source myself, but haven't found these myself.
Thank you if you can help. I have a really cool app I'm working on that's
commercial and open source, so I'm excited to release it...the scene will really benefit! |
|
| Back to top |
|
 |
Warren
Joined: 24 Jan 2004 Posts: 173 Location: San Diego, CA
|
Posted: Thu Aug 31, 2006 1:29 pm Post subject: |
|
|
| Try looking at the PSPMediaCenter source in the PSPWARE repository. It does this. |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|