当相机开始记录时,可可豆检测事件



在我的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];

然而,我需要检测相机预览可用的确切时间。

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

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

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

对于初学者来说,AVFoundation太高了,你需要下降到一个较低的水平,CoreMediaIO。在这方面没有很多文档,但基本上你需要执行几个查询。

要做到这一点,我们将使用调用的组合。首先,CMIOObjectGetPropertyDataSize让我们获得接下来要查询的数据的大小,然后我们可以在调用CMIOObjectGetPropertyData时使用它。要设置get属性数据大小调用,我们需要从顶部开始,使用以下属性地址:
var opa = CMIOObjectPropertyAddress(
    mSelector: CMIOObjectPropertySelector(kCMIOHardwarePropertyDevices),
    mScope: CMIOObjectPropertyScope(kCMIOObjectPropertyScopeGlobal),
    mElement: CMIOObjectPropertyElement(kCMIOObjectPropertyElementMaster)
)

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

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

从这里开始,我们需要等待,直到我们得到一些数据,所以让我们执行忙循环:

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)

一旦我们在执行中通过了这一点,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
    }
}

最后清理devices

free(devices)

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

var CMIOObjectPropertyAddress(
    mSelector: CMIOObjectPropertySelector(kCMIODevicePropertyDeviceIsRunningSomewhere),
    mScope: CMIOObjectPropertyScope(kCMIOObjectPropertyScopeWildcard),
    mElement: CMIOObjectPropertyElement(kCMIOObjectPropertyElementWildcard)
)

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

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

用户jer之前的答案绝对是正确的答案,但我只是想添加一个额外的重要信息。

如果侦听器块注册到CMIOObjectAddPropertyListenerBlock,则当前运行循环必须运行,否则不会收到任何事件,并且侦听器块永远不会触发。

最新更新