VRChatプレゼンシステムのスライド表示ずれ問題の原因と対策

VRChat用プレゼンシステムUnaSlidesにて、一部ユーザーでスライドの表示がずれる問題が報告されています。詳細な調査を実施したところ、他のプレゼンシステムでも問題が再現することがわかったため、公益性を鑑みてnoteに原因と対策をまとめます。

なお直接の原因は分かりましたが、その真因は不明(追うことが不可能)です。本記事を読んでいただいた方でわかる方・ヒントになる情報をお持ちの方はご連絡いただけますと助かります。


時間がない方へ

  • 原因は一部の動画フォーマットとAVProVideoPlayer(VRChatで利用される動画プレーヤー)との相性に起因するものと推定

  • ズレが生じない動画フォーマットに変換するサービス(WebScreen等)を利用するか、自身でフォーマットを指定する(FFmpegの場合`-bf 0`オプション)

  • ズレが生じる動画であっても、YoutubeにアップロードしたURLを使用し、UnaSlidesのオフセットを0.5秒以上にすることでズレを抑止

特に重要な機会においては、ズレの生じない動画を使用することに加えて、バックアップ用にYoutubeにもアップロードいただくことをお勧めします。
WebScreenは10/2のアップデートにより上記ズレが生じない条件に適合する動画変換を行うようになったため、利用を推奨します。

(2023.10.11追記)また、手元のスライド資料がズレるか否かをチェックするサービスを公開しました。よくわからないときはこちらをご利用ください。

発生している事象

発表者が表示しているスライドに対して、一部ユーザーに異なるスライドが表示される場合があります。

  • 主なケースとしては、1スライド遅れて表示されるというもの。発表者1スライド目 / 閲覧者1スライド目→発表者2スライド目 / 閲覧者1スライド目→発表者3スライド目 / 閲覧者2スライド目・・・

  • 2スライド目以降ずっとのケースや、たまにスライドが同じになったりまたズレたりするケースがある

原因

UnaSlidesでは (スライド番号 - 1) * スライド表示時間 + オフセット時間 で動画再生位置を算出し、VideoPlayerにSetTime()しています。この式で算出された値とVideoPlayerの再生位置をログ出力することによりズレの原因を調査しました。
幸いにも手元で再現する環境を準備することができたため詳細な調査を行いました。そこでわかった事象は以下のとおりです。

  1. 発表者とズレたユーザーとは、動画の再生位置(1秒1スライド・0.5秒オフセットのとき、1スライド目は0.5sec目、2スライド目は1.5sec目)は一致しているにもかかわらず表示されている内容が異なる

  2. ズレたユーザーは、1スライド目の再生位置が(1sec・0.5offsetのとき)0.5secにならず、1.0sec付近になる。2スライド目以降は同じ再生位置

  3. まれに計算上の再生位置(スライド番号 - 1 + offset)と実際の再生位置が異なる場合があり(1スライド目1sec、2スライド目2sec)、このとき、表示がズレずに同じ表示内容となる

上記1.から当初は再生位置をセットしたあとに表示を切り替えるためのトリガーが不足しているのでは?とも思いましたが、2.や3.のような異常から動画再生そのものの不良を疑い、ズレが生じているスライドとは別の方式で同じPDFから変換したものを使用したところ、ズレが生じなくなりました。それぞれのフォーマットは以下のとおりです。

  • ズレあり: FFmpeg / H.264 / Highプロファイル / 4FPS

  • ズレなし: cv2 / MPEG-4 / Simpleプロファイル / 1FPS

また、別のスライドも上記2通りでエンコードし、ズレが生じる・生じないの結果を一致させることができたほか、名前は伏せますが他のスライドシステムでもズレの状況が一致したため、動画エンコードとVRChatの動画再生上の仕様に起因するものと結論づけました。また追加で調査したところ、以下が分かりました。

  • ズレなし: FFmpeg / H.264 / baselineプロファイル / 4FPS

  • ズレなし: ズレあり動画をYoutubeにアップロードしたもの

その他のプロファイルやコーデック等についての調査は未済です。

調査結果の追記(2023.10.3)

追加で調査した結果、新たに判明したこと・考察したことを追記します。直接の原因に至れた可能性があります。少なくとも「こう考えると辻褄があう」というところまできました。

Bフレームの有無がズレに繋がっている

のりたまさんから、GOP長がズレに繋がっている可能性があるという情報をいただきました。

この情報をヒントにFFprobeを使用して動画のフレーム構成を確認したところ、ズレが生じる動画にはBフレームがあり、ズレない動画にはBフレームが含まれないことがわかりました。Bフレームというのは前後のフレームを参照してその差分を保持するフレームのようです。ほかに全ての情報を含むIフレーム、前のIフレームの差分を保持するPフレームがあります。
また、baselineプロファイルを使用するとズレなくなる件についても、baselineプロファイルを使用するとBフレームが使用されないことに付合します。
したがってBフレームの使用がズレを引き起こす要因として強く推定されます
しかしながら、Youtube動画にはBフレームが含まれるにも関わらず、ズレが解消しました。この件については次の事項が関係していると考えています。

先頭4フレームが再生不可と思われる

フレームレートを変更したとき、ズレ再現環境では1スライド目の再生ヘッド位置が常に4フレーム分後ろになっていることがわかりました。6fpsのとき0.67sec、3fpsのとき1.33secとなり、4fpsで1スライド目が1.0secとなるのはこれが原因とわかりました。
つまり1スライド目の位置として0.5秒目で停止しようとしても、いずれも4フレーム目以内であり、5フレーム目まで飛ばされているような挙動が考えられます。それを裏付けるように10fpsで試したところ0.5秒目で停止させることができました。
ただしYoutubeの動画については4フレームではなく2フレームのようで、6fps(Youtubeの最小フレームレート)のとき0.33secで停止しました。

※2023.10.11追記:FFprobeでの分析結果を比較したところ、`has_b_frames`の値がズレるフレーム数に影響しているようです。FFmpeg標準ではhas_b_frames=2、Youtube動画は1でした。ためしにFFmpegでエンコードする際に`-bf 1`を指定したところ、ズレが2フレームとなりました。
なおhas_b_frames=2のときのフレーム構成はI→B→B→P…、1のときはI→B→P→Pとなります。ただし、Youtube動画はBフレームが2つ連続しており、ズレ方は実態よりもヘッダー情報?的なものに依存している模様です。

ズレが発生するメカニズム

追加の調査結果からメカニズムを推定することができました。イメージしやすいように以下のとおり図示します。

Bフレームの使用により先頭4フレームの再生可否に影響が出ることがズレの原因と推定

このメカニズムを前提に対策を検証したところズレの発生を抑止することができましたので、以下にその方法と仕組みを記載します。

対策(2023.10.3)

1. Bフレームを使用しない動画を使用する

これが根本的な対策です。とはいえ、動画変換サービスを使用している場合など内部仕様についてまでコントロールすることはできません。私もBフレームという仕組みを知りませんでした。(差分を保持して圧縮している、程度の知識止まり)
WebScreenというのりちゃんさんが運営するVRChatスライド用のPDF→動画変換サービスは、10/2のアップデートによりBフレームを使用しないことが明言されています。検証したところズレも発生しておりません。そのため、こちらのご利用を強く推奨します。

FFmpegを使用して自身で動画変換を行う場合は、オプション`-bf 0`を付すまたは`-profile:v baseline`を付すことでBフレームが使用されなくなります。旧対策に掲載したcv2によるPythonコードも同様です。

2. フレームレートを増加する

フレームレートを増加することで、4フレーム分のズレを相対的に小さくすることができます。たとえば10fpsにすれば0.4secのズレになり、UnaSlidesのデフォルトのOffset値0.5secに収まるためズレが発生しなくなります(検証済み)。

フレームレートの増加により4フレームの影響を小さくする

3. 1スライドあたり秒数を長くする

同一フレームレートであってもスライドあたり秒数が長ければその分使用されるフレーム数が大きくなる(4fps・2秒1スライドのとき、8フレーム)ため、フレームレート増と同じ効果を得ることができます。

スライドあたり秒数を増やすとフレームレート増加と同じ効果が得られる

4. Youtubeにアップロードした動画を使用する(オフセット値注意)

既存の動画変換済みスライド資産を活用する場合に有効な手段です。Youtube動画は6fpsで先頭2フレーム(=0.33sec)が再生できないように見受けられるため、余裕を考慮してオフセット値を0.5秒以上にして利用してください。

旧対策(~2023.10.1掲載)

ズレが生じないフォーマットの動画使用していただくことになります。上記調査を踏まえて、以下のようになります。

  1. FFmpegを使用してH.264形式にする場合、baselineプロファイルを指定する(-profile:v baseline)

  2. cv2等を利用し、MPEG-4 (Simple Profile)で動画作成する

  3. YoutubeにアップロードしたURLを使用する

上記1〜3のいずれか、または (1 or 2) + 3 で対策いただくことでズレ問題は解消すると考えています。

ご参考までに、2.のための簡易的なソースコードを掲載します。cv2を使用しています。この処理で動画に変換した場合、ズレが生じないことを確認済みです。

# 事前に pip install opencv-python==4.6.0.66 でopencvをインストールしてください

import sys
import glob
import cv2

fps = 1.0   # 1フレームあたりのスライド数。1.0で1スライド/1秒、2.0で2スライド/1秒

def image2mp4(image_dir, mp4_dir, filename):
    image_files = sorted(glob.glob(f"{image_dir}/*.png"), reverse=True)

    print(len(image_files))

    height, width, _ = cv2.imread(image_files[0]).shape[:3]
    video_writer = cv2.VideoWriter(
        f"{mp4_dir}/{filename}.mp4",
        cv2.VideoWriter_fourcc('m','p','4','v'),
        fps, (width, height))

    for image_file in image_files:
        img = cv2.imread(image_file)
        video_writer.write(img)
    video_writer.release()

また、UnaSlidesではズレが生じた場合に事後的にオーディエンス側で調整できるような仕組みをあわせて検討したいと思います。

お願い

まだサンプル数が少ないため、皆様から情報をお寄せいただけますと幸いです。もし情報をご提供いただけるとすれば、特に以下の情報がありがたいです。

  • ズレが生じた動画のURLと、その時の状況

  • ズレが生じた方のVRChatのログ。UnaSlidesのチェックボックス「Debug」をオンにすることで出力されるようになります

  • 上記対策の効果。ズレが解消された・解消されなかった・他の不具合が生じたなど

  • 上記の調査内容とは異なる、またはコンフリクトする事象に関する情報

  • VRChat上における動画フォーマットと再生に関する情報

私はVRChatをはじめて割と早い段階でUnaSlidesを開発しました。VRChatにおける「集会」の文化に魅せられて、それに少しでも貢献したいと考えたことがその理由です。皆様が安心してプレゼンできる・快適にプレゼンを見られるように一刻も早く信頼できる対策を確立したいと考えておりますので、皆様からもお力添えをいただけますと幸いです。何卒よろしくお願いします。

謝辞

深夜まで検証のご支援やワールドの改修をいただいたあずきもちさん、不具合のご連絡とやはり深夜まで調査・検証にご協力いただいたクルツさん、直接の原因につながる重要な情報提供をしていただいたのりたまさん、動画変換サービスをすぐに対応してくださったのりちゃんさんをはじめ、ご協力をいただきました皆様に心から感謝申し上げます。ありがとうございます。

以上

この記事が気に入ったらサポートをしてみませんか?