转到后台并返回前台后无法继续从 AVAssetReaderOutput 读取

Posted

技术标签:

【中文标题】转到后台并返回前台后无法继续从 AVAssetReaderOutput 读取【英文标题】:Can't continue reading from AVAssetReaderOutput after going to background and back to foreground 【发布时间】:2013-03-21 17:14:52 【问题描述】:

我正在使用 AVAssetReaderOutputAVAsset 读取样本,对它们进行一些处理,然后使用 RemoteIO AU 播放结果。

问题是在调用AudioOutputUnitStop暂停播放后,再到后台再回到前台后,调用AudioOutputUnitStart后音频就不会再开始播放了。这是由于 AVAssetReaderOutputcopyNextSampleBuffer 方法返回的错误,该方法作为渲染管道的一部分调用。

调用copyNextSampleBufferAVAssetReaderstatus属性为AVAssetReaderStatusFailed,其error属性为Error Domain=AVFoundationErrorDomain Code=-11847 "Operation Interrupted" UserInfo=0x1d8b6100 NSLocalizedRecoverySuggestion=停止其他操作并重试。, NSLocalizedDescription=Operation Interrupted

我正在寻找一种解决方案,它不会迫使我在回到前台后重新初始化整个管道 - 希望有这样的解决方案,AVAssetReaders 可以在应用程序进入后台并返回时存活下来。 ..

注意事项

应用有权在后台播放音频。 我正在处理音频中断 - 在 AVAudiosessionDelegates endInterruptionWithFlags: 事件和应用程序激活时将我的 AVAudioSession 设置为活动的。无论我是否这样做都没有区别,都会出现同样的错误。

一些代码:

音频播放器

@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 渲染方法,最终由 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;
     
 ...

【问题讨论】:

这是压缩音频类型吗?据报道,某些设备型号上的某些硬件音频解压单元必须重新启动才能中断。 有趣。它用于从用户的 iTunes 库中读取从 MPMediaItem 检索到的 AVAsset,一首歌曲。所以mp3/m4a,我相信两者都是压缩的。关于“设备型号” - 这是 iPhone 5 上的一个问题。您所描述的内容是否有任何可用参考? 【参考方案1】:

图表和 AVReader 基础没有连接/链接。混乱可能来自如果 iOS 看到音频图正在运行,它不会“后台”(休眠)进程(因为这样就无法生成音频数据)。这就是为什么当您停止音频图时,2-3 分钟后,iOS 将后台处理您的进程(从 iOS 9 开始)。据推测,iOS 会查看您的进程中发生的情况并决定何时应该强制您的进程进入后台(通过beginBackgroundTaskWithName:expirationHandler)。

Apple 开发人员出于某种原因决定让 AVReader 在进入后台模式时停止(我的最佳猜测是 QA)。好消息是,当您的进程退出后台模式时,您可以检测到它并恢复,但您需要再次重新启动阅读器和 readerOutput。首先,当 AVAssetReaderOutput 的 copyNextSampleBuffer 返回 NULL 时,检查 AVAssetReader.error.code 是否为 AVErrorOperationInterrupted

我们在这里实现无缝恢复的方式是,当我们到达那个点时,我们首先计算我们停止的确切时间(很容易做到 - 只需维护一个已输出样本总数的计数器)。然后,您将需要重新启动 AVAssetReaderAVAssetReaderOutput 流程。但这没有问题,因为您的良好开发实践会将其封装在一个以寻道时间作为参数的函数中。在该函数中,使用AVAssetReader.timeRange 查找您需要恢复的时间并提前!

【讨论】:

【参考方案2】:

我会尝试的第一件事是:

没有打电话给AudioOutputUnitStop?你为什么不让图表运行,当音频暂停时根本不调用

[self.readerOutput copyNextSampleBuffer]

而只是用 0 填充缓冲区和/或对缓冲区使用 kAudioUnitRenderAction_OutputIsSilence 的渲染操作。启动和停止图表需要时间并导致图表无响应。当我的应用程序运行时,我从不停止图表。 (除非发生一些严重的中断)这可能会让资产阅读者感到高兴。

但我有一种预感,因为 AVAssetReader 没有直接连接到音频图(您只是碰巧请求样本并将它们放在渲染回调提供的缓冲区中)问题不在于音频图完全停止并重新启动。因此,我将尝试的下一条攻击线是从 AVAsset 请求样本,将应用程序置于后台并将其带回前台根本不使用 AUGraph。这将确定问题是否与AUGraph 或应用程序进入后台状态有关。

如果发生某些事情迫使您拆除 AVAssetReader,您将不得不重新连接到资产。因此,当您必须编写另一个代码时,我会考虑的选项是您不想做的那个。重新连接到AVAssetReader,寻找您离开的位置并继续前进。

【讨论】:

实际上我现在在暂停时所做的正是您所写的,用 0 填充缓冲区,这样当转到 bg 并返回 fg 并再次开始播放时,播放继续正常。这种方法的问题是“播放”图标保留在状态栏中。 那么您是说 AVAssetReader 链接到 AUGraph 了吗? 不确定链接是什么意思?我在 AU 的播放回调中读取了它。 我觉得奇怪的是,如果您停止 AUGraph,AVAssetReader 将不允许您在重新启动图形时读取样本。您设置它的方式意味着两者应该是独立的。当您保持图表运行时,即使您不请求样本,AVAssetReader 也会保持活动状态,但如果您停止图表,它就会死亡。 AVAssetReader 的类是否有可能被 ARC 垃圾收集? 机会渺茫,但如果您将 ioActionFlags 设置为 kAudioUnitRenderAction_OutputIsSilence,您还能获得播放指示吗?

以上是关于转到后台并返回前台后无法继续从 AVAssetReaderOutput 读取的主要内容,如果未能解决你的问题,请参考以下文章

当应用程序从后台返回到前台时,MCSession 无法重新连接

用异提交时,后台通过校验规则文件,校验不通过时,跳转到INPUT视图时,前台显示错误信息的解决办法

从后台返回后转到 iphone 应用程序的主屏幕

java之后台返回json格式字符串,前台接受并转为json文件

解决js中post提交数据并且跳转到指定页面的问题总结

大文件上传前台分片后后台合并的问题