前回調べた VAAPI によるハードウェアエンコードを Chinachu/EPGStation に適用する。
Contents
Chinachu
Chinachu は元々 vaapi によるハードウェアエンコードの枠組みがあるので、それを微調整して使う。
config.json
- "vaapiEnabled": true
- "vaapiDevice": "/dev/dri/renderD128" などの使用するデバイスファイル
- "gid": vaapi に用いるデバイスファイルのグループID
私の場合は/dev/dri/renderD128
を用い、このファイルのグループはrender
。
/etc/group
によるとrender
の ID は 109 なので、109
を指定した。
実行オプション最適化
専ら録画してから、録画済みの番組を後から視聴するので、
chinachu/api/script-recorded-program-watch.vm.js
のみを修正。
必要に応じて、channel-watch
とか recording-program-watch
なども合わせて。
下記以外は、WebUI からの引数などもあるので基本的に Chinachu のデフォルトコードをそのままで活用している。
hwaccel_output_format=vaapi
下準備での解説の通り。
ハードウェア(vaapi)系フィルタに直結させるなら vaapi 形式のままでよい。
1 |
args.push("-hwaccel_output_format", "vaapi"); |
scale_vaapi
こちらも下準備での説明の通り。
1080p のまま素通しさせる場合は、明示的にフィルタを通さない。
1 2 3 |
if ( height != "1080" ){ scale = `,scale_vaapi=w=${width}:h=${height}`; } |
scale_vaapi=h=720:w=-2
のように高さだけの指定で、元の比率で等倍指定でもよいかも。
ビデオフィルタ(vf)の整理
こちらも下準備での説明の通り。
1 |
args.push("-vf", `deinterlace_vaapi${scale}`); |
明示的に iHD を使わせる
Chinachu から呼ばれた ffmpeg が iHD/i965 のいずれを使っているかわかりにくいので、実行時環境を明示的に指定する。
1 |
var ffmpeg = child_process.spawn('ffmpeg', args, {env: {LIBVA_DRIVER_NAME: 'iHD'}}); |
その他
chinachu/usr/bin/ffmpeg
などが別途入れたシステムの ffmpeg を指すようにしてあった。
公式にも同梱ffmpeg は削除してビルド必須と書かれているが、
ffmpeg で --hwaccels
, --encoders
や --codecs
の出力を見る限り、
同梱の ffmpeg でも通るような気がするが、未確認。
EPGStation
コンテナ内の EPGStation は VAAPI によるハードウェアエンコードには対応していないので、
Dockerfile 内で対応した環境, ffmpeg などを用意してやる。
docker-compose.yml
vaapi に用いるデバイスファイルをコンテナ内に見せる。
1 2 3 4 5 6 |
services: ... epgstation: ... devices: - /dev/dri:/dev/dri |
epgstation/Dockerfile
下準備で説明したように、コンテナ内で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/Dockerfile +++ b/epgstation/Dockerfile @@ -10,6 +10,11 @@ RUN apt-get update && \ 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 && \ \ +### 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 && \ @@ -34,6 +39,7 @@ RUN apt-get update && \ --enable-nonfree \ --disable-debug \ --disable-doc \ + --enable-vaapi \ && \ cd /tmp/ffmpeg_sources/ffmpeg* && \ make -j${CPUCORE} && \ |
epgstation/config/enc.js
録画時のエンコードはこちらで設定、それ以外は別途 config.json で設定(後述)。
デフォルトの enc.js
をベースに 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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
const spawn = require('child_process').spawn; const ffmpeg = process.env.FFMPEG; const input = process.env.INPUT; const output = process.env.OUTPUT; const analyzedurationSize = '10M'; // Mirakurun の設定に応じて変更すること const probesizeSize = '32M'; // Mirakurun の設定に応じて変更すること const maxMuxingQueueSize = 1024; const dualMonoMode = 'main'; const videoHeight = parseInt(process.env.VIDEORESOLUTION, 10); const isDualMono = parseInt(process.env.AUDIOCOMPONENTTYPE, 10) == 2; const audioBitrate = videoHeight > 720 ? '192k' : '128k'; //const preset = 'veryfast'; const codec = 'h264_vaapi'; const qp = 20; // 20-22?? const args = ['-v', 8, '-y', '-analyzeduration', analyzedurationSize, '-probesize', probesizeSize]; // vaapi Array.prototype.push.apply(args, [ '-vaapi_device', '/dev/dri/renderD128', '-hwaccel', 'vaapi', '-hwaccel_output_format', 'vaapi' ]); // video filter 設定 let videoFilter = 'deinterlace_vaapi,scale_vaapi=h=720:w=-2' // dual mono 設定 if (isDualMono) { Array.prototype.push.apply(args, ['-dual_mono_mode', dualMonoMode]); } // input 設定 Array.prototype.push.apply(args,['-i', input]); // メタ情報を先頭に置く Array.prototype.push.apply(args,['-movflags', 'faststart']); // 字幕データを含めたストリームをすべてマップ Array.prototype.push.apply(args, ['-map', '0', '-ignore_unknown', '-max_muxing_queue_size', maxMuxingQueueSize, '-sn']); // Array.prototype.push.apply(args, ['-vf', videoFilter]); // H264 Main-profile, Lv4.1 Array.prototype.push.apply(args, ['-profile', 77]); Array.prototype.push.apply(args, ['-level', 41]); // maxrate, bufsize //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,[ '-c:v', codec, '-q', '-1', '-qp', qp, '-g', '300', '-bf', '8', '-i_qfactor', '0.7143', '-b_qfactor', '1.3', '-qmin', '20', '-qmax', '51', '-f', 'mp4', '-c:a', 'aac', '-ar', '48000', '-ab', audioBitrate, '-ac', '2', output ]); let str = ''; for (let i of args) { str += ` ${ i }` } console.error(str); const child = spawn(ffmpeg, args); child.stderr.on('data', (data) => { console.error(String(data)); }); child.on('error', (err) => { console.error(err); throw new Error(err); }); process.on('SIGINT', () => { child.kill('SIGINT'); }); |
明示的に iHD を使わせる
EPGStation でも明示的に iHD を使うようにさせる。
1 2 3 4 5 6 7 |
services: ... epgstation: ... environment: ... LIBVA_DRIVER_NAME: "iHD" |
config.json
WebUI 側から使うための設定。
私の場合は、EPGStation では基本的に録画時にエンコードまでやってしまうので、encode
と liveHLS
の設定のみ。
生ts で保存する場合は、recordedHLS
も変更するとよい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
"encode": [ ... { "name": "H264(vaapi)", "cmd": "%NODE% %ROOT%/config/enc_vaapi.js", "suffix": ".mp4", "default": true }, ... "liveHLS": [ { "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 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v h264_vaapi -vf deinterlace_vaapi -b:v 5M -minrate:v 5M -maxrate:v 5M -bufsize:v 10485760 -preset veryfast -flags +loop-global_header %OUTPUT%" }, |
[注意] 2021/01 に EPGStation v2 がリリースされ、これらの設定は config.yml
で行われるようになった。
まだ v2.0系ということで私は未導入のため、これらの記述は v1系用のものとなります。
デバイスファイルの権限設定
実はここが一番すっきりしていないのところなのだが、
私の環境では EPGStation はユーザー権限で動く rootless-docker 内で稼働している。
このユーザーをデバイスファイルのグループである renderに追加しても、
コンテナ内ので VAAPI ハードウェアエンコードが上手く動作しない。
そのため systemd で rc-local.service 的なものを作って、
その中で chmod 666 /dev/dri/renderD128
としている。
# /etc/udev/rules.d/~.rules でやるのがまともなやり方だと思います。
これで起動時に用いるデバイスファイルの権限が 666 となる。
セキュリティ的にはザルになっているのでどうなのか分からないが、
ひとまずこれで動作するようになった。
普通の docker や、システムに直接 EPGStation を入れた場合は不要かもしれない。
Known Issue
BS系(?) or ソースのビットレートが高い(?)時に vaapi によるハードウェアエンコードが失敗する。
そのため EPGStation では別途 enc_software.js を用意して、そちらも指定できるようにした。