Choppy Sound Work Around


zzhu8192

Still Fresh
Joined
May 10, 2006
Messages
50
Location
Austin, TX
Website
www.unicorn-jockey.com
I'm currently porting FCEU 0.98.12 to GP2X. I'm able to get about 57 FPS without
sound and 46-50 FPS with sound (depending on the game).
I'm using rlyehs-minimal-lib 0.A for video, sound, and input. compiled using gcc 4.1.0 -O3.

Everything works, but my sound output is choppy, and slightly out of sync.

If I do sound synchronusly, there's a bit a pause/choppiness in the sound.
I assume the pause is from the time write() system call for sound until the next one is done.

If I do sound asynchronusly (i.e. copy buffer in main thread, write() in sound loop thread), it's still choppy for some reason. I've validated the threading is working.

My questions:
1) Is the sound choppy because I can't get enough data to feed the sound write thread at the right
speed? Is it because I'm only getting 50fps?
I look at an emulator like fishyNes, which according to the frame rate is getting about 50FPS.
The sound sounds perfectly smooth though. How is it doing that if the above is the reason?

2) Has anyone used ryleh's sound example? Is using write() a good idea?
Instead of using the thread loop in rlyeh, I'm kind of using my own loop. It also seems that the nanosleep function is not behaving properly. I'm doing pthread_yield() instead.

3) I tried prolonging the sound slightly by interpolating, but obviously it decreases the pitch, and doesn't seem to help much :D

Any hints/suggestions would be appreciated!
 
I use a seperate thread to create a ring buffer but unlike Rlyeh I do not render sound in this thread. Instead I render sound from the main emulation thread. I increased the number of sound banks, I think Rlyeh's original code only has 2 but I increased this to 8.

I've put the code here as a guide, basically all the sound thread does is play a sound bank and then wait for the sound to finish playing, it then moves to the next bank and plays that. It keeps track of the current bank being played in CurrentSoundBank.

Code:
static
void *gp2x_sound_play(void)
{
	while(! gp2x_sound_thread_exit)
	{
		Timer++;
		CurrentSoundBank++;

		if (CurrentSoundBank >= 8) CurrentSoundBank = 0;
		
		write(gp2x_dev[3], (void *) (&gp2x_sound_buffer[4+(CurrentSoundBank*gp2x_sound_buffer[1])]), gp2x_sound_buffer[1]);
		ioctl(gp2x_dev[3], SOUND_PCM_SYNC, 0); 
	}
 
	return NULL;
}

Then in the main thread the code looks at CurrentSoundBank and calculates the next sound bank to render sound to.

Code:
static int SegAim()
{
  int aim=CurrentSoundBank;  

  aim--; if (aim<0) aim+=8;

  return aim;
}

static int RunAudio()
{
  int quit=0,done=0,aim=0,i=0;
  int tick=0,fps=0,skip=0;

  Timer=0;
  Frames=0;
  skip=frameskip-1;

  gp_initSound((int)sound_rates[sound_rate], 16, 1, frame_limit);

  // Get initial position in the audio loop:
  done=SegAim();

  while (quit==0)
  {
	
	for (i=0;i<10;i++)
	{
		if(Timer-tick>frame_limit)
	  {
		   fps=Frames;
		   Frames=0;
		   tick=Timer;
		   sprintf(fps_display,"Fps: %d",fps);
	  }
		
	  aim=SegAim();
	  if (done!=aim)
	  {
		 //We need to render more audio:  
		soundbuffer=pOutput[done];
		laststage=0;
		current_sample=0;
		last_sample=0;
		done++; if (done>=8) done=0;

		if(frameskip==0)
		{
			if (done==aim) quit|=DoFrame(1); // Render last frame
			else		   quit|=DoFrame(0);
		}
		else
		{
			if (skip) 
			{
				quit|=DoFrame(0); // Render last frame
				skip--;
			}
			else 
			{  
				quit|=DoFrame(1);
				skip=frameskip-1;
			}
		}	
		RefreshFm();
		if(laststage<sound_buffer_size) 
					  YM2612UpdateOne(soundbuffer+(laststage<<1),sound_buffer_size-laststage);
	}
	  if (done==aim) break; // Up to date now
	}

	done=aim; // Make sure up to date
  }
  
  gp_stopSound();

  return 0;
}

If none of this makes any sense let me know and I'll try to explain abit better.

Reesy
 
Reesy posted on May 19 2006 at 01:40 AM said:
I use a seperate thread to create a ring buffer but unlike Rlyeh I do not render sound in this thread. Instead I render sound from the main emulation thread. I increased the number of sound banks, I think Rlyeh's original code only has 2 but I increased this to 8.

I've put the code here as a guide, basically all the sound thread does is play a sound bank and then wait for the sound to finish playing, it then moves to the next bank and plays that. It keeps track of the current bank being played in CurrentSoundBank.

If none of this makes any sense let me know and I'll try to explain abit better.

Reesy

Very much appreciate the input and guidance here. I realized that I didn't call ioctl(SYNC). I don't believe it was in rlyeh's minimal.c. I also noticed that write() seems to be pretty fast. I assume internally the sound is copied to some hardware buffer. Sync is definitely blocking, so it's slow. So now I have write() in the main loop, and sync() in the thread, with pthread_yield() after every sync. Without the yield, the sync thread eats up too much cpu. (I tried using the rotating buffer, but it didn't seem to improve quality/stuttering much at all. I'm stil guessing i don't have enough sound to play while waiting for more sound to come in )

So now the sound is in sync. However, it's still a bit stuttery. I'm gonna read up on Linux sound programming some more, to see if I'm missing some concepts. I'm new to game programming.


In your code, it's not obvious to me what happens when there is no more sound to play, or rather the main loop has not yet filled the buffer for the next sound to play?
 
Last edited by a moderator:
zzhu8192 posted on May 19 2006 at 11:47 PM said:
In your code, it's not obvious to me what happens when there is no more sound to play, or rather the main loop has not yet filled the buffer for the next sound to play?

Its quite simple, the sound will crap out :) It assumes that the main loop will always have updated the next sound buffer. If it hasn't then the emulator is basically running too slowly, the main thread will correct this when running in auto-frameskip mode but if a fixed frameskip is being used then the sound will be choppy. Theres not much else I can do, I have to do it this way in order to get DAC emulation correctly. If I didn't care about DAC I could simply render the sound from the sound thread and not worry about it. This is the approach the Rlyeh uses. If you want to continue using this technique you could try increasing the sound fragments, this is something squidge uses in Squidgesnes.

The code used is below, hopefully it makes sense.

#define NUMSAMPLES (256)

Code:
void initaudio (void)
{
	int rate = 16500;
	int bits = 16;
	int stereo = 0;
	int frags = 0x00080008;
	
	
	audiofd = open("/dev/dsp", O_WRONLY);
	if (audiofd == -1)
	{
		printf ("erk! /dev/dsp failed to open!");
		return;
	}
	
	printf ("SNDCTL_DSP_RESET = %d\n", ioctl(audiofd, SNDCTL_DSP_RESET, 0));
	printf ("SNDCTL_DSP_SPEED = %d\n", ioctl(audiofd, SNDCTL_DSP_SPEED,  &rate));
 	printf ("SNDCTL_DSP_SETFMT = %d\n", ioctl(audiofd, SNDCTL_DSP_SETFMT, &bits));
	printf ("SNDCTL_DSP_STEREO = %d\n", ioctl(audiofd, SNDCTL_DSP_STEREO, &stereo)); 
	printf ("SNDCTL_DSP_SETFRAGMENT = %d\n", ioctl(audiofd, SNDCTL_DSP_SETFRAGMENT, &frags));
	printf ("SNDCTL_DSP_GETOSPACE = %d\n", ioctl(audiofd, SNDCTL_DSP_GETOSPACE, &abi));
	printf ("fragsavail: %d fragstotal: %d fragsize: %d bytesavail: %d\n", abi.fragments, abi.fragstotal, abi.fragsize, abi.bytes); 
	gp2x_printf(NULL, -1, -1,"Frags/Total: %d/%d Size: %d Avail: %d", abi.fragments, abi.fragstotal, abi.fragsize, abi.bytes);
	gp2x_video_RGB_flip(0);

	gp2x_get_sound_volume (&sndvolL, &sndvolR);
}

Code:
void *soundthread_f (void *a)
{
	static unsigned short samplebuffer[NUMSAMPLES*2];
	while (1)
	{
		if ((local_mr.apuenable) && (!local_mr.apumute))
		{
			S9xMixSamples ((unsigned char*)samplebuffer, NUMSAMPLES);			// automatically multiplies by 2 when sixteen bit samples asked for
			write (audiofd, (unsigned char*)samplebuffer, NUMSAMPLES<<1); 			// expects bytes, so we need to multiply by 2 for sixteen bit samples
			
		}
	}
	
	return NULL;
}
 
Last edited by a moderator:
Back
Top