400 lines
19 KiB
C#
Executable File
400 lines
19 KiB
C#
Executable File
using System;
|
||
using System.Linq;
|
||
using System.Runtime.InteropServices;
|
||
using UnityEngine;
|
||
|
||
namespace E7.Native
|
||
{
|
||
/// <summary>
|
||
/// Several properties about the device asked from the native side that might help you.
|
||
/// Returned from <see cref="NativeAudio.GetDeviceAudioInformation"/>
|
||
///
|
||
/// The content of this `struct` changes completely depending on active build platform.
|
||
/// You will want to use a preprocessor directive wrapping it.
|
||
/// </summary>
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
public struct DeviceAudioInformation
|
||
{
|
||
#if UNITY_IOS
|
||
/// <summary>
|
||
/// It is from [AVAudioSessionPortDescription](https://developer.apple.com/documentation/avfoundation/avaudiosessionportdescription).
|
||
/// </summary>
|
||
public enum IosAudioPortType
|
||
{
|
||
//---Output---
|
||
|
||
/// <summary>
|
||
/// Line-level output to the dock connector.
|
||
/// </summary>
|
||
LineOut = 0,
|
||
|
||
/// <summary>
|
||
/// Output to a wired headset.
|
||
/// </summary>
|
||
Headphones = 1,
|
||
|
||
/// <summary>
|
||
/// Output to a speaker intended to be held near the ear.
|
||
/// </summary>
|
||
BuiltInReceiver = 2,
|
||
|
||
/// <summary>
|
||
/// Output to the device’s built-in speaker.
|
||
/// </summary>
|
||
BuiltInSpeaker = 3,
|
||
|
||
/// <summary>
|
||
/// Output to a device via the High-Definition Multimedia Interface (HDMI) specification.
|
||
/// </summary>
|
||
HDMI = 4,
|
||
|
||
/// <summary>
|
||
/// Output to a remote device over AirPlay.
|
||
/// </summary>
|
||
AirPlay = 5,
|
||
|
||
/// <summary>
|
||
/// Output to a Bluetooth Low Energy (LE) peripheral.
|
||
/// </summary>
|
||
BluetoothLE = 6,
|
||
|
||
/// <summary>
|
||
/// Output to a Bluetooth A2DP device.
|
||
/// </summary>
|
||
BluetoothA2DP = 7,
|
||
|
||
//---Input---
|
||
|
||
/// <summary>
|
||
/// Line-level input from the dock connector.
|
||
/// </summary>
|
||
LineIn = 8,
|
||
|
||
/// <summary>
|
||
/// The built-in microphone on a device.
|
||
/// </summary>
|
||
BuiltInMic = 9,
|
||
|
||
/// <summary>
|
||
/// A microphone that is built-in to a wired headset.
|
||
/// </summary>
|
||
HeadsetMic = 10,
|
||
|
||
//---Input-Output---
|
||
|
||
/// <summary>
|
||
/// Input or output on a Bluetooth Hands-Free Profile device.
|
||
/// </summary>
|
||
BluetoothHFP = 11,
|
||
|
||
/// <summary>
|
||
/// Input or output on a Universal Serial Bus device.
|
||
/// </summary>
|
||
UsbAudio = 12,
|
||
|
||
/// <summary>
|
||
/// Input or output via Car Audio.
|
||
/// </summary>
|
||
CarAudio = 13,
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// [iOS] All OUTPUT audio devices currently active.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// The returned `enum` is native, so on an other platform the available choice completely changes.
|
||
/// Use `#if` directive to make this property compile on multiple platforms.
|
||
///
|
||
/// This is the return value from `[[AVAudioSession sharedInstance] currentRoute]` -> `outputs` -> each item.
|
||
/// </remarks>
|
||
public IosAudioPortType[] audioDevices { get; private set; }
|
||
|
||
/// <summary>
|
||
/// [iOS] The latency for audio output, measured in seconds.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// This is shared with Unity, not just for Native Audio, because `AVAudioSession` is a singleton and is shared.
|
||
///
|
||
/// A result from [this AVAudioSession instance property](https://developer.apple.com/documentation/avfoundation/avaudiosession/1616500-outputlatency).
|
||
///
|
||
/// For reference on my iPhone SE with device speaker, it is 0.0128750000149012.
|
||
/// (Regardless of Project Settings > Audio option selected)
|
||
///
|
||
/// Value is always specified in seconds; it yields sub-millisecond precision over a range of 10,000 years.
|
||
///
|
||
/// I don't know how could iOS know its own latency based on current connected device,
|
||
/// but I got curious and went to audio shop and debug this number on all the things they let me.
|
||
///
|
||
/// Hardware Name String Output Latency Brand
|
||
/// ---------------------------------------------------------
|
||
/// JBL T110BT 0.0825396850705147 JBL
|
||
/// JBL Reflect Mini2 0.0825396850705147 JBL
|
||
/// NW WS-623 0.0149886617437005 Sony Walkman
|
||
/// JBL E45BT 0.111564628779888 JBL
|
||
/// MAJOR III BLUETOOTH 0.149297058582306 Marshall
|
||
/// JBL Flip 4 0.111564628779888 JBL
|
||
/// JBL GO 2 0.0825396850705147 JBL
|
||
/// JBL GO 0.0825396850705147 JBL
|
||
/// JBL JR POP 0.111564628779888 JBL
|
||
/// JBL Charge 4 0.111564628779888 JBL
|
||
///
|
||
/// Looks like the measurement got several suspicious number, like exact same number across devices,
|
||
/// or even some that looks like 10x of the other.
|
||
/// So I think it must be calculated from something rather than measured live with fancy technique.
|
||
///
|
||
/// How is this useful? You could for example, display a warning in the game that your player's device
|
||
/// is not suitable to play the game due to high latency.
|
||
///
|
||
/// However when I test them, most sounds **almost** equal in latency (but all are bad for music games anyways),
|
||
/// but that Marshall MAJOR III has obviously much higher latency (almost 3x) than others.
|
||
/// Feels much higher than what you see in the data.
|
||
///
|
||
/// So it shows this number is somewhat reliable, but may not be 100% accurate of the real latency.
|
||
/// </remarks>
|
||
public double outputLatency { get; private set; }
|
||
|
||
/// <summary>
|
||
/// [iOS] The current audio sample rate, in hertz.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// This is shared with Unity, not just for Native Audio, because `AVAudioSession`
|
||
/// is a singleton and is shared.
|
||
///
|
||
/// A result from [this AVAudioSession instance property](https://developer.apple.com/documentation/avfoundation/avaudiosession/1616499-samplerate).
|
||
///
|
||
/// The available range for hardware sample rate is device dependent. It typically ranges from 8000 through 48000 hertz.
|
||
/// </remarks>
|
||
public double sampleRate { get; private set; }
|
||
|
||
/// <summary>
|
||
/// [iOS] The preferred sample rate, in hertz.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// This is shared with Unity, not just for Native Audio, because `AVAudioSession` is a singleton and is shared.
|
||
///
|
||
/// At native side this is freely specifiable, but iOS might give you something else that becomes <see cref="sampleRate"/>.
|
||
///
|
||
/// A result from [this AVAudioSession instance property](https://developer.apple.com/documentation/avfoundation/avaudiosession/1616543-preferredsamplerate).
|
||
/// </remarks>
|
||
public double preferredSampleRate { get; private set; }
|
||
|
||
/// <summary>
|
||
/// [iOS] The current I/O buffer duration, in seconds.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// A result from [this AVAudioSession instance property](https://developer.apple.com/documentation/avfoundation/avaudiosession/1616498-iobufferduration).
|
||
///
|
||
/// This could be viewed as an another representation of "buffer size" on Android.
|
||
/// This is instead in time unit rather than size. And it depends on the current sample rate in order
|
||
/// to calculate the resulting buffer size.
|
||
///
|
||
/// Value is always specified in seconds; it yields sub-millisecond precision over a range of 10,000 years.
|
||
///
|
||
/// The audio I/O buffer duration is the number of seconds for a single audio input/output cycle.
|
||
/// For example, with an I/O buffer duration of 0.005 s, on each audio I/O cycle:
|
||
///
|
||
/// You receive 0.005 s of audio if obtaining input.
|
||
/// You must provide 0.005 s of audio if providing output.
|
||
///
|
||
/// The typical maximum I/O buffer duration is 0.93 s
|
||
/// (corresponding to 4096 sample frames at a sample rate of 44.1 kHz).
|
||
/// The minimum I/O buffer duration is at least 0.005 s(256 frames)
|
||
/// but might be lower depending on the hardware in use.
|
||
///
|
||
/// For example if this is 0.01s, at sample rate 24000Hz (What Unity use)
|
||
/// it would have to get 0.01s of audio. But compared to 44000Hz rate, that
|
||
/// 0.01s of audio is of much less data. By using higher fidelity 44000Hz,
|
||
/// the same 0.01s could cause buffer underrun if the device is not fast enough.
|
||
///
|
||
/// This is shared with Unity, not just for Native Audio, because `AVAudioSession` is a singleton and is shared.
|
||
///
|
||
/// Here's some behaviour of this number based on my research.
|
||
///
|
||
/// For reference, with varying Project Settings > Audio options :
|
||
/// Best Latency : 0.0106666665524244
|
||
/// Good Latency : 0.0213333331048489
|
||
/// Best Performance : 0.0426666662096977
|
||
///
|
||
/// When connected to external audio device like a bluetooth headphone, the number on Best Latency
|
||
/// drops to 0.005 but sampling rate moved from 24000 to 44100.
|
||
/// This can be interpret as Unity try to make sampling rate compatible with external device,
|
||
/// but now must write half less audio seconds because it now have twice as many data.
|
||
///
|
||
/// By setting <see cref="preferredIOBufferDuration"/> at native side to 0.005
|
||
/// (the limit mentioned in the documentation) this became 0.005333.
|
||
///
|
||
/// When set <see cref="preferredIOBufferDuration"/> back to 0, this became 0.021333. (The same as Good Latency, even though Unity is currently in Best Latency.)
|
||
/// However by benchmarking sometimes 0.005 duration do produce worse latency than 0.01, I wonder why..
|
||
///
|
||
/// </remarks>
|
||
public double ioBufferDuration {get; private set;}
|
||
|
||
/// <summary>
|
||
/// [iOS] The preferred I/O buffer duration, in seconds.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// At native side this is freely specifiable, but iOS might give you something else that becomes <see cref="ioBufferDuration"/>.
|
||
///
|
||
/// This is shared with Unity, not just for Native Audio, because `AVAudioSession` is a singleton and is shared.
|
||
/// This seems to be always 0 in Unity games by default.
|
||
///
|
||
/// A result from [this AVAudioSession instance property](https://developer.apple.com/documentation/avfoundation/avaudiosession/1616464-preferrediobufferduration).
|
||
///
|
||
/// Value is always specified in seconds; it yields sub-millisecond precision over a range of 10,000 years.
|
||
/// </remarks>
|
||
public double preferredIOBufferDuration { get; private set; }
|
||
|
||
internal const int interopArrayLength = 5;
|
||
public DeviceAudioInformation(double[] interopDoubleArray, IosAudioPortType[] portArray)
|
||
{
|
||
if (interopDoubleArray.Length != interopArrayLength)
|
||
{
|
||
throw new ArgumentException("The array that fetched data from iOS should be of length " + interopArrayLength);
|
||
}
|
||
this.outputLatency = interopDoubleArray[0];
|
||
this.sampleRate = interopDoubleArray[1];
|
||
this.preferredSampleRate = interopDoubleArray[2];
|
||
this.ioBufferDuration = interopDoubleArray[3];
|
||
this.preferredIOBufferDuration = interopDoubleArray[4];
|
||
|
||
this.audioDevices = portArray;
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return string.Format(
|
||
"Audio devices : {0} Output Latency : {1} Sample Rate : {2} Preferred Sample Rate : {3} IO Buffer Duration : {4} Preferred IO Buffer Duration : {5}",
|
||
string.Join(", ", this.audioDevices.Select(x => x.ToString()).ToArray()),
|
||
outputLatency,
|
||
sampleRate,
|
||
preferredSampleRate,
|
||
ioBufferDuration,
|
||
preferredIOBufferDuration
|
||
);
|
||
}
|
||
#endif
|
||
|
||
#if UNITY_ANDROID
|
||
/// <summary>
|
||
/// [Android] Only audio matching this sampling rate on a native AudioTrack created with this
|
||
/// sampling rate is eligible for fast track playing.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// This is NOT the sample rate that Native Audio might be currently working with,
|
||
/// nor what Unity is using for their own audio source.
|
||
/// Just a per-device attribute.
|
||
/// </remarks>
|
||
public int nativeSamplingRate { get; private set; }
|
||
|
||
/// <summary>
|
||
/// [Android] How large of a buffer that your phone wants to work with.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// This is NOT the buffer size that Native Audio might be currently working with,
|
||
/// nor what Unity is using for their own audio source.
|
||
/// Just a per-device attribute.
|
||
/// </remarks>
|
||
public int optimalBufferSize { get; private set; }
|
||
|
||
/// <summary>
|
||
/// [Android] Indicates a continuous output latency of 45 ms or less.
|
||
/// Only valid if the device API >= 23 (6.0, Marshmallow), or else it is always `false`.
|
||
/// </summary>
|
||
public bool lowLatencyFeature { get; private set; }
|
||
|
||
/// <summary>
|
||
/// [Android] Indicates a continuous round-trip latency of 20 ms or less.
|
||
/// Only valid if the device API >= 23 (6.0, Marshmallow), or else it is always `false`.
|
||
/// </summary>
|
||
public bool proAudioFeature { get; private set; }
|
||
|
||
/// <summary>
|
||
/// [Android] All OUTPUT devices currently active.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// The returned `enum` is native, so on an other platform the available choice completely changes.
|
||
/// Use `#if` directive to make this property compile on multiple platforms.
|
||
///
|
||
/// These are returnd from `audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);` API.
|
||
/// Only valid if the device API >= 23 (6.0, Marshmallow), or else it is always `null`.
|
||
/// </remarks>
|
||
public AndroidAudioDeviceType[] audioDevices { get; private set; }
|
||
|
||
/// <summary>
|
||
/// I just copied everything from [AudioDeviceInfo](https://developer.android.com/reference/android/media/AudioDeviceInfo.html#constants_2).
|
||
/// </summary>
|
||
public enum AndroidAudioDeviceType
|
||
{
|
||
TYPE_AUX_LINE = 19,
|
||
TYPE_BLUETOOTH_A2DP = 8,
|
||
TYPE_BLUETOOTH_SCO = 7,
|
||
TYPE_BUILTIN_EARPIECE = 1,
|
||
TYPE_BUILTIN_MIC = 15,
|
||
TYPE_BUILTIN_SPEAKER = 2,
|
||
TYPE_BUS = 21,
|
||
TYPE_DOCK = 13,
|
||
TYPE_FM = 14,
|
||
TYPE_FM_TUNER = 16,
|
||
TYPE_HDMI = 9,
|
||
TYPE_HDMI_ARC = 10,
|
||
TYPE_HEARING_AID = 23,
|
||
TYPE_IP = 20,
|
||
TYPE_LINE_ANALOG = 5,
|
||
TYPE_LINE_DIGITAL = 6,
|
||
TYPE_TELEPHONY = 18,
|
||
TYPE_TV_TUNER = 17,
|
||
TYPE_UNKNOWN = 0,
|
||
TYPE_USB_ACCESSORY = 12,
|
||
TYPE_USB_DEVICE = 11,
|
||
TYPE_USB_HEADSET = 22,
|
||
TYPE_WIRED_HEADPHONES = 4,
|
||
TYPE_WIRED_HEADSET = 3,
|
||
}
|
||
|
||
public DeviceAudioInformation(AndroidJavaObject jo)
|
||
{
|
||
AndroidJavaClass versionClass = new AndroidJavaClass("android/os/Build$VERSION");
|
||
int sdkLevel = versionClass.GetStatic<int>("SDK_INT");
|
||
|
||
this.nativeSamplingRate = jo.Get<int>("nativeSamplingRate");
|
||
this.optimalBufferSize = jo.Get<int>("optimalBufferSize");
|
||
this.lowLatencyFeature = jo.Get<bool>("lowLatencyFeature");
|
||
this.proAudioFeature = jo.Get<bool>("proAudioFeature");
|
||
|
||
if (sdkLevel >= 23)
|
||
{
|
||
//This one is a Java array, we need to do JNI manually to each elements
|
||
AndroidJavaObject outputDevicesJo = jo.Get<AndroidJavaObject>("outputDevices");
|
||
|
||
IntPtr outputDevicesRaw = outputDevicesJo.GetRawObject();
|
||
int outputDeviceAmount = AndroidJNI.GetArrayLength(outputDevicesRaw);
|
||
|
||
this.audioDevices = new AndroidAudioDeviceType[outputDeviceAmount];
|
||
|
||
for (int i = 0; i < outputDeviceAmount; i++)
|
||
{
|
||
IntPtr outputDevice = AndroidJNI.GetObjectArrayElement(outputDevicesRaw, i);
|
||
IntPtr audioDeviceInfoClass = AndroidJNI.GetObjectClass(outputDevice);
|
||
IntPtr getTypeMethod = AndroidJNIHelper.GetMethodID(audioDeviceInfoClass, "getType");
|
||
int type = AndroidJNI.CallIntMethod(outputDevice, getTypeMethod, new jvalue[] { });
|
||
this.audioDevices[i] = (AndroidAudioDeviceType)type;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
this.audioDevices = new AndroidAudioDeviceType[0];
|
||
}
|
||
|
||
//Debug.Log(this.ToString());
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return string.Format("Native Sampling Rate: {0} | Optimal Buffer Size: {1} | Low Latency Feature: {2} | Pro Audio Feature: {3} | Output devices : {4}",
|
||
nativeSamplingRate, optimalBufferSize, lowLatencyFeature, proAudioFeature,
|
||
string.Join(", ", this.audioDevices.Select(x => x.ToString()).ToArray()));
|
||
}
|
||
#endif
|
||
}
|
||
} |