昨夏頃から比較的大きなパフォーマンスのチューニングが入り、
近く大きなアップデートがありそうな(※筆者の主観です) Mirakurun 周りですが、
その大きなアップデートに追随できるようにクライアント側の一つ、EPGStation のアップデートを試みた。
ちょうど1年前に EPGStation v1系 で VAAPI を用いたハードウェアエンコードをセットアップした。
当時はまだリリース直後だったので、v2 系の採用は保留したのだが、
なかなか魅力的な改良も施されており、これを契機にできればアップデートしたいところ。
ハードウェアエンコード周りがすんなり移行できるか否かが今回のポイントである。
Contents
環境
OS は Ubuntu Server 20.04 LTS (x86_64)。
録画環境は Mirakurun + Chinachu(自分用) + EPGStation(家族用)の構成。
Mirakurun, Chinachu は実機システム上で直接稼働(*)、
EPGStation(v1) はこちらをベースに (rootless-)docker で稼働中。
(*: Mirakurun は別途 docker 化の予定。Chinachu は、、、 Air化!?)
VAAPI on EPGStation-v2 docker
以下、docker-mirakurun-epgstation の 2021年末のコミットをベースに記述する。
編集するファイルは以下。
- docker-compose.yml
- epgstation/debian.Dockerfile
- epgstation/config/config.yml
- epgstation/config/enc.js
docker-compose.yml
v1 での対応とほぼ変わらない。
- epgstation/devices: /dev/dri:/dev/dri の有効化
- epgstation/environment: LIBVA_DRIVER_NAME: "iHD" の明示化
- (Mirakurun 関連の削除)
Dockerfile
epgstation/debian.Dockerfile をベースにする。
元の v1系がこちら系列であったこと、別件で alpine をベースにして性能が出なくてハマったなどによる。
dockerfile も v1系の時と同様に VAAPI環境を構築し、vaapi対応ffmpeg をビルドする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
--- a/epgstation/debian.Dockerfile 2022-02-06 22:28:08.618422562 +0900 +++ b/epgstation/debian.Dockerfile 2022-01-30 10:14:39.880787037 +0900 @@ -9,6 +9,11 @@ apt-get -y install libx265-dev libnuma-dev && \ apt-get -y install libasound2 libass9 libvdpau1 libva-x11-2 libva-drm2 libxcb-shm0 libxcb-xfixes0 libxcb-shape0 libvorbisenc2 libtheora0 libaribb24-dev && \ \ +### Added for vaapi support + echo "deb http://http.us.debian.org/debian stable main contrib non-free" | tee -a /etc/apt/sources.list && \ + apt-get update && \ + apt-get -y install i965-va-driver-shaders intel-media-va-driver-non-free vainfo && \ +\ #ffmpeg build mkdir /tmp/ffmpeg_sources && \ cd /tmp/ffmpeg_sources && \ @@ -32,6 +37,7 @@ --enable-nonfree \ --disable-debug \ --disable-doc \ + --enable-vaapi \ && \ make -j$(nproc) && \ make install && \ |
config.yml
epgstation/config/config.yml.template をベースにする。
以前 json で記述した内容を yml で書き直すだけ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
--- a/epgstation/config/config.yml.template 2022-01-30 10:14:39.880787037 +0900 +++ b/epgstation/config/config.yml 2022-02-06 18:09:20.638912346 +0900 @@ -36,8 +37,12 @@ encodeProcessNum: 4 concurrentEncodeNum: 1 encode: - - name: H.264 - cmd: '%NODE% %ROOT%/config/enc.js' + - name: H.264(vaapi) + cmd: '%NODE% %ROOT%/config/enc_vaapi.js' + suffix: .mp4 + rate: 4.0 + - name: H.264(soft) + cmd: '%NODE% %ROOT%/config/enc_software.js' suffix: .mp4 rate: 4.0 @@ -55,6 +60,14 @@ live: ts: m2ts: + - name: 1080p(vaapi) + cmd: + '%FFMPEG% -dual_mono_mode main -vaapi_device /dev/dri/renderD128 -hwaccel vaapi -hwaccel_output_format vaapi -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 + -c:v h264_vaapi -vf format=nv12|vaapi,hwupload,deinterlace_vaapi -b:v 5M -minrate:v 5M -maxrate:v 5M -bufsize:v 10485760 -preset veryfast -y -f mpegts pipe:1' - name: 720p cmd: '%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 @@ -106,6 +119,20 @@ pipe:1' hls: + - name: 1080p(vaapi) + cmd: + '%FFMPEG% -dual_mono_mode main -vaapi_device /dev/dri/renderD128 -hwaccel vaapi -hwaccel_output_format vaapi -i pipe:0 -sn -threads 0 -map 0 -ignore_unknown + -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 + -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a + aac -ar 48000 -b:a 128k -ac 2 -c:v h264_vaapi -vf format=nv12|vaapi,hwupload,deinterlace_vaapi -b:v 5M -minrate:v 5M -maxrate:v 5M -bufsize:v 10485760 -preset veryfast + -flags +loop-global_header %OUTPUT%' - name: 720p cmd: '%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -map 0 -threads 0 -ignore_unknown @@ -149,6 +176,13 @@ pipe:1' hls: + - name: 720p(vaapi) + cmd: + '%FFMPEG% -dual_mono_mode main -vaapi_device /dev/dri/renderD128 -hwaccel vaapi -hwaccel_output_format vaapi -i %INPUT% -sn -threads 0 -map 0 -ignore_unknown + -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 + -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a + aac -ar 48000 -b:a 96k -ac 2 -c:v h264_vaapi -vf format=nv12|vaapi,hwupload,deinterlace_vaapi,scale_vaapi=h=720:w=-2 -b:v 3M -minrate:v 3M -maxrate:v 3M -bufsize:v 6291456 -aspect 16:9 -preset veryfast + -flags +loop-global_header %OUTPUT%' - name: 720p cmd: '%FFMPEG% -dual_mono_mode main -i pipe:0 -sn -map 0 -threads 0 -ignore_unknown |
enc.js
テンプレートも色々細かくアップデートされているようなので、
epgstation/config/enc.js.template をコピーしてベースにする。
vaapi 用と一部ハードウェアエンコードがうまく動作しない時用のソフトエンコを用意する。
enc_vaapi.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
--- a/epgstation/config/enc.js.template 2022-01-30 10:14:39.880787037 +0900 +++ b/epgstation/config/enc_vaapi.js 2022-02-06 15:06:08.884494761 +0900 @@ -8,6 +8,12 @@ const isDualMono = parseInt(process.env.AUDIOCOMPONENTTYPE, 10) == 2; const args = ['-y']; + +// settings +const audioBitrate = '128k'; +const qp = 20; +const videoFilter = 'deinterlace_vaapi,scale_vaapi=h=720:w=-2' + /** * 動画長取得関数 * @param {string} filePath ファイルパス @@ -32,14 +38,26 @@ }); }; + +// vaapi +Array.prototype.push.apply(args, [ + '-vaapi_device', '/dev/dri/renderD128', + '-hwaccel', 'vaapi', + '-hwaccel_output_format', 'vaapi' +]); + + + + + // 字幕用 Array.prototype.push.apply(args, ['-fix_sub_duration']); // input 設定 Array.prototype.push.apply(args, ['-i', input]); // ビデオストリーム設定 -Array.prototype.push.apply(args, ['-map', '0:v', '-c:v', 'libx264']); +Array.prototype.push.apply(args, ['-map', '0:v', '-c:v', 'h264_vaapi']); // インターレス解除 -Array.prototype.push.apply(args, ['-vf', 'yadif']); +Array.prototype.push.apply(args, ['-vf', videoFilter]); // オーディオストリーム設定 if (isDualMono) { Array.prototype.push.apply(args, [ @@ -57,7 +75,27 @@ // 字幕ストリーム設定 Array.prototype.push.apply(args, ['-map', '0:s?', '-c:s', 'mov_text']); // 品質設定 -Array.prototype.push.apply(args, ['-preset', 'veryfast', '-crf', '26']); +//Array.prototype.push.apply(args, ['-preset', 'veryfast', '-crf', '26']); + + +// Other options ... +Array.prototype.push.apply(args,[ + '-q', '-1', + '-qp', qp, + '-g', '300', + '-bf', '8', + '-i_qfactor', '0.7143', + '-b_qfactor', '1.3', + '-qmin', '20', + '-qmax', '51', + '-f', 'mp4', + '-ar', '48000', + '-ab', audioBitrate, + '-ac', '2' +]); + + + // 出力ファイル Array.prototype.push.apply(args, [output]); |
enc_software.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
--- a/epgstation/config/enc.js.template 2022-01-30 10:14:39.880787037 +0900 +++ b/epgstation/config/enc_software.js 2022-02-06 14:56:07.434129669 +0900 @@ -8,6 +8,13 @@ const isDualMono = parseInt(process.env.AUDIOCOMPONENTTYPE, 10) == 2; const args = ['-y']; +// settings +const audioBitrate = '128k'; +const preset = 'veryfast'; +const crf = 24; +const videoFilter = 'yadif' + + /** * 動画長取得関数 * @param {string} filePath ファイルパス @@ -39,7 +46,7 @@ // ビデオストリーム設定 Array.prototype.push.apply(args, ['-map', '0:v', '-c:v', 'libx264']); // インターレス解除 -Array.prototype.push.apply(args, ['-vf', 'yadif']); +Array.prototype.push.apply(args, ['-vf', videoFilter]); // オーディオストリーム設定 if (isDualMono) { Array.prototype.push.apply(args, [ @@ -57,7 +64,22 @@ // 字幕ストリーム設定 Array.prototype.push.apply(args, ['-map', '0:s?', '-c:s', 'mov_text']); // 品質設定 -Array.prototype.push.apply(args, ['-preset', 'veryfast', '-crf', '26']); +Array.prototype.push.apply(args, ['-preset', preset, '-crf', crf]); + + +// Other options ... +Array.prototype.push.apply(args, ['-maxrate:v', '2.5M']); +Array.prototype.push.apply(args, ['-bufsize:v', 2621440]); // 1024*1024*2.5 +Array.prototype.push.apply(args, [ + '-aspect', '16:9', + '-f', 'mp4', + '-ar', '48000', + '-ab', audioBitrate, + '-ac', '2' +]); + + + // 出力ファイル Array.prototype.push.apply(args, [output]); |
動作確認
録画予約, 録画, トランスコード, 録画ファイルの視聴、ライブ視聴などを
いくつか軽く試してみたが、ハードウェアエンコードを含めいずれも正しく動作しているようだ。
enc.js.template への変更を吸収したため、「エンコード」内で進捗率が確認できるようになっていた。
感想
v1 から v2 系へメジャーバージョンがあがり、
UI はもちろん設定ファイルも json から yml に変わったので少し腰が重くなっていたものの、
当然といえば当然だが、試してみると思いのほかすんなり移行ができそうなことが確認でき、
config.yml は必要箇所をコピペで微調整するレベルであった。
enc.js については 同template がアップデートされているので、
それを吸収することで追加された機能をフルに活用できるようであるが、
書き方自体は既存のものと同じように見えるので特に大きな問題にならないだろう。
近々、稼働中の v1 系のデータ, db などを含めて本格的に移行予定。
毎回こんな変更を手作業でするのは面倒なので、本家と自分用とを git 管理して追随できるようにしておいた。
# というか、既に今回のマージ作業自体が半分 git の力でなんとかした。