I am working with an app that needs to play a sound “tap” at 60 ms intervals (making for 1000 taps per minute). When I get past about 120 ms intervals (500 taps per minute), the sounds become very erratic and seem to be piling up on one another.
The sound byte that I’m using is 10 ms long, and I have a thread timer that is running at 20 ms intervals. I am using AVAudioPlayer to play the sound, and have tried formats of wav, caf and m4a. I have tested with NSLog outputs to see when the AVAudioPlayer is being fired off, and when the timer loop is polling…and all the times seem accurate enough.
I have added Audio Session handling in my viewDidLoad() method as follows:
[[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryPlayback
error: &setCategoryError];
Here is my timer loop that is running in it’s own thread:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Give the sound thread high priority to keep the timing steady.
[NSThread setThreadPriority:1.0];
while (running) { // loop until stopped.
[self performSelectorOnMainThread:@selector(playOutput) withObject:nil waitUntilDone:NO];
// An autorelease pool to prevent the build-up of temporary objects.
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
NSDate *curtainTime = [[NSDate alloc] initWithTimeIntervalSinceNow:sleepValue];
NSDate *currentTime = [[NSDate alloc] init];
// Wake up periodically to see if we've been cancelled.
while (running && ([currentTime compare:curtainTime] == NSOrderedAscending)) {
[NSThread sleepForTimeInterval:0.015];
[currentTime release];
currentTime = [[NSDate alloc] init];
}
[curtainTime release];
[currentTime release];
[loopPool drain];
}
[pool drain];
Here is how I am initializing my AVAudioPlayer:
NSString *beatOne = @"3h";
NSString *beatOneSoundFile = [[NSBundle mainBundle]
pathForResource:beatOne ofType:@"caf"];
NSURL *beatOneURL = [[NSURL alloc] initFileURLWithPath:beatOneSoundFile];
beatPlayerOne = [[AVAudioPlayer alloc]initWithContentsOfURL:beatOneURL error:nil];
beatPlayerOne.delegate = self;
beatPlayerOne.currentTime = 0;
[beatPlayerOne prepareToPlay];
[beatOneURL release];
And here is where the audio player is played:
[beatPlayerOne pause];
beatPlayerOne.currentTime = 0;
[beatPlayerOne play];
Thanks in advance for any help…
As Dan Ray said,
NSTimerandNSThreadare not reliable for precise timing. If I were you, I would use an audio queue – it will request buffers of audio through an input callback, and you will fill them. Provided you respond promptly, it will request and reproduce these buffers at exactly the hardware sample rate, so you can do some simple calculations to figure out the timing of your taps and fill each buffer from your file or from samples stored in an array or vector. You’d fill it usingAudioFileReadPacketsif you choose to go straight from your file.This will be a little more work but it’s much more versatile and powerful. You can use audio queues for live synthesis and processing of audio, so their timing is perfectly precise as long as you meet their deadlines. If you’re careful about processor usage, you could have a tap sound every few milliseconds with no issue.
Take a look at the SpeakHere sample application to get started with audio queues. Audio units are also useful for this sort of thing, and you can provide data to them through a callback, but they take a little more setup and have a steeper learning curve.
Good luck!