NanoPi NEO3 Plus に HDMI を生やす

やりたいこと

3000円くらいの NanoPi NEO3 Plus だって HDMI 出力できるんだ!と証明したい。

…本末転倒だけどw せっかくの 48mm 角な小型 SBC が台無しだけど!w

結果:できるにはできるけど、制限あり

それでも、これはメッチャあり。

以下が制限事項

・Armbian での動作は未確認。Ubuntu24.04 で動作を確認。
・コンソール表示はできない
・1920×1080@30 な動画の再生は追い付かない(20fpsくらいなら出る)
・/dev/fbx や /dev/dri/cardx を使った直接描画はできない

必要なもの

USB-HDMI アダプタ
  これは超絶要注意事項。
  DisplayPort Alternate Mode(通称 DP Alt モード)製品ではだめ。(HTML Alt モードも同じくダメ)
  必ず、Display Link (DL-3xxx / DL-5xxx / DL-9xxx) 搭載製品が必要。

Display Link Direct SDK
  SDKだからね、ソフトウェアなんで用意と言ってもダウンロードするだけ
  なお、メーカーWebサイトからはドライバもダウンロードできるけど、それは不要。
  というか、むしろ入れちゃダメ。
  Display Link Manager という名称になってるけど、これが入ってると邪魔になる。
  SDKだけで良い。

FFmpeg RockchipMPP版
  MPP版にせずCPUだけでやってもいいけど、その場合、1920×1080な動画だと4コア全て100%に
  張り付いて全く追いつかない。
  ハードウェア支援を受けられる=VPUが使えると、だいぶ楽になる。
  …それでも1920×1080@20fpsくらいが限界かなぁ…

実際に購入した USB-HDMI アダプタは以下。
DL-3500 搭載製品で NanoPi NEO3 Plus で使うには十分すぎる。
…というか、NanoPi NEO3 Plus の2倍以上の価格ってのもなんかこう…ホント本末転倒よねぇ…w

手順

大まかにやることは以下の通り。

・DisplayLink Direct SDK の準備(公式ドライバは不要。というかあるとむしろダメ)
・映像出力確認
・FFmpeg RockchipMPP 対応版のビルド

映像出力までなら割と簡単。
MPP 対応は非力な CPU だけど、支援機能があるからそれを使って動画をデコードするためなので、やらなくてもいいっちゃぁいい。

DisplayLink Direct SDK の準備

まずは下準備。
python や python-venv、python-pip 辺りはすでにある、という前提。

$ sudo apt update
$ sudo apt install -y libusb-1.0-0 libc6 usbutils ffmpeg

Diplay Link Direct SDK を使うには割と新し目のものが必要なので、すでに入っていても更新しておいた方がよさそう。

続いて、Python の仮想環境を用意。
ついでにnumpyもインストールしておく。

$ mkdir -p ~/python/displaylink-tools
$ cd ~/python
$ python3 -m venv ./displaylink-tools
$ cd displaylink-tools
$ source ./bin/activate
$ ./bin/pip install numpy

SDK のインストール。

$ ./bin/pip install --no-index \
                    --find-links \
                    https://displaylink.github.io/displaylink-direct/pip-index \
                    dlsdk

ちゃんとインストールされたか確認。

$ ./bin/pip show dlsdk
Name: dlsdk
Version: 2.0.0+g287e926058.build4
Summary:
Home-page:
Author:
Author-email:
License:
Location: /home/userName/python/displaylink-tools/lib/python3.12/site-packages
Requires:
Required-by:

映像出力確認

USB-HDMI アダプタを接続し、モニタへの HDMI ケーブルも繋げておき lsusb。
以下のような表示になることを確認する。

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 004 Device 002: ID 17e9:xxxx DisplayLink USB Display

最後の行でちゃんと Display Link USB Display が出ているので認識はOK。

SDK が入っていて、USB デバイスとしての認識にも問題がなければ以下コードを実行してみる。

import dlsdk
import time
 
config = dlsdk.Config()
config.firmware_path = None
 
try:
    print(dlsdk.create_system(config))
 
    devices = dlsdk.get_devices()
    device = devices[0]
 
    displays = device.get_displays()
    display = displays[0]
 
    # ファームウェアバージョン確認
    print("Firmware:", device.firmware_version())

    print("\n--- Display information ---")
 
    # EDID取得
    edid_status, edid_data = display.edid()
    #print("EDID status:", edid_status)
    #print("EDID length:", len(edid_data))
 
    # 対応解像度一覧
    mode_status, modes = display.modes()
    #print("Modes status:", mode_status)
 
    for mode in modes:
        print(
            "Mode:",
            mode.resolution.width,
            "x",
            mode.resolution.height,
            "@",
            mode.refresh_rate_hz,
            "Hz"
        )
 
    # 推奨解像度
    preferred_status, preferred = display.preferred_mode()
 
    #print("Preferred status:", preferred_status)
 
    if preferred_status == dlsdk.dlsdk_status.DLSDK_SUCCESS:
        print(
            "Preferred:",
            preferred.resolution.width,
            "x",
            preferred.resolution.height,
            "@",
            preferred.refresh_rate_hz,
            "Hz"
        )
 
    # ディスプレイ電源ON
    print("\n--- Power ON ---")
    power_status = display.power_on()
    #print("Power status:", power_status)
 
    # 電源ON後に解像度取得
    size = display.size()
 
    print(
        "Active resolution:",
        size.width,
        "x",
        size.height
    )
 
    if size.width > 0 and size.height > 0:
        print("\nDisplay is ready!")
 
    time.sleep(10)
 
finally:
    dlsdk.delete_system()

実行した結果、下記のようにモニタが対応している解像度等の情報が得られれば、NanoPi NEO3 Plus による、USB-HDMI アダプタを経由したモニタとの通信が上手くいっていることになる。

$ sudo ./bin/python detect.py
Firmware: 12.3.37
--- Display information ---
Mode: 1920 x 1080 @ 60 Hz
Mode: 1920 x 1080 @ 50 Hz
Mode: 1680 x 1050 @ 60 Hz
Mode: 1600 x 900 @ 60 Hz
Mode: 1280 x 1024 @ 75 Hz
Mode: 1280 x 1024 @ 60 Hz
Mode: 1280 x 800 @ 60 Hz
Mode: 1280 x 720 @ 60 Hz
Mode: 1280 x 720 @ 50 Hz
Mode: 1152 x 870 @ 75 Hz
Mode: 1024 x 768 @ 75 Hz
Mode: 1024 x 768 @ 60 Hz
Mode: 832 x 624 @ 75 Hz
Mode: 800 x 600 @ 75 Hz
Mode: 800 x 600 @ 60 Hz
Mode: 720 x 576 @ 50 Hz
Mode: 720 x 480 @ 60 Hz
Mode: 720 x 400 @ 70 Hz
Mode: 640 x 480 @ 75 Hz
Mode: 640 x 480 @ 60 Hz
Preferred: 1920 x 1080 @ 60 Hz
 
--- Power ON ---
Active resolution: 1920 x 1080
 
Display is ready!

そして、ようやく映像表示を試す。
まぁ、とりあえず、最初は画像でも表示させてみよう。
画像は test.jpg としてコードと同じディレクトリの保存しておく。

import time
import subprocess
import numpy as np
import dlsdk
 
def main():
 
    try:
        # DisplayLink初期化
        status = dlsdk.create_system()
        #print("Initialize:", status)
 
        if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
            return

        # USB-HDMIアダプタ取得
        devices = dlsdk.get_devices()
 
        if not devices:
            print("DisplayLink device not found")
            return
 
        device = devices[0]
 
        # ディスプレイ取得
        displays = device.get_displays()
 
        if not displays:
            print("Display not found")
            return
 
        display = displays[0]
 
 
        # DisplayLink解像度設定
 
        # 対応モード取得
        status, modes = display.modes()
        if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
            print("Cannot get display modes")
            return
 
        # 目的の解像度をを探す
        target_mode = None
 
        for mode in modes:
            w = mode.resolution.width
            h = mode.resolution.height
            hz = mode.refresh_rate_hz
 
            if w == 1920 and h == 1080 and hz == 60:
            #if w == 1024 and h == 768 and hz == 60:
            #if w == 832 and h == 624 and hz == 75:
            #if w == 640 and h == 480 and hz == 60:
                target_mode = mode
                break
 
        if target_mode is None:
            print("Target mode not found")
            return
 
        # 指定したモードで起動
        status = display.power_on(mode=target_mode)
        #print("Power ON:", status)
 
        if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
            print("Failed to set display mode")
            return
 

        # DisplayLink解像度取得
        size = display.size()
        width = size.width
        height = size.height
        print(f"Resolution: {width} x {height}")

 
        # Python側フレームバッファ
        frame = np.empty(
            (height, width, 4),
            dtype=np.uint8
        )
 
        # NumPy配列をバイト単位のメモリビューに
        frame_bytes = memoryview(frame).cast("B")
        frame_size = len(frame_bytes)
 
        #print(f"Frame size: {frame_size:,} bytes")
 
 
        # JPGをFFmpegでデコードしてBGRAに変換
        cmd = [
            "ffmpeg",
            "-hide_banner",
            "-loglevel", "error",
            "-i", "test.jpg",
            "-vf", f"scale={width}:{height}",
            "-frames:v", "1",
            "-pix_fmt", "bgra",
            "-f", "rawvideo",
            "pipe:1"
        ]
 
        result = subprocess.run(
            cmd,
            stdout=subprocess.PIPE,
            check=True
        )
 
        # NumPy配列に変換
        frame = np.frombuffer(
            result.stdout,
            dtype=np.uint8
        ).reshape(height, width, 4)
 
 
        # DisplayLinkへフレーム送信して表示 
        status = display.show(
            dlsdk.DLSDK_PIXEL_FORMAT_XRGB,
            frame
        )
 
        if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
            print("Show error:", status)
            exit
 
        # DisplayLink転送完了待ち
        status = display.wait_on_show()
 
        if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
            print("Wait error:", status)
            exit
 
 
        while True:
            time.sleep(1)
 
 
    except KeyboardInterrupt:
        print("\nStopping...")
 
    except Exception as e: 
        print("Error:", repr(e))
 
    finally:
        dlsdk.delete_system()
        print("DisplayLink stopped")
 
 
if __name__ == "__main__":
    main()

画像が映れば後はこれをもとにいろいろとゴニョゴニョしていけば良いわけで。

FFmpeg RockchipMPP 対応版のビルド

写真をスライドショーのように流すのもいいけど、やっぱり動画を流したい。
けど、Rockchip RK3528A は4コアあるとはいえ、CPU にデコード全任せはでは重い… 1920×1080 は無理…

ということで、せっかく RK3528A には H.264 / H.265 等の VPU(ハードウェアデコード支援)があるのだから、それを使えばいいじゃない!

ただ、FFmpeg をそのままインストールしただけだと VPU は使えない。
使えるようにするには面倒だけどビルドし直さなければならない。 ホント面倒だけど…w

まずは下準備

$ sudo apt install -y \
           git cmake meson ninja-build pkg-config \
           build-essential nasm yasm \
           libdrm-dev libssl-dev \
           libass-dev libfreetype6-dev \
           libfontconfig1-dev libfribidi-dev \
           libx264-dev libx265-dev \
           libvpx-dev libopus-dev \
           libmp3lame-dev libvorbis-dev \
           libtheora-dev libopenjp2-7-dev

RockchipMPP版FFmpeg のビルドには librga というライブラリも必要なので導入。

$ cd /home/userName/
$ mkdir mpp_librga_src
$ cd mpp_librga_src
$ git clone https://github.com/airockchip/librga.git
$ cd librga
$ sudo mkdir -p /usr/local/include/librga
$ sudo mkdir -p /usr/local/lib
$ sudo mkdir -p /usr/local/lib/pkgconfig
$ sudo cp -a include/. /usr/local/include/librga/
$ sudo cp libs/Linux/gcc-aarch64/librga.so /usr/local/lib/

設定ファイルを作成する。

$ sudo vi /usr/local/lib/pkgconfig/librga.pc

内容は以下の通り。

prefix=/usr/local
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=/usr/include

Name: librga
Description: Rockchip RGA Library
Version: 1.10.6
Libs: -L${libdir} -lrga
Cflags: -I${includedir}/rga

設定ファイルの反映と確認。

$ sudo ldconfig
$ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
$ pkg-config --modversion librga
1.10.6
$ pkg-config --cflags --libs librga
-I/usr/local/include/librga -L/usr/local/lib -lrga

続いて、VPU を使うために必要な MPP というのをビルドする。
FFmpeg と VPU の仲介をしてくれるミドルウェアみたいなものやね。

$ cd /home/userName/
$ mkdir ffmpeg_mpp_src
$ cd ffmpeg_mpp_src
$ git clone -b jellyfin-mpp --depth=1 \
      https://gitee.com/nyanmisaka/mpp.git rkmpp
$ cd rkmpp
$ cmake -S . -B build \
  -DCMAKE_INSTALL_PREFIX=/usr/local \
  -DCMAKE_BUILD_TYPE=Release \
  -DBUILD_SHARED_LIBS=ON \
  -DBUILD_TEST=OFF
$ cmake --build build -j"$(nproc)"
$ sudo cmake --install build
$ sudo ldconfig

ビルドが終わったら確認。
以下のようになればOK

$ pkg-config --modversion rockchip_mpp
1.3.9

$ ldconfig -p | grep rockchip_mpp
librockchip_mpp.so.1 (libc6,AArch64) => /lib/aarch64-linux-gnu/librockchip_mpp.so.1
librockchip_mpp.so.1 (libc6,AArch64) => /usr/local/lib/librockchip_mpp.so.1
librockchip_mpp.so (libc6,AArch64) => /lib/aarch64-linux-gnu/librockchip_mpp.so
librockchip_mpp.so (libc6,AArch64) => /usr/local/lib/librockchip_mpp.so

ようやく FFmpeg RockchipMPP 対応版をビルドするところまできた。
で、ノーマルな FFmpeg と FFmpeg RockchipMPP 対応版の速度差を比較してみたかったりするので、RockchipMPP 対応版は /opt/ffmpeg-rockchip/ 配下にインストールすることにした。

$ cd /home/userName/
$ cd ffmpeg_mpp_src

$ git clone --depth=1 \
      https://github.com/nyanmisaka/ffmpeg-rockchip.git ffmpeg-rockchip

$ cd ffmpeg-rockchip

$ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib/aarch64-linuxgnu/pkgconfig:$PKG_CONFIG_PATH

$ ./configure \
      --prefix=/opt/ffmpeg-rockchip \
      --enable-gpl \
      --enable-version3 \
      --enable-libdrm \
      --enable-rkmpp \
      --enable-rkrga \
      --enable-shared \
      --disable-static

$ make -j4
$ sudo make install

$ echo /opt/ffmpeg-rockchip/lib | sudo tee /etc/ld.so.conf.d/ffmpeg-rockchip.conf
$ sudo ldconfig

これ、make にメッチャ時間かかる。
make -j4 を実行した後1時間くらいかかったんじゃないかな。

make が途中で止まる場合

特に NanoPi NEO3 Plus はメモリが 1GB のモデルもあり、その場合、make -j4 はギリギリ通らない。
SD カード運用ということもあり、Swap ファイルは未使用なためメモリが不足して止まる。

一番簡単な方法は「make -j4」はやめて

$ make -j2

とするか

$ make

要は「メモリが少ないなら並列処理は控えめに」ということ。
とはいえ、その分時間かかるけどね…

なので、一時的に Swap を有効にするというのも一つの手。
「make -j2」や「make」で待つのが嫌だ!という場合には以下を行ってもよい。

Swap ファイルは ext4 でフォーマットされている領域に作らなければならないので、Ubuntu だとちょっとひと手間が必要。

で、ext4 なのどこだ? と調べると以下の通り、mmcblk0p9 が該当する。
(一時的だからと、/tmp/swapfile みたいに作ると、そこは overlay な領域なので蹴られる)

$ lsblk -f
NAME FSTYPE FSVER LABEL   UUID   FSAVAIL FSUSE% MOUNTPOINTS
mmcblk0
|-mmcblk0p1
|-mmcblk0p2
|-mmcblk0p3
|-mmcblk0p4
|-mmcblk0p5
|-mmcblk0p6
|-mmcblk0p7
|-mmcblk0p8
|    ext4   1.0   rootfs   47c8ab88-2e47-4c52-99ff-8644b9683c15
-mmcblk0p9
     ext4   1.0   userdata ae70c8c7-db71-4345-980a-f51fe52a30b0

$ df -hT
Filesystem           Type     Size  Used Avail Use% Mounted on
tmpfs                tmpfs     97M  1.3M   96M   2% /run
overlay              overlay   55G  7.6G   45G  15% /
tmpfs                tmpfs    485M     0  485M   0% /dev/shm
tmpfs                tmpfs    5.0M  8.0K  5.0M   1% /run/lock
tmpfs                tmpfs     97M   12K   97M   1% /run/user/1000

ということで、ちょっと強引だけど、mmcblk0p9 を /mnt/swap にマウントして、そのなかにスワップファイルを作ってあげる。

$ sudo mkdir /mnt/swap
$ sudo mount /dev/mmcblk0p9 /mnt/swap
$ sudo dd if=/dev/zero of=/mnt/swap/swapfile \
    bs=1M count=1024 status=progress
$ sudo chmod 600 /mnt/swap/swapfile
$ sudo mkswap /mnt/swap/swapfile
$ sudo swapon -v /mnt/swap/swapfile

Swap が有効になったら再び make -j4 を実行。
Swap が 1GB あれば通るかと。

make が完了したら

何だかんだビルドが完了したらデコーダを確認してみる。

$ ./ffmpeg -hide_banner -decoders | grep rkmpp
 V..... av1_rkmpp            Rockchip MPP (Media Process Platform) AV1 decoder (codec av1)
 V..... h263_rkmpp           Rockchip MPP (Media Process Platform) H263 decoder (codec h263)
 V..... h264_rkmpp           Rockchip MPP (Media Process Platform) H264 decoder (codec h264)
 V..... hevc_rkmpp           Rockchip MPP (Media Process Platform) HEVC decoder (codec hevc)
 V..... mjpeg_rkmpp          Rockchip MPP (Media Process Platform) MJPEG decoder (codec mjpeg)
 V..... mpeg1_rkmpp          Rockchip MPP (Media Process Platform) MPEG1VIDEO decoder (codec mpeg1video)
 V..... mpeg2_rkmpp          Rockchip MPP (Media Process Platform) MPEG2VIDEO decoder (codec mpeg2video)
 V..... mpeg4_rkmpp          Rockchip MPP (Media Process Platform) MPEG4 decoder (codec mpeg4)
 V..... vp8_rkmpp            Rockchip MPP (Media Process Platform) VP8 decoder (codec vp8)
 V..... vp9_rkmpp            Rockchip MPP (Media Process Platform) VP9 decoder (codec vp9)

うんうん、いい感じ。
これで Rockchip の MPP を使った高速軽負荷デコードができるようになったはず!

試してみよう。
1920×1080 な映像を適当に用意して、MPPを使用してデコードしてみる。

$ /opt/ffmpeg-rockchip/bin/ffmpeg -re -stream_loop -1 -hwaccel rkmpp -i test.mp4 -f null -

CPU はだいたいどのコアも 5~9% 程度の使用率。
いいね、ほとんど負荷になってない。

じゃぁ、MPP を使わない場合はどうなるか…

$ ffmpeg -re -stream_loop -1 -i test.mp4 -f null -

だいたい、35~45% あたりをうろつく。

VPU は 4K 対応をうたっているけど、どのくらい CPU 使用率を抑えられるのか試してみた。

まずは MPP を使ってみる。

すごいね。
1コアだけ 35~40% の使用率になるけど 4K 映像デコードさせてこれなら申し分ない。

CPU では…?

無理だ!w

まぁ、MPP 使ったとしても、デコードは良いけどそのフレームを Display Link に転送するのがちょっと重いのか、そこだけで28~29msかかるので、1920×1080@30fpsは出ないね。
やっぱり20fps程度が良いところ。
(いや、まだ詰められるところはあるから詰めればもしかしたら…ではあるけど、そこまで求めていないw)

Rockchip MPP 対応版 FFmpeg で動画垂れ流しコード

import time
import subprocess
import numpy as np
import dlsdk
 
 
# 設定
FFMPEG = "/opt/ffmpeg-rockchip/bin/ffmpeg"
VIDEO = "/home/userName/test.mp4"
 
# FFmpeg
def start_ffmpeg(width, height):
    cmd = [
        FFMPEG,
        "-hide_banner",
        "-loglevel", "error",
 
        # 入力動画を無限ループ
        "-stream_loop", "-1",
 
        # Rockchip MPP ハードウェアデコード
        "-hwaccel", "rkmpp",
 
        "-i", VIDEO,
 
        "-an",
        "-sn",
 
        # DisplayLinkの解像度へ変換
        "-vf", f"scale={width}:{height}",
 
        # bgraにする
        "-pix_fmt", "bgra",
 
        # 生映像を標準出力へ
        "-f", "rawvideo",
        "pipe:1"
    ]
 
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=None,
        bufsize=0
    )
 
    return proc
 
 
# FFmpegからフレームを読み込む
def read_frame_into(pipe, frame_buffer):

    pos = 0
    size = len(frame_buffer)
 
    while pos < size:
        n = pipe.readinto(frame_buffer[pos:])
        if not n:
            return False
        pos += n
    return True
 
 
# メイン処理
def main():
 
   ffmpeg = None
    initialized = False
 
    try:

        # DisplayLink初期化
        status = dlsdk.create_system()
        if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
            return
 
        initialized = True
 
        devices = dlsdk.get_devices()
        if not devices:
            print("DisplayLink device not found")
            return
        device = devices[0]

        displays = device.get_displays()
        if not displays:
            print("Display not found")
            return
        display = displays[0]
 

        # DisplayLink解像度変更
 
        # 対応モード取得
        status, modes = display.modes()
        if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
            print("Cannot get display modes")
            exit()
 
        # 目的の解像度をを探す
        target_mode = None
 
        for mode in modes:
            w = mode.resolution.width
            h = mode.resolution.height
            hz = mode.refresh_rate_hz
 
            if w == 1920 and h == 1080 and hz == 60:
            #if w == 1024 and h == 768 and hz == 60:
            #if w == 832 and h == 624 and hz == 75:
            #if w == 640 and h == 480 and hz == 60:
                target_mode = mode
                break
 
        if target_mode is None:
            print("Target mode not found")
            exit()
 
        # 指定したモードで起動
        status = display.power_on(mode=target_mode)
        print("Power ON:", status)
 
        if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
            print("Failed to set display mode")
            exit()

 
        # DisplayLink解像度取得
        size = display.size()
        width = size.width
        height = size.height
  
        # Python側フレームバッファ
        frame = np.empty(
            (height, width, 4),
            dtype=np.uint8
        )
 
        # NumPy配列をバイト単位のメモリビューにする
        frame_bytes = memoryview(frame).cast("B")
  
        # FFmpeg起動
        ffmpeg = start_ffmpeg(width, height)
 
        # メインループ
        while True:
 
            loop_start = time.perf_counter()
 
            # FFmpegから直接フレーム読み込み
            ok = read_frame_into(
                ffmpeg.stdout,
                frame_bytes
            )
 
            if not ok:
                print("FFmpeg output ended")
                break

 
            # DisplayLinkへフレーム送信
            status = display.show(
                dlsdk.DLSDK_PIXEL_FORMAT_XRGB,
                frame
            )

            if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
                print("Show error:", status)
                break
 

            # DisplayLink転送完了待ち
            status = display.wait_on_show()
            if status != dlsdk.dlsdk_status.DLSDK_SUCCESS:
                print("Wait error:", status)
                break
 

    except KeyboardInterrupt:
        print("\nStopping...")
 
    except Exception as e:
        print("Error:", repr(e))
 
    finally:
        # FFmpeg終了
        if ffmpeg is not None:
            if ffmpeg.stdout is not None:
                ffmpeg.stdout.close()
            ffmpeg.terminate()
 
            try:
                ffmpeg.wait(timeout=3)
            except subprocess.TimeoutExpired:
                ffmpeg.kill()
                ffmpeg.wait()
 
            print("FFmpeg stopped")
 
        # DisplayLink終了
        if initialized:
            dlsdk.delete_system()
            print("DisplayLink stopped")
 

if __name__ == "__main__":
    main()

これで 1920×1080 な動画を再生していると、CPU 使用率はこんな感じ。

4K な動画だと、フレームレートはさらに 8fps にまで落ちて、CPU使用率も上がる。

この記事にコメントしてみる