Q: With Mac OS 9.0 and Sound Manager 4.0, my sound callbacks are no longer being called in a regular manner. This makes it very hard to synchronize sound to video. How can I get my sound callbacks to be called regularly?
A: The big change with Sound Manager 4.0 is
that your callbacks happen as soon as the sound has been given to the
DMA engine to give to the hardware, and that the DMA buffer has increased in size. This means that the callbacks are getting called a little sooner than they used to. This problem is exasperated by having a buffer that is smaller than the hardwares (or in this case the DMA buffers).
When you have a buffer of sound data to play that is smaller than the hardwares buffer, it will be copied completely into the
hardwares buffer. At that point the sound is considered to be
done, and the next command in the sound channels queue will be called. Typically the next command in the queue would be a
callBackCmd . If you were expecting that the sound be done when your callback was called, you are in for a problem because the sound is probably not done.
The solution to this problem is simple: increase the size of the buffers that you play. You want the buffers size to be at least the size of the hardwares, and for most situations, twice the hardwares buffer size is a good choice.
Currently, with Sound Manager 4.0 the buffer size without VM on is 512 samples, and with VM on is 4,096 samples.
However, you should not assume that you know these values as they may change in the future. You can query the hardware to get these
values using the following code:
/*
Returns the size of the output buffer in bytes.
*/
static long GetSoundOutputBufferSize (Component outputDevice,
short sampleSize, short numChannels,
UnsignedFixed sampleRate) {
SoundComponentData outputFormat;
OSErr err;
SndChannelPtr chan = nil;
SndCommand cmd;
ExtSoundHeader sndHeader;
long bufSize = 0;
err = SndNewChannel (&chan, 0, 0, nil);
sndHeader.samplePtr = nil;
sndHeader.numChannels = numChannels;
sndHeader.sampleRate = sampleRate;
sndHeader.loopStart = 0;
sndHeader.loopEnd = 0;
sndHeader.encode = extSH;
sndHeader.baseFrequency = kMiddleC;
sndHeader.numFrames = 0;
sndHeader.markerChunk = nil;
sndHeader.instrumentChunks = nil;
sndHeader.AESRecording = nil;
sndHeader.sampleSize = sampleSize;
sndHeader.futureUse1 = 0;
sndHeader.futureUse2 = 0;
sndHeader.futureUse3 = 0;
sndHeader.futureUse4 = 0;
sndHeader.sampleArea[0] = 0;
// This really isn't needed since the Sound Manager currently
// ignores this value.
UnsignedFixedTox80 (sampleRate, &sndHeader.AIFFSampleRate);
// Get the sound channel setup so we can query it.
cmd.cmd = soundCmd;
cmd.param2 = (long)&sndHeader;
err = SndDoCommand (chan, &cmd, true);
if (err == noErr) {
err = GetSoundOutputInfo (outputDevice, siHardwareFormat, &outputFormat);
}
#if DEBUG
if (err != noErr) {
printf ("Got error #%d trying to do GetSoundOutputInfo with
siHardwareFormat\n\n", err);
} else {
bufSize = outputFormat.sampleCount * (sampleSize / 8) * numChannels;
printf ("Sound output buffer is %d samples, ", outputFormat.sampleCount);
printf ("which is %d bytes.\n\n", bufSize);
}
#endif
return (bufSize);
}
|
If the GetSoundOutputInfo call fails, you can fall back to assuming that the size of the sound output buffer is the size of the sound input buffer, and you can get that information with this call:
|
/*
Returns the size of the input buffer in bytes.
*/
static long GetSoundInputBufferSize (UInt32 siRefNum) {
OSErr err;
long inputBufferSize;
err = SPBGetDeviceInfo (siRefNum, siDeviceBufferInfo, &inputBufferSize);
#if DEBUG
if (err != noErr) {
printf ("\nGet device buffer info error, err: %d\n", err);
} else {
printf ("\nSound input buffer is %d bytes\n", inputBufferSize);
}
#endif
return (inputBufferSize);
}
|
[Oct 05 1999]
|