はじめに
こんにちは。HoloLensチームの野元です。
本記事は、Azure Communication Services(以降、ACSと称します)を使って、HoloLens 2とPC間でビデオ通話することを目的として実施した調査・検証結果をまとめたものです。
ACSの概要
ACSとは
音声・ビデオ・チャットなどのリアルタイム通信ができるAzureサービスのことです。https://docs.microsoft.com/ja-jp/azure/communication-services/overview
価格
ビデオ通話の利用には1ユーザーあたり約0.5円/分の費用が掛かります。(記事執筆時点)https://azure.microsoft.com/ja-jp/pricing/details/communication-services/
ACSのビデオ通話に使用するSDK
適宜、アプリケーションに次のSDKを導入します。
Azure.Communication.Identity
クライアントの認証、ユーザーIDの作成、アクセストークンの発行をする機能を提供します。
Azure.Communication.Calling
ビデオ通話に関する機能を提供します。
ACSのビデオ通話に必要なもの
1. ACSのAzureリソース(Communication Services)
ACSのサービスを利用する上で必要となるため、下記の手順を参考にしてCommunication Servicesのリソースを作成します。
2. ユーザーアクセストークン
ACSのCalling SDKを使ったビデオ通話をする上で、ユーザーアクセストークンが必須となります。
Identity SDKを使うことで、クライアントの認証→ユーザーIDの作成→アクセストークンの発行が可能となります。アクセストークンは、各クライアントごとに必要であり、有効期限は発行から1日間です。
下記にアクセストークンの発行方法を示します。
Azure Portalからアクセストークンを発行する方法
クイック スタート – テスト用の Azure Communication Services アクセス トークン をすばやく作成する
プログラムを書いてアクセストークンを発行する方法
3. 通信先のユーザーIDもしくはグループID
1対1のビデオ通話
通信先のユーザーIDが必要です。(ユーザーIDとはアクセストークンの発行の際に使用されるもののことです)
グループビデオ通話
グループIDが必要です。(グループIDはGuidでよしなに生成できます)
ACSのビデオ通話検証
検証環境
- Windows 10
- Visual Studio 2022
- Unity 2021.3.2f1
システム構成
次の3つのアプリケーション間でグループビデオ通話を行うことで、HoloLens 2とPC間でのビデオ通話が可能となります。
HoloLens 2で動かすアプリケーション
- Unityアプリケーション
- 3Dオブジェクト込みのビデオ通話を担当
- 2DUWPアプリケーション
- PCのビデオ映像のレンダリングを担当
PCで動かすアプリケーション
- 2DUWPアプリケーション
- HoloLens 2とのビデオ通話を担当
注意事項(記事執筆時点)
- Unityではビデオ映像のレンダリングができません。一方で、2DUWPアプリではビデオ映像のレンダリングができます。そのため、Unityと2DUWPのアプリケーションを併用することで、リモートクライアントの映像をHoloLens 2でも描画することが可能となります。
- ACSではスピーカーを制御する機能が提供されていません。そのため、1つのデバイスで2つのACSアプリを起動して同一のグループビデオ通話に入ると、自分が話した音声がスピーカーからも聞こえる現象が発生します。
- HoloLens 2で表示している3Dオブジェクト込みのビデオ通話を行うには、Unityを使用する必要があります。ただし、転送するビデオ映像には3Dオブジェクトの表示が強制されます。
- SDKはUnity Packageが用意されていないため、Nuget Packageから必要なものをよしなにUnityに取り込む必要があります。次にCalling SDKのUnityへの導入方法を示します。
UnityへのCalling SDKの導入方法
ダウンロードしたSDKの拡張子を.nupkg→.zipに変更後、下記のファイルを取り出しUnityにインポートします。下記に太字でUnityにインポートする際の階層例を示します。
- Assets\Plugins\azure.communication.calling.1.0.0-beta.31
azure.communication.calling.1.0.0-beta.31\lib\uap10.0
配下の.winmdをすべてインポートします。- ARM64
azure.communication.calling.1.0.0-beta.31\runtimes\win10-arm64\native
配下の.dllをすべてインポートし、各.dllのプラットフォームのCPU設定をARM64
にします。
- x64
azure.communication.calling.1.0.0-beta.31\runtimes\win10-x64\native
配下の.dllをすべてインポートし、各.dllのプラットフォームのCPU設定をX64
にします。
- x86
azure.communication.calling.1.0.0-beta.31\runtimes\win10-x86\native
配下の.dllをすべてインポートし、各.dllのプラットフォームのCPU設定をX86
にします。
Unityでのグループビデオ通話の実装例
下記のようなスクリプトを用意し、初期化処理(InitializeAsync()
)を実行した後に、グループビデオ通話の開始(GroupCallButton_ClickAsync()
)を行うことで、複数クライアント間でのグループビデオ通話が可能となります。もちろん、音声のみの通話も可能です。
#if WINDOWS_UWP using Azure.WinRT.Communication; using Azure.Communication.Calling; #endif using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Nextscape.AzureCommunicationServices { /// <summary> /// AzureCommunicationServicesのCalling SDKの機能を提供します。 /// </summary> public class ACSCalling { #if WINDOWS_UWP private CallMode _callMode = CallMode.AudioAndVideo; private CallClient _callClient; private CallAgent _callAgent; private Call _call; private DeviceManager _deviceManager; private LocalVideoStream[] _localVideoStream; private Dictionary<string, RemoteParticipant> _remoteParticipantDictionary; /// <summary> /// 初期化処理を実行する /// </summary> public async Task InitializeAsync(CallMode callMode, string accessToken, string userName) { _callMode = callMode; _callClient = new CallClient(); if (_callMode == CallMode.AudioAndVideo) { _deviceManager = await _callClient.GetDeviceManager(); _localVideoStream = new LocalVideoStream[1]; } var token_credential = new CommunicationTokenCredential(accessToken); var callAgentOptions = new CallAgentOptions() { DisplayName = userName }; _callAgent = await _callClient.CreateCallAgent(token_credential, callAgentOptions); if (_callMode == CallMode.AudioAndVideo) _callAgent.OnCallsUpdated += Agent_OnCallsUpdated; _callAgent.OnIncomingCall += Agent_OnIncomingCall; } /// <summary> /// 1対1のビデオ通話を開始する /// </summary> public async Task CallButton_ClickAsync(string destinationUserId) { if (_callMode == CallMode.AudioAndVideo) await GetCameraDeviceAsync(); var startCallOptions = new StartCallOptions(); if (_callMode == CallMode.AudioAndVideo) startCallOptions.VideoOptions = new VideoOptions(_localVideoStream); var callees = new ICommunicationIdentifier[1] { new CommunicationUserIdentifier(destinationUserId) }; _call = await _callAgent.StartCallAsync(callees, startCallOptions); } /// <summary> /// グループ通話を開始する /// </summary> public async Task GroupCallButton_ClickAsync(string groupId) { if (_callMode == CallMode.AudioAndVideo) await GetCameraDeviceAsync(); var groupCallLocator = new GroupCallLocator(Guid.Parse(groupId)); var joinCallOptions = new JoinCallOptions(); if (_callMode == CallMode.AudioAndVideo) joinCallOptions.VideoOptions = new VideoOptions(_localVideoStream); _call = await _callAgent.JoinAsync(groupCallLocator, joinCallOptions); } /// <summary> /// 通話を終了する /// </summary> public async Task HangupButton_ClickAsync() { var hangUpOptions = new HangUpOptions(); await _call.HangUpAsync(hangUpOptions); } /// <summary> /// 電話の着信を受け入れる /// </summary> private async void Agent_OnIncomingCall(object sender, IncomingCall incomingcall) { if (_callMode == CallMode.AudioAndVideo) await GetCameraDeviceAsync(); var acceptCallOptions = new AcceptCallOptions(); if (_callMode == CallMode.AudioAndVideo) acceptCallOptions.VideoOptions = new VideoOptions(_localVideoStream); _call = await incomingcall.AcceptAsync(acceptCallOptions); } /// <summary> /// リモート参加者とビデオストリーム /// </summary> private async void Agent_OnCallsUpdated(object sender, CallsUpdatedEventArgs args) { foreach (var call in args.AddedCalls) { foreach (var remoteParticipant in call.RemoteParticipants) { var remoteParticipantMRI = remoteParticipant.Identifier.ToString(); _remoteParticipantDictionary.Add(remoteParticipantMRI, remoteParticipant); await AddVideoStreamsAsync(remoteParticipant.VideoStreams); remoteParticipant.OnVideoStreamsUpdated += async (s, a) => await AddVideoStreamsAsync(a.AddedRemoteVideoStreams); } call.OnRemoteParticipantsUpdated += Call_OnRemoteParticipantsUpdated; call.OnStateChanged += Call_OnStateChanged; } } private async void Call_OnRemoteParticipantsUpdated(object sender, ParticipantsUpdatedEventArgs args) { foreach (var remoteParticipant in args.AddedParticipants) { var remoteParticipantMRI = remoteParticipant.Identifier.ToString(); _remoteParticipantDictionary.Add(remoteParticipantMRI, remoteParticipant); await AddVideoStreamsAsync(remoteParticipant.VideoStreams); remoteParticipant.OnVideoStreamsUpdated += async (s, a) => await AddVideoStreamsAsync(a.AddedRemoteVideoStreams); } } /// <summary> /// 通話状態の更新 /// </summary> private async void Call_OnStateChanged(object sender, PropertyChangedEventArgs args) { switch (((Call)sender).State) { // 通話終了 case CallState.Disconnected: break; default: break; } } /// <summary> /// リモートビデオをレンダリングする /// </summary> private async Task AddVideoStreamsAsync(IReadOnlyList<RemoteVideoStream> streams) { foreach (var remoteVideoStream in streams) { var remoteUri = await remoteVideoStream.Start(); } } /// <summary> /// 利用するカメラを取得する /// </summary> private async Task GetCameraDeviceAsync() { if (_deviceManager.Cameras.Count > 0) { var videoDeviceInfo = _deviceManager.Cameras[0]; _localVideoStream[0] = new LocalVideoStream(videoDeviceInfo); var localUri = await _localVideoStream[0].MediaUriAsync(); } } #endif } /// <summary> /// 通話モード /// </summary> public enum CallMode { // ビデオ通話 AudioAndVideo = 0, // 音声通話 Audio, } }
グループビデオ通話が動作している様子
HoloLens 2でACS.UnityアプリとACS.2DUWPアプリを、PCでACS.2DUWPアプリを動作させてグループビデオ通話をしている様子を以下に示します。
動画を見るとわかるように、HoloLens 2とPC間でグループビデオ通話を行い、HoloLens 2でもPC映像のレンダリングができています。
ただし、HoloLens 2で2つのACSを起動し同じグループで通話をしていることにより、自分の声がスピーカーから聞こえる状態となっています。現状、ACSにスピーカーをOFFにする機能はないため、これを解決する手段はありませんが、「3Dオブジェクト込みの映像転送」をしたい場合はACS.Unityアプリだけを使用し、「リモートビデオのレンダリング」をしたい場合はACS.2DUWPアプリだけを使用するといった方法で対応することが可能です。
まとめ
ACSを使用すると、HoloLens 2とPC間でビデオ通話を行うことができます。
ただし、Unityではビデオ映像のレンダリングに非対応となっているため、一工夫が必要となります。
また、本格的にサービスにACSのビデオ通話の機能を組み込んでいく際には、ユーザーID・アクセストークン・グループID等の管理をしっかりと行う必要があります。
まだ機能的に不完全ではあるものの、比較的簡単にビデオ通話を導入できるので、ユースケース次第ではサービスに組み込む選択肢も十分にあると思います。