3 #if SILICONSTUDIO_PLATFORM_ANDROID
6 using System.Collections.Generic;
7 using System.Reflection;
8 using System.Runtime.InteropServices;
11 using Android.Runtime;
13 using SiliconStudio.Core.Mathematics;
14 using SiliconStudio.Paradox.Audio.Wave;
16 using Math = System.Math;
18 namespace SiliconStudio.
Paradox.Audio
20 partial class SoundEffectInstance
22 private const int MaximumNumberOfTracks = 8;
23 private const int NumberOfSubBuffersInAudioTrack = 2;
24 protected internal const int SoundEffectInstanceFrameRate = 44100;
26 private static readonly Queue<TrackInfo> audioTrackPool =
new Queue<TrackInfo>();
28 private static readonly IntPtr audioTrackClassJavaHandle = JNIEnv.FindClass(
"android/media/AudioTrack");
29 private static readonly IntPtr audioTrackWriteMethodID = JNIEnv.GetMethodID(audioTrackClassJavaHandle,
"write",
"([BII)I");
31 private delegate
void SetJavaByteArrayRegionDelegate(IntPtr handle, IntPtr destination,
int offset,
int length, IntPtr sourceByteArray);
33 private static SetJavaByteArrayRegionDelegate setJavaByteArray;
35 private static IntPtr blankJavaDataBuffer;
37 private int readBufferPosition;
38 private int writeBufferPosition;
40 private TrackInfo currentTrack;
41 private readonly
object currentTrackLock =
new object();
43 private bool exitLoopRequested;
45 private class TrackInfo : IDisposable
47 private readonly byte[] dataBuffer;
49 private readonly
int bufferSize;
51 private readonly IntPtr javaDataBuffer;
53 private readonly JValue[] javaWriteCallValues;
55 public readonly AudioTrack Track;
57 public int BuffersToWrite;
59 public bool ShouldStop;
61 public SoundEffectInstance CurrentInstance;
63 public TrackInfo(AudioTrack track, IntPtr javaDataBuffer, byte[] dataBuffer,
int bufferSize)
66 this.javaDataBuffer = javaDataBuffer;
67 this.dataBuffer = dataBuffer;
68 this.bufferSize = bufferSize;
70 javaWriteCallValues =
new JValue[3];
73 Track.PeriodicNotification += (sender, args) => OnPeriodFinished();
74 var status = Track.SetPositionNotificationPeriod(bufferSize / 4);
75 if (status != TrackStatus.Success)
76 throw new AudioSystemInternalException(
"AudioTrack.SetNotificationMarkerPosition failed and failure was not handled. [error=" + status +
"].");
82 public void WriteNextAudioBufferToTrack()
86 var instance = CurrentInstance;
87 var soundEffect = instance.soundEffect;
90 while (sizeWriten < bufferSize)
92 var sizeToWrite = bufferSize - sizeWriten;
93 var shouldWriteBlank = instance.writeBufferPosition >= soundEffect.WaveDataSize;
95 if (!shouldWriteBlank)
97 sizeToWrite = Math.Min(sizeToWrite, soundEffect.WaveDataSize - instance.writeBufferPosition);
99 if (setJavaByteArray == null)
101 Array.Copy(soundEffect.WaveDataArray, instance.writeBufferPosition, dataBuffer, 0, sizeToWrite);
102 JNIEnv.CopyArray(dataBuffer, javaDataBuffer);
106 setJavaByteArray(JNIEnv.Handle, javaDataBuffer, 0, sizeToWrite, soundEffect.WaveDataPtr + instance.writeBufferPosition);
110 javaWriteCallValues[0] =
new JValue(shouldWriteBlank ? blankJavaDataBuffer : javaDataBuffer);
111 javaWriteCallValues[1] =
new JValue(0);
112 javaWriteCallValues[2] =
new JValue(sizeToWrite);
114 var writtenSize = JNIEnv.CallIntMethod(Track.Handle, audioTrackWriteMethodID, javaWriteCallValues);
116 sizeWriten += writtenSize;
117 instance.writeBufferPosition += writtenSize;
119 if (instance.writeBufferPosition >= soundEffect.WaveDataSize && instance.IsLooped && !instance.exitLoopRequested)
120 instance.writeBufferPosition = 0;
122 if (writtenSize != sizeToWrite)
127 private void OnPeriodFinished()
129 int audioDataBufferSize;
130 SoundEffectInstance instance;
135 if (BuffersToWrite == NumberOfSubBuffersInAudioTrack)
138 instance = CurrentInstance;
142 instance.readBufferPosition += bufferSize;
143 audioDataBufferSize = instance.soundEffect.WaveDataSize;
145 while (instance.readBufferPosition >= audioDataBufferSize && instance.IsLooped && !instance.exitLoopRequested)
146 instance.readBufferPosition -= audioDataBufferSize;
148 if (instance.readBufferPosition < audioDataBufferSize && !ShouldStop)
149 WriteNextAudioBufferToTrack();
151 if ((instance.readBufferPosition >= audioDataBufferSize || ShouldStop) && instance.PlayState !=
SoundPlayState.Paused)
155 public void Dispose()
159 JNIEnv.DeleteGlobalRef(javaDataBuffer);
163 internal static void CreateAudioTracks()
165 const int audioMemoryOS = 1024 * 1024;
166 const int memoryDealerHeaderSize = 64;
168 GetSetArrayRegionFunctionPointer();
171 var minimumBufferSize = AudioTrack.GetMinBufferSize(SoundEffectInstanceFrameRate, ChannelOut.Stereo, Encoding.Pcm16bit);
174 var memoryNeededForSoundMusic = 2 * (GetUpperPowerOfTwo(minimumBufferSize) + memoryDealerHeaderSize);
177 var subBufferSize = Math.Max((int)Math.Ceiling(minimumBufferSize / (
float)NumberOfSubBuffersInAudioTrack), 2 * 2 * 8000);
180 var memoryNeededAudioTrack = GetUpperPowerOfTwo(subBufferSize*NumberOfSubBuffersInAudioTrack);
183 blankJavaDataBuffer = JNIEnv.NewGlobalRef(JNIEnv.NewArray(
new byte[subBufferSize]));
187 while (trackNumber < MaximumNumberOfTracks && audioMemoryOS - (trackNumber+1) * memoryNeededAudioTrack >= memoryNeededForSoundMusic)
190 var audioTrack =
new AudioTrack(Stream.Music, SoundEffectInstanceFrameRate, ChannelOut.Stereo,
Encoding.Pcm16bit,
191 NumberOfSubBuffersInAudioTrack * subBufferSize, AudioTrackMode.Stream);
193 if (audioTrack.State == AudioTrackState.Uninitialized)
197 var dataBuffer =
new byte[subBufferSize];
200 var javaDataBuffer = JNIEnv.NewGlobalRef(JNIEnv.NewArray(dataBuffer));
203 var newTrackInfo =
new TrackInfo(audioTrack, javaDataBuffer, dataBuffer, subBufferSize) { BuffersToWrite = NumberOfSubBuffersInAudioTrack };
204 audioTrackPool.Enqueue(newTrackInfo);
210 private static int GetUpperPowerOfTwo(
int size)
212 var upperPowerOfTwo = 2;
213 while (upperPowerOfTwo < size)
214 upperPowerOfTwo = upperPowerOfTwo << 1;
216 return upperPowerOfTwo;
222 private static void GetSetArrayRegionFunctionPointer()
227 var jniEnvGetter = typeof(JNIEnv).GetMethod(
"get_Env", BindingFlags.Static | BindingFlags.NonPublic);
228 var jniEnvInstanceField = jniEnvGetter.ReturnType.GetField(
"JniEnv", BindingFlags.NonPublic | BindingFlags.Instance);
229 var setByteArrayFunctionField = jniEnvInstanceField.FieldType.GetField(
"SetByteArrayRegion", BindingFlags.Public | BindingFlags.Instance);
231 var jniEnvInstance = jniEnvInstanceField.GetValue(jniEnvGetter.Invoke(null, null));
232 var pointerToSetByteArrayFunction = (IntPtr)setByteArrayFunctionField.GetValue(jniEnvInstance);
234 setJavaByteArray = Marshal.GetDelegateForFunctionPointer<SetJavaByteArrayRegionDelegate>(pointerToSetByteArrayFunction);
238 setJavaByteArray = null;
243 internal static void StaticDestroy()
245 JNIEnv.DeleteGlobalRef(blankJavaDataBuffer);
248 foreach (var trackInfo
in audioTrackPool)
251 audioTrackPool.Clear();
254 internal void UpdateStereoVolumes()
256 lock (currentTrackLock)
258 if (currentTrack == null)
262 var status = currentTrack.Track.SetStereoVolume(
Volume * panChannelVolumes[0] * localizationChannelVolumes[0],
Volume * panChannelVolumes[1] * localizationChannelVolumes[1]);
263 if (status != TrackStatus.Success)
264 throw new AudioSystemInternalException(
"AudioTrack.SetStereoVolume failed and failure was not handled. [error:" + status +
"]");
268 internal override void UpdateLooping()
272 internal override void PlayImpl()
274 lock (currentTrackLock)
277 throw new AudioSystemInternalException(
"AudioTrack.PlayImpl was called with play state '" + PlayState +
"' and currentTrackInfo not null.");
279 if (currentTrack == null)
281 currentTrack = TryGetAudioTack();
282 if (currentTrack == null)
284 AudioEngine.Logger.Info(
"Failed to obtain an audio track for SoundEffectInstance '{0}'. Play call will be ignored.",Name);
291 UpdateStereoVolumes();
296 currentTrack.CurrentInstance =
this;
297 currentTrack.ShouldStop =
false;
299 currentTrack.Track.Play();
301 while (currentTrack.BuffersToWrite > 0)
302 currentTrack.WriteNextAudioBufferToTrack();
307 internal override void PauseImpl()
309 lock (currentTrackLock)
311 if (currentTrack == null)
314 currentTrack.ShouldStop =
true;
318 internal override void StopImpl()
320 exitLoopRequested =
false;
322 lock (currentTrackLock)
324 if (currentTrack == null)
330 currentTrack.ShouldStop =
true;
331 currentTrack.CurrentInstance = null;
335 readBufferPosition = 0;
336 writeBufferPosition = 0;
339 lock (audioTrackPool)
340 audioTrackPool.Enqueue(currentTrack);
343 lock (currentTrackLock)
348 internal override
void ExitLoopImpl()
350 exitLoopRequested =
true;
353 internal virtual void CreateVoice(WaveFormat
format)
358 private TrackInfo TryGetAudioTack()
361 lock (audioTrackPool)
363 if (audioTrackPool.Count > 0)
364 return audioTrackPool.Dequeue();
368 var soundEffectToStop = AudioEngine.GetLeastSignificativeSoundEffect();
369 if (soundEffectToStop == null)
373 soundEffectToStop.StopAllInstances();
375 lock (audioTrackPool)
377 if (audioTrackPool.Count > 0)
378 return audioTrackPool.Dequeue();
384 internal override void LoadBuffer()
388 internal virtual void DestroyVoice()
390 lock (currentTrackLock)
392 if (currentTrack != null)
393 throw new AudioSystemInternalException(
"The AudioTrackInfo was not null when destroying the SoundEffectInstance.");
397 internal void UpdatePitch()
399 lock (currentTrackLock)
401 if (currentTrack == null)
404 var status = currentTrack.Track.SetPlaybackRate((int)(MathUtil.Clamp((
float)Math.Pow(2, Pitch) * dopplerPitchFactor, 0.5f, 2f) * SoundEffectInstanceFrameRate));
405 if (status != (
int)TrackStatus.Success)
406 throw new AudioSystemInternalException(
"AudioTrack.SetPlaybackRate failed and failure was not handled. [error:" + status +
"]");
410 internal virtual void PlatformSpecificDisposeImpl()
415 private void Apply3DImpl(AudioListener listener, AudioEmitter emitter)
431 var vecListEmit = emitter.Position - listener.Position;
432 var distListEmit = vecListEmit.Length();
433 var attenuationFactor = distListEmit <= emitter.DistanceScale ? 1f : emitter.DistanceScale / distListEmit;
436 var repartRight = 0.5f;
437 var worldToList = Matrix.Identity;
438 var rightVec = Vector3.Cross(listener.Forward, listener.Up);
439 worldToList.Column1 =
new Vector4(rightVec, 0);
440 worldToList.Column2 =
new Vector4(listener.Forward, 0);
441 worldToList.Column3 =
new Vector4(listener.Up, 0);
442 var vecListEmitListBase = Vector3.TransformNormal(vecListEmit, worldToList);
443 var vecListEmitListBase2 = (
Vector2)vecListEmitListBase;
444 if (vecListEmitListBase2.Length() > 0)
446 const float c = 1.45f;
447 var absAlpha = Math.Abs(Math.Atan2(vecListEmitListBase2.Y, vecListEmitListBase2.X));
448 var normAlpha = (float)(absAlpha / (Math.PI / 2));
449 if (absAlpha > Math.PI / 2) normAlpha = 2 - normAlpha;
450 repartRight = 0.5f * (2 * (c - 1) * normAlpha * normAlpha * normAlpha - 3 * (c - 1) * normAlpha * normAlpha * normAlpha + c * normAlpha);
451 if (absAlpha > Math.PI / 2) repartRight = 1 - repartRight;
455 localizationChannelVolumes =
new[] { attenuationFactor * (1f - repartRight), attenuationFactor * repartRight };
456 UpdateStereoVolumes();
459 ComputeDopplerFactor(listener, emitter);
463 private void Reset3DImpl()
468 internal override void UpdateVolume()
470 UpdateStereoVolumes();
473 private void UpdatePan()
475 UpdateStereoVolumes();
SiliconStudio.Paradox.Games.Mathematics.Vector2 Vector2
System.Text.Encoding Encoding
SoundPlayState
Current state (playing, paused, or stopped) of a sound implementing the IPlayableSound interface...
_In_ size_t _In_ size_t _In_ DXGI_FORMAT format
_In_ size_t _In_ size_t size