AudioOutputSystem.cs
1 using Ryujinx.Audio.Common; 2 using Ryujinx.Audio.Integration; 3 using System; 4 using System.Threading; 5 6 namespace Ryujinx.Audio.Output 7 { 8 /// <summary> 9 /// Audio output system. 10 /// </summary> 11 public class AudioOutputSystem : IDisposable 12 { 13 /// <summary> 14 /// The session id associated to the <see cref="AudioOutputSystem"/>. 15 /// </summary> 16 private int _sessionId; 17 18 /// <summary> 19 /// The session the <see cref="AudioOutputSystem"/>. 20 /// </summary> 21 private readonly AudioDeviceSession _session; 22 23 /// <summary> 24 /// The target device name of the <see cref="AudioOutputSystem"/>. 25 /// </summary> 26 public string DeviceName { get; private set; } 27 28 /// <summary> 29 /// The target sample rate of the <see cref="AudioOutputSystem"/>. 30 /// </summary> 31 public uint SampleRate { get; private set; } 32 33 /// <summary> 34 /// The target channel count of the <see cref="AudioOutputSystem"/>. 35 /// </summary> 36 public uint ChannelCount { get; private set; } 37 38 /// <summary> 39 /// The target sample format of the <see cref="AudioOutputSystem"/>. 40 /// </summary> 41 public SampleFormat SampleFormat { get; private set; } 42 43 /// <summary> 44 /// The <see cref="AudioOutputManager"/> owning this. 45 /// </summary> 46 private readonly AudioOutputManager _manager; 47 48 /// <summary> 49 /// THe lock of the parent. 50 /// </summary> 51 private readonly object _parentLock; 52 53 /// <summary> 54 /// The dispose state. 55 /// </summary> 56 private int _disposeState; 57 58 /// <summary> 59 /// Create a new <see cref="AudioOutputSystem"/>. 60 /// </summary> 61 /// <param name="manager">The manager instance</param> 62 /// <param name="parentLock">The lock of the manager</param> 63 /// <param name="deviceSession">The hardware device session</param> 64 /// <param name="bufferEvent">The buffer release event of the audio output</param> 65 public AudioOutputSystem(AudioOutputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) 66 { 67 _manager = manager; 68 _parentLock = parentLock; 69 _session = new AudioDeviceSession(deviceSession, bufferEvent); 70 } 71 72 /// <summary> 73 /// Get the default device name on the system. 74 /// </summary> 75 /// <returns>The default device name on the system.</returns> 76 private static string GetDeviceDefaultName() 77 { 78 return Constants.DefaultDeviceOutputName; 79 } 80 81 /// <summary> 82 /// Check if a given configuration and device name is valid on the system. 83 /// </summary> 84 /// <param name="configuration">The configuration to check.</param> 85 /// <param name="deviceName">The device name to check.</param> 86 /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns> 87 private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) 88 { 89 if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) 90 { 91 return ResultCode.DeviceNotFound; 92 } 93 94 if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) 95 { 96 return ResultCode.UnsupportedSampleRate; 97 } 98 99 if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) 100 { 101 return ResultCode.UnsupportedChannelConfiguration; 102 } 103 104 return ResultCode.Success; 105 } 106 107 /// <summary> 108 /// Get the released buffer event. 109 /// </summary> 110 /// <returns>The released buffer event</returns> 111 public IWritableEvent RegisterBufferEvent() 112 { 113 lock (_parentLock) 114 { 115 return _session.GetBufferEvent(); 116 } 117 } 118 119 /// <summary> 120 /// Update the <see cref="AudioOutputSystem"/>. 121 /// </summary> 122 public void Update() 123 { 124 lock (_parentLock) 125 { 126 _session.Update(); 127 } 128 } 129 130 /// <summary> 131 /// Get the id of this session. 132 /// </summary> 133 /// <returns>The id of this session</returns> 134 public int GetSessionId() 135 { 136 return _sessionId; 137 } 138 139 /// <summary> 140 /// Initialize the <see cref="AudioOutputSystem"/>. 141 /// </summary> 142 /// <param name="inputDeviceName">The input device name wanted by the user</param> 143 /// <param name="sampleFormat">The sample format to use</param> 144 /// <param name="parameter">The user configuration</param> 145 /// <param name="sessionId">The session id associated to this <see cref="AudioOutputSystem"/></param> 146 /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns> 147 public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) 148 { 149 _sessionId = sessionId; 150 151 ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); 152 153 if (result == ResultCode.Success) 154 { 155 if (inputDeviceName.Length == 0) 156 { 157 DeviceName = GetDeviceDefaultName(); 158 } 159 else 160 { 161 DeviceName = inputDeviceName; 162 } 163 164 if (parameter.ChannelCount == 6) 165 { 166 ChannelCount = 6; 167 } 168 else 169 { 170 ChannelCount = 2; 171 } 172 173 SampleFormat = sampleFormat; 174 SampleRate = Constants.TargetSampleRate; 175 } 176 177 return result; 178 } 179 180 /// <summary> 181 /// Append a new audio buffer to the audio output. 182 /// </summary> 183 /// <param name="bufferTag">The unique tag of this buffer.</param> 184 /// <param name="userBuffer">The buffer informations.</param> 185 /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns> 186 public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) 187 { 188 lock (_parentLock) 189 { 190 AudioBuffer buffer = new() 191 { 192 BufferTag = bufferTag, 193 DataPointer = userBuffer.Data, 194 DataSize = userBuffer.DataSize, 195 }; 196 197 if (_session.AppendBuffer(buffer)) 198 { 199 return ResultCode.Success; 200 } 201 202 return ResultCode.BufferRingFull; 203 } 204 } 205 206 /// <summary> 207 /// Get the release buffers. 208 /// </summary> 209 /// <param name="releasedBuffers">The buffer to write the release buffers</param> 210 /// <param name="releasedCount">The count of released buffers</param> 211 /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns> 212 public ResultCode GetReleasedBuffer(Span<ulong> releasedBuffers, out uint releasedCount) 213 { 214 releasedCount = 0; 215 216 // Ensure that the first entry is set to zero if no entries are returned. 217 if (releasedBuffers.Length > 0) 218 { 219 releasedBuffers[0] = 0; 220 } 221 222 lock (_parentLock) 223 { 224 for (int i = 0; i < releasedBuffers.Length; i++) 225 { 226 if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) 227 { 228 break; 229 } 230 231 releasedBuffers[i] = buffer.BufferTag; 232 releasedCount++; 233 } 234 } 235 236 return ResultCode.Success; 237 } 238 239 /// <summary> 240 /// Get the current state of the <see cref="AudioOutputSystem"/>. 241 /// </summary> 242 /// <returns>Return the curent sta\te of the <see cref="AudioOutputSystem"/></returns> 243 /// <returns></returns> 244 public AudioDeviceState GetState() 245 { 246 lock (_parentLock) 247 { 248 return _session.GetState(); 249 } 250 } 251 252 /// <summary> 253 /// Start the audio session. 254 /// </summary> 255 /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns> 256 public ResultCode Start() 257 { 258 lock (_parentLock) 259 { 260 return _session.Start(); 261 } 262 } 263 264 /// <summary> 265 /// Stop the audio session. 266 /// </summary> 267 /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns> 268 public ResultCode Stop() 269 { 270 lock (_parentLock) 271 { 272 return _session.Stop(); 273 } 274 } 275 276 /// <summary> 277 /// Get the volume of the session. 278 /// </summary> 279 /// <returns>The volume of the session</returns> 280 public float GetVolume() 281 { 282 lock (_parentLock) 283 { 284 return _session.GetVolume(); 285 } 286 } 287 288 /// <summary> 289 /// Set the volume of the session. 290 /// </summary> 291 /// <param name="volume">The new volume to set</param> 292 public void SetVolume(float volume) 293 { 294 lock (_parentLock) 295 { 296 _session.SetVolume(volume); 297 } 298 } 299 300 /// <summary> 301 /// Get the count of buffer currently in use (server + driver side). 302 /// </summary> 303 /// <returns>The count of buffer currently in use</returns> 304 public uint GetBufferCount() 305 { 306 lock (_parentLock) 307 { 308 return _session.GetBufferCount(); 309 } 310 } 311 312 /// <summary> 313 /// Check if a buffer is present. 314 /// </summary> 315 /// <param name="bufferTag">The unique tag of the buffer</param> 316 /// <returns>Return true if a buffer is present</returns> 317 public bool ContainsBuffer(ulong bufferTag) 318 { 319 lock (_parentLock) 320 { 321 return _session.ContainsBuffer(bufferTag); 322 } 323 } 324 325 /// <summary> 326 /// Get the count of sample played in this session. 327 /// </summary> 328 /// <returns>The count of sample played in this session</returns> 329 public ulong GetPlayedSampleCount() 330 { 331 lock (_parentLock) 332 { 333 return _session.GetPlayedSampleCount(); 334 } 335 } 336 337 /// <summary> 338 /// Flush all buffers to the initial state. 339 /// </summary> 340 /// <returns>True if any buffers was flushed</returns> 341 public bool FlushBuffers() 342 { 343 lock (_parentLock) 344 { 345 return _session.FlushBuffers(); 346 } 347 } 348 349 public void Dispose() 350 { 351 GC.SuppressFinalize(this); 352 353 if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) 354 { 355 Dispose(true); 356 } 357 } 358 359 protected virtual void Dispose(bool disposing) 360 { 361 if (disposing) 362 { 363 _session.Dispose(); 364 365 _manager.Unregister(this); 366 } 367 } 368 } 369 }