Deep Understanding of Seek

# はじめに

ExoPlayerでは以下のようにシーク操作を行うことができます。

```kotlin

player.seekTo(positionMs)
```

今回はこれが内部でどのようなことをやっていのかを詳しく解説します。

具体的には

 - 渡されたpositionMsはどれだけ正確にシークされるのか。また、なぜそのような正確性になるのか。
 - シーク処理にはどのくらい時間がかかるのか。また、なぜそのような時間がかかるのか。

を解説します。

注) ExoPlayer2.10.8で検証を行っています。
注) ExoPlayerでの話なので、他のPlayerではロジックや結果などが異なる可能性があります。


# シークの正確性

シークの正確性を知るにはまずpts(presentation time stamp)を知る必要があります。

動画は一枚の瞬間を表したframeと呼ばれる集合体で構成されていますが、映像はこのframeがパラパラ漫画のように連続的に描画されることによって映し出されます。そして、この一つ一つのframeはptsというframeが表示されるべき時間を表したタイムスタンプが紐付いており、その時間になったタイミングで、decodeされたframeを描画する必要があります。

つまり、映像が持つ最小の時間単位はこのptsということになります。
(pts以外にもdts（decode time stamp）なども存在します。)

この映像が持つ最小単位の時間であるptsを元にシークするのが`frame accurate seeking`で、frame単位でシーク位置に最もふさわしいframeを見つけ出しそのframeから描画を開始します。

一方で、`key-frame accurate seeking`と呼ばれるものも存在します。これは、Key-Frame単位でシーク位置を探索するもので、frame accurate seekingよりもシーク後の1frame目が表示されるのが早いですが、シーク位置はframe accurate seekingよりも正確ではありません。

つまり、遅いけど正確な`frame accurate seeking`、早いけど曖昧な`key-frame accurate seeking`ということになります。

ExoPlayerではどちらが使われているかというとデフォルトで`frame accurate seeking`が使われています。

# シークの所要時間

ExoPlayerでは、`frame accurate seeking`が使われていますが、どのようにframeを見つけ出しているのでしょうか？
ExoPlayerはSyncPointsという概念を元にシークすべきframeを見つけています。

SyncPointsとは、探索可能な特定の区間を区切ることのできるframeらを示しており、この特定のFrameで区切られた区間を元にシークすべきframeを探索しています。

<img width="600px" align="middle" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93589/0557b08e-3151-8f4c-42fd-9809db503360.png">

では、探索可能な特定の区間を区切ることのできるframeとはどのframeのことでしょうか？
これは、ストリーミングプロトコルやコンテイナーフォーマットによって変わります。

たとえば、progressiveに再生されるmp4であれば`stbl`ボックスからkey-frameの位置が特定できるため、SyncPointsはKey-Frameになります。また、fmp4で再生されるMPEG-DASHであればSyncPointsはfmp4で区切られた区間になり、MPEG2-TSで再生されるHLSであればSyncPointsはTSファイルごとになります。


seekするframeが見つかるとExoPlayerはその区間からデータをデコーダーに詰めていき、デコードを開始します。
ここで注意したいのは、seekするframeではなく、区間の初めのframeからデコーダーに積まれていることです。そのため、ExoPlayerは区間の先頭のframeからseekするframe以前のBufferに対して、isDecodeOnlyのフラグを追加します。このフラグを追加することによって区間の先頭のframeからseekするframe以前のframeはデコーダーによってデコードされますが、デコードされたフレームは描画されずただskipされます。
そして、seekするframe以降のframeがDecodeされるとそのフレームはSurface上に描画されます。

(なぜseekしたいframeの直前のKey-FrameからDecoderに入れてないのかはわからないです。)

## SeekParameters

シーク処理は、シークするframeの探索とシークするframeまでのdecode処理により、ある程度時間がかかってしまいます。
ここで、シーク処理の時間を早めるためにSeekParametersというものがあります。

SeekParametersはそれぞれ、EXACT、CLOSEST_SYNC, PREVIOUS_SUNC, NEXT_SYNCの四つの種類があります。
EXACTは今まで説明してきた`frame accurate seeking`ですが、それ以外のものは、シークすべき対象のframeをSyncPointsで区切られたFrameにします。こうすることで、無駄なdecode処理が省かれシークされて1framem目が表示されるまでの時間が短くなります。

以下では区切られたPointのframeをsync-frameと読んでいますが、このsync-frameは先ほどのSyncPointsの定義から、場合によってはKey-Frameですが、再生種別によってはKey-Frame単位ではない場合もある(複数のkey-frameにまたがっている可能性がある)ので正確には`key-frame accurate seeking`ではないことに注意してください。

| SeekParameters | 概要 |
|:----------------:|:------------------:|
| EXACT | Defaultのシーク方法。 直後の一番近いframeにシークします。 |
| CLOSEST_SYNC | 一番近いsync-frameにシークします。 |
| PREVIOUS_SYNC | 直前の一番近いsync-frameにシークします。 |
| NEXT_SYNC | 直後の一番近いsync-frameにシークします。 |

# HLSはシークが遅い?

ExoPlayerでHLSのストリームをシークすると、他の再生種別に比べてシークが圧倒的に遅いです。
これは、なぜ遅いかというと通常TSファイルの動画の長さは通常6~10s程度あるので、他の再生種別と比べてSyncPointsの区間が圧倒的に長くなってしまうからです。(SynPointsはProgressiveなmp4であればkey-frame単位、fmp4のDASHではあればfmp4単位)

そのため、まずSeekしたいframeが見つかると6~10sものファイルをまず取得する必要があります。この時点でまず時間がかかります。また、そのあとにセグメントの後ろの方にSeekしたいFrameがあった場合には取ってきたほぼ全てのframeをデコードまでした後にそれらをSkipして捨てなければなりません。

実際に、通信環境が良好な環境(60Mbps程度)で、セグメントの前の方と後ろの方でシークして見ると、セグメントの前の方にシークした場合には上部のsb(skipped buffer count)が9になっておりシークまでの時間もある程度早いですが、セグメントの後ろの方にシークした場合には、sbが198になっており、シークまでの時間も先ほどと比べて時間がかかっています。

少しわかりにくいですが、「セグメントの最後の方にシーク」の方は映像が少し止まっているのがわかります。

実際には、seekTo()を読んでから1frame目が描画されるまで、「セグメントの最初の方にシーク」は85ms、「セグメントの最後の方にシーク」は757msかかっています。


セグメントの最初の方にシーク | セグメントの最後の方にシーク
:--:|:--:
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93589/f219ada0-7ff9-4729-55ed-4e510a4e0bd6.gif" width="300" /> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93589/34f44788-f294-259f-2c4d-62aa1af4b1db.gif" width="300" />

これで、さらに通信速度がそこまでよくない環境だった場合には、セグメントファイル(6~10s)を取得する時間も他の再生種別と比べて大きく影響してきます。

では、SeekParemetersを使うのはどうでしょうか？

実はExoPlayerはHLSにおいてはSeekParametersをサポートしていません。
理由はSyncPointsがあまりにもシーク位置と異なってしまうからです。そのため、HLSでは毎回frame accurate seekingが行われます。

https://github.com/google/ExoPlayer/issues/2882


# まとめ

 - ExoPlayerはデフォルトでframe accurate seekingを行うが、SeekParametersを使って挙動を変えることができる。(HLS以外)
 - HLSはSyncPointsの区間長さが他の再生種別よりも圧倒的に大きくなるためシークが遅くなる。