MP3 Buffered Playback

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

Moderators: cheriff, TyRaNiD

Post Reply
AyAn4m1
Posts: 15
Joined: Mon Sep 26, 2005 2:45 am

MP3 Buffered Playback

Post by AyAn4m1 »

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?
User avatar
Jim
Posts: 476
Joined: Sat Jul 02, 2005 10:06 pm
Location: Sydney
Contact:

Post by Jim »

Try double buffering - fill one buffer and play the other, swapping buffers when the playback hits the end.

Jim
Blue
Posts: 5
Joined: Sat Feb 25, 2006 9:14 pm

Post by Blue »

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.
.: Smerity :.
Posts: 9
Joined: Sat Feb 04, 2006 11:43 pm
Contact:

Post by .: Smerity :. »

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
AyAn4m1
Posts: 15
Joined: Mon Sep 26, 2005 2:45 am

Post by AyAn4m1 »

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?
RCON
Posts: 16
Joined: Wed Aug 03, 2005 1:02 am
Contact:

Post by RCON »

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
PeterM
Posts: 125
Joined: Sat Dec 31, 2005 7:25 pm
Location: Edinburgh, UK
Contact:

Post by PeterM »

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: Select all

#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&#40;"Test", 0, 1, 1&#41;;
PSP_MAIN_THREAD_ATTR&#40;PSP_THREAD_ATTR_USER&#41;;

namespace test
&#123;
	namespace main
	&#123;
		static int exitCallback&#40;int arg1, int arg2, void* common&#41;
		&#123;
			// Exit.
			sceKernelExitGame&#40;&#41;;
			return 0;
		&#125;

		static int powerCallback&#40;int unknown, int powerInfo, void* common&#41;
		&#123;
			return 0;
		&#125;

		static int callbackThread&#40;SceSize args, void *argp&#41;
		&#123;
			// Register the exit callback.
			const SceUID exitCallbackID = sceKernelCreateCallback&#40;"exitCallback", exitCallback, NULL&#41;;
			sceKernelRegisterExitCallback&#40;exitCallbackID&#41;;

			// Register the power callback.
			const SceUID powerCallbackID = sceKernelCreateCallback&#40;"powerCallback", powerCallback, NULL&#41;;
			scePowerRegisterCallback&#40;0, powerCallbackID&#41;;

			// Sleep and handle callbacks.
			sceKernelSleepThreadCB&#40;&#41;;
			return 0;
		&#125;

		static int setUpCallbackThread&#40;void&#41;
		&#123;
			const int thid = sceKernelCreateThread&#40;"callbackThread", callbackThread, 0x11, 0xFA0, 0, 0&#41;;
			if &#40;thid >= 0&#41;
				sceKernelStartThread&#40;thid, 0, 0&#41;;
			return thid;
		&#125;

		struct Sample
		&#123;
			short left;
			short right;
		&#125;;

		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&#91;2048&#93;;
		static unsigned int		samplesRead;

		static void fillFileBuffer&#40;&#41;
		&#123;
			// Open the file if it's not open.
			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/%s", cd, "01.mp3"&#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;%d&#41;.\n", file&#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;
					continue;
				&#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;
		&#125;

		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;
				// Fill up the remainder of the file buffer.
				fillFileBuffer&#40;&#41;;

				// Give new buffer to the stream.
				mad_stream_buffer&#40;&stream, fileBuffer, sizeof&#40;fileBuffer&#41;&#41;;
			&#125;

			// Synth the frame.
			mad_synth_frame&#40;&synth, &frame&#41;;
		&#125;

		static inline short convertSample&#40;mad_fixed_t sample&#41;
		&#123;
			/* round */
			sample += &#40;1L << &#40;MAD_F_FRACBITS - 16&#41;&#41;;

			/* clip */
			if &#40;sample >= MAD_F_ONE&#41;
				sample = MAD_F_ONE - 1;
			else if &#40;sample < -MAD_F_ONE&#41;
				sample = -MAD_F_ONE;

			/* quantize */
			return sample >> &#40;MAD_F_FRACBITS + 1 - 16&#41;;
		&#125;

		static void convertLeftSamples&#40;Sample* first, Sample* last, const mad_fixed_t* src&#41;
		&#123;
			for &#40;Sample* dst = first; dst != last; ++dst&#41;
			&#123;
				dst->left = convertSample&#40;*src++&#41;;
			&#125;
		&#125;

		static void convertRightSamples&#40;Sample* first, Sample* last, const mad_fixed_t* src&#41;
		&#123;
			for &#40;Sample* dst = first; dst != last; ++dst&#41;
			&#123;
				dst->right = convertSample&#40;*src++&#41;;
			&#125;
		&#125;

		static void fillOutputBuffer&#40;void* buffer, unsigned int samplesToWrite, void* userData&#41;
		&#123;
			// Where are we writing to?
			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;
	&#125;
&#125;

using namespace test;
using namespace test&#58;&#58;main;

int main&#40;int argc, char *argv&#91;&#93;&#41;
&#123;
	// Set up the callback thread.
	setUpCallbackThread&#40;&#41;;

	// Initialise the debug screen.
	pspDebugScreenInit&#40;&#41;;

	// Set up MAD.
	pspDebugScreenPrintf&#40;"Setting up MAD... "&#41;;
	mad_stream_init&#40;&stream&#41;;
	mad_frame_init&#40;&frame&#41;;
	mad_synth_init&#40;&synth&#41;;
	pspDebugScreenPrintf&#40;"OK.\n"&#41;;

	// Initialise the audio system.
	pspAudioInit&#40;&#41;;

	// Set the channel callback.
	pspDebugScreenPrintf&#40;"Decoding...\n"&#41;;
	pspAudioSetChannelCallback&#40;0, fillOutputBuffer, 0&#41;;

	// Wait for a button press.
	SceCtrlData pad;
	memset&#40;&pad, 0, sizeof&#40;pad&#41;&#41;;
	while &#40;pad.Buttons != 0&#41;
	&#123;
		sceCtrlReadBufferPositive&#40;&pad, 1&#41;;
	&#125;
	while &#40;pad.Buttons == 0&#41;
	&#123;
		sceCtrlReadBufferPositive&#40;&pad, 1&#41;;
	&#125;
	while &#40;pad.Buttons != 0&#41;
	&#123;
		sceCtrlReadBufferPositive&#40;&pad, 1&#41;;
	&#125;

	// Shut down audio.
	pspAudioEnd&#40;&#41;;

	// Shut down MAD.
	mad_synth_finish&#40;&synth&#41;;
	mad_frame_finish&#40;&frame&#41;;
	mad_stream_finish&#40;&stream&#41;;

	// Quit.
	sceKernelExitGame&#40;&#41;;
	return 0;
&#125;
Useful links to the MAD mailing list archives:
http://www.mars.org/mailman/public/mad- ... 00007.html
http://www.mars.org/mailman/public/mad- ... 00091.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
AyAn4m1
Posts: 15
Joined: Mon Sep 26, 2005 2:45 am

Post by AyAn4m1 »

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!
PeterM
Posts: 125
Joined: Sat Dec 31, 2005 7:25 pm
Location: Edinburgh, UK
Contact:

Post by PeterM »

Cool, I'm glad it works.
shifty
Posts: 32
Joined: Thu Jun 16, 2005 8:59 am
Location: MIT
Contact:

peterm's libmad mp3 code and latency

Post by shifty »

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!
Warren
Posts: 175
Joined: Sat Jan 24, 2004 8:26 am
Location: San Diego, CA

Post by Warren »

Try looking at the PSPMediaCenter source in the PSPWARE repository. It does this.
Post Reply