在swift3.2 / 4中正确释放MTAudioProcessingTapRef

问题描述 投票:1回答:2

我一直在我的项目中使用MTAudioProcessingTapRef,以便在播放流式音频时实时分析缓冲区数据。问题是,当我需要它时,我无法让tap处理器正确解除分配。

我有一个AudioViewController swift类,它引用了我的AudioTapProcessor目标-C类,swift类负责告诉处理器启动和停止AVPlayerItem的处理。处理器还有一个委托(在这种情况下是视图控制器),用于在处理时通知缓冲区更改。

我的问题是如果我声明处理器委托为弱(应该是),处理器将随机崩溃尝试通知已经解除分配的委托,因为在停止处理调用之后,点击处理器的处理方法被执行了几次。我发现修复此问题的唯一方法是将tap处理器委托声明为强属性,这显然会导致保留周期,并且我的AudioViewControllers永远不会被释放。

下面是一些可以帮助您了解相关情况的代码:

AudioTapProcessor.h

@interface AudioTapProcessor : NSObject

@property (nonatomic, strong) AVPlayerItem *item;
@property (nonatomic, strong) id<AudioProcessorDelegate> delegate;

- (instancetype)initWithDelegate:(id<AudioProcessorDelegate>)delegate 
    item:(AVPlayerItem *)item;
- (void)startProcessing;
- (void)stopProcessing;

@end

AudioTapProcessor.m

void init(MTAudioProcessingTapRef tap, void *clientInfo, void 
**tapStorageOut) {
    *tapStorageOut = clientInfo;
}

void finalize(MTAudioProcessingTapRef tap) {}

void prepare(
         MTAudioProcessingTapRef tap,
         CMItemCount maxFrames,
         const AudioStreamBasicDescription *processingFormat
         ) {}

void unprepare(MTAudioProcessingTapRef tap) {}

void process(
         MTAudioProcessingTapRef tap,
         CMItemCount numberFrames,
         MTAudioProcessingTapFlags flags,
         AudioBufferList *bufferListInOut,
         CMItemCount *numberFramesOut,
         MTAudioProcessingTapFlags *flagsOut
         ) {
 //Random crashes here if I declare the delegate weak
 //Something like AUDeferredRenderer-0x7ff8f448ef (364): EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
  AudioTapProcessor *processor = (__bridge AudioTapProcessor *)MTAudioProcessingTapGetStorage(tap);

  OSStatus err = MTAudioProcessingTapGetSourceAudio(tap, numberFrames, bufferListInOut, flagsOut, NULL, numberFramesOut);

  AudioBuffer *pBuffer = &bufferListInOut->mBuffers[0];
  UInt32 frameLength = pBuffer->mDataByteSize / sizeof(float);
  float *pData = (float *)pBuffer->mData;

  if (err == noErr && processor) {
    if ([processor.delegate 
      respondsToSelector:@selector(updateWith:withSize:)]) {
      [processor.delegate updateWith:pData withSize:frameLength];
    }
  }
 }

- (void)stopProcessing
{
  [self.item removeObserver:self forKeyPath:@"status"];
AVMutableAudioMixInputParameters *params =
(AVMutableAudioMixInputParameters *) _item.audioMix.inputParameters[0];
  MTAudioProcessingTapRef tap = params.audioTapProcessor;
  self.item.audioMix = nil;
  CFRelease(tap);
  //By doing this the tap processor does call its unprepare and finalize methods, so it is being deallocated fine.
}

然后在我的AudioViewController.swift中我有:

var processor: AudioTapProcessor!

override func prepareForPlayback() {
  super.prepareForPlayback()
  if processor == nil {
    processor = AudioTapProcessor(delegate: self, item: item)
    processor.startProcessing()
  }
}

override func viewWillDisappear(_ animated: Bool) {
   super.viewWillDisappear(animated)
   player.pause()
}

deinit {
  //I tried to do this early in the lifecycle(viewWillDissapear) and it is the same thing.
   processor.stopProcessing()
}

任何暗示都会受到欢迎,我会为此疯狂。谢谢

objective-c swift audio avplayer avplayeritem
2个回答
0
投票

我最终通过让MTAudioProcessingTapRef保留其AudioTapProcessor父母来解决问题。这样,它们就不会在生命周期的不同时刻被释放。

对原始代码的更改:

1.首先,我们使委托成为一个弱变量,因为它应该是:

@property (nonatomic, weak) id<AudioProcessorDelegate> delegate;

然后,我们将保留的self(我们的AudioTapProcessor)引用传递给创建的MTAudioProcessingTapRef

 callbacks.clientInfo = CFRetain((__bridge void *)(self));

3.还创建了一个自定义上下文来传递数据:

typedef struct TapProcessorContext {
  void *self;
} TapProcessorContext;

void init(MTAudioProcessingTapRef tap, void *clientInfo, void **tapStorageOut) {
  TapProcessorContext *context = calloc(1, sizeof(TapProcessorContext));
  //Initialize TapProcessorContext context.
  context->self = clientInfo;

  *tapStorageOut = context;
}

void finalize(MTAudioProcessingTapRef tap) {
  TapProcessorContext *context = (TapProcessorContext 
*)MTAudioProcessingTapGetStorage(tap);
  // Clearing the context. THIS IS KEY TO DEALLOCATE THE AUDIOTAPPROCESSOR
  CFRelease(context->self);
  context->self = NULL;

  free(context);
}

4.最后,我将一个解决方法应用于iOS中的已知错误到我们的stopProcessing方法:

- (void)stopProcessing
{
   if ( @available(iOS 11.0, *) ) {
    // Starting with iOS 11, it is not required to manually nil audioTapProcessor,
    // but we need to retain the audioMix for a bit to make sure the processing callback
    // will not be called after we release (this is due to a bug in iOS 11 which calls the release
    // callback but still calls the processing callback afterwards - it also releases internal data
    // on release, so simply checking for release in the processing block is not enough)
    // rdar://34977000
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      [self releaseTap];
    });
  } else {
    // Prior to iOS 11 we need to manually nil the audioTapProcessor
    [self releaseTap];
  }
}

-(void)releaseTap {
  AVMutableAudioMixInputParameters *params = (AVMutableAudioMixInputParameters *) _item.audioMix.inputParameters[0];
  params.audioTapProcessor = nil;
  _item.audioMix = nil;
}

0
投票

适用于所有iOS版本

⇨了解根本原因

1.AudioTapProcessor.m初始化

callbacks.clientInfo包含一个指向self的指针。这不仅仅是C指针的弱或强引用。因此,如果自我得到dealloc'ed我们context-> self指向一个坏的内存地址

- (AVAudioMix *)audioMix {
    if (!_audioMix) {
        AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
        if (audioMix) {
            AVMutableAudioMixInputParameters *audioMixInputParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:self.audioAssetTrack];
            if (audioMixInputParameters) {
                MTAudioProcessingTapCallbacks callbacks;

                ...
                callbacks.clientInfo = (__bridge void *)self;
                ...
            }
        }
     }
}

2.AudioTapProcessor.m processCallback

每次调用processCallback时都会进行安全检查以查看self是否被释放,但在上面的步骤1中记得即使self是dealloc'ed context-> self也不是nil但是指向一个坏的内存地址导致EXC_BAD_ACCESS。

static void tap_ProcessCallback(MTAudioProcessingTapRef tap, CMItemCount numberFrames, MTAudioProcessingTapFlags flags, AudioBufferList *bufferListInOut, CMItemCount *numberFramesOut, MTAudioProcessingTapFlags *flagsOut) {
    ...
    MYAudioTapProcessor *self = ((__bridge MYAudioTapProcessor *)context->self);
    if (!self) {
      NSLog(@"AudioTapProcessor - processCallback CANCELLED (self is nil)");
      return;
    }
    NSLog(@"AudioTapProcessor - processCallback PROCESSING");
}

⇨现在,如何解决这个问题?

1.ViewController.swift或audioTapProcessor的所有者

deinit {
  print("ViewController - Dealloc")
  audioTapProcessor.stopProcessing()
}

2.AudioTapProcessor.m

我们需要一种方法来告诉我们的audioTapProcessor停止processCallback。最简单自然的方法是使用上面的processCallback中的检查if(!self)return;

因此,停止audioTapProcessor只是正确地将context-> self设置为NULL。

- (void)stopProcessing {
    NSLog(@"AudioTapProcessor - stopProcessing");
    AVMutableAudioMixInputParameters *params = (AVMutableAudioMixInputParameters *)_audioMix.inputParameters[0];
    MTAudioProcessingTapRef audioProcessingTap = params.audioTapProcessor;
    AVAudioTapProcessorContext *context = (AVAudioTapProcessorContext *)MTAudioProcessingTapGetStorage(audioProcessingTap);

    // nils out the pointer so that we know in tapProcessorCallbacks that self will be dealloc'ed
    context->self = NULL;
}

⇨因此,生命周期得到纠正

而不是这个

  1. ViewController - Dealloc
  2. AudioTapProcessor - stopProcessing
  3. AudioTapProcessor - processCallback PROCESSING
  4. EXC_BAD_ADDRESS

我们得到这个

  1. ViewController - Dealloc
  2. AudioTapProcessor - stopProcessing
  3. AudioTapProcessor - processCallback CANCELED(self is nil)
  4. AudioTapProcessor - unexpareCallback
  5. AudioTapProcessor - finalizeCallback
  6. AudioTapProcessor - Dealloc
© www.soinside.com 2019 - 2024. All rights reserved.