コミュニティ

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

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
        })
    }
satoshi0212
カリフォルニア州立大学コンピュータサイエンス学部卒業。 都内IT系開発会社で勤務後フリーエンジニアになり、バンド活動、動画イベント主催など。その後再びIT系開発会社に就職。365日iOSアプリ開発しています。
cyberagent
サイバーエージェントは「21世紀を代表する会社を創る」をビジョンに掲げ、インターネットテレビ局「AbemaTV」の運営や国内トップシェアを誇るインターネット広告事業を展開しています。インターネット産業の変化に合わせ新規事業を生み出しながら事業拡大を続けています。
http://www.cyberagent.co.jp/
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
ユーザーは見つかりませんでした