Не удается продолжить чтение из AVAssetReaderOutput после перехода в фоновый режим и обратно на передний план
Я использую AVAssetReaderOutput
читать образцы из AVAsset
выполните некоторую обработку и воспроизведите результат, используя RemoteIO AU.
Проблема в том, что после звонка AudioOutputUnitStop
чтобы приостановить воспроизведение, затем после перехода на задний план и обратно на передний план звук не запустится снова после вызова AudioOutputUnitStart
, Это связано с ошибкой, возвращенной из copyNextSampleBuffer
метод AVAssetReaderOutput
, который вызывается как часть конвейера рендеринга.
status
собственностью AVAssetReader
после звонка copyNextSampleBuffer
является AVAssetReaderStatusFailed
, И его error
свойство Error Domain=AVFoundationErrorDomain Code=-11847 "Операция прервана" UserInfo=0x1d8b6100 {NSLocalizedRecoverySuggestion= Остановить другие операции и повторить попытку., NSLocalizedDescription= Операция прервана}
Я ищу решение, которое не заставит меня заново инициализировать весь конвейер после возвращения на передний план - Надеюсь, есть такое решение, что AVAssetReader
s может выжить, приложение переходит в фоновый режим и обратно...
Заметки
- Приложение имеет право воспроизводить аудио в фоновом режиме.
- Я работаю с прерываниями звука
AVAudioSession
как активный как наAVAudioSessionDelegate
sendInterruptionWithFlags:
событие и всякий раз, когда приложение становится активным. Не имеет значения, делаю ли я это или нет, получая ту же ошибку.
Некоторый код:
Аудиоплеер
@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
@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 Метод, который в конечном итоге вызывается функцией RenderCallback
-(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;
}
...
}
2 ответа
Основа графика и AVReader не связаны / не связаны вообще. Возможно, путаница возникла из-за того, что iOS не будет "фоновым" (спящим) процессом, если увидит, что звуковой график работает (потому что тогда аудиоданные не смогут быть сгенерированы). Вот почему, когда вы останавливаете звуковой график, через 2-3 минуты, iOS будет фоном вашего процесса (начиная с iOS 9). Предположительно, iOS смотрит на то, что происходит в вашем процессе, и решает, когда ваш процесс должен быть переведен в фоновый режим (через beginBackgroundTaskWithName:expirationHandler
).
Разработчики Apple решили по какой-то причине остановить AVReader при переводе в фоновый режим (мое лучшее предположение - QA). Хорошей новостью является то, что вы можете обнаружить его и восстановить, когда ваш процесс выйдет из фонового режима, но вам потребуется снова перезапустить ридер и readerOutput. Во-первых, когда AVAssetReaderOutput's copyNextSampleBuffer
возвращает NULL, проверьте AVAssetReader.error.code
за AVErrorOperationInterrupted
,
Способ, которым мы осуществляем восстановление без пропусков, заключается в том, что, когда мы добираемся до этой точки, мы сначала рассчитываем точное время, в которое мы остановились (это легко сделать - просто ведите счетчик всех сэмплов, уже выведенных). Затем вам нужно будет перезагрузить AVAssetReader
а также AVAssetReaderOutput
течь. Но это не проблема, так как ваши хорошие практики разработки заключили бы это в одну функцию, у которой время поиска является аргументом. В этой функции используйте AVAssetReader.timeRange
чтобы стремиться к тому времени, нужно возобновить и престо!
Там первое, что я хотел бы попробовать это:
Не звонит AudioOutputUnitStop
? Почему бы вам не оставить график работающим, а когда звук приостановлен, просто не звоните
[self.readerOutput copyNextSampleBuffer]
Вместо этого просто заполните буфер нулями и / или используйте действие рендеринга kAudioUnitRenderAction_OutputIsSilence для буферов. Запуск и остановка графа требует времени и приводит к не отвечающему графику. Я никогда не останавливаю график, пока мои приложения работают. (Если не произойдет серьезное прерывание) Это может держать читателя активным и счастливым.
Но у меня есть предчувствие, что как AVAssetReader
не подключен напрямую к звуковому графу (вы просто запрашиваете сэмплы и помещаете их в буфер, предоставленный обратным вызовом рендеринга), проблема не в том, что звуковой граф останавливается и перезапускается вообще. Поэтому следующая линия атаки, которую я бы попробовал, - запросить сэмплы у AVAsset, перевести приложение в фоновый режим и вернуть его на передний план , вообще не используя AUGraph. Это определит, связана ли проблема с AUGraph
или приложение переходит в фоновое состояние.
Если что-то происходит, что заставляет вас снести AVAssetReader
вам придется повторно подключиться к активу. Поэтому, поскольку вы должны написать этот код другой, я бы не выбрал вариант, на который я бы хотел обратить внимание. Переподключиться к AVAssetReader
ищите позицию, в которой вы остановились, и продолжайте движение.