草庐IT

ios - Replaykit Assets 编写器状态随机失败

coder 2023-09-11 原文

用例

我正在使用 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)
            })
        }
    }
}

最佳答案

我让它工作了。我相信这确实是一个异步问题。问题是,出于某种原因,您必须确保

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

或者甚至更好,整个 block 都在 CMSampleBufferDataIsReady 中,即。

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

让我知道它是否有效!

关于ios - Replaykit Assets 编写器状态随机失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46774237/

有关ios - Replaykit Assets 编写器状态随机失败的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  3. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  4. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  5. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  6. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  7. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  8. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  9. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  10. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

随机推荐