CentralAudioBuffer.h 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #pragma once
  2. #include <atomic>
  3. #include <cmath>
  4. #include <functional>
  5. #include <iostream>
  6. #include <memory>
  7. #include <mutex>
  8. #include "BellUtils.h"
  9. #include "CircularBuffer.h"
  10. #include "StreamInfo.h"
  11. #include "WrappedSemaphore.h"
  12. #ifdef _WIN32
  13. #define __attribute__(X)
  14. #endif
  15. typedef std::function<void(std::string)> shutdownEventHandler;
  16. namespace bell {
  17. class CentralAudioBuffer {
  18. private:
  19. std::mutex accessMutex;
  20. std::atomic<bool> isLocked = false;
  21. std::mutex dataAccessMutex;
  22. public:
  23. static const size_t PCM_CHUNK_SIZE = 4096;
  24. std::unique_ptr<bell::WrappedSemaphore> chunkReady;
  25. // Audio marker for track change detection, and DSP autoconfig
  26. struct AudioChunk {
  27. // Timeval
  28. int32_t sec;
  29. int32_t usec;
  30. // Unique track hash, used for track change detection
  31. size_t trackHash;
  32. // Audio format
  33. uint32_t sampleRate;
  34. uint8_t channels;
  35. uint8_t bitWidth;
  36. // PCM data size
  37. size_t pcmSize;
  38. // PCM data
  39. uint8_t pcmData[PCM_CHUNK_SIZE];
  40. } __attribute__((packed));
  41. CentralAudioBuffer(size_t chunks) {
  42. audioBuffer = std::make_shared<CircularBuffer>(chunks * sizeof(AudioChunk));
  43. chunkReady = std::make_unique<bell::WrappedSemaphore>(50);
  44. }
  45. std::shared_ptr<bell::CircularBuffer> audioBuffer;
  46. uint32_t currentSampleRate = 44100;
  47. /**
  48. * Returns current sample rate
  49. * @return sample rate
  50. */
  51. uint32_t getSampleRate() { return currentSampleRate; }
  52. /**
  53. * Clears input buffer, to be called for track change and such
  54. */
  55. void clearBuffer() {
  56. std::scoped_lock lock(this->dataAccessMutex);
  57. audioBuffer->emptyBuffer();
  58. hasChunk = false;
  59. }
  60. void emptyCompletely() {
  61. std::scoped_lock lock(this->dataAccessMutex);
  62. audioBuffer->emptyBuffer();
  63. }
  64. bool hasAtLeast(size_t chunks) {
  65. return this->audioBuffer->size() >= chunks * sizeof(AudioChunk);
  66. }
  67. /**
  68. * Locks access to audio buffer. Call after starting playback
  69. */
  70. void lockAccess() {
  71. if (!isLocked) {
  72. clearBuffer();
  73. this->accessMutex.lock();
  74. isLocked = true;
  75. }
  76. }
  77. /**
  78. * Frees access to the audio buffer. Call during shutdown
  79. */
  80. void unlockAccess() {
  81. if (isLocked) {
  82. clearBuffer();
  83. this->accessMutex.unlock();
  84. isLocked = false;
  85. }
  86. }
  87. AudioChunk currentChunk = {};
  88. bool hasChunk = false;
  89. AudioChunk lastReadChunk = {};
  90. AudioChunk* readChunk() {
  91. std::scoped_lock lock(this->dataAccessMutex);
  92. if (audioBuffer->size() < sizeof(AudioChunk)) {
  93. lastReadChunk.pcmSize = 0;
  94. return nullptr;
  95. }
  96. audioBuffer->read((uint8_t*)&lastReadChunk, sizeof(AudioChunk));
  97. currentSampleRate = static_cast<uint32_t>(lastReadChunk.sampleRate);
  98. return &lastReadChunk;
  99. }
  100. size_t writePCM(const uint8_t* data, size_t dataSize, size_t hash,
  101. uint32_t sampleRate = 44100, uint8_t channels = 2,
  102. BitWidth bitWidth = BitWidth::BW_16, int32_t sec = 0,
  103. int32_t usec = 0) {
  104. std::scoped_lock lock(this->dataAccessMutex);
  105. if (hasChunk && (currentChunk.trackHash != hash ||
  106. currentChunk.pcmSize >= PCM_CHUNK_SIZE)) {
  107. if ((audioBuffer->size() - audioBuffer->capacity()) <
  108. sizeof(AudioChunk)) {
  109. return 0;
  110. }
  111. // Track changed or buf full, return current chunk
  112. hasChunk = false;
  113. this->audioBuffer->write((uint8_t*)&currentChunk, sizeof(AudioChunk));
  114. // this->chunkReady->give();
  115. }
  116. // New chunk requested, initialize
  117. if (!hasChunk) {
  118. currentChunk.trackHash = hash;
  119. currentChunk.sampleRate = sampleRate;
  120. currentChunk.channels = channels;
  121. currentChunk.bitWidth = 16;
  122. currentChunk.sec = sec;
  123. currentChunk.usec = usec;
  124. currentChunk.pcmSize = 0;
  125. hasChunk = true;
  126. }
  127. // Calculate how much data we can write
  128. size_t toWriteSize = dataSize;
  129. if (currentChunk.pcmSize + toWriteSize > PCM_CHUNK_SIZE) {
  130. toWriteSize = PCM_CHUNK_SIZE - currentChunk.pcmSize;
  131. }
  132. // Copy it over :)
  133. memcpy(currentChunk.pcmData + currentChunk.pcmSize, data, toWriteSize);
  134. currentChunk.pcmSize += toWriteSize;
  135. return toWriteSize;
  136. }
  137. };
  138. } // namespace bell