PR
AI・プログラミングトックリツバメ観察

自宅ベランダのコシアカツバメ観察プロジェクト 第2弾 〜YouTubeライブ配信の技術的な壁を乗り越える〜

はじめに

第1弾の記事では、ベランダにやってきたコシアカツバメ(通称:トックリツバメ)の観察システムを低コストで構築した話をご紹介しました。今回はYouTubeライブ配信用のプログラム作成で苦戦したので、その技術的な解決策を詳しくお話ししたいと思います。

同じように野鳥観察や定点観測をやりたい方の参考になれば嬉しいです!

YouTubeライブ配信の2つの方法

YouTubeライブ配信には大きく分けて2つの方法があります。

ウェブカメラ配信(簡単だけど制約が多い)

一つは、webカメラを使って直接ストリーミングをかけるもの。これが最も簡単で手軽です。ブラウザからYouTube Studioにアクセスして、「ウェブカメラ」を選択するだけで配信開始できます。

しかし、この方法だと大きな問題がありました:

  • 定期的に何時から何時までを定点で配信できない
  • 長時間録画の場合、ダウンロードがいつまでたってもできる状態にならない

実際に39時間の連続配信を試したところ、YouTube側でダウンロード処理(一週間経過してもダメでした)が始まらず、映像としてダウンロードできませんでした。その結果、前回記事で紹介した自作の動体検知プログラムが使えず、編集動画をアップできなかったので要注意です。
なので、必要な分だけ自動で定期配信できるようするために以下のエンコーダ配信を使うことにしました。

エンコーダ配信

そこでエンコーダ配信を使ってみようと思ったのですが、ラズパイという低スペックPCだと曲様々な技術的課題がありました。

ちなみに、OBS配信ツールで手軽にエンコード配信ができるようですが、ラズパイ5といえど性能的に厳しいので、最軽量で配信できるように設計しようと思います。こちらの記事だと20フレームまでは実行できたということですが、30フレームは欲しいという思いもありました

今回使用している環境は以下になります。

  • Raspberry Pi 5 8GB
  • Logicool C270n

エンコード配信の仕組み

エンコード配信の実態は、カメラから上がってきたrawデータをffmpegを使って整形し、それをストリームに流すというものです。

具体的には以下のような処理をしています。

  1. データ取得: Webカメラから毎秒30フレームの画像データを取得
  2. エンコード処理: ffmpegがH.264コーデックを使用してrawデータを圧縮・変換
  3. ストリーミング配信: 圧縮されたデータをRTMPプロトコルでYouTubeサーバーに送信

ウェブカメラ配信と違って、プログラムで完全に制御できるのが大きなメリットです。

ウェブカメラ配信 vs エンコード配信の比較

実際に両方使ってみて分かった違いをまとめてみます:

ウェブカメラ配信はStudioを開いたままにする必要があり、処理負荷(cpu20%)程度が必要になりますが、エンコード配信はRTMP(Real Time Messaging Protocol)を使っていますので、画面は不要です。なのでとてもエコにできます。ラズパイだと待機状態の画面レスにできますので3W以下にすることができます。

項目ウェブカメラ配信エンコード配信
設定の簡単さ◎ ブラウザから直接設定△ ffmpeg設定が必要
処理負荷◎ 軽量△ CPU/GPU使用時間制御×
定期的な配信スケジュール× 困難◎ プログラムで自動制御可能
カスタマイズ性× 限定的◎ 解像度・品質を自由に設定
画面操作△ YouTube Studio必要◎ 完全ヘッドレス動作

軽量化への取り組み

ラズパイ5という限られたリソースで安定した配信を実現するため、FFmpegの設定を中心とした軽量化策を実施しました。

FFmpegエンコーダの最適化

ビデオエンコーダには広く使われているlibx264を採用し、その中でも最も処理負荷の低い**-preset ultrafast**を指定しています。ハードウェアエンコードも試してみましたが、カメラとの相性が悪いのかうまくいきませんでした。

スマホ視聴を意識した解像度設計

通常の1280×720から720×720の正方形に変更しました。この判断には以下の理由があります:

  • スマホでの視聴体験向上:縦画面・横画面どちらでも見やすい
  • データ量削減:約44%のデータ量削減を実現
  • 処理負荷軽減:FFmpegの動画処理負荷を軽減

TikTokなどスマホ特化アプリの普及を考えると、小画面でも気にならないユーザーが多く、最初から両方の画面向きに対応した方が実用的だと判断しました。

最終的な配信設定

以下のような設定にしています。詳しくは、ソースのffmpegオプション見ていただいたほうが良いかと思います。

  • 映像入力:1280×720 MJPEG形式
  • 出力解像度:720×720(正方形に切り抜き)
  • ビットレート:1200kbps(CBR固定ビットレート)
  • フレームレート:30fps
  • 音声:AAC 128kbps
  • エンコーダ:libx264(ultrafast preset)

最大の難敵:22分問題

問題の発生状況

配信開始から約22分で自動終了してしまう謎の現象に悩まされました。メモリやCPU使用率に問題はなく、ほぼ決まって22分のタイミングで配信が強制終了します。

鳥の巣のようにほとんど動きのない映像で特に発生しやすく、YouTubeのライブ配信画面側には以下のエラーメッセージが表示されます:

ストリームの現在のビットレート(716.05 Kbps)が推奨値より低くなっています。
ストリームのビットレートは 2500 Kbps を使用することをおすすめします。

22分あたり以下のメッセージが流れて終了してしまいました。これらのワーニングと終了間際のメッセージによってミスリードしてしまい、原因特定に時間がかかりました。

データなし – ストリーミング ソフトウェアで再起動しない限り、このストリームは間もなく終了します

試行錯誤の日々

原因特定のため、ログレベル調整、FFmpegオプション変更、ネットワーク監視など様々な対策を試しました。最も困ったのは22分待たないと結果が分からないことで、夜中に何度も再起動しては22分待つという非効率な検証を繰り返しました。

YouTube設定(DVR、遅延設定)やビットレート設定(可変→固定)を変更しても効果はありませんでした。

真の原因と解決策

最終的に判明した原因はstderrパイプバッファの詰まりでした。

問題の流れ:

  1. FFmpegのstderrバッファが満杯になりプロセスがハングアップ
  2. エンコード処理とYouTubeへのデータ送信が停止
  3. YouTube側が「データなし」と判断
  4. 一定時間後にストリームが強制終了

解決方法: プログラム終了後にまとめてstderrを読み込む方式から、1行ずつ継続的に読み込む方式に変更することで問題を解決しました。これにより、バッファの詰まりを防ぎ、長時間の安定配信が可能になりました。

元のソース。

    while self.should_monitor and self.process:
                if self.process.poll() is not None:
                    # プロセスが終了した後にstderrを読み込もうとする
                    # ...
                    try:
                        stdout, stderr = self.process.communicate(timeout=5) # ← ここでまとめて読み込み
                        if stderr:
                            self.logger.error(f"FFmpeg stderr: {stderr.decode('utf-8', errors='ignore')}")
                        # ...
                    except:
                        pass
                    # ...
                    break
                
                time.sleep(5) # プロセス実行中はstderrを積極的には読まない

改善後のソースです。プログラム終了後ではなく、標準エラーを都度を吐き出すようにしてます。

ちなみにffmpegオプションがVBR (可変ビットレート)だと「ストリームの現在のビットレート(716.05 Kbps)が推奨値より低くなっています。」とyoutube live配信側でワーニングがでますので、今回はCBR(固定ビットレート)を使うことでワーニングの回避しています。

        try:
            # ★変更点: iterとreadlineを使い、stderrを1行ずつ継続的に読み込む
            for line in iter(self.process.stderr.readline, ''):
                if self.stop_event.is_set(): # スレッド停止の仕組み (本質的ではないが関連)
                    break
                
                line = line.strip()
                if not line:
                    continue

                # 読み取った行 (line) をログに出力するなどの処理 (ここでは省略)
                # self.logger.debug(f"FFMPEG_RAW: {line}")
                # (FFMPEG_STATS のような統計情報のログ出力もここで行われる)
            # ...
        except Exception as e:
            # ... (エラー処理)

ソースコードの公開

今回作成したプログラムはGitHubで公開してます!簡単な手順も書いてますので、参考にしてください。

https://github.com/hiirofish/bird-watching-youtube-streamer

こちらを見てもらえれば、具体的なffmpegのコマンドやパラメータ設定、そして22分問題を解決する設定方法が分かります。Raspberry Pi 5とLogitech C270に最適化されたスクリプトで、スケジュール配信機能も付いています。

同じような野鳥観察をやりたい方は、このプログラムを使えば簡単に構築できると思います!以下に手順を書いておきます。

配信するための手順

youtube studioのストリームキー (youtube側の設定)

  1. youtube studioの右上にあるライブ配信マークをクリック

2. 左側にあるエンコード配信マークをクリックする。
タブにライブ配信の設定、ストリームキーがあるのでこれをコピーします。プログラム側で使用します。

YouTube Live配信の設定

DVR(巻き戻し機能)

YouTube Studioの配信設定で「DVRを有効にする」を選択しています。これにより視聴者はライブ配信中でも過去に遡って映像を見返すことができます。

遅延設定

「通常の遅延」「低遅延」「超低遅延」の選択肢から、通常の遅延を選択しました。私の観察プロジェクトではリアルタイム性が最重要ではなく、安定性を重視したためです。

ラズパイ側の設定

1. ソースコードの取得

bashgit clone git@github.com:hiirofish/bird-watching-youtube-streamer.git

2. 環境変数の設定 先ほど取得したストリームキーを環境変数に設定します:

bash# 一時的な設定
export YOUTUBE_STREAM_KEY='your_stream_key'

# 永続的な設定(推奨)
echo "export YOUTUBE_STREAM_KEY='your_stream_key'" >> ~/.bashrc
source ~/.bashrc

3. 配信開始

bashpython3 youtube_streamer.py

このような感じでリソースが表示されながら開始されます。デフォルトの時刻は5:00 – 20:00です。

Youtube Studioが以下のように変わっていれば配信されています。

実際の配信動画

配信映像はもう少し改良が進んでライブ配信予定ですが、ダイジェスト版はこちらでご覧いただけます。

ネクスト(今後の展開)

その日のダイジェスト版だと時刻を知りたいと思ったので、時刻を埋め込んだり、デフォルト画像を埋め込んだりできればと思っています。

動体検知プログラムを入れると処理がパンクするので、音声検知で何かアクションするというのをやってみようかと思ってます。鳥の鳴き声を検知して、重要なシーンを自動的にマーク付けできたら面白そうですよね。

野鳥観察の技術的な話でしたが、同じような定点観測をやりたい方の参考になれば嬉しいです。次回もお楽しみに!


https://take1bit.com/computer-ja/birds_system/#toc9

タイトルとURLをコピーしました