Re: Noisy DefaultOutputUnit
Re: Noisy DefaultOutputUnit
- Subject: Re: Noisy DefaultOutputUnit
- From: Kurt Revis <email@hidden>
- Date: Sat, 26 Jan 2008 16:44:44 -0800
On Jan 26, 2008, at 12:57 PM, Paul Scott wrote:
Now, while experimenting with DefaultOutputUnit, I discovered some
interesting problems. Running the program, unmodified, produces
nasty clicks in the speaker (on two iMacs and a MacBook Pro, and
presumably on all Macs) when switching sampling rates. The clicks
seem to arise when CFRunLoopRunInMode() times out.
That's not the root cause, though. I don't think this program was
ever intended to be click-free; there are at least two things that are
causing them. I can fix one but maybe not the other.
Also, I expect that this would never actually happen in your app. Why
would you need to change the sample rate of the data you're playing?
Just pick a reasonable value and stick with it. In other words, I'd
pay attention to the code that sets up, starts, and stops the AU, and
how data is provided to it. The outer wrapper, which that does all of
that multiple times with different settings, is not really relevant.
Let's look at what's going on in TestDefaultAU(). This all happens
each time that main() runs a given configuration (bit depth, channel
count, and sample rate).
1) It sets up the AU's input format with the current sample rate,
encoding, etc.
2) and calls AudioUnitInitialize() on it
3) and calls AudioOutputUnitStart() on it.
Sometime after this, the AU will start calling MyRenderer(), *on a
separate thread*.
4) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, false) is called.
For your purposes, it sits and does nothing for two seconds. (It may
be servicing notifications from CoreAudio during those two seconds,
but you don't have to worry about that.)
5) Calls AudioOutputUnitStop()
The AU will stop calling MyRenderer() and stop playing sound. This
call will not return until things are completely stopped (that is,
after it returns, we're guaranteed that MyRenderer() is done and won't
be called again).
6) Calls AudioUnitUninitialize().
Moving or removing AudioUnitStop(), AudioUnitStart(),
AudioUnitUninitialize(), etc. had no effect.
In my tests, they actually are causing one of the clicks.
Try changing sampleRateList[] to have several instances of the same
rate in a row (e.g. 8000.0, 8000.0, 8000.0,...). You'll still get a
click between each one, even though the rate hasn't actually changed.
This is because stopping and restarting the AU is a heavyweight
operation.
Now comment out the calls to AudioOutputUnitStop,
AudioUnitUninitialize, and AudioUnitReset. Also, change the code that
calls AudioUnitInitialize and AudioOutputUnitStart, so it only calls
them once (the first time through). You'll get no clicks between
subsequent calls to TestDefaultAU() which use the same sample rate.
So, in other words: if you don't want clicks, just start the output AU
once. Don't expect to stop and start it without some discontinuity.
Now: We're still getting clicks when the sample rate is changed. I
don't know how to fix that one, if it is fixable. (Is it expected to
be able to change the AUs sample rate w/o a discontinuity?)
(Also, note that the way sSampleRate is set is not thread-safe. It is
changed before telling the AU about the new rate, so in the meantime,
on the audio thread, MyRender() could pick up the new value of
sSampleRate while the AU is still using the old rate. This can't
happen of course if you only change sSampleRate while the AU is
stopped.)
3) If my application is Cocoa based, can I run this Core Audio code
with CFRunLoopRun() on its own thread?
Yes, but it isn't necessary. You can start and stop the AU on the main
thread. There is no need to call CFRunLoopRun() yourself, since Cocoa
already does it for you. (Keep in mind that MyRenderer() will always
get called on another thread.)
Let's imagine you have two buttons, one to start the audio and one to
stop. Your code would look like this:
- (void) start: (id) sender
{
// set up the AU's input format with the current sample rate,
encoding, etc.
// call AudioUnitInitialize() on it
// call AudioOutputUnitStart() on it
// Then just return. Cocoa will run the run loop for you. No need to
call CFRunLoopRun() yourself.
}
- (void) stop: (id) sender
{
// Call AudioOutputUnitStop().
// Call AudioUnitUninitialize().
}
Does this help at all?
--
Kurt Revis
email@hidden
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Coreaudio-api mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden