SoundIoAudioTrack.cs 24 KB

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