Touch Barで卯月をつんつんしてから一年が経った。 今年の新ネタはiPhone X関連かと思っていながら予約に負けたので,穏やかな心でイープラスに勝利しつつ文化の日となった。
というわけで今日は文化の日につくった,↓のツイートにあるデレステフォトスタジオのリアルタイム透過プレイヤー,PhotoStudioPlayerの話を書く。
きっかけ
ちょうど前日に,デレステにフォトスタジオという新機能の追加が発表されたところだった。
カメラの視点が自由に回せてズームインアウトもできるということで,従来カメラに対しては(縦化以外では)なにもできなかったデレステの,スクショの幅が増えた。当然MVのようにキャプチャしてLoveLiverで切り取ればライブフォトにもできる。無限に遊べる。
9年前のニコマス
さらにアイマストドンのタイムラインをみていると,どうやらブルーバックにできるらしく,スクショを経由して,アイドルだけを切り抜いた透過画像を作れるとのこと。これはもしや・・・昔のニコマスの・・・とiMacのディスクをSpotlightし,かつて作った透過プレイヤーのスクショとコードを発見した。
どうやら遅くとも2008年7月ごろのニコマスにはブルー(グリーン)バック素材の動画があったようで,私はいまはなき(なくはない?)当時最先端のQuartz Composerで,動画をリアルタイムに透過しながら再生するプレイヤーをつくっていたようだ。シェーダーめいたなにやらで色変換を記述することで,LLVM的ななにかでGPUに合わせてコンパイルされ透過がGPU処理となる,ような触れ込みだったはず。 当時のmacOSのAPIではこのQuartz Composerのファイルをあたかも動画ファイルのようにプレイヤーのViewに読み込ませることができた。あとは非矩形のウインドウを描く要領で背景透過ウインドウにすれば,アイドルだけがデスクトップに描画されるという仕組み。(→このプロジェクトをHigh SierraとXcode 9でビルドして動かす話は,老人会LT需要があったらやりますよ?)
PhotoStudioPlayer構想
あれから9年経ったいまならもっとすごいことが出来るのではないか。iPhone画面はQuickTime Playerで60fpsでプレビューとキャプチャができる。その経路をフックできれば,iPhoneの画面をリアルタイムに透過するプレイヤーができそう。さらにiPhoneでフォトスタジオのアイドルをフリックすれば,アイドルの向きを変えることもできる。
技術的課題を取り除いて透過プレイヤーを支える技術,の構築にひとまずフォーカスする。明らかな障壁はキャプチャ方法である。
- QuickTime Playerで録画する → ふつうに実現可能だが,録画ファイルよりはリアルタイム処理したい
- https://github.com/libimobiledevice/libimobiledevice をつかう → ライトニングの通信を解析した系があったような,と見てみたが,iOS 11では動かせなかった。深入りしない。
- AirPlayをつかう → 出来そうだが圧縮で不利なのと,調査コストは他に比べて低いと分かりきっているわけでもない
- iOSScreenCaptureAssistantつかう → QuickTime Playerの録画に使われるプロセス。どこが不可能ポイントかすら分からないのでそれが明らかになるまで調べてもいい。
iOSScreenCaptureAssistantの調査
なぜかふつうにmanがある。
iOSScreenCaptureAssistant is used by the CoreMediaIO Device Abstraction Layer Plug-In that provides audio/video capture from iOS devices using AVFoundation Capture APIs. The process allows multiple applications to simultaneously capture from the same iOS device.
複数プロセスからの同時キャプチャにも配慮した設計らしい。実際にQuickTime Playerを2プロセス起動すると,2画面で同時に(やや遅延に差は出る)同じiPhoneをキャプチャできる。 XPC的な制約があるのかはともかく,機能は期待できる。
このプロセスは /System/Library/LaunchDaemons/com.apple.cmio.iOSScreenCaptureAssistant.plist から起動されるようだ。
manのCoreMediaIOとは
https://developer.apple.com/library/content/samplecode/CoreMediaIO/Introduction/Intro.html
CoreMediaIO DAL (Device Abstraction Layer) とは https://developer.apple.com/library/content/samplecode/CoreMediaIO/Listings/Sources_Extras_CoreMediaIO_DeviceAbstractionLayer_Devices_Sample_PlugIn_CMIO_DP_Sample_PlugIn_cpp.html
なるほどわからないが‥ /System/Library/Frameworks/CoreMediaIO.framework/Versions/A/Resources/iOSScreenCapture.plugin にあるキャプチャプラグインを複数プロセスから触れるように,またはカメラとして認識できるように,ラップするものらしい。pluginバンドルとassistantプロセスのパターンは,CoreMediaIO DALではよくあるパターンのようだ。
OBSの調査
ところで3rd partyアプリである OBS - Free and open source software for live streaming and screen recording https://obsproject.com/mantis/ では,iPhoneのキャプチャとクロマキー合成が可能であるとみつける。iPhoneがソースに選べるとは知らなかったが,CoreMediaIO DALになっている仕組みからいって,3rd partyから読む手段はあるのではないか?と機能を探していて,まさにそのものがみつかった。
やりたいことのほとんどは,これで出来ている。画像の上にリアルタイムクロマキー透過したiPhoneを重ねている。あとはウインドウに切り出せれば。 ありがたいことにOBSのソースコードはgithubで公開されている。CoreMediaIO関連のコードをいくつか試していたので,見当をつけてgrepすると,iPhoneをソースとして検索するには,前段階である設定が必要であるとわかる。
さらにありがたいことに,この情報がWWDCで出ていたこともrefしてある。OBSはGPLだが,この行については完全にWWDCセッションのコピペなので,GPLにこだわらずもらってくる。 AppleがWWDCでこの情報を出したとき,私はWWDC現地にいたのだが,「Camera Capture: Manual Controls」というタイトルでこの話を出すのは初見回避というかなんというか。完全にウォッチ対象外であった。
(5:34 -)
Swift最小実装化
ともかくこれをpure Swiftで実装しなおして動くのを確認した。
import Cocoa | |
import CoreMediaIO | |
import AVFoundation | |
class ViewController: NSViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
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) | |
let devices = AVCaptureDevice.devices() | |
NSLog("%@", "devices = \(devices)") | |
} | |
} |
これでAVCaptureDeviceまで来ているので,iOSアプリ勢にもおなじみのポイントまで辿り着いた。 おそらく最も簡単に低負荷再生するにはAVCaptureVideoPreviewLayerであろうということで,そこに置いて,クロマキーなしのキャプチャ表示が実現した。まるでQuickTime Playerである。

クロマキー透過
AVCaptureVideoPreviewLayerのまま軽量な変換ができるとは限らない。これは軽量だが通常のCALayerではない。もちろんキャプチャセッションからサンプルバッファーをもらって画素のメモリを見てしまえばなんらかは変換可能だろうが,できればコードから直接メモリをみたりしたくない。そのあたりは裏でGPUをつかってよろしくやってくれる期待感をこめた実装にしたい。
CoreImageのCIFilterを使ってみる。Appleがオススメするクロマキーの例は,CIColorCubeという色変換のLook Up Tableのフィルタを使うことらしい。
Subclassing CIFilter: Recipes for Custom Effects
ただしそのままではlayerにCIFilterは当たらない‥と思っていたがどうやら,親ビューのlayerUsesCoreImageFiltersをtrueにすれば適用されるようになる。
view.layerUsesCoreImageFilters = true previewLayer.filters = [ChromaKeyFilter.filter()]
これで,昔のように,ウインドウの背景色をclearにしてnon-opaqueにして,アイドルのみが描画される透過プレイヤーが完成した。
PhotoStudioPlayerアプリ・コード公開
iPhoneとMacがあればビルドせずすぐ使えるようにアプリを公開するのと,アプリはともかくこの一連の技術を使ってさらに別のものを作れるようにするのと,という観点で,githubに公開・リリースしてtwitterに貼った。
PhotoStudioPlayer公開その後
透過した状態の静止画キャプチャ保存もあったら便利ではあると思って,機能追加した。その話はまた。
@mzp のPull Requestによって,ウインドウの常時前面表示と,複数デバイスのキャプチャ対応(切り替え,ではなく同時表示も可能)に対応した。
iPhone + Mac + デレステ,という組み合わせ以外のプラットフォームにも適用したいという動きがあるようで,いくつか既に実装されていた。 Windows:
Android:なんとなく,手前のブルーバックiPhone + 奥のデスクトップ画面 + 卯月,という文法で動画投稿されているのが,おもしろい。
GitHub Trending Swift Developer
かつてないほどtwitterからgithubにページビューが流れたなと思い,ふとGitHubのトレンディングのページをみると,Trending Swift Developerの23位に入っていた。repoの⭐️は48になった。
アイマスハッカソン
このあたりの話を含めて,技術はもちろん,よりアイマスに寄った方向性の話も,もっとしたいと思います。 今年の冬のアイマスハッカソンは12月16日(土)とのことなので,是非そこで会いましょう。おはなししましょう。