Aeonは、動画、画像、テキスト、図形をレイヤーとして重ね、位置・拡大率・回転・不透明度へキーフレームを設定するモーショングラフィックスエディタです。iOS版の画面はSwiftUIですが、合成の中心はCore Graphicsベースの CompositionRenderer です。
iOS実装は、同じ render 関数をプレビューの UIGraphicsImageRenderer と、書き出し用 CVPixelBuffer 上の CGContext から呼びます。Android版でもこの境界を残し、Composeの画面とは別に android.graphics.Canvas ベースのコンポジターを作りました。
同じ時刻なら、プレビューと書き出しで同じ描画関数を呼ぶ
Android側のレンダラーは、プロジェクト、時刻、Canvas、動画フレーム、画像供給関数を受け取ります。背景を塗り、最背面からレイヤーをたどり、各レイヤーの位置・回転・拡大率・反転・不透明度を適用します。テキスト、画像、動画、図形の描画に加え、クリッピング、ブレンド、影、マスク、線形・円形グラデーションもこの経路へ集めています。
Composeはエディタの状態と操作UIを担当しますが、完成画像の画素をComposeの部品へ依存させていません。そのため、720pxを上限とするプレビューBitmapと、指定解像度の書き出しフレームが同じレイヤー評価を通ります。
再生はChoreographer、動画フレームはMediaMetadataRetriever
プレビュー時刻は Choreographer のフレームコールバックで進めます。動画レイヤーは MediaMetadataRetriever から対象時刻のフレームを取得し、音声レイヤーと動画音声は MediaPlayer で同期させます。iOS側の再生クラスを一つのAndroid APIへ置換するのではなく、「表示時刻」「動画の画」「聞こえる音」を別々のAndroid機能へ割り当てた形です。
CanvasからH.264 + AACのMP4を作る
書き出しでは、各フレームの時刻を計算し、動画素材の該当フレームを集め、共通レンダラーでARGB_8888のBitmapへ描きます。その画素をBT.601のYUV420 Flexibleプレーンへ変換し、H.264用 MediaCodec の入力バッファへ渡します。
エンコーダーが返した映像サンプルにはフレーム番号から計算したPTSを付け、MediaMuxer のMP4トラックへ書き込みます。音声は複数レイヤーを16bitステレオPCMへ混ぜ、音量を反映してAAC-LCへエンコードした後、同じMP4へ格納します。iOS版の AVAssetWriter とコードは異なっても、「時刻ごとに合成し、映像と音声を一つのファイルへする」という約束は同じです。
VisionはML Kit Subject Segmentationへ
被写体の自動選択は、iOSのVision相当としてML Kit Subject Segmentationを使います。Android実装は enableForegroundBitmap() を指定し、得られた前景Bitmapを透過PNGへ圧縮します。画像を独自サーバーへ送る処理はなく、リポジトリではPlay services経由のオンデバイス処理として構成されています。
同じJSON構造でも、素材デコーダーは同じではない
Android版は filesDir/Projects/<UUID>/ に project.json、素材、サムネイルを保存し、UUIDを文字列、日時をepoch millisとしてiOS版と同じ構造のモデルを保ちます。一方、Androidの画像デコーダーではPSDを直接読まないため、PNG、JPEG、WebPなどへの変換を前提にしています。「保存形式が似ている」ことと「全素材を同じように開ける」ことは別です。