13

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

问题是在调用AudioOutputUnitStop暂停播放后,然后在进入后台并返回前台后,调用后音频不会再次开始AudioOutputUnitStartcopyNextSampleBuffer这是由于 的方法返回的错误,该方法AVAssetReaderOutput被称为渲染管道的一部分。

调用后的status属性为,其属性为Error Domain=AVFoundationErrorDomain Code=-11847 "Operation Interrupted" UserInfo=0x1d8b6100 {NSLocalizedRecoverySuggestion=停止其他操作并重试,NSLocalizedDescription=Operation Interrupted}AVAssetReadercopyNextSampleBufferAVAssetReaderStatusFailederror

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

笔记

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

一些代码:

音频播放器

@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);

音频阅读器设置

@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;
     }
 ...
}
4

2 回答 2

4

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

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

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

于 2016-04-22T21:36:54.950 回答
3

我要尝试的第一件事是:

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

[self.readerOutput copyNextSampleBuffer]

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

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

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

于 2013-04-09T04:59:45.383 回答