Cocoa - 当相机开始录制时检测事件

nrp*_*rph 1 cocoa video-capture objective-c

在我的 OSX 应用程序中,我使用下面的代码来显示来自相机的预览。

  [[self session] beginConfiguration];

  NSError *error = nil;
  AVCaptureDeviceInput *newVideoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];

  if (captureDevice != nil) {
    [[self session] removeInput: [self videoDeviceInput]];
    if([[self session] canAddInput: newVideoDeviceInput]) {
      [[self session] addInput:newVideoDeviceInput];
      [self setVideoDeviceInput:newVideoDeviceInput];
    } else {
      DLog(@"WTF?");
    }
  }

  [[self session] commitConfiguration];
Run Code Online (Sandbox Code Playgroud)

但是,我需要检测来自相机的预览可用的确切时间。

换句话说,我试图检测与 OSX 下的 Facetime 相同的时刻,一旦相机提供预览,动画就会开始。

实现这一目标的最佳方法是什么?

jer*_*jer 9

我知道这个问题真的很老,但是当我在寻找同样的问题时我也偶然发现了它,我已经找到了答案,所以在这里。

对于初学者来说,AVFoundation 的级别太高了,你需要降到更低的级别,CoreMediaIO。关于此的文档并不多,但基本上您需要执行几个查询。

为此,我们将使用调用的组合。首先,CMIOObjectGetPropertyDataSize让我们获取接下来要查询的数据的大小,然后我们可以在调用CMIOObjectGetPropertyData. 要设置获取属性数据大小调用,我们需要从顶部开始,使用此属性地址:

var opa = CMIOObjectPropertyAddress(
    mSelector: CMIOObjectPropertySelector(kCMIOHardwarePropertyDevices),
    mScope: CMIOObjectPropertyScope(kCMIOObjectPropertyScopeGlobal),
    mElement: CMIOObjectPropertyElement(kCMIOObjectPropertyElementMaster)
)
Run Code Online (Sandbox Code Playgroud)

接下来,我们将设置一些变量来保存我们需要的数据:

var (dataSize, dataUsed) = (UInt32(0), UInt32(0))
var result = CMIOObjectGetPropertyDataSize(CMIOObjectID(kCMIOObjectSystemObject), &opa, 0, nil, &dataSize)
var devices: UnsafeMutableRawPointer? = nil
Run Code Online (Sandbox Code Playgroud)

从现在开始,我们需要等到我们得到一些数据,所以让我们忙循环:

repeat {
    if devices != nil {
        free(devices)
        devices = nil
    }
    devices = malloc(Int(dataSize))
    result = CMIOObjectGetPropertyData(CMIOObjectID(kCMIOObjectSystemObject), &opa, 0, nil, dataSize, &dataUsed, devices);
} while result == OSStatus(kCMIOHardwareBadPropertySizeError)
Run Code Online (Sandbox Code Playgroud)

一旦我们在执行中通过了这一点,devices将指向潜在的许多设备。我们需要遍历它们,有点像这样:

if let devices = devices {
    for offset in stride(from: 0, to: dataSize, by: MemoryLayout<CMIOObjectID>.size) {
        let current = devices.advanced(by: Int(offset)).assumingMemoryBound(to: CMIOObjectID.self)
        // current.pointee is your object ID you will want to keep track of somehow
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,清理 devices

free(devices)
Run Code Online (Sandbox Code Playgroud)

现在,您需要使用上面保存的对象 ID 进行另一个查询。我们需要一个新的房产地址:

var CMIOObjectPropertyAddress(
    mSelector: CMIOObjectPropertySelector(kCMIODevicePropertyDeviceIsRunningSomewhere),
    mScope: CMIOObjectPropertyScope(kCMIOObjectPropertyScopeWildcard),
    mElement: CMIOObjectPropertyElement(kCMIOObjectPropertyElementWildcard)
)
Run Code Online (Sandbox Code Playgroud)

这告诉 CoreMediaIO 我们想知道设备当前是否正在某处运行(读取:在任何应用程序中),通配其余字段。接下来我们进入查询的核心,camera下面对应于您之前保存的 ID:

var (dataSize, dataUsed) = (UInt32(0), UInt32(0))
var result = CMIOObjectGetPropertyDataSize(camera, &opa, 0, nil, &dataSize)
if result == OSStatus(kCMIOHardwareNoError) {
    if let data = malloc(Int(dataSize)) {
        result = CMIOObjectGetPropertyData(camera, &opa, 0, nil, dataSize, &dataUsed, data)
        let on = data.assumingMemoryBound(to: UInt8.self)
        // on.pointee != 0 means that it's in use somewhere, 0 means not in use anywhere
    }
}
Run Code Online (Sandbox Code Playgroud)

有了上面的代码示例,您应该有足够的时间来测试相机是否在使用中。您只需要获得一次设备(答案的第一部分);但是,检查它是否正在使用中,您必须在需要此信息的任何时候进行。作为一个额外的练习,考虑CMIOObjectAddPropertyListenerBlock使用我们在上面使用的使用中的属性地址的事件更改通知。

虽然这个答案对于 OP 来说已经晚了近 3 年,但我希望它对未来的人有所帮助。这里的例子是用 Swift 3.0 给出的。