- 投稿日:2020-06-26T20:08:35+09:00
Raw Audio Data を使って自分の音声を可視化してみる
概要
Agora VideoSDK には通話中にリアルタイムで映像・音声の生データを取得し、これらを編集して SDK に戻す機能が備わっています。
※ SDK のプラットフォームによっては一部機能制限があります。これを活用することで、外部ツールを使った音声解析や、ビデオ映像にスタンプや高度な美顔フィルタを適用するといったインタラクティブコンテンツを提供することができるようになります。
今回は Raw Audio Data を使い、自分の音声を波形データとしてグラフ化する実装を試してみます。実装にあたり VideoSDK for Unity に含まれるサンプルに追加する形で進めていきます。まだサンプルを動かした事がない場合、先に Agora.io VideoSDK for Unity クイックスタートガイドをご覧いただく事をお勧めします。
実装
1.AudioRawDataObserver を登録し、コールバックを設定する
今回は自身の音声データを使うので、複数あるコールバックのうち SetOnRecordAudioFrameCallback を設定します。TestHelloUnityVideo.cspublic void setUpRawAudioData() { mAudioManager = AudioRawDataManager.GetInstance(mRtcEngine); // Registers the audio observer. mAudioManager.RegisterAudioRawDataObserver(); // Listens for the OnRecordAudioFrameHandler delegate. mAudioManager.SetOnRecordAudioFrameCallback(OnRecordAudioFrameHandler); }RegisterAudioRawDataObserver は JoinChannel 前にコールしておく必要があります。
TestHelloUnityVideo.cspublic void join(string channel) { ... setUpRawAudioData(); // join channel mRtcEngine.JoinChannel(channel, null, 0); ... }2.コールバックを実装する
コールバックを設定すると、定期的に AudioFrame が引数で渡されてきます。AudioFrame はサンプリングされた音声データを含むオブジェクトで、これを一時的にためておくキューをクラス内に用意しておきます。今回、キューに格納されたデータは描画のタイミングで 1 個ずつ順番に取り出す実装にしますが、万が一描画のタイミングが遅いなどしてデータが増えすぎてしまわないよう、キューに格納できる上限を設けておきます。TestHelloUnityVideo.cspublic void OnRecordAudioFrameHandler(AudioFrame audioFrame) { if(audioFrameQueue.Count <= 3) { audioFrameQueue.Enqueue(audioFrame); } }3.オーディオデータ取得のインターフェースを実装する
今回、音声波形の描画は TestHome.cs に任せるため、AudioFrame オブジェクトを返すメソッドを用意します。TestHelloUnityVideo.cspublic AudioFrame getAudioFrame() { return (audioFrameQueue.Count > 0) ? (AudioFrame)audioFrameQueue.Dequeue() : new AudioFrame(); }4.描画ロジックを実装する
LineRenderer を使って音声波形を描画します。先ずは、線の色・太さを設定します。TestHome.csvoid Start() { ... LineRenderer lineRenderer = gameObject.AddComponent<LineRenderer>(); float lineWidth = 0.1f; lineRenderer.startWidth = lineWidth; lineRenderer.endWidth = lineWidth; Color lineColor = new Color(0.9f, 0.9f, 0.5f); lineRenderer.startColor = lineColor; lineRenderer.endColor = lineColor; lineRenderer.material = new Material(Shader.Find("Sprites/Default")); }次に Update() メソッドに描画ロジックを追加します。AudioFrame オブジェクトを扱うので、次の一行をファイルに追加しておきます:
TestHome.csusing agora_gaming_rtc;AudioFrame に含まれるサンプル数と同じサイズの Vector3 配列 (頂点リスト)を用意し、各頂点の座標を計算していきます。サンプル一つ一つは 16 bit PCM 方式で格納されているため、復号してから Y 座標を計算していきます。
TestHome.csvoid Update() { ... if(app != null) { AudioFrame audioFrame = app.getAudioFrame(); LineRenderer lineRenderer = GetComponent<LineRenderer>(); lineRenderer.positionCount = audioFrame.samples; Vector3[] points = new Vector3[lineRenderer.positionCount]; float xStretch = 8.0f; float yAmp = 2.0f; float yOffset = 4.0f; for(int i = 0, idx = 0; i < lineRenderer.positionCount; i++, idx+=2) { // AudioFrame type is PCM 16bit little endian. short audioLevel = (short) ( (audioFrame.buffer[idx+1] << 8) | audioFrame.buffer[idx] ); points[i] = new Vector3( xStretch * (2.0f * i / (lineRenderer.positionCount - 1.0f) - 1.0f), yAmp * (audioLevel / 32768f) + yOffset, 0.0f ); } lineRenderer.SetPositions(points); } }実行結果
最後に
- 投稿日:2020-06-26T14:08:16+09:00
Virtual Chara開発 - その3
店舗接客などに使える仮想のキャラクターが扱えるアプリ開発をしました。そちらの説明はこちらになります。
https://qiita.com/NestVisual/items/fe4dd168178e27156682環境:
Windows 10
Unity 2018.4.1
RealSense D435iNuitrackのデバッグについて:
テスト中に視覚的なデバッグ情報を得ることは最初から大切でした。Nuitrackのドキュメントページに、骨格データを有用な方法で可視化するUnityのチュートリアルがあるのは良かったです:
https://download.3divi.com/Nuitrack/doc/UnityRGBandSkeletons_page.html
SimpleSkeletonAvatar.cs クラスを継承し、いくつかの調整を加えることで、骨格表現を常に ビデオ画像に合うようにスケールしたり移動させたりすることができました。こうすることで、利用者動画や骨格の可視化を縮小して右上に移動させて、キャラクターの邪魔にならないようにができました。 また、デバッグデータが表示されるのを切り替える機能をキーにマッピングしました。フェイストラッキングを実装する際には、視覚的なデバッグ用の顔データを表示する機能も必要になりました。残念ながらチュートリアルはありませんが、方法は似ていました。トラッキングされた顔の資料を取る方法はここに記載しています:
https://download.3divi.com/Nuitrack/doc/Instance_based_API.htmlNuitrack.GetInstancesJson()というメソッドがあるので、最新アップデートからすべての顔トラッキング資料を含むjson文字列を返します。この資料を「FaceInfo」というNuitrack SDKクラスに変換することができます。このクラスから得られる最も興味深い変数は 「landmark」 で、31の顔のポイントを含むVector2配列です。これらの正規化された座標から、利用者の顔に対応する目印を動画像上にインスタンス化するための投影座標を計算することができます。
sample1.csprivate void ProcessFaceLandmark() { nuitrack.Skeleton skeleton = CurrentUserTracker.CurrentSkeleton; _faceInfo = NuitrackHelper.GetFaceInfo(); if (CanProcessFaceLandmark(skeleton)) { for (int i = 0; i < _landmarks.Count; i++) { Vector2 landmarkNormalizedPos = _faceInfo.Instances[0].face.landmark[i]; _landmarks[i].SetActive(true); _landmarks[i].transform.position = GetLandmarkRenderPosition(_image, landmarkNormalizedPos); } } else { for (int i = 0; i < _landmarks.Count; i++) _landmarks[i].SetActive(false); } } private Vector2 GetLandmarkRenderPosition(RawImage image, Vector2 landmarkPosition) { float width = _image.rectTransform.rect.width; float height = _image.rectTransform.rect.height; Vector2 imagePosition = _image.gameObject.transform.position; float jointX = (imagePosition.x - (width / 2.0f)) + (landmarkPosition.x * width); float jointY = (imagePosition.y + (height / 2.0f)) - (landmarkPosition.y * height); return new Vector2(jointX, jointY); }次回はNuitrackの関節の回転制限に関して投稿します。それではまた