I'm using an AVAssetReaderOutput
to read samples from an AVAsset
, do some processing on them, and play the result using a RemoteIO AU.
The problem is that after calling AudioOutputUnitStop
to pause the playback, then after going to the background and back to the foreground the audio won't start again after calling AudioOutputUnitStart
. This is due to an error returned from the copyNextSampleBuffer
method of AVAssetReaderOutput
, which is called as part of the rendering pipeline.
The status
property of AVAssetReader
after calling copyNextSampleBuffer
is AVAssetReaderStatusFailed
, and its error
property is Error Domain=AVFoundationErrorDomain Code=-11847 "Operation Interrupted" UserInfo=0x1d8b6100 {NSLocalizedRecoverySuggestion=Stop other operations and try again., NSLocalizedDescription=Operation Interrupted}
I'm looking for a solution which won't force me to reinitialize the entire pipeline after coming back to the foreground - Hoping there is such a solution, that AVAssetReader
s can survive the app going to background and back...
Notes
- The app is entitled to play audio in the background.
- I'm handling audio interruptions - Setting my
AVAudioSession
as the active one both onAVAudioSessionDelegate
sendInterruptionWithFlags:
event and whenever the app becomes active. Doesn't make a difference whether I do this or not, getting the same error.
Some code:
AudioPlayer
@implementation AudioPlayer
...
// Audio Unit Setup
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &desc);
AudioComponentInstanceNew(defaultOutput, &_audioUnit);
AudioStreamBasicDescription audioFormat;
FillOutASBDForLPCM(audioFormat, 44100, 2, 16, 16, false, false);
AudioUnitSetProperty(self.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, sizeof(audioFormat));
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = RenderCallback;
callbackStruct.inputProcRefCon = (__bridge void*)self;
AudioUnitSetProperty(self.audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, kOutputBus, &callbackStruct, sizeof(callbackStruct));
AudioUnitInitialize(self.audioUnit);
AudioReader Setup
@implementation AudioReader
...
NSError* error = nil;
self.reader = [AVAssetReader assetReaderWithAsset:self.asset error:&error];
NSDictionary *outputSettings = ...
self.readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[self.asset.tracks objectAtIndex:0] outputSettings:outputSettings];
[self.reader addOutput:self.readerOutput];
[self.reader startReading];
AudioReader Render Method, called eventually by the RenderCallback function
-(BOOL)readChunkIntoBuffer
{
CMSampleBufferRef sampleBuffer = [self.readerOutput copyNextSampleBuffer];
if ( sampleBuffer == NULL )
{
NSLog(@"Couldn't copy next sample buffer, reader status=%d error=%@, self.reader.status, self.reader.error);
return NO;
}
...
}