AlsaSound.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. 
  2. // Use -lasound for linking to the winmm library in GCC/G++
  3. // Install on Arch: sudo pacman -S libasound-dev
  4. // Install on Debian: sudo apt-get install libasound-dev
  5. #include "../DFPSR/api/soundAPI.h"
  6. #include "../DFPSR/base/simd.h"
  7. #include <alsa/asoundlib.h>
  8. namespace dsr {
  9. static snd_pcm_t *pcm = nullptr;
  10. static int bufferElements = 0;
  11. static Buffer outputBuffer, floatBuffer;
  12. static SafePointer<int16_t> outputData;
  13. static SafePointer<float> floatData;
  14. static void allocateBuffers(int neededElements) {
  15. outputBuffer = buffer_create(neededElements * sizeof(int16_t));
  16. floatBuffer = buffer_create(neededElements * sizeof(float));
  17. outputData = buffer_getSafeData<int16_t>(outputBuffer, "Output data");
  18. floatData = buffer_getSafeData<float>(floatBuffer, "Output data");
  19. bufferElements = neededElements;
  20. }
  21. static void terminateSound() {
  22. if (pcm) {
  23. snd_pcm_drop(pcm);
  24. snd_pcm_drain(pcm);
  25. snd_pcm_close(pcm);
  26. pcm = nullptr;
  27. }
  28. }
  29. bool sound_streamToSpeakers(int32_t channels, int32_t sampleRate, std::function<bool(SafePointer<float> data, int32_t length)> soundOutput) {
  30. int errorCode;
  31. if ((errorCode = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
  32. terminateSound();
  33. sendWarning(U"Cannot open sound device. (", snd_strerror(errorCode), U")\n");
  34. return false;
  35. }
  36. snd_pcm_hw_params_t *hardwareParameters;
  37. snd_pcm_hw_params_alloca(&hardwareParameters);
  38. snd_pcm_hw_params_any(pcm, hardwareParameters);
  39. if ((errorCode = snd_pcm_hw_params_set_access(pcm, hardwareParameters, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
  40. terminateSound();
  41. sendWarning(U"Failed to select interleaved sound. (", snd_strerror(errorCode), U")\n");
  42. return false;
  43. }
  44. if ((errorCode = snd_pcm_hw_params_set_format(pcm, hardwareParameters, SND_PCM_FORMAT_S16_LE)) < 0) {
  45. terminateSound();
  46. sendWarning(U"Failed to select sound format. (", snd_strerror(errorCode), U")\n");
  47. return false;
  48. }
  49. if ((errorCode = snd_pcm_hw_params_set_channels(pcm, hardwareParameters, channels)) < 0) {
  50. terminateSound();
  51. sendWarning(U"Failed to select channel count. (", snd_strerror(errorCode), U")\n");
  52. return false;
  53. }
  54. if ((errorCode = snd_pcm_hw_params_set_buffer_size(pcm, hardwareParameters, 2048)) < 0) {
  55. terminateSound();
  56. sendWarning(U"Failed to select buffer size. (", snd_strerror(errorCode), U")\n");
  57. return false;
  58. }
  59. uint rate = sampleRate;
  60. if ((errorCode = snd_pcm_hw_params_set_rate_near(pcm, hardwareParameters, &rate, 0)) < 0) {
  61. terminateSound();
  62. sendWarning(U"Failed to select approximate sample rate. (", snd_strerror(errorCode), U")\n");
  63. return false;
  64. }
  65. if ((errorCode = snd_pcm_hw_params(pcm, hardwareParameters)) < 0) {
  66. terminateSound();
  67. sendWarning(U"Failed to select hardware parameters. (", snd_strerror(errorCode), U")\n");
  68. return false;
  69. }
  70. // Allocate a buffer for sending data to speakers
  71. snd_pcm_uframes_t samplesPerChannel;
  72. snd_pcm_hw_params_get_period_size(hardwareParameters, &samplesPerChannel, 0);
  73. // Allocate target buffers
  74. int totalSamples = samplesPerChannel * channels;
  75. allocateBuffers(totalSamples);
  76. while (true) {
  77. safeMemorySet(floatData, 0, totalSamples * sizeof(float));
  78. bool keepRunning = soundOutput(floatData, samplesPerChannel);
  79. // Convert to target format so that the sound can be played
  80. for (uint32_t t = 0; t < samplesPerChannel * channels; t+=8) {
  81. // SIMD vectorized sound conversion with scaling and clamping to signed 16-bit integers.
  82. F32x4 lowerFloats = F32x4::readAligned(floatData + t, "sound_streamToSpeakers: Reading lower floats");
  83. F32x4 upperFloats = F32x4::readAligned(floatData + t + 4, "sound_streamToSpeakers: Reading upper floats");
  84. I32x4 lowerInts = truncateToI32(clamp(F32x4(-32768.0f), lowerFloats * 32767.0f, F32x4(32767.0f)));
  85. I32x4 upperInts = truncateToI32(clamp(F32x4(-32768.0f), upperFloats * 32767.0f, F32x4(32767.0f)));
  86. // TODO: Create I16x8 SIMD vectors for processing sound as 16-bit integers?
  87. // Or just move unzip into simd.h with a fallback solution and remove simdExtra.h.
  88. // Or just implement reading and writing of 16-bit signed integers using multiple SIMD registers or smaller memory regions.
  89. IVector4D lower = lowerInts.get();
  90. IVector4D upper = upperInts.get();
  91. outputData[t+0] = (int16_t)lower.x;
  92. outputData[t+1] = (int16_t)lower.y;
  93. outputData[t+2] = (int16_t)lower.z;
  94. outputData[t+3] = (int16_t)lower.w;
  95. outputData[t+4] = (int16_t)upper.x;
  96. outputData[t+5] = (int16_t)upper.y;
  97. outputData[t+6] = (int16_t)upper.z;
  98. outputData[t+7] = (int16_t)upper.w;
  99. }
  100. errorCode = snd_pcm_writei(pcm, outputData.getUnsafe(), samplesPerChannel);
  101. if (errorCode == -EPIPE) {
  102. // Came too late! Not enough written samples to play.
  103. snd_pcm_prepare(pcm);
  104. } else if (errorCode < 0) {
  105. terminateSound();
  106. throwError(U"Failed writing data to PCM. (", snd_strerror(errorCode), U")\n");
  107. }
  108. if (!keepRunning) {
  109. break;
  110. }
  111. }
  112. terminateSound();
  113. return true;
  114. }
  115. }