Kinesis Video Streams Edge AgentをRaspberry PI上のDocker(Ubuntu)へデプロイして、ローカルのIPカメラの映像を送信してみました

2023.07.12

1 はじめに

CX 事業本部 delivery部の平内(SIN)です。

先月、Kinesis Video Streams Edge Agent(以下、Edge Agent)がGAされたとアナウンスがありました。
Amazon Kinesis Video Streams エッジエージェントの一般提供を開始

Edge Agentは、Javaのプログラムとして提供されており、そのままデプロイして使用することもできますが、Greengrassのカスタムコンポーネントとしても利用可能です。 ドキュメントには、「Greengrass」と「それ以外」へのデプロイ方法として案内されています。


https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/edge.html

今回は、Raspberry PI上のDockerでUbuntu(18.04)を使用し、Greengrassを使用しない要領 (Run Amazon Kinesis Video Streams Edge Agent in non-AWS IoT Greengrass mode)を試してみました。

やってみた構成は、以下のようなイメージです。

Ubuntu(18.04)を使用した理由は、Edge Agentの対応OSが以下のようになっており、RasPI(ARM)で使用するためです。
https://docs.aws.amazon.com/ja_jp/kinesisvideostreams/latest/dg/edge-faq.html

2 IPカメラ

IPカメラは、TP-Link Tapo C200 を使用しました。


https://www.amazon.co.jp/gp/product/B07YG7RNF2/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1

このカメラは、RTSPでの配信が可能になっています。
参考:Tapoを使用したRTSPライブストリーミングの利用方法

アカウントを設定することで、下記のようなURIでアクセス可能になります。

rtsp://ユーザー名:パスワード@IPアドレス:554/stream1

ユーザー名及び、パスワードを設定して、VLCプレーヤーでrtspによる接続を確認している様子です。

3 構築

Edge Agentの構築の手順は、以下の通りです。


(1) ダウンロード
(2) リソースの作成(CDK)
(3) モノの作成
(4) Dockerイメージの作成
(5) エージェントの設定

(1) ダウンロード

Edge Agentを使用するには、フォームに記入して、ダウンロードのURLをリクエストする必要があります。

ダウンロードした、ファイルは、12Mbyte程度の、1.1.0.tar.gzでした。1.1.0は、バージョンを表現しているため変化するものと思われます。

% ls -la *.tar.gz
-rw-r--r--@ 1 sin  staff  11888640  6 30 10:58 1.1.0.tar.gz

(2) リソース作成(CDK)

AWS側で必要なリソースを、CDKで作成しました。

  • Kinesis Video Streams kvs-adge-agent-stream
  • SecretManager kvs-adge-agent-secret
  • Role kvs-adge-agent-role
  • RoleAlias kvs-adge-agent-role-alias
  • Policy (IoT Coore) kvs-adge-agent-policy

ここでは、Kinesis Video Stremasのストリーム及び、IPカメラのURI情報をSecret Managerで作成し、これらにアクセスするためのロールを作成しています。 ロールは、IoT Codeから証明書で利用(Authorizing Direct Calls)が可能なようにロールエリアスが設定されています。

※「mediaURI」は、IPカメラのURIにあわせて編集が必要です

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

import {
  aws_iam,
  aws_secretsmanager,
  aws_iot,
  aws_kinesisvideo,
} from "aws-cdk-lib";

export class KvsEdgeAgentStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const tag = "kvs-edge-agent";
    const mediaURI = "rtsp://user:password@192.168.1.48:554/stream1"; // 【編集必要】
    const streamName = `${tag}-stream`;

    // Kinesis Video Streams
    const kvsStream = new aws_kinesisvideo.CfnStream(this, "KvsStream", {
      dataRetentionInHours: 2,
      name: streamName,
    });

    // Secrets Manager
    const secretString = new aws_secretsmanager.Secret(this, "Secret", {
      secretName: `${tag}-secret`,
      secretObjectValue: {
        MediaURI: cdk.SecretValue.unsafePlainText(mediaURI),
      },
    });

    // Role
    const role = new aws_iam.Role(this, "Role", {
      roleName: `${tag}-role`,
      assumedBy: new aws_iam.ServicePrincipal("credentials.iot.amazonaws.com"),
    });
    role.addToPolicy(
      new aws_iam.PolicyStatement({
        resources: ["*"],
        actions: [
          "cloudwatch:PutMetricData",
          "kinesisvideo:ListStreams",
          "iot:Connect",
          "iot:Publish",
          "iot:Subscribe",
          "iot:Receive",
        ],
      })
    );
    role.addToPolicy(
      new aws_iam.PolicyStatement({
        resources: [`arn:aws:kinesisvideo:*:*:stream/${streamName}/*`],
        actions: [
          "kinesisvideo:DescribeStream",
          "kinesisvideo:PutMedia",
          "kinesisvideo:TagStream",
          "kinesisvideo:GetDataEndpoint",
        ],
      })
    );
    role.addToPolicy(
      new aws_iam.PolicyStatement({
        resources: [secretString.secretArn],
        actions: ["secretsmanager:GetSecretValue"],
      })
    );

    // RoleAlias
    const roleAlias = new aws_iot.CfnRoleAlias(this, "MyCfnRoleAlias", {
      roleArn: role.roleArn,
      roleAlias: `${tag}-role-alias`,
    });
    // IoT Policy
    const cfnPolicy = new aws_iot.CfnPolicy(this, "IotPolicy", {
      policyDocument: {
        Version: "2012-10-17",
        Statement: [
          {
            Effect: "Allow",
            Action: [
              "iot:Connect",
              "iot:Publish",
              "iot:Subscribe",
              "iot:Receive",
            ],
            Resource: ["*"],
          },
          {
            Effect: "Allow",
            Action: ["iot:AssumeRoleWithCertificate"],
            Resource: roleAlias.attrRoleAliasArn,
          },
        ],
      },
      policyName: `${tag}-policy`,
    });
  }
}

CDKをデプロイすると、Outputsに StreamARNMediaUriSecretArn が出力されます。これらは、「(5)エージェント設定」で使用されます。

  • StreamARN (Kinesis Video Staream に作成したストリームのARN)
  • MediaUriSecretArn (SecretManagerに作成したシークレットのARN)
% cdk deploy

・・・略・・・

Outputs:
KvsEdgeAgentStack.MediaUriSecretArn = arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:kvs-edge-agent-secret-UUWmql
KvsEdgeAgentStack.StreamARN = arn:aws:kinesisvideo:ap-northeast-1:xxxxxxxxxxxx:stream/kvs-edge-agent-stream/1689011376458

・・・略・・・

以下、参考までに、CDKで作成されたリソースです。

  • Kinesis Video Streamsに作成されたストリーム

  • Secret Managerに作成されたシークレット

  • Kinesis Video Streams及び、Secret Managerにアクセスするためのロール

* ロールにアクセスするためのロールエリアス

  • モノで使用するポリシー

(3) モノ作成

モノの作成は、証明書等の扱いがあるため、手動で作成しました。(Thing名は、kvs-adge-agent としています)

証明書に紐づけるポリシーは、「(2) リソースの作成(CDK)」で作成されたポリシーです。

作成されたモノのARNは、「(5)エージェントの設定」で使用します。また、ダウンロードした証明書は、「(4) Dockerイメージの作成」で使用します。

(4) Dockerイメージの作成

使用したのは、RaspberryPi 4B(4G) で、OSは、今年5月の最新版(2023-05-03) Raspberry Pi OS (64-bit) A port Debian Bullseye with the Respbeyy Pi Desktop (Compatible with Raspberry Pi 3/4/400)です。

$ cat /proc/cpuinfo  | grep Revision
Revision	: c03112

$ uname -a
Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023 aarch64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 11 (bullseye)
Release:	11
Codename:	bullseye

また、下記を参考にDockerをインストールしました。
Raspberry Pi 4 に Docker と Docker Compose をインストールする

$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh
$ sudo usermod -aG docker pi

$ docker -v
Docker version 24.0.4, build 3713ee1

Dockerイメージを作成するために、以下のようにファイルを配置しています。

1.1.0.tar.gzは、「(1)ダウンロード」で取得したファイルで、同ディレクトリで解凍しています。 certsは、「(3)モノ作成」でダウンロードした証明書等です。

$ tree .
.
├── 1.1.0
│   ├── al2
│   └── ubuntu
│       ├── 18.04
│       │   └── arm
│       │       ├── kvs-edge-agent.tar.gz
│       │       └── sha256.sum
│       └── 22.04
├── 1.1.0.tar.gz
├── certs
│   ├── AmazonRootCA1.pem
│   ├── certificate.crt
│   └── private.key
└── Dockerfile

Dockerfile

FROM ubuntu:18.04
ENV EDGE_AGENT_VERSION 1.1.0
ENV CERT_DIR /certs
ENV AWS_REGION ap-northeast-1
ENV AWS_IOT_CA_CERT $CERT_DIR/AmazonRootCA1.pem
ENV AWS_IOT_CORE_CERT $CERT_DIR/certificate.crt
ENV AWS_IOT_CORE_PRIVATE_KEY $CERT_DIR/private.key
ENV AWS_IOT_CORE_CREDENTIAL_ENDPOINT xxxxxxxxxxxx.credentials.iot.ap-northeast-1.amazonaws.com
ENV AWS_IOT_CORE_DATA_ATS_ENDPOINT xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com
ENV AWS_IOT_CORE_ROLE_ALIAS kvs-edge-agent-role-alias
ENV AWS_IOT_CORE_THING_NAME kvs-edge-agent
ENV GST_PLUGIN_PATH /kvs-edge-agent/KvsEdgeComponent/artifacts/aws.kinesisvideo.KvsEdgeComponent/$EDGE_AGENT_VERSION/
ENV LD_LIBRARY_PATH /kvs-edge-agent/KvsEdgeComponent/artifacts/aws.kinesisvideo.KvsEdgeComponent/$EDGE_AGENT_VERSION/lib/

COPY certs $CERT_DIR

RUN apt-get update
RUN apt-get install -y gcc libssl-dev libcurl4-openssl-dev liblog4cplus-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-good gstreamer1.0-tools

RUN apt-get install -y wget gnupg software-properties-common
RUN wget -O- https://apt.corretto.aws/corretto.key | apt-key add -
RUN add-apt-repository 'deb https://apt.corretto.aws stable main'
RUN apt-get update; apt-get install -y java-11-amazon-corretto-jdk
#RUN apt-get install unzip;unzip java-11-amazon-corretto-jdk maven

# GLIBCXX_3.4.29 is required
RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test
RUN apt-get install -y g++-11

COPY 1.1.0/ubuntu/18.04/arm/kvs-edge-agent.tar.gz .
RUN tar -xvf kvs-edge-agent.tar.gz
RUN apt-get install -y maven
RUN cd kvs-edge-agent;mvn clean package; \
mv ./target/libs.jar ./KvsEdgeComponent/artifacts/aws.kinesisvideo.KvsEdgeComponent/$EDGE_AGENT_VERSION

CMD cd /kvs-edge-agent/KvsEdgeComponent/artifacts/aws.kinesisvideo.KvsEdgeComponent/1.1.0; \
java -Djava.library.path=/kvs-edge-agent/KvsEdgeComponent/artifacts/aws.kinesisvideo.KvsEdgeComponent/1.1.0 \
  --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
  -Dio.netty.tryReflectionSetAccessible=true \
  -cp kvs-edge-agent.jar:libs.jar \
  com.amazonaws.kinesisvideo.edge.controller.ControllerApp


※ libstdc++.so.6: version `GLIBCXX_3.4.29' not found となるので、 g++-11をinstallしています
※ AWS_IOT_CORE_CREDENTIAL_ENDPOINT は、IoT Coreの認証情報エンドポイントを設定しています
 例: credential-account-specific-prefix.credentials.iot.aws-region.amazonaws.com
※ AWS_IOT_CORE_DATA_ATS_ENDPOINT には、IoT Coreのデータプレーンエンドポイントを設定しています
 例: data-account-specific-prefix.iot.aws-region.amazon

ビルドして実行することで、Edge Agentとして動作を開始します。

% docker build -t kvs-adge-agent:latest .
% docker run kvs-adge-agent:latest

(5) エージェントの設定

録画の時間帯、送信の時間帯、データの保持期間などは、JSONファイルで定義して、start-edge-configuration-updateで設定します。

% aws kinesisvideo start-edge-configuration-update --cli-input-json "file://example-edge-configuration.json"

jsonファイルでは、下記について、編集が必要です。

  • StreamARN (Kinesis Video Staream に作成したストリームのARN) 「(2) リソースの作成(CDK)」
  • MediaUriSecretArn (SecretManagerに作成したシークレットのARN)「(2) リソースの作成(CDK)」
  • HubDeviceArn (Iot Coreで作成したモノのARN)「(3) モノの作成」

また、ScheduleExpression に設定されている Cron式のような指定は、先頭が「秒」になっっていることに注意が必要です。例では、動作確認がすぐにできるように、5分毎に録画が開始するようになっています。

example-edge-configuration.json

{
    "StreamARN": "arn:aws:kinesisvideo:ap-northeast-1:xxxxxxxxxxxx:stream/kvs-edge-agent-stream/1689011376458",
    "EdgeConfig": {
        "HubDeviceArn": "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:thing/kvs-edge-agent",
        "RecorderConfig": {
            "MediaSourceConfig": {
                "MediaUriSecretArn": "arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:kvs-edge-agent-secret-UUWmql",
                "MediaUriType": "RTSP_URI"
            },
            "ScheduleConfig": {
                "ScheduleExpression": "0 /5 * ? * * *",
                "DurationInSeconds": 299
            }
        },
        "UploaderConfig": {
            "ScheduleConfig": {
                "ScheduleExpression": "0 /5 * ? * * *",
                "DurationInSeconds": 299
            }
        },
        "DeletionConfig": {
            "EdgeRetentionInHours": 2,
            "LocalSizeConfig": {
                "MaxLocalMediaSizeInMB": 2800,
                "StrategyOnFullSize": "DELETE_OLDEST_MEDIA"
            },
            "DeleteAfterUpload": true
        }
    }
}

Kinesis Video Streamsのコンソールで、「メディア再生」を使用して、録画されたものを確認しています。(右上は、VNCによるRTSPのモニタです)

4 最後に

今回は、Kinesis Video Streams Edge AgentRaspberryPIにデプロイして、ローカル環境にあるIPカメラの映像を Kinesis Video Streamsに送信してみました。

Edge Agentは、RTSPサーバから取得したデータを、特にエンコード・デコードすることなく、そのまま送信しているので、RaspberryPIのような非力な端末でも特に問題なく利用できそうです。

また、IPカメラやRaspberryPIに、特に実装は必要なく、これまでの、Producer SDKを利用した形態よりも更に簡単に扱えるのも魅力だと思います。