Sound Frustrations


Alex.

Retired
Joined
Aug 24, 2005
Messages
4,616
I decided to finally start adding sound to my games. I'm using C with SDL, so I'm using SDL_mixer for the job. The problem is, the toll on the framerate is pretty bad. Here's the code:

Code:
Mix_OpenAudio(22050, AUDIO_S16, 2, BUFFER_SIZE);
Mix_Music *music = NULL;
music = Mix_LoadMUS("music.it");
Mix_PlayMusic(music, -1);

// game

Mix_HaltMusic();
Mix_FreeMusic(music);
music = NULL;
Mix_CloseAudio();

The IT file is roughly 12kb in size. When I set BUFFER_SIZE to anything between 256 and 1024, the toll on the framerate is ~17. When I increase it to 4096, the frame rate toll lowers to 6, but the game overall is very stuttery. Is there a better way of doing this? Note that I know no ASM whatsoever, so I'm stuck with SDL. :(

- Alex
 
On the gp2x you'll want the buffer around 256 or 512. Much larger and you start getting bad sound delays. Doesn't matter too much for music, but you'll really start to notice if you add sound effects. SDL_mixer shouldn't slow things down too much though. What is the framerate without music?
 
Without music, the framerate is 111. With music, it's ~96.

- Alex
 
It does? Ok, then another question: I use a frame cap function, and set the game to run at 90FPS. However, with the frame cap enabled (and with the music playing), it only reaches ~82. With the frame cap off, it goes up to ~96. Why is that? I thought music ran on a separate thread than the other stuff :-\

- Alex
 
You're worried about only getting 96fps? You do realise that's at least 36fps faster than most console games ever run at don't you (66fps faster in many cases). I wish my life was so simple that code running that 'slow' would worry me ;).
 
:(

On this game, simple as it may be, smoothness is noticeable. What puzzles me though, is why capped FPS can't reach its set 90, while uncapped goes up to 96.

- Alex
 
SDL does indeed use seperate threading (well, on the PC anyway.) From what you describe, it sounds like you might have an issue with your framerate capping code, maybe you can post it for us to see?
 
First the structure definition:
Code:
#define MAX_FPS 90

typedef struct {
	int fps;
	Uint32 oldtime;
	Uint32 newtime;
	int limit;
	int limitpress;
	int index;
	Uint32 list[MAX_FPS];
} FPS;

Then this is called before the game loop:
Code:
void setupFPS(FPS* fps)
{
	int l;

	for(l = 0; l < MAX_FPS; l++) {
		fps->list[l] = 0;
	}
}

This is called every game loop, after everything was completed:
Code:
void fpsCalc(FPS* fps, int MAX)
{
	int d, i;
	int l, sum;

	fps->oldtime = fps->newtime;
	fps->newtime = SDL_GetTicks();

	d = fps->newtime - fps->oldtime;
	i = (int)(1000.0f / (float)(MAX));

	if(fps->limit && d < i) {
		while(d < i) {
			fps->newtime = SDL_GetTicks();
			d = fps->newtime - fps->oldtime;
		}
	}

	fps->list[fps->index] = (int)(1000.0f / (float)(d));

	sum = 0;
	for(l = 0; l < MAX; l++) {
		sum += fps->list[l];
	}

	fps->fps = (int)(sum / MAX);

	fps->index++;
	if(fps->index > MAX - 1) fps->index = 0;
}

(part of every game loop)
Code:
fpsCalc(fps, MAX_FPS);

- Alex
 
I'd have to think about it but I think it would be because SDL_GetTicks() is probably not accurate to a fine level. This is the case with most timing functions, they are only really accurate on the large scale. What I imagine is happening is that some frames will be done too quickly (of course) and the code will wait for SDL_GetTicks() to tick over. Except, instead of jumping from 8 to 9 to 10 to 11 (which is 1000/90, the target), it might go something like 8, 8, 8, 15. That means that there is already 4ms wasted on this frame, and if this happens for most of the frames that would mean there is a deviation in framerate away from the cap.

First of all, here is a simplified version of your code. It uses a running total instead of recalculating and I have also removed the floating point divides as they are as good as equivilent to integer divides which are much faster.

Code:
#define MAX_FPS 90

typedef struct {
	int fps;
	Uint32 oldtime;
	Uint32 newtime;
	int limit;
	int limitpress;
	int index;
	int sum;	// running total
	Uint32 list[MAX_FPS];
} FPS;

void setupFPS(FPS* fps)
{
	int l;
	
	for(l = 0; l < MAX_FPS; l++) {
		fps->list[l] = 0;
	}
	
	fps->sum = 0;
}

void fpsCalc(FPS* fps, int MAX)
{
	int d, i = 1000/MAX;
	
	fps->oldtime = fps->newtime;
	
	do {
		fps->newtime = SDL_GetTicks();
		d = fps->newtime - fps->oldtime;
	} while (fps->limit && d < i);
	
	fps->sum -= fps->list[fps->index];
	fps->list[fps->index] = 1000/d;
	fps->sum += fps->list[fps->index];
	fps->fps = (fps->sum/MAX);
	
	fps->index++;
	if(fps->index > MAX - 1) fps->index = 0;
}

Now, the way round the cap delay is to not worry about serving a frame every 90th of a second, but rather to run frames as fast as possible and delay to push them towards an average.

Here is pseudo-code, which predicts the future frames timing and uses that to give frames at an average of 90fps:

Code:
#define MAX_FPS 90
#define FPSTIME (1000/(MAX_FPS))

nextframe = SDL_GetTicks() + FPSTIME;

while (game_loop)
{
	while (SDL_GetTicks() > nextframe)
	{
		nextframe += FPSTIME;
		
		game_logic();
	}
	
	critical_code();
}

If your rendering code and logic code are seperate, you might want to only render once after the frames have caught up. Of course this will make no difference if the framerate is high, but if it's low then it will give a significant boost. Here is a more advanced pseudo-code that also implements a minimum framerate and allows for cpu cycles to be freed for any background tasks using SDL_Delay(0).

Code:
#define MAX_FPS 90
#define MAX_FRAMESKIP 10
#define FPSTIME (1000/(MAX_FPS))

int skip;
nextframe = SDL_GetTicks() + FPSTIME;

while (game_loop)
{
	if (SDL_GetTicks() > nextframe)
	{
		skip = 0;
		while (SDL_GetTicks() > nextframe && skip++ < MAX_FRAMESKIP)
		{
			nextframe += FPSTIME;
			
			game_logic();
		}
		
		game_render();
	}
	else
		SDL_Delay(0); // release cpu briefly
	
	critical_code();
}

If you want to pause the game or take the code focus out of the main loop, you would need to record how long for and add that on to nextframe...

Hope these ideas help.
 
I used a very VERY simple capping system for flashplayer2x, where it counted the difference between start of a frame to the almost end, and taking that away from 1000/fps.


Something like this.
(1000/fps)-(after-before)

It caps the FPS OK, but in most cases, its just to make sure we aren't going at 100 fps on a blank screen or something simple. flashplayer2x rarely reaches full speed on the gp2x, so it may not work accurately.

Also, i believe a really good fps capper can be found with SDL_gfx if the gp2x one is fully implemented.

Good luck.
 
Tnanks for the help guys, I'll look into implementing your improvements in the frame capper :)

Now I have another question, is it possible to set the volume of an .it file via SDL_mixer? When I load a .wav file, I can easily call Mix_VolumeMusic(value), but on module files it has no effect. I peeked in SuperTux's sourcecode, and I saw that it includes MikMod directly. I tried playing with that but it brings the framerate down 70% and doesn't output any sound (most likely I implemented it wrong). Any ideas?

- Alex
 
First, read the manual XD jk.

And to make sure you know...

Code:
I HAVE _NEVER_ USED MIKMOD ON THE GP2X, THIS CODE MAY NOT WORK AS EXPECTED IF THE GP2X IMPLEMENTATION HAPPENS TO BE DIFFERENT THAN THE NORMAL ONE!!!


Ok now...
Heres a basic checklist, although they may seem obvious. Just go through them to make sure.

As far as i know, you need to be doing:
Code:
MikMod_RegisterAllDrivers();

MikMod_RegisterAllLoaders();

Before initialization.

Initialization should be something like:

Code:
md_mode |= DMODE_SOFT_MUSIC;
if (MikMod_Init("")) 
{
Error message and quit
}

Then you have to load the module. Weather its in a struct, class, etc. it should be defined something like this:

Code:
MODULE *modulename;

including the "*".

Loaded something like:

Code:
modulename = Player_Load(Module filename, 64, 0);

It probably should be played like this:

Code:
if (!modulename) {
Error message and quit
}

Player_Start(module);

while (game loop here) {
Game functions, SDL Event, etc here
Frame capping, or if your playing music without anything else to do besides it, usleep(10000);
	MikMod_Update();
}

// Before quiting we must do this. Player_Stop of course can be done during the game or app.
Player_Stop();
Player_Free(modulename);

If all of this is true, make sure the module is not compressed. Open it with winrar. If it fails to open, its decompressed. If it opens, you have 2 choices:
  • Decompress it before usage, meaning in the gp2x during the run of the game
  • Decompress it now and have a slightly larger file
You'll almost certainly want the second one, whereas all you need to do is to drag the file from the winrar out of the winrar window to the desktop or a folder and use it instead of the one you opened with winrar.

In case of the first, find out which compression it uses and simply use a library to decompress it.

Good luck.
 
Thanks for your help Nmn, I apologize for replying so late. I tried your solution, and reread the Mikmod site documentation, but to no avail. I even gave up on making it play in-game, all I want to do now is get it to output some sound (this is before the game loop, or any other game or graphic function):

Code:
	MODULE* music;
	MikMod_RegisterAllDrivers();
	MikMod_RegisterAllLoaders();
	md_mode |= DMODE_SOFT_MUSIC;
	if(MikMod_Init("")) {
		return 0;
	}
	music = Player_Load("music.it", 64, 0);
	if(!music) {
		return 0;
	}
	MikMod_EnableOutput();
	Player_Start(music);

	while(Player_Active()) {
		usleep(10000);
		MikMod_Update(); // WHY, WHY, WHY! :(
	}
If only there was a way to set the sound volume of modules directly from SDL_mixer :(

- Alex
 
Edit: problem fixed, thanks for all the help every one :)

- Alex
 
Back
Top