forums.ps2dev.org Forum Index forums.ps2dev.org
Homebrew PS2, PSP & PS3 Development Discussions
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

MP3 Buffered Playback

 
Post new topic   Reply to topic    forums.ps2dev.org Forum Index -> PSP Development
View previous topic :: View next topic  
Author Message
AyAn4m1



Joined: 26 Sep 2005
Posts: 15

PostPosted: Sat Feb 25, 2006 6:40 am    Post subject: MP3 Buffered Playback Reply with quote

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
View user's profile Send private message
Jim



Joined: 02 Jul 2005
Posts: 487
Location: Sydney

PostPosted: Sat Feb 25, 2006 4:51 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Blue



Joined: 25 Feb 2006
Posts: 5

PostPosted: Sat Feb 25, 2006 9:21 pm    Post subject: Reply with quote

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
View user's profile Send private message
.: Smerity :.



Joined: 04 Feb 2006
Posts: 9

PostPosted: Mon Feb 27, 2006 6:53 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website MSN Messenger
AyAn4m1



Joined: 26 Sep 2005
Posts: 15

PostPosted: Fri Mar 03, 2006 7:09 am    Post subject: Reply with quote

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
View user's profile Send private message
RCON



Joined: 03 Aug 2005
Posts: 16

PostPosted: Sat Mar 04, 2006 7:55 am    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
PeterM



Joined: 31 Dec 2005
Posts: 125
Location: Edinburgh, UK

PostPosted: Sat Mar 04, 2006 1:15 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
AyAn4m1



Joined: 26 Sep 2005
Posts: 15

PostPosted: Sun Mar 05, 2006 1:24 am    Post subject: Reply with quote

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
View user's profile Send private message
PeterM



Joined: 31 Dec 2005
Posts: 125
Location: Edinburgh, UK

PostPosted: Sun Mar 05, 2006 1:29 am    Post subject: Reply with quote

Cool, I'm glad it works.
Back to top
View user's profile Send private message Visit poster's website
shifty



Joined: 16 Jun 2005
Posts: 32
Location: MIT

PostPosted: Thu Aug 31, 2006 7:14 am    Post subject: peterm's libmad mp3 code and latency Reply with quote

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
View user's profile Send private message Visit poster's website
Warren



Joined: 24 Jan 2004
Posts: 173
Location: San Diego, CA

PostPosted: Thu Aug 31, 2006 1:29 pm    Post subject: Reply with quote

Try looking at the PSPMediaCenter source in the PSPWARE repository. It does this.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    forums.ps2dev.org Forum Index -> PSP Development All times are GMT + 10 Hours
Page 1 of 1

 
Jump to:  
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