録画サーバの構築(地デジ・BS・CS録画環境) – Debian Linuxによる自宅サーバ

自宅サーバ
自宅サーバ

パソコンに接続できるTVチューナーを使って、自宅サーバで地デジ・BS・CSを録画できる環境を構築する方法をご紹介します。具体的には、株式会社プレクスが販売しているPX-Q3U4/PX-Q3PE4のTVチューナーを使用し、録画アプリケーションとしてMirakurunとEPGStationをDocker上で動かします。

必要な機器

  • TVチューナー

    • PX-Q3U4 (外付け型)
    • PX-Q3PE4 (内蔵型)

    上記以外の機種でもドライバが対応していれば問題ありませんが、設定が変わる場合もあります。

  • ICカードリーダー
    クレジットカードやマイナンバーカードにはICチップが埋め込まれているのですが、それらを読み取る事ができる装置です。

    ICカードリーダ
    https://www.amazon.co.jp/gp/product/B003XF2JJY

  • B-CASカード

    地デジの情報を復号化するために必要です。
    既存のテレビに付属しているものを使用できますが、その場合、そのテレビでは地デジが見られなくなります。
    B-CASカードにはICチップが埋め込まれていて、ICカードリーダーから読み取ります。

  • アンテナ類
    チューナーに接続するアンテナケーブルやアンテナ分配器が必要になります。

ドライバのインストール

必要なパッケージをインストールします。

sudo apt install -y linux-headers-`uname -r` cmake gcc git g++ make dkms

ドライバをインストールします。

cd /usr/src

sudo git clone https://github.com/nns779/px4_drv

cd px4_drv/fwtool
sudo make
sudo wget http://plex-net.co.jp/plex/pxw3u4/pxw3u4_BDA_ver1x64.zip -O pxw3u4_BDA_ver1x64.zip
sudo unzip -oj pxw3u4_BDA_ver1x64.zip pxw3u4_BDA_ver1x64/PXW3U4.sys
sudo ./fwtool PXW3U4.sys it930x-firmware.bin
sudo mkdir -p /lib/firmware
sudo cp it930x-firmware.bin /lib/firmware/

# dkmsでカーネルドライバを自動的に設定
cd ../
sudo cp -a ./ /usr/src/px4_drv-0.2.1
sudo dkms add px4_drv/0.2.1
sudo dkms install px4_drv/0.2.1

再起動します。

sudo reboot

デバイスが表示されるか確認します。

ls /dev/px4*

下記のようなデバイスが表示されれば大丈夫です。

/dev/px4video0  /dev/px4video1  /dev/px4video2  /dev/px4video3  /dev/px4video4  /dev/px4video5  /dev/px4video6  /dev/px4video7

BIOSのSecure Bootの設定が有効になっているとドライバはインストールできません。
Secure Bootを無効にしてください。

ハードウェアエンコードの設定

動画のエンコードにハードウェアエンコードを行えるCPUがあります。
ハードウェアエンコードを使用する設定になります。

パッケージのインストールを行います。

sudo apt install -y firmware-amd-graphics i965-va-driver-shaders mesa-va-drivers vainfo
  • firmware-amd-graphics
    AMD/ATI グラフィックス チップ用のバイナリファームウェア

  • i965-va-driver-shaders
    Intel CPU用 VA-APIドライバ

  • mesa-va-drivers
    AMD CPU用 VA-APIドライバ

  • vainfo
    VA-APIのサポートをチェック

再起動します。

sudo reboot

VA-APIで使用するデバイスの確認。

ls /dev/dri

renderD128 が表示されれば問題ありません。

by-path  card0  renderD128

vainfo コマンドは、システムがハードウェアアクセラレーションに対応しているかどうか、そしてどのような機能が利用可能かを確認するのに役立ちます。

vainfo

以下のように表示され、サポート状況が確認できます。

error: can't connect to X server!
libva info: VA-API version 1.10.0
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
libva info: va_openDriver() returns -1
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/i965_drv_video.so
libva info: Found init function __vaDriverInit_1_8
libva info: va_openDriver() returns 0
vainfo: VA-API version: 1.10 (libva 2.10.0)
vainfo: Driver version: Intel i965 driver for Intel(R) Gemini Lake - 2.4.1
vainfo: Supported profile and entrypoints
      VAProfileMPEG2Simple            : VAEntrypointVLD
      VAProfileMPEG2Main              : VAEntrypointVLD
      VAProfileH264ConstrainedBaseline: VAEntrypointVLD
      VAProfileH264ConstrainedBaseline: VAEntrypointEncSlice
      VAProfileH264ConstrainedBaseline: VAEntrypointEncSliceLP
      VAProfileH264Main               : VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointEncSlice
      VAProfileH264Main               : VAEntrypointEncSliceLP
      VAProfileH264High               : VAEntrypointVLD
      VAProfileH264High               : VAEntrypointEncSlice
      VAProfileH264High               : VAEntrypointEncSliceLP
      VAProfileH264MultiviewHigh      : VAEntrypointVLD
      VAProfileH264MultiviewHigh      : VAEntrypointEncSlice
      VAProfileH264StereoHigh         : VAEntrypointVLD
      VAProfileH264StereoHigh         : VAEntrypointEncSlice
      VAProfileVC1Simple              : VAEntrypointVLD
      VAProfileVC1Main                : VAEntrypointVLD
      VAProfileVC1Advanced            : VAEntrypointVLD
      VAProfileNone                   : VAEntrypointVideoProc
      VAProfileJPEGBaseline           : VAEntrypointVLD
      VAProfileJPEGBaseline           : VAEntrypointEncPicture
      VAProfileVP8Version0_3          : VAEntrypointVLD
      VAProfileVP8Version0_3          : VAEntrypointEncSlice
      VAProfileHEVCMain               : VAEntrypointVLD
      VAProfileHEVCMain               : VAEntrypointEncSlice
      VAProfileHEVCMain10             : VAEntrypointVLD
      VAProfileHEVCMain10             : VAEntrypointEncSlice
      VAProfileVP9Profile0            : VAEntrypointVLD
      VAProfileVP9Profile0            : VAEntrypointEncSlice
      VAProfileVP9Profile2            : VAEntrypointVLD

デバイスファイルのアクセス権限を設定します。

echo 'KERNEL=="render*" GROUP="render", MODE="0666"' | sudo tee /etc/udev/rules.d/99-render.rules
sudo udevadm control --reload-rules && sudo udevadm trigger

Mirakurun・EPGStation

MirakurunとEPGStationをDocker上に設定します。

Docker Composeの設定

サンプルがあるのでダウンロードします。

mkdir -p ~/docker/mirakurun-epgstation
cd ~/docker/mirakurun-epgstation
git clone https://github.com/l3tnun/docker-mirakurun-epgstation .

Dockerイメージ chinachu/mirakurun に recpt1 をインストールします。

vi mirakurun/Dockerfile

mirakurun/Dockerfile
FROM chinachu/mirakurun ENV DEV="build-essential git libpcsclite-dev libssl-dev libtool pkg-config" RUN apt update \ && apt -y install $DEV \ # # recpt1 && git clone https://github.com/stz2012/recpt1.git \ && cd recpt1/recpt1 \ && ./autogen.sh \ && ./configure \ && make \ && make install \ && cd

サンプルの Compose ファイルをコピーします。

mv docker-compose-sample.yml compose.yaml

mirakurun では devices を正しく設定します。

epgstation でハードウェアエンコードを行うため devices/dev/dri を設定します。
ハードウェアエンコードが使えない場合はこちらをコメントアウトします。

vi compose.yaml

compose.yaml
services: mirakurun: build: context: mirakurun cap_add: - SYS_ADMIN - SYS_NICE ports: - "40772:40772" - "9229:9229" volumes: - ./mirakurun/conf:/app-config - ./mirakurun/data:/app-data environment: TZ: "Asia/Tokyo" devices: - /dev/bus:/dev/bus - /dev/px4video0:/dev/px4video0 - /dev/px4video1:/dev/px4video1 - /dev/px4video2:/dev/px4video2 - /dev/px4video3:/dev/px4video3 - /dev/px4video4:/dev/px4video4 - /dev/px4video5:/dev/px4video5 - /dev/px4video6:/dev/px4video6 - /dev/px4video7:/dev/px4video7 restart: always logging: driver: json-file options: max-file: "1" max-size: 10m mysql: image: mariadb:10.5 # image: mysql:8.0 # 囲み文字を使用する場合 volumes: - mysql-db:/var/lib/mysql environment: MYSQL_USER: epgstation MYSQL_PASSWORD: epgstation MYSQL_ROOT_PASSWORD: epgstation MYSQL_DATABASE: epgstation TZ: "Asia/Tokyo" command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --performance-schema=false --expire_logs_days=1 # for mariadb # command: --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_ci --performance-schema=false --expire_logs_days=1 --default-authentication-plugin=mysql_native_password # for myql restart: always logging: options: max-size: "10m" max-file: "3" epgstation: build: context: "./epgstation" dockerfile: "debian.Dockerfile" volumes: - ./epgstation/config:/app/config - ./epgstation/data:/app/data - ./epgstation/thumbnail:/app/thumbnail - ./epgstation/logs:/app/logs - ./recorded:/app/recorded environment: TZ: "Asia/Tokyo" depends_on: - mirakurun - mysql ports: - "8888:8888" - "8889:8889" user: "1000:1000" devices: - /dev/dri:/dev/dri restart: always volumes: mysql-db: driver: local

Mirakurunの設定

Mirakurunのチューナーの設定です。
デバイスとGR/BS/CSを正しく設定します。

vi mirakurun/conf/tuners.yml

mirakurun/conf/tuners.yml
- name: PX4-S1 types: - BS - CS command: recpt1 --device /dev/px4video0 <channel> - - decoder: arib-b25-stream-test isDisabled: false - name: PX4-S2 types: - BS - CS command: recpt1 --device /dev/px4video1 <channel> - - decoder: arib-b25-stream-test isDisabled: false - name: PX4-S3 types: - BS - CS command: recpt1 --device /dev/px4video4 <channel> - - decoder: arib-b25-stream-test isDisabled: false - name: PX4-S4 types: - BS - CS command: recpt1 --device /dev/px4video5 <channel> - - decoder: arib-b25-stream-test isDisabled: false - name: PX4-T1 types: - GR command: recpt1 --device /dev/px4video2 <channel> - - decoder: arib-b25-stream-test isDisabled: false - name: PX4-T2 types: - GR command: recpt1 --device /dev/px4video3 <channel> - - decoder: arib-b25-stream-test isDisabled: false - name: PX4-T3 types: - GR command: recpt1 --device /dev/px4video6 <channel> - - decoder: arib-b25-stream-test isDisabled: false - name: PX4-T4 types: - GR command: recpt1 --device /dev/px4video7 <channel> - - decoder: arib-b25-stream-test isDisabled: false

Mirakurunのチャンネルの設定です。
地域によってチャンネル設定が異なるので、中身を確認して調べて設定してみてください。

curl https://gist.githubusercontent.com/horatjp/6d99040a1c0c1ad79bbb4e5ae39f1730/raw/a994364766d908e0d38d2d9be3c05796c4e2cd9e/Mirakurun_channels.yml --output mirakurun/conf/channels.yml

※gistにアップしてあります。

EPGStationの設定

EPGStationのサンプルの設定をコピー(ファイル移動)します。

mv epgstation/config/enc.js.template epgstation/config/enc.js
mv epgstation/config/config.yml.template epgstation/config/config.yml
mv epgstation/config/operatorLogConfig.sample.yml epgstation/config/operatorLogConfig.yml
mv epgstation/config/epgUpdaterLogConfig.sample.yml epgstation/config/epgUpdaterLogConfig.yml
mv epgstation/config/serviceLogConfig.sample.yml epgstation/config/serviceLogConfig.yml

保存するディレクトリを2か所設定します。
こちらは自分の好みで設定します。

vi epgstation/config/config.yml

epgstation/config/config.yml
recorded: - name: original path: '%ROOT%/recorded/original' - name: program path: '%ROOT%/recorded/program'

外部ストレージにディレクトリを作成し、そこにシンボリックリンクを張ります。

mkdir /mnt/share/録画
rm -r recorded
ln -s /mnt/share/録画 recorded

H.264・H.265それぞれでソフトウェアエンコード・ハードウェアエンコードを行えるように設定してあります。
エンコード設定はお好みで設定してください。

vi epgstation/config/enc_multi.js

epgstation/config/enc_multi.js
const spawn = require('child_process').spawn; const execFile = require('child_process').execFile; const ffmpeg = process.env.FFMPEG; const ffprobe = process.env.FFPROBE; const input = process.env.INPUT; const output = process.env.OUTPUT; const name = process.env.NAME; const isDualMono = parseInt(process.env.AUDIOCOMPONENTTYPE, 10) == 2; const isCodec265 = process.argv[2] && process.argv[2] == '265' const isHardwareEncode = process.argv[3] && process.argv[3] == 'hardware' /** * 動画長取得関数 * @param {string} filePath ファイルパス * @return number 動画長を返す (秒) */ const getDuration = filePath => { return new Promise((resolve, reject) => { execFile(ffprobe, ['-v', '0', '-show_format', '-of', 'json', filePath], (err, stdout) => { if (err) { reject(err); return; } try { const result = JSON.parse(stdout); resolve(parseFloat(result.format.duration)); } catch (err) { reject(err); } }); }); }; const args = []; Array.prototype.push.apply(args, [ '-y', '-fix_sub_duration', '-analyzeduration', "10M", '-probesize', '32M', ]); if (isHardwareEncode) { Array.prototype.push.apply(args, [ '-vaapi_device', '/dev/dri/renderD128', '-hwaccel', 'vaapi', '-hwaccel_output_format', 'vaapi', ]); } Array.prototype.push.apply(args, [ '-i', input, '-movflags', '+faststart', '-ignore_unknown', ]); // 音声 if (isDualMono) { Array.prototype.push.apply(args, [ '-filter_complex', 'channelsplit', '-metadata:s:a:0', 'title=main', '-metadata:s:a:1', 'title=sub' ]); } Array.prototype.push.apply(args, ['-map', '0:a', '-c:a', 'aac', '-ac', '2']); // 映像 Array.prototype.push.apply(args, ['-map', '0:v']); if (isHardwareEncode) { if (isCodec265) { Array.prototype.push.apply(args, [ '-vf', 'format=vaapi,deinterlace_vaapi', '-c:v', 'hevc_vaapi', '-qp', '27', '-tag:v', 'hvc1', ]); } else { Array.prototype.push.apply(args, [ '-vf', 'format=vaapi,deinterlace_vaapi', '-c:v', 'h264_vaapi', '-level', '40', '-qp', '27', ]); } } else { if (isCodec265) { Array.prototype.push.apply(args, [ '-vf', 'yadif', '-preset', 'veryfast', '-c:v', 'libx265', '-crf', '22', '-f', 'mp4', '-tag:v', 'hvc1', ]); } else { Array.prototype.push.apply(args, [ '-vf', 'yadif', '-preset', 'veryfast', '-c:v', 'libx264', '-crf', '22', '-f', 'mp4', ]); } } // その他 Array.prototype.push.apply(args, [ '-map', '0:s?', '-c:s', 'mov_text', '-max_muxing_queue_size', '1024', output ]); (async () => { // 進捗計算のために動画の長さを取得 const duration = await getDuration(input); const child = spawn(ffmpeg, args); /** * エンコード進捗表示用に標準出力に進捗情報を吐き出す * 出力する JSON * {"type":"progress","percent": 0.8, "log": "view log" } */ child.stderr.on('data', data => { let strbyline = String(data).split('\n'); for (let i = 0; i < strbyline.length; i++) { let str = strbyline[i]; if (str.startsWith('frame')) { // 想定log // frame= 5159 fps= 11 q=29.0 size= 122624kB time=00:02:51.84 bitrate=5845.8kbits/s dup=19 drop=0 speed=0.372x const progress = {}; const ffmpeg_reg = /frame=\s*(?<frame>\d+)\sfps=\s*(?<fps>\d+(?:\.\d+)?)\sq=\s*(?<q>[+-]?\d+(?:\.\d+)?)\sL?size=\s*(?<size>\d+(?:\.\d+)?)kB\stime=\s*(?<time>\d+[:\.\d+]*)\sbitrate=\s*(?<bitrate>\d+(?:\.\d+)?)kbits\/s(?:\sdup=\s*(?<dup>\d+))?(?:\sdrop=\s*(?<drop>\d+))?\sspeed=\s*(?<speed>\d+(?:\.\d+)?)x/; let ffmatch = str.match(ffmpeg_reg); /** * match結果 * [ * 'frame= 5159 fps= 11 q=29.0 size= 122624kB time=00:02:51.84 bitrate=5845.8kbits/s dup=19 drop=0 speed=0.372x', * '5159', * '11', * '29.0', * '122624', * '00:02:51.84', * '5845.8', * '19', * '0', * '0.372', * index: 0, * input: 'frame= 5159 fps= 11 q=29.0 size= 122624kB time=00:02:51.84 bitrate=5845.8kbits/s dup=19 drop=0 speed=0.372x \r', * groups: [Object: null prototype] { * frame: '5159', * fps: '11', * q: '29.0', * size: '122624', * time: '00:02:51.84', * bitrate: '5845.8', * dup: '19', * drop: '0', * speed: '0.372' * } * ] */ if (ffmatch === null) continue; progress['frame'] = parseInt(ffmatch.groups.frame); progress['fps'] = parseFloat(ffmatch.groups.fps); progress['q'] = parseFloat(ffmatch.groups.q); progress['size'] = parseInt(ffmatch.groups.size); progress['time'] = ffmatch.groups.time; progress['bitrate'] = parseFloat(ffmatch.groups.bitrate); progress['dup'] = ffmatch.groups.dup == null ? 0 : parseInt(ffmatch.groups.dup); progress['drop'] = ffmatch.groups.drop == null ? 0 : parseInt(ffmatch.groups.drop); progress['speed'] = parseFloat(ffmatch.groups.speed); let current = 0; const times = progress.time.split(':'); for (let i = 0; i < times.length; i++) { if (i == 0) { current += parseFloat(times[i]) * 3600; } else if (i == 1) { current += parseFloat(times[i]) * 60; } else if (i == 2) { current += parseFloat(times[i]); } } // 進捗率 1.0 で 100% const percent = current / duration; const log = 'frame= ' + progress.frame + ' fps=' + progress.fps + ' size=' + progress.size + ' time=' + progress.time + ' bitrate=' + progress.bitrate + ' drop=' + progress.drop + ' speed=' + progress.speed; console.log(JSON.stringify({ type: 'progress', percent: percent, log: log })); } } }); child.on('error', err => { console.error(err); throw new Error(err); }); process.on('SIGINT', () => { child.kill('SIGINT'); }); })();

エンコードの設定を行います。

vi epgstation/config/config.yml

epgstation/config/config.yml
encode: - name: H.264 cmd: '%NODE% %ROOT%/config/enc_multi.js 264' suffix: .mp4 rate: 4.0 - name: H.265 cmd: '%NODE% %ROOT%/config/enc_multi.js 265' suffix: .mp4 rate: 4.0 - name: H.264(HW) cmd: '%NODE% %ROOT%/config/enc_multi.js 264 hardware' suffix: .mp4 rate: 4.0 - name: H.265(HW) cmd: '%NODE% %ROOT%/config/enc_multi.js 265 hardware' suffix: .mp4 rate: 4.0

Dockerイメージ l3tnun/epgstation:master-debian を拡張します。
強引に、ソースリストを bullseye にして deb-multimedia.org のFFmpegをインストールしています。
wakasacat は使用していないのですが、念のためインストールしています。

vi epgstation/debian.Dockerfile

epgstation/debian.Dockerfile
FROM l3tnun/epgstation:master-debian RUN echo "deb http://deb.debian.org/debian/ bullseye main contrib non-free" > /etc/apt/sources.list \ && apt update \ && apt -y install golang git gpg \ # # ffmpeg install && gpg --keyserver keyring.debian.org --recv-keys 5C808C2B65558117 \ && gpg --armor --export 5C808C2B65558117 | apt-key add - \ && echo "deb http://www.deb-multimedia.org bullseye main non-free" >> /etc/apt/sources.list \ && apt update \ && apt -y install ffmpeg firmware-amd-graphics i965-va-driver-shaders mesa-va-drivers vainfo \ # # wakasacat && mkdir /usr/local/go \ && export GOPATH=/usr/local/go \ && go get github.com/kteru/wakasacat \ && go install github.com/kteru/wakasacat \ && cp /usr/local/go/bin/wakasacat /usr/local/bin/wakasacat \ && cd

FFmpegをパッケージからインストールするとパスが変わるので設定を変更します。

vi epgstation/config/config.yml

epgstation/config/config.yml
ffmpeg: /usr/bin/ffmpeg ffprobe: /usr/bin/ffprobe

番組表で非表示にする番組を設定します。
お好みで設定してください。

vi epgstation/config/config.yml

epgstation/config/config.yml
excludeChannels: - 3273701034

番組表の順番を変更します。
お好みで設定してください。

vi epgstation/config/config.yml

epgstation/config/config.yml
channelOrder: - 3273601024 - 3273701032 - 3273801040 - 3274101064 - 3273901048 - 3274201072 - 3274001056

起動

起動します。

docker compose up -d

ステータス確認

docker compose ps

ログ確認

docker compose logs -f

Mirakurunの確認

下記URLからMirakurunが表示できます。

http://[ IPアドレス ]:40772

Mirakurunの機能のみで、TVTestというWindows用のソフトからTVの視聴も可能です。

EPGStation確認

下記URLからEPGStationが表示できます。
初回起動から40分程度たつとMirakurunが収集したデータをもとに番組表が表示されるようになります。

http://[ IPアドレス ]:8888

ReadyMedia

DLNAサーバのReadyMediaをインストールします。
DLNA対応家電でサーバー上の動画が再生できます。

mkdir -p ~/docker/minidlna
cd ~/docker/minidlna

vi compose.yaml

compose.yaml
services: minidlna: image: vladgh/minidlna network_mode: "host" restart: always volumes: - /mnt/share/録画:/media/recorder environment: - MINIDLNA_MEDIA_DIR=V,/media/recorder - MINIDLNA_FRIENDLY_NAME=share

起動します。

docker compose up -d

下記URLから情報が確認できます。

http://[ IPアドレス ]:8200

おわりに

以上で、Debian Linuxによる自宅サーバでの録画環境構築の手順の紹介を終わります。
必要な設定が多くありますが、1つ1つ確認しながら進めていけば、構築できます。
今回紹介した手順を参考に、自宅サーバでの録画環境を構築してみてはいかがでしょうか。

コメント

タイトルとURLをコピーしました