MTAudioProcessingTap EXC_BAD_ACCESS, не всегда запускает обратный вызов завершения.как выпустить это? - PullRequest
0 голосов
/ 05 декабря 2018

Я пытаюсь реализовать MTAudioProcessingTap , и это прекрасно работает.Проблема в том, что когда я закончил использовать Tap, я восстановил свой класс и создал новый Tap.

Как я полагаю, что я освобождаю кран 1 - Я сохраняю кран как свойство при создании, надеясь, что смогу получить к нему доступ и позже освободить его 2 - В метод deinit () класса,Я установил аудиомикс на ноль и попытался сделать self.tap? .Release ()

Дело в том ... иногда это работает и вызывает обратный вызов FINALIZE, и все отлично, а иногда нет ипросто вылетает в tapProcess Линия обратного вызова:

let selfMediaInput = Unmanaged<VideoMediaInput>.fromOpaque(MTAudioProcessingTapGetStorage(tap)).takeUnretainedValue()

Вот полный код: https://gist.github.com/omarojo/03d08165a1a7962cb30c17ec01f809a3

import Foundation
import UIKit
import AVFoundation;
import MediaToolbox

protocol VideoMediaInputDelegate: class {
    func videoFrameRefresh(sampleBuffer: CMSampleBuffer) //could be audio or video
}

class VideoMediaInput: NSObject {
    private let queue = DispatchQueue(label: "com.GenerateMetal.VideoMediaInput")

    var videoURL: URL!

    weak var delegate: VideoMediaInputDelegate?

    private var playerItemObserver: NSKeyValueObservation?
    var displayLink: CADisplayLink!
    var player = AVPlayer()
    var playerItem: AVPlayerItem!
    let videoOutput = AVPlayerItemVideoOutput(pixelBufferAttributes: [String(kCVPixelBufferPixelFormatTypeKey): NSNumber(value: kCVPixelFormatType_32BGRA)])
    var audioProcessingFormat:  AudioStreamBasicDescription?//UnsafePointer<AudioStreamBasicDescription>?
    var tap: Unmanaged<MTAudioProcessingTap>?

    override init(){

    }

    convenience init(url: URL){
        self.init()
        self.videoURL = url

        self.playerItem = AVPlayerItem(url: url)

        playerItemObserver = playerItem.observe(\.status) { [weak self] item, _ in
            guard item.status == .readyToPlay else { return }
            self?.playerItemObserver = nil
            self?.player.play()
        }

        setupProcessingTap()


        player.replaceCurrentItem(with: playerItem)
        player.currentItem!.add(videoOutput)

        NotificationCenter.default.removeObserver(self)
        NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) {[weak self] notification in

            if let weakSelf = self {
                /*
                 Setting actionAtItemEnd to None prevents the movie from getting paused at item end. A very simplistic, and not gapless, looped playback.
                 */
                weakSelf.player.actionAtItemEnd = .none
                weakSelf.player.seek(to: CMTime.zero)
                weakSelf.player.play()
            }

        }
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(applicationDidBecomeActive(_:)),
            name: UIApplication.didBecomeActiveNotification,
            object: nil)

    }

    func stopAllProcesses(){
        self.queue.sync {
            self.player.pause()
            self.player.isMuted = true
            self.player.currentItem?.audioMix = nil
            self.playerItem.audioMix = nil
            self.playerItem = nil
            self.tap?.release()
        }
    }


    deinit{
        print(">> VideoInput deinited !!!! ??")
        if let link = self.displayLink {
            link.invalidate()
        }
        NotificationCenter.default.removeObserver(self)

        stopAllProcesses()

    }
    public func playVideo(){
        if (player.currentItem != nil) {
            print("Starting playback!")
            player.play()
        }
    }
    public func pauseVideo(){
        if (player.currentItem != nil) {
            print("Pausing playback!")
            player.pause()
        }
    }

    @objc func applicationDidBecomeActive(_ notification: NSNotification) {
        playVideo()
    }




    //MARK: GET AUDIO BUFFERS
    func setupProcessingTap(){

        var callbacks = MTAudioProcessingTapCallbacks(
            version: kMTAudioProcessingTapCallbacksVersion_0,
            clientInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()),
            init: tapInit,
            finalize: tapFinalize,
            prepare: tapPrepare,
            unprepare: tapUnprepare,
            process: tapProcess)

        var tap: Unmanaged<MTAudioProcessingTap>?
        let err = MTAudioProcessingTapCreate(kCFAllocatorDefault, &callbacks, kMTAudioProcessingTapCreationFlag_PostEffects, &tap)
        self.tap = tap


        print("err: \(err)\n")
        if err == noErr {
        }

        print("tracks? \(playerItem.asset.tracks)\n")

        let audioTrack = playerItem.asset.tracks(withMediaType: AVMediaType.audio).first!
        let inputParams = AVMutableAudioMixInputParameters(track: audioTrack)
        inputParams.audioTapProcessor = tap?.takeRetainedValue()//tap?.takeUnretainedValue()
//        tap?.release()

        // print("inputParms: \(inputParams), \(inputParams.audioTapProcessor)\n")
        let audioMix = AVMutableAudioMix()
        audioMix.inputParameters = [inputParams]

        playerItem.audioMix = audioMix
    }

    //MARK: TAP CALLBACKS

    let tapInit: MTAudioProcessingTapInitCallback = {
        (tap, clientInfo, tapStorageOut) in
        tapStorageOut.pointee = clientInfo

        print("init \(tap, clientInfo, tapStorageOut)\n")

    }

    let tapFinalize: MTAudioProcessingTapFinalizeCallback = {
        (tap) in
        print("finalize \(tap)\n")
    }

    let tapPrepare: MTAudioProcessingTapPrepareCallback = {
        (tap, itemCount, basicDescription) in
        print("prepare: \(tap, itemCount, basicDescription)\n")
        let selfMediaInput = Unmanaged<VideoMediaInput>.fromOpaque(MTAudioProcessingTapGetStorage(tap)).takeUnretainedValue()
        selfMediaInput.audioProcessingFormat = AudioStreamBasicDescription(mSampleRate: basicDescription.pointee.mSampleRate,
                                                                           mFormatID: basicDescription.pointee.mFormatID, mFormatFlags: basicDescription.pointee.mFormatFlags, mBytesPerPacket: basicDescription.pointee.mBytesPerPacket, mFramesPerPacket: basicDescription.pointee.mFramesPerPacket, mBytesPerFrame: basicDescription.pointee.mBytesPerFrame, mChannelsPerFrame: basicDescription.pointee.mChannelsPerFrame, mBitsPerChannel: basicDescription.pointee.mBitsPerChannel, mReserved: basicDescription.pointee.mReserved)
    }

    let tapUnprepare: MTAudioProcessingTapUnprepareCallback = {
        (tap) in
        print("unprepare \(tap)\n")
    }

    let tapProcess: MTAudioProcessingTapProcessCallback = {
        (tap, numberFrames, flags, bufferListInOut, numberFramesOut, flagsOut) in
        print("callback \(bufferListInOut)\n")

        let selfMediaInput = Unmanaged<VideoMediaInput>.fromOpaque(MTAudioProcessingTapGetStorage(tap)).takeUnretainedValue()

        let status = MTAudioProcessingTapGetSourceAudio(tap, numberFrames, bufferListInOut, flagsOut, nil, numberFramesOut)
        //print("get audio: \(status)\n")
        if status != noErr {
            print("Error TAPGetSourceAudio :\(String(describing: status.description))")
            return
        }

        selfMediaInput.processAudioData(audioData: bufferListInOut, framesNumber: UInt32(numberFrames))
    }
    func processAudioData(audioData: UnsafeMutablePointer<AudioBufferList>, framesNumber: UInt32) {
        var sbuf: CMSampleBuffer?
        var status : OSStatus?
        var format: CMFormatDescription?

        //FORMAT
//        var audioFormat = self.audioProcessingFormat//self.audioProcessingFormat?.pointee
        guard var audioFormat = self.audioProcessingFormat else {
            return
        }
        status = CMAudioFormatDescriptionCreate(allocator: kCFAllocatorDefault, asbd: &audioFormat, layoutSize: 0, layout: nil, magicCookieSize: 0, magicCookie: nil, extensions: nil, formatDescriptionOut: &format)
        if status != noErr {
            print("Error CMAudioFormatDescriptionCreater :\(String(describing: status?.description))")
            return
        }


        print(">> Audio Buffer mSampleRate:\(Int32(audioFormat.mSampleRate))")
        var timing = CMSampleTimingInfo(duration: CMTimeMake(value: 1, timescale: Int32(audioFormat.mSampleRate)), presentationTimeStamp: self.player.currentTime(), decodeTimeStamp: CMTime.invalid)


        status = CMSampleBufferCreate(allocator: kCFAllocatorDefault,
                                      dataBuffer: nil,
                                      dataReady: Bool(truncating: 0),
                                      makeDataReadyCallback: nil,
                                      refcon: nil,
                                      formatDescription: format,
                                      sampleCount: CMItemCount(framesNumber),
                                      sampleTimingEntryCount: 1,
                                      sampleTimingArray: &timing,
                                      sampleSizeEntryCount: 0, sampleSizeArray: nil,
                                      sampleBufferOut: &sbuf);
        if status != noErr {
            print("Error CMSampleBufferCreate :\(String(describing: status?.description))")
            return
        }
        status =   CMSampleBufferSetDataBufferFromAudioBufferList(sbuf!,
                                                                  blockBufferAllocator: kCFAllocatorDefault ,
                                                                  blockBufferMemoryAllocator: kCFAllocatorDefault,
                                                                  flags: 0,
                                                                  bufferList: audioData)
        if status != noErr {
            print("Error cCMSampleBufferSetDataBufferFromAudioBufferList :\(String(describing: status?.description))")
            return
        }

        let currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(sbuf!);
        print(" audio buffer at time: \(currentSampleTime)")
        self.delegate?.videoFrameRefresh(sampleBuffer: sbuf!)

    }


}

Как я использую свой класс

self.inputVideoMedia = nil
self.inputVideoMedia = VideoMediaInput(url: videoURL)
self.inputVideoMedia!.delegate = self

во второй раз, когда я делаю это .. он падает (но не всегда).Времена, когда он не падает, я вижу напечатанным в консоли выводом FINALIZE.

Ответы [ 2 ]

0 голосов
/ 06 декабря 2018

Аудио контекст работает в своем собственном потоке в реальном времени.Таким образом, аудиопроцессы не останавливаются синхронно с вызовом функции остановки или отмены, но через некоторое неизвестное время (порядка продолжительности некоторого количества аудиосэмплов в некоторых внутренних аудиобуферах) после истечения потока в реальном времени.

Таким образом, аудиобуферы, объекты и обратные вызовы не должны освобождаться (или перераспределяться) до некоторого (неизвестного, но менее пары секунд) времени после остановки любого аудиопотока в реальном времени.

В зависимости от сообщений объекта освобождения или состояний переменных экземпляра (включая слабые ссылки) между потоками реального времени, как сообщается, в настоящее время небезопасно в Swift (см. Сеанс WWDC 2018 по аудио).Таким образом, я рекомендую использовать семафоры (вне контекста реального времени, такого как аудио), или барьеры памяти posix (внутри мостового вызова функции C).(... пока какая-то будущая версия Swift не выяснит механизм параллелизма в реальном времени.) (... особенно на устройствах iOS, которые могут переупорядочивать операции записи в память).

0 голосов
/ 05 декабря 2018

Если VideoMediaInput освобождается до , то нажатие освобождается (что может произойти, так как кажется, что нет способа синхронно остановить нажатие), тогда обратный вызов касания может подавить ссылку на ваше освобождение.class.

Вы можете исправить это, передав слабую ссылку (вероятно, завернутую) в ваш класс.Вы можете сделать это следующим образом:

Сначала удалите переменную tap экземпляра и любые ссылки на нее - это не нужно.Затем внесите следующие изменения:

class VideoMediaInput: NSObject {

    class TapCookie {
        weak var input: VideoMediaInput?

        deinit {
            print("TapCookie deinit")
        }
    }
...

    func setupProcessingTap(){
        let cookie = TapCookie()
        cookie.input = self

        var callbacks = MTAudioProcessingTapCallbacks(
            version: kMTAudioProcessingTapCallbacksVersion_0,
            clientInfo: UnsafeMutableRawPointer(Unmanaged.passRetained(cookie).toOpaque()),
            init: tapInit,
            finalize: tapFinalize,
            prepare: tapPrepare,
            unprepare: tapUnprepare,
            process: tapProcess)
...


    let tapFinalize: MTAudioProcessingTapFinalizeCallback = {
        (tap) in
        print("finalize \(tap)\n")

       // release cookie
        Unmanaged<TapCookie>.fromOpaque(MTAudioProcessingTapGetStorage(tap)).release()
    }


    let tapPrepare: MTAudioProcessingTapPrepareCallback = {
        (tap, itemCount, basicDescription) in
        print("prepare: \(tap, itemCount, basicDescription)\n")
        let cookie = Unmanaged<TapCookie>.fromOpaque(MTAudioProcessingTapGetStorage(tap)).takeUnretainedValue()
        let selfMediaInput = cookie.input!
...

    let tapProcess: MTAudioProcessingTapProcessCallback = {
        (tap, numberFrames, flags, bufferListInOut, numberFramesOut, flagsOut) in
        print("callback \(bufferListInOut)\n")

        let cookie = Unmanaged<TapCookie>.fromOpaque(MTAudioProcessingTapGetStorage(tap)).takeUnretainedValue()

        guard let selfMediaInput = cookie.input else {
            print("Tap callback: VideoMediaInput was deallocated!")
            return
        }
...

Я не уверен, нужен ли класс cookie, он существует только для переноса ссылки weak.Новейшие эксперты Swift могут знать, как справиться со слабостью с помощью всех сырых указателей ниндзя-мутанта-подростка, но я не знаю.

...