🥐

Amazon Kinesis Video StreamsにPythonから動画を配信する (OpenCV+GStreamer)

2023/11/01に公開

Python + OpenCV + GStreamer で Amazon Kinesis Video Streams に動画配信します。

はじめに

Amazon Kinesis Video Streams(以下KVS)は、AWSのストリーミング動画分析・再生のためのサービスです。

KVSに動画を配信するには、C++かJavaのSDKを利用するか、GStreamerのプラグインを使用することになります。詳細は公式をご確認ください。
https://aws.amazon.com/jp/kinesis/video-streams/features/?nc=sn&loc=2

個人的にC++やJavaは触りたくなかったので、GStreamerを使って配信をしていました。その時に書いた記事がこちらになります。
https://zenn.dev/ncdc/articles/b4f95ff25bcfed

しかし、GStreamerでKVSに配信するとカメラ画像を直接的に配信されてしまうので、途中で画像に何らかの処理をすることが出来ません。単純な配信ではそれでもよいですが、アプリに組み込もうとすると画像処理もしたくなりました。
C++かJavaなら出来ると思いますが、あまり触りたくない。ということで困っていたのですが、、、

OpenCVのI/OにGStreamerを使用出来るらしいということを知りました。
https://qiita.com/TakahiroOta/items/a34b3d1db6475ddc31d7

ということで、 Python + OpenCV + GStreamer を使ったKVSへ配信を試してみました。

環境構築

今回の用途では、GStreamerのKVSプラグインのビルドとOpenCVのビルドが必要です。環境作るのがツラかったので、コンテナ化しました。
以下が作った、dockerfileです。

dockerfile
FROM python:3.11-bookworm AS base
# openCVがpython3.12に対応していないので、3.11を使う

# gstのビルドと実行の両方に共通するライブラリをインストール
RUN apt-get update && \
  apt-get install -y \
  libgstreamer-plugins-base1.0-dev


FROM base AS gst-build

WORKDIR /kinesis_video

# gstのビルドに必要なものをインストール
RUN apt-get update && \
  apt-get install -y \
  cmake m4 git build-essential \
  default-jdk

# gstのkvsプラグインのソースコードをclone
RUN git clone https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp.git

# ビルド
RUN mkdir ./amazon-kinesis-video-streams-producer-sdk-cpp/build
RUN cd ./amazon-kinesis-video-streams-producer-sdk-cpp/build && \
  cmake .. -DBUILD_GSTREAMER_PLUGIN=ON -DBUILD_JNI=TRUE && \
  make

FROM base AS gst-runtime
# gstreamerの実行環境

WORKDIR /kinesis_video

# ビルドしたものをコピー(buildとlibだけだとエラーになったので、全部持ってくる)
COPY --from=gst-build /kinesis_video/amazon-kinesis-video-streams-producer-sdk-cpp /kinesis_video/amazon-kinesis-video-streams-producer-sdk-cpp

# ライブラリのパスを通す
ENV LD_LIBRARY_PATH=/kinesis_video/amazon-kinesis-video-streams-producer-sdk-cpp/open-source/local/lib
ENV GST_PLUGIN_PATH=/kinesis_video/amazon-kinesis-video-streams-producer-sdk-cpp/build

# 必要なgstreamerのプラグインをインストール
RUN apt-get update && \
  apt-get install -y \
  gstreamer1.0-plugins-base-apps \
  gstreamer1.0-plugins-good \
  gstreamer1.0-plugins-ugly \
  gstreamer1.0-plugins-bad

# 何故かsslエラーになるので再インストールする
RUN apt-get reinstall -y ca-certificates && \
  update-ca-certificates


FROM gst-runtime AS opencv-build
# gstreamer込みでopencvをビルドする

WORKDIR /opencv

# opencvのビルドに必要なものをインストール
RUN apt-get update && apt-get install -y cmake 
RUN pip install numpy

# opencvのソースコードをclone
RUN git clone https://github.com/opencv/opencv.git && cd opencv && git checkout 4.8.1 && mkdir build && cd build

WORKDIR /opencv/opencv/build

# opencvをgstreamer込みでビルド
RUN cmake -D CMAKE_BUILD_TYPE=RELEASE \
  -D INSTALL_PYTHON_EXAMPLES=ON \
  -D INSTALL_C_EXAMPLES=OFF \
  -D PYTHON_EXECUTABLE=$(which python3) \
  -D BUILD_opencv_python2=OFF \
  -D CMAKE_INSTALL_PREFIX=$(python3 -c 'import sys; print(sys.prefix)') \
  -D PYTHON3_EXECUTABLE=$(which python3) \
  -D PYTHON3_INCLUDE_DIR=$(python3 -c 'from distutils.sysconfig import get_python_inc; print(get_python_inc())') \
  -D PYTHON3_PACKAGES_PATH=$(python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())') \
  -D WITH_GSTREAMER=ON \
  -D BUILD_EXAMPLES=ON ..
RUN make -j$(nproc)
RUN make install
RUN ldconfig

# ソースと中間ファイルを削除
WORKDIR /opencv
RUN rm -rf opencv

簡単な解説

それなりにコメントは書いたつもりなので読んでいただければ分かると思いますが、簡単に注意点を記載します。

  • 記事執筆時点でOpenCVがPython12に対応していないので、Python11の環境を用意しています。

    • これ、ハマりました。。。
  • Gstreamer本体はapt-getでインストール出来ますが、KVSプラグインはビルドする必要があります。

  • OpenCVからGstreamerを使用するには、Gstreamerがインストールされている環境でOpenCVをビルドする必要がありますので、Gstreamerインストール後にOpenCVをビルドしています。

実際にKVSに配信するコード

こんな感じで配信出来ました。
必要な設定値は環境変数から取るようにしています。

import cv2
import os
import time

# 環境変数から設定を取得
STREAM_NAME = os.getenv('STREAM_NAME', '')
RTSP_URL = os.getenv('RTSP_URL', '')

AWS_DEFAULT_REGION = os.getenv('AWS_DEFAULT_REGION', 'ap-northeast-1')
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID', '')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY', '')

STREAM_FPS = os.getenv('STREAM_FPS', 10)
WIDTH = os.getenv('WIDTH', 640)
HEIGHT = os.getenv('HEIGHT', 480)
STREAM_SIZE = (WIDTH, HEIGHT)


print("Start camera")

# 入力ストリーム
gst_cap_str : str = ' ! '.join([
    f'rtspsrc location={RTSP_URL} latency=300',
    'decodebin',
    'videoconvert',
    'video/x-raw,format=BGR',
    'appsink drop=1'
])
cap = cv2.VideoCapture(gst_cap_str, cv2.CAP_GSTREAMER)

# 出力ストリーム
gst_out_str : str = ' ! '.join([
    'appsrc',
    'videoconvert',
    f'x264enc bframes=1 key-int-max={STREAM_FPS} bitrate=1024',
    'video/x-h264,stream-format=avc,alignment=au',
    f'kvssink stream-name={STREAM_NAME} storage-size=128 access-key={AWS_ACCESS_KEY_ID} secret-key={AWS_SECRET_ACCESS_KEY} aws-region={AWS_DEFAULT_REGION}'
])

out = cv2.VideoWriter(
    gst_out_str,
    cv2.CAP_GSTREAMER, # 出力形式
    STREAM_FPS, # フレームレート
    STREAM_SIZE, # フレームサイズ
    True # isColor
)

while True:
    start = time.perf_counter()
    ret, frame = cap.read() # 画像を取得
    if ret:        
        resized_frame = cv2.resize(frame, STREAM_SIZE)
        out.write(resized_frame) # 画像を出力
    else:
        print("Can't receive frame (stream end?).")
    frame_time = time.perf_counter() - start
    # fpsにあわせて時間調整
    time.sleep(max(1.0/STREAM_FPS-frame_time, 0))

簡単な解説

  • 入力と出力のストリームの呪文は、Gstreamerを理解していない私のような人間にはツラかったです。インターネット上で答えが見つからなくて試行錯誤しました。

  • 入力はGStreamerを使わずに普通のOpenCVの機能でも可能ですが、Gstreamerを使ったほうが圧倒的に早かったので使いました。

  • 画像のサイズをVideoWriterで設定したものに合わせないと、配信された画像がグチャグチャになりました。なのでリサイズ処理をいれています。

  • FPSをVideoWriterで設定したものに合わせないと、グルったり遅延したりするので、sleepを入れて時間調整をしました。

    • FPSを上げすぎたり画像のサイズが大きすぎたりすると処理が追いつかないので要注意です。

まとめ

GStreamer+OpenCVを使うことで、PythonでAmazon Kinesis Video Streamsに動画を配信できました。
ただし、GStreamerのプラグインとOpenCVを自力でビルドする必要があるので、それなりに大変です。

参考にしました。

https://qiita.com/TakahiroOta/items/a34b3d1db6475ddc31d7

https://galaktyk.medium.com/how-to-build-opencv-with-gstreamer-b11668fa09c

https://dev.classmethod.jp/articles/kinesis-video-streams-python-put-media/

https://dev.classmethod.jp/articles/kinesis-video-streams-gstreamer-raspberrypi/

NCDCエンジニアブログ

Discussion