SoundIoAudioTrack.cs 21 KB

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