在 Swift 中使用实时滤镜录制视频

2024-01-16

我是 swift 的新手,试图构建一个相机应用程序,它可以应用实时滤镜,并使用应用的滤镜进行保存。

到目前为止,我可以使用应用的滤镜实时预览,但当我保存视频时,它全黑了。

import UIKit
import AVFoundation
import AssetsLibrary
import CoreMedia
import Photos

class ViewController: UIViewController , AVCaptureVideoDataOutputSampleBufferDelegate {

    var captureSession: AVCaptureSession!

    @IBOutlet weak var previewView: UIView!
    @IBOutlet weak var recordButtton: UIButton!
    @IBOutlet weak var imageView: UIImageView!

    var assetWriter: AVAssetWriter?
    var assetWriterPixelBufferInput: AVAssetWriterInputPixelBufferAdaptor?
    var isWriting = false
    var currentSampleTime: CMTime?
    var currentVideoDimensions: CMVideoDimensions?

    override func viewDidLoad() {
        super.viewDidLoad()
        FilterVendor.register()
        setupCaptureSession()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func setupCaptureSession() {
        let captureSession = AVCaptureSession()
        captureSession.sessionPreset = AVCaptureSessionPresetPhoto

        guard let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo), let input = try? AVCaptureDeviceInput(device: captureDevice) else {
            print("Can't access the camera")
            return
        }

        if captureSession.canAddInput(input) {
            captureSession.addInput(input)
        }

        let videoOutput = AVCaptureVideoDataOutput()

        videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
        if captureSession.canAddOutput(videoOutput) {
            captureSession.addOutput(videoOutput)
        }

        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        if((previewLayer) != nil) {
            view.layer.addSublayer(previewLayer!)
        }

        captureSession.startRunning()
    }

    @IBAction func record(_ sender: Any) {
        if isWriting {
            print("stop record")
            self.isWriting = false
            assetWriterPixelBufferInput = nil
            assetWriter?.finishWriting(completionHandler: {[unowned self] () -> Void in
                self.saveMovieToCameraRoll()
            })
        } else {
            print("start record")
            createWriter()
            assetWriter?.startWriting()
            assetWriter?.startSession(atSourceTime: currentSampleTime!)
            isWriting = true
        }
    }

    func saveMovieToCameraRoll() {
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: self.movieURL() as URL)
        }) { saved, error in
            if saved {
                print("saved")
            }
        }
    }

    func movieURL() -> NSURL {
        let tempDir = NSTemporaryDirectory()
        let url = NSURL(fileURLWithPath: tempDir).appendingPathComponent("tmpMov.mov")
        return url! as NSURL
    }

    func checkForAndDeleteFile() {
        let fm = FileManager.default
        let url = movieURL()
        let exist = fm.fileExists(atPath: url.path!)

        if exist {
            do {
                try fm.removeItem(at: url as URL)
            } catch let error as NSError {
                print(error.localizedDescription)
            }
        }
    }

    func createWriter() {
        self.checkForAndDeleteFile()

        do {
            assetWriter = try AVAssetWriter(outputURL: movieURL() as URL, fileType: AVFileTypeQuickTimeMovie)
        } catch let error as NSError {
            print(error.localizedDescription)
            return
        }

        let outputSettings = [
            AVVideoCodecKey : AVVideoCodecH264,
            AVVideoWidthKey : Int(currentVideoDimensions!.width),
            AVVideoHeightKey : Int(currentVideoDimensions!.height)
        ] as [String : Any]

        let assetWriterVideoInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: outputSettings as? [String : AnyObject])
        assetWriterVideoInput.expectsMediaDataInRealTime = true
        assetWriterVideoInput.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI / 2.0))

        let sourcePixelBufferAttributesDictionary = [
            String(kCVPixelBufferPixelFormatTypeKey) : Int(kCVPixelFormatType_32BGRA),
            String(kCVPixelBufferWidthKey) : Int(currentVideoDimensions!.width),
            String(kCVPixelBufferHeightKey) : Int(currentVideoDimensions!.height),
            String(kCVPixelFormatOpenGLESCompatibility) : kCFBooleanTrue
        ] as [String : Any]

        assetWriterPixelBufferInput = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterVideoInput,
                                                                           sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)

        if assetWriter!.canAdd(assetWriterVideoInput) {
            assetWriter!.add(assetWriterVideoInput)
        } else {
            print("no way\(assetWriterVideoInput)")
        }
    }

    func captureOutput(_ captureOutput: AVCaptureOutput, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection) {
        autoreleasepool {

            connection.videoOrientation = AVCaptureVideoOrientation.landscapeLeft;

            guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
            let cameraImage = CIImage(cvPixelBuffer: pixelBuffer)

            let filter = CIFilter(name: "Fİlter")!
            filter.setValue(cameraImage, forKey: kCIInputImageKey)


            let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)!
            self.currentVideoDimensions = CMVideoFormatDescriptionGetDimensions(formatDescription)
            self.currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer)

            if self.isWriting {
                if self.assetWriterPixelBufferInput?.assetWriterInput.isReadyForMoreMediaData == true {
                    var newPixelBuffer: CVPixelBuffer? = nil

                    CVPixelBufferPoolCreatePixelBuffer(nil, self.assetWriterPixelBufferInput!.pixelBufferPool!, &newPixelBuffer)

                    let success = self.assetWriterPixelBufferInput?.append(newPixelBuffer!, withPresentationTime: self.currentSampleTime!)

                    if success == false {
                        print("Pixel Buffer failed")
                    }
                }
            }

            DispatchQueue.main.async {

                if let outputValue = filter.value(forKey: kCIOutputImageKey) as? CIImage {
                    let filteredImage = UIImage(ciImage: outputValue)
                    self.imageView.image = filteredImage
                }
            }
        }
    }
}

我在下面的关键部分添加了一些评论:

func captureOutput(_ captureOutput: AVCaptureOutput, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection) {
    autoreleasepool {

        connection.videoOrientation = AVCaptureVideoOrientation.landscapeLeft;

        // COMMENT: This line makes sense - this is your pixelbuffer from the camera.
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

        // COMMENT: OK, so you turn pixelBuffer into a CIImage...
        let cameraImage = CIImage(cvPixelBuffer: pixelBuffer)

        // COMMENT: And now you've create a CIImage with a Filter instruction...
        let filter = CIFilter(name: "Fİlter")!
        filter.setValue(cameraImage, forKey: kCIInputImageKey)


        let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)!
        self.currentVideoDimensions = CMVideoFormatDescriptionGetDimensions(formatDescription)
        self.currentSampleTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer)

        if self.isWriting {
            if self.assetWriterPixelBufferInput?.assetWriterInput.isReadyForMoreMediaData == true {
                // COMMENT: Here's where it gets weird. You've declared a new, empty pixelBuffer... but you already have one (pixelBuffer) that contains the image you want to write...
                var newPixelBuffer: CVPixelBuffer? = nil

                // COMMENT: And you grabbed memory from the pool.
                CVPixelBufferPoolCreatePixelBuffer(nil, self.assetWriterPixelBufferInput!.pixelBufferPool!, &newPixelBuffer)

                // COMMENT: And now you wrote an empty pixelBuffer back <-- this is what's causing the black frame.
                let success = self.assetWriterPixelBufferInput?.append(newPixelBuffer!, withPresentationTime: self.currentSampleTime!)

                if success == false {
                    print("Pixel Buffer failed")
                }
            }
        }

        // COMMENT: And now you're sending the filtered image back to the screen.
        DispatchQueue.main.async {

            if let outputValue = filter.value(forKey: kCIOutputImageKey) as? CIImage {
                let filteredImage = UIImage(ciImage: outputValue)
                self.imageView.image = filteredImage
            }
        }
    }
}

在我看来,您基本上是在获取屏幕图像,创建过滤后的副本,然后创建一个空的新像素缓冲区并将其写出来。

如果您写入抓取的像素缓冲区而不是您正在创建的新像素缓冲区,则应该成功写入图像。

要成功写出过滤后的视频,您需要从 CIImage 创建一个新的 CVPixelBuffer - 该解决方案已经存在于 StackOverflow 上,我知道,因为我自己需要这一步!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Swift 中使用实时滤镜录制视频 的相关文章

随机推荐

  • 为 nextjs 默认服务器上的静态文件服务设置缓存控制标头

    我正在使用默认的 nextjs 服务器通过此命令运行我的 nextjs 程序next start 但是 我无法更改公共文件夹下文件的缓存控制标头 有没有什么方法可以在不设置自定义服务器的情况下设置缓存控制标头 有未记录的功能或错误 但它有效
  • 如何检查批处理脚本中的参数(或变量)是否为数字

    我需要检查传递给 Windows 批处理文件的参数是否为数值 如果检查也适用于变量 那就太好了 我找到了一个answer https superuser com a 404359到类似的question https superuser co
  • 找不到用于调试 .NET 源代码的 .cs 文件

    我尝试按照以下步骤设置调试 NET 源MDSN 演练 https msdn microsoft com en us library cc667410 aspx 符号缓存已正确设置 检查 启用 NET Framework 源步进 也是如此 但
  • 加入 Google Bigquery

    我知道正在开展工作来改进 Bigquery 上的联接功能 不是在这里咆哮 但如果不能正确使用联接 将很难分析 广告 的 太字节 数据集 好吧 回到问题 我有两个表 一个是 600 Megs 另一个是 50 Megs 我确实尝试进行连接 但出
  • 使用 cmake 和命令行构建 MSVC 项目

    再会 让我们有一个源文件main cpp and a CMakeLists txt包含下一个文本的文件 cmake minimum required VERSION 2 6 project tmp set CMAKE CXX FLAGS W
  • 准备好的语句中空 LIKE 的性能影响

    我设置了一个 GiSTpg trgm http www postgresql org docs current interactive pgtrgm html上的索引name的栏目files table 准备好的语句的简化查询如下所示 SE
  • 将对象传递给 NSThread 选择器

    当我创建 NSThread 时 我向它传递了一个我希望进程知道的数字 我可以理解如何设置数字 但我不知道如何从线程选择器方法中读取数字 以便我可以将其传递给计时器 你怎么做呢 void setthread 在这里将数字传递给选择器就好了 N
  • 如何在 ColdFusion 中获取计划任务列表和上次运行结果?

    我们正在尝试为我们的 cron 作业 CF Java SQLServer 等 构建一个仪表板 以便我们可以看到上次运行的时间 结果是什么以及计划下次运行的时间 有没有办法使用 CFAdmin API 或一些未记录的
  • 日期时间支持的格式化语言?

    DateTime 让您可以根据当前文化进行格式化 默认支持哪些文化 我想到的使用场景this Date Value ToString MMMM 如果文化设置为英语 美国 则将打印 January 但如果文化设置为法语 ca 则将打印 Jan
  • Blogger 著名模板 - 分页后修复或替换?

    Blogger Notable 模板的主页底部的帖子分页链接仅显示 更多帖子 链接 它缺少 以前的帖子 它还缺少传统的 主页 链接 更多帖子 链接正确地消失在帖子的最后一页上 我希望至少启用一个 以前的帖子 链接 这似乎是 Google 故
  • 没有 Storyboard 的 Segue

    我正在尝试制作一个在每个视图上都有一个主页按钮的应用程序 但我正在寻找一种在单击此按钮时导航到主屏幕的方法 而不需要为 StoryBoard 上的每个屏幕到主页创建一个 物理 链接屏幕 我尝试使用这段代码 IBAction func btn
  • 用于在 Internet Explorer 7 中打开多个页面的 Windows 控制台命令

    如何使用单个 DOS 命令在 Internet Explorer 7 中打开多个页面 批处理文件是执行此操作的唯一方法吗 Thanks 批处理文件将作为一种快速但肮脏的解决方案 echo off setlocal openurl set u
  • Jenkins Pipeline 有“多个候选版本”并且正在选择旧版本

    我配置了一个 Jenkins 多分支管道 它应该从远程 GIT 存储库获取源代码以进行构建 詹金斯似乎 随机 选择一个旧的提交来构建 并在构建日志文件中显示消息 多个候选修订 我的管道看起来像 checkout class GitSCM b
  • XML:如何将一个文件读入另一个文件

    我有一个文件 A xml 包含如下内容
  • 嵌入或引用关系

    我使用 mongodb 和 mongoid gem 我想得到一些建议 我有一个应用程序 其中用户has many市场与市场has many产品 我需要在属于用户的所有 或任何 市场中搜索特定价格范围内的产品 哪种关系更适合这种情况 嵌入关系
  • ActiveRecord :includes - 如何使用带有加载关联的地图?

    我有一个小型 Rails 应用程序 我正在尝试获取一些订单统计信息 所以我有一个管理模型和一个订单模型 具有一对多关联 class Admin lt ActiveRecord Base attr accessible name has ma
  • 多线程将对象引用传递给静态帮助器方法

    我只是 Java 的初学者 偶然发现了多线程应用程序 我知道这个问题与这里的一些帖子类似 但我找不到更好的答案来回答我的问题 基本上 我想将对象传递给静态方法 该方法将仅根据对象的值 属性返回输出 对于每次调用 我都会创建该对象的一个 新实
  • 视图控制器可以访问传入 Segue 的标识符吗?

    视图控制器可以访问用于转换到它的 Segue 的标识符吗 例如 我使用带有标识符 mySegue 的 Segue 从视图控制器 A 转换到视图控制器 B 无论如何 视图控制器 B 是否可以获取 segue 的标识符 我不相信有这样的财产 这
  • JAX-RS 中的 @Produces 注释

    我的服务方法产生其中之一MediaTypes它可能会产生pdf or excel文件或其他 Produces application pdf application vnd ms excel 我的问题 我的服务返回响应类型applicati
  • 在 Swift 中使用实时滤镜录制视频

    我是 swift 的新手 试图构建一个相机应用程序 它可以应用实时滤镜 并使用应用的滤镜进行保存 到目前为止 我可以使用应用的滤镜实时预览 但当我保存视频时 它全黑了 import UIKit import AVFoundation imp