ScreenCaptureKitでmacOSで画面録画
macOS 12.3からScreenCaptureKitが追加されました
これまでのQuartz Display Servicesはディスプレイ単位でしか画面をキャプチャできませんでした
しかし、このFrameworkを利用する事で、キャプチャしたいコンテンツの選択などキメ細かい制御をして画面をキャプチャする事ができるようになりました
ScreenCaptureKitで画面録画する
macOS12.4, Xcode13.4で試してみました
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)
}
}
}
プライバシーにアプリを登録
システム環境設定 > セキュリティとプライバシー > プライバシー > 画面収録 に作成したアプリを登録します
再度、アプリを起動すると画面をキャプチャできるようになっています
コード全文
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よりも性能が高いように感じます、とても良さそうです