在 Swift 中录制视频时如何添加叠加层?

How do you add an overlay while recording a video in Swift?

我正在尝试使用 AVFoundation 在 Swift 中录制并保存视频。这行得通。我还尝试向视频添加叠加层,例如包含日期的文本标签。

例如:保存的视频不仅是摄像头看到的,还有时间戳。

以下是我保存视频的方式:

func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
    saveVideo(toURL: movieURL!)
  }

  private func saveVideo(toURL url: URL) {
    PHPhotoLibrary.shared().performChanges({
      PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
    }) { (success, error) in
      if(success) {
        print("Video saved to Camera Roll.")
      } else {
        print("Video failed to save.")
      }
    }
  }

我有一个 movieOuputAVCaptureMovieFileOutput。我的预览层不包含任何子层。我尝试将时间戳标签的图层添加到 previewLayer,但这没有成功。

我试过Ray Wenderlich's example as well as this stack overflow question. Lastly, I also tried this tutorial,都没有用。

如何向相机胶卷中保存的视频中的视频添加叠加层?

我没有 Swift 可以利用 AVFoundation 的实际开发环境。因此,我无法为您提供任何示例代码。

要在录制时将元数据(日期、位置、时间戳、水印、帧速率等)添加为视频的叠加层,您必须逐帧实时处理视频源,录音时。很可能您必须将帧存储在缓冲区中并在实际记录它们之前对其进行处理。

现在说到元数据,有两种类型,静态的和动态的。对于水印等静态类型,应该很容易,因为所有帧都会得到相同的东西。

但是,对于时间戳或 GPS 位置等动态元数据类型,需要考虑一些事项。处理视频帧需要计算能力和时间。因此,取决于动态数据的类型以及您获取它们的方式,有时处理后的值可能不是正确的值。例如,如果您在 1:00:01 处得到一个帧,您将对其进行处理并为其添加时间戳。假设处理时间戳需要 2 秒。你得到的下一帧是在 1:00:02,但是你直到 1:00:03 才能处理它,因为处理前一帧用了 2 秒。因此,取决于您如何获得新帧的新时间戳,该时间戳值可能不是您想要的值。

对于处理动态元数据,您还应该考虑硬件延迟。例如,该软件应该为每一帧添加实时 GPS 位置数据,并且在开发或测试中没有任何滞后。然而,在现实生活中,用户在网络连接不良的地区使用该软件,并且他在获取 GPS 位置时 phone 滞后。他的一些滞后持续了长达 5 秒。在这种情况下你会怎么做?您是否为 GPS 定位设置了超时并使用了最后一个好的位置?你报告错误吗?当 GPS 数据可用(这可能会破坏实时记录)并使用昂贵的算法来尝试预测该帧的用户位置时,您是否会推迟处理该帧?

除了要考虑的那些之外,我这里还有一些我认为可能对您有所帮助的参考资料。我觉得 medium.com 的那个看起来不错。

https://medium.com/ios-os-x-development/ios-camera-frames-extraction-d2c0f80ed05a

Adding watermark to currently recording video and save with watermark

Render dynamic text onto CVPixelBufferRef while recording video

在没有更多信息的情况下,听起来您要求的是水印。 不是叠加。

水印是视频上的标记,将与视频一起保存。 叠加层通常显示为预览层上的子视图,不会与视频一起保存。

在这里查看:

func addWatermark(inputURL: URL, outputURL: URL, handler:@escaping (_ exportSession: AVAssetExportSession?)-> Void) {
    let mixComposition = AVMutableComposition()
    let asset = AVAsset(url: inputURL)
    let videoTrack = asset.tracks(withMediaType: AVMediaType.video)[0]
    let timerange = CMTimeRangeMake(kCMTimeZero, asset.duration)

        let compositionVideoTrack:AVMutableCompositionTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))!

    do {
        try compositionVideoTrack.insertTimeRange(timerange, of: videoTrack, at: kCMTimeZero)
        compositionVideoTrack.preferredTransform = videoTrack.preferredTransform
    } catch {
        print(error)
    }

    let watermarkFilter = CIFilter(name: "CISourceOverCompositing")!
    let watermarkImage = CIImage(image: UIImage(named: "waterMark")!)
    let videoComposition = AVVideoComposition(asset: asset) { (filteringRequest) in
        let source = filteringRequest.sourceImage.clampedToExtent()
        watermarkFilter.setValue(source, forKey: "inputBackgroundImage")
        let transform = CGAffineTransform(translationX: filteringRequest.sourceImage.extent.width - (watermarkImage?.extent.width)! - 2, y: 0)
        watermarkFilter.setValue(watermarkImage?.transformed(by: transform), forKey: "inputImage")
        filteringRequest.finish(with: watermarkFilter.outputImage!, context: nil)
    }

    guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset640x480) else {
        handler(nil)

        return
    }

    exportSession.outputURL = outputURL
    exportSession.outputFileType = AVFileType.mp4
    exportSession.shouldOptimizeForNetworkUse = true
    exportSession.videoComposition = videoComposition
    exportSession.exportAsynchronously { () -> Void in
        handler(exportSession)
    }
}

下面是调用函数的方法。

let outputURL = NSURL.fileURL(withPath: "TempPath")
let inputURL = NSURL.fileURL(withPath: "VideoWithWatermarkPath")
addWatermark(inputURL: inputURL, outputURL: outputURL, handler: { (exportSession) in
    guard let session = exportSession else {
        // Error 
        return
    }
    switch session.status {
        case .completed:
        guard NSData(contentsOf: outputURL) != nil else {
            // Error
            return
        }

        // Now you can find the video with the watermark in the location outputURL

        default:
        // Error
    }
})

让我知道此代码是否适合您。 它位于 swift 3 中,因此需要进行一些更改。 我目前正在我的一个应用程序上使用此代码。还没有更新到 swift 5