EPGStationでhevc_qsv(10bit)を活かして、低負荷で綺麗な視聴・エンコード環境を作る話

最近あまりこの手の話を書いていなかったのと、触ってもなかったのですが、様々なことをしているうちに久しぶりに録画環境をいじりたくなったので備忘録を残しておきます。なお、内容としては設定を残しておく感じなので、今回エンコードで使うffmpegのビルド関係は省略します。そもそも、ビルドについてはネットの海に沢山あるのと、公式ドキュメント読めばできる話だと思うので省略します。

そもそも、この記事を書こうと思ったきっかけですが、

  • hevc_qsvを使ったエンコードのサンプルが見当たらない
  • IceLakeからの向上した画質を活かした環境の記事がない
  • EPGStationで動くスクリプトの書き方や試行錯誤の仕方がわからない
  • JasperLakeでの正しいHWエンコードの手法を知らなかった(買ったときに最新すぎてまともにサポートされていなかった)

といったのが主な理由になっています。個人的にきっかけとして一番大きいのは、HWエンコードの性能向上にともなって、HEVCの画質は大幅に向上したにも関わらず、未だにh264に関連した内容が多く散見される点です。というわけで、ここからは私が書きたいことを書いていきます。

目次

背景

IceLake以降のHEVCにおけるHWエンコーダー大きく向上しています。ハードウェアエンコードの性能に近くなって

一方で、使える世代が新しいこと、以前のHEVCにおけるHWエンコーダーは性能が低く、h264と比較しても画質面での優位は無いといっても差し支えないレベルでした。それでいて、HEVCはデコードにパワーを使うため、以前のモバイル端末での負荷は大きなものがあり、メリットが無い状態でした。

コンピュータの性能向上はグラフィック分野では今でも目覚ましいものがあり、今やモバイル端末には当然の様にHEVCをHWデコードできる機能が付き、少ない消費電力で高圧縮・高画質な再生を行うことができるようになりました。

高圧縮・高画質の観点でいうと、現在AV1という規格がありますが、これはまだまだ発展途上のフォーマットであり、現在の最新のものでも、HWエンコーダー(デコーダー)を持たないものが多く、まだまだ様々なマシンで利用するという意味での実用に遠いものとなっています。

その点、HEVCはHWエンコード/デコードが十分に普及した状態であり、新世代のフォーマットを模索している今こそh264からHEVCに乗り換えるときがやってきたと言えます。もちろん、SWエンコードの方が画質は良いですが、HEVCはやはり処理に時間がかかるため、画質を考慮しても

ここで、最初の話に戻ると、IceLake以降のマシンではHEVC Main 10(以降HEVC 10bit)での画質と圧縮のバランスがHWエンコードの中でも非常に優れています。
参考:rigayaの日記兼メモ帳「QSV画質比較 (2022/12, 固定品質編)」

そうなると、そういった環境を構築できるマシンを持っていると作りたくなるものです。そもそも、これをやりたい思う一番のモチベーションとしては、EPGStationでの録画データを何かしらの形で扱いたいというのがあります。ですが、EPGStationは直接コマンドをたたくわけではなく、EPGStationで使うための一種のハンドラを実装する必要があり、単純にffmpegを使うのとは勝手が違います。

そしてEPGStationのffmpegでhevc_qsvを用いて実装している例がすぐ見当たらないため、簡単に環境構築できる環境や情報が無いということが、その障壁になっていると考えました。

画質の細かい調整というは個人の好みや設定もあるので、ある程度のひな型を提供しつつ、自分好みの設定をするための話をこの後書いていきます。

環境

私の環境について始めに示します。以下の録画環境は2022年の春ごろに組んだものなので、今の最新とは違うかと思いますが、スクリプトなどは基本的に同一で使えると思います。

  • Ubuntu 22.04
  • Intel Celeron N5095(15W,16EU, UHD Gen 11th, Jasper Lake)
  • RAM:8GB x 1 @DDR4-2400
  • EPGStaion v2.6.2
  • ffmpeg version N-106643-g70db14376c

多分他に関係する要素はほぼないと思うのですが、必要なのがあれば掲載します。

なお、上で少し書いたのですが、このマシンを購入したときは、製品が数個出回る程度の最新のCPUだったのもあり、Ubuntu側のサポートが酷い状態だったので、QSVを使うのにも一苦労だったのですが、Jaspler Lake世代のCPUは多く出回っており環境も安定しているので、変な苦労が発生することはあまりないかと思います。

また、後述しますが、比較的新しいCPUでHEVCを使う場合は、HuCやGuCといった項目を有効化しておく必要があります。

内臓グラフィックの世代が12世代からはoneVPLがサポートされていたり、過渡期のiGPUであるためJasper Lakeは不遇であったことは間違いなく、今でも仕様なのか放置されているだけなのかは不明ですが、Jaspler Lakeでは使えないエンコード設定があります(固定品質設定(ICQ)など)。なので、新規で用意するならAlder Lake世代の方がよいでしょう。一応手持ちにはIntel N95も持っていますが、こちらはサーバーにとって非常に素晴らしい性能だと思います。

また、この世代のCPUはドライバが悪いのか、非対応なのかいまいちはっきりとしませんが、現状は色々な機能が使えないということが後で判明してPC変更を行ったので、以下のような環境でも動作を確認しています。

  • Ubuntu 24.04
  • Intel Core i3 1215U(15W,64EU,UHD Gen12,Alder Lake)
  • RAM:4GB x 2 @DDR4-3200
  • ffmpeg version 7.1

ここでは書きませんが、最近の製品(といっても2年ぐらい前ですが…)なのに、ドライバ周りで面倒なことがあったりffmpegでqsvを使うまで苦労しました。

ちなみに、ICQが対応しているかどうかは以下のコマンドで判別できます。

$ vainfo --all
----
いろいろ表示される中から、使いたいプロファイルを探して、
VAConfigAttribRateControlの部分に「VA_RC_ICQ」があるか確認
これはN5095の場合
----
VAProfileHEVCMain10/VAEntrypointEncSliceLP
    VAConfigAttribRTFormat                 : VA_RT_FORMAT_YUV420
                                             VA_RT_FORMAT_YUV444
                                             VA_RT_FORMAT_YUV420_10
                                             VA_RT_FORMAT_YUV444_10
                                             VA_RT_FORMAT_RGB32
                                             VA_RT_FORMAT_RGB32_10
                                             VA_RT_FORMAT_RGB32_10BPP
                                             VA_RT_FORMAT_YUV420_10BPP
    VAConfigAttribRateControl              : VA_RC_CBR
                                             VA_RC_VBR
                                             VA_RC_VCM
                                             VA_RC_CQP
                                             VA_RC_MB

VAProfileHEVCMain10/VAEntrypointEncSliceLPとなっていますが、後ろのLPがLowPowerModeの略で、N5095はLPしか対応していないので、ICQは対応しないことになっています。i3 1215Uの場合は以下のようになっています。

$vainfo --all
----
VAProfileHEVCMain10/VAEntrypointEncSlice
    VAConfigAttribRTFormat                 : VA_RT_FORMAT_YUV420
                                             VA_RT_FORMAT_YUV422
                                             VA_RT_FORMAT_YUV420_10
                                             VA_RT_FORMAT_YUV422_10
                                             VA_RT_FORMAT_YUV420_12
                                             VA_RT_FORMAT_YUV422_12
                                             VA_RT_FORMAT_YUV420_10BPP
    VAConfigAttribRateControl              : VA_RC_CBR
                                             VA_RC_VBR
                                             VA_RC_VCM
                                             VA_RC_CQP
                                             VA_RC_ICQ
                                             VA_RC_MB
                                             VA_RC_QVBR

VAProfileHEVCMain10/VAEntrypointEncSliceという末尾に「LP」がついていない項目があり、ICQに対応していることが確認できます。一方で「LP」のついた項目もあり、そちらにはICQがないのでLPモードの場合ICQには非対応です。

上記コマンドでドライバ側で使えるレートコントロールモードを見ることができるので、一応確認しておくとよいでしょう。

ffmpegにおける確認

ffmpegのビルドは各自でやってもらうとして、それ以降の話を書いておこうと思います。本当かは知りませんが、Debianではaptで振ってくるffmpegはHWエンコードできるとかでビルドしなくても良いとかいう噂があるので、Ubuntu版も確認してみると良いかもしれません。私は1年ちょっと前にビルドしたのが使えたのでそれを使っています。新しいマシンでは、ソースからコンパイルしたものを使って落ち着いたのでそのまま使っています。

まずffmpegを使う前に、vainfoで正しくGPUを使える環境があるのか確認します。2~3行だけしか表示されなかったら何か設定がおかしいかもしれません。正しくできれば以下のような表示になります。

$ vainfo
error: can't connect to X server!
libva info: VA-API version 1.23.0
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
libva info: Found init function __vaDriverInit_1_23
libva info: va_openDriver() returns 0
vainfo: VA-API version: 1.23 (libva 2.12.0)
vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics - 24.3.4 ()
vainfo: Supported profile and entrypoints
      VAProfileNone                   : VAEntrypointVideoProc
      VAProfileNone                   : VAEntrypointStats
      VAProfileMPEG2Simple            : VAEntrypointVLD
      VAProfileMPEG2Simple            : VAEntrypointEncSlice
      VAProfileMPEG2Main              : VAEntrypointVLD
      VAProfileMPEG2Main              : VAEntrypointEncSlice
      VAProfileH264Main               : VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointEncSlice
      VAProfileH264Main               : VAEntrypointFEI
      VAProfileH264Main               : VAEntrypointEncSliceLP
      VAProfileH264High               : VAEntrypointVLD
      VAProfileH264High               : VAEntrypointEncSlice
      VAProfileH264High               : VAEntrypointFEI
      VAProfileH264High               : VAEntrypointEncSliceLP
      VAProfileVC1Simple              : VAEntrypointVLD
      VAProfileVC1Main                : VAEntrypointVLD
      VAProfileVC1Advanced            : VAEntrypointVLD
      VAProfileJPEGBaseline           : VAEntrypointVLD
      VAProfileJPEGBaseline           : VAEntrypointEncPicture
      VAProfileH264ConstrainedBaseline: VAEntrypointVLD
      VAProfileH264ConstrainedBaseline: VAEntrypointEncSlice
      VAProfileH264ConstrainedBaseline: VAEntrypointFEI
      VAProfileH264ConstrainedBaseline: VAEntrypointEncSliceLP
      VAProfileVP8Version0_3          : VAEntrypointVLD
      VAProfileHEVCMain               : VAEntrypointVLD
      VAProfileHEVCMain               : VAEntrypointEncSlice
      VAProfileHEVCMain               : VAEntrypointFEI
      VAProfileHEVCMain               : VAEntrypointEncSliceLP
      VAProfileHEVCMain10             : VAEntrypointVLD
      VAProfileHEVCMain10             : VAEntrypointEncSlice
      VAProfileHEVCMain10             : VAEntrypointEncSliceLP
      VAProfileVP9Profile0            : VAEntrypointVLD
      VAProfileVP9Profile0            : VAEntrypointEncSliceLP
      VAProfileVP9Profile1            : VAEntrypointVLD
      VAProfileVP9Profile1            : VAEntrypointEncSliceLP
      VAProfileVP9Profile2            : VAEntrypointVLD
      VAProfileVP9Profile2            : VAEntrypointEncSliceLP
      VAProfileVP9Profile3            : VAEntrypointVLD
      VAProfileVP9Profile3            : VAEntrypointEncSliceLP
      VAProfileHEVCMain12             : VAEntrypointVLD
      VAProfileHEVCMain12             : VAEntrypointEncSlice
      VAProfileHEVCMain422_10         : VAEntrypointVLD
      VAProfileHEVCMain422_10         : VAEntrypointEncSlice
      VAProfileHEVCMain422_12         : VAEntrypointVLD
      VAProfileHEVCMain422_12         : VAEntrypointEncSlice
      VAProfileHEVCMain444            : VAEntrypointVLD
      VAProfileHEVCMain444            : VAEntrypointEncSliceLP
      VAProfileHEVCMain444_10         : VAEntrypointVLD
      VAProfileHEVCMain444_10         : VAEntrypointEncSliceLP
      VAProfileHEVCMain444_12         : VAEntrypointVLD
      VAProfileHEVCSccMain            : VAEntrypointVLD
      VAProfileHEVCSccMain            : VAEntrypointEncSliceLP
      VAProfileHEVCSccMain10          : VAEntrypointVLD
      VAProfileHEVCSccMain10          : VAEntrypointEncSliceLP
      VAProfileHEVCSccMain444         : VAEntrypointVLD
      VAProfileHEVCSccMain444         : VAEntrypointEncSliceLP
      VAProfileAV1Profile0            : VAEntrypointVLD
      VAProfileHEVCSccMain444_10      : VAEntrypointVLD
      VAProfileHEVCSccMain444_10      : VAEntrypointEncSliceLP

次に、ご自身が使っているCPUに合わせて、GuCとHuCを有効化しておきます。GuCはGuraphics micro(μ) Controller、HuCはHEVC/h265 micro(μ) Controllerの略で、グラフィックに関連する機能とHEVCに関連するコントローラなのだけは間違いないことが名前からわかるでしょう。実は、一度昨年にこのシステムを組もうとして諦めたのがこの点で、このGuCとHuCを有効化していないと、今回のメインである、HEVCエンコードができません。ですが、H264ではHWエンコードできるし何が悪いかがわからずドツボにハマってしまったからです。

こんなGuCとHucの設定ですが、これが結構環境によって違うので癖があります。有効化する方法としては、modprobeにオプションを記述して、それで起動すると有効化されるという感じなのですが、そのオプションがCPU依存なところがあります。

さらに、録画環境だけだとトラブルになる可能性が低いとは思いますが、ハイバネから復帰したときにフリーズしたりする可能性があるらしいです。なので、ONにしてトラブルになるようならQSVでのHEVCを諦めなければいけません。

詳細はこのページに書いていますが、Jasper Lakeの場合は以下の様に設定します。適当なconfファイルでいいのですが、上記ページに従うなら「/etc/modprobe.d/i915.conf」とでもしておきましょう。

options.i915.enable_guc=2

環境によってはまちまちなのでIntelのドキュメントでも眺めながら0~3のどれかを入れたら大丈夫です。ただし、参考ページによると、Intelのドキュメントと設定が違うものがあるようなので、有効化できない場合は違う値を入れてみたら良いかと思います。

余談ですが、私が入れた段階では新しすぎてここら辺がうまく対応できていない、古いUbuntu22.04を使っていてたせいでずっと有効化できず、apt update とapt upgradeをして再起動するだけで動くという間抜けなこともあったので、ご自身が使われているCPUをしっかりとOSがサポートしているかは確認したほうが良いかもしれません。レアケースなので出会うことはそうないと思いますが…

ひとまず、この設定をして再起動してGuCとHuCが有効化されていれば大丈夫です。確認は以下のコマンドでどうぞ。何かいっぱい出てくれば正解、disableと出ていれば有効化できていないです。Ubuntu24.04だと別の方法で確認が必要ですが、説明は割愛します。

cat /sys/kernel/debug/dri/0/gt/uc/guc_info
cat /sys/kernel/debug/dri/0/gt/uc/huc_info

ここまで終わってやっとffmpegでhevcでのエンコードができるようになっているので、この状態でエンコードできるかを確認します。EPGStationで録画した適当な入力ファイル、ここでは「input.ts」として、これを「out.mp4」に変換します。このとき、デインターレースとスケール、エンコードをqsvを用いて行っています。

ffmpeg -dual_mono_mode main -hwaccel qsv -hwaccel_output_format qsv -c:v mpeg2_qsv -i input.ts -vf deinterlace_qsv,scale_qsv=-1:720 -c:v hevc_qsv -tag:v hvc1 -f mp4 out.mp4

これで正しくエンコードできればひとまず正しく動くと言えるでしょう。これはHEVC 10bitではないので、HEVC 10bitに変換する場合ですが、-profile:v main10を追加するだけでいいんでしょと思いきや、実はそれではうまくいきません。FFmpegのサイトを見ていたら-pix_fmt p010leを指定しろとドキュメントが変更されてました(まあそれでも動いたり動かなかったりしますが)。

最近はこのハードウェアエンコードの種類が増えて複雑になってきたため、動きそうなのに動かないフィルタの組み合わせがあったりして結構難儀します。はっきりとはわかりませんが、今回もそのパターンです。

今回はscale_qsvというリサイズするフィルタはなぜかhevcの10bitでは使えません。おそらく何かの形式が違うためだとは思うので、渡し方を変えたり色々したのですが、私には解決方法が見つけられませんでした。ドキュメントやらコードの実装を読んだら良いのでしょうが、最近は仕事以外でコードを読む気が起きないのでパスです。

もう少し調べてみると非常に便利なフィルタを見つけました。それがvpp_qsvです。とりあえず適当に出力してみます。

Filter vpp_qsv
  Quick Sync Video VPP.
    Inputs:
       #0: default (video)
    Outputs:
       #0: default (video)
vpp_qsv AVOptions:
   deinterlace       <int>        ..FV....... deinterlace mode: 0=off, 1=bob, 2=advanced (from 0 to 2) (default 0)
     bob             1            ..FV....... Bob deinterlace mode.
     advanced        2            ..FV....... Advanced deinterlace mode.
   denoise           <int>        ..FV....... denoise level [0, 100] (from 0 to 100) (default 0)
   detail            <int>        ..FV....... enhancement level [0, 100] (from 0 to 100) (default 0)
   framerate         <rational>   ..FV....... output framerate (from 0 to DBL_MAX) (default 0/1)
   procamp           <int>        ..FV....... Enable ProcAmp (from 0 to 1) (default 0)
   hue               <float>      ..FV....... ProcAmp hue (from -180 to 180) (default 0)
   saturation        <float>      ..FV....... ProcAmp saturation (from 0 to 10) (default 1)
   contrast          <float>      ..FV....... ProcAmp contrast (from 0 to 10) (default 1)
   brightness        <float>      ..FV....... ProcAmp brightness (from -100 to 100) (default 0)
   transpose         <int>        ..FV....... set transpose direction (from -1 to 6) (default -1)
     cclock_hflip    0            ..FV....... rotate counter-clockwise with horizontal flip
     clock           1            ..FV....... rotate clockwise
     cclock          2            ..FV....... rotate counter-clockwise
     clock_hflip     3            ..FV....... rotate clockwise with horizontal flip
     reversal        4            ..FV....... rotate by half-turn
     hflip           5            ..FV....... flip horizontally
     vflip           6            ..FV....... flip vertically
   cw                <string>     ..FV....... set the width crop area expression (default "iw")
   ch                <string>     ..FV....... set the height crop area expression (default "ih")
   cx                <string>     ..FV....... set the x crop area expression (default "(in_w-out_w)/2")
   cy                <string>     ..FV....... set the y crop area expression (default "(in_h-out_h)/2")
   w                 <string>     ..FV....... Output video width (default "cw")
   width             <string>     ..FV....... Output video width (default "cw")
   h                 <string>     ..FV....... Output video height (default "w*ch/cw")
   height            <string>     ..FV....... Output video height (default "w*ch/cw")
   format            <string>     ..FV....... Output pixel format (default "same")
   async_depth       <int>        ..FV....... Internal parallelization depth, the higher the value the higher the latency. (from 0 to INT_MAX) (default 0)
   scale_mode        <int>        ..FV....... scale mode: 0=auto, 1=low power, 2=high quality (from 0 to 2) (default 0)

このフィルタ一つでscale_qsvやdeinterlace_qsvに相当する機能が使えます。また、-profile main10の代わりとしてフィルタ内のformatオプションで指定すると正しく動かすことができます。というわけでHEVC main10プロファイルのエンコードはこんな感じでできます。

ffmpeg -hwaccel qsv -dual_mono_mode main -c:v mpeg2_qsv -i input.ts -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v hevc_qsv -vf vpp_qsv=deinterlace=2:h=720:w=h*cw/ch:format=p010le:scale_mode=2 -b:v 1500k -preset veryfast -y -f mp4 output.mp4

このオプション内には横幅をアスペクト比を保ちつつ720pにリサイズする形式となっていますが、この指定するオプションは式による評価ができるので呪文みたいになっています。気になる人は自分で調べてみてください

join_logo_scpで出力されるavsファイルを処理する場合はこんな感じです。なぜかオプションの指定を変えないと動かない謎っぷりです。

ffmpeg -hwaccel qsv -hwaccel_output_format qsv -dual_mono_mode main -i in_cutcm_logo.avs -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v hevc_qsv -vf hwupload=extra_hw_frames=128,vpp_qsv=deinterlace=2:format=p010le -global_quality 23 -look_ahead 1 -preset medium -y -f mp4 output.mp4

これだけできればffmpegは問題なく機能しているはずなので、EPGStationの方での使い方も考えてみます。

Linux版のjoin_logo_scpを使う場合は、ffmpegにavsファイルを使う都合かvpp_qsvでは正しくインターレース解除ができない場合がありました。ffmpegのバージョンは同じでもなぜかJasprer Lakeはだめ、Alder Lakeはできる状態でした。正直Jasper Lakeはバグなのか仕様なのかよくわからないものが多すぎたので、使いたくないですね…

EPGStationでの確認

EPGStationの設定ファイルにエンコードスクリプトを書くだけなの簡単です。EPGStation/config.config,ymlに記述すればできます。

上述のffmpegのコードをベースにすればリアルタイム視聴も低負荷で可能になります。一つ気を付けなければならないことがあります。iPhoneなどのApple製品の場合は、HEVCとHLSの組み合わせではいくつかの制約があります。一つはtagをhvc1とすること、もう一つがセグメントの動画ファイルはfMP4(m4s)であることです。そのため、それを明示したオプションとする必要があります。私は以下のようなコードをHLS配信で使っています。

ffmpeg -dual_mono_mode main -hwaccel qsv -hwaccel_output_format qsv -c:v mpeg2_qsv -i pipe:0 -sn -threads 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_segment_type fmp4 -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.m4s -hls_flags delete_segments -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v hevc_qsv -vf vpp_qsv=deinterlace=2:h=720:w=h*cw/ch:format=p010le:scale_mode=2 -b:v 1500k -preset veryfast -tag:v hvc1 -flags +loop-global_header %OUTPUT%%

リアルタイム視聴に限らず、録画ファイルを視聴する際のストリーミングもバッファサイズを変更すればいけます。エンコード速度も2~3倍ぐらいでできるので結構快適に見ることができます。

あとは録画ファイルをエンコードする際は先ほどのffmpegのテストコードを使うと比較的綺麗に早くできるでしょう。画質にうるさいなら時間かけてソフトウェアエンコードをした方が良いです。

まとめ

HWエンコーダが高性能化したのを活かすためにEPGStationや普通のエンコードでHEVC mai10プロファイルを活かしたエンコードをする方法を紹介しました。

H264のソフトフェアエンコードにこだわる方もいまだ多い印象を受けますが、IceLake以降のHEVCエンコーダの性能は圧倒的です。ソフトウェアエンコードと比較しても自分程度の目だと見わけがつかないくらいのところまでやってきたので、時間や負荷のバランス、モバイルデバイスなどの進歩を見てもH264を辞めるには十分な性能といえると思います。エンコードも躓きさえしなければそこまで大きく変えることなくできますしね。

毎回そうなんですが、あまり書かれていないことや最新のことを追いすぎてバグを踏まされている感があったりするので、ここで紹介したようなバグに近い内容は時間が経つにつれて無くったり気づかないようになっていくのだと思います。

ただ、今躓くことは事実なので備忘録として、後発の方のために残しておこうと思いました。

誰かのお役にたてれば幸いです。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です