SoundIoAudioTrack.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. using SoundIOSharp;
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.Linq;
  5. using System.Runtime.CompilerServices;
  6. using System.Runtime.InteropServices;
  7. namespace Ryujinx.Audio.SoundIo
  8. {
  9. internal class SoundIoAudioTrack : IDisposable
  10. {
  11. /// <summary>
  12. /// The audio track ring buffer
  13. /// </summary>
  14. private SoundIoRingBuffer m_Buffer;
  15. /// <summary>
  16. /// A list of buffers currently pending writeback to the audio backend
  17. /// </summary>
  18. private ConcurrentQueue<SoundIoBuffer> m_ReservedBuffers;
  19. /// <summary>
  20. /// Occurs when a buffer has been released by the audio backend
  21. /// </summary>
  22. private event ReleaseCallback BufferReleased;
  23. /// <summary>
  24. /// The track ID of this <see cref="SoundIoAudioTrack"/>
  25. /// </summary>
  26. public int TrackID { get; private set; }
  27. /// <summary>
  28. /// The current playback state
  29. /// </summary>
  30. public PlaybackState State { get; private set; }
  31. /// <summary>
  32. /// The <see cref="SoundIO"/> audio context this track belongs to
  33. /// </summary>
  34. public SoundIO AudioContext { get; private set; }
  35. /// <summary>
  36. /// The <see cref="SoundIODevice"/> this track belongs to
  37. /// </summary>
  38. public SoundIODevice AudioDevice { get; private set; }
  39. /// <summary>
  40. /// The audio output stream of this track
  41. /// </summary>
  42. public SoundIOOutStream AudioStream { get; private set; }
  43. /// <summary>
  44. /// Released buffers the track is no longer holding
  45. /// </summary>
  46. public ConcurrentQueue<long> ReleasedBuffers { get; private set; }
  47. private int _hardwareChannels;
  48. private int _virtualChannels;
  49. /// <summary>
  50. /// Constructs a new instance of a <see cref="SoundIoAudioTrack"/>
  51. /// </summary>
  52. /// <param name="trackId">The track ID</param>
  53. /// <param name="audioContext">The SoundIO audio context</param>
  54. /// <param name="audioDevice">The SoundIO audio device</param>
  55. public SoundIoAudioTrack(int trackId, SoundIO audioContext, SoundIODevice audioDevice)
  56. {
  57. TrackID = trackId;
  58. AudioContext = audioContext;
  59. AudioDevice = audioDevice;
  60. State = PlaybackState.Stopped;
  61. ReleasedBuffers = new ConcurrentQueue<long>();
  62. m_Buffer = new SoundIoRingBuffer();
  63. m_ReservedBuffers = new ConcurrentQueue<SoundIoBuffer>();
  64. }
  65. /// <summary>
  66. /// Opens the audio track with the specified parameters
  67. /// </summary>
  68. /// <param name="sampleRate">The requested sample rate of the track</param>
  69. /// <param name="hardwareChannels">The requested hardware channels</param>
  70. /// <param name="virtualChannels">The requested virtual channels</param>
  71. /// <param name="callback">A <see cref="ReleaseCallback" /> that represents the delegate to invoke when a buffer has been released by the audio track</param>
  72. /// <param name="format">The requested sample format of the track</param>
  73. public void Open(
  74. int sampleRate,
  75. int hardwareChannels,
  76. int virtualChannels,
  77. ReleaseCallback callback,
  78. SoundIOFormat format = SoundIOFormat.S16LE)
  79. {
  80. // Close any existing audio streams
  81. if (AudioStream != null)
  82. {
  83. Close();
  84. }
  85. if (!AudioDevice.SupportsSampleRate(sampleRate))
  86. {
  87. throw new InvalidOperationException($"This sound device does not support a sample rate of {sampleRate}Hz");
  88. }
  89. if (!AudioDevice.SupportsFormat(format))
  90. {
  91. throw new InvalidOperationException($"This sound device does not support SoundIOFormat.{Enum.GetName(typeof(SoundIOFormat), format)}");
  92. }
  93. if (!AudioDevice.SupportsChannelCount(hardwareChannels))
  94. {
  95. throw new InvalidOperationException($"This sound device does not support channel count {hardwareChannels}");
  96. }
  97. _hardwareChannels = hardwareChannels;
  98. _virtualChannels = virtualChannels;
  99. AudioStream = AudioDevice.CreateOutStream();
  100. AudioStream.Name = $"SwitchAudioTrack_{TrackID}";
  101. AudioStream.Layout = SoundIOChannelLayout.GetDefault(hardwareChannels);
  102. AudioStream.Format = format;
  103. AudioStream.SampleRate = sampleRate;
  104. AudioStream.WriteCallback = WriteCallback;
  105. BufferReleased += callback;
  106. AudioStream.Open();
  107. }
  108. /// <summary>
  109. /// This callback occurs when the sound device is ready to buffer more frames
  110. /// </summary>
  111. /// <param name="minFrameCount">The minimum amount of frames expected by the audio backend</param>
  112. /// <param name="maxFrameCount">The maximum amount of frames that can be written to the audio backend</param>
  113. private unsafe void WriteCallback(int minFrameCount, int maxFrameCount)
  114. {
  115. int bytesPerFrame = AudioStream.BytesPerFrame;
  116. uint bytesPerSample = (uint)AudioStream.BytesPerSample;
  117. int bufferedFrames = m_Buffer.Length / bytesPerFrame;
  118. long bufferedSamples = m_Buffer.Length / bytesPerSample;
  119. int frameCount = Math.Min(bufferedFrames, maxFrameCount);
  120. if (frameCount == 0)
  121. {
  122. return;
  123. }
  124. SoundIOChannelAreas areas = AudioStream.BeginWrite(ref frameCount);
  125. int channelCount = areas.ChannelCount;
  126. byte[] samples = new byte[frameCount * bytesPerFrame];
  127. m_Buffer.Read(samples, 0, samples.Length);
  128. // This is a huge ugly block of code, but we save
  129. // a significant amount of time over the generic
  130. // loop that handles other channel counts.
  131. // Mono
  132. if (channelCount == 1)
  133. {
  134. SoundIOChannelArea area = areas.GetArea(0);
  135. fixed (byte* srcptr = samples)
  136. {
  137. if (bytesPerSample == 1)
  138. {
  139. for (int frame = 0; frame < frameCount; frame++)
  140. {
  141. ((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame];
  142. area.Pointer += area.Step;
  143. }
  144. }
  145. else if (bytesPerSample == 2)
  146. {
  147. for (int frame = 0; frame < frameCount; frame++)
  148. {
  149. ((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1];
  150. area.Pointer += area.Step;
  151. }
  152. }
  153. else if (bytesPerSample == 4)
  154. {
  155. for (int frame = 0; frame < frameCount; frame++)
  156. {
  157. ((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2];
  158. area.Pointer += area.Step;
  159. }
  160. }
  161. else
  162. {
  163. for (int frame = 0; frame < frameCount; frame++)
  164. {
  165. Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample);
  166. area.Pointer += area.Step;
  167. }
  168. }
  169. }
  170. }
  171. // Stereo
  172. else if (channelCount == 2)
  173. {
  174. SoundIOChannelArea area1 = areas.GetArea(0);
  175. SoundIOChannelArea area2 = areas.GetArea(1);
  176. fixed (byte* srcptr = samples)
  177. {
  178. if (bytesPerSample == 1)
  179. {
  180. for (int frame = 0; frame < frameCount; frame++)
  181. {
  182. // Channel 1
  183. ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0];
  184. // Channel 2
  185. ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1];
  186. area1.Pointer += area1.Step;
  187. area2.Pointer += area2.Step;
  188. }
  189. }
  190. else if (bytesPerSample == 2)
  191. {
  192. for (int frame = 0; frame < frameCount; frame++)
  193. {
  194. // Channel 1
  195. ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0];
  196. // Channel 2
  197. ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1];
  198. area1.Pointer += area1.Step;
  199. area2.Pointer += area2.Step;
  200. }
  201. }
  202. else if (bytesPerSample == 4)
  203. {
  204. for (int frame = 0; frame < frameCount; frame++)
  205. {
  206. // Channel 1
  207. ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0];
  208. // Channel 2
  209. ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1];
  210. area1.Pointer += area1.Step;
  211. area2.Pointer += area2.Step;
  212. }
  213. }
  214. else
  215. {
  216. for (int frame = 0; frame < frameCount; frame++)
  217. {
  218. // Channel 1
  219. Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample);
  220. // Channel 2
  221. Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample);
  222. area1.Pointer += area1.Step;
  223. area2.Pointer += area2.Step;
  224. }
  225. }
  226. }
  227. }
  228. // Surround
  229. else if (channelCount == 6)
  230. {
  231. SoundIOChannelArea area1 = areas.GetArea(0);
  232. SoundIOChannelArea area2 = areas.GetArea(1);
  233. SoundIOChannelArea area3 = areas.GetArea(2);
  234. SoundIOChannelArea area4 = areas.GetArea(3);
  235. SoundIOChannelArea area5 = areas.GetArea(4);
  236. SoundIOChannelArea area6 = areas.GetArea(5);
  237. fixed (byte* srcptr = samples)
  238. {
  239. if (bytesPerSample == 1)
  240. {
  241. for (int frame = 0; frame < frameCount; frame++)
  242. {
  243. // Channel 1
  244. ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0];
  245. // Channel 2
  246. ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1];
  247. // Channel 3
  248. ((byte*)area3.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 2];
  249. // Channel 4
  250. ((byte*)area4.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 3];
  251. // Channel 5
  252. ((byte*)area5.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 4];
  253. // Channel 6
  254. ((byte*)area6.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 5];
  255. area1.Pointer += area1.Step;
  256. area2.Pointer += area2.Step;
  257. area3.Pointer += area3.Step;
  258. area4.Pointer += area4.Step;
  259. area5.Pointer += area5.Step;
  260. area6.Pointer += area6.Step;
  261. }
  262. }
  263. else if (bytesPerSample == 2)
  264. {
  265. for (int frame = 0; frame < frameCount; frame++)
  266. {
  267. // Channel 1
  268. ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0];
  269. // Channel 2
  270. ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1];
  271. // Channel 3
  272. ((short*)area3.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 2];
  273. // Channel 4
  274. ((short*)area4.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 3];
  275. // Channel 5
  276. ((short*)area5.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 4];
  277. // Channel 6
  278. ((short*)area6.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 5];
  279. area1.Pointer += area1.Step;
  280. area2.Pointer += area2.Step;
  281. area3.Pointer += area3.Step;
  282. area4.Pointer += area4.Step;
  283. area5.Pointer += area5.Step;
  284. area6.Pointer += area6.Step;
  285. }
  286. }
  287. else if (bytesPerSample == 4)
  288. {
  289. for (int frame = 0; frame < frameCount; frame++)
  290. {
  291. // Channel 1
  292. ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0];
  293. // Channel 2
  294. ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1];
  295. // Channel 3
  296. ((int*)area3.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 2];
  297. // Channel 4
  298. ((int*)area4.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 3];
  299. // Channel 5
  300. ((int*)area5.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 4];
  301. // Channel 6
  302. ((int*)area6.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 5];
  303. area1.Pointer += area1.Step;
  304. area2.Pointer += area2.Step;
  305. area3.Pointer += area3.Step;
  306. area4.Pointer += area4.Step;
  307. area5.Pointer += area5.Step;
  308. area6.Pointer += area6.Step;
  309. }
  310. }
  311. else
  312. {
  313. for (int frame = 0; frame < frameCount; frame++)
  314. {
  315. // Channel 1
  316. Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample);
  317. // Channel 2
  318. Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample);
  319. // Channel 3
  320. Unsafe.CopyBlockUnaligned((byte*)area3.Pointer, srcptr + (frame * bytesPerFrame) + (2 * bytesPerSample), bytesPerSample);
  321. // Channel 4
  322. Unsafe.CopyBlockUnaligned((byte*)area4.Pointer, srcptr + (frame * bytesPerFrame) + (3 * bytesPerSample), bytesPerSample);
  323. // Channel 5
  324. Unsafe.CopyBlockUnaligned((byte*)area5.Pointer, srcptr + (frame * bytesPerFrame) + (4 * bytesPerSample), bytesPerSample);
  325. // Channel 6
  326. Unsafe.CopyBlockUnaligned((byte*)area6.Pointer, srcptr + (frame * bytesPerFrame) + (5 * bytesPerSample), bytesPerSample);
  327. area1.Pointer += area1.Step;
  328. area2.Pointer += area2.Step;
  329. area3.Pointer += area3.Step;
  330. area4.Pointer += area4.Step;
  331. area5.Pointer += area5.Step;
  332. area6.Pointer += area6.Step;
  333. }
  334. }
  335. }
  336. }
  337. // Every other channel count
  338. else
  339. {
  340. SoundIOChannelArea[] channels = new SoundIOChannelArea[channelCount];
  341. // Obtain the channel area for each channel
  342. for (int i = 0; i < channelCount; i++)
  343. {
  344. channels[i] = areas.GetArea(i);
  345. }
  346. fixed (byte* srcptr = samples)
  347. {
  348. for (int frame = 0; frame < frameCount; frame++)
  349. for (int channel = 0; channel < areas.ChannelCount; channel++)
  350. {
  351. // Copy channel by channel, frame by frame. This is slow!
  352. Unsafe.CopyBlockUnaligned((byte*)channels[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample);
  353. channels[channel].Pointer += channels[channel].Step;
  354. }
  355. }
  356. }
  357. AudioStream.EndWrite();
  358. UpdateReleasedBuffers(samples.Length);
  359. }
  360. /// <summary>
  361. /// Releases any buffers that have been fully written to the output device
  362. /// </summary>
  363. /// <param name="bytesRead">The amount of bytes written in the last device write</param>
  364. private void UpdateReleasedBuffers(int bytesRead)
  365. {
  366. bool bufferReleased = false;
  367. while (bytesRead > 0)
  368. {
  369. if (m_ReservedBuffers.TryPeek(out SoundIoBuffer buffer))
  370. {
  371. if (buffer.Length > bytesRead)
  372. {
  373. buffer.Length -= bytesRead;
  374. bytesRead = 0;
  375. }
  376. else
  377. {
  378. bufferReleased = true;
  379. bytesRead -= buffer.Length;
  380. m_ReservedBuffers.TryDequeue(out buffer);
  381. ReleasedBuffers.Enqueue(buffer.Tag);
  382. }
  383. }
  384. }
  385. if (bufferReleased)
  386. {
  387. OnBufferReleased();
  388. }
  389. }
  390. /// <summary>
  391. /// Starts audio playback
  392. /// </summary>
  393. public void Start()
  394. {
  395. if (AudioStream == null)
  396. {
  397. return;
  398. }
  399. AudioStream.Start();
  400. AudioStream.Pause(false);
  401. AudioContext.FlushEvents();
  402. State = PlaybackState.Playing;
  403. }
  404. /// <summary>
  405. /// Stops audio playback
  406. /// </summary>
  407. public void Stop()
  408. {
  409. if (AudioStream == null)
  410. {
  411. return;
  412. }
  413. AudioStream.Pause(true);
  414. AudioContext.FlushEvents();
  415. State = PlaybackState.Stopped;
  416. }
  417. /// <summary>
  418. /// Appends an audio buffer to the tracks internal ring buffer
  419. /// </summary>
  420. /// <typeparam name="T">The audio sample type</typeparam>
  421. /// <param name="bufferTag">The unqiue tag of the buffer being appended</param>
  422. /// <param name="buffer">The buffer to append</param>
  423. public void AppendBuffer<T>(long bufferTag, T[] buffer) where T: struct
  424. {
  425. if (AudioStream == null)
  426. {
  427. return;
  428. }
  429. int sampleSize = Unsafe.SizeOf<T>();
  430. int targetSize = sampleSize * buffer.Length;
  431. // Do we need to downmix?
  432. if (_hardwareChannels != _virtualChannels)
  433. {
  434. if (sampleSize != sizeof(short))
  435. {
  436. throw new NotImplementedException("Downmixing formats other than PCM16 is not supported!");
  437. }
  438. short[] downmixedBuffer;
  439. ReadOnlySpan<short> bufferPCM16 = MemoryMarshal.Cast<T, short>(buffer);
  440. if (_virtualChannels == 6)
  441. {
  442. downmixedBuffer = Downmixing.DownMixSurroundToStereo(bufferPCM16);
  443. if (_hardwareChannels == 1)
  444. {
  445. downmixedBuffer = Downmixing.DownMixStereoToMono(downmixedBuffer);
  446. }
  447. }
  448. else if (_virtualChannels == 2)
  449. {
  450. downmixedBuffer = Downmixing.DownMixStereoToMono(bufferPCM16);
  451. }
  452. else
  453. {
  454. throw new NotImplementedException($"Downmixing from {_virtualChannels} to {_hardwareChannels} not implemented!");
  455. }
  456. targetSize = sampleSize * downmixedBuffer.Length;
  457. // Copy the memory to our ring buffer
  458. m_Buffer.Write(downmixedBuffer, 0, targetSize);
  459. // Keep track of "buffered" buffers
  460. m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, targetSize));
  461. }
  462. else
  463. {
  464. // Copy the memory to our ring buffer
  465. m_Buffer.Write(buffer, 0, targetSize);
  466. // Keep track of "buffered" buffers
  467. m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, targetSize));
  468. }
  469. }
  470. /// <summary>
  471. /// Returns a value indicating whether the specified buffer is currently reserved by the track
  472. /// </summary>
  473. /// <param name="bufferTag">The buffer tag to check</param>
  474. public bool ContainsBuffer(long bufferTag)
  475. {
  476. return m_ReservedBuffers.Any(x => x.Tag == bufferTag);
  477. }
  478. /// <summary>
  479. /// Closes the <see cref="SoundIoAudioTrack"/>
  480. /// </summary>
  481. public void Close()
  482. {
  483. if (AudioStream != null)
  484. {
  485. AudioStream.Pause(true);
  486. AudioStream.Dispose();
  487. }
  488. m_Buffer.Clear();
  489. OnBufferReleased();
  490. ReleasedBuffers.Clear();
  491. State = PlaybackState.Stopped;
  492. AudioStream = null;
  493. BufferReleased = null;
  494. }
  495. private void OnBufferReleased()
  496. {
  497. BufferReleased?.Invoke();
  498. }
  499. /// <summary>
  500. /// Releases the unmanaged resources used by the <see cref="SoundIoAudioTrack" />
  501. /// </summary>
  502. public void Dispose()
  503. {
  504. Close();
  505. }
  506. ~SoundIoAudioTrack()
  507. {
  508. Dispose();
  509. }
  510. }
  511. }