7
2

More than 1 year has passed since last update.

ScreenCaptureKitでmacOSで画面録画

Last updated at Posted at 2022-07-24

ScreenCaptureKitでmacOSで画面録画

macOS 12.3からScreenCaptureKitが追加されました
これまでのQuartz Display Servicesはディスプレイ単位でしか画面をキャプチャできませんでした
しかし、このFrameworkを利用する事で、キャプチャしたいコンテンツの選択などキメ細かい制御をして画面をキャプチャする事ができるようになりました

ScreenCaptureKitで画面録画する

macOS12.4, Xcode13.4で試してみました

screen-capture.gif

SCStreamの作成

SCWindowのリストを取得し、お目当てのwindowを使って、SCContentFilterとSCStreamConfigurationを設定し、SCStreamを作成します

let windows: [SCWindow] = try await SCShareableContent.current.windows
guard let window = windows.first(where: { $0.owningApplication?.applicationName == "Google Chrome" }) else { return }
let filter = SCContentFilter(desktopIndependentWindow: window)
let configuration = SCStreamConfiguration()
configuration.width = Int(window.frame.width)
configuration.height = Int(window.frame.height)
configuration.showsCursor = true
let stream = SCStream(filter: filter, configuration: configuration, delegate: nil)

Captureの実行とCMSampleBufferの処理

StreamOutput を追加して、Captureを開始します
CMSampleBufferはNSImageに変換し、表示させます
CMSampleBufferからCVPixelBufferを取得し、CIImageとCGImageを経由させて、NSImageに変換します

class ContentViewModel {
    func start() async throws {
        /* ~~ 中略 ~~ */
        try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: DispatchQueue.main)
        try await stream.startCapture()
    }
}
extension ContentViewModel: SCStreamOutput {
    func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
        let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        let size = CGSize(width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))
        let ci = CIImage(cvPixelBuffer: pixelBuffer)
        let cg = CIContext().createCGImage(ci, from: .init(origin: .zero, size: size))!
        self.image = NSImage(cgImage: cg, size: size)
    }
}

NSImageの表示

SwiftUIのImageを使って表示しました

struct ContentView: View {
    @ObservedObject var viewModel = ContentViewModel()
    var body: some View {
        if let image = viewModel.image {
            Image(nsImage: image)
                .resizable()
                .scaledToFit()
        } else {
            Text("Hello, world!")
                .padding(120)
        }
    }
}

プライバシーにアプリを登録

システム環境設定 > セキュリティとプライバシー > プライバシー > 画面収録 に作成したアプリを登録します
再度、アプリを起動すると画面をキャプチャできるようになっています

スクリーンショット 2022-07-23 22.04.50.png

コード全文

import ScreenCaptureKit
import SwiftUI

struct ContentView: View {
    @ObservedObject var viewModel = ContentViewModel()
    var body: some View {
        if let image = viewModel.image {
            Image(nsImage: image)
                .resizable()
                .scaledToFit()
        } else {
            Text("Hello, world!")
                .padding(120)
        }
    }
}

class ContentViewModel: NSObject, ObservableObject {
    @Published var image: NSImage?
    var stream: SCStream!
    override init() {
        super.init()
        Task {
            do {
                try await setup()
            } catch let error {
                print("error: \(error)")
            }
        }
    }
    @MainActor func setup() async throws {
        let windows: [SCWindow] = try await SCShareableContent.current.windows
        guard let window = windows.first(where: { $0.owningApplication?.applicationName == "Google Chrome" }) else { return }
        let filter = SCContentFilter(desktopIndependentWindow: window)
        let configuration = SCStreamConfiguration()
        configuration.width = Int(window.frame.width)
        configuration.height = Int(window.frame.height)
        configuration.showsCursor = true
        stream = SCStream(filter: filter, configuration: configuration, delegate: nil)
        try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: DispatchQueue.main)
        try await stream.startCapture()
    }
}
extension ContentViewModel: SCStreamOutput {
    func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
        let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        let size = CGSize(width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))
        let ci = CIImage(cvPixelBuffer: pixelBuffer)
        let cg = CIContext().createCGImage(ci, from: .init(origin: .zero, size: size))!
        self.image = NSImage(cgImage: cg, size: size)
    }
}

まとめ

ScreenCaptureKitを使うと簡単に画面録画ができるようになっていました
filterやconfigurationで細かくキャプチャ内容を制御できる上に、Quartz Display Servicesよりも性能が高いように感じます、とても良さそうです

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2