├── VirtualCameraSampleController
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── WindowController.swift
├── VirtualCameraSampleController.entitlements
├── AppDelegate.swift
├── Info.plist
├── ViewController.swift
└── Base.lproj
│ └── Main.storyboard
├── VirtualCameraSample
├── Log.swift
├── Main.swift
├── Plugin.swift
├── VirtualCameraSample-Bridging-Header.h
├── CameraCapture.swift
├── Info.plist
├── GPUDevice.swift
├── Object.swift
├── CVPixelBuffer+SimpleDALPlugin.swift
├── Device.swift
├── Property.swift
├── VideoComposer.swift
├── Stream.swift
└── PluginInterface.swift
├── Common
├── Config.swift
└── SettingsPasteboard.swift
├── VirtualCameraSample.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── VirtualCameraSample.xcscheme
│ │ └── VirtualCameraSampleController.xcscheme
└── project.pbxproj
├── README.md
├── LICENSE
└── .gitignore
/VirtualCameraSampleController/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/VirtualCameraSample/Log.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | func log(_ message: Any = "", function: String = #function) {
4 | NSLog("VirtualCameraSample DALPlugin: \(function): \(message)")
5 | }
6 |
--------------------------------------------------------------------------------
/Common/Config.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | class Config {
4 |
5 | static let mainAppBundleIdentifier = "tokyo.shmdevelopment.VirtualCameraSample"
6 |
7 | static let settingsFileName = "Settings.json"
8 | }
9 |
--------------------------------------------------------------------------------
/VirtualCameraSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/VirtualCameraSample/Main.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreMediaIO
3 |
4 | @_cdecl("VirtualCameraSampleMain")
5 | func VirtualCameraSampleMain(allocator: CFAllocator, requestedTypeUUID: CFUUID) -> CMIOHardwarePlugInRef {
6 | return pluginRef
7 | }
8 |
--------------------------------------------------------------------------------
/VirtualCameraSampleController/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/VirtualCameraSample/Plugin.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | class Plugin: Object {
4 | var objectID: CMIOObjectID = 0
5 | let name = "VirtualCameraSample"
6 |
7 | lazy var properties: [Int : Property] = [
8 | kCMIOObjectPropertyName: Property(name),
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/VirtualCameraSample/VirtualCameraSample-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import
6 | #import
7 | #import
8 |
--------------------------------------------------------------------------------
/VirtualCameraSampleController/WindowController.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Cocoa
3 |
4 | class WindowController: NSWindowController {
5 |
6 | }
7 |
8 | extension WindowController: NSWindowDelegate {
9 | func windowWillClose(_ notification: Notification) {
10 | NSApplication.shared.terminate(self)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/VirtualCameraSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/VirtualCameraSampleController/VirtualCameraSampleController.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/VirtualCameraSampleController/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 |
3 | @NSApplicationMain
4 | class AppDelegate: NSObject, NSApplicationDelegate {
5 |
6 | func applicationDidFinishLaunching(_ aNotification: Notification) {
7 | // Insert code here to initialize your application
8 | }
9 |
10 | func applicationWillTerminate(_ aNotification: Notification) {
11 | // Insert code here to tear down your application
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VirtualCameraSample
2 |
3 | ## Overview
4 |
5 | Minimum implementation of a virtual camera for macOS.
6 |
7 | ## How to use
8 |
9 | 1. Open the project in Xcode and select the "VirtualCameraSample" schema to build
10 |
11 | 2. Place the generated `VirtualCameraSample.plugin` in `/Library/CoreMediaIO/Plug-Ins/DAL/`.
12 |
13 | 3. Select the `VirtualCameraSampleController` schema in Xcode, build and execute it
14 |
15 | 4. Select `VirtualCameraSample` in Zoom, enter a string from the controller application, and Send
16 |
17 |
18 | ## 概要
19 |
20 | macOS用仮想カメラの最小実装です。
21 |
22 | ## 本リポジトリの使い方
23 |
24 | 1. Xcodeでプロジェクトを開き「VirtualCameraSample」スキーマを選択しビルド
25 |
26 | 2. 生成された `VirtualCameraSample.plugin`を`/Library/CoreMediaIO/Plug-Ins/DAL/`に配置
27 |
28 | 3. Xcodeで「VirtualCameraSampleController」スキーマを選択しビルドし実行
29 |
30 | 4. Zoomで`VirtualCameraSample`を選択し、コントローラアプリから文字列を入力しSend
31 |
32 | ## 参考リンク
33 |
34 | - [macOS仮想カメラ「テロップカム」実装方法とその先](https://note.com/shm/n/nd5343d2a589a)
35 |
--------------------------------------------------------------------------------
/VirtualCameraSampleController/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSMainStoryboardFile
26 | Main
27 | NSPrincipalClass
28 | NSApplication
29 |
30 |
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Hattori Satoshi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/VirtualCameraSample/CameraCapture.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import AVFoundation
3 |
4 | @objcMembers
5 | class CameraCapture: NSObject {
6 |
7 | private let session = AVCaptureSession()
8 | let output = AVCaptureVideoDataOutput()
9 |
10 | override init() {
11 | session.sessionPreset = .high
12 |
13 | if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) {
14 | if (device as AnyObject).description!.contains("FaceTime") {
15 | do {
16 | let input = try AVCaptureDeviceInput(device: device)
17 | if session.canAddInput(input) {
18 | session.addInput(input)
19 | if session.canAddOutput(output) {
20 | session.addOutput(output)
21 | }
22 | }
23 | } catch {
24 | print(error)
25 | }
26 | }
27 | }
28 | }
29 |
30 | func startRunning() {
31 | session.startRunning()
32 | }
33 |
34 | func stopRunning() {
35 | session.stopRunning()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/VirtualCameraSample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | NSPrincipalClass
22 |
23 | CFPlugInFactories
24 |
25 | 075F6ABA-7D00-4E66-A107-B56430DC5B81
26 | VirtualCameraSampleMain
27 |
28 | CFPlugInTypes
29 |
30 | 30010C1C-93BF-11D8-8B5B-000A95AF9C6A
31 | 075F6ABA-7D00-4E66-A107-B56430DC5B81
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/VirtualCameraSampleController/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/VirtualCameraSample/GPUDevice.swift:
--------------------------------------------------------------------------------
1 | //import Foundation
2 | import Metal
3 | import simd
4 |
5 | class GPUDevice {
6 | static let shared = GPUDevice()
7 |
8 | let device = MTLCreateSystemDefaultDevice()!
9 | var library : MTLLibrary!
10 | lazy var vertexFunction : MTLFunction = library.makeFunction(name: "vertexShaderX")!
11 |
12 | var resolutionBuffer : MTLBuffer! = nil
13 | var timeBuffer : MTLBuffer! = nil
14 |
15 | private init() {
16 | library = device.makeDefaultLibrary()
17 |
18 | setUpBeffers()
19 | }
20 |
21 | func setUpBeffers() {
22 | resolutionBuffer = device.makeBuffer(length: 2 * MemoryLayout.size, options: [])
23 | timeBuffer = device.makeBuffer(length: MemoryLayout.size, options: [])
24 | }
25 |
26 | func updateResolution(width: Float, height: Float) {
27 | memcpy(resolutionBuffer.contents(), [width, height], MemoryLayout.size * 2)
28 | }
29 |
30 | func updateTime(_ time: Float) {
31 | updateBuffer(time, timeBuffer)
32 | }
33 |
34 | func render() {
35 |
36 | }
37 |
38 | private func updateBuffer(_ data:T, _ buffer: MTLBuffer) {
39 | let pointer = buffer.contents()
40 | let value = pointer.bindMemory(to: T.self, capacity: 1)
41 | value[0] = data
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/VirtualCameraSampleController/ViewController.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 |
3 | class ViewController: NSViewController {
4 |
5 | @IBOutlet weak var mainTextField: NSTextField!
6 |
7 | deinit {
8 | let panel = NSFontManager.shared.fontPanel(true)
9 | panel?.close()
10 | }
11 |
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | mainTextField.delegate = self
15 | }
16 |
17 | override func viewDidAppear() {
18 | mainTextField.window?.makeFirstResponder(mainTextField)
19 | let current = SettingsPasteboard.shared.current()
20 | mainTextField.stringValue = current["text1"] as? String ?? ""
21 | }
22 |
23 | @IBAction func sendButton_action(_ sender: Any) {
24 | SettingsPasteboard.shared.settings["text1"] = mainTextField.stringValue
25 | SettingsPasteboard.shared.update()
26 | }
27 |
28 | @IBAction func clearButton_action(_ sender: Any) {
29 | SettingsPasteboard.shared.settings["text1"] = ""
30 | SettingsPasteboard.shared.update()
31 | }
32 | }
33 |
34 | extension ViewController: NSTextFieldDelegate {
35 |
36 | func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
37 | if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
38 | SettingsPasteboard.shared.settings["text1"] = mainTextField.stringValue
39 | SettingsPasteboard.shared.update()
40 | return true
41 | }
42 | return false
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/VirtualCameraSample/Object.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | protocol Object: class {
4 | var objectID: CMIOObjectID { get }
5 | var properties: [Int: Property] { get }
6 | }
7 |
8 | extension Object {
9 | func hasProperty(address: CMIOObjectPropertyAddress) -> Bool {
10 | return properties[Int(address.mSelector)] != nil
11 | }
12 |
13 | func isPropertySettable(address: CMIOObjectPropertyAddress) -> Bool {
14 | guard let property = properties[Int(address.mSelector)] else {
15 | return false
16 | }
17 | return property.isSettable
18 | }
19 |
20 | func getPropertyDataSize(address: CMIOObjectPropertyAddress) -> UInt32 {
21 | guard let property = properties[Int(address.mSelector)] else {
22 | return 0
23 | }
24 | return property.dataSize
25 | }
26 |
27 | func getPropertyData(address: CMIOObjectPropertyAddress, dataSize: inout UInt32, data: UnsafeMutableRawPointer) {
28 | guard let property = properties[Int(address.mSelector)] else {
29 | return
30 | }
31 | dataSize = property.dataSize
32 | property.getData(data: data)
33 | }
34 |
35 | func setPropertyData(address: CMIOObjectPropertyAddress, data: UnsafeRawPointer) {
36 | guard let property = properties[Int(address.mSelector)] else {
37 | return
38 | }
39 | property.setData(data: data)
40 | }
41 | }
42 |
43 | var objects = [CMIOObjectID: Object]()
44 |
45 | func addObject(object: Object) {
46 | objects[object.objectID] = object
47 | }
48 |
--------------------------------------------------------------------------------
/VirtualCameraSample/CVPixelBuffer+SimpleDALPlugin.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AVFoundation
3 |
4 | extension CVPixelBuffer {
5 | static func create(size: CGSize) -> CVPixelBuffer? {
6 | var pixelBuffer: CVPixelBuffer?
7 | let options = [
8 | kCVPixelBufferCGImageCompatibilityKey as String: true,
9 | kCVPixelBufferCGBitmapContextCompatibilityKey as String: true,
10 | ] as [String: Any]
11 |
12 | let error = CVPixelBufferCreate(
13 | kCFAllocatorSystemDefault,
14 | Int(size.width),
15 | Int(size.height),
16 | kCVPixelFormatType_32ARGB,
17 | options as CFDictionary,
18 | &pixelBuffer)
19 |
20 | guard error == noErr else {
21 | log("CVPixelBufferCreate error: \(error)")
22 | return nil
23 | }
24 | return pixelBuffer
25 | }
26 |
27 | var width: Int {
28 | return CVPixelBufferGetWidth(self)
29 | }
30 | var height: Int {
31 | return CVPixelBufferGetHeight(self)
32 | }
33 |
34 | func modifyWithContext(callback: (CGContext) -> Void) {
35 | CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
36 | let data = CVPixelBufferGetBaseAddress(self)
37 |
38 | let colorSpace = CGColorSpaceCreateDeviceRGB();
39 | let bytesPerRow = CVPixelBufferGetBytesPerRow(self)
40 |
41 | let context = CGContext(
42 | data: data,
43 | width: width, height: height, bitsPerComponent: 8,
44 | bytesPerRow: bytesPerRow,
45 | space: colorSpace,
46 | bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)
47 |
48 | if let context = context {
49 | callback(context)
50 | }
51 |
52 | CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/VirtualCameraSample/Device.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import IOKit
3 |
4 | class Device: Object {
5 | var objectID: CMIOObjectID = 0
6 | var streamID: CMIOStreamID = 0
7 | let name = "VirtualCameraSample"
8 | let manufacturer = "satoshi0212"
9 | let deviceUID = "VirtualCameraSample Device"
10 | let modelUID = "VirtualCameraSample Model"
11 | var excludeNonDALAccess: Bool = false
12 | var deviceMaster: Int32 = -1
13 |
14 | lazy var properties: [Int : Property] = [
15 | kCMIOObjectPropertyName: Property(name),
16 | kCMIOObjectPropertyManufacturer: Property(manufacturer),
17 | kCMIODevicePropertyDeviceUID: Property(deviceUID),
18 | kCMIODevicePropertyModelUID: Property(modelUID),
19 | kCMIODevicePropertyTransportType: Property(UInt32(kIOAudioDeviceTransportTypeBuiltIn)),
20 | kCMIODevicePropertyDeviceIsAlive: Property(UInt32(1)),
21 | kCMIODevicePropertyDeviceIsRunning: Property(UInt32(1)),
22 | kCMIODevicePropertyDeviceIsRunningSomewhere: Property(UInt32(1)),
23 | kCMIODevicePropertyDeviceCanBeDefaultDevice: Property(UInt32(1)),
24 | kCMIODevicePropertyCanProcessAVCCommand: Property(UInt32(0)),
25 | kCMIODevicePropertyCanProcessRS422Command: Property(UInt32(0)),
26 | kCMIODevicePropertyHogMode: Property(Int32(-1)),
27 | kCMIODevicePropertyStreams: Property { [unowned self] in self.streamID },
28 | kCMIODevicePropertyExcludeNonDALAccess: Property(
29 | getter: { [unowned self] () -> UInt32 in self.excludeNonDALAccess ? 1 : 0 },
30 | setter: { [unowned self] (value: UInt32) -> Void in self.excludeNonDALAccess = value != 0 }
31 | ),
32 | kCMIODevicePropertyDeviceMaster: Property(
33 | getter: { [unowned self] () -> Int32 in self.deviceMaster },
34 | setter: { [unowned self] (value: Int32) -> Void in self.deviceMaster = value }
35 | ),
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/VirtualCameraSample.xcodeproj/xcshareddata/xcschemes/VirtualCameraSample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Common/SettingsPasteboard.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Cocoa
3 |
4 | extension NSPasteboard.Name {
5 | static let main = NSPasteboard.Name(Config.mainAppBundleIdentifier)
6 | }
7 |
8 | extension NSPasteboard.PasteboardType {
9 | static let plain = NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text")
10 | }
11 |
12 | class SettingsPasteboard {
13 | static let shared = SettingsPasteboard()
14 | open var settings = [String: Any]()
15 |
16 | init() {
17 | importFile()
18 | }
19 |
20 | open func current() -> [String: Any] {
21 | let pasteboard = NSPasteboard(name: .main)
22 | if let element = pasteboard.pasteboardItems?.last, let str = element.string(forType: .plain), let data = str.data(using: .utf8) {
23 | do {
24 | let json = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as! [String: Any]
25 | settings = json
26 | } catch {
27 | print("can't convert json")
28 | }
29 | }
30 | return settings
31 | }
32 |
33 | open func update() {
34 | let jsonStr = stringify(json: settings)
35 | let pasteboard = NSPasteboard(name: .main)
36 | pasteboard.declareTypes([.string], owner: nil)
37 | pasteboard.setString(jsonStr, forType: .string)
38 |
39 | exportFile()
40 | }
41 |
42 | private func importFile() {
43 | if let jsonObject = try? JSONSerialization.jsonObject(with: Data(contentsOf: getPath()), options: []) as? [String: Any] {
44 | settings = jsonObject
45 | }
46 | }
47 |
48 | private func exportFile() {
49 | let path = getPath()
50 | let fileCoordinator = NSFileCoordinator()
51 | fileCoordinator.coordinate(writingItemAt: path, options: [], error: nil) { (URL) in
52 | if let jsonData = try? JSONSerialization.data(withJSONObject: self.settings, options: []) {
53 | try? JSONEncoder().encode(jsonData).write(to: path)
54 | }
55 | }
56 | }
57 |
58 | private func getPath() -> URL {
59 | let documentPath = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
60 | return documentPath.appendingPathComponent(Config.settingsFileName)
61 | }
62 |
63 | private func stringify(json: Any, prettyPrinted: Bool = false) -> String {
64 | var options: JSONSerialization.WritingOptions = []
65 | if prettyPrinted {
66 | options = JSONSerialization.WritingOptions.prettyPrinted
67 | }
68 |
69 | do {
70 | let data = try JSONSerialization.data(withJSONObject: json, options: options)
71 | if let string = String(data: data, encoding: .utf8) {
72 | return string
73 | }
74 | } catch {
75 | print(error)
76 | }
77 |
78 | return ""
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/VirtualCameraSample.xcodeproj/xcshareddata/xcschemes/VirtualCameraSampleController.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/VirtualCameraSample/Property.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | protocol PropertyValue {
4 | var dataSize: UInt32 { get }
5 | func toData(data: UnsafeMutableRawPointer)
6 | static func fromData(data: UnsafeRawPointer) -> Self
7 | }
8 |
9 | extension String: PropertyValue {
10 | var dataSize: UInt32 {
11 | return UInt32(MemoryLayout.size)
12 | }
13 | func toData(data: UnsafeMutableRawPointer) {
14 | let cfString = self as CFString
15 | let unmanagedCFString = Unmanaged.passRetained(cfString)
16 | UnsafeMutablePointer>(OpaquePointer(data)).pointee = unmanagedCFString
17 | }
18 | static func fromData(data: UnsafeRawPointer) -> Self {
19 | fatalError("not implemented")
20 | }
21 | }
22 |
23 | extension CMFormatDescription: PropertyValue {
24 | var dataSize: UInt32 {
25 | return UInt32(MemoryLayout.size)
26 | }
27 | func toData(data: UnsafeMutableRawPointer) {
28 | let unmanaged = Unmanaged.passRetained(self as! Self)
29 | UnsafeMutablePointer>(OpaquePointer(data)).pointee = unmanaged
30 | }
31 | static func fromData(data: UnsafeRawPointer) -> Self {
32 | fatalError("not implemented")
33 | }
34 | }
35 |
36 | extension CFArray: PropertyValue {
37 | var dataSize: UInt32 {
38 | return UInt32(MemoryLayout.size)
39 | }
40 | func toData(data: UnsafeMutableRawPointer) {
41 | let unmanaged = Unmanaged.passRetained(self as! Self)
42 | UnsafeMutablePointer>(OpaquePointer(data)).pointee = unmanaged
43 | }
44 | static func fromData(data: UnsafeRawPointer) -> Self {
45 | fatalError("not implemented")
46 | }
47 | }
48 |
49 | struct CFTypeRefWrapper {
50 | let ref: CFTypeRef
51 | }
52 |
53 | extension CFTypeRefWrapper: PropertyValue {
54 | var dataSize: UInt32 {
55 | return UInt32(MemoryLayout.size)
56 | }
57 | func toData(data: UnsafeMutableRawPointer) {
58 | let unmanaged = Unmanaged.passRetained(ref)
59 | UnsafeMutablePointer>(OpaquePointer(data)).pointee = unmanaged
60 | }
61 | static func fromData(data: UnsafeRawPointer) -> Self {
62 | fatalError("not implemented")
63 | }
64 | }
65 |
66 | extension UInt32: PropertyValue {
67 | var dataSize: UInt32 {
68 | return UInt32(MemoryLayout.size)
69 | }
70 | func toData(data: UnsafeMutableRawPointer) {
71 | UnsafeMutablePointer(OpaquePointer(data)).pointee = self
72 | }
73 | static func fromData(data: UnsafeRawPointer) -> Self {
74 | return UnsafePointer(OpaquePointer(data)).pointee
75 | }
76 | }
77 |
78 | extension Int32: PropertyValue {
79 | var dataSize: UInt32 {
80 | return UInt32(MemoryLayout.size)
81 | }
82 | func toData(data: UnsafeMutableRawPointer) {
83 | UnsafeMutablePointer(OpaquePointer(data)).pointee = self
84 | }
85 | static func fromData(data: UnsafeRawPointer) -> Self {
86 | return UnsafePointer(OpaquePointer(data)).pointee
87 | }
88 | }
89 |
90 | extension Float64: PropertyValue {
91 | var dataSize: UInt32 {
92 | return UInt32(MemoryLayout.size)
93 | }
94 | func toData(data: UnsafeMutableRawPointer) {
95 | UnsafeMutablePointer(OpaquePointer(data)).pointee = self
96 | }
97 | static func fromData(data: UnsafeRawPointer) -> Self {
98 | return UnsafePointer(OpaquePointer(data)).pointee
99 | }
100 | }
101 |
102 | extension AudioValueRange: PropertyValue {
103 | var dataSize: UInt32 {
104 | return UInt32(MemoryLayout.size)
105 | }
106 | func toData(data: UnsafeMutableRawPointer) {
107 | UnsafeMutablePointer(OpaquePointer(data)).pointee = self
108 | }
109 | static func fromData(data: UnsafeRawPointer) -> Self {
110 | return UnsafePointer(OpaquePointer(data)).pointee
111 | }
112 | }
113 |
114 | class Property {
115 | let getter: () -> PropertyValue
116 | let setter: ((UnsafeRawPointer) -> Void)?
117 |
118 | var isSettable: Bool {
119 | return setter != nil
120 | }
121 |
122 | var dataSize: UInt32 {
123 | getter().dataSize
124 | }
125 |
126 | convenience init(_ value: Element) {
127 | self.init(getter: { value })
128 | }
129 |
130 | convenience init(getter: @escaping () -> Element) {
131 | self.init(getter: getter, setter: nil)
132 | }
133 |
134 | init(getter: @escaping () -> Element, setter: ((Element) -> Void)?) {
135 | self.getter = getter
136 | self.setter = (setter != nil) ? { data in setter?(Element.fromData(data: data)) } : nil
137 | }
138 |
139 | func getData(data: UnsafeMutableRawPointer) {
140 | let value = getter()
141 | value.toData(data: data)
142 | }
143 |
144 | func setData(data: UnsafeRawPointer) {
145 | setter?(data)
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/VirtualCameraSample/VideoComposer.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import AVFoundation
3 |
4 | @objc
5 | protocol VideoComposerDelegate: class {
6 | func videoComposer(_ composer: VideoComposer, didComposeImageBuffer imageBuffer: CVImageBuffer)
7 | }
8 |
9 | @objcMembers
10 | class VideoComposer: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
11 |
12 | weak var delegate: VideoComposerDelegate?
13 |
14 | private let cameraCapture = CameraCapture()
15 | private let context = CIContext()
16 | private var settingsTimer: Timer?
17 |
18 | private let filter = CIFilter(name: "CISourceOverCompositing")
19 | private var textImage: CIImage?
20 |
21 | private let CVPixelBufferCreateOptions: [String: Any] = [
22 | kCVPixelBufferCGImageCompatibilityKey as String: true,
23 | kCVPixelBufferCGBitmapContextCompatibilityKey as String: true,
24 | kCVPixelBufferIOSurfacePropertiesKey as String: [:]
25 | ]
26 |
27 | deinit {
28 | stopRunning()
29 | }
30 |
31 | func startRunning() {
32 | startPollingSettings()
33 | cameraCapture.output.setSampleBufferDelegate(self, queue: .main)
34 | cameraCapture.startRunning()
35 | }
36 |
37 | func stopRunning() {
38 | settingsTimer?.invalidate()
39 | settingsTimer = nil
40 | cameraCapture.stopRunning()
41 | }
42 |
43 | private func startPollingSettings() {
44 | settingsTimer?.invalidate()
45 | settingsTimer = nil
46 | settingsTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
47 | let settings = SettingsPasteboard.shared.current()
48 | let text1 = settings["text1"] as? String ?? "no value"
49 | self.textImage = self.makeTextCIImage(text: text1)
50 | }
51 | settingsTimer?.fire()
52 | }
53 |
54 | func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
55 | if output == cameraCapture.output {
56 |
57 | guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
58 |
59 | let cameraImage = CIImage(cvImageBuffer: imageBuffer)
60 | let compositedImage = compose(bgImage: cameraImage, overlayImage: self.textImage)
61 |
62 | var pixelBuffer: CVPixelBuffer?
63 |
64 | _ = CVPixelBufferCreate(
65 | kCFAllocatorDefault,
66 | Int(compositedImage.extent.size.width),
67 | Int(compositedImage.extent.height),
68 | kCVPixelFormatType_32BGRA,
69 | self.CVPixelBufferCreateOptions as CFDictionary,
70 | &pixelBuffer
71 | )
72 |
73 | if let pixelBuffer = pixelBuffer {
74 | context.render(compositedImage, to: pixelBuffer)
75 | delegate?.videoComposer(self, didComposeImageBuffer: pixelBuffer)
76 | }
77 | }
78 | }
79 |
80 | private func makeTextCIImage(text: String) -> CIImage? {
81 | let font = NSFont(name: "HiraginoSans-W8", size: 100) ?? NSFont.systemFont(ofSize: 100)
82 | let size = NSSize(width: 1280.0, height: 720)
83 |
84 | let image = NSImage(size: size, flipped: false) { (rect) -> Bool in
85 | let paragraphStyle = NSMutableParagraphStyle()
86 | paragraphStyle.alignment = .center
87 |
88 | let rectangle = NSRect(x: 0, y: 40, width: size.width, height: font.lineHeight() + 12.0)
89 | let textAttributes = [
90 | .strokeColor: NSColor.black,
91 | .foregroundColor: NSColor.white,
92 | .strokeWidth: -2,
93 | .font: font,
94 | .paragraphStyle: paragraphStyle
95 | ] as [NSAttributedString.Key : Any]
96 | (text as NSString).draw(in: rectangle, withAttributes: textAttributes)
97 | return true
98 | }
99 |
100 | return image.ciImage
101 | }
102 |
103 | private func compose(bgImage: CIImage, overlayImage: CIImage?) -> CIImage {
104 | guard let filter = filter, let overlayImage = overlayImage else {
105 | return bgImage
106 | }
107 | filter.setValue(overlayImage, forKeyPath: kCIInputImageKey)
108 | filter.setValue(bgImage, forKeyPath: kCIInputBackgroundImageKey)
109 | return filter.outputImage!
110 | }
111 |
112 | }
113 |
114 | extension NSFont {
115 |
116 | func lineHeight() -> CGFloat {
117 | return CGFloat(ceilf(Float(ascender + descender + leading)))
118 | }
119 | }
120 |
121 | extension NSImage {
122 |
123 | convenience init(color: NSColor, size: NSSize) {
124 | self.init(size: size)
125 | lockFocus()
126 | color.drawSwatch(in: NSRect(origin: .zero, size: size))
127 | unlockFocus()
128 | }
129 |
130 | func resized(to newSize: NSSize) -> NSImage? {
131 | if let bitmapRep = NSBitmapImageRep(
132 | bitmapDataPlanes: nil, pixelsWide: Int(newSize.width), pixelsHigh: Int(newSize.height),
133 | bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
134 | colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0
135 | ) {
136 | bitmapRep.size = newSize
137 | NSGraphicsContext.saveGraphicsState()
138 | NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep)
139 | draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: .zero, operation: .copy, fraction: 1.0)
140 | NSGraphicsContext.restoreGraphicsState()
141 |
142 | let resizedImage = NSImage(size: newSize)
143 | resizedImage.addRepresentation(bitmapRep)
144 | return resizedImage
145 | }
146 |
147 | return nil
148 | }
149 |
150 | var ciImage: CIImage? {
151 | let newImage = self.resized(to: size)!
152 | guard let data = newImage.tiffRepresentation, let bitmap = NSBitmapImageRep(data: data) else { return nil }
153 | return CIImage(bitmapImageRep: bitmap)
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/VirtualCameraSample/Stream.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | class Stream: Object {
4 | var objectID: CMIOObjectID = 0
5 | let name = "VirtualCameraSample"
6 | let width = 1280
7 | let height = 720
8 | let frameRate = 30
9 |
10 | private var sequenceNumber: UInt64 = 0
11 | private var queueAlteredProc: CMIODeviceStreamQueueAlteredProc?
12 | private var queueAlteredRefCon: UnsafeMutableRawPointer?
13 |
14 | private var targetImageBuffer: CVImageBuffer?
15 |
16 | private lazy var videoComposer: VideoComposer? = {
17 | let obj = VideoComposer()
18 | obj.delegate = self
19 | return obj
20 | }()
21 |
22 | private lazy var formatDescription: CMVideoFormatDescription? = {
23 | var formatDescription: CMVideoFormatDescription?
24 | let error = CMVideoFormatDescriptionCreate(
25 | allocator: kCFAllocatorDefault,
26 | codecType: kCVPixelFormatType_32ARGB,
27 | width: Int32(width), height: Int32(height),
28 | extensions: nil,
29 | formatDescriptionOut: &formatDescription)
30 | guard error == noErr else {
31 | log("CMVideoFormatDescriptionCreate Error: \(error)")
32 | return nil
33 | }
34 | return formatDescription
35 | }()
36 |
37 | private lazy var clock: CFTypeRef? = {
38 | var clock: Unmanaged? = nil
39 |
40 | let error = CMIOStreamClockCreate(
41 | kCFAllocatorDefault,
42 | "VirtualCameraSample clock" as CFString,
43 | Unmanaged.passUnretained(self).toOpaque(),
44 | CMTimeMake(value: 1, timescale: 10),
45 | 100, 10,
46 | &clock);
47 | guard error == noErr else {
48 | log("CMIOStreamClockCreate Error: \(error)")
49 | return nil
50 | }
51 | return clock?.takeUnretainedValue()
52 | }()
53 |
54 | private lazy var queue: CMSimpleQueue? = {
55 | var queue: CMSimpleQueue?
56 | let error = CMSimpleQueueCreate(
57 | allocator: kCFAllocatorDefault,
58 | capacity: 30,
59 | queueOut: &queue)
60 | guard error == noErr else {
61 | log("CMSimpleQueueCreate Error: \(error)")
62 | return nil
63 | }
64 | return queue
65 | }()
66 |
67 | private lazy var timer: DispatchSourceTimer = {
68 | let interval = 1.0 / Double(frameRate)
69 | let timer = DispatchSource.makeTimerSource()
70 | timer.schedule(deadline: .now() + interval, repeating: interval)
71 | timer.setEventHandler(handler: { [weak self] in
72 | self?.enqueueBuffer()
73 | })
74 | return timer
75 | }()
76 |
77 | lazy var properties: [Int : Property] = [
78 | kCMIOObjectPropertyName: Property(name),
79 | kCMIOStreamPropertyFormatDescription: Property(formatDescription!),
80 | kCMIOStreamPropertyFormatDescriptions: Property([formatDescription!] as CFArray),
81 | kCMIOStreamPropertyDirection: Property(UInt32(0)),
82 | kCMIOStreamPropertyFrameRate: Property(Float64(frameRate)),
83 | kCMIOStreamPropertyFrameRates: Property(Float64(frameRate)),
84 | kCMIOStreamPropertyMinimumFrameRate: Property(Float64(frameRate)),
85 | kCMIOStreamPropertyFrameRateRanges: Property(AudioValueRange(mMinimum: Float64(frameRate), mMaximum: Float64(frameRate))),
86 | kCMIOStreamPropertyClock: Property(CFTypeRefWrapper(ref: clock!)),
87 | ]
88 |
89 | func start() {
90 | timer.resume()
91 | videoComposer?.startRunning()
92 | }
93 |
94 | func stop() {
95 | timer.suspend()
96 | videoComposer?.stopRunning()
97 | }
98 |
99 | func copyBufferQueue(queueAlteredProc: CMIODeviceStreamQueueAlteredProc?, queueAlteredRefCon: UnsafeMutableRawPointer?) -> CMSimpleQueue? {
100 | self.queueAlteredProc = queueAlteredProc
101 | self.queueAlteredRefCon = queueAlteredRefCon
102 | return self.queue
103 | }
104 |
105 | private func enqueueBuffer() {
106 | guard let queue = queue else {
107 | log("queue is nil")
108 | return
109 | }
110 |
111 | guard CMSimpleQueueGetCount(queue) < CMSimpleQueueGetCapacity(queue) else {
112 | log("queue is full")
113 | return
114 | }
115 |
116 | guard let pixelBuffer = targetImageBuffer else {
117 | log("pixelBuffer is nil")
118 | return
119 | }
120 |
121 | let currentTimeNsec = mach_absolute_time()
122 |
123 | var timing = CMSampleTimingInfo(
124 | duration: CMTime(value: 1, timescale: CMTimeScale(frameRate)),
125 | presentationTimeStamp: CMTime(value: CMTimeValue(currentTimeNsec), timescale: CMTimeScale(1000_000_000)),
126 | decodeTimeStamp: .invalid
127 | )
128 |
129 | var error = noErr
130 |
131 | error = CMIOStreamClockPostTimingEvent(timing.presentationTimeStamp, currentTimeNsec, true, clock)
132 | guard error == noErr else {
133 | log("CMSimpleQueueCreate Error: \(error)")
134 | return
135 | }
136 |
137 | var formatDescription: CMFormatDescription?
138 | error = CMVideoFormatDescriptionCreateForImageBuffer(
139 | allocator: kCFAllocatorDefault,
140 | imageBuffer: pixelBuffer,
141 | formatDescriptionOut: &formatDescription)
142 | guard error == noErr else {
143 | log("CMVideoFormatDescriptionCreateForImageBuffer Error: \(error)")
144 | return
145 | }
146 |
147 | var sampleBufferUnmanaged: Unmanaged? = nil
148 | error = CMIOSampleBufferCreateForImageBuffer(
149 | kCFAllocatorDefault,
150 | pixelBuffer,
151 | formatDescription,
152 | &timing,
153 | sequenceNumber,
154 | UInt32(kCMIOSampleBufferNoDiscontinuities),
155 | &sampleBufferUnmanaged
156 | )
157 | guard error == noErr else {
158 | log("CMIOSampleBufferCreateForImageBuffer Error: \(error)")
159 | return
160 | }
161 |
162 | CMSimpleQueueEnqueue(queue, element: sampleBufferUnmanaged!.toOpaque())
163 | queueAlteredProc?(objectID, sampleBufferUnmanaged!.toOpaque(), queueAlteredRefCon)
164 |
165 | sequenceNumber += 1
166 | }
167 | }
168 |
169 | extension Stream: VideoComposerDelegate {
170 |
171 | func videoComposer(_ composer: VideoComposer, didComposeImageBuffer imageBuffer: CVImageBuffer) {
172 | targetImageBuffer = imageBuffer
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/VirtualCameraSample/PluginInterface.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | private func QueryInterface(plugin: UnsafeMutableRawPointer?, uuid: REFIID, interface: UnsafeMutablePointer?) -> HRESULT {
4 | log()
5 | let pluginRefPtr = UnsafeMutablePointer(OpaquePointer(interface))
6 | pluginRefPtr?.pointee = pluginRef
7 | return HRESULT(noErr)
8 | }
9 |
10 | private func AddRef(plugin: UnsafeMutableRawPointer?) -> ULONG {
11 | log()
12 | return 0
13 | }
14 |
15 | private func Release(plugin: UnsafeMutableRawPointer?) -> ULONG {
16 | log()
17 | return 0
18 | }
19 |
20 | private func Initialize(plugin: CMIOHardwarePlugInRef?) -> OSStatus {
21 | log()
22 | return OSStatus(kCMIOHardwareIllegalOperationError)
23 | }
24 |
25 | private func InitializeWithObjectID(plugin: CMIOHardwarePlugInRef?, objectID: CMIOObjectID) -> OSStatus {
26 | log()
27 | guard let plugin = plugin else {
28 | return OSStatus(kCMIOHardwareIllegalOperationError)
29 | }
30 |
31 | var error = noErr
32 |
33 | let pluginObject = Plugin()
34 | pluginObject.objectID = objectID
35 | addObject(object: pluginObject)
36 |
37 | let device = Device()
38 | error = CMIOObjectCreate(plugin, CMIOObjectID(kCMIOObjectSystemObject), CMIOClassID(kCMIODeviceClassID), &device.objectID)
39 | guard error == noErr else {
40 | log("error: \(error)")
41 | return error
42 | }
43 | addObject(object: device)
44 |
45 | let stream = Stream()
46 | error = CMIOObjectCreate(plugin, device.objectID, CMIOClassID(kCMIOStreamClassID), &stream.objectID)
47 | guard error == noErr else {
48 | log("error: \(error)")
49 | return error
50 | }
51 | addObject(object: stream)
52 |
53 | device.streamID = stream.objectID
54 |
55 | error = CMIOObjectsPublishedAndDied(plugin, CMIOObjectID(kCMIOObjectSystemObject), 1, &device.objectID, 0, nil)
56 | guard error == noErr else {
57 | log("error: \(error)")
58 | return error
59 | }
60 |
61 | error = CMIOObjectsPublishedAndDied(plugin, device.objectID, 1, &stream.objectID, 0, nil)
62 | guard error == noErr else {
63 | log("error: \(error)")
64 | return error
65 | }
66 |
67 | return noErr
68 | }
69 |
70 | private func Teardown(plugin: CMIOHardwarePlugInRef?) -> OSStatus {
71 | log()
72 | return noErr
73 | }
74 |
75 | private func ObjectShow(plugin: CMIOHardwarePlugInRef?, objectID: CMIOObjectID) {
76 | log()
77 | }
78 |
79 | private func ObjectHasProperty(plugin: CMIOHardwarePlugInRef?, objectID: CMIOObjectID, address: UnsafePointer?) -> DarwinBoolean {
80 | guard let address = address?.pointee else {
81 | log("Address is nil")
82 | return false
83 | }
84 | guard let object = objects[objectID] else {
85 | log("Object not found")
86 | return false
87 | }
88 | return DarwinBoolean(object.hasProperty(address: address))
89 | }
90 |
91 | private func ObjectIsPropertySettable(plugin: CMIOHardwarePlugInRef?, objectID: CMIOObjectID, address: UnsafePointer?, isSettable: UnsafeMutablePointer?) -> OSStatus {
92 | guard let address = address?.pointee else {
93 | log("Address is nil")
94 | return OSStatus(kCMIOHardwareBadObjectError)
95 | }
96 | guard let object = objects[objectID] else {
97 | log("Object not found")
98 | return OSStatus(kCMIOHardwareBadObjectError)
99 | }
100 | let settable = object.isPropertySettable(address: address)
101 | isSettable?.pointee = DarwinBoolean(settable)
102 | return noErr
103 | }
104 |
105 | private func ObjectGetPropertyDataSize(plugin: CMIOHardwarePlugInRef?, objectID: CMIOObjectID, address: UnsafePointer?, qualifiedDataSize: UInt32, qualifiedData: UnsafeRawPointer?, dataSize: UnsafeMutablePointer?) -> OSStatus {
106 | guard let address = address?.pointee else {
107 | log("Address is nil")
108 | return OSStatus(kCMIOHardwareBadObjectError)
109 | }
110 | guard let object = objects[objectID] else {
111 | log("Object not found")
112 | return OSStatus(kCMIOHardwareBadObjectError)
113 | }
114 | dataSize?.pointee = object.getPropertyDataSize(address: address)
115 | return noErr
116 | }
117 |
118 | private func ObjectGetPropertyData(plugin: CMIOHardwarePlugInRef?, objectID: CMIOObjectID, address: UnsafePointer?, qualifiedDataSize: UInt32, qualifiedData: UnsafeRawPointer?, dataSize: UInt32, dataUsed: UnsafeMutablePointer?, data: UnsafeMutableRawPointer?) -> OSStatus {
119 | guard let address = address?.pointee else {
120 | log("Address is nil")
121 | return OSStatus(kCMIOHardwareBadObjectError)
122 | }
123 | guard let object = objects[objectID] else {
124 | log("Object not found")
125 | return OSStatus(kCMIOHardwareBadObjectError)
126 | }
127 | guard let data = data else {
128 | log("data is nil")
129 | return OSStatus(kCMIOHardwareBadObjectError)
130 | }
131 | var dataUsed_: UInt32 = 0
132 | object.getPropertyData(address: address, dataSize: &dataUsed_, data: data)
133 | dataUsed?.pointee = dataUsed_
134 | return noErr
135 | }
136 |
137 | private func ObjectSetPropertyData(plugin: CMIOHardwarePlugInRef?, objectID: CMIOObjectID, address: UnsafePointer?, qualifiedDataSize: UInt32, qualifiedData: UnsafeRawPointer?, dataSize: UInt32, data: UnsafeRawPointer?) -> OSStatus {
138 | log()
139 |
140 | guard let address = address?.pointee else {
141 | log("Address is nil")
142 | return OSStatus(kCMIOHardwareBadObjectError)
143 | }
144 | guard let object = objects[objectID] else {
145 | log("Object not found")
146 | return OSStatus(kCMIOHardwareBadObjectError)
147 | }
148 | guard let data = data else {
149 | log("data is nil")
150 | return OSStatus(kCMIOHardwareBadObjectError)
151 | }
152 | object.setPropertyData(address: address, data: data)
153 | return noErr
154 | }
155 |
156 | private func DeviceSuspend(plugin: CMIOHardwarePlugInRef?, deviceID: CMIODeviceID) -> OSStatus {
157 | log()
158 | return noErr
159 | }
160 |
161 | private func DeviceResume(plugin: CMIOHardwarePlugInRef?, deviceID: CMIODeviceID) -> OSStatus {
162 | log()
163 | return noErr
164 | }
165 |
166 | private func DeviceStartStream(plugin: CMIOHardwarePlugInRef?, deviceID: CMIODeviceID, streamID: CMIOStreamID) -> OSStatus {
167 | log()
168 | guard let stream = objects[streamID] as? Stream else {
169 | log("no stream")
170 | return OSStatus(kCMIOHardwareBadObjectError)
171 | }
172 | stream.start()
173 | return noErr
174 | }
175 |
176 | private func DeviceStopStream(plugin: CMIOHardwarePlugInRef?, deviceID: CMIODeviceID, streamID: CMIOStreamID) -> OSStatus {
177 | log()
178 | guard let stream = objects[streamID] as? Stream else {
179 | log("no stream")
180 | return OSStatus(kCMIOHardwareBadObjectError)
181 | }
182 | stream.stop()
183 | return noErr
184 | }
185 |
186 | private func DeviceProcessAVCCommand(plugin: CMIOHardwarePlugInRef?, deviceID: CMIODeviceID, avcCommand: UnsafeMutablePointer?) -> OSStatus {
187 | log()
188 | return OSStatus(kCMIOHardwareIllegalOperationError)
189 | }
190 |
191 | private func DeviceProcessRS422Command(plugin: CMIOHardwarePlugInRef?, deviceID: CMIODeviceID, rs422Command: UnsafeMutablePointer?) -> OSStatus {
192 | log()
193 | return OSStatus(kCMIOHardwareIllegalOperationError)
194 | }
195 |
196 | private func StreamCopyBufferQueue(plugin: CMIOHardwarePlugInRef?, streamID: CMIOStreamID, queueAlteredProc: CMIODeviceStreamQueueAlteredProc?, queueAlteredRefCon: UnsafeMutableRawPointer?, queueOut: UnsafeMutablePointer?>?) -> OSStatus {
197 | log()
198 | guard let queueOut = queueOut else {
199 | log("no queueOut")
200 | return OSStatus(kCMIOHardwareBadObjectError)
201 | }
202 | guard let stream = objects[streamID] as? Stream else {
203 | log("no stream")
204 | return OSStatus(kCMIOHardwareBadObjectError)
205 | }
206 | guard let queue = stream.copyBufferQueue(queueAlteredProc: queueAlteredProc, queueAlteredRefCon: queueAlteredRefCon) else {
207 | log("no queue")
208 | return OSStatus(kCMIOHardwareBadObjectError)
209 | }
210 | queueOut.pointee = Unmanaged.passRetained(queue)
211 | return noErr
212 | }
213 |
214 | private func StreamDeckPlay(plugin: CMIOHardwarePlugInRef?, streamID: CMIOStreamID) -> OSStatus {
215 | log()
216 | return OSStatus(kCMIOHardwareIllegalOperationError)
217 | }
218 |
219 | private func StreamDeckStop(plugin: CMIOHardwarePlugInRef?, streamID: CMIOStreamID) -> OSStatus {
220 | log()
221 | return OSStatus(kCMIOHardwareIllegalOperationError)
222 | }
223 |
224 | private func StreamDeckJog(plugin: CMIOHardwarePlugInRef?, streamID: CMIOStreamID, speed: Int32) -> OSStatus {
225 | log()
226 | return OSStatus(kCMIOHardwareIllegalOperationError)
227 | }
228 |
229 | private func StreamDeckCueTo(plugin: CMIOHardwarePlugInRef?, streamID: CMIOStreamID, requestedTimecode: Float64, playOnCue: DarwinBoolean) -> OSStatus {
230 | log()
231 | return OSStatus(kCMIOHardwareIllegalOperationError)
232 | }
233 |
234 | private func createPluginInterface() -> CMIOHardwarePlugInInterface {
235 | return CMIOHardwarePlugInInterface(
236 | _reserved: nil,
237 | QueryInterface: QueryInterface,
238 | AddRef: AddRef,
239 | Release: Release,
240 | Initialize: Initialize,
241 | InitializeWithObjectID: InitializeWithObjectID,
242 | Teardown: Teardown,
243 | ObjectShow: ObjectShow,
244 | ObjectHasProperty: ObjectHasProperty,
245 | ObjectIsPropertySettable: ObjectIsPropertySettable,
246 | ObjectGetPropertyDataSize: ObjectGetPropertyDataSize,
247 | ObjectGetPropertyData: ObjectGetPropertyData,
248 | ObjectSetPropertyData: ObjectSetPropertyData,
249 | DeviceSuspend: DeviceSuspend,
250 | DeviceResume: DeviceResume,
251 | DeviceStartStream: DeviceStartStream,
252 | DeviceStopStream: DeviceStopStream,
253 | DeviceProcessAVCCommand: DeviceProcessAVCCommand,
254 | DeviceProcessRS422Command: DeviceProcessRS422Command,
255 | StreamCopyBufferQueue: StreamCopyBufferQueue,
256 | StreamDeckPlay: StreamDeckPlay,
257 | StreamDeckStop: StreamDeckStop,
258 | StreamDeckJog: StreamDeckJog,
259 | StreamDeckCueTo: StreamDeckCueTo)
260 | }
261 |
262 | let pluginRef: CMIOHardwarePlugInRef = {
263 | let interfacePtr = UnsafeMutablePointer.allocate(capacity: 1)
264 | interfacePtr.pointee = createPluginInterface()
265 |
266 | let pluginRef = CMIOHardwarePlugInRef.allocate(capacity: 1)
267 | pluginRef.pointee = interfacePtr
268 | return pluginRef
269 | }()
270 |
--------------------------------------------------------------------------------
/VirtualCameraSample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | ABB04E622515481A0038D75C /* CVPixelBuffer+SimpleDALPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E572515481A0038D75C /* CVPixelBuffer+SimpleDALPlugin.swift */; };
11 | ABB04E632515481A0038D75C /* PluginInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E582515481A0038D75C /* PluginInterface.swift */; };
12 | ABB04E642515481A0038D75C /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E592515481A0038D75C /* Property.swift */; };
13 | ABB04E652515481A0038D75C /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E5A2515481A0038D75C /* Stream.swift */; };
14 | ABB04E662515481A0038D75C /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E5B2515481A0038D75C /* Main.swift */; };
15 | ABB04E672515481A0038D75C /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E5C2515481A0038D75C /* Object.swift */; };
16 | ABB04E682515481A0038D75C /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E5D2515481A0038D75C /* Plugin.swift */; };
17 | ABB04E692515481A0038D75C /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E5E2515481A0038D75C /* Log.swift */; };
18 | ABB04E6A2515481A0038D75C /* CameraCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E5F2515481A0038D75C /* CameraCapture.swift */; };
19 | ABB04E6B2515481A0038D75C /* VideoComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E602515481A0038D75C /* VideoComposer.swift */; };
20 | ABB04E7225154B1F0038D75C /* Device.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E7125154B1F0038D75C /* Device.swift */; };
21 | ABB04E7F2515A8140038D75C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E7E2515A8140038D75C /* AppDelegate.swift */; };
22 | ABB04E812515A8140038D75C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E802515A8140038D75C /* ViewController.swift */; };
23 | ABB04E832515A8160038D75C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ABB04E822515A8160038D75C /* Assets.xcassets */; };
24 | ABB04E862515A8160038D75C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ABB04E842515A8160038D75C /* Main.storyboard */; };
25 | ABB04E9D25162D310038D75C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04E9C25162D310038D75C /* WindowController.swift */; };
26 | ABB04EB125194A7D0038D75C /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04EAF25194A7D0038D75C /* Config.swift */; };
27 | ABB04EB225194A7D0038D75C /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04EAF25194A7D0038D75C /* Config.swift */; };
28 | ABB04EB325194A7D0038D75C /* SettingsPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04EB025194A7D0038D75C /* SettingsPasteboard.swift */; };
29 | ABB04EB425194A7D0038D75C /* SettingsPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABB04EB025194A7D0038D75C /* SettingsPasteboard.swift */; };
30 | /* End PBXBuildFile section */
31 |
32 | /* Begin PBXFileReference section */
33 | ABB04E4C2515477A0038D75C /* VirtualCameraSample.plugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VirtualCameraSample.plugin; sourceTree = BUILT_PRODUCTS_DIR; };
34 | ABB04E4F2515477A0038D75C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
35 | ABB04E572515481A0038D75C /* CVPixelBuffer+SimpleDALPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CVPixelBuffer+SimpleDALPlugin.swift"; sourceTree = ""; };
36 | ABB04E582515481A0038D75C /* PluginInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginInterface.swift; sourceTree = ""; };
37 | ABB04E592515481A0038D75C /* Property.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Property.swift; sourceTree = ""; };
38 | ABB04E5A2515481A0038D75C /* Stream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = ""; };
39 | ABB04E5B2515481A0038D75C /* Main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = ""; };
40 | ABB04E5C2515481A0038D75C /* Object.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Object.swift; sourceTree = ""; };
41 | ABB04E5D2515481A0038D75C /* Plugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = ""; };
42 | ABB04E5E2515481A0038D75C /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; };
43 | ABB04E5F2515481A0038D75C /* CameraCapture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraCapture.swift; sourceTree = ""; };
44 | ABB04E602515481A0038D75C /* VideoComposer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoComposer.swift; sourceTree = ""; };
45 | ABB04E6E25154A330038D75C /* VirtualCameraSample-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "VirtualCameraSample-Bridging-Header.h"; sourceTree = ""; };
46 | ABB04E7125154B1F0038D75C /* Device.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = ""; };
47 | ABB04E7C2515A8140038D75C /* VirtualCameraSampleController.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VirtualCameraSampleController.app; sourceTree = BUILT_PRODUCTS_DIR; };
48 | ABB04E7E2515A8140038D75C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
49 | ABB04E802515A8140038D75C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
50 | ABB04E822515A8160038D75C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
51 | ABB04E852515A8160038D75C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
52 | ABB04E872515A8160038D75C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
53 | ABB04E882515A8160038D75C /* VirtualCameraSampleController.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VirtualCameraSampleController.entitlements; sourceTree = ""; };
54 | ABB04E9C25162D310038D75C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; };
55 | ABB04EAF25194A7D0038D75C /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; };
56 | ABB04EB025194A7D0038D75C /* SettingsPasteboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPasteboard.swift; sourceTree = ""; };
57 | /* End PBXFileReference section */
58 |
59 | /* Begin PBXFrameworksBuildPhase section */
60 | ABB04E492515477A0038D75C /* Frameworks */ = {
61 | isa = PBXFrameworksBuildPhase;
62 | buildActionMask = 2147483647;
63 | files = (
64 | );
65 | runOnlyForDeploymentPostprocessing = 0;
66 | };
67 | ABB04E792515A8140038D75C /* Frameworks */ = {
68 | isa = PBXFrameworksBuildPhase;
69 | buildActionMask = 2147483647;
70 | files = (
71 | );
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | /* End PBXFrameworksBuildPhase section */
75 |
76 | /* Begin PBXGroup section */
77 | ABB04E432515477A0038D75C = {
78 | isa = PBXGroup;
79 | children = (
80 | ABB04EAE25194A7D0038D75C /* Common */,
81 | ABB04E4E2515477A0038D75C /* VirtualCameraSample */,
82 | ABB04E7D2515A8140038D75C /* VirtualCameraSampleController */,
83 | ABB04E4D2515477A0038D75C /* Products */,
84 | );
85 | sourceTree = "";
86 | };
87 | ABB04E4D2515477A0038D75C /* Products */ = {
88 | isa = PBXGroup;
89 | children = (
90 | ABB04E4C2515477A0038D75C /* VirtualCameraSample.plugin */,
91 | ABB04E7C2515A8140038D75C /* VirtualCameraSampleController.app */,
92 | );
93 | name = Products;
94 | sourceTree = "";
95 | };
96 | ABB04E4E2515477A0038D75C /* VirtualCameraSample */ = {
97 | isa = PBXGroup;
98 | children = (
99 | ABB04E5F2515481A0038D75C /* CameraCapture.swift */,
100 | ABB04E572515481A0038D75C /* CVPixelBuffer+SimpleDALPlugin.swift */,
101 | ABB04E7125154B1F0038D75C /* Device.swift */,
102 | ABB04E4F2515477A0038D75C /* Info.plist */,
103 | ABB04E5E2515481A0038D75C /* Log.swift */,
104 | ABB04E5B2515481A0038D75C /* Main.swift */,
105 | ABB04E5C2515481A0038D75C /* Object.swift */,
106 | ABB04E5D2515481A0038D75C /* Plugin.swift */,
107 | ABB04E582515481A0038D75C /* PluginInterface.swift */,
108 | ABB04E592515481A0038D75C /* Property.swift */,
109 | ABB04E5A2515481A0038D75C /* Stream.swift */,
110 | ABB04E602515481A0038D75C /* VideoComposer.swift */,
111 | ABB04E6E25154A330038D75C /* VirtualCameraSample-Bridging-Header.h */,
112 | );
113 | path = VirtualCameraSample;
114 | sourceTree = "";
115 | };
116 | ABB04E7D2515A8140038D75C /* VirtualCameraSampleController */ = {
117 | isa = PBXGroup;
118 | children = (
119 | ABB04E7E2515A8140038D75C /* AppDelegate.swift */,
120 | ABB04E802515A8140038D75C /* ViewController.swift */,
121 | ABB04E9C25162D310038D75C /* WindowController.swift */,
122 | ABB04E822515A8160038D75C /* Assets.xcassets */,
123 | ABB04E842515A8160038D75C /* Main.storyboard */,
124 | ABB04E872515A8160038D75C /* Info.plist */,
125 | ABB04E882515A8160038D75C /* VirtualCameraSampleController.entitlements */,
126 | );
127 | path = VirtualCameraSampleController;
128 | sourceTree = "";
129 | };
130 | ABB04EAE25194A7D0038D75C /* Common */ = {
131 | isa = PBXGroup;
132 | children = (
133 | ABB04EAF25194A7D0038D75C /* Config.swift */,
134 | ABB04EB025194A7D0038D75C /* SettingsPasteboard.swift */,
135 | );
136 | path = Common;
137 | sourceTree = "";
138 | };
139 | /* End PBXGroup section */
140 |
141 | /* Begin PBXNativeTarget section */
142 | ABB04E4B2515477A0038D75C /* VirtualCameraSample */ = {
143 | isa = PBXNativeTarget;
144 | buildConfigurationList = ABB04E522515477A0038D75C /* Build configuration list for PBXNativeTarget "VirtualCameraSample" */;
145 | buildPhases = (
146 | ABB04E482515477A0038D75C /* Sources */,
147 | ABB04E492515477A0038D75C /* Frameworks */,
148 | ABB04E4A2515477A0038D75C /* Resources */,
149 | );
150 | buildRules = (
151 | );
152 | dependencies = (
153 | );
154 | name = VirtualCameraSample;
155 | productName = VirtualCameraSample;
156 | productReference = ABB04E4C2515477A0038D75C /* VirtualCameraSample.plugin */;
157 | productType = "com.apple.product-type.bundle";
158 | };
159 | ABB04E7B2515A8140038D75C /* VirtualCameraSampleController */ = {
160 | isa = PBXNativeTarget;
161 | buildConfigurationList = ABB04E892515A8160038D75C /* Build configuration list for PBXNativeTarget "VirtualCameraSampleController" */;
162 | buildPhases = (
163 | ABB04E782515A8140038D75C /* Sources */,
164 | ABB04E792515A8140038D75C /* Frameworks */,
165 | ABB04E7A2515A8140038D75C /* Resources */,
166 | );
167 | buildRules = (
168 | );
169 | dependencies = (
170 | );
171 | name = VirtualCameraSampleController;
172 | productName = VirtualCameraSampleController;
173 | productReference = ABB04E7C2515A8140038D75C /* VirtualCameraSampleController.app */;
174 | productType = "com.apple.product-type.application";
175 | };
176 | /* End PBXNativeTarget section */
177 |
178 | /* Begin PBXProject section */
179 | ABB04E442515477A0038D75C /* Project object */ = {
180 | isa = PBXProject;
181 | attributes = {
182 | LastSwiftUpdateCheck = 1200;
183 | LastUpgradeCheck = 1200;
184 | TargetAttributes = {
185 | ABB04E4B2515477A0038D75C = {
186 | CreatedOnToolsVersion = 12.0;
187 | LastSwiftMigration = 1200;
188 | };
189 | ABB04E7B2515A8140038D75C = {
190 | CreatedOnToolsVersion = 12.0;
191 | };
192 | };
193 | };
194 | buildConfigurationList = ABB04E472515477A0038D75C /* Build configuration list for PBXProject "VirtualCameraSample" */;
195 | compatibilityVersion = "Xcode 9.3";
196 | developmentRegion = en;
197 | hasScannedForEncodings = 0;
198 | knownRegions = (
199 | en,
200 | Base,
201 | );
202 | mainGroup = ABB04E432515477A0038D75C;
203 | productRefGroup = ABB04E4D2515477A0038D75C /* Products */;
204 | projectDirPath = "";
205 | projectRoot = "";
206 | targets = (
207 | ABB04E4B2515477A0038D75C /* VirtualCameraSample */,
208 | ABB04E7B2515A8140038D75C /* VirtualCameraSampleController */,
209 | );
210 | };
211 | /* End PBXProject section */
212 |
213 | /* Begin PBXResourcesBuildPhase section */
214 | ABB04E4A2515477A0038D75C /* Resources */ = {
215 | isa = PBXResourcesBuildPhase;
216 | buildActionMask = 2147483647;
217 | files = (
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | };
221 | ABB04E7A2515A8140038D75C /* Resources */ = {
222 | isa = PBXResourcesBuildPhase;
223 | buildActionMask = 2147483647;
224 | files = (
225 | ABB04E832515A8160038D75C /* Assets.xcassets in Resources */,
226 | ABB04E862515A8160038D75C /* Main.storyboard in Resources */,
227 | );
228 | runOnlyForDeploymentPostprocessing = 0;
229 | };
230 | /* End PBXResourcesBuildPhase section */
231 |
232 | /* Begin PBXSourcesBuildPhase section */
233 | ABB04E482515477A0038D75C /* Sources */ = {
234 | isa = PBXSourcesBuildPhase;
235 | buildActionMask = 2147483647;
236 | files = (
237 | ABB04E662515481A0038D75C /* Main.swift in Sources */,
238 | ABB04EB325194A7D0038D75C /* SettingsPasteboard.swift in Sources */,
239 | ABB04E642515481A0038D75C /* Property.swift in Sources */,
240 | ABB04E682515481A0038D75C /* Plugin.swift in Sources */,
241 | ABB04E7225154B1F0038D75C /* Device.swift in Sources */,
242 | ABB04E632515481A0038D75C /* PluginInterface.swift in Sources */,
243 | ABB04EB125194A7D0038D75C /* Config.swift in Sources */,
244 | ABB04E6A2515481A0038D75C /* CameraCapture.swift in Sources */,
245 | ABB04E6B2515481A0038D75C /* VideoComposer.swift in Sources */,
246 | ABB04E622515481A0038D75C /* CVPixelBuffer+SimpleDALPlugin.swift in Sources */,
247 | ABB04E672515481A0038D75C /* Object.swift in Sources */,
248 | ABB04E692515481A0038D75C /* Log.swift in Sources */,
249 | ABB04E652515481A0038D75C /* Stream.swift in Sources */,
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | };
253 | ABB04E782515A8140038D75C /* Sources */ = {
254 | isa = PBXSourcesBuildPhase;
255 | buildActionMask = 2147483647;
256 | files = (
257 | ABB04E9D25162D310038D75C /* WindowController.swift in Sources */,
258 | ABB04EB225194A7D0038D75C /* Config.swift in Sources */,
259 | ABB04E812515A8140038D75C /* ViewController.swift in Sources */,
260 | ABB04E7F2515A8140038D75C /* AppDelegate.swift in Sources */,
261 | ABB04EB425194A7D0038D75C /* SettingsPasteboard.swift in Sources */,
262 | );
263 | runOnlyForDeploymentPostprocessing = 0;
264 | };
265 | /* End PBXSourcesBuildPhase section */
266 |
267 | /* Begin PBXVariantGroup section */
268 | ABB04E842515A8160038D75C /* Main.storyboard */ = {
269 | isa = PBXVariantGroup;
270 | children = (
271 | ABB04E852515A8160038D75C /* Base */,
272 | );
273 | name = Main.storyboard;
274 | sourceTree = "";
275 | };
276 | /* End PBXVariantGroup section */
277 |
278 | /* Begin XCBuildConfiguration section */
279 | ABB04E502515477A0038D75C /* Debug */ = {
280 | isa = XCBuildConfiguration;
281 | buildSettings = {
282 | ALWAYS_SEARCH_USER_PATHS = NO;
283 | CLANG_ANALYZER_NONNULL = YES;
284 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
285 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
286 | CLANG_CXX_LIBRARY = "libc++";
287 | CLANG_ENABLE_MODULES = YES;
288 | CLANG_ENABLE_OBJC_ARC = YES;
289 | CLANG_ENABLE_OBJC_WEAK = YES;
290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
291 | CLANG_WARN_BOOL_CONVERSION = YES;
292 | CLANG_WARN_COMMA = YES;
293 | CLANG_WARN_CONSTANT_CONVERSION = YES;
294 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
296 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
297 | CLANG_WARN_EMPTY_BODY = YES;
298 | CLANG_WARN_ENUM_CONVERSION = YES;
299 | CLANG_WARN_INFINITE_RECURSION = YES;
300 | CLANG_WARN_INT_CONVERSION = YES;
301 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
302 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
303 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
304 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
305 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
306 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
307 | CLANG_WARN_STRICT_PROTOTYPES = YES;
308 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
309 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
310 | CLANG_WARN_UNREACHABLE_CODE = YES;
311 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
312 | COPY_PHASE_STRIP = NO;
313 | DEBUG_INFORMATION_FORMAT = dwarf;
314 | ENABLE_STRICT_OBJC_MSGSEND = YES;
315 | ENABLE_TESTABILITY = YES;
316 | GCC_C_LANGUAGE_STANDARD = gnu11;
317 | GCC_DYNAMIC_NO_PIC = NO;
318 | GCC_NO_COMMON_BLOCKS = YES;
319 | GCC_OPTIMIZATION_LEVEL = 0;
320 | GCC_PREPROCESSOR_DEFINITIONS = (
321 | "DEBUG=1",
322 | "$(inherited)",
323 | );
324 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
325 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
326 | GCC_WARN_UNDECLARED_SELECTOR = YES;
327 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
328 | GCC_WARN_UNUSED_FUNCTION = YES;
329 | GCC_WARN_UNUSED_VARIABLE = YES;
330 | MACOSX_DEPLOYMENT_TARGET = 10.15;
331 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
332 | MTL_FAST_MATH = YES;
333 | ONLY_ACTIVE_ARCH = YES;
334 | SDKROOT = macosx;
335 | };
336 | name = Debug;
337 | };
338 | ABB04E512515477A0038D75C /* Release */ = {
339 | isa = XCBuildConfiguration;
340 | buildSettings = {
341 | ALWAYS_SEARCH_USER_PATHS = NO;
342 | CLANG_ANALYZER_NONNULL = YES;
343 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
345 | CLANG_CXX_LIBRARY = "libc++";
346 | CLANG_ENABLE_MODULES = YES;
347 | CLANG_ENABLE_OBJC_ARC = YES;
348 | CLANG_ENABLE_OBJC_WEAK = YES;
349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
350 | CLANG_WARN_BOOL_CONVERSION = YES;
351 | CLANG_WARN_COMMA = YES;
352 | CLANG_WARN_CONSTANT_CONVERSION = YES;
353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
355 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
356 | CLANG_WARN_EMPTY_BODY = YES;
357 | CLANG_WARN_ENUM_CONVERSION = YES;
358 | CLANG_WARN_INFINITE_RECURSION = YES;
359 | CLANG_WARN_INT_CONVERSION = YES;
360 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
361 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
362 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
364 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
365 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
366 | CLANG_WARN_STRICT_PROTOTYPES = YES;
367 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
368 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
369 | CLANG_WARN_UNREACHABLE_CODE = YES;
370 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
371 | COPY_PHASE_STRIP = NO;
372 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
373 | ENABLE_NS_ASSERTIONS = NO;
374 | ENABLE_STRICT_OBJC_MSGSEND = YES;
375 | GCC_C_LANGUAGE_STANDARD = gnu11;
376 | GCC_NO_COMMON_BLOCKS = YES;
377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
379 | GCC_WARN_UNDECLARED_SELECTOR = YES;
380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
381 | GCC_WARN_UNUSED_FUNCTION = YES;
382 | GCC_WARN_UNUSED_VARIABLE = YES;
383 | MACOSX_DEPLOYMENT_TARGET = 10.15;
384 | MTL_ENABLE_DEBUG_INFO = NO;
385 | MTL_FAST_MATH = YES;
386 | SDKROOT = macosx;
387 | };
388 | name = Release;
389 | };
390 | ABB04E532515477A0038D75C /* Debug */ = {
391 | isa = XCBuildConfiguration;
392 | buildSettings = {
393 | CLANG_ENABLE_MODULES = YES;
394 | CODE_SIGN_STYLE = Automatic;
395 | COMBINE_HIDPI_IMAGES = YES;
396 | DEVELOPMENT_TEAM = 247V527KAQ;
397 | INFOPLIST_FILE = VirtualCameraSample/Info.plist;
398 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
399 | LD_RUNPATH_SEARCH_PATHS = (
400 | "$(inherited)",
401 | "@executable_path/../Frameworks",
402 | "@loader_path/../Frameworks",
403 | );
404 | PRODUCT_BUNDLE_IDENTIFIER = tokyo.shmdevelopment.VirtualCameraSample;
405 | PRODUCT_NAME = "$(TARGET_NAME)";
406 | SKIP_INSTALL = YES;
407 | SWIFT_OBJC_BRIDGING_HEADER = "VirtualCameraSample/VirtualCameraSample-Bridging-Header.h";
408 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
409 | SWIFT_VERSION = 5.0;
410 | WRAPPER_EXTENSION = plugin;
411 | };
412 | name = Debug;
413 | };
414 | ABB04E542515477A0038D75C /* Release */ = {
415 | isa = XCBuildConfiguration;
416 | buildSettings = {
417 | CLANG_ENABLE_MODULES = YES;
418 | CODE_SIGN_STYLE = Automatic;
419 | COMBINE_HIDPI_IMAGES = YES;
420 | DEVELOPMENT_TEAM = 247V527KAQ;
421 | INFOPLIST_FILE = VirtualCameraSample/Info.plist;
422 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
423 | LD_RUNPATH_SEARCH_PATHS = (
424 | "$(inherited)",
425 | "@executable_path/../Frameworks",
426 | "@loader_path/../Frameworks",
427 | );
428 | PRODUCT_BUNDLE_IDENTIFIER = tokyo.shmdevelopment.VirtualCameraSample;
429 | PRODUCT_NAME = "$(TARGET_NAME)";
430 | SKIP_INSTALL = YES;
431 | SWIFT_OBJC_BRIDGING_HEADER = "VirtualCameraSample/VirtualCameraSample-Bridging-Header.h";
432 | SWIFT_VERSION = 5.0;
433 | WRAPPER_EXTENSION = plugin;
434 | };
435 | name = Release;
436 | };
437 | ABB04E8A2515A8160038D75C /* Debug */ = {
438 | isa = XCBuildConfiguration;
439 | buildSettings = {
440 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
441 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
442 | CODE_SIGN_ENTITLEMENTS = VirtualCameraSampleController/VirtualCameraSampleController.entitlements;
443 | CODE_SIGN_STYLE = Automatic;
444 | COMBINE_HIDPI_IMAGES = YES;
445 | DEVELOPMENT_TEAM = 247V527KAQ;
446 | ENABLE_HARDENED_RUNTIME = YES;
447 | INFOPLIST_FILE = VirtualCameraSampleController/Info.plist;
448 | LD_RUNPATH_SEARCH_PATHS = (
449 | "$(inherited)",
450 | "@executable_path/../Frameworks",
451 | );
452 | MACOSX_DEPLOYMENT_TARGET = 10.14;
453 | PRODUCT_BUNDLE_IDENTIFIER = tokyo.shmdevelopment.VirtualCameraSampleController;
454 | PRODUCT_NAME = "$(TARGET_NAME)";
455 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
456 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
457 | SWIFT_VERSION = 5.0;
458 | };
459 | name = Debug;
460 | };
461 | ABB04E8B2515A8160038D75C /* Release */ = {
462 | isa = XCBuildConfiguration;
463 | buildSettings = {
464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
465 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
466 | CODE_SIGN_ENTITLEMENTS = VirtualCameraSampleController/VirtualCameraSampleController.entitlements;
467 | CODE_SIGN_STYLE = Automatic;
468 | COMBINE_HIDPI_IMAGES = YES;
469 | DEVELOPMENT_TEAM = 247V527KAQ;
470 | ENABLE_HARDENED_RUNTIME = YES;
471 | INFOPLIST_FILE = VirtualCameraSampleController/Info.plist;
472 | LD_RUNPATH_SEARCH_PATHS = (
473 | "$(inherited)",
474 | "@executable_path/../Frameworks",
475 | );
476 | MACOSX_DEPLOYMENT_TARGET = 10.14;
477 | PRODUCT_BUNDLE_IDENTIFIER = tokyo.shmdevelopment.VirtualCameraSampleController;
478 | PRODUCT_NAME = "$(TARGET_NAME)";
479 | SWIFT_COMPILATION_MODE = wholemodule;
480 | SWIFT_OPTIMIZATION_LEVEL = "-O";
481 | SWIFT_VERSION = 5.0;
482 | };
483 | name = Release;
484 | };
485 | /* End XCBuildConfiguration section */
486 |
487 | /* Begin XCConfigurationList section */
488 | ABB04E472515477A0038D75C /* Build configuration list for PBXProject "VirtualCameraSample" */ = {
489 | isa = XCConfigurationList;
490 | buildConfigurations = (
491 | ABB04E502515477A0038D75C /* Debug */,
492 | ABB04E512515477A0038D75C /* Release */,
493 | );
494 | defaultConfigurationIsVisible = 0;
495 | defaultConfigurationName = Release;
496 | };
497 | ABB04E522515477A0038D75C /* Build configuration list for PBXNativeTarget "VirtualCameraSample" */ = {
498 | isa = XCConfigurationList;
499 | buildConfigurations = (
500 | ABB04E532515477A0038D75C /* Debug */,
501 | ABB04E542515477A0038D75C /* Release */,
502 | );
503 | defaultConfigurationIsVisible = 0;
504 | defaultConfigurationName = Release;
505 | };
506 | ABB04E892515A8160038D75C /* Build configuration list for PBXNativeTarget "VirtualCameraSampleController" */ = {
507 | isa = XCConfigurationList;
508 | buildConfigurations = (
509 | ABB04E8A2515A8160038D75C /* Debug */,
510 | ABB04E8B2515A8160038D75C /* Release */,
511 | );
512 | defaultConfigurationIsVisible = 0;
513 | defaultConfigurationName = Release;
514 | };
515 | /* End XCConfigurationList section */
516 | };
517 | rootObject = ABB04E442515477A0038D75C /* Project object */;
518 | }
519 |
--------------------------------------------------------------------------------
/VirtualCameraSampleController/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
726 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
--------------------------------------------------------------------------------