Cocoa - 摄像头开始录制时检测事件

时间:2014-05-31 18:41:37

标签: objective-c cocoa video-capture

在我的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一样检测同一时刻,一旦相机提供预览,动画就会启动。

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

2 个答案:

答案 0 :(得分:3)

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

对于初学者来说,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。

答案 1 :(得分:1)

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

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