Статус записи активов Replaykit случайно не работает

Случай использования

Я использую платформу iOS 11 Replaykit, чтобы попытаться записать кадры с экрана и звук как из приложения, так и с микрофона.

Проблема

Случайным образом, когда я вызываю свой .append(sampleBuffer), получаю AVAssetWriterStatus.failed с отображением AssetWriter.Error

Error Domain=AVFoundationErrorDomain Code=-11823 "Cannot Save" UserInfo={NSLocalizedRecoverySuggestion=Try saving again., NSLocalizedDescription=Cannot Save, NSUnderlyingError=0x1c044c360 {Error Domain=NSOSStatusErrorDomain Code=-12412 "(null)"}}

Побочная проблема: я воспроизвожу повторяющийся звук, когда приложение записывает, чтобы попытаться убедиться, что звук записан, но звук останавливается, когда я начинаю запись, даже если работает видео и внешний аудиомикрофон.

Если вам нужна дополнительная информация, я также могу загрузить другой код на GitHub.

Идеи

Поскольку иногда запись сохраняется (я могу экспортировать в приложение «Фотографии» и воспроизвести видео), я думаю, что это должны быть асинхронные проблемы, когда я загружаю вещи не по порядку. Пожалуйста, дайте мне знать, если вы их увидите!

Я думаю, что я попытаюсь сохранить в свою собственную папку в /Documents вместо непосредственно в /Documents в случае странных ошибок разрешений. Хотя я считаю, что это будет вызывать постоянные ошибки, а не только иногда ломаться.

Мой код

func startRecording() {
    guard let firstDocumentDirectoryPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return }

    let directoryContents = try! FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: firstDocumentDirectoryPath), includingPropertiesForKeys: nil, options: [])
    print(directoryContents)

    videoURL = URL(fileURLWithPath: firstDocumentDirectoryPath.appending("/\(arc4random()).mp4"))

    print(videoURL.absoluteString)

    assetWriter = try! AVAssetWriter(url: videoURL, fileType: AVFileType.mp4)

    let compressionProperties:[String:Any] = [...]
    let videoSettings:[String:Any] = [...]
    let audioSettings:[String:Any] = [...]

    videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
    audioMicInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)
    audioAppInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)

    guard let assetWriter = assetWriter else { return }
    guard let videoInput = videoInput else { return }
    guard let audioAppInput = audioAppInput else { return }
    guard let audioMicInput = audioMicInput else { return }

    videoInput.mediaTimeScale = 60
    videoInput.expectsMediaDataInRealTime = true
    audioMicInput.expectsMediaDataInRealTime = true
    audioAppInput.expectsMediaDataInRealTime = true

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

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

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

    assetWriter.movieTimeScale = 60

    RPScreenRecorder.shared().startCapture(handler: recordingHandler(sampleBuffer:sampleBufferType:error:)) { (error:Error?) in
        if error != nil {
            print("RPScreenRecorder.shared().startCapture: \(error.debugDescription)")
        } else {
            print("start capture complete")
        }
    }
}

func recordingHandler (sampleBuffer:CMSampleBuffer, sampleBufferType:RPSampleBufferType, error:Error?){
    if error != nil {
        print("recordingHandler: \(error.debugDescription)")
    }

    if CMSampleBufferDataIsReady(sampleBuffer) {
        guard let assetWriter = assetWriter else { return }
        guard let videoInput = videoInput else { return }
        guard let audioAppInput = audioAppInput else { return }
        guard let audioMicInput = audioMicInput else { return }

        if assetWriter.status == AVAssetWriterStatus.unknown {
            print("AVAssetWriterStatus.unknown")
            if !assetWriter.startWriting() {
                return
            }
            assetWriter.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
        }

        if assetWriter.status == AVAssetWriterStatus.failed {
            print("AVAssetWriterStatus.failed")
            print("assetWriter.error: \(assetWriter.error.debugDescription)")
            return
        }

        if sampleBufferType == RPSampleBufferType.video {
            if videoInput.isReadyForMoreMediaData {
                print("=appending video data")
                videoInput.append(sampleBuffer)
            }
        }

        if sampleBufferType == RPSampleBufferType.audioApp {
            if audioAppInput.isReadyForMoreMediaData {
                print("==appending app audio data")
                audioAppInput.append(sampleBuffer)
            }
        }

        if sampleBufferType == RPSampleBufferType.audioMic {
            if audioMicInput.isReadyForMoreMediaData {
                print("===appending mic audio data")
                audioMicInput.append(sampleBuffer)
            }
        }
    }
}

func stopRecording() {
    RPScreenRecorder.shared().stopCapture { (error) in
        guard let assetWriter = self.assetWriter else { return }
        guard let videoInput = self.videoInput else { return }
        guard let audioAppInput = self.audioAppInput else { return }
        guard let audioMicInput = self.audioMicInput else { return }

        if error != nil {
            print("recordingHandler: \(error.debugDescription)")
        } else {
            videoInput.markAsFinished()
            audioMicInput.markAsFinished()
            audioAppInput.markAsFinished()

            assetWriter.finishWriting(completionHandler: {
                print(self.videoURL)
                self.saveToCameraRoll(URL: self.videoURL)
            })
        }
    }
}

person stanley    schedule 16.10.2017    source источник
comment
Вы нашли решение, которое сработало для вас?   -  person Brandon A    schedule 12.04.2018
comment
Вы нашли решение этой проблемы? У меня точно такая же проблема. Предложение WLee не сработало для меня.   -  person Geoff H    schedule 16.04.2018
comment
К сожалению нет :( Может посмотрю не запилил ли кто на него радар или запилил новый   -  person stanley    schedule 16.04.2018
comment
Спасибо за быстрый ответ. Сделаю. Моя проблема отличается только тем, что мое видео отлично записывается каждый раз, когда пользователь выбирает «Запись только с экрана», но когда выбирается «Запись экрана и микрофона», оно всегда терпит неудачу с первой попытки, а затем случайным образом удается при последующих попытках. Я думаю, вы можете быть на что-то с асинхронной идеей. Я использую многопоточность в своем приложении.   -  person Geoff H    schedule 17.04.2018
comment
Видео @stanley записано успешно, но аудиозапись не работает. пожалуйста, предложите.   -  person Nilesh Parmar    schedule 01.06.2020
comment
@stanley, когда я записываю видео. аудиозапись не работает. пожалуйста, предложите   -  person Nilesh Parmar    schedule 01.06.2020


Ответы (2)


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

assetWriter.startWriting()
assetWriter.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer))

происходят строго последовательно.

Измените свой код из этого:

if assetWriter.status == AVAssetWriterStatus.unknown {
    print("AVAssetWriterStatus.unknown")
    if !assetWriter.startWriting() {
        return
    }
    assetWriter.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
}

к этому:

DispatchQueue.main.async { [weak self] in
    if self?.assetWriter.status == AVAssetWriterStatus.unknown {
        print("AVAssetWriterStatus.unknown")
        if !self?.assetWriter.startWriting() {
            return
        }
        self?.assetWriter.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
    }
}

Или еще лучше весь блок внутри CMSampleBufferDataIsReady т.е.

if CMSampleBufferDataIsReady(sampleBuffer) {
    DispatchQueue.main.async { [weak self] in
        ...
        ...
    }
}

Дайте мне знать, если это сработает!

person Geoff H    schedule 16.04.2018
comment
Это устранило проблему с запуском записи. Код для меня почти такой же, поэтому добавил «DispatchQueue.main.async», и он начал работать. - person Vipin Johney; 14.10.2019
comment
когда я записываю видео. аудиозапись не работает. пожалуйста, предложите. - person Nilesh Parmar; 01.06.2020
comment
@NileshParmar тебе все еще нужна помощь со звуком? - person user4478383; 22.10.2020

У меня была аналогичная проблема. Я исправил это, сначала проверив, существует ли уже файл videoURL. Если это так, сначала удалите его, тогда ошибка исчезнет.

person WLee    schedule 03.01.2018
comment
Так как результат воспроизведения видео я сохраняю во временную директорию, перед созданием нового видео проверяю, не существовал ли уже файл. Если это так, удалите его. - person WLee; 21.03.2018
comment
Так как результат воспроизведения видео я сохраняю во временную директорию, перед созданием нового видео проверяю, не существовал ли уже файл. Если это так, удалите его. если FileManager().fileExists(atPath: Constants.TempPath) {попробуйте! FileManager().removeItem(atPath: Constants.TempPath)} Также перед добавлением обязательно проверьте, можно ли добавить видео: if assetsWriter.canAdd(videoInput){assetWriter.add(videoInput)} - person WLee; 21.03.2018