├── 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 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | Default 531 | 532 | 533 | 534 | 535 | 536 | 537 | Left to Right 538 | 539 | 540 | 541 | 542 | 543 | 544 | Right to Left 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | Default 556 | 557 | 558 | 559 | 560 | 561 | 562 | Left to Right 563 | 564 | 565 | 566 | 567 | 568 | 569 | Right to Left 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 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 | --------------------------------------------------------------------------------