【问题标题】:AVAssetWriterInput appendSampleBuffer: Cannot append sample buffer: Must start a session (using -AVAssetWriter startSessionAtSourceTime:) first'AVAssetWriterInput appendSampleBuffer:无法附加样本缓冲区:必须首先启动会话(使用 -AVAssetWriter startSessionAtSourceTime:)'
【发布时间】:2020-11-10 16:57:06
【问题描述】:

我正在使用ARVideoKit 录制屏幕(ReplayKit 不适用于此),有时我可以毫无问题地录制和保存。其他时候我录制,当我去保存时,我遇到了崩溃:

** 由于未捕获的异常 'NSInternalInconsistencyException' 导致应用程序终止,原因:'*** -[AVAssetWriterInput appendSampleBuffer:] 无法附加样本缓冲区:必须启动会话 (首先使用 -[AVAssetWriter startSessionAtSourceTime:)'

查看 StackTrace,它是一个 __pthread__kill,它出现在 thread 83 内:

具体在这个DispatchQueue:

let audioBufferQueue = DispatchQueue(label: "com.ahmedbekhit.AudioBufferQueue")

如何防止这种情况发生?

这是文件中的代码:

import AVFoundation
import CoreImage
import UIKit

@available(iOS 11.0, *)
class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
    private var assetWriter: AVAssetWriter!
    private var videoInput: AVAssetWriterInput!
    private var audioInput: AVAssetWriterInput!
    private var session: AVCaptureSession!

    private var pixelBufferInput: AVAssetWriterInputPixelBufferAdaptor!
    private var videoOutputSettings: Dictionary<String, AnyObject>!
    private var audioSettings: [String: Any]?

    let audioBufferQueue = DispatchQueue(label: "com.ahmedbekhit.AudioBufferQueue")

    private var isRecording: Bool = false

    weak var delegate: RecordARDelegate?
    var videoInputOrientation: ARVideoOrientation = .auto

    init(output: URL, width: Int, height: Int, adjustForSharing: Bool, audioEnabled: Bool, orientaions:[ARInputViewOrientation], queue: DispatchQueue, allowMix: Bool) {
        super.init()
        do {
            assetWriter = try AVAssetWriter(outputURL: output, fileType: AVFileType.mp4)
        } catch {
            // FIXME: handle when failed to allocate AVAssetWriter.
            return
        }
        if audioEnabled {
            if allowMix {
                let audioOptions: AVAudioSession.CategoryOptions = [.mixWithOthers , .allowBluetooth, .defaultToSpeaker, .interruptSpokenAudioAndMixWithOthers]
                try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: AVAudioSession.Mode.spokenAudio, options: audioOptions)
                try? AVAudioSession.sharedInstance().setActive(true)
            }
            AVAudioSession.sharedInstance().requestRecordPermission({ permitted in
                if permitted {
                    self.prepareAudioDevice(with: queue)
                }
            })
        }

        //HEVC file format only supports A10 Fusion Chip or higher.
        //to support HEVC, make sure to check if the device is iPhone 7 or higher
        videoOutputSettings = [
            AVVideoCodecKey: AVVideoCodecType.h264 as AnyObject,
            AVVideoWidthKey: width as AnyObject,
            AVVideoHeightKey: height as AnyObject
        ]

        let attributes: [String: Bool] = [
            kCVPixelBufferCGImageCompatibilityKey as String: true,
            kCVPixelBufferCGBitmapContextCompatibilityKey as String: true
        ]
        videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)

        videoInput.expectsMediaDataInRealTime = true
        pixelBufferInput = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoInput, sourcePixelBufferAttributes: nil)

        var angleEnabled: Bool {
            for v in orientaions {
                if UIDevice.current.orientation.rawValue == v.rawValue {
                    return true
                }
            }
            return false
        }

        var recentAngle: CGFloat = 0
        var rotationAngle: CGFloat = 0
        switch UIDevice.current.orientation {
        case .landscapeLeft:
            rotationAngle = -90
            recentAngle = -90
        case .landscapeRight:
            rotationAngle = 90
            recentAngle = 90
        case .faceUp, .faceDown, .portraitUpsideDown:
            rotationAngle = recentAngle
        default:
            rotationAngle = 0
            recentAngle = 0
        }

        if !angleEnabled {
            rotationAngle = 0
        }

        var t = CGAffineTransform.identity

        switch videoInputOrientation {
        case .auto:
            t = t.rotated(by: ((rotationAngle*CGFloat.pi) / 180))
        case .alwaysPortrait:
            t = t.rotated(by: 0)
        case .alwaysLandscape:
            if rotationAngle == 90 || rotationAngle == -90 {
                t = t.rotated(by: ((rotationAngle * CGFloat.pi) / 180))
            } else {
                t = t.rotated(by: ((-90 * CGFloat.pi) / 180))
            }
        }

        videoInput.transform = t

        if assetWriter.canAdd(videoInput) {
            assetWriter.add(videoInput)
        } else {
            delegate?.recorder(didFailRecording: assetWriter.error, and: "An error occurred while adding video input.")
            isWritingWithoutError = false
        }
        assetWriter.shouldOptimizeForNetworkUse = adjustForSharing
    }

    func prepareAudioDevice(with queue: DispatchQueue) {
        let device: AVCaptureDevice = AVCaptureDevice.default(for: .audio)!
        var audioDeviceInput: AVCaptureDeviceInput?
        do {
            audioDeviceInput = try AVCaptureDeviceInput(device: device)
        } catch {
            audioDeviceInput = nil
        }

        let audioDataOutput = AVCaptureAudioDataOutput()
        audioDataOutput.setSampleBufferDelegate(self, queue: queue)

        session = AVCaptureSession()
        session.sessionPreset = .medium
        session.usesApplicationAudioSession = true
        session.automaticallyConfiguresApplicationAudioSession = false

        if session.canAddInput(audioDeviceInput!) {
            session.addInput(audioDeviceInput!)
        }
        if session.canAddOutput(audioDataOutput) {
            session.addOutput(audioDataOutput)
        }


        audioSettings = audioDataOutput.recommendedAudioSettingsForAssetWriter(writingTo: .m4v) as? [String: Any]

        audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)
        audioInput.expectsMediaDataInRealTime = true

        audioBufferQueue.async {
            self.session.startRunning()
        }

        if assetWriter.canAdd(audioInput) {
            assetWriter.add(audioInput)
        }
    }

    var startingVideoTime: CMTime?
    var isWritingWithoutError: Bool?
    var currentDuration: TimeInterval = 0 // Seconds

    func insert(pixel buffer: CVPixelBuffer, with intervals: CFTimeInterval) {
        let time: CMTime = CMTime(seconds: intervals, preferredTimescale: 1000000)
        insert(pixel: buffer, with: time)
    }

    func insert(pixel buffer: CVPixelBuffer, with time: CMTime) {
        if assetWriter.status == .unknown {
            guard startingVideoTime == nil else {
                isWritingWithoutError = false
                return
            }
            startingVideoTime = time
            if assetWriter.startWriting() {
                assetWriter.startSession(atSourceTime: startingVideoTime!)
                currentDuration = 0
                isRecording = true
                isWritingWithoutError = true
            } else {
                delegate?.recorder(didFailRecording: assetWriter.error, and: "An error occurred while starting the video session.")
                currentDuration = 0
                isRecording = false
                isWritingWithoutError = false
            }
        } else if assetWriter.status == .failed {
            delegate?.recorder(didFailRecording: assetWriter.error, and: "Video session failed while recording.")
            logAR.message("An error occurred while recording the video, status: \(assetWriter.status.rawValue), error: \(assetWriter.error!.localizedDescription)")
            currentDuration = 0
            isRecording = false
            isWritingWithoutError = false
            return
        }

        if videoInput.isReadyForMoreMediaData {
            append(pixel: buffer, with: time)
            currentDuration = time.seconds - startingVideoTime!.seconds
            isRecording = true
            isWritingWithoutError = true
            delegate?.recorder?(didUpdateRecording: currentDuration)
        }
    }

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        if let input = audioInput {
            audioBufferQueue.async { [weak self] in
                if input.isReadyForMoreMediaData && (self?.isRecording)! {
                    input.append(sampleBuffer)
                }
            }
        }
    }

    func pause() {
        isRecording = false
    }

    func end(writing finished: @escaping () -> Void) {
        if let session = session {
            if session.isRunning {
                session.stopRunning()
            }
        }

        if assetWriter.status == .writing {
            assetWriter.finishWriting(completionHandler: finished)
        }
    }

    func cancel() {
        if let session = session {
            if session.isRunning {
                session.stopRunning()
            }
        }
        assetWriter.cancelWriting()
    }
}

@available(iOS 11.0, *)
private extension WritAR {
    func append(pixel buffer: CVPixelBuffer, with time: CMTime) {
        pixelBufferInput.append(buffer, withPresentationTime: time)
    }
}

//Simple Logging to show logs only while debugging.
class logAR {
    class func message(_ message: String) {
        #if DEBUG
            print("ARVideoKit @ \(Date().timeIntervalSince1970):- \(message)")
        #endif
    }

    class func remove(from path: URL?) {
        if let file = path?.path {
            let manager = FileManager.default
            if manager.fileExists(atPath: file) {
                do{
                    try manager.removeItem(atPath: file)
                    self.message("Successfuly deleted media file from cached after exporting to Camera Roll.")
                } catch let error {
                    self.message("An error occurred while deleting cached media: \(error)")
                }
            }
        }
    }
}

【问题讨论】:

    标签: ios swift grand-central-dispatch avassetwriter


    【解决方案1】:

    这是导致此错误的代码:

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        if let input = audioInput {
            audioBufferQueue.async { [weak self] in
                if input.isReadyForMoreMediaData && (self?.isRecording)! {
                    input.append(sampleBuffer)
                }
            }
        }
    }
    

    在调用input.append(..) 之前,您应该确保您的 AVAssetWriter 会话已启动。 AVAssetWriter 似乎没有返回会话状态的属性,因此您应该在 WritAR 类中添加 isSessionStarted 标志。
    然后在调用input.append(..) 之前检查此标志并(重新)启动会话(如果需要)。

    编辑: 编写一个辅助函数来启动会话:

    func startSessionIfNeeded(atSourceTime time: CMTime) {
       if isSessionStarted {
          return
       }
       assetWriter.startSession(atSourceTime: time)
       isSessionStarted = true
    }
    

    在您的代码中:

    func insert(pixel buffer: CVPixelBuffer, with time: CMTime) {
        if assetWriter.status == .unknown {
            guard startingVideoTime == nil else {
                isWritingWithoutError = false
                return
            }
            startingVideoTime = time
            if assetWriter.startWriting() {
                assetWriter.startSession(atSourceTime: startingVideoTime!)
                currentDuration = 0
                isRecording = true
                isWritingWithoutError = true
            } else {
                ...
            }
    

    assetWriter.startSession(atSourceTime: startingVideoTime!) 行替换为辅助函数startSessionIfNeeded(atSourceTime: startingVideoTime)

    另外,改变你的 captureOutput 方法:

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        if let input = audioInput {
            audioBufferQueue.async { [weak self] in
                self?.startSessionIfNeeded(atSourceTime: self?.startingVideoTime)
                if input.isReadyForMoreMediaData && (self?.isRecording)! {
                    input.append(sampleBuffer)
                }
            }
        }
    }
    

    【讨论】:

    • 它还是崩溃了。我在 captureOutput 方法中 c+p 新代码,但使用可选绑定来安全地解开 self?.startingVideoTime (它不会采用可选的)。它不断地从同一个线程给出同样的崩溃,奇怪
    • 请在您的代码中添加一些日志输出。特别是在“取消”、“结束(写作..)”和“插入”方法的不同情况下。在您尝试附加缓冲区之前,会话可能因某种原因而结束
    • 是的,肯定还有其他事情发生。我看看能不能打印出来。一旦我缩小范围,我会回复你。感谢您的帮助:)
    【解决方案2】:

    崩溃可能是由于不正确的弱 self 使用。我在这里修复了潜在的崩溃 - https://github.com/AFathi/ARVideoKit/pull/122/files

    检查是否有帮助。

    【讨论】:

      猜你喜欢
      • 2021-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-23
      • 2019-09-10
      • 1970-01-01
      相关资源
      最近更新 更多