2025,江端さんの技術メモ

先日、昨年末に放送された、「NHKスペシャル 量子もつれ アインシュタイン 最後の謎」について、その番組と無関係な日記をしました。

The other day, I wrote a diary entry about the “NHK Special: Quantum Entanglement - Einstein's Last Mystery” broadcast at the end of last year, but the diary had nothing to do with the program.

サッカー、野球、バレー、ありとあらゆるスポーツも"AI+センサ(カメラ等)+コンピュータ"で片付けていいんじゃないですか?

私、以前、「量子コンピュータ」の連載を担当していたとき、この番組に登場した人物を一人(デビット・ボームさん)を除いて全部調べ挙げました。

When I was in charge of the serialization of “Quantum Computers” before, I looked up all the people who appeared in the program except for one (David Bohm).

デビット・ボームさんは、新しい量子力学の理論の解釈を提唱し、その後、アカデミズムから冷や飯をくらい続けた方です。

David Bohm proposed a new interpretation of quantum mechanics but was shunned by academia.

その後、デビット・ボームさんは、超能力やカルトに傾倒していったようです。量子力学とは、そういう性質があるようです ―― まさか、研究者自身がハマるとは思いませんでしたが。

After that, David Bohm seems devoted to psychic powers and cults. Quantum mechanics seems to have that kind of property - I never thought that the researcher himself would become involved.

(↑クリックするとコラムに飛びます)

(Click to go to the column)

要するに、学会の主流でない研究の主張しつづけることは、研究者としてはリスクがある、ということです(ちなみに、デビット・ボームさんのこの理論は、現在も見直しされていないようです(一応、調べました))。

In short, there is a risk for researchers to continue to advocate research that is not mainstream in academic circles (incidentally, it seems that David Bohm's theory has not been revised to date (I checked)).

ただ、この「精神」は次の世代に引き継がれました。

However, this “spirit” was passed on to the next generation.

『どんな複雑で難関な理論であろうとも、実験で検証しなればならない』ということです。

"Regardless of a theory's complexity or challenge, it requires validation through experimentation.'

私、ジョン・スチュワート・ベルさんの「ベルの不等式」を理解した時に、凄いショックを受けました。

I was shocked when I understood John Stewart Bell's “Bell's Inequality.”

で、それを本当に実験したのが、ジョン・クラウザーさんです。

And the person who experimented was John Crowther.

私が尊敬している科学者の一人です。

He is one of the scientists I admire.

なにしろ、この人、「アインシュタインさんの大ファン」で、「量子力学の内容が難解で、理解できないから、全否定したい」という、おおよそ、量子力学の歴史の中で、登場できる人物ではないのです。

After all, this person is “a big fan of Einstein” and “wants to completely deny quantum mechanics because it is too difficult to understand” and is not a character who could appear in the history of quantum mechanics.

ところが、自分の願いに反して、実験によって量子力学の正当性を証明してしまった、という、稀有な人物なのです。

However, he is a rare person who, against his wishes, proved the validity of quantum mechanics through his experiments.

彼は、その実験設備を、スクラッチから「全部手作り」で完成させました。

He completed the experimental equipment “all by hand” from scratch.

実験予算ゼロの中で、大学の中の研究室の実験設備の残骸を拾ってきては、スクラッチから研究装置を作り上げた、という話は、私を魅了してやみません。

The story of how, with zero budget for experiments, he built up research equipment from scratch by picking up the remnants of experimental equipment from university laboratories never fascinates me.

さらに、他の研究者から、実験装置の不備を指摘されて、最終的にその実験を最終的に中止するに至ってしまった、という話は、『研究者という立場の弱さ」を示す顕著な例になります。

Furthermore, the story of how the experiment was eventually canceled after being pointed out by another researcher as being flawed in the experimental equipment is a striking example of “the weakness of the researcher's position.”

-----

で、まあ、私が言いたいのは、私も、今、従来にはなかった新しい手法をつかって、かなり無茶な方向で研究を暴走させている最中なのですが、デビット・ボームさんや、ジョン・クラウザーさんが喰らった「冷や飯」の話を聞くと―― なんか、力づけられるのです。

So, I'm also in the middle of letting my research run wild in a rather absurd direction, using new methods that have never been used before. Hearing about the “cold rice” that David Bohm and John Crowther had to eat gives me strength.

まあ、私の場合、ずっと「冷や飯」という公算が高いのですが ―― もうキャリアとか考える必要もないシニアとなっていることは、逆に「強み」と言えるかもしれません。

Well, in my case, I'll likely be “cold rice” for the rest of my life, but I'm already a senior citizen who doesn't have to think about my career anymore, which might be considered an advantage.

とは言え、晩年に、カルト宗教に嵌って、家族に迷惑をかけるシニアになるよりは、シミュレーションのコーディングで一人で狂っている老人の方が、まだマシだ、と思うのです。

I think that it is better to be a crazy older man alone, coding simulations, than to be a senior who gets caught up in a cult religion and causes trouble for his family in his later years.

2024,江端さんの技術メモ

頻繁に忘れれるので、メモ(バージョンによってコロコロ替わるので、これは一例)

sudo nano /etc/dhcpcd.conf
interface eth0
static ip_address=192.168.0.200/24
static routers=192.168.0.1
static domain_name_servers=8.8.8.8 8.8.4.4

======
別のケースとしては、
sudo nano /etc/netplan/99_config.yaml

 

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      addresses:
        - 192.168.101.30/24
      routes:
        - { to: default, via: 192.168.101.1 }
      nameservers:
        addresses:
          - 8.8.8.8
          - 8.8.4.4
    enx207bd2222d29:
      dhcp4: false
      addresses:
        - 192.168.0.15/24

 

2024,江端さんの技術メモ

古いラズパイの再活用方法

 

1. 背景と目的

実験システム用にTCが2台必要となったので、2019年の実証実験で使っていたラズパイを改造してTCにすることにした。

2. 今回のターゲットとしたラズパイとその構成

ディストリビューションはRaspbianである。

Linux raspberrypi 4.19.66-v7+ #1253 SMP Thu Aug 15 11:49:46 BST 2019 armv7l GNU/Linux

ラズパイにはイーサポートが1つしかないので、2つ目のポートは、USB-Ethを使う。

Cable Matters USB LAN変換アダプター 有線LANアダプター USB イーサネットアダプタ USB2.0 to RJ45

ディストリビューションはRaspbianである。

3. 古いラズパイの再構築

3.1. 状況の把握

古いラズパイを再利用するために、sudo apt-get updateをすると、

E: Failed to fetch http://raspbian.raspberrypi.org/raspbian/pool/main/c/cyrus-sasl2/libsasl2-modules_2.1.27~101-g0780600+dfsg-3+deb9u1_armhf.deb 404 Not Found [IP: 2a00:1098:0:80:1000:75:0:3 80]

E: Failed to fetch http://raspbian.raspberrypi.org/raspbian/pool/main/libv/libvpx/libvpx4_1.6.1-3+deb9u2_armhf.deb 404 Not Found [IP: 2a00:1098:0:80:1000:75:0:3 80]

という表示がでてくる。

3.2 リポジトリの更新更新

この場合リポジトリのURLが古い可能性があるので。/etc/apt/sources.listまたは/etc/apt/sources.list.d/raspi.listを編集し、最新のリポジトリURLを使用していることを確認する。

sudo nano /etc/apt/sources.list.d/raspi.list

deb行を以下のように変更する:

deb http://archive.raspbian.org/raspbian/ buster main contrib non-free rpi

3.3 パッケージリストを更新更新

リポジトリURLを変更した後、以下のコマンドを実行してパッケージリストを更新する。

アップグレード手順の例:

sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
sudo apt-get autoremove

必要に応じて、リリースをアップグレード:

sudo sed -i 's/stretch/buster/g' /etc/apt/sources.list
sudo sed -i 's/stretch/buster/g' /etc/apt/sources.list.d/raspi.list
sudo apt-get update
sudo apt-get upgrade

以上

2024,江端さんの技術メモ

これまで、動画の一部を切り取る、という作業をしてきましたが、有償ソフトであり、他の人には紹介できない状況でした。

しかし、先日、「OBS Studio」というフリーのソフトを教えてもらい、使ってみました。

ちょっとハマったところがあったので、自分の為にメモを残しておきます(私が分かりそうな部分は、ザックリ省略します)

Step 1 OBS起動

Step 2 「ウインドウキャプチャ」

で、ここからハマった

画面の切出しが分からない。ChatGPTに聞いたところ、『Altキーを押しながらドラッグで切り出せる』と言われたんだけど、
「Altキーを押しながらドラッグ」すると、画面全体を動かし、サイズも変えることはできますが、画面の部分を指定することができません。

で、なんどか質問を繰り返して

プレビュー画面の赤枠の「端」Altキーを押した状態でドラッグすると、その部分を切り取れます。

で分かりました。

Step 3 で、この後は、「録画開始」で切りとった部分だけが録画できるようになります。

今回は、比較的軽い「ハマり」で助かりました。

重要なのは「端」です。この一語がないだけで、ハマってしまうのが、この(IT)界隈の面倒くささです。

2024,江端さんの技術メモ

Windowsのmsys2でもmsys64でもUbuntu22.04でもdockerでも、VMAF機能尽きのffmpegが上手くいかなくて困っています。

g:\home\ebata\vmafに

FROM ubuntu:22.04

# 必要なパッケージのインストール
RUN apt-get update && apt-get install -y \
    wget build-essential yasm pkg-config libtool autoconf automake cmake git \
    libx264-dev libx265-dev meson ninja-build nasm python3-pip && \
    apt-get clean

# VMAFのビルドとインストール
RUN git clone https://github.com/Netflix/vmaf.git /vmaf && \
    cd /vmaf/libvmaf && \
    meson setup build --buildtype release && \
    ninja -C build && \
    ninja -C build install && \
    ldconfig

# FFmpegのビルドとインストール
RUN git clone https://git.ffmpeg.org/ffmpeg.git /ffmpeg && \
    cd /ffmpeg && \
    ./configure --enable-gpl --enable-libx264 --enable-libx265 --enable-libvmaf && \
    make && make install

# 実行用のディレクトリを作成
RUN mkdir /workspace
WORKDIR /workspace

で、

docker build -t custom-ffmpeg-vmaf .

をやっても、かならず、こんなエラーが出てきて、これが、どうしても消せない。

G:\home\ebata\vmaf>docker run --rm -it -v G:\download:/workspace ffmpeg-vmaf ffmpeg -i /workspace/104014_1280x720.mp4 -i /workspace/104014_1280x720.mp4 -lavfi libvmaf="model=/workspace/vmaf_v0.6.1.json" -f null -
ffmpeg version N-118070-g17e4746687 Copyright (c) 2000-2024 the FFmpeg developers
built with gcc 11 (Ubuntu 11.4.0-1ubuntu1~22.04)
configuration: --enable-gpl --enable-libx264 --enable-libx265 --enable-libvmaf
libavutil 59. 51.100 / 59. 51.100
libavcodec 61. 27.101 / 61. 27.101
libavformat 61. 9.101 / 61. 9.101
libavdevice 61. 4.100 / 61. 4.100
libavfilter 10. 6.101 / 10. 6.101
libswscale 8. 12.100 / 8. 12.100
libswresample 5. 4.100 / 5. 4.100
libpostproc 58. 4.100 / 58. 4.100
[Parsed_libvmaf_0 @ 0x55f5cc0b97c0] could not parse model config: /workspace/vmaf_v0.6.1.json
[AVFilterGraph @ 0x55f5cc0b91c0] Error initializing filters
Error : Invalid argument


2日間かけたのですが、疲れ果てたので、闘うのを止めました。

で、代替手段です。

■ブラウザのサービスを使う方法

VMAF on Browser

■VMAFを組み込んだffmpegのバイナリダウンロード
https://www.gyan.dev/ffmpeg/builds/
https://www.gyan.dev/ffmpeg/builds/#git-master-builds

ダウンロードして展開した場所の内部で(パスが通っていない)ffmpegを実行

G:\download\ffmpeg-2024-12-11-git-a518b5540d-full_build\bin>ffmpeg -i input.mp4 -i reference.mp4 -lavfi libvmaf -f null -

実行結果は以下の通り(欲しいのは以下の赤字のところだけ) # 今回は同じファイルを使って比較しただけ

G:\download\ffmpeg-2024-12-11-git-a518b5540d-full_build\bin>ffmpeg -i input.mp4 -i reference.mp4 -lavfi libvmaf -f null -
ffmpeg version 2024-12-11-git-a518b5540d-full_build-www.gyan.dev Copyright (c) 2000-2024 the FFmpeg developers
built with gcc 14.2.0 (Rev1, Built by MSYS2 project)
configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libaribb24 --enable-libaribcaption --enable-libdav1d --enable-libdavs2 --enable-libopenjpeg --enable-libquirc --enable-libuavs3d --enable-libxevd --enable-libzvbi --enable-libqrencode --enable-librav1e --enable-libsvtav1 --enable-libvvenc --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxeve --enable-libxvid --enable-libaom --enable-libjxl --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libcodec2 --enable-libilbc --enable-libgsm --enable-liblc3 --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint
libavutil 59. 49.100 / 59. 49.100
libavcodec 61. 26.100 / 61. 26.100
libavformat 61. 9.100 / 61. 9.100
libavdevice 61. 4.100 / 61. 4.100
libavfilter 10. 6.101 / 10. 6.101
libswscale 8. 12.100 / 8. 12.100
libswresample 5. 4.100 / 5. 4.100
libpostproc 58. 4.100 / 58. 4.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: mp41isom
creation_time : 2024-02-14T12:54:44.000000Z
Duration: 00:00:30.46, start: 0.000000, bitrate: 2228 kb/s
Stream #0:0[0x1](und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(progressive), 722x444 [SAR 1:1 DAR 361:222], 2032 kb/s, 29.97 fps, 29.97 tbr, 29970 tbn (default)
Metadata:
creation_time : 2024-02-14T12:54:44.000000Z
handler_name : VideoHandler
vendor_id : [0][0][0][0]
encoder : AVC Coding
Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 195 kb/s (default)
Metadata:
creation_time : 2024-02-14T12:54:44.000000Z
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Input #1, mov,mp4,m4a,3gp,3g2,mj2, from 'reference.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: mp41isom
creation_time : 2024-02-14T12:54:44.000000Z
Duration: 00:00:30.46, start: 0.000000, bitrate: 2228 kb/s
Stream #1:0[0x1](und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(progressive), 722x444 [SAR 1:1 DAR 361:222], 2032 kb/s, 29.97 fps, 29.97 tbr, 29970 tbn (default)
Metadata:
creation_time : 2024-02-14T12:54:44.000000Z
handler_name : VideoHandler
vendor_id : [0][0][0][0]
encoder : AVC Coding
Stream #1:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 195 kb/s (default)
Metadata:
creation_time : 2024-02-14T12:54:44.000000Z
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Stream mapping:
Stream #0:0 (h264) -> libvmaf
Stream #1:0 (h264) -> libvmaf
libvmaf:default -> Stream #0:0 (wrapped_avframe)
Stream #0:1 -> #0:1 (aac (native) -> pcm_s16le (native))
Press [q] to stop, [?] for help
Output #0, null, to 'pipe:':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: mp41isom
encoder : Lavf61.9.100
Stream #0:0: Video: wrapped_avframe, yuv420p(progressive), 722x444 [SAR 1:1 DAR 361:222], q=2-31, 200 kb/s, 29.97 fps, 29.97 tbn
Metadata:
encoder : Lavc61.26.100 wrapped_avframe
Stream #0:1(und): Audio: pcm_s16le, 48000 Hz, stereo, s16, 1536 kb/s (default)
Metadata:
encoder : Lavc61.26.100 pcm_s16le
creation_time : 2024-02-14T12:54:44.000000Z
handler_name : SoundHandler
vendor_id : [0][0][0][0]
[Parsed_libvmaf_0 @ 00000226c63e6640] VMAF score: 99.992967e=N/A speed=1.57x
[out#0/null @ 00000226c61d1ac0] video:392KiB audio:5616KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: unknown
frame= 913 fps= 47 q=-0.0 Lsize=N/A time=00:00:29.95 bitrate=N/A speed=1.55x

で、この評価の見方は以下の通りです。


1. [Parsed_libvmaf_0 @ 000001f6e343da40] VMAF score: 97.465053

  • [Parsed_libvmaf_0 @ ...]
    libvmafフィルタが処理を行ったことを示しています。
  • VMAF score: 97.465053
    最終的なVMAFスコアが97.465です。この値は、評価対象の動画(input.mp4)と参照動画(reference.mp4)の視覚的な品質の差異を数値化したものです。スコアは通常 0〜100 の範囲で表され、高いスコア(90以上)は非常に高品質であることを意味します。

2. e=N/A speed=0.299x

  • e=N/A
    現在のところ特定のエンコードステータスがありません(通常は気にしなくて大丈夫です)。
  • speed=0.299x
    処理速度を示しています。speed=0.299xは、処理がリアルタイムの29.9%の速度で進行したことを意味します。この低速さは、VMAF計算が非常に計算集約的であるためです。

3. [out#0/null @ 000001f6e1dc98c0] video:754KiB ... muxing overhead: unknown

  • video:754KiB
    出力された動画データの合計サイズが754KiBであることを示します。
  • audio:0KiB subtitle:0KiB other streams:0KiB
    音声、字幕、その他のストリームが含まれていないことを示します(VMAF評価ではこれらは不要)。
  • global headers:0KiB
    グローバルヘッダーのデータサイズも0KiBであることを示します。
  • muxing overhead: unknown
    出力ファイルのオーバーヘッド(余分なデータ量)が計算されていないことを示しています。nullフォーマットを使用しているため、通常は無視して問題ありません。

4. frame= 1755 fps= 18 q=-0.0 Lsize=N/A time=00:00:29.27 bitrate=N/A speed=0.299x

  • frame=1755
    VMAF計算の対象となったフレーム数です。1755フレームが処理されました。
  • fps=18
    平均で1秒間に18フレームが処理されたことを示します。
  • q=-0.0
    映像の品質パラメータで、VMAF評価では特に重要ではありません。
  • Lsize=N/A
    出力ファイルの総サイズ(nullフォーマットなので未計算)。
  • time=00:00:29.27
    処理された動画の総時間が29.27秒であることを示します。
  • bitrate=N/A
    ビットレートが計算されていないことを示します(評価中なので未計算)。
  • speed=0.299x
    処理速度がリアルタイムの約30%であることを再度示しています。

 

■VMAFについての評価方法や読み方については、以下が詳しいです。

【Netflixでも使ってる!】 映像品質評価ライブラリVMAFを使ったエンコードパラメータの決定方法の紹介

2024,江端さんの技術メモ

注意点は、通信プロトコルのメトリクスが2以上あろうとも、
main()の開始近くで

GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);
を1回、

main()の終了近くで

// 共有のイベントループを実行
g_main_loop_run(main_loop);

// メインループをクリーンアップ
g_main_loop_unref(main_loop);

を、それぞれ1回だけ発行すること。

g_main_loop_run(main_loop)でプログラムは無限ループに入ってロックするので、その下のプログラムは実行されないことにも注意。

(以下はイメージ。コンパイルしても稼動しません)

#include <glib.h>
#include <gst/gst.h>
#include <pthread.h>

typedef struct {
    GstElement *pipeline;
    GMainLoop *main_loop;  // 共有のイベントループ
} ArgsRtspRTSP;

typedef struct {
    GstElement *srtserversink;
    GMainLoop *main_loop;  // 共有のイベントループ
} Args;

void *print_rtsp_metrics_thread(void *arg) {
    ArgsRtspRTSP *args = (ArgsRtspRTSP *)arg;
    // RTSPメトリクス計測のロジック
    while (1) {
        g_usleep(1000000); // 1秒ごとに計測
        g_print("RTSP Metrics: Calculating...\n");
    }
    return NULL;
}

void *print_srt_metrics_thread(void *arg) {
    Args *args = (Args *)arg;
    // SRTメトリクス計測のロジック
    while (1) {
        g_usleep(1000000); // 1秒ごとに計測
        g_print("SRT Metrics: Calculating...\n");
    }
    return NULL;
}

void initialize_rtsp_pipeline(ArgsRtspRTSP *args) {
    // パイプラインの初期化
    args->pipeline = gst_pipeline_new("rtsp-pipeline");
    GstBus *bus = gst_element_get_bus(args->pipeline);
    gst_bus_add_watch(bus, (GstBusFunc)gst_bus_async_signal_func, args->main_loop);
    gst_object_unref(bus);
}

void initialize_srt_pipeline(Args *args) {
    // パイプラインの初期化
    args->srtserversink = gst_element_factory_make("srtserversink", "srt-sink");
    GstBus *bus = gst_element_get_bus(args->srtserversink);
    gst_bus_add_watch(bus, (GstBusFunc)gst_bus_async_signal_func, args->main_loop);
    gst_object_unref(bus);
}

int main(int argc, char *argv[]) {
    gst_init(&argc, &argv);

    // 共有のイベントループを作成
    GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);

    // RTSP引数の初期化
    ArgsRtspRTSP rtsp_args;
    rtsp_args.main_loop = main_loop;
    initialize_rtsp_pipeline(&rtsp_args);

    // SRT引数の初期化
    Args srt_args;
    srt_args.main_loop = main_loop;
    initialize_srt_pipeline(&srt_args);

    // スレッドの作成
    pthread_t rtsp_thread, srt_thread;

    pthread_create(&rtsp_thread, NULL, print_rtsp_metrics_thread, &rtsp_args);
    pthread_create(&srt_thread, NULL, print_srt_metrics_thread, &srt_args);

    // 共有のイベントループを実行
    g_main_loop_run(main_loop);

    // メインループをクリーンアップ
    g_main_loop_unref(main_loop);
    return 0;
}

ようするに、開始と終了は、1回のみ宣言する、ということ。

g_main_loop_run(main_loop) の後は、プログラムがそこでロックされて、その後のプログラムコードに進まないことに注意。

2024,江端さんの技術メモ

改めてGeoEntityDBの作り方の手順を示して下さい。

 

以下に、GeoEntityDB を作成する手順を改めて示します。


GeoEntityDBの作成手順

1. GeoEntityDBの作成

  1. PostgreSQLに接続します。
    psql -U postgres

     

  2. 新しいデータベースを作成し、PostGISを有効化します。
    CREATE DATABASE GeoEntityDB; \c GeoEntityDB CREATE EXTENSION postgis; CREATE EXTENSION pgrouting; -- 必要に応じて有効化

     


2. 地物テーブルの作成

地物データを格納するためのテーブルを作成します。

  1. テーブルスキーマを定義します。以下はカテゴリ付きの地物データ用のスキーマです。
    CREATE TABLE geo_entities ( id SERIAL PRIMARY KEY, -- 自動生成の一意識別子 category VARCHAR(10), -- カテゴリ (例: A1, B1, D2) stay_time INTEGER, -- 滞在時間 (分) geom GEOMETRY(POINT, 4326) -- 座標データ (EPSG:4326の緯度経度) );

    # id SERIAL PRIMARY KEY の部分は自動的にインポートされるので、csvの先頭の番号は行削除しておくこと

  2. インデックスを作成して検索性能を向上させます。
    CREATE INDEX geo_entities_geom_idx ON geo_entities USING GIST (geom);

     


3. CSVデータのインポート

CSVファイルから地物データをインポートします。

  1. 一時テーブルを作成(緯度・経度データを変換するために使用)。
    CREATE TEMP TABLE temp_geo_entities ( category VARCHAR(10), stay_time INTEGER, latitude DOUBLE PRECISION, longitude DOUBLE PRECISION );

     

  2. CSVをインポート(ファイルのパスは実際のCSVファイルの場所を指定してください)。
    COPY temp_geo_entities (category, stay_time, latitude, longitude) FROM '/path/to/geo_entities.csv' DELIMITER ',' CSV HEADER;

    # 実は、'/path/to/geo_entities.csv'の部分で酷く転けた記憶があるが、結果的にインポートできてしまったので、忘れた。

  3. 本テーブルに挿入(緯度・経度を POINT 型に変換して挿入)。
    INSERT INTO geo_entities (category, stay_time, geom) SELECT category, stay_time, ST_SetSRID(ST_MakePoint(longitude, latitude), 4326) FROM temp_geo_entities;

     

  4. データが正しくインポートされたか確認します。
    SELECT * FROM geo_entities LIMIT 10;

    #ここから先は試していないが、メモに残しておく。


4. 既存DBとの連携設定

方法 1: postgres_fdw を使った連携

hakata_ex_cost_db(道路ネットワークDB)と GeoEntityDB を連携させます。

  1. postgres_fdw を有効化(GeoEntityDBで実行)。
    CREATE EXTENSION postgres_fdw;

     

  2. 外部サーバーを登録
    CREATE SERVER hakata_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname 'hakata_ex_cost_db', host 'localhost');

     

  3. 外部DBへのユーザーマッピングを作成
    CREATE USER MAPPING FOR current_user SERVER hakata_server OPTIONS (user 'postgres', password 'your_password');

     

  4. 必要なテーブルをインポート(例: 道路ネットワークテーブル)。
    IMPORT FOREIGN SCHEMA public FROM SERVER hakata_server INTO public;

     

  5. テーブルが使用可能か確認
    SELECT * FROM ways LIMIT 10;

     


5. 経路計算の設定

最短経路計算の基本例

エージェントの現在地から目的地(地物)までの経路を計算します。

  1. エージェントの現在地に最も近いノードを取得
    SELECT id FROM ways_vertices_pgr ORDER BY geom <-> ST_SetSRID(ST_MakePoint(130.3980676, 33.59543503), 4326) LIMIT 1;

     

  2. 地物の位置に最も近いノードを取得
    SELECT id FROM ways_vertices_pgr ORDER BY geom <-> (SELECT geom FROM geo_entities WHERE id = 1) LIMIT 1;

     

  3. Dijkstraアルゴリズムで経路計算
    SELECT seq, id1 AS node, id2 AS edge, cost FROM pgr_dijkstra( 'SELECT id, source, target, cost FROM ways', start_node_id, end_node_id, directed := false );

     


6. 地物データと経路計算の組み合わせ

以下のようなクエリで、地物データの位置と経路計算を連携させます。

  1. エージェントの現在地から15メートル以内の地物を検索(これ、上手く稼動しません。下の方は使って下さい)
    SELECT id, category, stay_time, ST_Distance(geom, ST_SetSRID(ST_MakePoint(130.3980676, 33.59543503), 4326)) AS distance FROM geo_entities WHERE ST_DWithin(geom, ST_SetSRID(ST_MakePoint(130.3980676, 33.59543503), 4326), 15);

     

  2. 発見した地物への最短経路を計算
    SELECT seq, id1 AS node, id2 AS edge, cost FROM pgr_dijkstra( 'SELECT id, source, target, cost FROM ways', start_node_id, (SELECT id FROM ways_vertices_pgr ORDER BY geom <-> (SELECT geom FROM geo_entities WHERE id = found_geo_id) LIMIT 1), directed := false );

     


まとめ

  1. GeoEntityDBの作成とデータインポート:
    • PostGISとpgRoutingを有効化し、CSVデータを地物テーブルに格納。
  2. 既存DBとの連携:
    • postgres_fdw を使用して、hakata_ex_cost_db の道路ネットワークを参照。
  3. 経路計算の実装:
    • エージェントの現在地と地物データを連携させ、道路ネットワーク上で最短経路を計算。

これにより、エージェントが嗜好に応じて地物に向かう動きをリアルにシミュレーションする仕組みを構築できます。

ーーーーー
こういうミスもやったので、以下のメモに残しておく。

SELECT * FROM geo_entities WHERE category = 'B1'; としたらエントリーがでてきませんが、
SELECT * FROM geo_entities WHERE category = ' B1'; としたらエントリーがでてきました。
categoryの要素でえ最初に空白が入っているものを修正したいのですが、どうしたら良いでしょうか

 

category の要素の先頭に空白が含まれている場合、それを修正するには TRIM 関数を使用して先頭と末尾の空白を取り除く必要があります。以下に、データを修正する方法を説明します。


手順

1. データの確認

まず、空白が含まれているデータを確認します。

SELECT * FROM geo_entities WHERE category LIKE ' %' OR category LIKE '% ';

 

  • LIKE ' %' は先頭に空白があるデータを検索します。
  • LIKE '% ' は末尾に空白があるデータを検索します。

2. 空白を修正

TRIM 関数を使用して、category カラムから空白を取り除きます。

すべてのエントリを修正する場合

以下のクエリで、空白を削除した値に更新します。

UPDATE geo_entities SET category = TRIM(category) WHERE category LIKE ' %' OR category LIKE '% ';

 


3. 修正結果の確認

修正後に、データが正しく更新されたか確認します。

SELECT * FROM geo_entities WHERE category LIKE ' %' OR category LIKE '% ';

 

  • クエリの結果が空であれば、すべての空白が正常に削除されたことを意味します。

4. 再度クエリを実行

修正後、以下のようにクエリを実行して正しいデータが取得できるか確認します。

SELECT * FROM geo_entities WHERE category = 'B1';

 


補足

  • TRIM の動作: TRIM 関数は、先頭と末尾の両方から空白を削除します(不要なスペースも含む)。
  • データの一時バックアップ: データ更新は破壊的変更となるため、念のため更新前にバックアップを取得しておくことをおすすめします。

これで問題が解決するはずです!もしさらなるサポートが必要であればお知らせください。


ざっくりとした使い方

 

geoentitydb=# \l
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype | ICU Locale | ICU Rules | Access privileges
--------------------+----------+----------+-----------------+------------+------------+------------+-----------+-----------------------
geoentitydb | postgres | UTF8 | libc | en_US.utf8 | en_US.utf8 | | |
hakata_db | postgres | UTF8 | libc | en_US.utf8 | en_US.utf8 | | |
hakata_ex_cost_db | postgres | UTF8 | libc | en_US.utf8 | en_US.utf8 | | |

geoentitydb=# SELECT id, category, stay_time, geom FROM geo_entities where id = 29;
id | category | stay_time | geom
----+----------+-----------+----------------------------------------------------
29 | B1 | 20 | 0101000020E610000006802A6E9C4C6040099FB5F44BCB4040

 

geoentitydb=# SELECT id, category, stay_time, ST_X(geom) AS longitude, ST_Y(geom) AS latitude FROM geo_entities where id = 29;
id | category | stay_time | longitude | latitude
----+----------+-----------+-------------+-------------
29 | B1 | 20 | 130.3940955 | 33.58825549

こんな感じででてくる。

以下は、130.4064827799044, 33.591348965846656 から 100メートル以内のジオエンティティを抽出するSQLです。(こっちが正しい)

geoentitydb=# SELECT id, category, stay_time, ST_Distance(geom::geography, ST_SetSRID(ST_MakePoint(130.4064827799044, 33.591348965846656), 4326)::geography) AS distance FROM geo_entities WHERE ST_DWithin(geom::geography, ST_SetSRID(ST_MakePoint(130.4064827799044, 33.591348965846656), 4326)::geography, 100);

id | category | stay_time | distance
------+----------+-----------+-------------
304 | A2 | 60 | 68.03775787
357 | A3 | 120 | 75.80618279
366 | A3 | 120 | 76.48825161
414 | D2 | 180 | 80.65210758
415 | D2 | 180 | 79.93815148
429 | D2 | 180 | 61.23626794
435 | D2 | 180 | 62.50441807
546 | E1 | 480 | 77.36793898
550 | E1 | 480 | 90.09868468
813 | E2 | 300 | 54.40053345
1174 | F2 | 10 | 92.85196166
1217 | F2 | 10 | 89.44133931
1933 | H2 | 30 | 64.5772006
1942 | H2 | 30 | 84.70916514
(14 rows)

2024,江端さんの技術メモ

江端がずっとメンテナンスしているgetDijkstraPath関数は、costを変更することによって、ダイクストラ計算を引き曲げているので、距離が正確に出せない。

そこで、agg_costを使わずに、agg_length_mというものを作って、強制的に距離を作り出すようにコードを変更した。

 

func getDijkstraPath(dbMap *sql.DB, locInfoStart, locInfoGoal LocInfo) ([]LocInfo, float64) {

	var path []LocInfo // 経路 (返り値の一つ目)
	var totalDistanceKm float64

	// 例外処理 locInfoStart.source == locInfoGoal.source の場合
	if locInfoStart.Source == locInfoGoal.Source {
		source := locInfoStart.Source

		// SQLクエリの作成
		query := fmt.Sprintf(`
		SELECT x1, y1
		FROM ways
		WHERE source = %d;
	`, source)

		// SQLクエリの実行
		var x1, y1 float64
		err := dbMap.QueryRow(query).Scan(&x1, &y1)
		if err != nil {
			fmt.Println("tools.go line 204 error occures")
			//log.Fatal(err)  // ここで止めたらリターンしなくなる
		}

		var loc LocInfo
		loc.Source = source
		loc.Lon = x1
		loc.Lat = y1

		path = append(path, loc)

		totalDistanceKm = 0.0
		return path, totalDistanceKm
	}

	// こちらは、cost, reverse_cost を使っている
	query := `
	SELECT seq, source, target, x1, y1, x2, y2, agg_cost, length_m
	FROM pgr_dijkstra(
		'SELECT gid as id, source, target, cost, reverse_cost FROM ways', 
		$1::bigint, 
		$2::bigint, 
		directed:=false
	) a 
	INNER JOIN ways b ON (a.edge = b.gid) 
	ORDER BY seq
	`

	rowsDijkstra, errDijkstra := dbMap.Query(query, locInfoStart.Source, locInfoGoal.Source)
	if errDijkstra != nil {
		log.Fatal(errDijkstra)
		os.Exit(1)
	}
	defer rowsDijkstra.Close()

	var agg_cost float64
	var length_m float64
	agg_length_m := 0.0

	var loc LocInfo
	var x1, y1, x2, y2 float64
	var seq int
	var target int
	var source int

	isFirstCheck := true
	isSourceCheck := true

	count := 0

	for rowsDijkstra.Next() {
		// まずnodeを読む
		if err := rowsDijkstra.Scan(&seq, &source, &target, &x1, &y1, &x2, &y2, &agg_cost, &length_m); err != nil {
			fmt.Println(err)
		}

		agg_length_m += length_m
		// fmt.Println("length_m", length_m, "agg_length_m", agg_length_m)

		// 最初の1回だけチェックのために入る これについては、https://wp.kobore.net/江端さんの技術メモ/post-7668/を参照のこと
		// もし rowsDijkstra.Scanで最初のsource値を読みとり、locInfoStart.Source の値と同じであれば、x1,y1をベースとして、異なる値であれば、x2,y2をベースとする

		if isFirstCheck {
			if source == locInfoStart.Source {
				isSourceCheck = true // x1, y1をベースとする処理になる
			} else {
				isSourceCheck = false // x2,y2をベースとする処理になる
			}
			isFirstCheck = false // 最初の1回をチェックすることで、2回目はこのループには入らなくなる
		}

		//var loc LocInfo

		if isSourceCheck { // x1, y1をベースとする処理
			loc.Source = source
			loc.Lon = x1
			loc.Lat = y1
		} else { // x2,y2をベースとする処理
			loc.Source = target
			loc.Lon = x2
			loc.Lat = y2
		}
		path = append(path, loc)

		count++
	}

	// ラストノードだけは手入力 (ここは引っくり返す) (今、ここの部分は無視されている(いいのかな?))
	if isSourceCheck { // x1, y1をベースとする処理
		loc.Source = target
		loc.Lon = x2
		loc.Lat = y2
	} else { // x2,y2をベースとする処理
		loc.Source = source
		loc.Lon = x1
		loc.Lat = y1
	}

	// なんかprg_dijkstraが変な値を返す場合があるので、その対応(ロジカルではないが、パッチ的に対応)
	if loc.Source == locInfoGoal.Source {
		path = append(path, loc)
	}

	fmt.Println("count", count)

	if count == 0 { // 1行のみの場合、ヌルになるという問題に対応するため、
		loc.Source = locInfoGoal.Source
		loc.Lon = locInfoGoal.Lon
		loc.Lat = locInfoGoal.Lat

		// 入力値の指定
		source := locInfoStart.Source
		target := locInfoGoal.Source

		// SQLクエリの作成
		query := fmt.Sprintf(`
		SELECT length_m, x1, y1, x2, y2
		FROM ways
		WHERE source = %d AND target = %d;
	`, source, target)

		// SQLクエリの実行
		var length float64
		var x1, y1, x2, y2 float64
		err := dbMap.QueryRow(query).Scan(&length, &x1, &y1, &x2, &y2)
		if err != nil {
			log.Println("First attempt failed. Retrying with swapped source and target.")
			// 入れ替えたsourceとtargetの値でクエリを再実行
			query = fmt.Sprintf(`
			SELECT length_m, x1, y1, x2, y2
			FROM ways
			WHERE source = %d AND target = %d;
		`, target, source)
			err = dbMap.QueryRow(query).Scan(&length, &x1, &y1, &x2, &y2)
			if err != nil {
				//log.Fatal(err)  // 諦める。とりあえず、エラーを返す
				return nil, 0.0
			}
		}

		// 結果の出力
		fmt.Printf("length_m: %f\n", length)
		fmt.Printf("source: %d\n", source)
		fmt.Printf("target: %d\n", target)
		fmt.Printf("x1: %f\n", x1)
		fmt.Printf("y1: %f\n", y1)
		fmt.Printf("x2: %f\n", x2)
		fmt.Printf("y2: %f\n", y2)

		if source == locInfoGoal.Source {
			loc.Lon = x1
			loc.Lat = y1
		} else {
			loc.Lon = x2
			loc.Lat = y2
		}

		agg_cost = length

		fmt.Println("loc", loc)

		path = append(path, loc) // 354
	} // if count == 0

	//totalDistanceKm = agg_cost / 1000.0
	totalDistanceKm = agg_length_m / 1000.0
	return path, totalDistanceKm
}

2024,江端さんの技術メモ

仮想RTSPカメラの作り方と使い方のメモ

2024/11/21
江端
この仮想RTSPカメラは、クライアント(受信側)の接続を止めて、再度接続しようとするとクライアント(受信側)に動画が表示されなくなる、という欠点が発覚しております(2024/11/26) 

1. 背景と要件

1.1. 背景

  1. 地車間実験において複数カメラを準備する為、複数の仮想のRTSPカメラが必要となったため、これに対応する

1.2. 仮想RTSPカメラの要件

  1. 1台のWindowsPCにて最大4台程度の仮想RTSPカメラが実現できること
  2. 仮想カメラは指定したmp4ファイルの映像を配送する
  3. 上記のmp4ファイルは繰り返し再生できることが望ましい
  4. 仮想RTSPカメラは、通常のRTSPカメラと同様にrtsp://127.0.0.1:8554/testという形式で指定できること
  5. できるだけ簡易かつ汎用的なコマンドで実現できることが望ましい。

1.3. 事前検討

  1. 仮想RTSPカメラの実現方法としては、(1)ffmpeg, (2)Gstreamer, (3)VLC(VideoLAN Client), (4)C言語(Gstreamerライブラリ)が挙げられている。
  2. 検討の結果、mp4ファイルを繰り返し再生できる方法は指定されているが、試したところ実際に動いたのはVLCのみだったの、VLCで実現することにした。
  3. vlcのバージョンは、3.0.20以上であること(3.0.14では稼動しないことを確認済)。

2. 説明用の設定

  • 仮想RTSPカメラを設定するWindows10パソコンのIPアドレスを、便宜的に、192.168.0.3として取り扱うこととする。
  • 仮想RTSPカメラを再生するWindows10パソコンのIPアドレスを、便宜的に、192.168.0.25として取り扱うこととする。

3. VLCによる仮想カメラの作り方

3.1. 事前準備: 送信側(192.168.0.3)のファイアウォール設定設定

本節の設定は、設定しなくても動くこともあるので、動かなくなった場合のみに対応する。

送信側のVLCは、RTSP制御パケット(TCP:8554)とRTPデータストリーム(UDPポート)を送信する。これらがブロックされないようにする。

3.1.1. 受信規則の設定(クライアントからのRTSP制御パケットを許可)を許可)

  1. 「Windowsセキュリティ」懼「ファイアウォールとネットワーク保護」懼「詳細設定」を開きます。
  2. 左側の「受信規則」を右クリックして「新しい規則」を選択。
  3. **「ポート」**を選択して「次へ」。
  4. TCPを選択し、「特定のローカルポート」に8554を入力して「次へ」。
  5. 「接続を許可する」を選択し「次へ」。
  6. プロファイルを選択(プライベートにチェックを入れる)し「次へ」。
  7. 名前を設定(例: VLC RTSP TCP)して「完了」をクリック。

3.1.2. 送信規則の設定(RTP/RTCPデータを送信)を送信)

  1. 同様に「送信規則」を右クリックして「新しい規則」を選択。
  2. **「ポート」**を選択して「次へ」。
  3. UDPを選択し、「特定のローカルポート」に5000-5500を入力して「次へ」。
  4. 「接続を許可する」を選択し「次へ」。
  5. プロファイルを選択(プライベートにチェックを入れる)し「次へ」。
  6. 名前を設定(例: VLC RTP UDP)して「完了」をクリック。

3.2. 事前準備: 受信側(192.168.0.25)のファイアウォール設定設定

受信側が、Windows10でない場合は、本節は無視して下さい。

受信側のGStreamerは、RTSP制御パケット(TCP:8554)とRTPデータストリーム(UDPポート)を受信します。これらを許可します。

3.2.1. 受信規則の設定(RTSPとRTPストリームの受信を許可)を許可)

  1. 「Windowsセキュリティ」懼「ファイアウォールとネットワーク保護」懼「詳細設定」を開きます。
  2. 左側の「受信規則」を右クリックして「新しい規則」を選択。
  3. **「ポート」**を選択して「次へ」。
  4. TCPを選択し、「特定のローカルポート」に8554を入力して「次へ」。
  5. 「接続を許可する」を選択し「次へ」。
  6. プロファイルを選択(プライベートにチェックを入れる)し「次へ」。
  7. 名前を設定(例: GStreamer RTSP TCP)して「完了」をクリック。

3.2.1.1. RTPストリームのポート設定設定**

  1. 再度「受信規則」を右クリックして「新しい規則」を選択。
  2. **「ポート」**を選択して「次へ」。
  3. UDPを選択し、「特定のローカルポート」に5000-5500を入力して「次へ」。
  4. 「接続を許可する」を選択し「次へ」。
  5. プロファイルを選択(プライベートにチェックを入れる)し「次へ」。
  6. 名前を設定(例: GStreamer RTP UDP)して「完了」をクリック。

3.2.2. アプリケーション許可(必要に応じて)応じて)

GStreamerがファイアウォールでブロックされている場合、gst-launch-1.0の実行ファイルを許可します。

  1. 「Windows Defender ファイアウォールを介したアプリまたは機能を許可する」をクリック。
  2. 「別のアプリを許可する」をクリックし、GStreamerの実行ファイル(例: C:\msys64\mingw64\bin\gst-launch-1.0.exe)を指定して追加。
  3. 「プライベート」にチェックを入れて「OK」をクリック。

3.3. 仮想RTSPカメラの起動方法

以下のコマンド(例示)で起動する。

$ "C:\Program Files\VideoLAN\VLC\vlc.exe" -vvv "0326_JP.mp4" --sout="#rtp{sdp=rtsp://0.0.0.0:8554/test}" --loop

コマンドの内容は、以下の通りである。

  • "C:\Program Files\VideoLAN\VLC\vlc.exe": vlc.exeを起動するコマンドをフルパスで指定。
  • vvv: デバッグ情報を詳細に出力する。問題発生時のトラブルシューティングに役立つ。
  • "0326_JP.mp4": 再生するmp4のファイル名(このファイル名は例示であるので、使用時のファイル名を使用する)
  • rtsp://0.0.0.0:8554/test: RTSPアドレス名。0.0.0.0は特殊なアドレスで、「すべてのネットワークインターフェース」を意味する。サーバーがリッスンする対象が、特定のIPアドレスではなく、すべての有効なインターフェースで接続を受け付ける設定である。8554は、ポート番号である。RTSPのデフォルトポート番号は554であるが、アプリケーションによって異なるポート番号が指定されることがある。この例では8554を使用している。/testは、ストリームのパス名を示す。サーバー内でストリームを識別するための名前である。
  • loop: ファイルの繰り返し再生を指定する。

なお、上記のコマンドを投入すると、以下の黒い画面が立ち上がり、コマンドは直ちにリターンする。矢印は再生している位置を示している。

このコマンドを停止するには、この画面を右上の『・』を押下する。

3.4. 仮想RTSPカメラのフレームレート/画像サイズの変更方法

VLCを使って仮想RTSPカメラを実現した際に、送信するフレームレートや画像サイズ(解像度)を変更することは可能である。以下の方法で設定を変更する。

$ "C:\Program Files\VideoLAN\VLC\vlc.exe" -vvv "0326_JP.mp4" --sout="#transcode{vcodec=h264,fps=15,width=640,height=360}:rtp{sdp=rtsp://0.0.0.0:8554/test}" --loop

  • fps=15:
    • 送信するフレームレートを15fpsに設定する。
    • 必要に応じて、他の値(例: 3060)に変更可能。
  • width=640,height=360:
    • 解像度を640x360に設定しています。
    • 必要に応じて、他の解像度(例: 1920x1080640x480)に変更可能。

3.5. 複数の仮想RTSPカメラの起動方法

今回のケースでは、1台のPCで複数のカメラを実現するので、IPアドレスを固定として、異なるポート番号を使って、複数のカメラを実現することとする。

具体的には、以下のポート番号(8554, 8555, 8556)を変えて、それぞれコマンドを押下することで、同じmp4ファイルで3台のカメラが実現できる(もちろん、mp4ファイルを変えても良い)。

$ "C:\Program Files\VideoLAN\VLC\vlc.exe" -vvv "0326_JP.mp4" --sout="#rtp{sdp=rtsp://0.0.0.0:8554/test}" --loop

$ "C:\Program Files\VideoLAN\VLC\vlc.exe" -vvv "0326_JP.mp4" --sout="#rtp{sdp=rtsp://0.0.0.0:8555/test}" --loop

$ "C:\Program Files\VideoLAN\VLC\vlc.exe" -vvv "0326_JP.mp4" --sout="#rtp{sdp=rtsp://0.0.0.0:8556/test}" --loop

4. 仮想RTSPカメラの起動確認方法

4.1. VLCを使った起動確認方法

簡易な確認方法として、画像受信もVLCを使用する方法を提示する。

受信側Windows10(192.168.0.25)のVLCを立ち上げて、以下の操作を行う。

ネットワークURLに、rtsp://192.168.0.3:8554/testと入力する。

これで映像が表示されれば成功である。

4.2. GStreamerを使った起動確認方法

以下のコマンドを投入して下さい。

$ gst-launch-1.0 -v rtspsrc location=rtsp://192.168.0.3:8554/test latency=0 ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! glimagesink

(Windows10の場合は、autovideosinkでは動かないことが多い)

4.3. 注意点

仮想RTSPカメラはmp4ファイルを繰り返し再生するが、ファイル終了時にVLC(および殆どのクライアント)は、映像が停止したものと判断して再生を終了する。

現時点で、この対応方法は不明である。

以上