パソコンに接続できるTVチューナーを使って、自宅サーバで地デジ・BS・CSを録画できる環境を構築する方法をご紹介します。具体的には、株式会社プレクスが販売しているPX-Q3U4/PX-Q3PE4のTVチューナーを使用し、録画アプリケーションとしてMirakurunとEPGStationをDocker上で動かします。
必要な機器
-
TVチューナー
- PX-Q3U4 (外付け型)
- PX-Q3PE4 (内蔵型)
上記以外の機種でもドライバが対応していれば問題ありませんが、設定が変わる場合もあります。
-
ICカードリーダー
クレジットカードやマイナンバーカードにはICチップが埋め込まれているのですが、それらを読み取る事ができる装置です。 -
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
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
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
- 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
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
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
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
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
ffmpeg: /usr/bin/ffmpeg
ffprobe: /usr/bin/ffprobe
番組表で非表示にする番組を設定します。
お好みで設定してください。
vi epgstation/config/config.yml
excludeChannels:
- 3273701034
番組表の順番を変更します。
お好みで設定してください。
vi 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
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つ確認しながら進めていけば、構築できます。
今回紹介した手順を参考に、自宅サーバでの録画環境を構築してみてはいかがでしょうか。
コメント