Eureka Engineering
Published in

Eureka Engineering

Pairsのアイキャッチを支える動画合成システムのアーキテクチャを実装してみて

こんにちは!Eureka Backend Engineer 23年入社予定のぺりーです。

今回は、2022年の4月末にリリースされたPairsの新機能アイキャッチを支える動画合成システムのアーキテクチャについて書いてみます。

目次

  1. 新機能のアイキャッチとは
  2. 技術要件
  3. 検討した選択肢
    3.1 クライアントで合成する場合
    3.2 バックエンドで合成する場合
  4. 実装手法決定の根拠
    4.1 バックエンドの実装手法の洗い出しと選定理由
  5. 採用したアーキテクチャ
  6. 課題と解決方法
  7. 振り返って

こちらの記事は2022年7月20日のイベントでの発表内容の詳細です。

1. 新機能のアイキャッチとは

アイキャッチさがす画面表示

さがす画面で自分のプロフィールをフレーム付きで表示し、趣味や好みのあうお相手からの注目を集められる機能です。

機能の詳細は下記ヘルプページをご覧ください。

2. 技術要件

プロフィール選択画面
  • ニアリアルタイムで静止画の上に動画を合成する
  • ピンチインピンチアウトして合成前に画像を調整できる
  • 合成前にクライアントでのプレビューができる
  • コメントを審査しなければならない
アイキャッチコメント入力画面

3. 検討した選択肢と実装手法決定の根拠

ニアリアルタイムで動画を合成する場合は大きく二つの実装方法がありました。

  1. クライアントで合成する
  2. バックエンドで合成する

まずはそれぞれの方法のメリットデメリットを整理します。

3.1 クライアントで合成する場合

動画編集SDKを利用して動画を合成しAPI経由でアップロードする。

Pros:

  • プレビューと、さがす画面で表示する際の整合性が取れる
  • バックエンドで動画合成する際の負荷をクライアントが受け持てる

Cons:

  • 合成速度や合成できるかどうかが端末依存になってしまう可能性がある
  • 動画の検品が必須
  • リクエストのペイロードが重くなる
  • 動画編集SDKを使用する場合、料金がかかる

プレビュー画面で合成した動画をそのままAPIでアップロードするので、他のユーザーがさがす画面からみた時に問題が起こる可能性が低いと言えます。
実際、プレビューとの整合性が取れないことを検知することは難しいため、これは大きなメリットとなります。

一方、アップロードされた動画を検品してPairsアプリで作成されたものと保証することは技術的に困難で大きなデメリットになります。
これを解決しなければ、不適切な内容の動画をアップロードされるリスクが発生します。

また、社内オペレータに審査をしてもらうこともできますが、少なくとも5秒ある動画を目視で確認すると時間がかかってしまう上に利用件数が増えた場合、運用できなくなってしまいます。

次にバックエンドで合成する場合を検討してみます。

3.2 バックエンドで合成する場合

Pros:

  • 端末に依存しない
  • 動画の検品が不要
  • 審査を非同期に行える
  • 不具合調査がしやすい
  • 合成失敗時にリトライできる
  • クライアント側で動画編集SDKを使う際と比較してパフォーマンスチューニングがしやすい

Cons:

  • クライアントでのプレビューに不整合が生じる可能性がある

主にクライアントで合成する際のメリットがデメリットに、デメリットがメリットになっています。

4. 実装手法の根拠

以上を踏まえた結果、今回はニアリアルタイムで動画を合成し、コメント審査を完了させ、機能を利用可能にする必要があるため、バックエンドでの合成がより良いと判断しました。

次にバックエンドでどのような実装手法があるか洗い出して整理しました。

4.1 バックエンドの実装手法の洗い出しと選定理由

バックエンド実装案マトリクス図

AWS Elemental MediaConvert, AWS Batch on AWS Fargate, Lambdaこの辺りを実装手法として考え、メリットデメリットを比較しました。

今回は下記の理由からAWS Elemental MediaConvertは見送ることになりました。

  • プロフィール画像(静止画)の上に動画を合成しなければならないため、FFmpegが前処理に必要
  • AWS Elemental MediaConvertの機能が今回の要件を満たすか検証が必要

AWS Batch on AWS FargateとLambdaはどちらでも実装ができましたが、環境構築の手間、アイキャッチが使用可能になるまでの時間、初期のアクセス数など総合的に考えた結果、Lambda + Custom Layerを採用することに決定しました。

次にアーキテクチャを考えました。

5. 採用したアーキテクチャ

アーキテクチャ図

API — SQS — Lambda — S3の流れになるように設計しました。

API<->SQSのシーケンス図

APIおよびSQSではピンチイン・ピンチアウトした際の画像のバウンディングボックスを受け取り、座標ベースでの合成をおこなっています。

Custom Layerに登録したFFmpegとImageMagickはS3にzipを格納し、バージョンコントロールをしています。
パスを通して、S3にアップロードしたzipファイルをLambdaで使用すると宣言するだけでCustom Layerに登録することができます。(パスが通っていないことを忘れがち)

FFmpegは動画の合成、ImageMagickはバウンディングボックスをもとにトリミング・拡大・縮小に使用しています。(詳しくは記事後半で)

EurekaではGoが採用されることが多くメンテナビリティの観点からもDB更新を含めた一連の処理を全てGoに寄せたかったため、LambdaのランタイムはGoで動いています。
動画合成部分はos/execパッケージを使用しています。

合成処理サンプルコード

仮にLambdaで動画合成処理が何度もコケた場合は、Dead Letter Queueにmessageが積まれるようにしておき、DLQの長さでアラームを設定し、監視体制を整えました。

ユーザーの動画合成状況のステータスを変更するため、RDS Proxyによってコネクションプールを使用してAuroraにアクセスするようにしています。

SQS<->Lambdaシーケンス図

また、Lambdaでの合成処理を早くするため、パイプライン処理を行うようにしたり、プロフィール画像、合成するフレームのパスをSQSから受け取り、DBアクセスはステータスを更新するだけになるようにしました。

さらに、コメントの審査を非同期で行うことでどちらか遅かった方が完了したタイミングでユーザーに通知を飛ばすことができるようになりました。

実装としては概ね期待通りでしたが、開発中、リリース後にいくつか改善しないといけない点も見えてきました。

6. 課題と解決方法

クライアントとバックエンドで使用する素材が異なった

バックエンド側で動画合成する場合もクライアント側でプレビュー再生できるようにするため、クライアントでも合成する必要がありました。

当初は動画編集SDKを利用していたため盲点となっていたのですが、バックエンド側で用意したアルファチャンネル付きマトリョーシカファイル(透過情報を持ったmovファイルから抽出)はiOSアプリの標準UIコンポーネントで再生できなかったため、標準UIコンポーネントでオーバーレイできるエンコーディングに変更する必要がありました。

端末負荷

バックエンド側で動画合成する際も、さがす画面に動画が最大6つ並ぶため、端末が発熱したり、動画が再生されなくなったり、端末の充電の減りが速くなったりするなど利用されづらくなってしまう可能性がありました。

以下の方法で対応し、さがす画面が全てアイキャッチ利用者になっても問題なく動くことが確認できました。

  • 合成後動画のfpsチューニング
    モバイルの画面で劣化がわからない最大値を判断
  • ファイルサイズ圧縮
    API通信量・S3転送量・クライアントの見え方を総合的に判断
  • FastStart化してストリーミング再生

合成時間

プレビューでは合成動画が完成しているにも関わらず、動画合成に時間がかかってしまうと、アイキャッチの利用がすぐにできず、ユーザー体験が悪くなってしまいます。

以下の方法で解消しました。

  • コメントの審査と動画合成を非同期にする
  • SQSのDLQで合成失敗を検知・監視する
  • Lambda内でDBなど外部アクセスをなるべく行わない
  • SQSのbatchSizeをチューニングする
  • S3のリトライ処理を増やす

プレビューとの差を生じさせないようにする

Pairsではプロフィール画像などを適切な解像度にリサイズしてユーザーに表示しています。

一方、前述したようにLambdaでは直接S3のパスから画像データを取得しているため、解像度が異なり、バウンディングボックス通りにクロップしても、プレビューとは異なってしまいます。

解像度不一致による失敗例

また、静止画に動画を重ねるには静止画を動画に変換し、動画の上に動画を重ねる必要があるのですが、プロフィール画像の解像度がフレームの解像度にあっていなかった場合、動画が見切れたり、余白が生じてしまいます。(下の解像度に合わせて重ねるので)

解像度が一致している成功例

以上を踏まえて以下のような手順で解決しました。

  1. フレームを透過素材にする
    (透過でのみ使用するので事前に透過で保存しておく)
  2. プロフィール画像をクライアントで表示している解像度に拡大・縮小
  3. プロフィール画像をバウンディングボックスに合わせてクロップ
    (この時点でアスペクト比がフレームと同じになる)
  4. プロフィール画像をフレーム動画の解像度に合わせて拡大・縮小
    (見切れたり余白が生じるのを防止する)
  5. プロフィール画像をフレーム動画の長さの動画に変換
    (静止画の上に動画は重ねられない)
  6. 動画にフレーム動画を重ねる
  7. 1フレーム目をキャプチャする
    (合成後動画のファイルサイズが大きいため保存期間を短くし、画像で保存する)

7. 振り返って

ユースケースに応じて最適なアーキテクチャを選定するには、以下のことが大事だと改めて感じました。

  • さまざまな実装パターンを検討する
  • 実装パターンのメリットデメリットを整理する
  • クライアントサイドや先輩エンジニアとディスカッションする

これまで、動画をBackendエンジニアとして扱ってこなかったため、とてもチャレンジングで楽しかったです。
iOSエンジニアの Kenji Tayamaや、Backendエンジニアの Hiroki Kojimaに多くの相談に乗っていただきました。ありがとうございます!

参考

--

--

Learn about Eureka’s engineering efforts, product developments and more.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store