AudioDeviceSession.cs
1 using Ryujinx.Audio.Integration; 2 using Ryujinx.Common; 3 using System; 4 using System.Diagnostics; 5 6 namespace Ryujinx.Audio.Common 7 { 8 /// <summary> 9 /// An audio device session. 10 /// </summary> 11 class AudioDeviceSession : IDisposable 12 { 13 /// <summary> 14 /// The volume of the <see cref="AudioDeviceSession"/>. 15 /// </summary> 16 private float _volume; 17 18 /// <summary> 19 /// The state of the <see cref="AudioDeviceSession"/>. 20 /// </summary> 21 private AudioDeviceState _state; 22 23 /// <summary> 24 /// Array of all buffers currently used or released. 25 /// </summary> 26 private readonly AudioBuffer[] _buffers; 27 28 /// <summary> 29 /// The server index inside <see cref="_buffers"/> (appended but not queued to device driver). 30 /// </summary> 31 private uint _serverBufferIndex; 32 33 /// <summary> 34 /// The hardware index inside <see cref="_buffers"/> (queued to device driver). 35 /// </summary> 36 private uint _hardwareBufferIndex; 37 38 /// <summary> 39 /// The released index inside <see cref="_buffers"/> (released by the device driver). 40 /// </summary> 41 private uint _releasedBufferIndex; 42 43 /// <summary> 44 /// The count of buffer appended (server side). 45 /// </summary> 46 private uint _bufferAppendedCount; 47 48 /// <summary> 49 /// The count of buffer registered (driver side). 50 /// </summary> 51 private uint _bufferRegisteredCount; 52 53 /// <summary> 54 /// The count of buffer released (released by the driver side). 55 /// </summary> 56 private uint _bufferReleasedCount; 57 58 /// <summary> 59 /// The released buffer event. 60 /// </summary> 61 private readonly IWritableEvent _bufferEvent; 62 63 /// <summary> 64 /// The session on the device driver. 65 /// </summary> 66 private readonly IHardwareDeviceSession _hardwareDeviceSession; 67 68 /// <summary> 69 /// Max number of buffers that can be registered to the device driver at a time. 70 /// </summary> 71 private readonly uint _bufferRegisteredLimit; 72 73 /// <summary> 74 /// Create a new <see cref="AudioDeviceSession"/>. 75 /// </summary> 76 /// <param name="deviceSession">The device driver session associated</param> 77 /// <param name="bufferEvent">The release buffer event</param> 78 /// <param name="bufferRegisteredLimit">The max number of buffers that can be registered to the device driver at a time</param> 79 public AudioDeviceSession(IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent, uint bufferRegisteredLimit = 4) 80 { 81 _bufferEvent = bufferEvent; 82 _hardwareDeviceSession = deviceSession; 83 _bufferRegisteredLimit = bufferRegisteredLimit; 84 85 _buffers = new AudioBuffer[Constants.AudioDeviceBufferCountMax]; 86 _serverBufferIndex = 0; 87 _hardwareBufferIndex = 0; 88 _releasedBufferIndex = 0; 89 90 _bufferAppendedCount = 0; 91 _bufferRegisteredCount = 0; 92 _bufferReleasedCount = 0; 93 _volume = deviceSession.GetVolume(); 94 _state = AudioDeviceState.Stopped; 95 } 96 97 /// <summary> 98 /// Get the released buffer event. 99 /// </summary> 100 /// <returns>The released buffer event</returns> 101 public IWritableEvent GetBufferEvent() 102 { 103 return _bufferEvent; 104 } 105 106 /// <summary> 107 /// Get the state of the session. 108 /// </summary> 109 /// <returns>The state of the session</returns> 110 public AudioDeviceState GetState() 111 { 112 Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped); 113 114 return _state; 115 } 116 117 /// <summary> 118 /// Get the total buffer count (server + driver + released). 119 /// </summary> 120 /// <returns>Return the total buffer count</returns> 121 private uint GetTotalBufferCount() 122 { 123 uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount; 124 125 Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax); 126 127 return bufferCount; 128 } 129 130 /// <summary> 131 /// Register a new <see cref="AudioBuffer"/> on the server side. 132 /// </summary> 133 /// <param name="buffer">The <see cref="AudioBuffer"/> to register</param> 134 /// <returns>True if the operation succeeded</returns> 135 private bool RegisterBuffer(AudioBuffer buffer) 136 { 137 if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax) 138 { 139 return false; 140 } 141 142 _buffers[_serverBufferIndex] = buffer; 143 _serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; 144 _bufferAppendedCount++; 145 146 return true; 147 } 148 149 /// <summary> 150 /// Flush server buffers to hardware. 151 /// </summary> 152 private void FlushToHardware() 153 { 154 uint bufferToFlushCount = Math.Min(Math.Min(_bufferAppendedCount, 4), _bufferRegisteredLimit - _bufferRegisteredCount); 155 156 AudioBuffer[] buffersToFlush = new AudioBuffer[bufferToFlushCount]; 157 158 uint hardwareBufferIndex = _hardwareBufferIndex; 159 160 for (int i = 0; i < buffersToFlush.Length; i++) 161 { 162 buffersToFlush[i] = _buffers[hardwareBufferIndex]; 163 164 _bufferAppendedCount--; 165 _bufferRegisteredCount++; 166 167 hardwareBufferIndex = (hardwareBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; 168 } 169 170 _hardwareBufferIndex = hardwareBufferIndex; 171 172 for (int i = 0; i < buffersToFlush.Length; i++) 173 { 174 _hardwareDeviceSession.QueueBuffer(buffersToFlush[i]); 175 } 176 } 177 178 /// <summary> 179 /// Get the current index of the <see cref="AudioBuffer"/> playing on the driver side. 180 /// </summary> 181 /// <param name="playingIndex">The output index of the <see cref="AudioBuffer"/> playing on the driver side</param> 182 /// <returns>True if any buffer is playing</returns> 183 private bool TryGetPlayingBufferIndex(out uint playingIndex) 184 { 185 if (_bufferRegisteredCount > 0) 186 { 187 playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; 188 189 return true; 190 } 191 192 playingIndex = 0; 193 194 return false; 195 } 196 197 /// <summary> 198 /// Try to pop the <see cref="AudioBuffer"/> playing on the driver side. 199 /// </summary> 200 /// <param name="buffer">The output <see cref="AudioBuffer"/> playing on the driver side</param> 201 /// <returns>True if any buffer is playing</returns> 202 private bool TryPopPlayingBuffer(out AudioBuffer buffer) 203 { 204 if (_bufferRegisteredCount > 0) 205 { 206 uint bufferIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; 207 208 buffer = _buffers[bufferIndex]; 209 210 _buffers[bufferIndex] = null; 211 212 _bufferRegisteredCount--; 213 214 return true; 215 } 216 217 buffer = null; 218 219 return false; 220 } 221 222 /// <summary> 223 /// Try to pop a <see cref="AudioBuffer"/> released by the driver side. 224 /// </summary> 225 /// <param name="buffer">The output <see cref="AudioBuffer"/> released by the driver side</param> 226 /// <returns>True if any buffer has been released</returns> 227 public bool TryPopReleasedBuffer(out AudioBuffer buffer) 228 { 229 if (_bufferReleasedCount > 0) 230 { 231 uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; 232 233 buffer = _buffers[bufferIndex]; 234 235 _buffers[bufferIndex] = null; 236 237 _bufferReleasedCount--; 238 239 return true; 240 } 241 242 buffer = null; 243 244 return false; 245 } 246 247 /// <summary> 248 /// Release a <see cref="AudioBuffer"/>. 249 /// </summary> 250 /// <param name="buffer">The <see cref="AudioBuffer"/> to release</param> 251 private void ReleaseBuffer(AudioBuffer buffer) 252 { 253 buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds; 254 255 _bufferRegisteredCount--; 256 _bufferReleasedCount++; 257 258 _releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; 259 } 260 261 /// <summary> 262 /// Update the released buffers. 263 /// </summary> 264 /// <param name="updateForStop">True if the session is currently stopping</param> 265 private void UpdateReleaseBuffers(bool updateForStop = false) 266 { 267 bool wasAnyBuffersReleased = false; 268 269 while (TryGetPlayingBufferIndex(out uint playingIndex)) 270 { 271 if (!updateForStop && !_hardwareDeviceSession.WasBufferFullyConsumed(_buffers[playingIndex])) 272 { 273 break; 274 } 275 276 if (updateForStop) 277 { 278 _hardwareDeviceSession.UnregisterBuffer(_buffers[playingIndex]); 279 } 280 281 ReleaseBuffer(_buffers[playingIndex]); 282 283 wasAnyBuffersReleased = true; 284 } 285 286 if (wasAnyBuffersReleased) 287 { 288 _bufferEvent.Signal(); 289 } 290 } 291 292 /// <summary> 293 /// Append a new <see cref="AudioBuffer"/>. 294 /// </summary> 295 /// <param name="buffer">The <see cref="AudioBuffer"/> to append</param> 296 /// <returns>True if the buffer was appended</returns> 297 public bool AppendBuffer(AudioBuffer buffer) 298 { 299 if (_hardwareDeviceSession.RegisterBuffer(buffer)) 300 { 301 if (RegisterBuffer(buffer)) 302 { 303 FlushToHardware(); 304 305 return true; 306 } 307 308 _hardwareDeviceSession.UnregisterBuffer(buffer); 309 } 310 311 return false; 312 } 313 314 public static bool AppendUacBuffer(AudioBuffer buffer, uint handle) 315 { 316 // NOTE: On hardware, there is another RegisterBuffer method taking a handle. 317 // This variant of the call always return false (stubbed?) as a result this logic will never succeed. 318 319 return false; 320 } 321 322 /// <summary> 323 /// Start the audio session. 324 /// </summary> 325 /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns> 326 public ResultCode Start() 327 { 328 if (_state == AudioDeviceState.Started) 329 { 330 return ResultCode.OperationFailed; 331 } 332 333 _hardwareDeviceSession.Start(); 334 335 _state = AudioDeviceState.Started; 336 337 FlushToHardware(); 338 339 _hardwareDeviceSession.SetVolume(_volume); 340 341 return ResultCode.Success; 342 } 343 344 /// <summary> 345 /// Stop the audio session. 346 /// </summary> 347 /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns> 348 public ResultCode Stop() 349 { 350 if (_state == AudioDeviceState.Started) 351 { 352 _hardwareDeviceSession.Stop(); 353 354 UpdateReleaseBuffers(true); 355 356 _state = AudioDeviceState.Stopped; 357 } 358 359 return ResultCode.Success; 360 } 361 362 /// <summary> 363 /// Get the volume of the session. 364 /// </summary> 365 /// <returns>The volume of the session</returns> 366 public float GetVolume() 367 { 368 return _hardwareDeviceSession.GetVolume(); 369 } 370 371 /// <summary> 372 /// Set the volume of the session. 373 /// </summary> 374 /// <param name="volume">The new volume to set</param> 375 public void SetVolume(float volume) 376 { 377 _volume = volume; 378 379 if (_state == AudioDeviceState.Started) 380 { 381 _hardwareDeviceSession.SetVolume(volume); 382 } 383 } 384 385 /// <summary> 386 /// Get the count of buffer currently in use (server + driver side). 387 /// </summary> 388 /// <returns>The count of buffer currently in use</returns> 389 public uint GetBufferCount() 390 { 391 return _bufferAppendedCount + _bufferRegisteredCount; 392 } 393 394 /// <summary> 395 /// Check if a buffer is present. 396 /// </summary> 397 /// <param name="bufferTag">The unique tag of the buffer</param> 398 /// <returns>Return true if a buffer is present</returns> 399 public bool ContainsBuffer(ulong bufferTag) 400 { 401 uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; 402 403 uint totalBufferCount = GetTotalBufferCount(); 404 405 for (int i = 0; i < totalBufferCount; i++) 406 { 407 if (_buffers[bufferIndex].BufferTag == bufferTag) 408 { 409 return true; 410 } 411 412 bufferIndex = (bufferIndex + 1) % Constants.AudioDeviceBufferCountMax; 413 } 414 415 return false; 416 } 417 418 /// <summary> 419 /// Get the count of sample played in this session. 420 /// </summary> 421 /// <returns>The count of sample played in this session</returns> 422 public ulong GetPlayedSampleCount() 423 { 424 if (_state == AudioDeviceState.Stopped) 425 { 426 return 0; 427 } 428 429 return _hardwareDeviceSession.GetPlayedSampleCount(); 430 } 431 432 /// <summary> 433 /// Flush all buffers to the initial state. 434 /// </summary> 435 /// <returns>True if any buffer was flushed</returns> 436 public bool FlushBuffers() 437 { 438 if (_state == AudioDeviceState.Stopped) 439 { 440 return false; 441 } 442 443 uint bufferCount = GetBufferCount(); 444 445 while (TryPopReleasedBuffer(out AudioBuffer buffer)) 446 { 447 _hardwareDeviceSession.UnregisterBuffer(buffer); 448 } 449 450 while (TryPopPlayingBuffer(out AudioBuffer buffer)) 451 { 452 _hardwareDeviceSession.UnregisterBuffer(buffer); 453 } 454 455 if (_bufferRegisteredCount == 0 || (_bufferReleasedCount + _bufferAppendedCount) > Constants.AudioDeviceBufferCountMax) 456 { 457 return false; 458 } 459 460 _bufferReleasedCount += _bufferAppendedCount; 461 _releasedBufferIndex = (_releasedBufferIndex + _bufferAppendedCount) % Constants.AudioDeviceBufferCountMax; 462 _bufferAppendedCount = 0; 463 _hardwareBufferIndex = _serverBufferIndex; 464 465 if (bufferCount > 0) 466 { 467 _bufferEvent.Signal(); 468 } 469 470 return true; 471 } 472 473 /// <summary> 474 /// Update the session. 475 /// </summary> 476 public void Update() 477 { 478 if (_state == AudioDeviceState.Started) 479 { 480 UpdateReleaseBuffers(); 481 FlushToHardware(); 482 } 483 } 484 485 public void Dispose() 486 { 487 Dispose(true); 488 } 489 490 protected virtual void Dispose(bool disposing) 491 { 492 if (disposing) 493 { 494 // Tell the hardware session that we are ending. 495 _hardwareDeviceSession.PrepareToClose(); 496 497 // Unregister all buffers 498 499 while (TryPopReleasedBuffer(out AudioBuffer buffer)) 500 { 501 _hardwareDeviceSession.UnregisterBuffer(buffer); 502 } 503 504 while (TryPopPlayingBuffer(out AudioBuffer buffer)) 505 { 506 _hardwareDeviceSession.UnregisterBuffer(buffer); 507 } 508 509 // Finally dispose hardware session. 510 _hardwareDeviceSession.Dispose(); 511 512 _bufferEvent.Signal(); 513 } 514 } 515 } 516 }