ADX2LEでマイク入力

※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

目次


ADX2LEでマイク入力レベルを得るテスト

問題と動機

Unityの標準オーディオでマイク入力を行おうとすると、iOSでの実行時にエラーになる。
(Mac,PC,Androidは問題ないらしい(PC,Android未確認))

おそらく、Unity標準オーディオのデバイス処理とADX2LEのデバイス処理が何かしら問題を起こしていると推測される。
→ここらへんを追求する方法もあるかもしれないが、
ADX2LEの初期化を遅らせる方法など不明
Unity標準オーディオでのiOSのオーディオセッションの変更タイミングや、制御が不明
などの理由から断念


ADX2LEforUnity自体にはマイク入力の処理は実装されていない
(ADX2製品版ネイティブプラグイン版にはある様子。また、製品版だが各種ゲームの録画、配信システムなどのミドルウェア対応もある)
→マイクの入力はできるはずだが、ADX2LEforUnityに無いだけ?(またはUnityの問題?)

そこで、
ここでは、
iOSのマイクレベルを直接取り出すようなネイティブプラグインを実装するとどうなるか試してみた。
結論としては、Micの入力レベルを取り出すことはできたので、
 マイク入力を使ったゲームなどを作ることはできそう。

実際の動作

iOSでMic入力を有効にすると、iPhoneなどでは、スピーカの出力先が変わる(ハウリングなどを軽減するためかと思われる)
ので、実機の出力音量が極端に小さくなったようになる。
(イヤホンの出力は変化しない様子)

やり方

ここに紹介するソースファイルをUnityEditor上で作成する。
MicInputTest.mmファイルは、AssetsフォルダのPlugins/iOSに保存する。
(これで、iOSビルド時に一緒にコンパイルできるようになる。)

MicTest.csはAssetsのしたの好きなところへ保存する。
MicTestはUnityのコンポーネントで、なんらかのゲームオブジェクトにくっつけて使用する。
同じゲームオブジェクトに3Dテキストのコンポーネントもくっつけておく。

画面をタップするとオーディオのモードが切り替わり、Micの入力レベルを
3Dテキストで表示し続けます。

MicInputTest.mm

// ADX2LEでマイク入力レベルを得るテスト
// Plugins/iOSに保存して使う


// 参考サイト:http://blog.koogawa.com/entry/2013/11/24/121807

extern "C" {
	void StartUpdatingVolume_();
	float GetPeakPower_();
	void StopUpdatingVolume_();
}

#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>

@interface MicInputTest :NSObject
@end

@implementation MicInputTest

AudioQueueRef   _queue;     // 音声入力用のキュー
NSTimer         *_timer;    // 監視タイマー

static void AudioInputCallback(
							   void* inUserData,
							   AudioQueueRef inAQ,
							   AudioQueueBufferRef inBuffer,
							   const AudioTimeStamp *inStartTime,
							   UInt32 inNumberPacketDescriptions,
							   const AudioStreamPacketDescription *inPacketDescs)
{
	// 録音はしないので未実装
}

/*
 - (void)preStartUnity
 {
 // 音を拾う
 [self startUpdatingVolume];
 }*/
/*
 - (void)didReceiveMemoryWarning
 {
 [super didReceiveMemoryWarning];
 // Dispose of any resources that can be recreated.
 }
 */

bool initFlag = false;

- (void)startUpdatingVolume
{
	
	NSLog(@"#startUpdatingVolume");
	
	if(initFlag == false){
		
		AVAudioSession *audioSession = [AVAudioSession sharedInstance];
		NSError *err = nil;
		[audioSession setCategory :AVAudioSessionCategoryPlayAndRecord error:&err];
		if(err){
			NSLog(@"audioSession: %@ %d %@", [err domain], [err code], [[err userInfo] description]);
			return;
		}
		[audioSession setActive:YES error:&err];
		err = nil;
		if(err){
			NSLog(@"audioSession: %@ %d %@", [err domain], [err code], [[err userInfo] description]);
			return;
		}
	}
	
	// 記録するデータフォーマットを決める
	AudioStreamBasicDescription dataFormat;
	dataFormat.mSampleRate = 44100.0f;
	dataFormat.mFormatID = kAudioFormatLinearPCM;
	dataFormat.mFormatFlags = kLinearPCMFormatFlagIsBigEndian | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
	dataFormat.mBytesPerPacket = 2;
	dataFormat.mFramesPerPacket = 1;
	dataFormat.mBytesPerFrame = 2;
	dataFormat.mChannelsPerFrame = 1;
	dataFormat.mBitsPerChannel = 16;
	dataFormat.mReserved = 0;
	
	
	// レベルの監視を開始する
	AudioQueueNewInput(&dataFormat, AudioInputCallback, (__bridge void *)(self), CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &_queue);
	AudioQueueStart(_queue, NULL);
	
	// レベルメータを有効化する
	UInt32 enabledLevelMeter = true;
	AudioQueueSetProperty(_queue, kAudioQueueProperty_EnableLevelMetering, &enabledLevelMeter, sizeof(UInt32));
	
	/*
	 // 定期的にレベルメータを監視する
	 _timer = [NSTimer scheduledTimerWithTimeInterval:0.5
	 target:self
	 selector:@selector(detectVolume:)
	 userInfo:nil
	 repeats:YES];
	 */
}

- (void)stopUpdatingVolume
{
	// キューを空にして停止
	AudioQueueFlush(_queue);
	AudioQueueStop(_queue, NO);
	AudioQueueDispose(_queue, YES);
}

/*
 - (void)detectVolume:(NSTimer *)timer
 {
 // レベルを取得
 AudioQueueLevelMeterState levelMeter;
 UInt32 levelMeterSize = sizeof(AudioQueueLevelMeterState);
 AudioQueueGetProperty(_queue, kAudioQueueProperty_CurrentLevelMeterDB, &levelMeter, &levelMeterSize);
 
 NSLog(@"%.2f", levelMeter.mPeakPower);
 
 // 最大レベル、平均レベルを表示
 //self.peakTextField.text = [NSString stringWithFormat:@"%.2f", levelMeter.mPeakPower];
 //self.averageTextField.text = [NSString stringWithFormat:@"%.2f", levelMeter.mAveragePower];
 
 // mPeakPowerが -1.0 以上なら "LOUD!!" と表示
 //self.loudLabel.hidden = (levelMeter.mPeakPower >= -1.0f) ? NO : YES;
 }*/

MicInputTest* ma;

void StartUpdatingVolume_()
{
	NSLog(@"#StartUpdatingVolume_");
	
	ma = [[MicInputTest alloc] init];
	
	[ma startUpdatingVolume];
}

float GetPeakPower_()
{
	// レベルを取得
	AudioQueueLevelMeterState levelMeter;
	UInt32 levelMeterSize = sizeof(AudioQueueLevelMeterState);
	AudioQueueGetProperty(_queue, kAudioQueueProperty_CurrentLevelMeterDB, &levelMeter, &levelMeterSize);
	
	NSLog(@"%.2f %.2f", levelMeter.mPeakPower,levelMeter.mAveragePower);
	
	return levelMeter.mPeakPower;
}

void StopUpdatingVolume_()
{
	NSLog(@"#StopUpdatingVolume");
	
	if(ma != nil)
	{
		[ma stopUpdatingVolume];
	}
}

@end

MicTest.cs


// GameObjectに貼付ける。画面タップするとレベルが取得できるようになる。
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class MicTest : MonoBehaviour {
	[DllImport("__Internal")]
	private static extern void StartUpdatingVolume_();
	public static void StartUpdatingVolume() {
		if (Application.platform != RuntimePlatform.OSXEditor) {
			StartUpdatingVolume_();
		}
	}
	[DllImport("__Internal")]
	private static extern void StopUpdatingVolume_();
	public static void StopUpdatingVolume() {
		if (Application.platform != RuntimePlatform.OSXEditor) {
			StopUpdatingVolume_();
		}
	}
	[DllImport("__Internal")]
	private static extern float GetPeakPower_();
	public static float GetPeakPower() {
		if (Application.platform != RuntimePlatform.OSXEditor) {
			return GetPeakPower_();
		}
		return 0.0f;
	}
	public TextMesh tm;

	public static MicTest main = null;
	
	void Awake()
	{
		if(main != null)
		{
			GameObject.Destroy(this);
		} else
		{
			main = this;
		}
	}

	// Use this for initialization
	void Start () {


	}

	void OnDestroy()
	{
		MicTest.StopUpdatingVolume();
		micEnable = false;
		initFlag = false;
	}

	void OnApplicationPause(bool pause)
	{
		MicTest.StopUpdatingVolume();
		micEnable = false;
		initFlag = false;
	}

	float level = 0.0f;
	public float volumInputGain = 10.0f;
	
	public float GetVolumeLevel()
	{
		return level;
	}
	
	public float time = 0;
	
	float lastVolume = 0f;

	bool micEnable = false;
	bool initFlag = false;
	
	// Update is called once per frame
	void Update () {

		if(Input.GetMouseButton(0)){
			if(micEnable == false){
				micEnable = true;
			}
		}

		if(micEnable){
			if(initFlag == false){

				MicTest.StartUpdatingVolume();

				/*
				AudioSource audioSource = this.gameObject.AddComponent<AudioSource>();
				audio.clip = Microphone.Start(null, true, 999, 44100);  // マイクからのAudio-InをAudioSourceに流す
				audio.loop = true;                                      // ループ再生にしておく
				audio.mute = true;                                      // マイクからの入力音なので音を流す必要がない
				while (!(Microphone.GetPosition("") > 0)){}             // マイクが取れるまで待つ。空文字でデフォルトのマイクを探してくれる
				audio.Play();   
				*/
				initFlag = true;
			}

			if(initFlag){
				/*
				float vol = GetAveragedVolume();
				lastVolume = Mathf.Lerp(lastVolume, vol, Time.time*0.001f);
				level = vol*volumInputGain;
				*/

				tm.text = MicTest.GetPeakPower().ToString();//level.ToString();

			}
		}
	}
	
	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;
	}
}


iOSでデバッグではまりやすい罠

一度あったので備忘録です。

iOSのプライバシー設定でマイクが無効になっているとプログラムは正しくても、マイク入力は一切反応しない状態になります。
確認はiOSの端末の「設定>プライバシー>マイク」にあるアプリ一覧から該当アプリがオンになっているか確認します。

余談

マイク入力して、録音した音をゲーム内に反映させるには、wav再生などの機能が必要だが・・・
(ADX2Pro版ならcriAtomExPlayer_SetFileでwav再生ができる様子)