Using the bulk of the info here, I made a simple ogg player for 3.xx firmware (runs fine on my slim). It still plays the last buffer of sound if you stop one song and start another, but I wasn't too worried about that. The main things about this player - showing how to make sure your app works on the slim, and the file requestor. The commented out lines in the makefile, the main.c includes, and the buffer fill thread are for using ogg-vorbis instead of tremor. In the file requester, press X to select a file or enter a directory (they're shown in [ ] ), triangle to go back one directory level, and O to cancel.
EDIT: fixed a bug when navigating up or left in the file requester, and implemented double-buffering to get rid of data copying in the audio callback. Added volume control - press LTRIGGER or RTRIGGER during playback to change the volume (needs some key debounce still).
EDIT: Fixed noise in playback. Seems that setting the length each time through the audio fill loop can cause noise. Don't do it! Pass the same amount of data every time and it'll be noise free. If you look at the fillbuffer routine, you'll notice I now ov_read() until I have precisely as many samples as we need each time. This gives you noise-free playback.
makefile
Code: Select all
TARGET = OggPlayer
OBJS = main.o reqfile.o
INCDIR =
CFLAGS = -O2 -G0 -Wall
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS = $(CFLAGS)
BUILD_PRX = 1
PSP_FW_VERSION = 371
LIBDIR =
LIBS = -lvorbisidec -lvorbis -logg -lpspaudiolib -lpspaudio -lpsppower
#LIBS = -lvorbisfile -lvorbis -logg -lpspaudiolib -lpspaudio -lpsppower -lm
LDFLAGS =
EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = Simple Ogg Player
PSP_EBOOT_ICON="icon0.png"
#PSP_EBOOT_PIC1="pic1.png"
#PSP_EBOOT_SND0="snd0.at3"
PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak
main.c
Code: Select all
#include <pspkernel.h>
#include <pspctrl.h>
#include <pspdebug.h>
#include <pspaudio.h>
#include <pspaudiolib.h>
#include <psppower.h>
#include <pspdisplay.h>
#include <string.h>
#include <stdio.h>
#include <tremor/ivorbiscodec.h>
#include <tremor/ivorbisfile.h>
//#include <vorbis/codec.h>
//#include <vorbis/vorbisfile.h>
#define printf pspDebugScreenPrintf
#define VERS 1
#define REVS 0
extern char *RequestFile(char *);
PSP_MODULE_INFO("OggPlayer", 0, VERS, REVS);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
PSP_HEAP_SIZE_KB(2500);
#define OUTPUT_BUFFER 8192 // must be less than PSP_AUDIO_SAMPLE_MAX*4
/////////////////////////////////////////////////////////////////////////////////////////
//Globals
/////////////////////////////////////////////////////////////////////////////////////////
int runningFlag;
int bufferEmpty;
int bufferFull;
int audioVol = PSP_AUDIO_VOLUME_MAX;
char pcmout1[OUTPUT_BUFFER];
char pcmout2[OUTPUT_BUFFER];
long pcmlen1, pcmlen2;
int bufferFlip = 0;
OggVorbis_File OGG_VorbisFile;
int OGG_eos = 0;
int OGG_audio_channel = 0;
int bitStream;
static FILE *OGG_file = 0;
/////////////////////////////////////////////////////////////////////////////////////////
//Callbacks
/////////////////////////////////////////////////////////////////////////////////////////
/* Exit callback */
int exit_callback(int arg1, int arg2, void *common) {
sceKernelExitGame();
return 0;
}
/* Callback thread */
int CallbackThread(SceSize args, void *argp) {
int cbid;
cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
sceKernelRegisterExitCallback(cbid);
sceKernelSleepThreadCB();
return 0;
}
/* Sets up the callback thread and returns its thread id */
int SetupCallbacks(void) {
int thid = 0;
thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, PSP_THREAD_ATTR_USER, 0);
if(thid >= 0) {
sceKernelStartThread(thid, 0, 0);
}
return thid;
}
/////////////////////////////////////////////////////////////////////////////////////////
//Buffer filling
/////////////////////////////////////////////////////////////////////////////////////////
int fillBuffer(SceSize args, void *argp)
{
int bytes, bytesRed;
int fillbuf;
while(runningFlag){
sceKernelWaitSema(bufferEmpty, 1, 0);
if (OGG_eos || !runningFlag)
break;
bytesRed = 0;
fillbuf = bufferFlip ? (int)pcmout2 : (int)pcmout1;
while (bytesRed < OUTPUT_BUFFER && runningFlag && !OGG_eos)
{
bytes = ov_read(&OGG_VorbisFile,(char *)(fillbuf+bytesRed),OUTPUT_BUFFER-bytesRed,&bitStream);
//bytes = ov_read(&OGG_VorbisFile,(char *)(fillbuf+bytesRed),OUTPUT_BUFFER-bytesRed,0,2,1,&bitStream);
if (bytes == 0)
{
//EOF:
OGG_eos = 1;
ov_clear(&OGG_VorbisFile);
}
else if (bytes > 0)
{
bytesRed += bytes;
}
}
if (bufferFlip)
pcmlen2 = bytesRed;
else
pcmlen1 = bytesRed;
sceKernelSignalSema(bufferFull, 1);
}
sceKernelExitDeleteThread(0);
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////
//Audio output
/////////////////////////////////////////////////////////////////////////////////////////
int audioOutput(SceSize args, void *argp)
{
int playlen;
char *playbuf;
while(runningFlag)
{
sceKernelWaitSema(bufferFull, 1, 0);
if (OGG_eos || !runningFlag)
break;
//playlen = bufferFlip ? pcmlen1 : pcmlen2;
playbuf = bufferFlip ? pcmout1 : pcmout2;
bufferFlip ^= 1;
sceKernelSignalSema(bufferEmpty, 1);
//sceAudioSetChannelDataLen(OGG_audio_channel, playlen/4);
sceAudioOutputBlocking(OGG_audio_channel, audioVol, playbuf);
}
sceKernelExitDeleteThread(0);
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////
//Main
/////////////////////////////////////////////////////////////////////////////////////////
int main() {
char *filename;
SceCtrlData pad;
char dest[9];
long secs;
int h;
int m;
int s;
vorbis_info *vi;
pspDebugScreenInit();
SetupCallbacks();
//Creates semaphores:
bufferEmpty = sceKernelCreateSema("bufferEmpty", 0, 1, 1, 0);
bufferFull = sceKernelCreateSema("bufferFull", 0, 0, 1, 0);
pspDebugScreenInit();
pspDebugScreenSetBackColor(0xA0602000);
pspDebugScreenSetTextColor(0xffffff00);
audioVol = 0x4000; // half max volume
mloop:
filename = RequestFile("ms0:/MUSIC/");
pspDebugScreenClear();
pspDebugScreenSetXY(25, 1);
printf("Simple Ogg Player");
pspDebugScreenSetXY(25, 3);
printf("CPU: %i BUS: %i", scePowerGetCpuClockFrequency(), scePowerGetBusClockFrequency());
pspDebugScreenSetXY(21, 4);
printf("Press X to stop playback");
printf("\n\n Playing %s\n\n", filename);
sceKernelDelayThread(2*1000*1000);
//Apro il file OGG:
OGG_file = fopen(filename, "r");
if ((OGG_file) != NULL)
ov_open(OGG_file, &OGG_VorbisFile, NULL, 0);
else
{
printf(" Error opening file\n");
sceKernelDelayThread(2*1000*1000);
goto mloop;
}
//Stampo le informazioni sul file:
vi = ov_info(&OGG_VorbisFile, -1);
printf(" Channels : %d\n", vi->channels);
printf(" Sample rate: %ld Hz\n", vi->rate);
printf(" Bitrate : %ld kBit\n", vi->bitrate_nominal/1000);
h = 0;
m = 0;
s = 0;
secs = (long)ov_time_total(&OGG_VorbisFile, -1)/1000;
h = secs / 3600;
m = (secs - h * 3600) / 60;
s = secs - h * 3600 - m * 60;
snprintf(dest, sizeof(dest), "%2.2i:%2.2i:%2.2i", h, m, s);
printf(" Length : %s\n", dest);
sceKernelDelayThread(2*1000*1000);
memset(pcmout1, 0, OUTPUT_BUFFER);
memset(pcmout2, 0, OUTPUT_BUFFER);
pcmlen1 = 0;
pcmlen2 = 0;
OGG_eos = 0;
runningFlag = 1;
sceKernelSignalSema(bufferEmpty, 1);
sceKernelSignalSema(bufferFull, 0);
//Reserve audio channel:
OGG_audio_channel = sceAudioChReserve(OGG_audio_channel, OUTPUT_BUFFER/4, PSP_AUDIO_FORMAT_STEREO);
//Start buffer filling thread:
int bufferThid = sceKernelCreateThread("bufferFilling", fillBuffer, 0x12, 0x1800, PSP_THREAD_ATTR_USER, NULL);
if(bufferThid < 0)
sceKernelExitGame();
sceKernelStartThread(bufferThid, 0, NULL);
//Start audio output thread:
int audioThid = sceKernelCreateThread("audioOutput", audioOutput, 0x16, 0x1800, PSP_THREAD_ATTR_USER, NULL);
if(audioThid < 0)
sceKernelExitGame();
sceKernelStartThread(audioThid, 0, NULL);
while(runningFlag && !OGG_eos)
{
//Print timestring:
secs = (long) ov_time_tell(&OGG_VorbisFile)/1000;
h = secs / 3600;
m = (secs - h * 3600) / 60;
s = secs - h * 3600 - m * 60;
snprintf(dest, sizeof(dest), "%2.2i:%2.2i:%2.2i", h, m, s);
pspDebugScreenSetXY(1, 16);
printf("Current : %s\n", dest);
sceCtrlReadBufferPositive(&pad, 1);
if(pad.Buttons & PSP_CTRL_CROSS)
break; // stop playback
if (pad.Buttons & PSP_CTRL_LTRIGGER)
if (audioVol > 0)
audioVol -= 0x0800; // 16 steps on the audio
if (pad.Buttons & PSP_CTRL_RTRIGGER)
if (audioVol < 0x8000)
audioVol += 0x0800; // 16 steps on the audio
sceKernelDelayThread(100*1000);
}
if (OGG_eos == 1)
printf("\n\n End of file\n");
else
printf("\n\n Playback stopped\n");
runningFlag = 0; // kill the threads
sceKernelSignalSema(bufferEmpty, 1);
sceKernelSignalSema(bufferFull, 1);
sceKernelDelayThread(1*1000*1000);
//Release channel:
sceAudioChRelease(OGG_audio_channel);
goto mloop;
return 0; // never reaches here - just supresses compiler warning
}
reqfile.c
Code: Select all
#include <pspkernel.h>
#include <pspctrl.h>
#include <pspdebug.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#define printf pspDebugScreenPrintf
#define MAXFILES 1000
#define PAGESIZE 32
static struct fileentries {
char filename[FILENAME_MAX];
char path[FILENAME_MAX];
int flags;
} cdfiles[MAXFILES];
static int maxfiles;
/****************************************************************************
* get_buttons
*
****************************************************************************/
static unsigned int get_buttons()
{
SceCtrlData pad;
sceCtrlReadBufferPositive(&pad, 1);
return pad.Buttons;
}
/****************************************************************************
* ParseDirectory
*
* Parse the directory, returning the number of files found
****************************************************************************/
int parse_dir (char *path)
{
DIR *dir;
DIR *test_dir;
struct dirent *dirent = 0;
struct stat fstat;
char file_name[FILENAME_MAX];
FILE *file;
maxfiles = 0;
/* open directory */
if ( ( dir = opendir( path ) ) == 0 )
return 0;
while ( ( dirent = readdir( dir ) ) != 0 )
{
if ( dirent->d_name[0] == '.' ) continue;
/* get stats */
sprintf( file_name, "%s/%s", path, dirent->d_name );
if ( stat( file_name, &fstat ) == -1 ) continue;
/* check directory */
if ( S_ISDIR( fstat.st_mode ) )
{
if ( ( test_dir = opendir( file_name ) ) == 0 ) continue;
closedir( test_dir );
memset (&cdfiles[maxfiles], 0, sizeof (struct fileentries));
strncpy(cdfiles[maxfiles].path, path, FILENAME_MAX);
cdfiles[maxfiles].path[FILENAME_MAX-1] = 0;
strncpy(cdfiles[maxfiles].filename, dirent->d_name, FILENAME_MAX);
cdfiles[maxfiles].filename[FILENAME_MAX-1] = 0;
cdfiles[maxfiles].flags = 1;
maxfiles++;
}
else
/* check regular file */
if ( S_ISREG( fstat.st_mode ) )
{
/* test it */
if ( ( file = fopen( file_name, "r" ) ) == 0 ) continue;
fclose( file );
memset (&cdfiles[maxfiles], 0, sizeof (struct fileentries));
strncpy(cdfiles[maxfiles].path, path, FILENAME_MAX);
cdfiles[maxfiles].path[FILENAME_MAX-1] = 0;
strncpy(cdfiles[maxfiles].filename, dirent->d_name, FILENAME_MAX);
cdfiles[maxfiles].filename[FILENAME_MAX-1] = 0;
maxfiles++;
}
if (maxfiles == MAXFILES)
break;
}
/* close dir */
closedir( dir );
return maxfiles;
}
/****************************************************************************
* ShowFiles
*
* Support function for FileSelector
****************************************************************************/
void ShowFiles( int offset, int selection )
{
int i,j;
char text[69];
pspDebugScreenClear();
j = 0;
for ( i = offset; i < ( offset + PAGESIZE ) && i < maxfiles ; i++ )
{
if ( cdfiles[i].flags )
{
strcpy(text,"[");
strncat(text, cdfiles[i].filename,66);
strcat(text,"]");
}
else
strncpy(text, cdfiles[i].filename, 68);
text[68]=0;
pspDebugScreenSetTextColor(j == ( selection - offset ) ? 0xbbbbbb00 : 0xffffff00);
pspDebugScreenSetXY((68 - strlen(text)) / 2, i - offset + 1);
printf("%s", text);
j++;
}
pspDebugScreenSetTextColor(0xffffff00);
}
/****************************************************************************
* FileSelector
*
* Press X to select, O to cancel, and Triangle to go back a level
****************************************************************************/
int FileSelector()
{
int offset = 0;
int selection = 0;
int havefile = 0;
int redraw = 1;
unsigned int p = get_buttons();
while ( havefile == 0 && !(p & PSP_CTRL_CIRCLE) )
{
if ( redraw )
ShowFiles( offset, selection );
redraw = 0;
while (!(p = get_buttons()))
sceKernelDelayThread(10000);
while (p == get_buttons())
sceKernelDelayThread(10000);
if ( p & PSP_CTRL_DOWN )
{
selection++;
if ( selection == maxfiles )
selection = offset = 0; // wrap around to top
if ( ( selection - offset ) == PAGESIZE )
offset += PAGESIZE; // next "page" of entries
redraw = 1;
}
if ( p & PSP_CTRL_UP )
{
selection--;
if ( selection < 0 )
{
selection = maxfiles - 1;
offset = maxfiles > PAGESIZE ? selection - PAGESIZE + 1 : 0; // wrap around to bottom
}
if ( selection < offset )
{
offset -= PAGESIZE; // previous "page" of entries
if ( offset < 0 )
offset = 0;
}
redraw = 1;
}
if ( p & PSP_CTRL_RIGHT )
{
selection += PAGESIZE;
if ( selection >= maxfiles )
selection = offset = 0; // wrap around to top
if ( ( selection - offset ) >= PAGESIZE )
offset += PAGESIZE; // next "page" of entries
redraw = 1;
}
if ( p & PSP_CTRL_LEFT )
{
selection -= PAGESIZE;
if ( selection < 0 )
{
selection = maxfiles - 1;
offset = maxfiles > PAGESIZE ? selection - PAGESIZE + 1 : 0; // wrap around to bottom
}
if ( selection < offset )
{
offset -= PAGESIZE; // previous "page" of entries
if ( offset < 0 )
offset = 0;
}
redraw = 1;
}
if ( p & PSP_CTRL_CROSS )
{
if ( cdfiles[selection].flags ) /*** This is directory ***/
{
char fname[FILENAME_MAX+FILENAME_MAX];
strncpy(fname, cdfiles[selection].path, FILENAME_MAX);
fname[FILENAME_MAX-1] = 0;
strncat(fname, cdfiles[selection].filename, FILENAME_MAX);
fname[FILENAME_MAX+FILENAME_MAX-2] = 0;
strcat(fname, "/");
offset = selection = 0;
parse_dir(fname);
}
else
return selection;
redraw = 1;
}
if ( p & PSP_CTRL_TRIANGLE )
{
char fname[FILENAME_MAX];
int pathpos = strlen(cdfiles[1].path) - 2;
while (pathpos > 5)
{
if (cdfiles[1].path[pathpos] == '/') break;
pathpos--;
}
if (pathpos < 5) pathpos = 5; /** handle root case */
strncpy(fname, cdfiles[1].path, pathpos+1);
fname[pathpos+1] = 0;
offset = selection = 0;
parse_dir(fname);
redraw = 1;
}
}
return -1; // no file selected
}
/****************************************************************************
* RequestFile
*
* return pointer to filename selected
****************************************************************************/
char *RequestFile (char *initialPath)
{
int selection;
static char fname[FILENAME_MAX+FILENAME_MAX];
if (!parse_dir(initialPath))
return 0;
selection = FileSelector ();
if (selection < 0)
return 0;
strncpy (fname, cdfiles[selection].path, FILENAME_MAX);
fname[FILENAME_MAX-1] = 0;
strncat (fname, cdfiles[selection].filename, FILENAME_MAX);
fname[FILENAME_MAX+FILENAME_MAX-1] = 0;
return fname;
}