70
77

More than 3 years have passed since last update.

macにケーブル接続したiPhoneの画面をリアルタイム取得する

Last updated at Posted at 2020-10-11

macにケーブル接続したiPhoneの画面をリアルタイム取得するミニマム実装を作りました。

これまではQuickTime Playerを起動し「新規ムービー収録」からiPhoneを選択するなどのアプリ外での取り回しが必要だったのですが、これで自作プログラムで実現可能となります。

output.gif

GitHubにアップしています。
https://github.com/satoshi0212/DeviceCameraMonitorSample

この実装含め、仮想カメラ/AR/映像表現などの情報更新はTwitterで投稿しています。
https://twitter.com/shmdevelop

実装ポイント

プロジェクト設定

「Hardware」「Camera」 の選択が必要。

スクリーンショット 2020-10-11 23.41.04.png

plist

plistに Privacy - Camera Usage Description を追加してください。

スクリーンショット 2020-10-12 2.26.56.png

Device探索時設定

AVCaptureDevice.DiscoverySession 実行前に以下を指定することでオプトインで外部デバイスが表示されるようになります。


        var prop = CMIOObjectPropertyAddress(
            mSelector: CMIOObjectPropertySelector(kCMIOHardwarePropertyAllowScreenCaptureDevices),
            mScope: CMIOObjectPropertyScope(kCMIOObjectPropertyScopeGlobal),
            mElement: CMIOObjectPropertyElement(kCMIOObjectPropertyElementMaster))
        var allow: UInt32 = 1;
        CMIOObjectSetPropertyData(CMIOObjectID(kCMIOObjectSystemObject), &prop, 0, nil, UInt32(MemoryLayout.size(ofValue: allow)), &allow)

そして以下のパラメータで探索するとdevicesにiPhoneが含まれています。
見つかったdevicesを modelIDmanufacturer で適宜フィルタするとiPhoneデバイスが特定できます。


        let devices = AVCaptureDevice.DiscoverySession(deviceTypes: [.externalUnknown], mediaType: nil, position: .unspecified).devices
        if let device = devices.filter({ $0.modelID == "iOS Device" && $0.manufacturer == "Apple Inc." }).first {
            ...
        }

ただし起動直後や探索直後はiPhoneが見つからない場合があるため AVCaptureDeviceWasConnectedNotification のnotificationをobserveする必要もありました。


        let nc = NotificationCenter.default
        nc.addObserver(forName: NSNotification.Name(rawValue: "AVCaptureDeviceWasConnectedNotification"), object: nil, queue: .main) { (notification) in
            print(notification)
            guard let device = notification.object as? AVCaptureDevice else { return }
            ...
        }

余談: 表示用リサイズ

アップした実装では画面表示用にリサイズしました。

高さを固定値として比率を計算し幅を算出しimageViewのサイズ指定。
画像の方が CGAffineTransform でサイズ変換しています。


    private func resizeIfNeeded(w: CGFloat, h: CGFloat) {
        guard targetRect == nil else { return }
        let aspect = h / fixedHeight
        let rect = CGRect(x: 0, y: 0, width: floor(w / aspect), height: fixedHeight)
        imageView.frame = rect
        targetRect = rect
    }

    ...

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        connection.videoOrientation = .portrait

        DispatchQueue.main.async(execute: {
            let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
            let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
            let w = CGFloat(CVPixelBufferGetWidth(pixelBuffer))
            let h = CGFloat(CVPixelBufferGetHeight(pixelBuffer))
            self.resizeIfNeeded(w: w, h: h)

            guard let targetRect = self.targetRect else { return }
            let m = CGAffineTransform(scaleX: targetRect.width / w, y: targetRect.height / h)
            let resizedImage = ciImage.transformed(by: m)
            let cgimage = self.context.createCGImage(resizedImage, from: targetRect)!
            let image = NSImage(cgImage: cgimage, size: targetRect.size)
            self.imageView.image = image
        })
    }
70
77
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
70
77