Discussion:
Converting OSS sound code to SDL ...
(too old to reply)
Stephen Anthony
2002-08-12 11:55:21 UTC
Permalink
I'm working on the Stella (Atari 2600) emulator. Right now, there is an
external sound server written using OSS. I'm trying to convert it to SDL
sound (so it wil be portable) and re-integrate it into the program (eliminate
the server part).

For some reason, the sound is somewhat 'choppy' and delayed in the SDL
version. It's like the sound is not continuous in SDL. BTW, I've tried
different sample rates.

I've asked questions on this topic before, but I don't know if I was really
clear. So this time, I'm providing links to the original OSS code and my
current attempt at an SDL equivalent.

Would someone be able to take a look at both, and tell me what's missing??

Also, how does one adjust volume in SDL? Right now, the OSS code is changing
the hardware mixer upon start, and restoring it again on exit. Does SDL
support changing *the data itself*, so that I don't have to change the
hardware mixer? If so, how? If not, how does one change the hardware mixer
in SDL?

The files are located at:

http://www.cs.mun.ca/~stephena/linux/OSS_orig.c
http://www.cs.mun.ca/~stephena/linux/OSS_sdl.c

Thanks for any info,
Steve
DrEvil
2002-08-12 14:28:47 UTC
Permalink
Post by Stephen Anthony
Would someone be able to take a look at both, and tell me what's missing??
While I won't claim to be an expert, I think this might be part of the
problem:

desired.samples = 1024;

If I'm not mistaken, that should be at least 2048? Admittedly, I have
done little with SDL's audio API, but I seem to remember Ryan mentioning
this...
Stephen Anthony
2002-08-12 14:37:30 UTC
Permalink
Post by DrEvil
Post by Stephen Anthony
Would someone be able to take a look at both, and tell me what's missing??
While I won't claim to be an expert, I think this might be part of the
desired.samples = 1024;
If I'm not mistaken, that should be at least 2048? Admittedly, I have
done little with SDL's audio API, but I seem to remember Ryan mentioning
this...
I tried 2048 (and 4096 and 8192), and while some of the smoothness returns,
the sounds begin to 'sound' different.

Does anyone know what number of samples are used in OSS? I don't actually
specify the number of samples in OSS, but it seems to work fine with the
default.

Thanks,
Steve
DrEvil
2002-08-12 15:04:38 UTC
Permalink
Post by Stephen Anthony
I tried 2048 (and 4096 and 8192), and while some of the smoothness returns,
the sounds begin to 'sound' different.
By chance are you running a Sound Server in the background? Like Arts,
or ESD?
Stephen Anthony
2002-08-12 15:15:53 UTC
Permalink
Post by DrEvil
Post by Stephen Anthony
I tried 2048 (and 4096 and 8192), and while some of the smoothness
returns, the sounds begin to 'sound' different.
By chance are you running a Sound Server in the background? Like Arts,
or ESD?
Definitely not. But it does sound that way. I don't run (and never have)
any sound servers. They screw with real-time sound from games and emulators.

I'm thinking it may have something to do with blocking vs. non-blocking mode.
The OSS code sets non-blocking mode. Does SDL do this by default? If not,
does anyone know how to enable it??

Thanks,
Steve
Ryan C. Gordon
2002-08-12 19:16:00 UTC
Permalink
Post by Stephen Anthony
I'm thinking it may have something to do with blocking vs. non-blocking mode.
The OSS code sets non-blocking mode. Does SDL do this by default? If not,
does anyone know how to enable it??
I don't think it blocks, since it runs in a separate thread. If you are
choking the processor, this might be a problem.

--ryan.
nbs
2002-08-12 16:22:32 UTC
Permalink
Post by Stephen Anthony
I'm working on the Stella (Atari 2600) emulator. Right now, there is an
external sound server written using OSS. I'm trying to convert it to SDL
sound (so it wil be portable) and re-integrate it into the program (eliminate
the server part).
Awesome! :) Sound for the Zaurus soon! :)
Post by Stephen Anthony
For some reason, the sound is somewhat 'choppy' and delayed in the SDL
version. It's like the sound is not continuous in SDL. BTW, I've tried
different sample rates.
Have you tried different buffer sizes?

-bill!
Stephen Anthony
2002-08-12 17:02:55 UTC
Permalink
Post by nbs
Post by Stephen Anthony
I'm working on the Stella (Atari 2600) emulator. Right now, there is an
external sound server written using OSS. I'm trying to convert it to SDL
sound (so it wil be portable) and re-integrate it into the program
(eliminate the server part).
Awesome! :) Sound for the Zaurus soon! :)
Well, maybe not so soon :) I'm hoping to get it working ASAP, but sound
programming is not my strong point. Hence this posting.
Post by nbs
Post by Stephen Anthony
For some reason, the sound is somewhat 'choppy' and delayed in the SDL
version. It's like the sound is not continuous in SDL. BTW, I've tried
different sample rates.
Have you tried different buffer sizes?
Of course, that is what I meant to say :) I've tried different buffer sizes.
The sample rate is set at 31400 Hz. I may try to go to 22050 (or 44100), but
SDL will emulate whatever it requires, won't it ??

Steve
Ryan C. Gordon
2002-08-12 19:17:57 UTC
Permalink
Post by Stephen Anthony
Of course, that is what I meant to say :) I've tried different buffer sizes.
The sample rate is set at 31400 Hz. I may try to go to 22050 (or 44100), but
SDL will emulate whatever it requires, won't it ??
31400?!

If your sound card can't do that, SDL will emulate it, poorly, in software.

--ryan.
nbs
2002-08-12 20:25:17 UTC
Permalink
Post by Ryan C. Gordon
Post by Stephen Anthony
Of course, that is what I meant to say :) I've tried different buffer sizes.
The sample rate is set at 31400 Hz. I may try to go to 22050 (or 44100), but
SDL will emulate whatever it requires, won't it ??
31400?!
Hehe - Was that rate picked because it had something to do with the
original Atari 8-bit's sound chips, I wonder? :)

-bill!
Stephen Anthony
2002-08-12 20:53:41 UTC
Permalink
Post by nbs
Post by Ryan C. Gordon
Post by Stephen Anthony
Of course, that is what I meant to say :) I've tried different
buffer sizes. The sample rate is set at 31400 Hz. I may try to go
to 22050 (or 44100), but SDL will emulate whatever it requires,
won't it ??
31400?!
Hehe - Was that rate picked because it had something to do with the
original Atari 8-bit's sound chips, I wonder? :)
Yes, it was. I tried going back to 22050, and it sounds the same. Since
it doesn't make it any worse, I'll leave it at that for now. That
*should* be supported by almost all soundcards I think.

Steve
Neil Bradley
2002-08-12 22:25:31 UTC
Permalink
Post by Stephen Anthony
Post by nbs
Post by Ryan C. Gordon
31400?!
Hehe - Was that rate picked because it had something to do with the
original Atari 8-bit's sound chips, I wonder? :)
Yes, it was. I tried going back to 22050, and it sounds the same. Since
it doesn't make it any worse, I'll leave it at that for now. That
*should* be supported by almost all soundcards I think.
What would be the difficulty/problem with upsampling to 44100? Roughly,
every 1.4044 samples, you would insert your own "oversampled" sample that
is halfway between the prior and next sample. It's a crude method, but
you'll find that you'll get significantly more frequency response by using
a higher sampling rate (and yes, the chip DID generate tones higher than
11khz). Quite a bit of the pokey's character will be lost without it.

BTW, Are you doing Pokey emulation? there's already a pokey library out
there that emulates the Pokey at whatever speed you want it to. Let me
know if you'd like a copy.

-->Neil

-------------------------------------------------------------------------------
Neil Bradley What are burger lovers saying
Synthcom Systems, Inc. about the new BK Back Porch Griller?
ICQ #29402898 "It tastes like it came off the back porch." - Me
Stephen Anthony
2002-08-12 22:28:33 UTC
Permalink
Post by Neil Bradley
Post by Stephen Anthony
Post by nbs
Post by Ryan C. Gordon
31400?!
Hehe - Was that rate picked because it had something to do with the
original Atari 8-bit's sound chips, I wonder? :)
Yes, it was. I tried going back to 22050, and it sounds the same.
Since it doesn't make it any worse, I'll leave it at that for now.
That *should* be supported by almost all soundcards I think.
What would be the difficulty/problem with upsampling to 44100? Roughly,
every 1.4044 samples, you would insert your own "oversampled" sample
that is halfway between the prior and next sample. It's a crude method,
but you'll find that you'll get significantly more frequency response
by using a higher sampling rate (and yes, the chip DID generate tones
higher than 11khz). Quite a bit of the pokey's character will be lost
without it.
Main difficulty would be the fact that sound programming is my weak spot,
and I don't know enough (right now) on how to do it. While your
suggestion is probably a better solution, I just want to translate as
close as possible from the OSS code. Also, when I went to 44100, I get
no sound at all :( Very frustrating. I don't know if SDL is causing the
problem, or if it's the TIASound library.
Post by Neil Bradley
BTW, Are you doing Pokey emulation? there's already a pokey library out
there that emulates the Pokey at whatever speed you want it to. Let me
know if you'd like a copy.
Sorry, I'm not familiar with that. Actually, I'm working on adding
features and completing the port to SDL. For the guts of the emulator,
Brad Mott is the one to speak to. He originally wrote the thing. But
send along any info which you think is relevant. I (or somebody else on
the team) may be able to make use of it.

Thanks,
Steve
Stephen Anthony
2002-08-12 17:10:51 UTC
Permalink
I have another question wrt. this. When using OSS, you open /dev/dsp and
write to the soundcard directly (using write(...)). When that write is
finished, does the sound keep playing? That is, when you write data to the
sound card, does it keep using that data until something else is written to
it?

That's what seems to be happening in the OSS code I have now. If SDL doesn't
do this, that *could* explain why the SDL version is choppy. It only plays
what data is written to it, then stops. These 'stops' could be interpreted
as choppiness in the playback.

Is this how it works? Or is this theory useless.

Is this somehow related to blocking vs. non-blocking mode? I'm still not
sure what the difference is between these.

Thanks,
Steve
Ryan C. Gordon
2002-08-12 19:22:40 UTC
Permalink
Post by Stephen Anthony
I have another question wrt. this. When using OSS, you open /dev/dsp and
write to the soundcard directly (using write(...)). When that write is
finished, does the sound keep playing? That is, when you write data to the
sound card, does it keep using that data until something else is written to
it?
Depends on the driver, I think.
Post by Stephen Anthony
That's what seems to be happening in the OSS code I have now. If SDL doesn't
do this, that *could* explain why the SDL version is choppy. It only plays
what data is written to it, then stops. These 'stops' could be interpreted
as choppiness in the playback.
Is this how it works? Or is this theory useless.
SDL writes to /dev/dsp (or whatnot) and then calls your callback when it
needs more data. If you don't feed it as much data as it wants when your
callback gets called, you'll probably get choppiness or maybe repeats if
you happen to get the same memory buffer and it hasn't been reinitialized
(depends on the SDL target and hardware driver). But that memory buffer in
your callback is going to the audio device, ready or not.
Post by Stephen Anthony
Is this somehow related to blocking vs. non-blocking mode? I'm still not
sure what the difference is between these.
Blocking says "write to the audio device, and don't return from the
write() call until all the data is written (or a fatal error occurs)". Non
blocking says "write to the audio device and return immediately, telling
me how much you managed to write".

SDL writes to the audio device in a separate thread, so blocking is more
or less irrelevant, since all this thread does is write to the audio
device, get more data from your callback as needed, and write to the audio
device again.

--ryan.
Stephen Anthony
2002-08-12 21:00:30 UTC
Permalink
This post might be inappropriate. Click to display it.
Ryan C. Gordon
2002-08-13 08:24:59 UTC
Permalink
Post by Stephen Anthony
Guess what I want is a way to simulate write(...) to the soundcard, and
only send stuff to the card when a write is done (and under my control).
Keep a buffer of data somewhere. Call SDL_LockAudio() when putting more
data into that buffer, and SDL_UnlockAudio() when you are done putting
data into it. (Lock/UnlockAudio guarantees that the audio callback won't
run...do NOT use it for extended periods of time! Just as a mutex to
prevent race conditions. Do not call LockAudio from inside the callback,
either.)

As the callback runs, feed SDL as much data as it wants. If you don't have
anymore data, feed it silence.

This is a different paradigm than direct write()s to /dev/dsp, and your
code needs to be adapted to it.

--ryan.
Stephen Anthony
2002-08-14 13:57:39 UTC
Permalink
Post by Ryan C. Gordon
Post by Stephen Anthony
Guess what I want is a way to simulate write(...) to the soundcard,
and only send stuff to the card when a write is done (and under my
control).
Keep a buffer of data somewhere. Call SDL_LockAudio() when putting more
data into that buffer, and SDL_UnlockAudio() when you are done putting
data into it. (Lock/UnlockAudio guarantees that the audio callback
won't run...do NOT use it for extended periods of time! Just as a mutex
to prevent race conditions. Do not call LockAudio from inside the
callback, either.)
Yes, that is what I'm doing now. As an experiment, I set up a mutex (of
sorts) and 2 count variables. In between calls to SDL_LockAudio() /
SDL_UnlockAudio(), I update the sound buffer (separate code to do this),
set the mutex to 1 and increment a variable that counts how many times
the buffer was updated.

In the callback, I check to see if the mutex is 1, then copy the buffer to
the SDL stream, set mutex to 0, and increment a variable that says how
many times the stream was copied.

All things being equal, the 2 counts should be the same at the end of the
run. But they aren't. There are approx. 4 times as many attempts as
there are actual updates to the stream. In other words, only 25% of the
sounds I process are actually sent to the soundcard. This would
definitely explain the dropouts I'm hearing.

Problem is, I don't know what causes it. Is the callback 'atomic'. That
is, while it's being called, it can't be called again (from the sound
thread)?

I'm really lost here. The mutex is set to 1 whenever the buffer is
updated. The callback seems to not 'see' many of these changes. So
maybe the callback isn't being called often enough??
Post by Ryan C. Gordon
As the callback runs, feed SDL as much data as it wants. If you don't
have anymore data, feed it silence.
How do you know how much data "SDL wants"? And how to you feed it silence
(I assume by memset'ing 0 to the stream)?
Post by Ryan C. Gordon
This is a different paradigm than direct write()s to /dev/dsp, and your
code needs to be adapted to it.
Problem is, I'm not sure how to do that :)

Thanks,
Steve
Loren Osborn
2002-08-14 17:48:59 UTC
Permalink
Post by Stephen Anthony
Post by Ryan C. Gordon
Post by Stephen Anthony
Guess what I want is a way to simulate
write(...) to the soundcard,
Post by Ryan C. Gordon
Post by Stephen Anthony
and only send stuff to the card when a write is
done (and under my
Post by Ryan C. Gordon
Post by Stephen Anthony
control).
Keep a buffer of data somewhere. Call
SDL_LockAudio() when putting more
Post by Ryan C. Gordon
data into that buffer, and SDL_UnlockAudio() when
you are done putting
Post by Ryan C. Gordon
data into it. (Lock/UnlockAudio guarantees that
the audio callback
Post by Ryan C. Gordon
won't run...do NOT use it for extended periods of
time! Just as a mutex
Post by Ryan C. Gordon
to prevent race conditions. Do not call LockAudio
from inside the
Post by Ryan C. Gordon
callback, either.)
Yes, that is what I'm doing now. As an experiment,
I set up a mutex (of
sorts) and 2 count variables. In between calls to
SDL_LockAudio() /
SDL_UnlockAudio(), I update the sound buffer
(separate code to do this),
set the mutex to 1 and increment a variable that
counts how many times
the buffer was updated.
In the callback, I check to see if the mutex is 1,
then copy the buffer to
the SDL stream, set mutex to 0, and increment a
variable that says how
many times the stream was copied.
All things being equal, the 2 counts should be the
same at the end of the
run. But they aren't. There are approx. 4 times as
many attempts as
there are actual updates to the stream. In other
words, only 25% of the
sounds I process are actually sent to the soundcard.
This would
definitely explain the dropouts I'm hearing.
Problem is, I don't know what causes it. Is the
callback 'atomic'. That
is, while it's being called, it can't be called
again (from the sound
thread)?
Unless it's recursive, it should not be called again
until it returns. But it is in a seperate thread, it
won't be at all in sync with the rest of your program.
Post by Stephen Anthony
I'm really lost here. The mutex is set to 1
whenever the buffer is
updated. The callback seems to not 'see' many of
these changes. So
maybe the callback isn't being called often enough??
For me anyway, it is much easier to to think of the
mutex being "locked" or "unlocked" rather than 1 or
0...

The point is the callback isn't called in sync with
any part of your program, so you should probably make
a mutex-protected ring buffer... The callback can read
out of it, and the rest of your sound code can feed
it... When it becomes empty, the callback should send
the silence value.
Post by Stephen Anthony
Post by Ryan C. Gordon
As the callback runs, feed SDL as much data as it
wants. If you don't
Post by Ryan C. Gordon
have anymore data, feed it silence.
How do you know how much data "SDL wants"? And how
to you feed it silence
(I assume by memset'ing 0 to the stream)?
The SDL_AudioSpec structure has a member value called
"silence" that indicates what silence value to write
to the stream.
Post by Stephen Anthony
Post by Ryan C. Gordon
This is a different paradigm than direct write()s
to /dev/dsp, and your
Post by Ryan C. Gordon
code needs to be adapted to it.
Problem is, I'm not sure how to do that :)
Hope this helps,

-Loren



__________________________________________________
Do You Yahoo!?
HotJobs - Search Thousands of New Jobs
http://www.hotjobs.com
Stephen Anthony
2002-08-14 18:20:21 UTC
Permalink
Post by Loren Osborn
For me anyway, it is much easier to to think of the
mutex being "locked" or "unlocked" rather than 1 or
0...
That's why I called it a mutex of sorts. It isn't really a mutex at all.
Post by Loren Osborn
The point is the callback isn't called in sync with
any part of your program, so you should probably make
a mutex-protected ring buffer... The callback can read
out of it, and the rest of your sound code can feed
it... When it becomes empty, the callback should send
the silence value.
Yes, this is what Sam mentioned, but I wasn't sure what he meant. Now I
know :) Quite a bit more complicated than an ordinary write(). I was
still thinking in terms of one write for one sound. I forgot about the
threading part, where the sound thread isn't necessarily in sync with the
other code. Going from single-threaded to multi-threaded can be
difficult sometimes.

BTW, how do you know how much data you can write on each call to the
callback?
Post by Loren Osborn
The SDL_AudioSpec structure has a member value called
"silence" that indicates what silence value to write
to the stream.
OK, I didn't know that.

Thanks,
Steve
Loren Osborn
2002-08-14 18:57:01 UTC
Permalink
Post by Stephen Anthony
Post by Loren Osborn
For me anyway, it is much easier to to think of
the
Post by Loren Osborn
mutex being "locked" or "unlocked" rather than 1
or
Post by Loren Osborn
0...
That's why I called it a mutex of sorts. It isn't
really a mutex at all.
Post by Loren Osborn
The point is the callback isn't called in sync
with
Post by Loren Osborn
any part of your program, so you should probably
make
Post by Loren Osborn
a mutex-protected ring buffer... The callback can
read
Post by Loren Osborn
out of it, and the rest of your sound code can
feed
Post by Loren Osborn
it... When it becomes empty, the callback should
send
Post by Loren Osborn
the silence value.
Yes, this is what Sam mentioned, but I wasn't sure
what he meant. Now I
know :) Quite a bit more complicated than an
ordinary write(). I was
still thinking in terms of one write for one sound.
I forgot about the
threading part, where the sound thread isn't
necessarily in sync with the
other code. Going from single-threaded to
multi-threaded can be
difficult sometimes.
BTW, how do you know how much data you can write on
each call to the
callback?
The callback prototype looks like:

void (*callback)(void *userdata, Uint8 *stream, int
len);

so, the buffer length is passed in to you, you
probably need to divide it by the number of channels
(1 or 2) and the number of bytes per sample (1 or
2)...

Best of luck,

-Loren

__________________________________________________
Do You Yahoo!?
HotJobs - Search Thousands of New Jobs
http://www.hotjobs.com
Stephen Anthony
2002-08-14 19:13:17 UTC
Permalink
Post by Loren Osborn
Post by Stephen Anthony
BTW, how do you know how much data you can write on
each call to the
callback?
void (*callback)(void *userdata, Uint8 *stream, int
len);
so, the buffer length is passed in to you, you
probably need to divide it by the number of channels
(1 or 2) and the number of bytes per sample (1 or
2)...
So, that is what the 'len' argument is used for. I always thought it to
be unnecessary since you always want to write the full buffer, and the
buffer you create is always the same as 'len'. Obviously, this isn't
always true.

One final question. Using a ring buffer may help every sound fragment to
be played (eventually), but what about lag? That is, if there are many
fragments waiting in the queue, what stops the program from going out of
sync? There may be things that have already happened onscreen but still
have a sound waiting in the queue to play. This may get worse as time
goes by. How do you get around that??

Thanks,
Steve
Sam Lantinga
2002-08-13 04:21:03 UTC
Permalink
Post by Stephen Anthony
Now you may have something here. I think this may be the problem. Right
now, when there is data to write (to the soundcard), the code does a
write(), and thats it.
With SDL, it seems that the buffer has to kept full when the *callback*
wants to use it. As the code is now, the buffer is only filled when the
*sound server* wants to write some data.
Is there an easy way to make SDL behave this way? I want the code itself
controlling when to do the write, not have the callback function control
that.
What you can do is keep a write queue of audio buffers, and have the audio
decoding callback pull data from that queue. That's actually how the write()
interface is implemented in the kernel. So, you can use a ring buffer or
a mutex protected queue for queueing sound. The reason why SDL uses a
callback is because the audio data is requested exactly when the callback
runs. If you prepare the data too far in advance, then you'll have audio
delays, if you don't keep the queue filled you'll have drop outs.

For what it's worth, this is tricky stuff to get right, so don't feel
bad about struggling with it. :)

Oh, and you're probably best off using the 22Khz sound until you're
comfortable with the sound pipeline. Once you have it working, then
you can work on upsampling to 44Khz.

See ya,
-Sam Lantinga, Software Engineer, Blizzard Entertainment
Robert Wohleb
2002-08-14 19:37:39 UTC
Permalink
If the thread reading the buffer is that lagged, something is wrong. At this point you might want to ask yourself if such a delayed sound should be played at all. Detecting such a large lag and flushing the buffer would at least keep sounds from being too out of sync. Like I said though, they wouldn't get played.

~Rob

-----Original Message-----
From: Stephen Anthony [mailto:***@roadrunner.nf.net]
Sent: Wednesday, August 14, 2002 12:13 PM
To: ***@libsdl.org
Subject: Re: [SDL] Converting OSS sound code to SDL ...
Post by Loren Osborn
Post by Stephen Anthony
BTW, how do you know how much data you can write on
each call to the
callback?
void (*callback)(void *userdata, Uint8 *stream, int
len);
so, the buffer length is passed in to you, you
probably need to divide it by the number of channels
(1 or 2) and the number of bytes per sample (1 or
2)...
So, that is what the 'len' argument is used for. I always thought it to
be unnecessary since you always want to write the full buffer, and the
buffer you create is always the same as 'len'. Obviously, this isn't
always true.

One final question. Using a ring buffer may help every sound fragment to
be played (eventually), but what about lag? That is, if there are many
fragments waiting in the queue, what stops the program from going out of
sync? There may be things that have already happened onscreen but still
have a sound waiting in the queue to play. This may get worse as time
goes by. How do you get around that??

Thanks,
Steve
Stephen Anthony
2002-08-14 20:04:42 UTC
Permalink
Post by Robert Wohleb
If the thread reading the buffer is that lagged, something is wrong. At
this point you might want to ask yourself if such a delayed sound
should be played at all. Detecting such a large lag and flushing the
buffer would at least keep sounds from being too out of sync. Like I
said though, they wouldn't get played.
~Rob
Maybe I'm trying to stuff too many 'small' sounds to the card. Right now,
the emulator does a virtual 'poke' to write some sound. That 'poke'
value is sent to some external code (TIAsound library) and put in a
fragment. Then that fragment is sent to the card.

I know that the thread itself is not lagging, since the emulator and sound
server together take up maybe 1.5% of my CPU. The sound MUST be played.
The solution can't involve dropping sounds. I need to play all
fragments, but not have lag in doing so. And as I mentioned, the problem
is not CPU-bound.

I suppose I could try queuing *each* poke until one frame has passed, then
process all of those in one fragment, and pass *that* to the card
instead. Maybe that would cut down on the stuff that the callback should
process, and a ring buffer wouldn't even be needed. I just don't
understand why it's so complicated implementing a simple 'write()' to a
soundcard.
Post by Robert Wohleb
-----Original Message-----
Sent: Wednesday, August 14, 2002 12:13 PM
Subject: Re: [SDL] Converting OSS sound code to SDL ...
Post by Loren Osborn
Post by Stephen Anthony
BTW, how do you know how much data you can write on
each call to the
callback?
void (*callback)(void *userdata, Uint8 *stream, int
len);
so, the buffer length is passed in to you, you
probably need to divide it by the number of channels
(1 or 2) and the number of bytes per sample (1 or
2)...
So, that is what the 'len' argument is used for. I always thought it
to be unnecessary since you always want to write the full buffer, and
the buffer you create is always the same as 'len'. Obviously, this
isn't always true.
One final question. Using a ring buffer may help every sound fragment
to be played (eventually), but what about lag? That is, if there are
many fragments waiting in the queue, what stops the program from going
out of sync? There may be things that have already happened onscreen
but still have a sound waiting in the queue to play. This may get
worse as time goes by. How do you get around that??
Thanks,
Steve
_______________________________________________
SDL mailing list
http://www.libsdl.org/mailman/listinfo/sdl
_______________________________________________
SDL mailing list
http://www.libsdl.org/mailman/listinfo/sdl
Neil Bradley
2002-08-14 21:25:56 UTC
Permalink
Post by Stephen Anthony
Maybe I'm trying to stuff too many 'small' sounds to the card. Right now,
the emulator does a virtual 'poke' to write some sound. That 'poke'
value is sent to some external code (TIAsound library) and put in a
fragment. Then that fragment is sent to the card.
That won't work, unfortunately. When I did Pokey emulation, I had to
create a "virtual CPU to actual timestamp" queue, where I took the
emulated CPU time of each write to the Pokey, and the callback to update
the Pokey sound fragments would apply them as it went. Without it, I'd get
missing sounds, lack of detail, and overall just oddball sounds. This was
really prevalent in games like Tempest where the Pokey's registers were
changed at a much faster rate (roughly every 50-100 samples on average,
and in some cases 10-20!).

You don't want to look at it in terms of "frames", but rather your
emulated CPU->sample rate. After each sample, figure out how much time has
passed as compared to the CPU, and apply any pending writes to the TIA
sound chip (the predecessor to the Pokey).
Post by Stephen Anthony
process, and a ring buffer wouldn't even be needed. I just don't
understand why it's so complicated implementing a simple 'write()' to a
soundcard.
Probably because the design of the original software had a "push sound out
the port" mentality with an expectation of fixed sample rates that are not
available, which leads to dropouts, clicks, and pops. Plus, you're also
assuming that audio is a simple thing when doing emulation - it isn't. So
really the problem is the TIA sound routines not being written to do
variable sample rates, nor taking into account the possibility of sound
callbacks.

SDL's method is far superior and is the way it should've been done since
the dawn of time on all platforms. Even DOS sound card drivers could do
this. If it needs more data to keep the sound buffer full (I.E. no
dropouts), it asks for more. Voila - no dropouts.

If you're interested, get with me offline on this. Ican save you a lot of
additional hassle.

-->Neil

-------------------------------------------------------------------------------
Neil Bradley What are burger lovers saying
Synthcom Systems, Inc. about the new BK Back Porch Griller?
ICQ #29402898 "It tastes like it came off the back porch." - Me
Ryan C. Gordon
2002-08-14 21:54:20 UTC
Permalink
Post by Neil Bradley
If you're interested, get with me offline on this. Ican save you a lot of
additional hassle.
(For the record, this is what I like about this mailing list. People here
really can't stand to see developers get stuck, and they drop what they're
doing to help, answer, patch, assist where they can. Thank you, Neil.)

--ryan.
Stephen Anthony
2002-08-14 23:07:44 UTC
Permalink
Post by Ryan C. Gordon
Post by Neil Bradley
If you're interested, get with me offline on this. Ican save you a
lot of additional hassle.
(For the record, this is what I like about this mailing list. People
here really can't stand to see developers get stuck, and they drop what
they're doing to help, answer, patch, assist where they can. Thank you,
Neil.)
--ryan.
And a big thank you to you Ryan, Sam, Neil, and all the others who offered
help. I'm that much closer to getting it all working, and much more
importantly, understanding how it all works :)

Steve
Ryan C. Gordon
2002-08-14 21:50:37 UTC
Permalink
Post by Stephen Anthony
process, and a ring buffer wouldn't even be needed. I just don't
understand why it's so complicated implementing a simple 'write()' to a
soundcard.
Portability.

The audio callback needs to feed a DirectSound secondary buffer when
Windows passes the SDL app a windows event saying the buffer needs to be
refilled. On MacOS, you're doing the same thing, but you're filling a
sound buffer during a hardware interrupt.

Most systems do _NOT_ treat the audio device as a file you write a stream
of bytes to, and SDL needs to deal with that.

As for lag, if you've chosen a small enough setting in the "samples" field
you passed to SDL_OpenAudio, you shouldn't have trouble. The callback does
tend to run with some degree of reliability.

--ryan.
Stephen Anthony
2002-08-14 22:59:49 UTC
Permalink
Post by Ryan C. Gordon
Post by Stephen Anthony
process, and a ring buffer wouldn't even be needed. I just don't
understand why it's so complicated implementing a simple 'write()' to
a soundcard.
Portability.
The audio callback needs to feed a DirectSound secondary buffer when
Windows passes the SDL app a windows event saying the buffer needs to
be refilled. On MacOS, you're doing the same thing, but you're filling
a sound buffer during a hardware interrupt.
Most systems do _NOT_ treat the audio device as a file you write a
stream of bytes to, and SDL needs to deal with that.
As for lag, if you've chosen a small enough setting in the "samples"
field you passed to SDL_OpenAudio, you shouldn't have trouble. The
callback does tend to run with some degree of reliability.
OK, thanks for the info. I'm used to thinking of the UNIX way of doing
things. But I definitely want portability. In fact, that's why I'm
porting the OSS code to SDL in the first place :)

Thanks,
Steve
Neil Bradley
2002-08-14 23:53:46 UTC
Permalink
Post by Ryan C. Gordon
The audio callback needs to feed a DirectSound secondary buffer when
Windows passes the SDL app a windows event saying the buffer needs to be
refilled. On MacOS, you're doing the same thing, but you're filling a
sound buffer during a hardware interrupt.
Most systems do _NOT_ treat the audio device as a file you write a stream
of bytes to, and SDL needs to deal with that.
Based on the implementations I've seen in all UNIXen, sound is more of an
afterthought than "designed in". Using the "pushing sound out the port
every once in a while" method is a good way to get clicks, repeats, pops,
and dropouts in your audio, especially in a nonrealtime system.

It still boggles my mind why Windows, MacOS, and even DOS use callbacks
but none of the UNIXen do. It's as if whomever implemented audio
originally in various UNIXen tried to fit the audio card to UNIX's driver
model, rather than realizing it's not a good way to handle it and instead
create different APIs that closely mirror the sound card hardware.

-->Neil

-------------------------------------------------------------------------------
Neil Bradley What are burger lovers saying
Synthcom Systems, Inc. about the new BK Back Porch Griller?
ICQ #29402898 "It tastes like it came off the back porch." - Me
Brian Hook
2002-08-14 23:50:47 UTC
Permalink
Post by Neil Bradley
It still boggles my mind why Windows, MacOS, and even DOS use
callbacks but none of the UNIXen do.
- desire to adhere to Unix's file-based design methodology. There's a
certain elegance to being able to "cat" to /dev/audio

- the fact that Unix has generally never been a multimedia targeted
machine. Playing audio was never a high priority.

These don't make up for the fact that it's non-ideal (to say the least),
but I would argue that audio (and any other pull-model tasks) are
difficult to model generally, portably and cleanly. Different
architectures have too many different ways of handling this -- you have
interrupt service routines calling callbacks under DOS and MacOS, and
under DirectSound/Win32 there are at least three different mechanisms
I'm aware of (sleep() and poll, WaitForObject() with event objects, and
MM_xxx message processing). I'm sure CoreAudio on OS X is just as grim,
assuming they ever documented it =)

Brian
Neil Bradley
2002-08-15 00:45:19 UTC
Permalink
This post might be inappropriate. Click to display it.
Sam Lantinga
2002-08-14 20:55:42 UTC
Permalink
Post by Stephen Anthony
I suppose I could try queuing *each* poke until one frame has passed, then
process all of those in one fragment, and pass *that* to the card
instead. Maybe that would cut down on the stuff that the callback should
process, and a ring buffer wouldn't even be needed. I just don't
understand why it's so complicated implementing a simple 'write()' to a
soundcard.
Continually queue "pokes" into a list, and in the audio callback, keep
writing "pokes" into the audio stream buffer until it's full. If the
poke rate and the sound card eat rate is similar, then you should get
smooth sound.

If you're consistently getting fewer pokes than the amount of audio
requested by the card, then your emulator rate and audio output rate
are not synchronized. I've seen this often with emulators, where due
to drift in virtual interrupts, the audio isn't fed at the same rate
that the audio device is opened. If this is happening, you probably
need to look at your audio design and make sure it's really doing what
you expect it to. For example, does your poke span a particular time?
If so, is it really being called regularly for that interval of time?
If not, maybe you need to interpolate between the number of pokes you
get and the number you should have gotten in a given time frame.

Like I said, make sure you're using 22KHz until you have everything working.

Oh, the silence member of the audio spec structure is calculated from
the audio format. It's either 0x00 or 0x80, depending on the format.
You don't ever need to specify it at all.

See ya,
-Sam Lantinga, Software Engineer, Blizzard Entertainment
Stephen Anthony
2002-08-14 21:31:01 UTC
Permalink
Post by Sam Lantinga
Post by Stephen Anthony
I suppose I could try queuing *each* poke until one frame has passed,
then process all of those in one fragment, and pass *that* to the
card instead. Maybe that would cut down on the stuff that the
callback should process, and a ring buffer wouldn't even be needed.
I just don't understand why it's so complicated implementing a simple
'write()' to a soundcard.
Continually queue "pokes" into a list, and in the audio callback, keep
writing "pokes" into the audio stream buffer until it's full. If the
poke rate and the sound card eat rate is similar, then you should get
smooth sound.
This is sort of what I was thinking. Problem is, I can't queue the pokes,
but the fragments instead. This is because there is external code that
processes the pokes and generates an appropriate fragment. Basically,
the sound module is one big state machine. I feed pokes to it, and call
TIA_Update(...), and get a fragment with all those pokes already done.
Post by Sam Lantinga
If you're consistently getting fewer pokes than the amount of audio
requested by the card, then your emulator rate and audio output rate
are not synchronized. I've seen this often with emulators, where due
to drift in virtual interrupts, the audio isn't fed at the same rate
that the audio device is opened. If this is happening, you probably
need to look at your audio design and make sure it's really doing what
you expect it to. For example, does your poke span a particular time?
If so, is it really being called regularly for that interval of time?
If not, maybe you need to interpolate between the number of pokes you
get and the number you should have gotten in a given time frame.
When I initialize the sound module using TIA_Init(...), you pass it the
sample rate of the virtual machine (31400) and the sample rate of the
actual sound card you are using (in this case, 22050). So I assume that
any syncronization issues are taken care of in the module. I didn't
write it, so I'll have to check for sure.

I'm inclined to think that there is nothing wrong with the infrastructure
as it is, since it works fine when using OSS. I think its a timing
problem because SDL does sound in a separate thread. Not that is the
fault of SDL either. It's just that the emulator code wasn't designed to
do it that way.

Steve
Jacek Popławski
2002-08-14 21:31:09 UTC
Permalink
Post by Stephen Anthony
I'm working on the Stella (Atari 2600) emulator. Right now, there is an
external sound server written using OSS. I'm trying to convert it to SDL
sound (so it wil be portable) and re-integrate it into the program (eliminate
the server part).
Have you checked my Atari800 SDL port? It used OSS and I wrote SDL support.
--
http://decopter.sf.net - free unrealistic helicopter simulator
Stephen Anthony
2002-08-14 22:01:37 UTC
Permalink
Post by Jacek Popławski
Post by Stephen Anthony
I'm working on the Stella (Atari 2600) emulator. Right now, there is
an external sound server written using OSS. I'm trying to convert it
to SDL sound (so it wil be portable) and re-integrate it into the
program (eliminate the server part).
Have you checked my Atari800 SDL port? It used OSS and I wrote SDL support.
I hadn't, but you can be sure I will do so immediately :)

Thanks,
Steve
Loren Osborn
2002-08-15 01:22:52 UTC
Permalink
Post by Stephen Anthony
On Mon, Aug 12, 2002 at 09:25:21AM -0230, Stephen
Post by Stephen Anthony
I'm working on the Stella (Atari 2600) emulator.
Right now, there is
Post by Stephen Anthony
an external sound server written using OSS. I'm
trying to convert it
Post by Stephen Anthony
to SDL sound (so it wil be portable) and
re-integrate it into the
Post by Stephen Anthony
program (eliminate the server part).
Have you checked my Atari800 SDL port? It used OSS
and I wrote SDL
support.
I hadn't, but you can be sure I will do so
immediately :)
Thanks,
Steve
Hmm... I just had a thought... I noticed you mentioned
that you thought the "len" argument was redundant, and
also that you didn't know WHEN the sound buffer wasn't
getting enough data. Perhaps it's that you're getting
the buffer size of the original sound file and the SDL
sound buffer size confused?

Basically every so often the SDL sound subsystem
decides that the buffer is getting close to empty, and
it needs more data. It calls your callback saying it
needs another "len" bytes of data... This len will be
some power of 2, but will almost never be the same
length as your sound file. if your sound file is
longer than this length, you need to cut your sound
file into pieces, and feed the sound subsystem a chunk
at a time. If your sound is shorter than the
destination buffer, you can either append more sounds,
or silence... but you need to fill up the whole
buffer, or you'll hear junk from the uninitialized
buffer being played, and that can be rather hideous...

I think what you need is to queue up the sounds to be
played into a ring buffer, empty the ring-buffer in
"len" size chunks, and fill the rest of the dest
buffer with silence when it empties.

I hope that this helps,

-Loren

__________________________________________________
Do You Yahoo!?
HotJobs - Search Thousands of New Jobs
http://www.hotjobs.com

Continue reading on narkive:
Loading...