Skip to content

Instantly share code, notes, and snippets.

@ldenoue
Created June 22, 2022 10:48
Show Gist options
  • Save ldenoue/84210280853f0490c79473b6edd25e9d to your computer and use it in GitHub Desktop.
Save ldenoue/84210280853f0490c79473b6edd25e9d to your computer and use it in GitHub Desktop.
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()
}
}
...
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