AVAudioEngine how to start engine without NSAssert

In Apple's AVAudioMixerSample project, the following code is used to start the AVAudioEngine object:

-(void) startEngine {
if (!_engine.isRunning) {
  NSError *error;
  NSAssert([_engine startAndReturnError:&error], @"couldn't start engine, %@", [error localizedDescription]);
  }
}


The above code will NOT work in a Release Build with Enable Foundation Assertions set to NO.

NSAssert is normally used in Debug Builds..


The code below, without NSAssert does NOT function properly in a Release Build with Enable Foundation Assertions set to NO:


  NSError *error = nil;
  BOOL engineDidStart = [_engine startAndReturnError:&error];
  if (!engineDidStart) {
  NSLog(@"AVAudioEngine failed to start");
  }


How do you properly START an AVAudioEngine object WITHOUT using NSAssert?

I was easily able to duplicate what you're talking about here - can you please file a bug and post the number to this thread so we can investigate what's going on. <http://bugreport.apple.com>.


AVAudioEngine makes extensive use of assertions which may be at the root of the issue. But always best to get confirmation from the engineers who wrote the framework.

This is likely directly related to <https://forums.developer.apple.com/message/84040>

Thank you very much for the response!


I created a class for handling audio in my tvOS game app, using AVAudioEngine. Due to the behavior, that you have verified, I will have to scrap using AVAudioEngine for this project. Instead, I will have to use Karl Stenerud's ObjectAL (OpenAL based) classes..


I am the one who started the other thread you linked to. Profiling the AVAudioMixerSample project, in Instruments, does not work, because of the same reason. The bug that prevents AVAudioEngine to function properly with Asserts disabled, also causes problems with Profiling, if Asserts are disabled.


I see your screen name is "analogkid". I suggest Apple's audio engineers look into how arcade game audio engineers, over three decades ago, developed discrete analog audio boards that were capable of stopping, low frequeny, looped audio, cleanly without ANY annoying "Popping" sounds. An example of a low frequency sound, that can induce Popping when terminated from a loop, would be the player ship THRUST sound effect from Atari's Asteroids arcade machine. Cocos Denshion, the audio framework from older versions of Cocos2d, could achieve this Pop Free loop termination feat, up until the release of iOS 9, which BROKE it.. I have a game on the iOS App Store that was stopping low frequency, loops, Pop Free for 3 years (under iOS 6, 7 & 8).. Now under iOS 9, the same app is Pop City!


When AVAudioEngine terminates a low frequency looped WAV file, it POPS loudly. If you set the AVAudioPlayerNode's volume property = 0.0, while it is looping, the sound terminates WITHOUT a POP (the desired results). This pop free, functionality, of loop termination should be something that is automatically implemented when calling [AVAudioPlayerNode stop]. It should NOT be necessary to keep an AVAudioPlayerNode looping continuously, and toggle the volume property from say 1.0 (or whatever normal playback volume is desired), to 0.0, to terminate the loop playback without hearing an audible POP every time you want to stop the loop, after starting it back looping again.


Desired functionality should be:


-(void) playLoop1PlayerNode {
     [self startEngine];
     [_loop1Player scheduleBuffer:_loop1Buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
     [AVAudioPlayerNode play]
}

-(void) stopLoop1PlayerNode {
     [AVAudioPlayerNode stop] // NO POP HEARD, even when stopping low frequency audio sample playback.
}

I filed Bug Report # 23404149 on this issue.

I figured out what the problem was with the Asserts in AVAudioMixerSample.


The Apple AVAudioMixerSample project and OTHER Apple AVAudioEngine related sample projects (this definitely applies also to the project with the bouncing balls inside the box, with the 3D spatial sounds) need a code revision!


Whoever wrote this code, utilized NSAssert in a way that prevents these projects from compiling, or running if Asserts are turned OFF in Build Settings. This is not a proper way to utilize NSAssert. NSAssert statements should only run if Asserts are turned ON in Build Settings, they should NOT prevent program functionality if they are turned OFF in Build Settings (which is the standard setting for Release Builds). The Apple written code makes Asserts mandatory for the app to function at all..


Below are two examples of the problematic usage of NSAssert in AVAudioMixerSample. Each example shows modifications that eliminate critical code from being embedded into NSAssert statements. Every NSAssert statement in the project needs similar revision as shown below.


Apple's code that only functions with NSAssert enabled:

NSError *error;
NSAssert([_engine startAndReturnError:&error], @"couldn't start engine, %@", [error localizedDescription]);


Correct usage of NSAssert to start the engine, even if Asserts are turned OFF:

NSError *error;
BOOL success;
success = [_engine startAndReturnError:&error];
NSAssert(success, @"couldn't start engine, %@", [error localizedDescription]);


Apple's code that only functions with NSAssert enabled:

NSError *error;

// load marimba loop
NSURL *marimbaLoopURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"marimbaLoop" ofType:@"caf"]];
AVAudioFile *marimbaLoopFile = [[AVAudioFile alloc] initForReading:marimbaLoopURL error:&error];
_marimbaLoopBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[marimbaLoopFile processingFormat] frameCapacity:(AVAudioFrameCount)[marimbaLoopFile length]];
NSAssert([marimbaLoopFile readIntoBuffer:_marimbaLoopBuffer error:&error], @"couldn't read marimbaLoopFile into buffer, %@", [error localizedDescription]);

Correct usage of NSAssert to read AVAudioFile into the AVAudioPCMBuffer, even if Asserts are turned OFF:

NSError *error;
BOOL success;

// load marimba loop
NSURL *marimbaLoopURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"marimbaLoop" ofType:@"caf"]];
AVAudioFile *marimbaLoopFile = [[AVAudioFile alloc] initForReading:marimbaLoopURL error:&error];
_marimbaLoopBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[marimbaLoopFile processingFormat] frameCapacity:(AVAudioFrameCount)[marimbaLoopFile length]];
success = [marimbaLoopFile readIntoBuffer:_marimbaLoopBuffer error:&error];
NSAssert(success, @"couldn't read marimbaLoopFile into buffer, %@", [error localizedDescription]);


NOTE: My previous comments regarding stopping an AVAudioPlayerNode cleanly from a loop still stand! I still consider this to be a bug in AVAudioEngine..


The remaining desired functionality is:

-(void) playLoop1PlayerNode { 
     [self startEngine];
     [_loop1Player scheduleBuffer:_loop1Buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
     [AVAudioPlayerNode play];
}

-(void) stopLoop1PlayerNode {
     [AVAudioPlayerNode stop]; // NO POP HEARD, even when stopping low frequency audio sample playback.
}


Toggling the AVAudioPlayerNode's volume property from 0.0 to desired value does NOT produce a Pop. Calling stop on the AVAudioPlayerNode, should NOT produce a Pop either (Doing this currently does produce a Pop if the looping sample, in the buffer, is low frequency).

Hi Steve,


We're actually in the process of updating the AVAE samples adding new functionality, fixing bugs etc. so it's a good time catching this now so we can update everything at once as the NSAssert's are removing critical lines of code from being compiled in with the release build which is of course "bad".


As for the popping...engineering informed me that this is a known issue where fades are not applied between buffers that get interrupted or stopped resulting in the popping very apparent with low frequency audio. Your work around is fine but annoying to have to deal with - we're hoping to address the issue in a future release (but as you know it's impossible for us to be more specific regarding when a fix will be available).

AVAudioEngine how to start engine without NSAssert
 
 
Q