KVM上のUbuntuでPX-MLT5PEを用いてrecpt1コマンドでドロップが発生しないように録画を行う

以前この記事に従って,KVM上のUbuntuにMirakurun,EPGStationを使った録画サーバーを構築しました.

kotaro7750.hatenablog.com

その時に使ったチューナーはPLEX社のPX-S1UDというものでしたが,このチューナーは同時録画番組数が1しかなく,マシンとの接続がUSBだったりアンテナ部分が抜けやすかったりと本格的に運用するには少し心もとないものでした.

そこで,来季のアニメが始まる前により強力な録画設備にすべく,PLEX社のPX-MLT5PEというチューナーに更新しました.このチューナーは地デジ・BS/CSを5番組同時録画することができます.また配線がパソコンの内部で完結しアンテナ線もねじで止めるものになっており,本格運用にうってつけのチューナーです.

一方で巷では,このチューナーはドロップが多発したり安定性に欠けるといった情報も多く見られ,実際に私の環境でもドロップが多く発生しました.この記事ではその解決法も紹介していきます.

前提とする環境

この記事の内容は以下のような環境で行いました.

録画サーバーは仮想マシン上にありますが,マシンに直接録画サーバーを導入している方にとっても使える情報はあると思うので,合致していなくてもぜひ参考にしてみてください.

また,チューナー管理ソフトとしてMirakurun,録画管理ソフトとしてEPGStationを使っていますが,他のソフトでもrecpt1コマンドで録画するという部分が変わらなければ参考になると思います.

ホストマシン(物理マシン)

ホストマシンは仮想マシンの母艦となっており,直接録画には関係ないですが,チューナーなどの各種デバイス仮想マシンに渡す必要があります.

  • Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-156-generic x86_64)
  • CPUはIntel Pentium G4560
  • メモリは8GB
  • KVMで複数の仮想マシンが動作中
  • チューナーやB-CASカードリーダーはこのマシンに直接つながっている
  • KVMのUSBパススルー機能を使ってゲストマシンにチューナーとB-CASカードリーダーを見せている

ゲストマシン(仮想マシン

ゲストマシンは録画サーバーとなっています.

  • Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-156-generic x86_64)
  • vCPUは2コア
  • 割り当てメモリは4GB
  • Mirakurun,EPGStationが動作している
  • チューナーのドライバ,B-CASスクランブル解除用のライブラリがインストールされている

既に上記の記事に従ってMirakurunとEPGStation,B-CASスクランブル解除用のライブラリが導入されていることを前提にしていることに注意してください.

PX-MLT5PEのパソコンへの設置

まずはチューナーをパソコンに設置する必要があります.

箱と内容物はこんな感じ.

説明書の類は入っていなく,チューナー本体,データ転送用USBケーブル,ロープロファイル用ブラケットのみとシンプルです.

箱の外観

 

内容物

 

私のパソコンはデフォルトでついていたハイプロファイルブラケット対応のケースだったので,そのまま普通の拡張ボードと同様にPCI-Express x1に挿します.ロープロファイルのみ対応の場合にはブラケットを交換する必要がありそうです.

このチューナーは少し変則的で,電源はPCI経由・データ転送はUSB経由となっているので,USBケーブルをつなぐ必要があります.付属しているケーブルの白い方をチューナーの端子に,黒い方をマザーボードのUSB端子に繋ぎます.

チューナーとマザーボードの接続

 

 

このチューナーはボード側にB-CASカードリーダーがついていますが,今回使う予定のドライバ(後述)がカードリーダー機能をサポートしていないようなので,今までどおり外部のカードリーダーを使います.

KVMゲストマシンへのPCIパススルー

前に使っていたチューナーを使っていた際には,ホストマシンからゲストマシンへチューナーやカードリーダーを見せるためにKVMのUSBパススルーを使っていました.

実際に今回も最初はUSBパススルーをしたのですが,実際に録画をしてみたら大量のドロップ(1分間に200個くらい)が発生してとても見るに値しない状況になってしまいました.

そこでKVMPCIパススルーを試してみたところドロップが0となったので,この記事では最初からPCIパススルーのやり方を書こうと思います.

ただ,ドロップをなくすために丸一日くらい費やして試行錯誤したので,その供養はまた別の記事にしようと思います.PX-MLT5PEを使ってドロップに苦しんでいる方への助けになれば幸いです.

IOMMU拡張を有効化する

KVMPCIパススルーを有効化するにはIOMMU拡張を有効化する必要があります.
確認するためにはホスト上でdmesgコマンドを打ってIOMMUに関する記述を見つける必要があります.

kotaro@ginga:~$ dmesg | grep IOMMU
[    0.000000] DMAR: IOMMU enabled

私の場合には既に有効化されていました.もしされていないなら下の記事などを参考にして有効化してみてください.

kt-hiro.hatenablog.com

USBコントローラーを特定する

PCIパススルーをするといっても,実際に使うのはUSBデバイスなので,USBデバイスがつながるPCIバイス(USBコントローラー)をパススルーしてやる必要があります.そこでまずはチューナーやカードリーダーがつながるUSBコントローラーを特定する必要があります.

まずは,ホスト上でlspciコマンドを打ってUSBコントローラーを探します.

kotaro@ginga:~$ lspci
00:00.0 Host bridge: Intel Corporation Xeon E3-1200 v6/7th Gen Core Processor Host Bridge/DRAM Registers (rev 05)
00:02.0 VGA compatible controller: Intel Corporation HD Graphics 610 (rev 04)
00:08.0 System peripheral: Intel Corporation Xeon E3-1200 v5/v6 / E3-1500 v5 / 6th/7th Gen Core Processor Gaussian Mixture Model
00:14.0 USB controller: Intel Corporation 200 Series/Z370 Chipset Family USB 3.0 xHCI Controller
00:14.2 Signal processing controller: Intel Corporation 200 Series PCH Thermal Subsystem
00:16.0 Communication controller: Intel Corporation 200 Series PCH CSME HECI #1
00:17.0 SATA controller: Intel Corporation 200 Series PCH SATA controller [AHCI mode]
00:1c.0 PCI bridge: Intel Corporation 200 Series PCH PCI Express Root Port #7 (rev f0)
00:1f.0 ISA bridge: Intel Corporation 200 Series PCH LPC Controller (B250)
00:1f.2 Memory controller: Intel Corporation 200 Series/Z370 Chipset Family Power Management Controller
00:1f.3 Audio device: Intel Corporation 200 Series PCH HD Audio
00:1f.4 SMBus: Intel Corporation 200 Series/Z370 Chipset Family SMBus Controller
01:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 15)

結果を見ると,USB controllerという行があります.USBとつくものはこれしかなく,私のパソコンにはどうやら1つのUSBコントローラーしかないようです.
とりあえず00:14.0という番号を覚えておきます.

kotaro@ginga:~$ virsh nodedev-list | grep pci
pci_0000_00_00_0
pci_0000_00_02_0
pci_0000_00_08_0
pci_0000_00_14_0
pci_0000_00_14_2
pci_0000_00_16_0
pci_0000_00_17_0
pci_0000_00_1c_0
pci_0000_00_1f_0
pci_0000_00_1f_2
pci_0000_00_1f_3
pci_0000_00_1f_4
pci_0000_01_00_0
 
kotaro@ginga:~$ virsh nodedev-dumpxml pci_0000_00_14_0
<device>
  <name>pci_0000_00_14_0</name>
  <path>/sys/devices/pci0000:00/0000:00:14.0</path>
  <parent>computer</parent>
  <driver>
    <name>xhci_hcd</name>
  </driver>
  <capability type='pci'>
    <domain>0</domain>
    <bus>0</bus>
    <slot>20</slot>
    <function>0</function>
    <product id='0xa2af'>200 Series/Z370 Chipset Family USB 3.0 xHCI Controller</product>
    <vendor id='0x8086'>Intel Corporation</vendor>
    <iommuGroup number='3'>
      <address domain='0x0000' bus='0x00' slot='0x14' function='0x2'/>
      <address domain='0x0000' bus='0x00' slot='0x14' function='0x0'/>
    </iommuGroup>
  </capability>
</device>

先程覚えておいた00:14.0に対応するのはpci_0000_00_14_0なので,それを踏まえてデバイスの設定するためのxmlを出力させます.domainが0x0000,busが0x00,slotが0x14,functionが0x0だということが分かります(00:14.0はbus:slot.function という書き方に対応していそうです).

仮想マシン定義の編集

特定したUSBコントローラー情報を使って仮想マシンの定義を編集します.
virsh editコマンドを使って,domain->devices以下にhostdevノードを追加します.
この際,アドレスのところのdomain,bus,slot,functionには先程特定した情報を入力するようにしてください.

kotaro@ginga:~$ virsh edit myoujou
<domain type=kvm>
...
  <devices>
  ...
  ここに追加
  <hostdev mode='subsystem' type='pci' managed='yes'>
    <driver name='vfio'/>
    <source>
      <address domain='0x0000' bus='0x00' slot='0x14' function='0x0'/>
    </source>
  </hostdev>
  ...
  </devices>
</domain>

保存して仮想マシンを再起動したらゲストマシンでUSBコントローラーがパススルーされているか確認します.

record@myoujou:~$ lsusb
Bus 006 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 005 Device 003: ID 0511:024e N'Able (DataBook) Technologies, Inc.
Bus 005 Device 002: ID 04e6:5116 SCM Microsystems, Inc. SCR331-LC1 / SCR3310 SmartCard Reader
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0627:0001 Adomax Technology Co., Ltd
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

SmartCard Reader(カードリーダー)やN’Able(チューナー)がきちんと認識されています.このデバイス名は人によっては違う可能性があるので,デバイスを抜き差しして出力の違いを確かめると分かります.

ゲスト側へのデバイスドライバpx4_drvのインストール

チューナーデバイスを扱うためのデバイスドライバには,有志の方が用意してくれているpx4_drvを使います.githubのreadmeにインストール手順が書いてあるのでそれをなぞってインストールします.

github.com

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

以下の手順は私がcloneしてきたときの最新版(コミットハッシュ 7fa9f05d2cbdf1d821f479248d561f9868051b8b)でのものですので,以降のバージョンで手順が違うようだったらそちらを優先してください.

まずは必要なツールをインストールします.unzip,gcc,make,dkmsが必要とのことだったので,私の環境に無かったdkmsを入れます.

record@myoujou:~/px4_drv$ sudo apt update
record@myoujou:~/px4_drv$ sudo apt upgrade
record@myoujou:~/px4_drv$ sudo apt install dkms

次にファームウェアをインストールします.

record@myoujou:~/px4_drv$ cd fwtool/
 
record@myoujou:~/px4_drv/fwtool$ make
gcc -O2 -Wall   -c -o fwtool.o fwtool.c
gcc -O2 -Wall   -c -o tsv.o tsv.c
gcc -O2 -Wall   -c -o crc32.o crc32.c
gcc -o fwtool fwtool.o tsv.o crc32.o
 
record@myoujou:~/px4_drv/fwtool$ wget http://plex-net.co.jp/plex/pxw3u4/pxw3u4_BDA_ver1x64.zip -O pxw3u4_BDA_ver1x64.zip
--2021-09-12 08:35:45--  http://plex-net.co.jp/plex/pxw3u4/pxw3u4_BDA_ver1x64.zip
plex-net.co.jp (plex-net.co.jp) をDNSに問いあわせています... 157.7.144.5
plex-net.co.jp (plex-net.co.jp)|157.7.144.5|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 213410 (208K) [application/zip]
`pxw3u4_BDA_ver1x64.zip' に保存中
 
pxw3u4_BDA_ver1x64.zip                             100%[================================================================================================================>] 208.41K  --.-KB/s    時間 0.1s
 
2021-09-12 08:35:46 (1.47 MB/s) - `pxw3u4_BDA_ver1x64.zip' へ保存完了 [213410/213410]
 
record@myoujou:~/px4_drv/fwtool$ unzip -oj pxw3u4_BDA_ver1x64.zip pxw3u4_BDA_ver1x64/PXW3U4.sys
Archive:  pxw3u4_BDA_ver1x64.zip
Made with MacWinZipper (http://tidajapan.com/macwinzipper)
  inflating: PXW3U4.sys
 
record@myoujou:~/px4_drv/fwtool$ ./fwtool PXW3U4.sys it930x-firmware.bin
fwtool for px4 drivers
 
Driver file (in)    : PXW3U4.sys
Firmware file (out) : it930x-firmware.bin
 
Driver description: PX-W3U4 BDA Ver.1.0 64bit
Firmware length: 2169 bytes
Firmware CRC32: 0b41a994
OK.
 
record@myoujou:~/px4_drv/fwtool$ sudo mkdir -p /lib/firmware
 
record@myoujou:~/px4_drv/fwtool$ sudo cp it930x-firmware.bin /lib/firmware/

最後にdkmsを使ってドライバをインストールします.

record@myoujou:~/px4_drv/driver$ cd ..
 
record@myoujou:~/px4_drv$ sudo cp -a ./ /usr/src/px4_drv-0.2.1
 
record@myoujou:~/px4_drv$ sudo dkms add px4_drv/0.2.1
 
Creating symlink /var/lib/dkms/px4_drv/0.2.1/source ->
                 /usr/src/px4_drv-0.2.1
 
DKMS: add completed.
 
record@myoujou:~/px4_drv$ sudo dkms install px4_drv/0.2.1
 
Kernel preparation unnecessary for this kernel.  Skipping...
 
Building module:
cleaning build area...
cd ./driver; make KVER=4.15.0-156-generic px4_drv.ko.....
Signing module:
Generating a new Secure Boot signing key:
Can't load /var/lib/shim-signed/mok/.rnd into RNG
139977783509440:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/var/lib/shim-signed/mok/.rnd
Generating a RSA private key
................................................................................................................................+++++
.........................................................................................+++++
writing new private key to '/var/lib/shim-signed/mok/MOK.priv'
-----
 - /var/lib/dkms/px4_drv/0.2.1/4.15.0-156-generic/x86_64/module/px4_drv.ko
Secure Boot not enabled on this system.
cleaning build area...
 
DKMS: build completed.
 
px4_drv.ko:
Running module version sanity check.
 - Original module
   - No original module exists within this kernel
 - Installation
   - Installing to /lib/modules/4.15.0-156-generic/updates/dkms/
 
Running the post_install script:
'./etc/99-px4video.rules' -> '/etc/udev/rules.d/99-px4video.rules'
 
depmod...
 
DKMS: install completed.

ゲストマシンを再起動して正しくドライバが読み込まれているか確認します.

record@myoujou:~$ lsmod | grep px4
px4_drv               139264  0
 
record@myoujou:~$ ls /dev/px*
/dev/pxmlt5video0  /dev/pxmlt5video1  /dev/pxmlt5video2  /dev/pxmlt5video3  /dev/pxmlt5video4

5つのチューナーに対するデバイスファイルが作成されているのが分かります.

ゲスト側へのrecpt1コマンドのインストール

実際に録画を行うrecpt1コマンドをインストールします.
githubにある最新版(コミットハッシュ 8fa2339f74871d218b612e6e930524b4aba9be86)のreadme通りにやります.

github.com

record@myoujou:~/tool$ git clone https://github.com/stz2012/recpt1.git
Cloning into 'recpt1'...
remote: Enumerating objects: 270, done.
remote: Counting objects: 100% (48/48), done.
remote: Compressing objects: 100% (33/33), done.
remote: Total 270 (delta 31), reused 26 (delta 15), pack-reused 222
Receiving objects: 100% (270/270), 148.19 KiB | 3.29 MiB/s, done.
Resolving deltas: 100% (144/144), done.
warning: unable to access '/home/record/.config/git/attributes': 許可がありません
 
PX-S1UD_driver_Ver.1.0.1  PX-S1UD_driver_Ver.1.0.1.zip  libarib25  recdvb  recpt1
record@myoujou:~/tool$ cd recpt1/
 
record@myoujou:~/tool/recpt1$ cd recpt1/
 
record@myoujou:~/tool/recpt1/recpt1$ ./autogen.sh
Generating configure script and Makefiles for recpt1.
Running aclocal ...
Running autoheader ...
Running autoconf ...
 
record@myoujou:~/tool/recpt1/recpt1$ ./configure --enable-b25
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for create_arib_std_b25 in -larib25... yes
checking for log10 in -lm... yes
checking for pthread_kill in -lpthread... yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
 
record@myoujou:~/tool/recpt1/recpt1$ make
revh="`git rev-list HEAD | wc -l 2> /dev/null`"; \
if [ -n "$revh" ] && [ "$revh" != "0" ] ; then \
	echo "const char *version = \"rev.$revh by stz2012\";" > version.h; \
else \
	echo "const char *version = \""c8688d7d6382_with_http_server_RC4 by stz2012"\";" > version.h; \
fi
gcc -MM recpt1.c decoder.c mkpath.c tssplitter_lite.c recpt1core.c recpt1ctl.c recpt1core.c -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 > .deps
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o recpt1.o recpt1.c
recpt1.c: In function ‘main’:
recpt1.c:919:13: warning: ignoring return value of ‘write’, declared with attribute warn_unused_result [-Wunused-result]
             write(connected_socket, header, strlen(header));
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o decoder.o decoder.c
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o mkpath.o mkpath.c
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o tssplitter_lite.o tssplitter_lite.c
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o recpt1core.o recpt1core.c
recpt1core.c: In function ‘searchrecoff’:
recpt1core.c:58:53: warning: ‘__builtin___sprintf_chk’ may write a terminating nul past the end of the destination [-Wformat-overflow=]
                     sprintf(bs_channel_buf, "BS%d_%d", node, slot);
                                                     ^
In file included from /usr/include/stdio.h:862:0,
                 from recpt1core.c:1:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:33:10: note: ‘__builtin___sprintf_chk’ output between 6 and 19 bytes into a destination of size 8
   return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       __bos (__s), __fmt, __va_arg_pack ());
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gcc  -o recpt1 recpt1.o decoder.o mkpath.o tssplitter_lite.o recpt1core.o -lpthread -lm -larib25
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o recpt1ctl.o recpt1ctl.c
gcc  -o recpt1ctl recpt1ctl.o recpt1core.o -lm
gcc -O2 -g -pthread -I../driver -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64  -c -o checksignal.o checksignal.c
gcc  -o checksignal checksignal.o recpt1core.o -lpthread -lm
 
record@myoujou:~/tool/recpt1/recpt1$ make install

ドロップ無く録画できるかの確認

ここまででデバイスドライバとrecpt1コマンドをインストールできたのでいよいよ録画の確認を行っていきます.

record@myoujou:~$ recpt1 --b25 --strip --device /dev/pxmlt5video0 13 10 ./hoge.ts
using B25...
enable B25 strip
using device: /dev/pxmlt5video0
pid = 2098
device = /dev/pxmlt5video0
C/N = 27.500088dB
Recording...
Recorded 10sec

できたファイルにドロップがないことをtspacketchk というツールを使って確認します.

github.com

record@myoujou:~/tool/tspacketchk$ ./tspacketchk hoge.ts
<<< hoge.ts >>>


   pid      packets         drop        error   scrambling
-----------------------------------------------------------
0x0000           99            0            0            0
0x0010           10            0            0            0
0x0011            5            0            0            0
0x0012          366            0            0            0
0x0014            2            0            0            0
0x0024           10            0            0            0
0x1000           99            0            0            0
0x1001           99            0            0            0
0x1040          205            0            0            0
0x1050         8363            0            0            0
0x1070         1298            0            0            0
0x1071         1298            0            0            0
0x1072         1298            0            0            0
0x1140           99            0            0            0
0x1400        62620            0            0            0
0x1404          914            0            0            0
0x1420        59322            0            0            0
0x1424          909            0            0            0
-----------------------------------------------------------
             137016            0            0            0

            drop+error = 0
         syncbyte lost = 0
              duration = 00:00:09.89 (137016 packets, 25759008 byte)
            Check Time = 0.0 sec     (2251.86 Mbyte/sec)

ドロップが0なので正しく撮れていそうです.
recpt1コマンドで正しく撮れていることが確認できたら,Mirakurunなどチューナー管理ソフトに登録したら終了です.