streaming mp3 :)

Discuss the development of new homebrew software, tools and libraries.

Moderators: cheriff, TyRaNiD

Post Reply
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

streaming mp3 :)

Post by Ghoti »

Hi folks,

i want to stream mp3 music files in my application.
The way i do it now is that the whole mp3 is loaded into memory and played. However since my game has expanded alot i only have like 3 to 6 mb of spare memory to play music. Since alot of player have larger files in there music dir, the game crashes when such a large file is loaded.

What is want it to stream the mp3 so that for example there is only 1 mb needed for playing every mp3 there is no matter how large it is.

is this possible? what does this do with speed? what libraries do i need? are there any examples?

hope to get some replies about this,

greets,

ghoti
TyRaNiD
Posts: 907
Joined: Sun Jan 18, 2004 12:23 am

Post by TyRaNiD »

I think libmad can consume a stream of mp3 on the fly so to speak, in fact it would be mostly useless if it didn't. Try doing some searchs for streaming with libmad as I am sure there will be samples
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Ah yes thanks, I have search and found out that it is called buffering :) and also some sources and examples so i'll try it out.

thanks and great work on the psplink app btw
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Hi me again,

i have looked at the source of the pspmediacenter but if i am correct it loads the entire mp3 file into memory :(
is there a quick way of modifying it? (my mp3 codec skills are non existing :S )

another question:
at this thread: http://forums.ps2dev.org/viewtopic.php? ... d+playback PeterM has posted an example. in it i see this line:

Code: Select all

pspAudioSetChannelCallback(0, fillOutputBuffer, 0); 
does it set the callback so that you don't have to call it every loop iteration? or do i not understand correctly? and if so does it every callback check the fillOutputBuffer to see what has to be done?

also is it this way better then to do a custom callback every frameloop of the game?

hope to get some answer and thanks in advance
User avatar
Raphael
Posts: 646
Joined: Tue Jan 17, 2006 4:54 pm
Location: Germany
Contact:

Post by Raphael »

Ghoti wrote:Hi me again,
another question:
at this thread: http://forums.ps2dev.org/viewtopic.php? ... d+playback PeterM has posted an example. in it i see this line:

Code: Select all

pspAudioSetChannelCallback(0, fillOutputBuffer, 0); 
does it set the callback so that you don't have to call it every loop iteration? or do i not understand correctly? and if so does it every callback check the fillOutputBuffer to see what has to be done?

Code: Select all

void pspAudioSetChannelCallback(int channel, pspAudioCallback_t callback, void *pdata)
This is the declaration of the function. As you see, the second parameter is a function, not a buffer. It is the function that actually fills the buffer with the new data every time it gets called. The pdata parameter is a pointer to data that is submitted to the callback function every time, so you could submit playback settings with it.
You need to create the callback function yourself, if you take a look at that source, you should find a function named fillOutputBuffer(...). Take a look at it to see how it should look and work.

also is it this way better then to do a custom callback every frameloop of the game?

hope to get some answer and thanks in advance
Using the threaded callback method, you gain the advantage that the music playback is independent of your main loop, therefore you can easily pause your game and still have the music continue playing. Other than that, and saving you from having to manage the thread creation and management there is no advantage.
<Don't push the river, it flows.>
http://wordpress.fx-world.org - my devblog
http://wiki.fx-world.org - VFPU documentation wiki

Alexander Berl
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Hi i have the sample of PeterM almost working an i understand almost everything of the code. I have one problem though :s

Code: Select all

Sample* destination = static_cast<Sample*> &#40;buffer&#41;; 
This function (static_cast) converts the Sample to a buffer right?

however i get this error when i try to compile it:

Code: Select all

mp3player.c&#58; In function 'fillOutputBuffer'&#58;
mp3player.c&#58;789&#58; error&#58; 'static_cast' undeclared &#40;first use in this function&#41;
mp3player.c&#58;789&#58; error&#58; &#40;Each undeclared identifier is reported only once
mp3player.c&#58;789&#58; error&#58; for each function it appears in.&#41;
mp3player.c&#58;789&#58; error&#58; syntax error before 'Sample'
any ideas why this is and what do i not get?


btw my code is in c not c++

greets ghoti
hlide
Posts: 739
Joined: Sun Sep 10, 2006 2:31 am

Post by hlide »

Ghoti wrote:

Code: Select all

Sample* destination = static_cast<Sample*> &#40;buffer&#41;; 
btw my code is in c not c++
this is C++ specific. In c, just do :

Code: Select all

Sample* destination = &#40;Sample*&#41;buffer; 
PeterM
Posts: 125
Joined: Sat Dec 31, 2005 7:25 pm
Location: Edinburgh, UK
Contact:

Post by PeterM »

Woops, shows how often I check this board :-)

If you need any more info about how the MP3 code works just reply. I've subscribed to this thread so I'll get an email.
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Hi folks, i have the mp3 playing working but how do i stop the callback? and how do i pause it? and can i just change the file when a new song has to be loaded or do i have to do it some other way?

greets ghoti

PS to pause i have tried to make a boolean to just exit the callback function but then it gives a hanging sound as if the same part is playing in repeat. To stop it i have tried to shut down the audio and that works but there has to be a better way because that way i can't turn it back on. I have also tried to make a new callback to the 0 channel by passing 0 as a function and that stops the music but then if i try to load the fillbuffer into the callback then i get all craqcking and squeking noise...
Last edited by Ghoti on Mon Jan 15, 2007 6:51 am, edited 1 time in total.
PeterM
Posts: 125
Joined: Sat Dec 31, 2005 7:25 pm
Location: Edinburgh, UK
Contact:

Post by PeterM »

You probably don't want to stop the callback as such, but look into passing messages from your main thread to the callback thread.

Note that multi threaded programming is perhaps not for the uninitiated.

BTW, since the buffer will play continuously, you should fill it with zeroes if the player is paused or stopped.

Edit:

To clarify the message passing idea, investigate semaphores or mutexes. You probably want to set up a global queue.

The main thread could lock the queue, add a command message (play, pause, stop, skip, etc etc) and unlock the queue.

The callback thread could lock the queue, take the next command, unlock the queue and handle the command.

Note that it could hurt playback if opening files and stuff takes a long time and the decoded mp3 buffer can't be filled up in time before the buffer starts to play.

Clever buffering should fix this.
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

hi i'll look into the two things you have said, I'll learn alot from those things. But are those neccesary to just make the code pause, replay a song(isn't that just seek to the beginning of the file?) and load up a next song?

anyway, this will keep me occupite for a few days :) thanks and i'll let you know if i get it to work.

greets ghoti
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Hi I am trying to let the mp3 pause and It does not work like i want it:

Code: Select all

      static void decode&#40;&#41; 
      &#123; 
         // While we need to fill the buffer... 
         while &#40; 
            &#40;mad_frame_decode&#40;&frame, &stream&#41; == -1&#41; && 
            &#40;&#40;stream.error == MAD_ERROR_BUFLEN&#41; || &#40;stream.error == MAD_ERROR_BUFPTR&#41;&#41; 
            &#41; 
         &#123; 
            

            // Give new buffer to the stream. 
				if &#40;pausesong == 1&#41; &#123; 
						mad_stream_buffer&#40;&stream, 0, 0&#41;; 
						//return; 
				&#125;
				else &#123;
					// Fill up the remainder of the file buffer. 
						fillFileBuffer&#40;&#41;; 
						mad_stream_buffer&#40;&stream, fileBuffer, sizeof&#40;fileBuffer&#41;&#41;; 
				&#125;
         &#125; 

         // Synth the frame. 
         mad_synth_frame&#40;&synth, &frame&#41;; 
      &#125; 
What Am i doing wrong here? because it does actually pause but when i unpause it, it crashes.
PeterM
Posts: 125
Joined: Sat Dec 31, 2005 7:25 pm
Location: Edinburgh, UK
Contact:

Post by PeterM »

For pausing, you should probably leave the decode() function as it is, and concentrate on the fillOutputBuffer() function.

You want to fill the output buffer with zeroes if the music is paused, instead of the whole mp3 decoding while loop.
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

I'm sorry but i just don't get it :(
i have this:

Code: Select all

     static void fillOutputBuffer&#40;void* buffer, unsigned int samplesToWrite, void* userData&#41; 
      &#123; 
         // Where are we writing to? 
		 
		 Sample* destination = &#40;Sample*&#41;buffer;
		 if &#40;pausesong == 1&#41; &#123; 		  
			//convertLeftSamples&#40;destination, destination, NULL&#41;; 
            //convertRightSamples&#40;destination, destination, NULL&#41;; 
//mad_synth_frame&#40;NULL, NULL&#41;;

			return; 
		 &#125;
         //Sample* destination = static_cast<Sample*> &#40;buffer&#41;; 
			
         // While we've got samples to write... 

         while &#40;samplesToWrite > 0&#41; 
         &#123; 
            // Enough samples available? 
            const unsigned int samplesAvailable = synth.pcm.length - samplesRead; 
			
            if &#40;samplesAvailable > samplesToWrite&#41; 
            &#123; 
               // Write samplesToWrite samples. 
               convertLeftSamples&#40;destination, destination + samplesToWrite, &synth.pcm.samples&#91;0&#93;&#91;samplesRead&#93;&#41;; 
               convertRightSamples&#40;destination, destination + samplesToWrite, &synth.pcm.samples&#91;1&#93;&#91;samplesRead&#93;&#41;; 

               // We're still using the same PCM data. 
               samplesRead += samplesToWrite; 

               // Done. 
               samplesToWrite = 0; 
            &#125; 
            else 
            &#123; 
               // Write samplesAvailable samples. 
               convertLeftSamples&#40;destination, destination + samplesAvailable, &synth.pcm.samples&#91;0&#93;&#91;samplesRead&#93;&#41;; 
               convertRightSamples&#40;destination, destination + samplesAvailable, &synth.pcm.samples&#91;1&#93;&#91;samplesRead&#93;&#41;; 

               // We need more PCM data. 
               samplesRead = 0; 
               decode&#40;&#41;; 

               // We've still got more to write. 
               destination += samplesAvailable; 
               samplesToWrite -= samplesAvailable; 
            &#125; 
         &#125; 
      &#125; 
this pauses the game but still plays one veryvery small part so it sounds like it hangs.

I just don't really understand the code :( can you explain the code for me line by line or is that to much to ask?

PS. using the convertleft and right in the pause piece of code does not make any difference, leaving them out results also into a hang...
PeterM
Posts: 125
Joined: Sat Dec 31, 2005 7:25 pm
Location: Edinburgh, UK
Contact:

Post by PeterM »

Ghoti wrote:I'm sorry but i just don't get it :(
i have this:

Code: Select all

     static void fillOutputBuffer&#40;void* buffer, unsigned int samplesToWrite, void* userData&#41; 
      &#123; 
         // Where are we writing to? 
		 
		 Sample* destination = &#40;Sample*&#41;buffer;
		 if &#40;pausesong == 1&#41; &#123; 		  
			//convertLeftSamples&#40;destination, destination, NULL&#41;; 
            //convertRightSamples&#40;destination, destination, NULL&#41;; 
//mad_synth_frame&#40;NULL, NULL&#41;;

			return; 
		 &#125;
Try clearing the destination buffer inside the 'if' block. Use memset() or C++'s std::fill_n if you prefer.
Ghoti wrote:I just don't really understand the code :( can you explain the code for me line by line or is that to much to ask?
I'm afraid so -- it would take a while.

It sounds like you are missing some regular C and C++ knowledge. Perhaps you should start with a simpler project, on Windows not PSP.

Pete
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Hi,

i'll try to clear the buffer thanks for the tip.

I have done alot of C and C++ programming, I have made some software program for my company, also multi-user software. I have never come across these callback functions... :(

like i have never come across a function which you pass without parameters but the function itself has parameters which also already hold data. I did not understand that.

i understand almost everything from the code only the part why there is data in a function to which i give no parameters or data. That is what confusing me and what i don't get. :s

anyway thanks again for the tip try to find it out now.

greets ghoti
PeterM
Posts: 125
Joined: Sat Dec 31, 2005 7:25 pm
Location: Edinburgh, UK
Contact:

Post by PeterM »

Ahh! It sounds like you know what you're talking about then. Sorry about that - I assumed you were new and out of your depth. Guess I made an ass out of myself there. Sorry again.

If I understand you correctly, you're having problems with function pointers, is that right? They're indeed one of the dark corners of C (and they get even uglier with C++ member function pointers), but well worth knowing.

This page ( http://www.newty.de/fpt/index.html ) is very good at teaching about function pointers.
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Ah thank you very much for that link!
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Hi it's me again,

well the pause function works and i have put in a playnextsong button. the problem however is that I guess that the variable which hold the path to the song is being used by two thread :S
I have this char array, Song, this is used in fillFileBuffer() however this function is called as an callbackfunction (a different thread i guess)

the problem is that when i press the LBUTTON i call the function nextMusicNumber() which reads from a 2d char array which holds all songs(paths) but then i change the Song char. However since the song char can also be used by the thread, Now i have looked around and it looks like a dining philosophers problem. However how can i solve this on the PSP in C? I have not found any usable working articles about this in C. Is it possible in C? any pointers ??

thanks in advance also if it does not look like the problem i sketch or you have some other opinions please, please let me know it really gives me a headacke :S
PeterM
Posts: 125
Joined: Sat Dec 31, 2005 7:25 pm
Location: Edinburgh, UK
Contact:

Post by PeterM »

Hello,

Good to hear you've had some success in getting things working.

Sounds like you need to ensure that the two threads aren't reading/modifying the variables at the same time?

I don't think the PSP SDK has mutexes, but it does have semaphores. Hopefully a search on here for semaphore will turn something up.

Check here for some info on semaphores:
http://en.wikipedia.org/wiki/Semaphore_(programming)

Good luck!
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

Hi thanks! nice to know someone with alot of knowledge answers this thread :) anyway i have another problem:S

I have 9 mp3 in a 2d char array. I loop through it to play them. after the last one has played the increment value is set to 0 so that it plays the first song again. This works fine for the first song of the array. I can loop 1 time through the song array but the second time it can only open the first song and after that it cannot open the mp3 file :S the mp3file however has not been changed so i guess the problem is that it is not closed properly and is still open. However i tried to solve this but it does not work. any ideas ?

how i change the song:

Code: Select all

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

	ChangeSong&#58; This function changes the song that
				the mp3 buffering will play.

	Param1&#58;		A path to a musicsong
*/


	void ChangeSong&#40;const char *Songtmp&#41; &#123;

		// wait for thread to be open
		sceKernelWaitSema&#40;mutex, 1, 0&#41;; 

		// Song is not used by another thread so we can change it
		sprintf&#40;Song, "%s", Songtmp&#41;;

		// reset the file so it starts at the beginning
		//file = 0;

		// Mark status that the song is changed
		SongIsChanged = 1;

		// free the lock of this thread.
		sceKernelSignalSema&#40;mutex, 1&#41;;
		
		return;
	&#125; 
and the fillbuffer function:

Code: Select all

void fillFileBuffer&#40;&#41; 
&#123; 
	
	// wait for semaphore to be open
	sceKernelWaitSema&#40;mutex, 1, 0&#41;; 
 // Open the file if it's not open. 
	if &#40;SongIsChanged == 1&#41; &#123;
		//sceIoClose&#40;file&#41;;
		SongIsChanged = 0;
		file = 0;
	&#125;
	 if &#40;file <= 0&#41; 
	 &#123; 
		char   cd&#91;1024&#93;; 
		memset&#40;cd, 0, sizeof&#40;cd&#41;&#41;; 
		getcwd&#40;cd, sizeof&#40;cd&#41; - 1&#41;; 

		char   fileName&#91;1024&#93;; 
		memset&#40;fileName, 0, sizeof&#40;fileName&#41;&#41;; 
		snprintf&#40;fileName, sizeof&#40;fileName&#41; - 1, "%s", Song&#41;; 

		//pspDebugScreenPrintf&#40;"Opening %s... ", fileName&#41;; 
		file = sceIoOpen&#40;fileName, PSP_O_RDONLY, 777&#41;; 
		if &#40;file <= 0&#41; 
		&#123; 
		   pspDebugScreenPrintf&#40;"Failed &#40;%s&#41;.\n", Song&#41;; 
		   sceKernelSignalSema&#40;mutex, 1&#41;;
		   MusicPlayerNextSong&#40;&#41;;
		   return; 
		&#125; 
		else 
		&#123; 
		   //pspDebugScreenPrintf&#40;"OK &#40;%d&#41;.\n", file&#41;; 
		&#125; 

		// Get the size. 
		fileSize = sceIoLseek&#40;file, 0, SEEK_END&#41;; 
		sceIoLseek&#40;file, 0, SEEK_SET&#41;; 
	 &#125; 

 // Find out how much to keep and how much to fill. 
 const unsigned int   bytesToKeep   = stream.bufend - stream.next_frame; 
 unsigned int      bytesToFill   = sizeof&#40;fileBuffer&#41; - bytesToKeep; 
 //pspDebugScreenPrintf&#40;"bytesToFill = %u, bytesToKeep = %u.\n", bytesToFill, bytesToKeep&#41;; 

 // Want to keep any bytes? 
 if &#40;bytesToKeep&#41; 
 &#123; 
    // Copy the tail to the head. 
    memmove&#40;fileBuffer, fileBuffer + sizeof&#40;fileBuffer&#41; - bytesToKeep, bytesToKeep&#41;; 
 &#125; 

 // Read into the rest of the file buffer. 
 unsigned char* bufferPos = fileBuffer + bytesToKeep; 
 while &#40;bytesToFill > 0&#41; 
 &#123; 
    // Read some. 
    //pspDebugScreenPrintf&#40;"Reading %u bytes...\n", bytesToFill&#41;; 
    const unsigned int bytesRead = sceIoRead&#40;file, bufferPos, bytesToFill&#41;; 

    // EOF? 
    if &#40;bytesRead == 0&#41; 
    &#123; 
       //pspDebugScreenPrintf&#40;"End of file.\n"&#41;; 
       //sceIoLseek&#40;file, 0, SEEK_SET&#41;; 
       //filePos = 0; 
	   MusicPlayerNextSong&#40;&#41;;
       break; 
    &#125; 

    // Adjust where we're writing to. 
    bytesToFill -= bytesRead; 
    bufferPos += bytesRead; 
    filePos += bytesRead; 

    //pspDebugScreenPrintf&#40;"Read %u bytes from the file, %u left to fill.\n", bytesRead, bytesToFill&#41;; 
    //pspDebugScreenPrintf&#40;"%u%%.\n", filePos * 100 / fileSize&#41;; 
 &#125; 
 sceKernelSignalSema&#40;mutex, 1&#41;;
&#125; 
any idea what is going wrong? is it really the opening and forgetting the closing of the file ?

greets ghoti
User avatar
Raphael
Posts: 646
Joined: Tue Jan 17, 2006 4:54 pm
Location: Germany
Contact:

Post by Raphael »

Yes, that should be the problem. You should probably try to close the file as soon as the EOF is reached.

Code: Select all

if &#40;SongIsChanged == 1&#41; &#123;
      SongIsChanged = 0;
      if &#40;file>0&#41; sceIoClose&#40;file&#41;; // If the file wasn't closed before &#40;got to next song before reaching EOF&#41;, close it now
      file = 0;
   &#125;

Code: Select all

if &#40;bytesRead == 0&#41;
    &#123;
       //pspDebugScreenPrintf&#40;"End of file.\n"&#41;;
       sceIoClose&#40;file&#41;;
       file = 0;
       //sceIoLseek&#40;file, 0, SEEK_SET&#41;;
       //filePos = 0;
      MusicPlayerNextSong&#40;&#41;;
       break;
    &#125; 
Also, you probably should only call the open file function when a new song is selected, rather then every time the callback is entered without a file open, because the playback might just be stopped alltogether. Then change the fillbuffer to fill the buffer with zeros when file <= 0.
<Don't push the river, it flows.>
http://wordpress.fx-world.org - my devblog
http://wiki.fx-world.org - VFPU documentation wiki

Alexander Berl
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

ah yes that fixed the problem :D mp3 buffering is working :) thank you all
Ghoti
Posts: 288
Joined: Sat Dec 31, 2005 11:06 pm

Post by Ghoti »

back again after quite a while :)

The buffering is working correctly and without any problems :D

The one big problem is that it is quite slow :(

Now I heard something about using the ME ? is there any progress made on this? example codes or something like that available ?

greets ghoti
cooleyes
Posts: 123
Joined: Thu May 18, 2006 3:30 pm

Post by cooleyes »

Ghoti wrote:back again after quite a while :)

The buffering is working correctly and without any problems :D

The one big problem is that it is quite slow :(

Now I heard something about using the ME ? is there any progress made on this? example codes or something like that available ?

greets ghoti
en , you can use audiocodec.prx to decode mp3 frame

here is my demo code

http://forums.ps2dev.org/viewtopic.php? ... highlight=
Vincent_M
Posts: 73
Joined: Tue Apr 03, 2007 4:16 am

Post by Vincent_M »

Does PSPMediaCenter handle code on the ME? Just curious. I'd check the code out for myself if I had the time, but I'll be gone for a while, and I won't be able to do much for the moment.
Post Reply