Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
仮想カメラで切り開く拡張現実の世界
Search
satoshi0212
December 08, 2020
Programming
670
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
仮想カメラで切り開く拡張現実の世界
satoshi0212
December 08, 2020
More Decks by satoshi0212
See All by satoshi0212
macOSで自分のカメラを作ってみよう - Core Media IO Extensions
satoshi0212
3
1.7k
NDIとARKitを連動させた新しい映像表現
satoshi0212
3
1.3k
100日間AR表現を実装して見つけた面白い実装を全力解説
satoshi0212
5
2.3k
Working on mobile AR implementation, what I've implemented and beyond
satoshi0212
0
570
macOS仮想カメラ「テロップカム」 実装方法とその先
satoshi0212
5
4k
ARで悪の組織の会議を実現する
satoshi0212
0
630
クロマキー合成を使い透過動画をAR空間に表示する
satoshi0212
3
10k
ARKit Maniacs
satoshi0212
1
3.8k
ARで作る価値のある物についての考察
satoshi0212
3
710
Other Decks in Programming
See All in Programming
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
840
1年で人数1.5倍、PR数5.5倍増。 品質とアウトカムはどうなったか、 何が効いたか
ike002jp
0
110
【SRE NEXT 2026 Lunch Session】一人目専任SREの立ち上げを加速する ― AIと進めたオンボーディングで2分を0.04秒にした話
pkshadeck
PRO
0
2.2k
AI がコードを書く時代における新卒エンジニアの仕事風景 (2026) / New Graduate Engineers in the Era of AI Coding (2026)
sushichan044
0
210
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
270
Contextとはなにか
chiroruxx
1
390
symfony/aiとlaravel/boost
77web
0
120
20260623_Loop Engineeringで自分の分身の問い合わせBotを作る
ryugen04
0
200
共通化で考えるべきは、実装より公開する型だった
codeegg
0
190
使用 Meilisearch 建立新聞搜尋工具
johnroyer
0
120
フィードバックで育てるAI開発
kotaminato
1
110
関数型プログラミングのメリットって何だろう?
wanko_it
0
160
Featured
See All Featured
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
460
Why Our Code Smells
bkeepers
PRO
340
58k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1k
Designing for humans not robots
tammielis
254
26k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.5k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.9k
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.9k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
170
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
How GitHub (no longer) Works
holman
316
150k
The Spectacular Lies of Maps
axbom
PRO
1
850
Transcript
ԾΧϝϥͰΓ։֦͘ுݱ࣮ͷੈք 93,BJHJ ෦ஐ5XJUUFS!TINEFWFMPQ
ԾΧϝϥʹڵຯ͕͋ΔΤϯδχΞʹಧ͚ େܕελδΦͰͳ͘σεΫτοϓεϚʔτϑΥϯ ఆλʔήοτ
όʔνϟϧΧϝϥ࡞ΕΔʂָͦ͠͏ʂ ͱ͍͏ฏΛ։͍ͯ΄͍͠ʂ
࣮ํ๏ ݱ࣮֦ுͱͯ͠ͷར༻
ԾΧϝϥ 4OBQ$BNFSB NNINN
None
None
None
None
None
None
None
࣮ํ๏ ݱ࣮֦ுͱͯ͠ͷར༻
5XJUUFSͰ(8όʔνϟϧΧϝϥ࡞νϟϨϯδͰ࡞աఔπΠʔτ͍ͯ͠·͢
࡞ͬͨࡍͷۤ࿑ΛৼΓฦΓ
֎෦ϥΠϒϥϦͳ͠ɺ4XJGUͷΈͰ࣮Մೳ
ԾΧϝϥ࣮αϯϓϧ IUUQTHJUIVCDPNTBUPTIJ7JSUVBM$BNFSB4BNQMF
ߏཁૉ
ߏཁૉ ɹ$PSF.FEJB*0%"-1MVHJO ɹίϯτϩʔϥΞϓϦ
$PSF.FEJB*0%"- 1MVHJO ίϯτϩʔϥΞϓϦ /41BTUFCPBSE -JCSBSZ$PSF.FEJB*01MVH*OT%"- ԾΧϝϥͷ࣮ମ 6*Λ࣋ͨͳ͍1MVHJOʹ Λ͢ΞϓϦ
ϓϩδΣΫτ࡞ QMJTUใՃ ΤϯτϦʔϙΠϯτ࣮ ΠϯλʔϑΣΠε࣮ Χϝϥө૾ͷऔಘͱग़ྗ $PSF.FEJB*0%"-1MVHJO
ϓϩδΣΫτ࡞ QMJTUใՃ ΤϯτϦʔϙΠϯτ࣮ ΠϯλʔϑΣΠε࣮ Χϝϥө૾ͷऔಘͱग़ྗ $PSF.FEJB*0%"-1MVHJO
None
None
ϓϩδΣΫτ࡞ QMJTUใՃ ΤϯτϦʔϙΠϯτ࣮ ΠϯλʔϑΣΠε࣮ Χϝϥө૾Λग़ྗઃఆ $PSF.FEJB*0%"-1MVHJO
1MVHJOGBDUPSZJOUFSGBDFT 1MVHJOUZQFT ΛՃ
None
ࣗͰࢦఆ͢Δ*%ɻ66*%ͳͲઃఆ
ΤϯτϦʔϙΠϯτGVODUJPO໊
ԾΧϝϥ1MVH*Oݻఆ ઌड़ͷ66*%
ϓϩδΣΫτ࡞ QMJTUใՃ ΤϯτϦʔϙΠϯτ࣮ ΠϯλʔϑΣΠε࣮ Χϝϥө૾Λग़ྗઃఆ $PSF.FEJB*0%"-1MVHJO
import Foundation import CoreMediaIO @_cdecl("VirtualCameraSampleMain") func VirtualCameraSampleMain(allocator: CFAllocator, requestedTypeUUID: CFUUID)
-> CMIOHardwarePlugInRef { return pluginRef } Main.swift
import Foundation import CoreMediaIO @_cdecl("VirtualCameraSampleMain") func VirtualCameraSampleMain(allocator: CFAllocator, requestedTypeUUID: CFUUID)
-> CMIOHardwarePlugInRef { return pluginRef } Main.swift ΤϯτϦʔϙΠϯτ ΠϯλʔϑΣΠεͷࢀরΛฦ٫
ϓϩδΣΫτ࡞ QMJTUใՃ ΤϯτϦʔϙΠϯτ࣮ ΠϯλʔϑΣΠε࣮ Χϝϥө૾Λग़ྗઃఆ $PSF.FEJB*0%"-1MVHJO
private func createPluginInterface() -> CMIOHardwarePlugInInterface { return CMIOHardwarePlugInInterface( _reserved: nil,
QueryInterface: QueryInterface, AddRef: AddRef, Release: Release, Initialize: Initialize, InitializeWithObjectID: InitializeWithObjectID, Teardown: Teardown, ObjectShow: ObjectShow, ObjectHasProperty: ObjectHasProperty, ObjectIsPropertySettable: ObjectIsPropertySettable, ObjectGetPropertyDataSize: ObjectGetPropertyDataSize, ObjectGetPropertyData: ObjectGetPropertyData, ObjectSetPropertyData: ObjectSetPropertyData, DeviceSuspend: DeviceSuspend, DeviceResume: DeviceResume, DeviceStartStream: DeviceStartStream, DeviceStopStream: DeviceStopStream, DeviceProcessAVCCommand: DeviceProcessAVCCommand, DeviceProcessRS422Command: DeviceProcessRS422Command, StreamCopyBufferQueue: StreamCopyBufferQueue, StreamDeckPlay: StreamDeckPlay, StreamDeckStop: StreamDeckStop, StreamDeckJog: StreamDeckJog, StreamDeckCueTo: StreamDeckCueTo) } PluginInterface.swift (ൈਮ)
private func createPluginInterface() -> CMIOHardwarePlugInInterface { return CMIOHardwarePlugInInterface( _reserved: nil,
QueryInterface: QueryInterface, AddRef: AddRef, Release: Release, Initialize: Initialize, InitializeWithObjectID: InitializeWithObjectID, Teardown: Teardown, ObjectShow: ObjectShow, ObjectHasProperty: ObjectHasProperty, ObjectIsPropertySettable: ObjectIsPropertySettable, ObjectGetPropertyDataSize: ObjectGetPropertyDataSize, ObjectGetPropertyData: ObjectGetPropertyData, ObjectSetPropertyData: ObjectSetPropertyData, DeviceSuspend: DeviceSuspend, DeviceResume: DeviceResume, DeviceStartStream: DeviceStartStream, DeviceStopStream: DeviceStopStream, DeviceProcessAVCCommand: DeviceProcessAVCCommand, DeviceProcessRS422Command: DeviceProcessRS422Command, StreamCopyBufferQueue: StreamCopyBufferQueue, StreamDeckPlay: StreamDeckPlay, StreamDeckStop: StreamDeckStop, StreamDeckJog: StreamDeckJog, StreamDeckCueTo: StreamDeckCueTo) } PluginInterface.swift (ൈਮ) ΠϯλʔϑΣΠε͕ظ͢ΔͷΛฦ٫
PluginInterface.swift (ൈਮ) ৄࡉιʔεࢀর
ϓϩδΣΫτ࡞ QMJTUใՃ ΤϯτϦʔϙΠϯτ࣮ ΠϯλʔϑΣΠε࣮ Χϝϥө૾औಘͱग़ྗ $PSF.FEJB*0%"-1MVHJO
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, ɹɹɹɹɹɹɹɹɹɹɹ ɹɹfrom connection:
AVCaptureConnection) { if output == cameraCapture.output { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let cameraImage = CIImage(cvImageBuffer: imageBuffer) let compositedImage = compose(bgImage: cameraImage, overlayImage: self.textImage) var pixelBuffer: CVPixelBuffer? _ = CVPixelBufferCreate( kCFAllocatorDefault, Int(compositedImage.extent.size.width), Int(compositedImage.extent.height), kCVPixelFormatType_32BGRA, self.CVPixelBufferCreateOptions as CFDictionary, &pixelBuffer ) if let pixelBuffer = pixelBuffer { context.render(compositedImage, to: pixelBuffer) delegate?.videoComposer(self, didComposeImageBuffer: pixelBuffer) } } } VideoComposer.swift (ൈਮ)
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, ɹɹɹɹɹɹɹɹɹɹɹ ɹɹfrom connection:
AVCaptureConnection) { if output == cameraCapture.output { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let cameraImage = CIImage(cvImageBuffer: imageBuffer) let compositedImage = compose(bgImage: cameraImage, overlayImage: self.textImage) var pixelBuffer: CVPixelBuffer? _ = CVPixelBufferCreate( kCFAllocatorDefault, Int(compositedImage.extent.size.width), Int(compositedImage.extent.height), kCVPixelFormatType_32BGRA, self.CVPixelBufferCreateOptions as CFDictionary, &pixelBuffer ) if let pixelBuffer = pixelBuffer { context.render(compositedImage, to: pixelBuffer) delegate?.videoComposer(self, didComposeImageBuffer: pixelBuffer) } } } VideoComposer.swift (ൈਮ) ผ్ੜͨ͠ΦʔόʔϨΠςΩετը૾ͱ߹ EFMFHBUFܦ༝ͰQJYFM#V⒎FSΛ͢
private lazy var timer: DispatchSourceTimer = { let interval =
1.0 / Double(frameRate) let timer = DispatchSource.makeTimerSource() timer.schedule(deadline: .now() + interval, repeating: interval) timer.setEventHandler(handler: { [weak self] in self?.enqueueBuffer() }) return timer }() Stream.swift (ൈਮ)
private lazy var timer: DispatchSourceTimer = { let interval =
1.0 / Double(frameRate) let timer = DispatchSource.makeTimerSource() timer.schedule(deadline: .now() + interval, repeating: interval) timer.setEventHandler(handler: { [weak self] in self?.enqueueBuffer() }) return timer }() Stream.swift (ൈਮ) GSBNF3BUFࢦఆ͠ߋ৽ॲཧݺͼग़͠
private func enqueueBuffer() { (தུ) var sampleBufferUnmanaged: Unmanaged<CMSampleBuffer>? = nil
error = CMIOSampleBufferCreateForImageBuffer( kCFAllocatorDefault, pixelBuffer, formatDescription, &timing, sequenceNumber, UInt32(kCMIOSampleBufferNoDiscontinuities), &sampleBufferUnmanaged ) guard error == noErr else { log("CMIOSampleBufferCreateForImageBuffer Error: \(error)") return } CMSimpleQueueEnqueue(queue, element: sampleBufferUnmanaged!.toOpaque()) queueAlteredProc?(objectID, sampleBufferUnmanaged!.toOpaque(), queueAlteredRefCon) sequenceNumber += 1 } Stream.swift (ൈਮ)
private func enqueueBuffer() { (தུ) var sampleBufferUnmanaged: Unmanaged<CMSampleBuffer>? = nil
error = CMIOSampleBufferCreateForImageBuffer( kCFAllocatorDefault, pixelBuffer, formatDescription, &timing, sequenceNumber, UInt32(kCMIOSampleBufferNoDiscontinuities), &sampleBufferUnmanaged ) guard error == noErr else { log("CMIOSampleBufferCreateForImageBuffer Error: \(error)") return } CMSimpleQueueEnqueue(queue, element: sampleBufferUnmanaged!.toOpaque()) queueAlteredProc?(objectID, sampleBufferUnmanaged!.toOpaque(), queueAlteredRefCon) sequenceNumber += 1 } Stream.swift (ൈਮ) QJYFM#V⒎FS͔Βੜͨ͠ $.4BNQMF#V⒎FSΛRVFVFʹՃ
@IBAction func sendButton_action(_ sender: Any) { SettingsPasteboard.shared.settings["text1"] = mainTextField.stringValue SettingsPasteboard.shared.update()
} ViewController.swift (ൈਮ)
None
QMVHJOϑΝΠϧΛஔ
-JCSBSZ$PSF.FEJB*01MVH*OT%"-
දࣔ͞Εͨʂ
None
ߏཁૉ ɹ$PSF.FEJB*0%"-1MVHJO ɹίϯτϩʔϥΞϓϦ
ίϯτϩʔϥΞϓϦ
$PSF.FEJB*0%"- 1MVHJO ίϯτϩʔϥΞϓϦ /41BTUFCPBSE -JCSBSZ$PSF.FEJB*01MVH*OT%"- ԾΧϝϥͷ࣮ମ 6*Λ࣋ͨͳ͍1MVHJOʹ Λ͢ΞϓϦ
1BTUFCPBSEʹΛஔׂ͘
ϓϩδΣΫτ࡞ ίϯτϩʔϧஔ /41BTUFCPBSEͰσʔλڞ༗ ίϯτϩʔϥΞϓϦ
None
None
ϓϩδΣΫτ࡞ ίϯτϩʔϧஔ /41BTUFCPBSEͰσʔλڞ༗ ίϯτϩʔϥΞϓϦ
None
None
ϓϩδΣΫτ࡞ ίϯτϩʔϧஔ /41BTUFCPBSEͰσʔλڞ༗ ίϯτϩʔϥΞϓϦ
extension NSPasteboard.Name { static let main = NSPasteboard.Name(Config.mainAppBundleIdentifier) } extension
NSPasteboard.PasteboardType { static let plain = NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text") } class SettingsPasteboard { static let shared = SettingsPasteboard() open var settings = [String: Any]() open func current() -> [String: Any] { let pasteboard = NSPasteboard(name: .main) if let element = pasteboard.pasteboardItems?.last, let str = element.string(forType: .plain), let data = str.data(using: .utf8) { do { let json = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as! [String: Any] settings = json } catch { print("can't convert json") } } return settings } open func update() { let jsonStr = stringify(json: settings) let pasteboard = NSPasteboard(name: .main) pasteboard.declareTypes([.string], owner: nil) pasteboard.setString(jsonStr, forType: .string) } ... } SettingsPasteboard.swift (ൈਮ)
None
$PSF.FEJB*0%"- 1MVHJO ίϯτϩʔϥΞϓϦ /41BTUFCPBSE -JCSBSZ$PSF.FEJB*01MVH*OT%"- ԾΧϝϥͷ࣮ମ 6*Λ࣋ͨͳ͍1MVHJOʹ Λ͢ΞϓϦ
ίϯτϩʔϥΞϓϦ͔ΒΛૹ৴
None
None
None
࣮ํ๏ ݱ࣮֦ுͱͯ͠ͷར༻
'BDFUJNFΧϝϥө૾ Իೖྗ ը૾Ճ "1*Ϩεϙϯε ΩʔϘʔυೖྗ ,FZOPUFը໘ FUD ֎෦ ԾΧϝϥ ग़ྗՃػߏ
ݱ࣮֦ுͱͯ͠ͷԾΧϝϥ ֤छೖྗ
NNINNϏοάϋϯυϞʔυ
Ή͜͏ͷ͘ʹϑΟϧλɺϚΠϖʔδɺϝλόʔε
ϑΟϧλҟٞ͋Γɺͬͨ
ϑΟϧλΠϩϞωΞత
UPOBSJԕִϦΞϧλΠϜө૾
ϚΠϯυϚοϓ
None
δΣωϨΠςΟϒදݱ
ελϯϓϦΞΫγϣϯ
Իೝࣝͱ༁
None
None
ݱ࣮֦ுͱͯ͠ͷԾΧϝϥ
ࢀߟ IUUQTEFWFMPQFSBQQMFDPNMJCSBSZBSDIJWFTBNQMFDPEF$PSF.FEJB*0*OUSPEVDUJPO*OUSPIUNM IUUQTHJUIVCDPNKPIOCPJMFTDPSFNFEJBJPEBMNJOJNBMFYBNQMF IUUQTHJUIVCDPNTFBODIBT4JNQMF%"-1MVHJO IUUQTTQFBLFSEFDLDPNLJTIJLBXBLBUTVNJWJSUVBMXFCDBNFSBXP[VPSPV
5XJUUFSͰ࠷৽ใൃ৴த !TINEFWFMPQ ԾΧϝϥ࣮αϯϓϧ IUUQTHJUIVCDPNTBUPTIJ7JSUVBM$BNFSB4BNQMF