Created
June 22, 2022 10:48
-
-
Save ldenoue/84210280853f0490c79473b6edd25e9d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import CoreMediaIO | |
let CMIOExtensionPropertyCustomPropertyData_just: CMIOExtensionProperty = CMIOExtensionProperty(rawValue: "4cc_just_glob_0000") | |
let CMIOExtensionPropertyCustomPropertyData_dust: CMIOExtensionProperty = CMIOExtensionProperty(rawValue: "4cc_dust_glob_0000") | |
... | |
class cameraStreamSource: NSObject, CMIOExtensionStreamSource { | |
private(set) var stream: CMIOExtensionStream! | |
let device: CMIOExtensionDevice | |
private let _streamFormat: CMIOExtensionStreamFormat | |
init(localizedName: String, streamID: UUID, streamFormat: CMIOExtensionStreamFormat, device: CMIOExtensionDevice) { | |
self.device = device | |
self._streamFormat = streamFormat | |
super.init() | |
self.stream = CMIOExtensionStream(localizedName: localizedName, streamID: streamID, direction: .source, clockType: .hostTime, source: self) | |
} | |
var formats: [CMIOExtensionStreamFormat] { | |
return [_streamFormat] | |
} | |
var activeFormatIndex: Int = 0 { | |
didSet { | |
if activeFormatIndex >= 1 { | |
os_log(.error, "Invalid index") | |
} | |
} | |
} | |
var availableProperties: Set<CMIOExtensionProperty> { | |
return [.streamActiveFormatIndex, .streamFrameDuration, CMIOExtensionPropertyCustomPropertyData_just, CMIOExtensionPropertyCustomPropertyData_dust] | |
} | |
public var just: String = "toto" | |
public var dust: Data = "titi est content".data(using: .utf8)! | |
func streamProperties(forProperties properties: Set<CMIOExtensionProperty>) throws -> CMIOExtensionStreamProperties { | |
let streamProperties = CMIOExtensionStreamProperties(dictionary: [:]) | |
if properties.contains(.streamActiveFormatIndex) { | |
streamProperties.activeFormatIndex = 0 | |
} | |
if properties.contains(.streamFrameDuration) { | |
let frameDuration = CMTime(value: 1, timescale: Int32(kFrameRate)) | |
streamProperties.frameDuration = frameDuration | |
} | |
if properties.contains(CMIOExtensionPropertyCustomPropertyData_just) { | |
streamProperties.setPropertyState(CMIOExtensionPropertyState(value: self.just as NSString), forProperty: CMIOExtensionPropertyCustomPropertyData_just) | |
} | |
if properties.contains(CMIOExtensionPropertyCustomPropertyData_dust) { | |
streamProperties.setPropertyState(CMIOExtensionPropertyState(value: self.dust as NSData), forProperty: CMIOExtensionPropertyCustomPropertyData_dust) | |
} | |
return streamProperties | |
} | |
func setStreamProperties(_ streamProperties: CMIOExtensionStreamProperties) throws { | |
if let activeFormatIndex = streamProperties.activeFormatIndex { | |
self.activeFormatIndex = activeFormatIndex | |
} | |
if let state = streamProperties.propertiesDictionary[CMIOExtensionPropertyCustomPropertyData_just] { | |
if let deviceSource = device.source as? cameraDeviceSource { | |
deviceSource.lastMessage = "got just property change" | |
} | |
if let newValue = state.value as? String { | |
self.just = newValue | |
if let deviceSource = device.source as? cameraDeviceSource { | |
deviceSource.lastMessage = "got just property change=" + self.just | |
} | |
} | |
} | |
if let state = streamProperties.propertiesDictionary[CMIOExtensionPropertyCustomPropertyData_dust] { | |
if let deviceSource = device.source as? cameraDeviceSource { | |
deviceSource.lastMessage = "got dust property change" | |
} | |
if let newValue = state.value as? Data { | |
self.dust = newValue | |
if let deviceSource = device.source as? cameraDeviceSource { | |
deviceSource.lastMessage = "new dust property count=\(newValue.count)" | |
deviceSource.lastImage = NSImage(data: self.dust) | |
} | |
} else { | |
if let deviceSource = device.source as? cameraDeviceSource { | |
deviceSource.lastMessage = "got dust property err ? Data" | |
} | |
} | |
} | |
} | |
func authorizedToStartStream(for client: CMIOExtensionClient) -> Bool { | |
// An opportunity to inspect the client info and decide if it should be allowed to start the stream. | |
return true | |
} | |
func startStream() throws { | |
guard let deviceSource = device.source as? cameraDeviceSource else { | |
fatalError("Unexpected source type \(String(describing: device.source))") | |
} | |
deviceSource.startStreaming() | |
} | |
func stopStream() throws { | |
guard let deviceSource = device.source as? cameraDeviceSource else { | |
fatalError("Unexpected source type \(String(describing: device.source))") | |
} | |
deviceSource.stopStreaming() | |
} | |
} | |
... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import CoreMediaIO | |
import AVFoundation | |
extension FourCharCode: StringLiteralConvertible { | |
public init(stringLiteral value: StringLiteralType) { | |
var code: FourCharCode = 0 | |
// Value has to consist of 4 printable ASCII characters, e.g. '420v'. | |
// Note: This implementation does not enforce printable range (32-126) | |
if value.characters.count == 4 && value.utf8.count == 4 { | |
for byte in value.utf8 { | |
code = code << 8 + FourCharCode(byte) | |
} | |
} | |
else { | |
print("FourCharCode: Can't initialize with '\(value)', only printable ASCII allowed. Setting to '????'.") | |
code = 0x3F3F3F3F // = '????' | |
} | |
self = code | |
} | |
public init(extendedGraphemeClusterLiteral value: String) { | |
self = FourCharCode(stringLiteral: value) | |
} | |
public init(unicodeScalarLiteral value: String) { | |
self = FourCharCode(stringLiteral: value) | |
} | |
public init(_ value: String) { | |
self = FourCharCode(stringLiteral: value) | |
} | |
public var string: String? { | |
let cString: [CChar] = [ | |
CChar(self >> 24 & 0xFF), | |
CChar(self >> 16 & 0xFF), | |
CChar(self >> 8 & 0xFF), | |
CChar(self & 0xFF), | |
0 | |
] | |
return String(cString: cString) | |
} | |
} | |
final class ScreegleViewController: NSViewController { | |
func getDevice(name: String) -> AVCaptureDevice? { | |
let devices = AVCaptureDevice.devices(for: .video) | |
return devices.first { $0.localizedName == name} | |
} | |
func getCMIODevice(uid: String) -> CMIOObjectID? { | |
var dataSize: UInt32 = 0 | |
var devices = [CMIOObjectID]() | |
var dataUsed: UInt32 = 0 | |
var opa = CMIOObjectPropertyAddress(CMIOObjectPropertySelector(kCMIOHardwarePropertyDevices), .global, .main) | |
CMIOObjectGetPropertyDataSize(CMIOObjectPropertySelector(kCMIOObjectSystemObject), &opa, 0, nil, &dataSize); | |
let nDevices = Int(dataSize) / MemoryLayout<CMIOObjectID>.size | |
devices = [CMIOObjectID](repeating: 0, count: Int(nDevices)) | |
CMIOObjectGetPropertyData(CMIOObjectPropertySelector(kCMIOObjectSystemObject), &opa, 0, nil, dataSize, &dataUsed, &devices); | |
for deviceObjectID in devices { | |
opa.mSelector = CMIOObjectPropertySelector(kCMIODevicePropertyDeviceUID) | |
CMIOObjectGetPropertyDataSize(deviceObjectID, &opa, 0, nil, &dataSize) | |
var name: CFString = "" as NSString | |
//CMIOObjectGetPropertyData(deviceObjectID, &opa, 0, nil, UInt32(MemoryLayout<CFString>.size), &dataSize, &name); | |
CMIOObjectGetPropertyData(deviceObjectID, &opa, 0, nil, dataSize, &dataUsed, &name); | |
if String(name) == uid { | |
return deviceObjectID | |
} | |
} | |
return nil | |
} | |
func getInputStreams(deviceId: CMIODeviceID) -> [CMIOStreamID] | |
{ | |
var dataSize: UInt32 = 0 | |
var dataUsed: UInt32 = 0 | |
var opa = CMIOObjectPropertyAddress(CMIOObjectPropertySelector(kCMIODevicePropertyStreams), .global, .main) | |
CMIOObjectGetPropertyDataSize(deviceId, &opa, 0, nil, &dataSize); | |
let numberStreams = Int(dataSize) / MemoryLayout<CMIOStreamID>.size | |
var streamIds = [CMIOStreamID](repeating: 0, count: numberStreams) | |
CMIOObjectGetPropertyData(deviceId, &opa, 0, nil, dataSize, &dataUsed, &streamIds) | |
return streamIds | |
} | |
func getJustProperty(streamId: CMIOStreamID) -> String? { | |
let selector = FourCharCode("just") | |
var address = CMIOObjectPropertyAddress(selector, .global, .main) | |
let exists = CMIOObjectHasProperty(streamId, &address) | |
if exists { | |
var dataSize: UInt32 = 0 | |
var dataUsed: UInt32 = 0 | |
CMIOObjectGetPropertyDataSize(streamId, &address, 0, nil, &dataSize) | |
var name: CFString = "" as NSString | |
CMIOObjectGetPropertyData(streamId, &address, 0, nil, dataSize, &dataUsed, &name); | |
return name as String | |
} else { | |
return nil | |
} | |
} | |
func setJustProperty(streamId: CMIOStreamID, newValue: String) { | |
let selector = FourCharCode("just") | |
var address = CMIOObjectPropertyAddress(selector, .global, .main) | |
let exists = CMIOObjectHasProperty(streamId, &address) | |
if exists { | |
var settable: DarwinBoolean = false | |
CMIOObjectIsPropertySettable(streamId,&address,&settable) | |
if settable == false { | |
return | |
} | |
var dataSize: UInt32 = 0 | |
CMIOObjectGetPropertyDataSize(streamId, &address, 0, nil, &dataSize) | |
var newName: CFString = newValue as NSString | |
CMIOObjectSetPropertyData(streamId, &address, 0, nil, dataSize, &newName) | |
} | |
} | |
func getDustProperty(streamId: CMIOStreamID) -> Data? { | |
let selector = FourCharCode("dust") | |
var address = CMIOObjectPropertyAddress(selector, .global, .main) | |
let exists = CMIOObjectHasProperty(streamId, &address) | |
if exists { | |
var dataSize: UInt32 = 0 | |
var dataUsed: UInt32 = 0 | |
CMIOObjectGetPropertyDataSize(streamId, &address, 0, nil, &dataSize) | |
let data = UnsafeMutableRawPointer.allocate(byteCount: Int(dataSize), alignment: MemoryLayout<CFData>.alignment) | |
CMIOObjectGetPropertyData(streamId, &address, 0, nil, dataSize, &dataUsed, data); | |
let frameData = Data(bytesNoCopy: data, count: Int(dataSize), deallocator: .none) | |
defer { data.deallocate() } | |
return frameData | |
} else { | |
return nil | |
} | |
} | |
func setDustProperty(streamId: CMIOStreamID, newValue: Data) { | |
let selector = FourCharCode("dust") | |
var address = CMIOObjectPropertyAddress(selector, .global, .main) | |
let exists = CMIOObjectHasProperty(streamId, &address) | |
if exists { | |
var settable: DarwinBoolean = false | |
CMIOObjectIsPropertySettable(streamId,&address,&settable) | |
if settable == false { | |
return | |
} | |
let dataSizeNewValue: UInt32 = UInt32(newValue.count) | |
newValue.withUnsafeBytes { rawBufferPointer in | |
let rawPtr = rawBufferPointer.baseAddress! | |
CMIOObjectSetPropertyData(streamId, &address, 0, nil, dataSizeNewValue, rawPtr) | |
//defer { data.deallocate() } | |
} | |
} | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
if let device = getDevice(name: "Screegle Camera"), let deviceObjectId = getCMIODevice(uid: device.uniqueID) { | |
let streamIds = getInputStreams(deviceId: deviceObjectId) | |
print(device,deviceObjectId,streamIds) | |
if let firstStream = streamIds.first { | |
let oldValue = self.getJustProperty(streamId: firstStream) | |
self.setJustProperty(streamId: firstStream, newValue: "je suis content") | |
let newValue = self.getJustProperty(streamId: firstStream) | |
print(oldValue,newValue) | |
if let image = NSImage(named: "chamonix") { | |
print("image.size=",image.size) | |
if let data = image.imageJPEGRepresentation() { | |
let img = NSImage(data: data) | |
print(img!.size) | |
print("data len=",data.count) | |
self.setDustProperty(streamId: firstStream, newValue: data) | |
} | |
} | |
let newDataValue = self.getDustProperty(streamId: firstStream) | |
print(newDataValue?.count) | |
} | |
} | |
} | |
extension NSImage { | |
func imageJPEGRepresentation() -> Data? { | |
if let imageTiffData = self.tiffRepresentation, let imageRep = NSBitmapImageRep(data: imageTiffData) { | |
let imageProps = [NSBitmapImageRep.PropertyKey.compressionFactor: 0.7] // Tiff/Jpeg | |
// let imageProps = [NSImageInterlaced: NSNumber(value: true)] // PNG | |
//let imageProps: [String: Any] = [:] | |
let imageData = imageRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: imageProps) as Data? | |
return imageData | |
} | |
return nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment