Amazon Kinesis Video Streams を使ってみた

Amazon Kinesis Video Streams は、AWS の動画配信サービスです。

Kinesis Video Streams に配信した映像をリアルタイムまたはオンデマンドで多数のユーザーに配信することができます。動画の保存期間は最長でなんと 10 年。

料金

参考) 料金

東京リージョンの場合は以下のような感じです。1GB は 2Mbps の動画なら 1 時間ぶんといった感じです。

|項目|料金| |:--|:--|:--| |配信 (Produce)|0.01097 USD/GB| |閲覧 (Consume)|0.01536 USD/GB| |ストレージ|0.025 USD/GB•月|

このほかにインターネットへのデータ転送料金(0.084~0.114 USD/GB)がかかります。インターネットからの受信、及び同一リージョン内のデータ転送料金はかかりません。データの保存期間は 0 から設定可能なので、ライブ配信だけならストレージ料金は不要になります。

ユーザーの作成

必要な権限だけを持ったテストユーザーとして、配信用と閲覧用を作成し、それぞれのアクセスキーを発行します。アクセスキーはこのあと使うのでメモっておきます。

配信用 (kinesis-video-producer-test)

Kinesis Video Streams のストリームへ配信(Produce)する権限を持つユーザーです。

$ aws iam create-user --user-name kinesis-video-producer-test
$ aws iam attach-user-policy --user-name kinesis-video-producer-test \
    --policy-arn arn:aws:iam::aws:policy/AmazonKinesisVideoStreamsFullAccess
$ aws iam create-access-key --user-name kinesis-video-producer-test

閲覧用 (kinesis-video-consumer-test)

Kinesis Video Streams のストリームを閲覧(Consume)する権限を持つユーザーです。

$ aws iam create-user --user-name kinesis-video-consumer-test
$ aws iam attach-user-policy --user-name kinesis-video-consumer-test \
    --policy-arn arn:aws:iam::aws:policy/AmazonKinesisVideoStreamsReadOnlyAccess
$ aws iam create-access-key --user-name kinesis-video-consumer-test

ストリームの作成

Kinesis Video Streams に、ストリームを作成します。必要なパラメーターはストリーム名と、保存期間のみです。

$ aws kinesisvideo create-stream --stream-name test --data-retention-in-hours 24
{
    "StreamARN": "arn:aws:kinesisvideo:ap-northeast-1:(AccountNumber):stream/test/XXXXXXXXXXXXX"
}

動画配信 (Producer)

Kinesis Video Streams Producer Library という C++/Java の SDK を使って開発するか、GStreamer で Kinesis Video Streams プラグイン を使って配信することができます。ここでは、GStreamer を使った方法をいくつか試してみました。

GStreamer on Docker で静的な動画ファイルを配信する

参考) 開発者ガイド - はじめに - Kinesis ビデオストリームにデータを送信する

SDK がインストールされたコンテナイメージが用意されているので、それに入っている GStreamer を使ってまずは動画ファイルを配信してみます。

$ aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 546150905175.dkr.ecr.us-west-2.amazonaws.com
$ docker pull 546150905175.dkr.ecr.us-west-2.amazonaws.com/kinesis-video-producer-sdk-cpp-amazon-linux:latest
$ docker run -it -v $(pwd):/work --network="host" \
    546150905175.dkr.ecr.us-west-2.amazonaws.com/kinesis-video-producer-sdk-cpp-amazon-linux \
    /bin/bash

動画ファイルをループさせて配信するため、以下のようなスクリプトを書きます。動画ファイルは NHK クリエイティブライブラリー等から適当にダウンロードしました。

// play.sh
#!/bin/sh

export LD_LIBRARY_PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/lib:$LD_LIBRARY_PATH
export PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/bin:$PATH
export GST_PLUGIN_PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/lib:$GST_PLUGIN_PATH

# kinesis-video-producer-test のアクセスキー
AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
AWS_REGION=ap-northeast-1
STREAM_NAME=test
MOVIE_FILE=/work/sample.mp4

while [ 1 ]
do
    gst-launch-1.0 \
        filesrc location="${MOVIE_FILE}" \
        ! qtdemux name=demux \
        ! queue \
        ! h264parse \
        ! video/x-h264,format=avc,alignment=au \
        ! kvssink stream-name="${STREAM_NAME}" storage-size=512 \
            access-key="${AWS_ACCESS_KEY_ID}" \
            secret-key="${AWS_SECRET_ACCESS_KEY}" \
            aws-region="${AWS_REGION}"
done

gst-launch-1.0 は「!」でつないでパイプラインを構成し、順番にプラグインを処理していきます。動画の形式などによってうまく変換してあげる必要があります。正直よく分かってはいない。

  • filesrc : ファイルから読み込む
  • queue : データをバッファに一旦貯める
  • h264parse : H.264 ストリームを解析
  • kvssink : Kinesis Video Streams に送信

正しく配信されているかどうかは、マネジメントコンソールから動画を再生することで確認できます。

  • Kinesis Video Streams > Vidoe streams > test > Media playback

GStreamer on Raspberry Pi でカメラモジュールの動画を配信する

参考) Kinesis Video Streams Producer SDK GStreamer Plugin

※初代 Raspberry Pi Model B 512MB / Raspbian Buster で確認しました。

公式ドキュメントに書いてあるとおりにインストールします。min-install-script の直前の行は自分の環境では足りなかったので追加でインストールしました(実行すると入れとけって出る)。

$ sudo apt-get update
$ sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base-apps
$ sudo apt-get install -y gstreamer1.0-plugins-bad gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools
$ sudo apt-get install -y gstreamer1.0-omx
$ mkdir /opt/awssdk && cd /opt/awssdk
$ git clone https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp
$ cd amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build
$ sudo apt-get install -y pkg-config libssl-dev cmake libcurl4-openssl-dev liblog4cplus-1.1-9 liblog4cplus-dev
$ ./min-install-script
$ ./gstreamer-plugin-install-script

Raspberry Pi のカメラモジュールを動画で配信します。

export LD_LIBRARY_PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/lib:$LD_LIBRARY_PATH
export PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/bin:$PATH
export GST_PLUGIN_PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/lib:$GST_PLUGIN_PATH

# kinesis-video-producer-test のアクセスキー
AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
AWS_REGION=ap-northeast-1
STREAM_NAME=test

BPS=2048000
FPS=5
gst-launch-1.0 v4l2src device=/dev/video0 \
    ! videoconvert \
    ! video/x-raw,format=I420,width=640,height=480,framerate=${FPS}/1 \
    ! omxh264enc control-rate=2 target-bitrate=${BPS} periodicity-idr=45 inline-header=FALSE \
    ! h264parse \
    ! video/x-h264,stream-format=avc,alignment=au,profile=baseline \
    ! kvssink stream-name="${STREAM_NAME}" \
        access-key="${AWS_ACCESS_KEY_ID}" \
        secret-key="${AWS_SECRET_ACCESS_KEY}" \
        aws-region="${AWS_REGION}"
  • videoconvert : フォーマット変換
  • omxh264enc : H.264 ハードウェアエンコーダ
  • h264parse : H.264 ストリームを解析
  • kvssink : Kinesis Video Streams に送信

GStreamer on Raspberry Pi でネットワークカメラの動画を配信する

RTSP(MPEG4-TS) で取得できるネットワークカメラから配信する場合はこんな感じ。

RTSP_URL=rtsp://admin:password@192.168.13.200:554/ipcam.sdp
BPS=2048000
gst-launch-1.0 -v \
    rtspsrc location="${RTSP_URL}" protocols=tcp \
    ! rtpmp4vdepay \
    ! v4l2mpeg4dec \
    ! videoconvert \
    ! video/x-raw,format=I420,width=640,height=480 \
    ! omxh264enc control-rate=2 target-bitrate=${BPS} periodicity-idr=15 inline-header=FALSE \
    ! h264parse \
    ! video/x-h264,stream-format=avc,alignment=au,profile=baseline \
    ! kvssink stream-name="${STREAM_NAME}" \
        access-key="${AWS_ACCESS_KEY_ID}" \
        secret-key="${AWS_SECRET_ACCESS_KEY}" \
        aws-region="${AWS_REGION}"
  • rtpmp4vdepay : RTP パケットからペイロードを取得
  • v4l2mpeg4dec : MPEG4 デコード
  • videoconvert : フォーマット変換
  • omxh264enc : H.264 ハードウェアエンコーダ
  • h264parse : H.264 ストリームを解析
  • kvssink : Kinesis Video Streams に送信

GStreamer on Mac で内蔵カメラの動画を配信する

GStreamer/Docker による動画ファイルの配信と同様に Docker 環境を使いますが、Docker 環境内からは直接、内蔵カメラを呼べないようで小細工が必要です。 具体的には、「ホストで内蔵カメラをストリーム -> Docker 内ストリーム -> Kiniesis Video Streams」 という流れにします。

Mac 環境

# 初回のみ: gstreamer をインストール
brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad
brew install gst-plugins-ugly

# ローカルで、内蔵カメラのストリームサーバを立てる
gst-launch-1.0 autovideosrc ! videoconvert \
    ! video/x-raw,width=1280,height=720 \
    ! vtenc_h264_hw allow-frame-reordering=FALSE realtime=TRUE max-keyframe-interval=45 bitrate=512 \
    ! gdppay ! tcpserversink host=127.0.0.1

Docker 環境

$ aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 546150905175.dkr.ecr.us-west-2.amazonaws.com
$ docker pull 546150905175.dkr.ecr.us-west-2.amazonaws.com/kinesis-video-producer-sdk-cpp-amazon-linux:latest
$ docker run -it -v $(pwd):/work --network="host" \
    546150905175.dkr.ecr.us-west-2.amazonaws.com/kinesis-video-producer-sdk-cpp-amazon-linux \
    /bin/bash
export LD_LIBRARY_PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/lib:$LD_LIBRARY_PATH
export PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/bin:$PATH
export GST_PLUGIN_PATH=/opt/awssdk/amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/lib:$GST_PLUGIN_PATH

# kinesis-video-producer-test のアクセスキー
export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export AWS_DEFAULT_REGION=ap-northeast-1
STREAM_NAME=test

# ホストで立てたストリームを読んで、Kinesis Video Streams に流す
gst-launch-1.0 -v tcpclientsrc host=host.docker.internal \
    ! gdpdepay ! video/x-h264, format=avc,alignment=au \
    ! h264parse ! kvssink stream-name=${STREAM_NAME}

参考) [Kinesis Video Streams] DockerイメージのプロデューサーライブラリでMacのカメラを使用してみました | Developers.IO

動画閲覧 (Consumer)

参考) ビデオストリーム再生

Kinesis Video Streams Parser Library という C++/Java の SDK を使って開発するか、HLS または MPEG-DASH プレイヤーを使って簡単に再生することができます。HLS (HTTP Live Streaming) は Apple が開発したもの、MPEG-DASH (Dynamic Adaptive Streaming over HTTP) は後発の国際規格です。

HLS で閲覧する

ブラウザ上では Video.js または Google Shaka Player といったライブラリを使って再生できます。hls.js もたぶん大丈夫。Safari, Edge はブラウザ自体がネイティブ対応しているので URL 直接入力でも再生できるようです。

再生用のセッション URL は以下の手順で取得します。セッション URL は有効期限を持ったセッショントークンが埋め込まれた一時的なものになります。

uml diagram

  1. GetDataEndpoint API で、ストリーム用の API エンドポイントを取得する。
  2. 取得した API エンドポイントに対して GetHLSStreamingSessionURL API を呼び、セッション URL を取得する。 例) https://b-87178fb5.kinesisvideo.ap-northeast-1.amazonaws.com/hls/v1/getHLSMasterPlaylist.m3u8?SessionToken=XXXXXXX

CLI でセッション URL を取得する例。取得したセッション URL をプレイヤーに指定すれば再生できます。

ENDPOINT_URL=$(aws kinesisvideo get-data-endpoint \
    --stream-name ${STREAM_NAME} \
    --api-name GET_HLS_STREAMING_SESSION_URL \
    | jq -r '.DataEndpoint')
aws kinesis-video-archived-media get-hls-streaming-session-url \
    --endpoint ${ENDPOINT_URL} \
    --stream-name ${STREAM_NAME}

JavaScript でセッション URL を取得し、Video.js に再生させる場合の例。ここではとりあえず HTML にアクセスキーを埋め込んでいますが、ユーザーに丸見えになるので、通常はバックエンド側で URL を発行します。GetHLSStreamingSessionURL のオプションで、再生モード、再生する動画の日時、動画のフォーマット(MPEG-TS or Fragmented MP4)や有効期限などを指定します。

<!DOCTYPE html>
<html>

<head>
    <link href="https://vjs.zencdn.net/7.7.5/video-js.css" rel="stylesheet" />
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.642.0.min.js"></script>
    <title>HLS Player</title>
</head>

<body>
    <video id="videojs" class="player video-js vjs-default-skin" controls autoplay></video>

    <script src="https://vjs.zencdn.net/7.7.5/video.min.js"></script>
    <script>
        async function getURL() {
            // kinesis-video-consumer-test のアクセスキー
            const accessKeyId = 'XXXXXXXXXXXXXXXXXXXX';
            const secretAccessKey = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
            const region = 'ap-northeast-1';
            const streamName = 'test';

            const options = {
                accessKeyId: accessKeyId,
                secretAccessKey: secretAccessKey,
                region: region,
            }
            const kinesisVideoClient = new AWS.KinesisVideo(options);
            const kinesisVideoArchivedMediaClient = new AWS.KinesisVideoArchivedMedia(options);

            // GetDataEndpoint
            // https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_GetDataEndpoint.html 
            const e = await kinesisVideoClient.getDataEndpoint({
                APIName: 'GET_HLS_STREAMING_SESSION_URL',
                StreamName: streamName
            }).promise();
            kinesisVideoArchivedMediaClient.endpoint = new AWS.Endpoint(e.DataEndpoint);

            // GetHLSStreamingSessionURL API
            // https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_reader_GetHLSStreamingSessionURL.html
            const d = await kinesisVideoArchivedMediaClient.getHLSStreamingSessionURL({
                //ContainerFormat: 'FRAGMENTED_MP4', // or 'MPEG_TS'
                //DiscontinuityMode: 'ALWAYS', // or 'NEVER' or 'ON_DISCONTINUITY'
                //DisplayFragmentTimestamp: 'NEVER', // or 'ALWAYS'
                //Expires: 300,
                //PlaybackMode: 'LIVE', // or 'LIVE_REPLAY' or 'ON_DEMAND'
                StreamName: streamName
            }).promise();
            return d.HLSStreamingSessionURL;
        }

        document.addEventListener('DOMContentLoaded', async () => {
            const url = await getURL();
            const player = videojs('videojs');
            player.src({
                src: url,
                type: 'application/x-mpegURL'
            });
        });
    </script>
</body>

</html>

指定した日時から追っかけ再生する場合。開始日時を指定する。

    const d = await kinesisVideoArchivedMediaClient.getHLSStreamingSessionURL({
        HLSFragmentSelector: {
            FragmentSelectorType: 'SERVER_TIMESTAMP',
            TimestampRange: {
                StartTimestamp: 1588912753 // 再生開始タイムスタンプ
            }
        },
        PlaybackMode: 'LIVE_REPLAY',
        StreamName: streamName
    }).promise();

過去の動画を再生する場合の例。プレイヤー側で動画内の任意の日時にシークできる。

    const d = await kinesisVideoArchivedMediaClient.getHLSStreamingSessionURL({
        PlaybackMode: 'ON_DEMAND',
        HLSFragmentSelector: {
            FragmentSelectorType: 'SERVER_TIMESTAMP',
            TimestampRange: {
                StartTimestamp: 1588912753, // 再生開始タイムスタンプ
                EndTimestamp: 1588912813, // 再生終了タイムスタンプ
            }
        },
        StreamName: streamName
    }).promise();

MPEG-DASH で閲覧する

Dash.js または Google Shaka Player といったライブラリで再生できます。Video.js 7 (includes videojs-http-streaming) で再生できました。

再生用のセッション URL は HLS と同様の方法で取得できます。取得した URL は有効期限を持ったセッショントークンが埋め込まれた一時的なものになります。MPEG-DASH の場合、動画のフォーマットは Fragmented MP4 固定です。

uml diagram

  1. GetDataEndpoint API で、ストリーム用の API エンドポイントを取得する。
  2. 取得した API エンドポイントに対して GetDASHStreamingSessionURL API を呼び、セッション URL を取得する。

CLI でセッション URL を取得する例。取得したセッション URL をプレイヤーに指定すれば再生できます。

ENDPOINT_URL=$(aws kinesisvideo get-data-endpoint \
    --stream-name ${STREAM_NAME} \
    --api-name GET_DASH_STREAMING_SESSION_URL \
    | jq -r '.DataEndpoint')
aws kinesis-video-archived-media get-dash-streaming-session-url \
    --endpoint ${ENDPOINT_URL} \
    --stream-name ${STREAM_NAME}

JavaScript でセッション URL を取得し、Video.js に再生させる場合の例。ここではとりあえず HTML にアクセスキーを埋め込んでいますが、ユーザーに丸見えになるので、通常はバックエンド側で URL を発行します。GetDASHStreamingSessionURL のオプションで、再生モード、再生する動画の日時やセッションの有効期限などを指定します。

<!DOCTYPE html>
<html>

<head>
    <link href="https://vjs.zencdn.net/7.7.5/video-js.css" rel="stylesheet" />
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.642.0.min.js"></script>
    <title>MPEG-DASH Player</title>
</head>

<body>
    <video id="videojs" class="player video-js vjs-default-skin" controls autoplay></video>

    <script src="https://vjs.zencdn.net/7.7.5/video.min.js"></script>
    <script>
        async function getURL() {
            // kinesis-video-consumer-test のアクセスキー
            const accessKeyId = 'XXXXXXXXXXXXXXXXXXXX';
            const secretAccessKey = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
            const region = 'ap-northeast-1';
            const streamName = 'test';

            const options = {
                accessKeyId: accessKeyId,
                secretAccessKey: secretAccessKey,
                region: region,
            }
            const kinesisVideoClient = new AWS.KinesisVideo(options);
            const kinesisVideoArchivedMediaClient = new AWS.KinesisVideoArchivedMedia(options);

            // GetDataEndpoint
            // https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_GetDataEndpoint.html 
            const e = await kinesisVideoClient.getDataEndpoint({
                APIName: 'GET_DASH_STREAMING_SESSION_URL',
                StreamName: streamName
            }).promise();
            kinesisVideoArchivedMediaClient.endpoint = new AWS.Endpoint(e.DataEndpoint);

            // GetDASHStreamingSessionURL API
            // https://docs.aws.amazon.com/ja_jp/kinesisvideostreams/latest/dg/API_reader_GetDASHStreamingSessionURL.html
            const d = await kinesisVideoArchivedMediaClient.getDASHStreamingSessionURL({
                //DisplayFragmentTimestamp: 'NEVER', // or 'ALWAYS'
                //Expires: 300,
                //PlaybackMode: 'LIVE', // or 'LIVE_REPLAY' or 'ON_DEMAND'
                StreamName: streamName
            }).promise();
            return d.DASHStreamingSessionURL;
        }

        document.addEventListener('DOMContentLoaded', async () => {
            const url = await getURL();
            const player = videojs('videojs');
            player.src({
                src: url,
                type: 'application/dash+xml'
            });
        });
    </script>
</body>

</html>

指定した日時から追っかけ再生する場合。開始日時を指定する。

    const d = await kinesisVideoArchivedMediaClient.getDASHStreamingSessionURL({
        DASHFragmentSelector: {
            FragmentSelectorType: 'SERVER_TIMESTAMP',
            TimestampRange: {
                StartTimestamp: 1588912753 // 再生開始タイムスタンプ
            }
        },
        PlaybackMode: 'LIVE_REPLAY',
        StreamName: streamName
    }).promise();

過去の動画を再生する場合の例。プレイヤー側で動画内の任意の日時にシークできる。

    const d = await kinesisVideoArchivedMediaClient.getDASHStreamingSessionURL({
        DASHFragmentSelector: {
            FragmentSelectorType: 'SERVER_TIMESTAMP',
            TimestampRange: {
                StartTimestamp: 1588912753, // 再生開始タイムスタンプ
                EndTimestamp: 1588912813, // 再生終了タイムスタンプ
            }
        },
        PlaybackMode: 'ON_DEMAND',
        StreamName: streamName
    }).promise();