I am using multiple instances of AVAudioPlayer to play multiple sound files in a succession.
I am noticing that there is a roughly one second pause when two different sound files are played. Is it possible to eliminate this one second pause? I call [myPlayer prepareToPlay] for each player ahead of time already.
According to AVAudioPlayer documentation you could use playAtTime: for synchronized playing.
Modified example would look like this:
- (void) startSynchronizedPlayback {
NSTimeInterval shortStartDelay = 0.01; // seconds
NSTimeInterval now = player.deviceCurrentTime;
[player playAtTime: now + shortStartDelay];
[secondPlayer playAtTime: now + shortStartDelay + player.duration];
}
I didn't test this but guess it should work. You should of course rename player and secondPlayer according to you names.
Related
This is the code I am using to play a sound with AVAudioPlayerNode. It is just playing the last sound of the beats array. On iPhone, all sounds from the beats array are playing simultaneously with the same function.
-(void)playMix{
for (int i = 0; i< mix.beatsArray.count;i++) {
beat = [[WatchBeatObject alloc] initWithFileName:mix.beatsArray[i].fileName];
[beat.audioPlayerNode play];
[beat.audioPlayerNode scheduleBuffer:beat.buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:^{
}];
}
}
N.B: Method initWithFileName: handles initializing and creating AVAudioPlayerNode and everything needed.
Thank you in advance.
Does that WatchBeatObject has any category option in AVAudioSession to set AVAudioSessionCategoryAmbient? If yes, set it, they would mix different sound.
I'm trying to implement an Audio Player to play multiple files as if they were a single one. However, the initial buffer time should be only the first part's duration, and the other files should be loaded in sequence.
For example:
File1:
Part1 - 0:35s
Part2 - 0:47s
Part3 - 0:07s
The File1 should be played as if it had 1:29, but we'd only wait (at most) until Part1 is loaded to start playing.
I've had a look at AVAsset, but it doesn't seem to solve the problem. I also thought of implementing it using AVAudioPlayer and doing all the logic myself.
Has anyone had this issue before?
Thanks!
You can use AVQueuePlayer for achieving this. You need to create AVPlayerItem using your files url and then need to initialize the AVQueuePlayer using it's queuePlayerWithItems: method.
AVPlayerItem *firstItem = [[AVPlayerItem alloc] initWithURL:firstPartURL];
AVPlayerItem *secondItem = [[AVPlayerItem alloc] initWithURL:secondPartURL];
AVPlayerItem *thirdItem = [[AVPlayerItem alloc] initWithURL:thirdPartURL];
NSArray *itemsToPlay = [NSArray arrayWithObjects:firstItem, secondItem, thirdItem, nil];
AVQueuePlayer *qPlayer = [AVQueuePlayer queuePlayerWithItems:itemsToPlay];
[qPlayer play];
When I change the currentTime of AVAudioPlayer after pausing the player, it gives a lag (some time in positive and some time in negative).
[self.bookAudioPlayer pause];
[self.bookAudioPlayer setCurrentTime:[currentPage.audioStartTime doubleValue]];
[self.bookAudioPlayer prepareToPlay];
When I print the currentTime and audioStartTime it prints values with slight difference. For example,
audioStartTime : 2.203665, currentTime : 2.194286
audioStartTime : 137.521347, currentTime : 137.508571
I have tried to fix it using the following code but results stay the same.
- (void)fixSetCurentTime:(NSTimeInterval)newTime {
self.currentTime = newTime;
if (self.currentTime != newTime) {
[self prepareToPlay];
self.currentTime = newTime;
}
}
Has anyone experienced this issue? Any pointers for a possible fix?
Note that the MP3 audio frames are about 26 milliseconds long. So, block-based audio compression formats are only (re)startable on time-quantized block boundaries... And the nearest block start might be earlier (or later) in time.
Credit: Apple Developer Forum User (Reference)
I'm trying to make a simple Drumpad app. The app needs to be super fast and play the sound with as little latency as possible. The premise is store audio samples in an array and each one is played when you hit a pad.
The catch is that some pads are chords (and not drums) so they need to stop right away On Touch Up.
Here are the approaches I have tried:
System Sound - Very simple to implement, super responsive, no way to stop sounds without destroying them, sounds cant be more than 30 seconds.
AV Framework - Way to slow.
The Amazing Audio Engine - Seems nice, but not sure what the advantage is over CoreAudio as setup was rather complicated I wasn't able to get a sound to play. Also not sure about latency.
Novocaine - I settled on this for now, it seems very fast, and I can play sounds, but I've seen no way to stop them once they start. I don't know how I can stop a single sound without stopping the whole audioManager?
- (void) playSoundN:(int)padNum
{
__weak ViewController * wself = self;
NSURL *inputFileURL = [[NSBundle mainBundle] URLForResource:#"Drum - Kick" withExtension:#"wav"];
self.fileReader = [[AudioFileReader alloc]
initWithAudioFileURL:inputFileURL
samplingRate:self.audioManager.samplingRate
numChannels:self.audioManager.numOutputChannels];
[self.fileReader play];
self.fileReader.currentTime = 0.0;
[self.audioManager setOutputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels)
{
[wself.fileReader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels];
NSLog(#"Time: %f", wself.fileReader.currentTime);
}];
[self.audioManager play];
}
Seems like a simple task, starting and stoping a sound with super low latency.
Should I use one of the above approaches or just bite the bullet and dive into Core Audio Units.
Anyone have any ideas?
I just made an app that has similar functionality to what you are trying to accomplish. My recommendation:
Use AVAudioPlayer (and AVAudioRecorder for recording).
Use the prepareToPlay method of the class to preload the sound and reduce lag.
You can read more about the classes in the AVFoundation documentation:
https://developer.apple.com/library/ios/DOCUMENTATION/AVFoundation/Reference/AVFoundationFramework/_index.html
Surprisedly, alexbw accepted the patch quickly. If you updated to newest code, you may simply use following lines in output block:
[weakSelf.reader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels];
if (!weakSelf.reader.playing) {
weakSelf.audioManager.outputBlock = nil;
dispatch_async(dispatch_get_main_queue(), ^{
// UI stuff
});
}
Novocaine cannot tell EOF of the file, the last buffer is used repeat.
Considering the author think AudioFileReader is just a piece of sugar and not likely to fix it, here is a dirty solution.
__block float lastTimeLeft = 0.0f;
[self.audioManager setOutputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels) {
float timeLeft = weakSelf.reader.duration - [weakSelf.reader getCurrentTime];
if ( timeLeft > 0.01 && timeLeft != lastTimeLeft ) {
lastTimeLeft = timeLeft;
[weakSelf.reader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels];
} else {
[weakSelf.reader retrieveFreshAudio:data numFrames:numFrames numChannels:numChannels]; //forget this line will cause a buzz at the end.
[weakSelf.reader pause];
weakSelf.audioManager.outputBlock = nil;
}
}];
A better solution would be to patch the AudioFileReader.
I'd like to crossfade from one track to the next in a Spotify enabled app. Both tracks are Spotify tracks, and since only one data stream at a time can come from Spotify, I suspect I need to buffer (I think I can read ahead 1.5 x playback speed) the last few seconds of the first track, start the stream for track two, fade out one and fade in two using an AudioUnit.
I've reviewed sample apps:
Viva - https://github.com/iKenndac/Viva SimplePlayer with EQ - https://github.com/iKenndac/SimplePlayer-with-EQ and tried to get my mind around the SPCircularBuffer, but I still need help. Could someone point me to another example or help bullet-point a track crossfade game plan?
Update: Thanks to iKenndac, I'm about 95% there. I'll post what I have so far:
in SPPlaybackManager.m: initWithPlaybackSession:(SPSession *)aSession {
added:
self.audioController2 = [[SPCoreAudioController alloc] init];
self.audioController2.delegate = self;
and in
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
...
self.audioController.audioOutputEnabled = self.playbackSession.isPlaying;
// for crossfade, add
self.audioController2.audioOutputEnabled = self.playbackSession.isPlaying;
and added a new method based on playTrack
-(void)crossfadeTrack:(SPTrack *)aTrack callback:(SPErrorableOperationCallback)block {
// switch audiocontroller from current to other
if (self.playbackSession.audioDeliveryDelegate == self.audioController)
{
self.playbackSession.audioDeliveryDelegate = self.audioController2;
self.audioController2.delegate = self;
self.audioController.delegate = nil;
}
else
{
self.playbackSession.audioDeliveryDelegate = self.audioController;
self.audioController.delegate = self;
self.audioController2.delegate = nil;
}
if (aTrack.availability != SP_TRACK_AVAILABILITY_AVAILABLE) {
if (block) block([NSError spotifyErrorWithCode:SP_ERROR_TRACK_NOT_PLAYABLE]);
self.currentTrack = nil;
}
self.currentTrack = aTrack;
self.trackPosition = 0.0;
[self.playbackSession playTrack:self.currentTrack callback:^(NSError *error) {
if (!error)
self.playbackSession.playing = YES;
else
self.currentTrack = nil;
if (block) {
block(error);
}
}];
}
this starts a timer for crossfade
crossfadeTimer = [NSTimer scheduledTimerWithTimeInterval: 0.5
target: self
selector: #selector ( crossfadeCountdown)
userInfo: nil
repeats: YES];
And in order to keep the first track playing after its data has loaded in SPCoreAudioController.m I changed target buffer length:
static NSTimeInterval const kTargetBufferLength = 20;
and in SPSession.m : end_of_track(sp_session *session) {
I removed
// sess.playing = NO;
I call preloadTrackForPlayback: about 15 seconds before end of track, then crossfadeTrack: at 10 seconds before.
Then set crossfadeCountdownTime = [how many seconds you want the crossfade]*2;
I fade volume over the crosssfade with:
- (void) crossfadeCountdown
{
[UIAppDelegate.playbackSPManager setVolume:(1- (((float)crossfadeCountdownTime/ (thisCrossfadeSeconds*2.0)) *0.2) )];
crossfadeCountdownTime -= 0.5;
if (crossfadeCountdownTime == 1.0)
{
NSLog(#"Crossfade countdown done");
crossfadeCountdownTime = 0;
[crossfadeTimer invalidate];
crossfadeTimer = nil;
[UIAppDelegate.playbackSPManager setVolume:1.0];
}
}
I'll keep working on it, and update if I can make it better. Thanks again to iKenndac for his always spot-on help!
There isn't a pre-written crossfade example that I'm aware of that uses CocoaLibSpotify. However, a (perhaps not ideal) game plan would be:
Make two separate audio queues. SPCoreAudioController is an encapsulation of an audio queue, so you should just be able to instantiate two of them.
Play music as normal to one queue. When you're approaching the end of the track, call SPSession's preloadTrackForPlayback:callback: method with the next track to get it ready.
When all audio data for the playing track has been delivered, SPSession will fire the audio delegate method sessionDidEndPlayback:. This means that all audio data has been delivered. However, since CocoaLibSpotify buffers the audio from libspotify, there's still some time before audio stops.
At this point, start playing the new track but divert the audio data to the second audio queue. Start ramping down the volume of the first queue while ramping up the volume of the next one. This should give a pleasing crossfade.
A few pointers:
In SPCoreAudioController.m, you'll find the following line, which defines how much audio CocoaLibSpotify buffers, in seconds. If you want a bigger crossfade, you'll need to increase it.
static NSTimeInterval const kTargetBufferLength = 0.5;
Since you get audio data at a maximum of 1.5x actual playback speed, be careful not to do, for example, a 5 second crossfade when the user has just skipped near to the end of the track. You might not have enough audio data available to pull it off.
Take a good look at SPPlaybackManager.m. This class is the interface between CocoaLibSpotify and Core Audio. It's not too complicated, and understanding it will get you a long way. SPCoreAudioController and SPCircularBuffer are pretty much implementation details of getting the audio into Core Audio, and you shouldn't need to understand their implementations to achieve what you want.
Also, make sure you understand the various delegates SPSession has. The audio delivery delegate only has one job - to receive audio data. The playback delegate gets all other playback events - when audio has finished being delivered to the audio delivery delegate, etc. There's nothing stopping one class being both, but in the current implementation, SPPlaybackManager is the playback delegate, which creates an instance of SPCoreAudioController to be the audio delivery delegate. If you modify SPPlaybackManager to have two Core Audio controllers and alternate which one is the audio delivery delegate, you should be golden.