COLUMN

【テックメモシリーズ】Amazon Chime SDK C++を使ったライブラリ機能を試してみた

最近、インテリア照明に興味のある中村です

NTT東日本が行ってきた さまざまな開発テクニックを紹介する「テックメモシリーズ」、開発者が実際の業務のなかで書き溜めていった作業メモをそのままリアルにご紹介するシリーズとなります。

今回は2022年夏にAmazon Chime SDKに新しく追加された、C++クライアントライブラリについて、次の2つの検証記録をご紹介します。

1.軽量・小型のRaspBerry PIからビデオ会議へ参加可能かの調査記録

2.応用編としてAmazon Chimeのビデオ会議とデータメッセージを組み合わせ、ドローン車両を対象にした遠隔制御ついて動作検証した際の検証記録

Amazon Chime SDK C++ クライアントライブラリの調査

Amazon Chime は、AWSが展開する、グループのビデオ・音声・チャットによるコミュニケーションを支援する統合ツールです。 Amazon Chimeは、Saasアプリと、アプリの開発基盤を提供するAmazon Chime SDKがあります。 今回は、2022年夏にAmazon Chime SDKに追加されたC++ クライアントライブラリを紹介します。

https://aws.amazon.com/jp/chime/chime-sdk/

https://aws.amazon.com/jp/about-aws/whats-new/2022/08/amazon-chime-sdk-supports-signaling-client-c-plus-plus/

Amazon Chime SDKのおさらい

Amazon Chime SDKは

  • ビデオ・オーディオのコミュニケーション(ビデオ会議など。Amazon Chime Meeting)
  • メッセージのコミュニケーション(チャットなど。Amazon Chime Messageing)
  • 電話機との接続(Amazon Chime PSTN Audio)

が存在しますが、 今回紹介するAmazon Chime SDK C++ クライアントライブラリは、その中のAmazon Chime Meeting、ビデオ会議用のサービスとなります。

C++ クライアントライブラリの概要

Chime SDKのビデオ会議への参加は、対応しているAmazon Chime SDK クライアントライブラリが必要です。 これまでは、Android、iOS、Javascriptのクライアントライブラリが有りました。

今回、C++ クライアントライブラリの追加により、c++を実行できる環境下であれば、プラットフォームを問わずAmazon Chime のビデオ会議へ参加できるようになりました。

動作確認済の環境ですが、Amazon Linux2と、Ubuntu(x86_64)が確認ができているようです。 https://github.com/aws/amazon-chime-sdk-cpp/commit/d873d2dc00030e23613e7a910cc249f5fe8ce354

また、2023年2月3日には、WIndowsでも使用可能になったことがアナウンスされています。 https://aws.amazon.com/jp/about-aws/whats-new/2023/02/amazon-chime-sdk-windows-client-library/

C++ クライアントライブラリの調査

今回は、RaspberryPI(arm64)にc++クライアントライブラリを導入し、会議への参加を調査しました。

RaspBerryPIは4Bモデルを選びました。ベースのOSはUbuntu 18.04を使用します。

SDカードは64GBで動作しています。 基本的なWifiやSSHの設定は済んでいるものとします。

詳細は割愛しますが、 まずcmakeを導入し、chromiumベースのwebrtcをインストールしています。

sudo apt update
sudo apt install -y gcc autoconf automake libtool curl make g++ unzip &&\
sudo apt install -y binutils bison bzip2 cdbs curl dbus-x11 dpkg-dev elfutils &&\
sudo apt install -y devscripts fakeroot flex git-core gperf libasound2-dev libatspi2.0-dev libbrlapi-dev libbz2-dev libcairo2-dev libcap-dev libc6-dev libcups2-dev libcurl4-gnutls-dev libdrm-dev libelf-dev libevdev-dev libffi-dev libgbm-dev libglib2.0-dev libglu1-mesa-dev libgtk-3-dev libkrb5-dev libnspr4-dev libnss3-dev libpam0g-dev libpci-dev libpulse-dev libsctp-dev libspeechd-dev libsqlite3-dev libssl-dev libudev-dev libva-dev libwww-perl libxshmfence-dev libxslt1-dev libxss-dev libxt-dev libxtst-dev locales openbox p7zip patch perl pkg-config rpm ruby subversion uuid-dev wdiff x11-utils xcompmgr xz-utils zip &&\
sudo apt install -y libasound2 libatk1.0-0 libatspi2.0-0 libc6 libcairo2 libcap2 libcups2 libdrm2 libevdev2 libexpat1 libfontconfig1 libfreetype6 libgbm1 libglib2.0-0 libgtk-3-0 libpam0g libpango-1.0-0 libpci3 libpcre3 libpixman-1-0 libspeechd2 libstdc++6 libsqlite3-0 libuuid1 libwayland-egl1-mesa libx11-6 libx11-xcb1 libxau6 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxdmcp6 libxext6 libxfixes3 libxi6 libxinerama1 libxrandr2 libxrender1 libxtst6 zlib1g &&\
sudo apt install -y python-is-python3 clang-12 lld libc++-12-dev libc++abi-12-dev

cd ~ && wget https://github.com/Kitware/CMake/releases/download/v3.22.3/cmake-3.22.3.tar.gz &&\
tar -xzf cmake-3.22.3.tar.gz &&\
cd cmake-3.22.3 &&\
./bootstrap &&\
make &&\
sudo make install

cd ~ &&\
git clone https://github.com/ninja-build/ninja.git -b v1.8.2 &&\
cd ninja && ./configure.py --bootstrap &&\
alias ninja=~/ninja/ninja

cd ~ && git clone [https://gn.googlesource.com/gn](https://gn.googlesource.com/gn) &&\
cd gn &&\
export CC=clang-12 &&\
export CXX=clang++-12 &&\
python build/gen.py &&\
ninja -C out

cd ~ &&\
mkdir webrtc-build && cd webrtc-build &&\
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git &&\
export PATH=$HOME/webrtc-build/depot_tools:$PATH &&\
fetch --nohooks webrtc &&\
cd src &&\
git checkout -b _my__m99_branch refs/remotes/branch-heads/4844 &&\
cd .. &&\
gclient sync &&\
gclient sync -D &&\
cd src

// Change source
vim build/config/compiler/BUILD.gn
// Replace -ffile-compilation-dir with -fdebug-compilation-dir

rm -f buildtools/linux64/gn
cp ~/gn/out/gn buildtools/linux64/

alias ninja=~/ninja/ninja &&\
export CC=clang-12 &&\
export CXX=clang++-12 &&\
export AR=ar &&\
export NM=nm &&\
gn gen out/Default --args='is_debug=false rtc_include_tests=false target_os="linux" use_glib=true libcxx_abi_unstable=false rtc_use_h264=true rtc_enable_libevent=false libcxx_is_shared=false rtc_use_dummy_audio_file_devices=true rtc_include_pulse_audio=false ffmpeg_branding="Chrome" custom_toolchain="//build/toolchain/linux/unbundle:default" host_toolchain="//build/toolchain/linux/unbundle:default" clang_use_chrome_plugins=false treat_warnings_as_errors=false' &&\
ninja -C out/Default -v &&\
cd .. && mv src webrtc

cd ~ &&\
git clone https://github.com/aws/amazon-chime-sdk-cpp.git &&\
cd amazon-chime-sdk-cpp/chime-sdk-signaling-cpp

// Change clang to clang-12 or make clang-12 to clang by creating symlink
vim cmake/toolchains/LinuxClang.cmake

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_C_COMPILER  /usr/bin/clang-12)
set(CMAKE_CXX_COMPILER /usr/bin/clang++-12)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")

続いて、c++ クライアントライブラリをビルドします。 また、簡単な動作が確認できるデモアプリが付属しており、同じくビルドします。

export LIBWEBRTC_LIBRARY=$HOME/webrtc-build/webrtc/buildtools/linux64/out/Default/obj/libwebrtc.a &&\
export VANILLA_WEBRTC_SRC=$HOME/webrtc-build &&\
export BORING_SSL_LIBS=$VANILLA_WEBRTC_SRC/webrtc/build/linux/debian_sid_arm64-sysroot/usr/lib/aarch64-linux-gnu/ &&\
export BORING_SSL_INCLUDE_DIR=$VANILLA_WEBRTC_SRC/webrtc/third_party/boringssl/src/include &&\
export TOOLCHAIN_FILE=$HOME/amazon-chime-sdk-cpp/chime-sdk-signaling-cpp/cmake/toolchains/LinuxClang.cmake &&\
cd $HOME/amazon-chime-sdk-cpp/chime-sdk-signaling-cpp &&\
rm -rf build && cmake -S . -B build -DLWS_OPENSSL_LIBRARIES="${BORING_SSL_LIBS}/libssl.a;${BORING_SSL_LIBS}/libcrypto.a" -DLWS_OPENSSL_INCLUDE_DIRS=$BORING_SSL_INCLUDE_DIR  -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN_FILE}" &&\
cmake --build build &&\
cd demo/cli &&\
rm -rf build && cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE="../../cmake/toolchains/LinuxClang.cmake"  && cmake --build build

以上で準備は完了です。

RaspBerry PIから、会議に参加できるかどうか試してみます。 今回のテストも、Reactアプリケーションをベースに行います。

Reactアプリケーション上から、

1.会議を作成し、作成された会議情報を取得し、メモしておきます。

2.出席者を2人作成します。

a:一人目は、Reactアプリケーション上で、RaspBerryPIから送信された映像を見る出席者です。Reactアプリケーションから会議に参加します。

b:二人目は、RaspBerryPIから参加する出席者です。作成された出席者情報を取得し、メモしておきます。 「会議情報」と「出席者情報」の2つをRaspBerryPIに渡し、RaspBerryPIのデモアプリケーションから、参加させます。

それぞれの情報は、前回と同じようにReactアプリケーションから取得しましたが、直接aws-cliを実行して、同様の情報を受け取ることもできます。

デモアプリケーションを実行します。

cd build
./my_cli --attendee_id=b1258e0d-803b-b684-7106-xxxxxxxx --audio_host_url=xxxxxxxxxxx.k.m3.an1.app.chime.aws:3478 --external_meeting_id=test1 --external_user_id=xxxxxxxx --join_token=xxxxxxxx --log_level=error --meeting_id=9d723be0-1344-4df2-98f3-xxxxxxx --signaling_url=wss://signal.m3.an1.app.chime.aws/control/9d723be0-1344-4df2-98f3-xxxxxx

起動できました。 KeypressControllerでイベントが定義されており 「c」でローカルビデオON/OFF、「d」でデータメッセージ送信、「q」でプログラムが停止します。

// amazon-chime-sdk-cpp/blob/main/chime-sdk-signaling-cpp/demo/cli/controllers/keypress_controller.cc
void KeypressController::OnKeypress(char key) {
	std::cout << "Keypress received: " << std::string(1, key) << std::endl;
	if (!controller_) return;

	switch (key) {
		case 'q':  // Quit
			controller_->Stop();
			break;
		case 'c':  // Toggle capturer start/stop
			controller_->StartLocalVideo();
			break;
		case 'd':  // Send data message
			controller_->SendDataMessage("Hello from Signaling SDK demo!");
		default:
			break;
	}
}

「c」を押すと、緑にフラッシュする映像が、会議の出席者に送信されます。 FakeVideoSourceで、一秒おきに色の異なるwebrtc::I420Bufferを生成し、frame_bufferとして設定することで、カメラ無しでダミーの映像を送信しています。

// amazon-chime-sdk-cpp/chime-sdk-signaling-cpp/demo/shared/video/fake_video_source.cc
while (send_frames_) {
	frame_count %= (2 * fps);
	frame_count++;
	// wait time approximately corresponds to given fps
	std::this_thread::sleep_for(std::chrono::nanoseconds(kNsWaitInterval));
	rtc::scoped_refptrwebrtc::I420Buffer buffer = webrtc::I420Buffer::Create(width, height);
	if (frame_count > fps) {
		buffer->SetBlack(buffer);
	} else {
		buffer->InitializeData();
	}
	auto timestamp_us =
	std::chrono::duration_caststd::chrono::microseconds(std::chrono::system_clock::now().time_since_epoch())
	.count();
	webrtc::VideoFrame frame =
	webrtc::VideoFrame::Builder().set_video_frame_buffer(buffer).set_timestamp_us(timestamp_us).build();
	Broadcast(frame);
}

また、デモアプリケーション側で受信した映像は、raspberryPIのディスク内に映像をファイル保存しています。

// amazon-chime-sdk-cpp/chime-sdk-signaling-cpp/demo/shared/video/write_to_file_yuv_video_sink.cc
void WriteToFileYuvVideoSink::OnFrame(const webrtc::VideoFrame& frame) {
int width = frame.width();
int height = frame.height();

rtc::scoped_refptrwebrtc::I420BufferInterface buffer(frame.video_frame_buffer()->ToI420());
int stride_uv = buffer->StrideU();
int chroma_size = stride_uv * height / 2;
fwrite(buffer->DataY(), 1, buffer->StrideY() * height, file_);
fwrite(buffer->DataU(), 1, chroma_size, file_);
fwrite(buffer->DataV(), 1, chroma_size, file_);
fflush(file_);

「d」を押すと、データメッセージが送信されます。

https://aws.amazon.com/jp/about-aws/whats-new/2020/05/amazon-chime-sdk-data-messages-real-time-signaling/

自分自身を含む、会議の出席者全員に2KB程度のデータを送り、共有できる仕組みです。 Amazon Chimeは、内部でリアルタイムな通信を行うWebRTCの仕組みを持っていますが、この機能により、単純に映像や音声を送るだけでなく、チャットや制御コマンドを配ることが可能になります。

// amazon-chime-sdk-cpp/chime-sdk-signaling-cpp/demo/shared/observers/data_message_observer.cc
void DataMessageObserver::OnDataMessageReceived(const std::vector<DataMessageReceived>& messages) {
  for (const auto& message : messages) {
    RTC_LOG(LS_INFO) << "data message: " << message.data;
  }
}

c++クライアントライブラリの確認は以上となります。

遠隔制御のテスト

amazon chimeのビデオ会議と、データメッセージを組み合わせて、遠隔制御をテストしてみました。

ドローン等の無人車両には、車両本体に乗せた、追加のコンピュータから操縦を行う、コンパニオンPCと呼ばれる概念があります。

今回のテストにあたって、DeepRacerをドローンとして2台用意し、それぞれに、コンパニオンPCとして、C++ クライアントライブラリを搭載したRaspBerry PIを載せ、リモートで操縦を行ってみます。

https://aws.amazon.com/jp/deepracer/

本来のDeepRacerは、機械学習により、カメラ等の各種センサーを使用した自動運転の仕組みを提供するものですが 今回は機械学習やカメラの部分は使用せず、手動での運転を調査しています。

Reactアプリケーションをベースとしてビデオ会議を作成し、DeepRacer用の2名の出席者をそれぞれ作成し、RaspBerry PIから参加させます。

Reactアプリケーションに参加した出席者は、、それぞれのDeepRacerのリアルタイムな映像を見ることができます。 また、Reactアプリケーションに操縦可能なWebUIシステムを用意することで、一括、あるいは個別にDeepRacerの操縦や、車両情報の取得を行うことができます。

デモアプリケーションを改修していきます。 改修のポイントです。

  • FakeVideoSourceはダミーの映像を配信していましたが、RaspBerryPIに搭載したカメラから映像を入力するように改修します。 カメラは、V2カメラモジュールを使用しました。 今回はgstreamerを使用しましたが、opencvやffmpegでもカメラの取り込みは可能です。 gstreamerとアプリケーションのデータはパイプで受け渡しを行い、set_video_frame_bufferで書き込みを行います。
gst-launch-1.0 -eq v4l2src device="/dev/video0" ! 'video/x-raw, width=640, height=480, framerate=15/1, format=(string)I420' ! filesink location=$($PWD/.pipe) blocksize=307200
  • データメッセージ送受信処理を追加します。

Reactから、「前進」や「停止」を実行できるデータメッセージを送信し、RaspBerryPIで受け取る為の準備を行います。

送信側は、JSON等で階層化されたデータを文字列化して送信します。

データメッセージは全ての出席者が受信してしまうので、そのまま「前進」のコマンドを送ると、全てのDeepRacerが前進してしまいます。

そこで、階層化されたデータに「宛先情報」を含むことで 受信側は、宛先の処理対象が自分だった時に、処理を行うようにしています。

受信側は、データメッセージ受信時、DataMessageObserver::OnDataMessageReceivedがトリガーされるので message.dataを参照することで、データメッセージの内容を確認できます。

宛先情報確認のロジックを追加します。

// amazon-chime-sdk-cpp/chime-sdk-signaling-cpp/demo/shared/observers/data_message_observer.cc
void DataMessageObserver::OnDataMessageReceived(const std::vector<DataMessageReceived>& messages) {
	for (const auto& message : messages) {
		RTC_LOG(LS_INFO) << "data message: " << message.data;

		auto jobj = nlohmann_json::parse(message.data);
		if(jobj["targetAttendeeId"] != "" && controller_->attendeeId != jobj["targetAttendeeId"] )		   {
		   std::cout << "非ターゲットのため処理を停止"  << std::endl;
		}else{
			// exec command
		}
}
  • 処理対象が自分だった場合、コマンドを実行します。

DeepRacerは内蔵されたサーバを持ち、API経由でタイヤの向きや速度を制御でき、手動で操作できるようになっています。 c++からsystemコマンドでpythonを実行し、pythonからDeepRacerをAPIで操作します。 API操作部分は、aws_deepracer_control_v2のパッケージを使用しました。

def main(command):
    client = awsdeepracer_control.Client(password=PASSWORD, ip=IP)

    if command=="forward":
        client.set_manual_mode()
        client.start_car()
        client.move(0,-0.83,0.5)

    if command=="forward-left":
        client.set_manual_mode()
        client.start_car()
        client.move(-0.99,-0.83,0.5)

    if command=="forward-right":
        client.set_manual_mode()
        client.start_car()
        client.move(0.99,-0.83,0.5)

準備は以上です。それぞれのDeepracerを会議に参加させ、Reactアプリケーションで表示させます。

画面下部のコントローラで、対象の映像に対して、操作を行うデータメッセージを送る仕組みです。

Reactアプリケーションからコマンドを送信し、Amazon Chime 経由でDeepRacerが動くことを確認できました。

まとめ

C++ クライアントライブラリについて調査し、応用例としてDeepRacer動作のアプリケーションを作成しました。 Amazon chimeはビデオ通話のメリットがよく着目されますが、実態はwebrtcをベースとした、安定したリアルタイムな通信機能の基盤であり、人・モノへのコミュニケーションサービスとして提供されています。 今回、軽量・小型のRaspBerry PIから参加できることで、ネットワークカメラやデバイスの遠隔制御等、用途の可能性が更に広がったと感じました。

おわりに

今回は2022年夏にAmazon Chime SDKに新しく追加されたC++ クライアントライブラリについて軽量・⼩型のRaspBerry PIから会議に参加できるかの検証記録と、応用編してAmazon Chimeのビデオ会議とデータメッセージを組み合わせ、ドローン車両を対象とした遠隔操作の検証記録をご紹介しました。

「Amazon Chimeを使ってことがない」「Amazon Chime SDK C++クライアントライブラリをまだ試したことがない」といった方の参考になれば幸いです。

これからもNTT東日本が行ってきた さまざまな開発テクニックを紹介していきますのでご期待ください。

  • Amazon Web Services(AWS)および記載するすべてのAmazonのサービス名は、米国その他の諸国における、Amazon.com, Inc.またはその関連会社の商標です。

ページ上部へ戻る

相談無料!プロが中立的にアドバイスいたします

クラウド・AWS・Azureでお困りの方はお気軽にご相談ください。