SoundIoHardwareDeviceSession.cs
1 using Ryujinx.Audio.Backends.Common; 2 using Ryujinx.Audio.Backends.SoundIo.Native; 3 using Ryujinx.Audio.Common; 4 using Ryujinx.Common.Memory; 5 using Ryujinx.Memory; 6 using System; 7 using System.Buffers; 8 using System.Collections.Concurrent; 9 using System.Runtime.CompilerServices; 10 using System.Threading; 11 using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; 12 13 namespace Ryujinx.Audio.Backends.SoundIo 14 { 15 class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase 16 { 17 private readonly SoundIoHardwareDeviceDriver _driver; 18 private readonly ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers; 19 private SoundIoOutStreamContext _outputStream; 20 private readonly DynamicRingBuffer _ringBuffer; 21 private ulong _playedSampleCount; 22 private readonly ManualResetEvent _updateRequiredEvent; 23 private float _volume; 24 private int _disposeState; 25 26 public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) 27 { 28 _driver = driver; 29 _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); 30 _queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>(); 31 _ringBuffer = new DynamicRingBuffer(); 32 _volume = 1f; 33 34 SetupOutputStream(driver.Volume); 35 } 36 37 private void SetupOutputStream(float requestedVolume) 38 { 39 _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount); 40 _outputStream.WriteCallback += Update; 41 _outputStream.Volume = requestedVolume; 42 // TODO: Setup other callbacks (errors, etc.) 43 44 _outputStream.Open(); 45 } 46 47 public override ulong GetPlayedSampleCount() 48 { 49 return Interlocked.Read(ref _playedSampleCount); 50 } 51 52 public override float GetVolume() 53 { 54 return _volume; 55 } 56 57 public override void PrepareToClose() { } 58 59 public override void QueueBuffer(AudioBuffer buffer) 60 { 61 SoundIoAudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); 62 63 _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); 64 65 _queuedBuffers.Enqueue(driverBuffer); 66 } 67 68 public override void SetVolume(float volume) 69 { 70 _volume = volume; 71 72 _outputStream.SetVolume(_driver.Volume * volume); 73 } 74 75 public void UpdateMasterVolume(float newVolume) 76 { 77 _outputStream.SetVolume(newVolume * _volume); 78 } 79 80 public override void Start() 81 { 82 _outputStream.Start(); 83 _outputStream.Pause(false); 84 85 _driver.FlushContextEvents(); 86 } 87 88 public override void Stop() 89 { 90 _outputStream.Pause(true); 91 92 _driver.FlushContextEvents(); 93 } 94 95 public override void UnregisterBuffer(AudioBuffer buffer) { } 96 97 public override bool WasBufferFullyConsumed(AudioBuffer buffer) 98 { 99 if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) 100 { 101 return true; 102 } 103 104 return driverBuffer.DriverIdentifier != buffer.DataPointer; 105 } 106 107 private unsafe void Update(int minFrameCount, int maxFrameCount) 108 { 109 int bytesPerFrame = _outputStream.BytesPerFrame; 110 uint bytesPerSample = (uint)_outputStream.BytesPerSample; 111 112 int bufferedFrames = _ringBuffer.Length / bytesPerFrame; 113 114 int frameCount = Math.Min(bufferedFrames, maxFrameCount); 115 116 if (frameCount == 0) 117 { 118 return; 119 } 120 121 Span<SoundIoChannelArea> areas = _outputStream.BeginWrite(ref frameCount); 122 123 int channelCount = areas.Length; 124 125 using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * bytesPerFrame); 126 127 Span<byte> samples = samplesOwner.Span; 128 129 _ringBuffer.Read(samples, 0, samples.Length); 130 131 // This is a huge ugly block of code, but we save 132 // a significant amount of time over the generic 133 // loop that handles other channel counts. 134 // TODO: Is this still right in 2022? 135 136 // Mono 137 if (channelCount == 1) 138 { 139 ref SoundIoChannelArea area = ref areas[0]; 140 141 fixed (byte* srcptr = samples) 142 { 143 if (bytesPerSample == 1) 144 { 145 for (int frame = 0; frame < frameCount; frame++) 146 { 147 ((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame]; 148 149 area.Pointer += area.Step; 150 } 151 } 152 else if (bytesPerSample == 2) 153 { 154 for (int frame = 0; frame < frameCount; frame++) 155 { 156 ((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1]; 157 158 area.Pointer += area.Step; 159 } 160 } 161 else if (bytesPerSample == 4) 162 { 163 for (int frame = 0; frame < frameCount; frame++) 164 { 165 ((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2]; 166 167 area.Pointer += area.Step; 168 } 169 } 170 else 171 { 172 for (int frame = 0; frame < frameCount; frame++) 173 { 174 Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample); 175 176 area.Pointer += area.Step; 177 } 178 } 179 } 180 } 181 // Stereo 182 else if (channelCount == 2) 183 { 184 ref SoundIoChannelArea area1 = ref areas[0]; 185 ref SoundIoChannelArea area2 = ref areas[1]; 186 187 fixed (byte* srcptr = samples) 188 { 189 if (bytesPerSample == 1) 190 { 191 for (int frame = 0; frame < frameCount; frame++) 192 { 193 // Channel 1 194 ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; 195 196 // Channel 2 197 ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1]; 198 199 area1.Pointer += area1.Step; 200 area2.Pointer += area2.Step; 201 } 202 } 203 else if (bytesPerSample == 2) 204 { 205 for (int frame = 0; frame < frameCount; frame++) 206 { 207 // Channel 1 208 ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; 209 210 // Channel 2 211 ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1]; 212 213 area1.Pointer += area1.Step; 214 area2.Pointer += area2.Step; 215 } 216 } 217 else if (bytesPerSample == 4) 218 { 219 for (int frame = 0; frame < frameCount; frame++) 220 { 221 // Channel 1 222 ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; 223 224 // Channel 2 225 ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1]; 226 227 area1.Pointer += area1.Step; 228 area2.Pointer += area2.Step; 229 } 230 } 231 else 232 { 233 for (int frame = 0; frame < frameCount; frame++) 234 { 235 // Channel 1 236 Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); 237 238 // Channel 2 239 Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample); 240 241 area1.Pointer += area1.Step; 242 area2.Pointer += area2.Step; 243 } 244 } 245 } 246 } 247 // Surround 248 else if (channelCount == 6) 249 { 250 ref SoundIoChannelArea area1 = ref areas[0]; 251 ref SoundIoChannelArea area2 = ref areas[1]; 252 ref SoundIoChannelArea area3 = ref areas[2]; 253 ref SoundIoChannelArea area4 = ref areas[3]; 254 ref SoundIoChannelArea area5 = ref areas[4]; 255 ref SoundIoChannelArea area6 = ref areas[5]; 256 257 fixed (byte* srcptr = samples) 258 { 259 if (bytesPerSample == 1) 260 { 261 for (int frame = 0; frame < frameCount; frame++) 262 { 263 // Channel 1 264 ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; 265 266 // Channel 2 267 ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1]; 268 269 // Channel 3 270 ((byte*)area3.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 2]; 271 272 // Channel 4 273 ((byte*)area4.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 3]; 274 275 // Channel 5 276 ((byte*)area5.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 4]; 277 278 // Channel 6 279 ((byte*)area6.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 5]; 280 281 area1.Pointer += area1.Step; 282 area2.Pointer += area2.Step; 283 area3.Pointer += area3.Step; 284 area4.Pointer += area4.Step; 285 area5.Pointer += area5.Step; 286 area6.Pointer += area6.Step; 287 } 288 } 289 else if (bytesPerSample == 2) 290 { 291 for (int frame = 0; frame < frameCount; frame++) 292 { 293 // Channel 1 294 ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; 295 296 // Channel 2 297 ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1]; 298 299 // Channel 3 300 ((short*)area3.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 2]; 301 302 // Channel 4 303 ((short*)area4.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 3]; 304 305 // Channel 5 306 ((short*)area5.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 4]; 307 308 // Channel 6 309 ((short*)area6.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 5]; 310 311 area1.Pointer += area1.Step; 312 area2.Pointer += area2.Step; 313 area3.Pointer += area3.Step; 314 area4.Pointer += area4.Step; 315 area5.Pointer += area5.Step; 316 area6.Pointer += area6.Step; 317 } 318 } 319 else if (bytesPerSample == 4) 320 { 321 for (int frame = 0; frame < frameCount; frame++) 322 { 323 // Channel 1 324 ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; 325 326 // Channel 2 327 ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1]; 328 329 // Channel 3 330 ((int*)area3.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 2]; 331 332 // Channel 4 333 ((int*)area4.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 3]; 334 335 // Channel 5 336 ((int*)area5.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 4]; 337 338 // Channel 6 339 ((int*)area6.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 5]; 340 341 area1.Pointer += area1.Step; 342 area2.Pointer += area2.Step; 343 area3.Pointer += area3.Step; 344 area4.Pointer += area4.Step; 345 area5.Pointer += area5.Step; 346 area6.Pointer += area6.Step; 347 } 348 } 349 else 350 { 351 for (int frame = 0; frame < frameCount; frame++) 352 { 353 // Channel 1 354 Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); 355 356 // Channel 2 357 Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample); 358 359 // Channel 3 360 Unsafe.CopyBlockUnaligned((byte*)area3.Pointer, srcptr + (frame * bytesPerFrame) + (2 * bytesPerSample), bytesPerSample); 361 362 // Channel 4 363 Unsafe.CopyBlockUnaligned((byte*)area4.Pointer, srcptr + (frame * bytesPerFrame) + (3 * bytesPerSample), bytesPerSample); 364 365 // Channel 5 366 Unsafe.CopyBlockUnaligned((byte*)area5.Pointer, srcptr + (frame * bytesPerFrame) + (4 * bytesPerSample), bytesPerSample); 367 368 // Channel 6 369 Unsafe.CopyBlockUnaligned((byte*)area6.Pointer, srcptr + (frame * bytesPerFrame) + (5 * bytesPerSample), bytesPerSample); 370 371 area1.Pointer += area1.Step; 372 area2.Pointer += area2.Step; 373 area3.Pointer += area3.Step; 374 area4.Pointer += area4.Step; 375 area5.Pointer += area5.Step; 376 area6.Pointer += area6.Step; 377 } 378 } 379 } 380 } 381 // Every other channel count 382 else 383 { 384 fixed (byte* srcptr = samples) 385 { 386 for (int frame = 0; frame < frameCount; frame++) 387 { 388 for (int channel = 0; channel < areas.Length; channel++) 389 { 390 // Copy channel by channel, frame by frame. This is slow! 391 Unsafe.CopyBlockUnaligned((byte*)areas[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample); 392 393 areas[channel].Pointer += areas[channel].Step; 394 } 395 } 396 } 397 } 398 399 _outputStream.EndWrite(); 400 401 ulong sampleCount = (ulong)(samples.Length / bytesPerSample / channelCount); 402 403 ulong availaibleSampleCount = sampleCount; 404 405 bool needUpdate = false; 406 407 while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) 408 { 409 ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); 410 ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); 411 412 Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount); 413 availaibleSampleCount -= playedAudioBufferSampleCount; 414 415 if (Interlocked.Read(ref driverBuffer.SamplePlayed) == driverBuffer.SampleCount) 416 { 417 _queuedBuffers.TryDequeue(out _); 418 419 needUpdate = true; 420 } 421 422 Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount); 423 } 424 425 // Notify the output if needed. 426 if (needUpdate) 427 { 428 _updateRequiredEvent.Set(); 429 } 430 } 431 432 protected virtual void Dispose(bool disposing) 433 { 434 if (disposing && _driver.Unregister(this)) 435 { 436 PrepareToClose(); 437 Stop(); 438 439 _outputStream.Dispose(); 440 } 441 } 442 443 public override void Dispose() 444 { 445 if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) 446 { 447 Dispose(true); 448 } 449 } 450 } 451 }