2017-10-23 2 views
0

Ich habe in meinem Projekt mit einem MTAudioProcessingTapRef gearbeitet, um die Pufferdaten in Echtzeit während der Wiedergabe von Audio-Streaming zu analysieren. Die Sache ist, ich kann den Wasserhahnprozessor nicht dazu bringen, sich korrekt zu entziehen, wenn ich ihn brauche.MTAudioProcessingTapRef in swift3.2/4 korrekt entfernen

Ich habe eine AudioViewController Swift-Klasse mit einem Verweis auf meine AudioTapProcessor Objective-C-Klasse, die Swift-Klasse ist verantwortlich für den Prozessor die Verarbeitung für das AVPlayerItem zu starten und zu stoppen. Der Prozessor hat auch einen Delegaten (in diesem Fall den View-Controller), um während der Verarbeitung über Pufferänderungen zu informieren.

Mein Problem ist, wenn ich den Prozessor delegieren als schwach (wie es sein sollte), der Prozessor wird zufällig abstürzen versuchen, einen bereits freigegebenen Delegierten informieren, weil die Prozessmethode des Tap-Prozessor wurde ein paar Mal nach dem Stopp ausgeführt Verarbeitungsaufruf Der einzige Weg, den ich gefunden habe, um dies zu beheben, ist, den Tap-Prozessor-Delegaten als eine starke Eigenschaft zu deklarieren, was offensichtlich einen Retain-Zyklus verursacht, und meine AudioViewControllers werden niemals freigegeben.

Unten einige Code, den Sie relevant aus de Situation helfen könnte:

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. 
} 

Da ist in meinem AudioViewController.swift ich habe:

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() 
} 

Jeder Tipp wäre zu schätzen, ich verrückt damit. Danke

Antwort

0

Ich endete, das Problem zu lösen, indem ich das MTAudioProcessingTapRef machte, um sein AudioTapProcessor Elternteil zu behalten. Auf diese Weise werden sie nicht zu verschiedenen Zeitpunkten des Lebenszyklus freigegeben.

Änderungen an den ursprünglichen Code:

1.First, machen wir den Delegierten eine schwache Variable wie es sein sollte:

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

2.Then, wir beibehalten Referenzselbst passieren (Unser AudioTapProcessor) an die MTAudioProcessingTapRef erstellt:

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

3.Also einen benutzerdefinierten Kontext erzeugt, um Daten entlang der tap passieren:

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.Finally, wandte ich eine Abhilfe zu einem Know Bug in iOS zu unserer Stopprocessing Methode:

- (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; 
}