macOS で macOS でデスクトップキャプチャ
- macOS のデスクトップを unity でキャプチャできるプラグインを作ってみました
unityでmacのデスクトップキャプチャできた! pic.twitter.com/x2AkZ6z6zF
— ふじき (@fzkqi) June 10, 2021
- リポジトリはこちらです
実装の仕組み
- macOS は AVCaptureScreenInput を使うことで、デスクトップをキャプチャする事が可能です
- Unity から直接 AVCaptureScreenInput にアクセスできないので、Swift で Native Plugin を実装します
- CMSampleBuffer を取得できるので、MTLTexture に変換します
- MTLTexture のポインタを Unity へ渡して、Texture2D.CreateExternalTexture を使って Texture2D を得ます
AVCaptureScreenInput
- macOSは、AVCaptureScreenInput を使うことで、デスクトップをキャプチャする事が可能です
- この記事と同じやり方で Native Plugin を実装します
- AVCaptureScreenInput は他の AVCaptureInput と同じように使えます
- AVCaptureVideoDataOutput を使って、キャプチャしたフレームを受け取ります
let captureSession = AVCaptureSession()
let displayID = CGDirectDisplayID(CGMainDisplayID())
let videoDataInput: AVCaptureScreenInput = AVCaptureScreenInput(displayID: displayID)!
captureSession.addInput(videoDataInput)
let videoDataOutput: AVCaptureVideoDataOutput = AVCaptureVideoDataOutput()
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA] as [String : Any]
videoDataOutput.setSampleBufferDelegate(self, queue: videoQueue)
videoDataOutput.alwaysDiscardsLateVideoFrames = true
captureSession.addOutput(videoDataOutput)
captureSession. startRunning()
CMSampleBuffer -> MTLTexture
- AVCaptureVideoDataOutputSampleBufferDelegate を実装します
- CVPixelBuffer を取得し、MTLTexture に変換します
extension DesktopCapture: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
let imageBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
var textureCache : CVMetalTextureCache! = nil
CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, mtlDevice, nil, &textureCache)
let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
var imageTexture: CVMetalTexture! = nil
_ = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache,
imageBuffer, nil, .bgra8Unorm_srgb,
width, height, 0, &imageTexture)
let texture: MTLTexture = CVMetalTextureGetTexture(imageTexture)!
}
MTLTexture -> Texture
- Unity から呼び出される mcDesktopCapture_getCurrentFrame 関数を実装します
- retain して、テクスチャのポインタを Unity に返します
@_cdecl("mcDesktopCapture_getCurrentFrame")
public func mcDesktopCapture_getCurrentFrame() -> FrameEntity {
let texturePtr = Unmanaged.passRetained(texture).toOpaque()
let e = FrameEntity(width: texture.width, height: texture.height, texturePtr: texturePtr)
return e
}
- FrameEntity を取得して、Texture2D.CreateExternalTexture を使って、Textur2D を作成します
- 作成した、Texture2D は通常の Texture 同様に利用可能です
FrameEntity frameEntity = mcDesktopCapture_getCurrentFrame();
Texture2D texture = Texture2D.CreateExternalTexture((int)frameEntity.width, (int)frameEntity.height, TextureFormat.ARGB32, false, false, frameEntity.texturePtr);
Renderer m_Renderer = GetComponent<Renderer>();
m_Renderer.material.SetTexture("_MainTex", texture);
おわりに
- macOS では、API が用意されているため、デスクトップキャプチャの仕組みを容易に実装できました
- macOS はアプリ単位で取得する方法が見つからず、画面全体をキャプチャすることにしました