ubuntu
カテゴリ
モバイル端末LTE環境でTV視聴改善
2026/06/06 17時watching tv
前回は PX-Q3U4 チューナー8個分だけエンコードを行い、チャンネルを自由に変えて視聴することに成功した、しかし外出先で視聴するのはNHKのメジャーリーグの1番組がメインで、時々サブチャンネルに切り替わるだけなのに、8個も視聴しない動画をエンコードしてCPU稼働率が目一杯になりハングアップ寸前となるので30分も視聴できない状態だった。

そこで、視聴するTV番組を一個に限定し、チャンネルを切り替わる時は、面倒でもコマンドで切り替える様に変更し、起動後に3,600秒(1時間)後に自動停止し、CPU負荷を低減する。

■スクリプトファイルの作成

前回使用していた hls_start.sh hls_proxy.py をリネーム保存
cd /mnt/data/backup/scripts/
sudo mv hls_start.sh hls_start8.sh
sudo mv hls_proxy.py hls_proxy8.py
/mnt/data/backup/scripts/hls_start.sh 作成
#!/bin/bash
# hls_start.sh - 1チャンネル起動スクリプト(systemdから呼ばれる)
# 使用例: bash hls_start.sh 400101

set -uo pipefail

if [ $# -eq 0 ]; then
    echo "[ERROR] SIDを指定してください"
    echo "使用例: bash hls_start.sh 400101"
    exit 1
fi

SID="$1"
HLS_DIR="/tmp/hls"
TAILSCALE_IP="100.89.99.23"
PROXY_PORT="8890"

# 使用チャンネルのみ定義
declare -A CH_NAME
CH_NAME[400101]="BS-NHK"
CH_NAME[400102]="BS-NHKサブ"
CH_NAME[3273601024]="NHK総合"
CH_NAME[3273601025]="NHKサブ"

NAME="${CH_NAME[$SID]:-SID_$SID}"

# ===== 既存の同SIDプロセスを停止 =====
pkill -f "ffmpeg.*services/${SID}/stream" 2>/dev/null || true
sleep 1

# ===== HLSディレクトリ作成 =====
mkdir -p "${HLS_DIR}/${SID}"

# ===== ffmpeg起動 =====
echo "[INFO] 起動: ${NAME} (SID: ${SID})"

nohup ffmpeg \
    -hwaccel vaapi \
    -hwaccel_output_format vaapi \
    -vaapi_device /dev/dri/renderD128 \
    -analyzeduration 10000000 \
    -probesize 10000000 \
    -fflags +discardcorrupt+genpts \
    -i "http://localhost:40772/api/services/${SID}/stream" \
    -map 0:v:0 -map 0:a:0 \
    -vf 'scale_vaapi=854:480' \
    -c:v h264_vaapi \
    -b:v 400k \
    -c:a aac -ar 44100 -b:a 96k -ac 2 \
    -sn \
    -f hls -hls_time 3 -hls_list_size 5 \
    -hls_flags delete_segments+append_list \
    -hls_segment_filename "${HLS_DIR}/${SID}/seg%03d.ts" \
    "${HLS_DIR}/${SID}/stream.m3u8" \
    > "/tmp/ffmpeg_${SID}.log" 2>&1 &

FFMPEG_PID=$!
echo "[INFO] ffmpeg PID: ${FFMPEG_PID}"
echo "[INFO] 視聴URL: http://${TAILSCALE_IP}:${PROXY_PORT}/hls/${SID}/stream.m3u8"
echo "[INFO] 完了: 約90秒後に視聴可能"
/mnt/data/backup/scripts/hls_proxy.py 作成
#!/usr/bin/env python3
# hls_proxy.py - HLSプロキシ(ポート8890)
# エンドポイント:
#   /hls/{SID}/stream.m3u8   マニフェスト(URL書き換え)
#   /hls/{SID}/seg*.ts        セグメント配信

import http.server
import os
import re
import sys

TAILSCALE_IP = "100.89.99.23"
PORT = 8890
HLS_DIR = "/tmp/hls"

# 使用チャンネルのみ定義
CH_NAMES = {
    "400101":     "BS-NHK",
    "400102":     "BS-NHKサブ",
    "3273601024": "NHK総合",
    "3273601025": "NHKサブ",
}

class HLSProxyHandler(http.server.BaseHTTPRequestHandler):

    def log_message(self, fmt, *args):
        print(f"[{self.address_string()}] {fmt % args}")

    def do_GET(self):
        path = self.path

        # /hls/{SID}/stream.m3u8 - マニフェスト(URL書き換え)
        m = re.match(r"^/hls/(\d+)/(.+\.m3u8)$", path)
        if m:
            sid, fname = m.group(1), m.group(2)
            local_path = os.path.join(HLS_DIR, sid, fname)
            self._serve_m3u8_rewritten(local_path, sid)
            return

        # /hls/{SID}/seg*.ts - セグメント
        m = re.match(r"^/hls/(\d+)/(.+\.ts)$", path)
        if m:
            sid, fname = m.group(1), m.group(2)
            local_path = os.path.join(HLS_DIR, sid, fname)
            self._serve_file(local_path, "video/MP2T")
            return

        self._send_404()

    def _serve_m3u8_rewritten(self, filepath, sid):
        """m3u8を読み込み、相対URLを絶対URLに書き換えて返す"""
        if not os.path.exists(filepath):
            self._send_404()
            return
        try:
            with open(filepath, "r") as f:
                content = f.read()
            base_url = f"http://{TAILSCALE_IP}:{PORT}/hls/{sid}"
            def rewrite(line):
                line = line.rstrip()
                if line and not line.startswith("#") and not line.startswith("http"):
                    return f"{base_url}/{line}"
                return line
            body = ("\n".join(rewrite(l) for l in content.splitlines()) + "\n").encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "application/vnd.apple.mpegurl")
            self.send_header("Content-Length", str(len(body)))
            self.send_header("Cache-Control", "no-cache")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(body)
        except Exception as e:
            print(f"[ERROR] m3u8: {e}")
            self._send_500()

    def _serve_file(self, filepath, content_type):
        """ファイルをそのまま返す"""
        if not os.path.exists(filepath):
            self._send_404()
            return
        try:
            with open(filepath, "rb") as f:
                body = f.read()
            self.send_response(200)
            self.send_header("Content-Type", content_type)
            self.send_header("Content-Length", str(len(body)))
            self.send_header("Cache-Control", "no-cache")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(body)
        except Exception as e:
            print(f"[ERROR] file: {e}")
            self._send_500()

    def _send_404(self):
        self.send_response(404)
        self.end_headers()
        self.wfile.write(b"Not Found")

    def _send_500(self):
        self.send_response(500)
        self.end_headers()
        self.wfile.write(b"Internal Server Error")


if __name__ == "__main__":
    server = http.server.ThreadingHTTPServer(("0.0.0.0", PORT), HLSProxyHandler)
    print(f"[INFO] HLS Proxy起動: port {PORT}")
    print(f"[INFO] 使用チャンネル:")
    for sid, name in CH_NAMES.items():
        print(f"[INFO]   {name} → http://{TAILSCALE_IP}:{PORT}/hls/{sid}/stream.m3u8")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        sys.exit(0)
スクリプトファイルに実行権限付与
sudo chmod +x /mnt/data/backup/scripts/hls_start.sh
sudo chmod +x /mnt/data/backup/scripts/hls_proxy.py

■サービスファイルの作成

hls-proxy.service(常時起動・自動起動・enabled)
[Unit]
Description=HLS Proxy (port 8890)
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /mnt/data/backup/scripts/hls_proxy.py
Restart=always
RestartSec=5
User=root

[Install]
WantedBy=multi-user.target
hls-bs1.service BS-NHK
[Unit]
Description=HLS Streaming - BS-NHK (400101)
After=network.target hls-proxy.service
Requires=hls-proxy.service

[Service]
Type=forking
ExecStartPre=/bin/mkdir -p /tmp/hls/400101
ExecStart=/mnt/data/backup/scripts/hls_start.sh 400101
RuntimeMaxSec=3600
Restart=on-failure
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
hls-bs2.service BS-NHKサブ
[Unit]
Description=HLS Streaming - BS-NHKサブ (400102)
After=network.target hls-proxy.service
Requires=hls-proxy.service

[Service]
Type=forking
ExecStartPre=/bin/mkdir -p /tmp/hls/400102
ExecStart=/mnt/data/backup/scripts/hls_start.sh 400102
RuntimeMaxSec=3600
Restart=on-failure
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
hls-nhk1.service NHK総合
[Unit]
Description=HLS Streaming - NHK総合 (3273601024)
After=network.target hls-proxy.service
Requires=hls-proxy.service

[Service]
Type=forking
ExecStartPre=/bin/mkdir -p /tmp/hls/3273601024
ExecStart=/mnt/data/backup/scripts/hls_start.sh 3273601024
RuntimeMaxSec=3600
Restart=on-failure
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
hls-nhk2.service NHKサブ
[Unit]
Description=HLS Streaming - NHKサブ (3273601025)
After=network.target hls-proxy.service
Requires=hls-proxy.service

[Service]
Type=forking
ExecStartPre=/bin/mkdir -p /tmp/hls/3273601025
ExecStart=/mnt/data/backup/scripts/hls_start.sh 3273601025
RuntimeMaxSec=3600
Restart=on-failure
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
作成した serviceファイルを /etc/systemd/system/ へ複写
sudo cp *.service /etc/systemd/system/

■端末の設定

Termius スニペットに登録 1時間後に自動停止するが停止コマンドも登録
1.BS-NHKを見る
sudo systemctl start hls-bs1
2.BS-NHKサブ に切替
sudo systemctl stop hls-bs1; sudo systemctl start hls-bs2
3.BS-NHKサブ 停止
sudo systemctl stop hls-bs2
4.BS-NHKサブを見る
sudo systemctl start hls-bs2
5.BS-NHK に切替
sudo systemctl stop hls-bs2; sudo systemctl start hls-bs1
6.BS-NHK 停止
sudo systemctl stop hls-bs1
7.古い画像の繰り返しが起きた場合システム再起動
bash /mnt/data/backup/scripts/hls_fix.sh
8.音声ファイルをテキスト変換する
cd /mnt/data/docker/whisper
time ./venv/bin/python transcribe.py

VLC 左上のメニュー(≡)→ 「ストリーム」URLを入力:
http://100.89.99.23:8890/m3u/active.m3u

■外出先の遅いWi-Fiで視聴する場合の次の手 2026.9.24追記

自宅のWi-Fiなら、TSファイルのまま視聴できたが、喫茶店の遅いWi-Fiでは画像が途切れ途切れになり、VLCのキャッシュを増やしても効果が無かったので、画像を圧縮を視聴する手段を付け加える、LTE用だと画像が荒いので切り替えスクリプトを作成。

#!/bin/bash
# hls_start.sh - 1チャンネル起動スクリプト(systemdから呼ばれる)
# 使用例: bash hls_start.sh 400101
# プロファイル: /tmp/hls/profile.conf に "wifi" と書かれていればWi-Fi高画質、
#              未設定 or それ以外なら lte(従来通り) で起動

set -uo pipefail

if [ $# -eq 0 ]; then
    echo "[ERROR] SIDを指定してください"
    echo "使用例: bash hls_start.sh 400101"
    exit 1
fi

SID="$1"
HLS_DIR="/tmp/hls"
TAILSCALE_IP="100.89.99.23"
PROXY_PORT="8890"
PROFILE_FILE="/tmp/hls/profile.conf"

# ===== プロファイル読み込み(未設定ならlte) =====
mkdir -p "${HLS_DIR}"
PROFILE="lte"
[ -f "${PROFILE_FILE}" ] && PROFILE=$(cat "${PROFILE_FILE}")

case "${PROFILE}" in
  wifi)
    SCALE="1280:720"
    VBITRATE="2500k"
    ABITRATE="128k"
    ;;
  *)
    PROFILE="lte"
    SCALE="854:480"
    VBITRATE="400k"
    ABITRATE="96k"
    ;;
esac

# 使用チャンネルのみ定義
declare -A CH_NAME
CH_NAME[400101]="BS-NHK"
CH_NAME[400102]="BS-NHKサブ"
CH_NAME[3273601024]="NHK総合"
CH_NAME[3273601025]="NHKサブ"

NAME="${CH_NAME[$SID]:-SID_$SID}"

# ===== 既存の同SIDプロセスを停止 =====
pkill -f "ffmpeg.*services/${SID}/stream" 2>/dev/null || true
sleep 1

# ===== HLSディレクトリ作成 =====
mkdir -p "${HLS_DIR}/${SID}"

# ===== ffmpeg起動 =====
echo "[INFO] 起動: ${NAME} (SID: ${SID}, PROFILE: ${PROFILE})"

nohup ffmpeg \
    -hwaccel vaapi \
    -hwaccel_output_format vaapi \
    -vaapi_device /dev/dri/renderD128 \
    -analyzeduration 10000000 \
    -probesize 10000000 \
    -fflags +discardcorrupt+genpts \
    -i "http://localhost:40772/api/services/${SID}/stream" \
    -map 0:v:0 -map 0:a:0 \
    -vf "scale_vaapi=${SCALE}" \
    -c:v h264_vaapi \
    -b:v ${VBITRATE} \
    -c:a aac -ar 44100 -b:a ${ABITRATE} -ac 2 \
    -sn \
    -f hls -hls_time 3 -hls_list_size 5 \
    -hls_flags delete_segments+append_list \
    -hls_segment_filename "${HLS_DIR}/${SID}/seg%03d.ts" \
    "${HLS_DIR}/${SID}/stream.m3u8" \
    > "/tmp/ffmpeg_${SID}.log" 2>&1 &

FFMPEG_PID=$!
echo "[INFO] ffmpeg PID: ${FFMPEG_PID}"
echo "[INFO] 視聴URL: http://${TAILSCALE_IP}:${PROXY_PORT}/hls/${SID}/stream.m3u8"
echo "[INFO] 完了: 約90秒後に視聴可能"

Termius スニペットに以下を追加
0-A. Wi-Fi高画質モードに切替
echo wifi | sudo tee /tmp/hls/profile.conf > /dev/null; sudo systemctl stop hls-bs1 hls-bs2 hls-nhk1 hls-nhk2
0-B. LTE低画質に切替(全チャンネル停止)
echo lte | sudo tee /tmp/hls/profile.conf > /dev/null; sudo systemctl stop hls-bs1 hls-bs2 hls-nhk1 hls-nhk2

①喫茶店でWi-Fi接続をして、『VLC media player』を起動後、M3Uプレイリストを開きTV視聴
②画像が途切れて視聴に耐えない
③Termius を起動して0-A を実行(Wi-Fiモードに設定)後にチャンネルを選択、『VLC media player』を起動後、「ストリーム」URLを入力

データ使用量の概算数値、LTE通信でも速ければWi-Fi仕様で視聴可能でそれほど使用しない
LTE用 :映像 400kbps+音声 96kbps=合計 496kbps 1時間あたり:約0.2GB
Wi-Fi用:映像2,500kbps+音声128kbps=合計2,628kbps 1時間あたり:約1.2GB
記事一覧