【自由研究】セミの羽化をAmazon Kinesis Video Streamsへリアルタイムに配信してみた。
はじめに
皆様こんにちは、あかいけです。
最近は猛暑の日が続いておりますが、いかがお過ごしでしょうか。
夏といえば夏休み。
夏休みといえば自由研究。
自由研究といえばセミの羽化観察。
というわけで今回は自由研究として、
セミの羽化を Amazon Kinesis Video Streams へリアルタイムに配信してみました。
モチベーション
しかしわざわざ KVS で記録する必要はあるのでしょうか?
ただセミの羽化を観察するだけならその場で観察すればいいだけではないでしょうか。
自分の目で観察対象を直接目視して、得られた発見に胸を躍らせる、それが自由研究の醍醐味でしょう。
しかしいくつかの懸念によって私は、録画して、
なおかつ KVS へ配信する必要性に気づいてしまったのです。
懸念 1. セミの羽化を待たずして寝てしまう。
はい、これは想像しやすいかと思います。
最近の子供にとって夜更かしは当たり前かもしれませんが、正直当時の私はセミの羽化を見届ける前に確実に寝てしまっていました。
なのでおのずと動画として記録しておく選択肢がまず思いつきます。
スマホなりパソコンなり、または家の片隅に転がっているビデオカメラを引っ張り出してくるわけですね。
素晴らしい!これで問題は何もないでしょう。
しかしここで次の懸念が出てきます。
懸念 2. 羽化したセミのオシッコによって機材が破壊されてしまう。
これも想像しやすいでしょう。
そうです、セミといえばオシッコ。
羽化直後のセミのオシッコ率がどれぐらいかは知りませんが、
無慈悲なオシッコによってビデオカメラが破壊される様子が容易に想像できます。
そして貴重なセミの羽化シーンは忘却の彼方へと霧散していくのです…。
ならば録画データを遠隔地へリアルタイムでバックアップしておくのがベストと考えられるでしょう。
そして私は AWS に力を入れている会社に所属しています、もうこれは KVS を使わない理由はないでしょう。
構成図
今回の構成は以下の通りです。
シンプルにローカル端末で録画したデータをそのまま KVS へ配信しています。
また配信したデータは KVS に保存されているので、そのほかの保存先は用意していません。
なお GStreamer が二つある理由ですが、これは今回物理カメラを使いたいためです。
まず Docker 上の GStreamer は物理カメラを認識できず、ネットワークカメラを利用する必要があります。
ただ今回は Mac に USB 接続した Web カメラを利用したいので、
Mac 上で GStreamer を起動して、そのデータを Docker 上の GStreamer へ連携、
そして最終的に KVS へ配信しています。
事前準備
セミの幼虫
今回のゲストです。
彼 (または彼女) なくしてこのブログは成立しません。
今回はオフィス近くの日比谷公園で探してみました。
最初こそ「こんなコンクリートジャングルに果たしてセミがいるのだろうか…。」、と不安になりましたが、意外と見つけられました。
ただ開始 5 分ぐらいで発見できたので、かなりラッキーだったのかもしれません。
Web カメラ
セミの幼虫を録画するためのカメラです。
別に Mac 内蔵のカメラでもいいんですが、セミの幼虫をカーテンにデプロイする都合上、Web カメラの方が扱いやすいのでこちらを利用します。
Mac
KVS へ映像を配信するためのプロデューサーライブラリ (GStreamer) を動かします。
今回は Docker を使うので、事前に Docker のインストールを済ませておきます。
AWS 側 リソース作成
まずは AWS 上のリソースを作成します。
作成するリソースは以下だけです。
- 配信先の KVS ビデオストリーム
- プロデューサー用 IAM ユーザー/IAM ポリシー
あと Amazon Kinesis Video Streams 上のデータ保存期間はセミにちなんで 7 日にしてみました。
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Kinesis Video Stream
resource "aws_kinesis_video_stream" "mac_camera_stream" {
name = var.stream_name
data_retention_in_hours = var.data_retention_hours
media_type = "video/h264"
tags = {
Name = var.stream_name
Environment = var.environment
}
}
# IAM Role for Kinesis Video Streams
resource "aws_iam_role" "kinesis_video_role" {
name = "${var.stream_name}-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "kinesisvideo.amazonaws.com"
}
},
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
}
]
})
tags = {
Name = "${var.stream_name}-role"
Environment = var.environment
}
}
# IAM Policy for Kinesis Video Streams
resource "aws_iam_policy" "kinesis_video_policy" {
name = "${var.stream_name}-policy"
description = "Policy for Kinesis Video Streams access"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"kinesisvideo:CreateStream",
"kinesisvideo:DescribeStream",
"kinesisvideo:GetDataEndpoint",
"kinesisvideo:PutMedia",
"kinesisvideo:GetMedia",
"kinesisvideo:ListStreams",
"kinesisvideo:UpdateStream",
"kinesisvideo:GetStreamSession",
"kinesisvideo:StartStreamEncryption",
"kinesisvideo:StopStreamEncryption"
]
Resource = [
aws_kinesis_video_stream.mac_camera_stream.arn,
"${aws_kinesis_video_stream.mac_camera_stream.arn}/*",
"arn:aws:kinesisvideo:${var.aws_region}:${data.aws_caller_identity.current.account_id}:stream/*"
]
},
{
Effect = "Allow"
Action = [
"kinesisvideo:ListStreams"
]
Resource = "*"
}
]
})
}
# Attach Policy to Role
resource "aws_iam_role_policy_attachment" "kinesis_video_policy_attachment" {
role = aws_iam_role.kinesis_video_role.name
policy_arn = aws_iam_policy.kinesis_video_policy.arn
}
# IAM User for application access (optional)
resource "aws_iam_user" "kinesis_video_user" {
name = "${var.stream_name}-user"
tags = {
Name = "${var.stream_name}-user"
Environment = var.environment
}
}
# Attach Policy to User
resource "aws_iam_user_policy_attachment" "kinesis_video_user_policy_attachment" {
user = aws_iam_user.kinesis_video_user.name
policy_arn = aws_iam_policy.kinesis_video_policy.arn
}
# Access Key for the user (optional, for programmatic access)
resource "aws_iam_access_key" "kinesis_video_access_key" {
user = aws_iam_user.kinesis_video_user.name
}
# Data source for current AWS account
data "aws_caller_identity" "current" {}
variable "aws_region" {
description = "AWS region for resources"
type = string
default = "ap-northeast-1"
}
variable "stream_name" {
description = "Name of the Kinesis Video Stream"
type = string
default = "dockerStream"
}
variable "data_retention_hours" {
description = "Data retention period in hours"
type = number
default = 168 # 7 days (7 * 24 hours)
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
output "kinesis_video_stream_name" {
description = "Name of the Kinesis Video Stream"
value = aws_kinesis_video_stream.mac_camera_stream.name
}
output "kinesis_video_stream_arn" {
description = "ARN of the Kinesis Video Stream"
value = aws_kinesis_video_stream.mac_camera_stream.arn
}
output "kinesis_video_stream_creation_time" {
description = "Creation time of the Kinesis Video Stream"
value = aws_kinesis_video_stream.mac_camera_stream.creation_time
}
output "iam_role_arn" {
description = "ARN of the IAM role for Kinesis Video Streams"
value = aws_iam_role.kinesis_video_role.arn
}
output "iam_user_name" {
description = "Name of the IAM user for programmatic access"
value = aws_iam_user.kinesis_video_user.name
}
output "access_key_id" {
description = "Access Key ID for the IAM user"
value = aws_iam_access_key.kinesis_video_access_key.id
}
output "secret_access_key" {
description = "Secret Access Key for the IAM user"
value = aws_iam_access_key.kinesis_video_access_key.secret
sensitive = true
}
以下のコマンドでデプロイします。
terraform init;
terraform apply;
IAM ユーザーのクレデンシャル情報は後で使うので、以下のコマンドで確認しておきます。
terraform output --json
ローカル側 リソース作成
次はローカル側のリソース作成です。
ほぼほぼ以下の記事の手順を参考にしています、
めちゃめちゃ助かりました、ありがとうございます!
Docker
まずは AWS 公式が提供している ECR リポジトリにログインして、Docker Image を Pull します。
aws ecr get-login-password --region us-west-2 | docker login -u AWS --password-stdin https://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
次に以下のコマンドでコンテナを立ち上げてログインします。
sudo docker run -it --network="host" 546150905175.dkr.ecr.us-west-2.amazonaws.com/kinesis-video-producer-sdk-cpp-amazon-linux /bin/bash
ログインしたら以下のコマンドで環境変数を設定します。
PATH の方はそのまま実行で OK で、クレデンシャル情報は先ほど作成した IAM ユーザーの情報に置き換えて実行して下さい。
# PATH Config
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;
# Credential Config
export AWS_DEFAULT_REGION=ap-northeast-1;
export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX;
export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;
Docker 側の準備はこれで OK です。
ホスト
次にホスト側の準備です。
ホストで gstreamer を実行する必要があるため、インストールします。
brew install gstreamer
データ容量が大きいためかちょっと時間かかるので、気長にお待ちください。
ホスト側の準備はこれで完了です。
配信コマンド
まずはホスト上で以下のコマンドを実行します。
gst-launch-1.0 \
avfvideosrc device-index=1 ! \
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 sync=false
コマンドの補足
avfvideosrc device-index=1
: macOS の Web カメラから映像を取得(device-index=1 は外部 USB カメラを指定)videoconvert
: 映像フォーマットを変換video/x-raw,width=1280,height=720
: 解像度を 1280x720 に設定vtenc_h264_hw
: Apple の Hardware エンコーダーを使用して H.264 でエンコードallow-frame-reordering=FALSE
: フレームの並び替えを無効化(遅延削減)realtime=TRUE
: リアルタイム配信モードを有効化max-keyframe-interval=45
: キーフレーム間隔を 45 フレームに設定bitrate=512
: ビットレートを 512 kbps に設定
gdppay
: GDP(GStreamer Data Protocol)形式でパケット化tcpserversink
: TCP サーバーとして映像データを配信host=127.0.0.1
: ローカルホストで待機sync=false
: 同期を無効化(リアルタイム配信のため)
次に Docker 上で以下のコマンドを実行します。
gst-launch-1.0 -v \
tcpclientsrc host=host.docker.internal ! \
gdpdepay ! \
video/x-h264, format=avc,alignment=au ! \
h264parse ! \
kvssink stream-name=dockerStream
コマンドの補足
gst-launch-1.0 -v
: GStreamer パイプラインを詳細ログ付きで実行tcpclientsrc host=host.docker.internal
: ホスト側の TCP サーバーに接続gdpdepay
: GDP 形式のパケットを解析video/x-h264, format=avc,alignment=au
: H.264 ストリームの形式を指定format=avc
: AVC(Advanced Video Coding)形式alignment=au
: Access Unit 単位でアライメント
h264parse
: H.264 ストリームを解析kvssink stream-name=dockerStream
: Kinesis Video Streams へ配信
ここまででエラーが発生しなければ、Kinesis Video Streams のメディア再生から、映像が確認できるはずです。
あとはセミをカーテンにデプロイして、Web カメラをセミの幼虫に向けてセッティングして、羽化が始まりそうになるまで待ちましょう。
配信開始
さてセミの様子が大人しくなってきたら、いよいよ羽化の時間です。
先ほどのコマンドを実行して KVS 側で正常に映像が配信されることを確認したら、あとはセミを見ながら優雅なひと時をお過ごしください。
自由研究 結果
自由研究らしく、簡単に結果をまとめておきます。
正直セミは子供の頃に知り尽くしている気持ちでいましたが、
改めて観察してみるといろんな発見があって脳みそのリフレッシュになりました。
セミになるまでの流れ
21:37 - 羽化開始
23:00 - 羽化終了
01:00 - 体が3割ぐらい乾き
03:00 - 体が半乾き
06:45 - 体が乾燥完了してフェードアウト
07:00 - 朝起きた時、すごい神々しい感じになっていました
気づいたこと
- 意外と羽化のスピードが早い
- 羽化の最中、結構激しく動いていて落ちないか心配になる
- 羽化直後の薄緑色がとても美しい、実質妖精
- 朝方に羽化するイメージだったけど、普通に真夜中に羽化が完了した、これは個体差あるかも
- 羽化より、羽化後に体が固まるまでの方が時間がかかる
反省点
- 映像がガビガビすぎる。次はgst-launch-1.0コマンドのオプションで画質を調整してみる
- 映像だけだと時間がわかりづらいので、置き時計を設置した方がいい
- 観察結果が 1 匹だけであり相対的な比較ができないため、より多くのセミの幼虫を用意した方がいい
補足
羽化したセミは翌日元いた日比谷公園で解放しています。
そのためセミは無事です、ご安心ください。
さいごに
以上、セミの羽化をAmazon Kinesis Video Streamsへリアルタイムに配信してみました。
まさか人生でセミの羽化を AWS へ配信する日が来るとは思いませんでしたが、やってみると意外と楽しく、公式のDocker Imageも素直に動いてくれたため思いの外簡単にできました。
そして何より、普段は画面の向こう側にいるクラウドサービスが、リアルな自然現象と繋がった瞬間は、なんだか不思議で感動的でした。
セミも AWS も、どちらも夏の風物詩として記憶に残りそうです。
あと最近は小学生からプログラミングの授業があると聞きますし、自由研究の題材としても面白いんじゃないでしょうか。
「僕の夏休みの自由研究は AWS を使ったセミの観察です」なんて言ったら、先生も同級生もびっくりすること間違いなしです。