RPScreenRecorder.shared().startCapture 无法写入/持续失败。

huangapple go评论73阅读模式
英文:

RPScreenRecorder.shared().startCapture won't write / keeps failing

问题

在尝试使用ReplayKit录制和保存音频/视频时,我一直遇到错误。我正在使用以下配置:

  • Xcode: 版本 11.2.1
  • Swift 5
  • iOS 13
  • iPhone 7+ 物理设备

当我设置文件路径时,我已经使用了 URL(fileURLWithPath: )。文件扩展名和 AVFileType 都是 .mp4。我检查了文件是否已经存在于 FileManager 中,如果存在,我会将其删除:do { try FileManager.default.removeItem(at: videoURL) }。我尝试将路径本身更改为像@florianSAP的回答中的 "Library/Caches/",但没有成功。

这里是3个错误:

  1. 从录制开始的错误:

    if !self.assetWriter.startWriting() {
        print("无法写入")
        return
    }
    
  2. 从录制开始的错误:

    if self.assetWriter.status == AVAssetWriter.Status.failed {
        print("StartCapture 出错,状态 = \(self.assetWriter.status.rawValue)\(self.assetWriter.error?.localizedDescription) \(self.assetWriter.error?.debugDescription)")
        return
    }
    
  3. 尝试在 PHAssetChangeRequest.creationRequestForAssetFromVideo 完成处理程序中保存 URL 时的错误:

    if let error = error {
        print("PHAssetChangeRequest 视频错误: \(error.localizedDescription)")
        return
    }
    

错误消息如下:

StartCapture 出错,状态 = 3,无法完成操作,可选(错误域=AVFoundationErrorDomain 代码=-11800 "无法完成操作" 用户信息= {NSLocalizedFailureReason=发生了未知错误(-17508),NSLocalizedDescription=无法完成操作,NSUnderlyingError=0x2833a93b0 {错误域=NSOSStatusErrorDomain 代码=-17508 "(null)"}})

PHAssetChangeRequest 视频错误: 无法完成操作(PHPhotosErrorDomain 错误 -1。)

我在哪里出错了?

开始录制:

let recorder = RPScreenRecorder.shared()
var assetWriter: AVAssetWriter!
var videoURL: URL!
var videoInput: AVAssetWriterInput!
var audioMicInput: AVAssetWriterInput!

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

videoURL = URL(fileURLWithPath: documentsPath.appending(UUID().uuidString + ".mp4"))

guard let videoURL = videoURL else { return }

do {
    try FileManager.default.removeItem(at: videoURL)
} catch {}

do {
    try assetWriter = AVAssetWriter(outputURL: videoURL, fileType: .mp4)
} catch {}

let videoSettings: [String : Any] = [
    AVVideoCodecKey: AVVideoCodecType.h264,
    AVVideoWidthKey: view.bounds.width,
    AVVideoHeightKey: view.bounds.height
]

videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
videoInput.expectsMediaDataInRealTime = true
if assetWriter.canAdd(videoInput) {
    assetWriter.add(videoInput)
}

let audioSettings: [String:Any] = [AVFormatIDKey : kAudioFormatMPEG4AAC,
    AVNumberOfChannelsKey : 2,
    AVSampleRateKey : 44100.0,
    AVEncoderBitRateKey: 192000
]

audioMicInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)
audioMicInput.expectsMediaDataInRealTime = true
if assetWriter.canAdd(audioMicInput) {
    assetWriter.add(audioMicInput)
}

guard recorder.isAvailable else { return }

recorder.startCapture(handler: { (cmSampleBuffer, rpSampleBufferType, err) in

    if let err = err { return }

    DispatchQueue.main.async {

        switch rpSampleBufferType {
           case .video:
                
                if self.assetWriter.status == AVAssetWriter.Status.unknown {
                    
                    if !self.assetWriter.startWriting() {
                        print("无法开始写入")
                        return
                    }
                    
                    print("开始写入")
                    self.assetWriter.startWriting()
                    self.assetWriter.startSession(atSourceTime:  CMSampleBufferGetPresentationTimeStamp(cmSampleBuffer))
                }
                
                if self.assetWriter.status == AVAssetWriter.Status.failed {
                    print("StartCapture 出错,状态 = \(self.assetWriter.status.rawValue)\(self.assetWriter.error?.localizedDescription) \(self.assetWriter.error?.debugDescription)")
                    return
                }
                
                if self.assetWriter.status == AVAssetWriter.Status.writing {
                    if self.videoInput.isReadyForMoreMediaData {
                        if self.videoInput.append(cmSampleBuffer) == false {
                            print("写入视频出现问题")
                        }
                    }
                }
                
            case .audioMic:
                if self.audioMicInput.isReadyForMoreMediaData {
                    print("添加音频Mic数据")
                    self.audioMicInput.append(cmSampleBuffer)
                }

            default:
                print("不是视频样本")
            }
        }
    }

}, completionHandler: { (error) in

    if let error = error { return }
})

停止录制:

recorder.stopCapture { (error) in

    if let error = error { return }

    guard let videoInput = self.videoInput else { return }
    guard let audioMicInput = self.audioMicInput else { return }
    guard let assetWriter = self.assetWriter else { return }
    guard let videoURL = self.videoURL else { return }

    videoInput.markAsFinished()
    audioMicInput.markAsFinished()
    assetWriter.finishWriting(completionHandler: {

        PHPhotoLibrary.shared().performChanges({
                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoUrl)
            }) { (saved, error) in
            
                if let error = error {
                    print("PHAssetChangeRequest 视频错误: \(error.localizedDescription)")
                    return
                }
            
                if saved {
                    // ... 显示成功消息
                }
            }
    })
}

RPScreenRecorder 代理从未被调用:

func screenRecorder(_ screenRecorder: RPScreenRecorder, didStopRecordingWith previewViewController: RPPreviewViewController?, error: Error?) {
    if let error = error {
        print(error.localizedDescription)
    }
}
英文:

When trying to record and save audio/video with ReplayKit I keep getting errors. I'm using

Xcode: Version 11.2.1
Swift 5
iOS 13
iPhone 7+ physical device

When I set the filePath I'm already using URL(fileURLWithPath: ). The file extension and AVFileType are both .mp4. I check to see if the file already exists in the FileManager and if so I remove it: do { try FileManager.default.removeItem(at: videoURL) }. I tried to change the path itself to "Library/Caches/" like in @florianSAP answer which didn't work.

Here the 3 errors:

// 1. from recording
if !self.assetWriter.startWriting() {
print("Can't write")
return
}
// 2. from recording
if self.assetWriter.status == AVAssetWriter.Status.failed {
print("StartCapture Error Occurred, Status = \(self.assetWriter.status.rawValue), \(self.assetWriter.error?.localizedDescription) \(self.assetWriter.error?.debugDescription)")
return
}
// 3. this one is when trying to save the url in the PHAssetChangeRequest.creationRequestForAssetFromVideo completionHandler
if let error = error {
print("PHAssetChangeRequest Video Error: \(error.localizedDescription)")
return
}
// 4. this isn't an error but inside the switch rpSampleBufferType { } statement "not a video sample" kept printing out

The error messages are:

> StartCapture Error Occurred, Status = 3, The operation could not be
> completed Optional(Error Domain=AVFoundationErrorDomain Code=-11800
> "The operation could not be completed"
> UserInfo={NSLocalizedFailureReason=An unknown error occurred (-17508),
> NSLocalizedDescription=The operation could not be completed,
> NSUnderlyingError=0x2833a93b0 {Error Domain=NSOSStatusErrorDomain
> Code=-17508 "(null)"}})
>
> PHAssetChangeRequest Video Error: The operation couldn’t be completed. (PHPhotosErrorDomain
> error -1.)

Where am I going to wrong at?

Start Recording

let recorder = RPScreenRecorder.shared()
var assetWriter: AVAssetWriter!
var videoURL: URL!
var videoInput: AVAssetWriterInput!
var audioMicInput: AVAssetWriterInput!
guard let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return }
videoURL = URL(fileURLWithPath: documentsPath.appending(UUID().uuidString + ".mp4"))
guard let videoURL = videoURL else { return }
do {
try FileManager.default.removeItem(at: videoURL)
} catch {}
do {
try assetWriter = AVAssetWriter(outputURL: videoURL, fileType: .mp4) // AVAssetWriter(url: videoURL, fileType: .mp4) didn't make a difference
} catch {}
let videoSettings: [String : Any] = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: view.bounds.width,
AVVideoHeightKey: view.bounds.height
]
videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
videoInput.expectsMediaDataInRealTime = true
if assetWriter.canAdd(videoInput) {
assetWriter.add(videoInput)
}
let audioSettings: [String:Any] = [AVFormatIDKey : kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey : 2,
AVSampleRateKey : 44100.0,
AVEncoderBitRateKey: 192000
]
audioMicInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)
audioMicInput.expectsMediaDataInRealTime = true
if assetWriter.canAdd(audioMicInput) {
assetWriter.add(audioMicInput)
}
guard recorder.isAvailable else { return }
recorder.startCapture(handler: { (cmSampleBuffer, rpSampleBufferType, err) in
if let err = err { return }
// I tried to check if this was ready and added the below code to it but it made no difference
// if CMSampleBufferDataIsReady(cmSampleBuffer) { ... the code below was put in here ... }
DispatchQueue.main.async {
switch rpSampleBufferType {
case .video:
if self.assetWriter.status == AVAssetWriter.Status.unknown {
if !self.assetWriter.startWriting() {
print("Can't write")
return
}
print("Starting writing")
self.assetWriter.startWriting()
self.assetWriter.startSession(atSourceTime:  CMSampleBufferGetPresentationTimeStamp(cmSampleBuffer))
}
if self.assetWriter.status == AVAssetWriter.Status.failed {
print("StartCapture Error Occurred, Status = \(self.assetWriter.status.rawValue), \(self.assetWriter.error?.localizedDescription) \(self.assetWriter.error?.debugDescription)")
return
}
if self.assetWriter.status == AVAssetWriter.Status.writing {
if self.videoInput.isReadyForMoreMediaData {
if self.videoInput.append(cmSampleBuffer) == false {
print("problem writing video")
}
}
}
case .audioMic:
if self.audioMicInput.isReadyForMoreMediaData {
print("audioMic data added")
self.audioMicInput.append(cmSampleBuffer)
}
default:
print("not a video sample")
}
}
}
}, completionHandler: { (error) in
if let error = error { return }
})

Stop Recording:

recorder.stopCapture { (error) in
if let error = error { return }
guard let videoInput = self.videoInput else { return }
guard let audioMicInput = self.audioMicInput else { return }
guard let assetWriter = self.assetWriter else { return }
guard let videoURL = videoURL else { return }
videoInput.markAsFinished()
audioMicInput.markAsFinished()
assetWriter.finishWriting(completionHandler: {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoUrl)
}) { (saved, error) in
if let error = error {
print("PHAssetChangeRequest Video Error: \(error.localizedDescription)")
return
}
if saved {
// ... show success message
}
}
})
}

RPScreenRecorder Delegate that never gets called:

func screenRecorder(_ screenRecorder: RPScreenRecorder, didStopRecordingWith previewViewController: RPPreviewViewController?, error: Error?) {
if let error = error {
print(error.localizedDescription)
}
}

答案1

得分: 1

以下是您要翻译的部分:

1 - 我解决了这个问题,方法如下:

  • 首先,我更改了videoURL的文件路径,从:

    // 旧的方式导致某种路径错误
    videoURL = URL(fileURLWithPath: documentsPath.appending(UUID().uuidString + ".mp4"))

    // 旧路径的示例。请看Documents后面以506...开头的一系列数字
    ///var/mobile/Containers/Data/Application/AAEF38A2-7AF1-4A32-A612-296B1584A764/Documents506D36BA-0C27-466A-A0BA-C197481F471A.mp4

 // 新的方式使路径正常工作
let dirPath = "\(documentsPath)/Videos_\(UUID().uuidString).mp4"
videoURL = URL(fileURLWithPath: dirPath)
// 新路径示例。Documents后面现在有一个正斜杠,单词Videos下划线,然后以506...开头的一系列数字
///var/mobile/Containers/Data/Application/AAEF38A2-7AF1-4A32-A612-296B1584A764/Documents/Videos_506D36BA-0C27-466A-A0BA-C197481F471A.mp4

2 - 我做的第二件事是更改了recorder.startCapture(handler: { (cmSampleBuffer, rpSampleBufferType, err)中的代码:

recorder.startCapture(handler: { (cmSampleBuffer, rpSampleBufferType, err) in

    if let err = err { return }
if CMSampleBufferDataIsReady(cmSampleBuffer) {
DispatchQueue.main.async {
switch rpSampleBufferType {
case .video:
print("writing sample....")
if self.assetWriter?.status == AVAssetWriter.Status.unknown {
print("Started writing")
self.assetWriter?.startWriting()
self.assetWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(cmSampleBuffer))
}
if self.assetWriter.status == AVAssetWriter.Status.failed {
print("StartCapture Error Occurred, Status = \(self.assetWriter.status.rawValue), \(self.assetWriter.error!.localizedDescription) \(self.assetWriter.error.debugDescription)")
return
}
if self.assetWriter.status == AVAssetWriter.Status.writing {
if self.videoInput.isReadyForMoreMediaData {
print("Writing a sample")
if self.videoInput.append(cmSampleBuffer) == false {
print("problem writing video")
}
}
}
case .audioMic:
if self.audioMicInput.isReadyForMoreMediaData {
print("audioMic data added")
self.audioMicInput.append(cmSampleBuffer)
}
default:
print("not a video sample")
}
}
}, completionHandler: { (error) in
if let error = error { return }
})

如果与实际问题无关,但音频未同步,则必须在viewDidLoad下添加以下代码。我从这里的评论部分获取了这个代码。

do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .videoRecording, options: [.defaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
} catch {
#if DEBUG
print("Setting category to AVAudioSessionCategoryPlayback failed.")
#endif
}

如果需要查找错误代码的含义,可以在这里查看https://www.osstatus.com。它帮助我找到了这个问题的11800,但没有找到17508

英文:

I was able to solve this by doing 2 things:

1- The first thing I did was I change the videoURL’s filePath from:

// Old Way that was causing some sort of path error
videoURL = URL(fileURLWithPath: documentsPath.appending(UUID().uuidString + ".mp4"))
// This is what the Old Path looked like. Look at the series of numbers beginning with 506... directly after Documents
///var/mobile/Containers/Data/Application/AAEF38A2-7AF1-4A32-A612-296B1584A764/Documents506D36BA-0C27-466A-A0BA-C197481F471A.mp4

to

// New Way that got the path to work
let dirPath = "\(documentsPath)/Videos_\(UUID().uuidString).mp4"
videoURL = URL(fileURLWithPath: dirPath)
// This is what the new path looks like. After Documents there is now a forward slash, the word Videos with an underscore, and then the series of numbers beginning with 506...
///var/mobile/Containers/Data/Application/AAEF38A2-7AF1-4A32-A612-296B1584A764/Documents/Videos_506D36BA-0C27-466A-A0BA-C197481F471A.mp4

2 -The second thing I did was change the code inside the recorder.startCapture(handler: { (cmSampleBuffer, rpSampleBufferType, err):

recorder.startCapture(handler: { (cmSampleBuffer, rpSampleBufferType, err) in
if let err = err { return }
if CMSampleBufferDataIsReady(cmSampleBuffer) {
DispatchQueue.main.async {
switch rpSampleBufferType {
case .video:
print("writing sample....")
if self.assetWriter?.status == AVAssetWriter.Status.unknown {
print("Started writing")
self.assetWriter?.startWriting()
self.assetWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(cmSampleBuffer))
}
if self.assetWriter.status == AVAssetWriter.Status.failed {
print("StartCapture Error Occurred, Status = \(self.assetWriter.status.rawValue), \(self.assetWriter.error!.localizedDescription) \(self.assetWriter.error.debugDescription)")
return
}
if self.assetWriter.status == AVAssetWriter.Status.writing {
if self.videoInput.isReadyForMoreMediaData {
print("Writing a sample")
if self.videoInput.append(cmSampleBuffer) == false {
print("problem writing video")
}
}
}
case .audioMic:
if self.audioMicInput.isReadyForMoreMediaData {
print("audioMic data added")
self.audioMicInput.append(cmSampleBuffer)
}
default:
print("not a video sample")
}
}
}, completionHandler: { (error) in
if let error = error { return }
})

This has nothing to do with the actual problem I ran into but if audio isn't syncing then you have to add this code below to viewDidLoad. I got it from the comments section here.

do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .videoRecording, options: [.defaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
} catch {
#if DEBUG
print("Setting category to AVAudioSessionCategoryPlayback failed.")
#endif
}

If you need to find the meaning of error codes you can look here https://www.osstatus.com. It helped me find 11800 for this problem but not 17508.

huangapple
  • 本文由 发表于 2020年1月6日 22:15:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/59613660.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定