はじめに
(厳密に音量が取得できているかどうかわからないので、参考にした結果何が起きても責任はとれません。それっぽく動いたので今回記事にしています。)
前提
以前PhotonとDFVoiceを使ったボイスチャットを作成しました。
(参照:http://qiita.com/CST_negi/items/bf3bc0ede34bbcd2f7b2)
で、あること(※後述)がやりたくて自分のボイスの音量を取得したいと思っていたのですがなかなかうまくできず…という感じでハマってしまったのでメモしておきます。
以降の記述では先程参照したURLの話が前提となっていますので注意してください。
ボイスチャットを実現するにあたってネットワークを介してモデルを生成しているので、そこも注意してください。
システムを起動したら自分のモデルが生成されると同時にログインしている他の人のモデルも生成されるイメージです。
あること
=音量に合わせてアバター(MMD4Mecanimで変換したUnityで扱える形式に変換したもの)に口パクをさせたい。
実際DFVoiceを使わない場合ではこれは簡単でした。
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MMD4MecanimMorphHelper))]
public class MicTest : Photon.MonoBehaviour
{
private new AudioSource audio;
MouseHelper mouseHelper;
public int p = 5;
public float m = 0.1f;
private float volume = 0;
void Start()
{
audio = GetComponent<AudioSource>();
mouseHelper = GetComponent<MouseHelper>();
audio.clip = Microphone.Start(null, true, 999, 44100); // マイクからのAudio-InをAudioSourceに流す
audio.loop = true; // ループ再生にしておく
//audio.mute = true; // マイクからの入力音なので音を流す必要がない
while (!(Microphone.GetPosition("") > 0)) { } // マイクが取れるまで待つ。空文字でデフォルトのマイクを探してくれる
audio.Play(); // 再生する
}
void Update()
{
volume = GetAveragedVolume();
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.isWriting)
{
// We own this player: send the others our data
stream.SendNext(volume);
}
else
{
// Network player, receive data
GetComponent<MMD4MecanimMorphHelper>().morphWeight = (float)stream.ReceiveNext();
}
}
float GetAveragedVolume()
{
float[] data = new float[256];
float a = 0;
audio.GetOutputData(data, 0);
foreach (float s in data)
{
a += Mathf.Abs(s);
}
return a / 256.0f;
}
}
これをAudioSourceとPhotonViewとMMD3MecanimMorphHelperコンポーネントを追加したモデルにAddComponentすればOKでした。
ちなみにこれは何をしているかというと
マイクから音量を取得して音量に合わせて
・自分のシステム上で自分のモデルには何もせず、
・相手のシステム上の自分のモデルには口パクをさせる
ということをしています。
Start()ではマイクから音量を取得するための準備、そしてマイクから拾った音をAudioSourceで流しています
Update()では逐一音量をGetAveragedVolume()で計算しています。
onPhotonSerializeView()では、先ほどの太字のことを実現しています。
また、OnPhotonSerializeView()ではコメントの通り、
・stream.isWritingがTrueなら自分が起動しているシステム上の自分のモデル
・stream.isWritingがFalseなら他の人が起動しているシステム上での自分のモデル
というように処理を分けることができます。
なので今回はRPCを使っていません。
DFVoiceでは何が問題なのか
DFVoiceを用いて先ほどのようにやろうとしてもできません。
問題は2点あります。
①Microphone(マイク)からの入力を複数のクラスでとれない
②DFVoiceの仕様上、自分のシステム上では自分の声は再生されないため自分のシステム上の自分のモデルのAudioSourceの音量は常に0
①のため、他の適当なGameObjectにMicTest.csのようにDFVoiceとは別にマイクで音量を取得して音量を受け渡ししようとしてもできない。
②のため、AudioSourceから音量を取得しようとしても無理
解決策
でもちゃんとやり方はあります。
そもそもDFVoiceって自分のマイクの入力をエンコードして他の人に送信しているんだからマイク入力をどこかで処理しているはずです。
そう思って探したところ予想が当たりました。
VoiceControllerBase.csの162行目付近にあるOnMicroPhoneDataReady()ですね。ここで処理しているようです。
ここに
var vol = Mathf.Abs(newData.Items.Average() * 1000); GetComponent<MouseHelper>().volume = vol;
の2行を追加して、
using UnityEngine;
using System.Collections;
using System.Linq;
[RequireComponent(typeof(MMD4MecanimMorphHelper))]
public class MouseHelper : Photon.MonoBehaviour {
private MMD4MecanimMorphHelper morphHelper;
public float volume {get;set;}
// Use this for initialization
void Start () {
morphHelper = GetComponent<MMD4MecanimMorphHelper>();
}
public void MorphChange(float vol)
{
if (morphHelper != null)
{
morphHelper.morphWeight = vol;
}
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.isWriting)
{
//もし自分のシステム上で口パクさせたいならコメントアウト
//MorphChange(volume);
// We own this player: send the others our data
stream.SendNext(volume);
}
else
{
// Network player, receive data
MorphChange((float)stream.ReceiveNext());
}
}
で、できました。
問題点
最初に言ったとおり厳密に音量を取得できているのかわかりません。
というのも、
newData.Items.Average()
がおそらくボイスの音量を示しているわけではないからです。
推測ですがボイスの音声情報を扱っていてかつ音量情報も入ってるんだと思います。
一応実際に検証してみたところ、
何もマイク入力してない時→1.0 * e-05
喋った時→0.01~
というようにたまたま自分の想定通りに動いてたのでこれを利用させてもらっただけですね。
なので更に良いやり方がある人はコメントで教えて下さい。
毎回すみません。