2
0

ChunkedAudioStream.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. #include "ChunkedAudioStream.h"
  2. #include "Logger.h"
  3. #include "BellUtils.h"
  4. static size_t vorbisReadCb(void *ptr, size_t size, size_t nmemb, ChunkedAudioStream *self)
  5. {
  6. auto data = self->read(nmemb);
  7. std::copy(data.begin(), data.end(), (char *)ptr);
  8. return data.size();
  9. }
  10. static int vorbisCloseCb(ChunkedAudioStream *self)
  11. {
  12. return 0;
  13. }
  14. static int vorbisSeekCb(ChunkedAudioStream *self, int64_t offset, int whence)
  15. {
  16. if (whence == 0)
  17. {
  18. offset += SPOTIFY_HEADER_SIZE;
  19. }
  20. static constexpr std::array<Whence, 3> seekDirections{
  21. Whence::START, Whence::CURRENT, Whence::END};
  22. self->seek(offset, seekDirections.at(static_cast<size_t>(whence)));
  23. return 0;
  24. }
  25. static long vorbisTellCb(ChunkedAudioStream *self)
  26. {
  27. return static_cast<long>(self->pos);
  28. }
  29. ChunkedAudioStream::~ChunkedAudioStream()
  30. {
  31. }
  32. ChunkedAudioStream::ChunkedAudioStream(std::vector<uint8_t> fileId, std::vector<uint8_t> audioKey, uint32_t duration, std::shared_ptr<MercuryManager> manager, uint32_t startPositionMs, bool isPaused)
  33. {
  34. this->audioKey = audioKey;
  35. this->duration = duration;
  36. this->manager = manager;
  37. this->fileId = fileId;
  38. this->startPositionMs = startPositionMs;
  39. this->isPaused = isPaused;
  40. auto beginChunk = manager->fetchAudioChunk(fileId, audioKey, 0, 0x4000);
  41. beginChunk->keepInMemory = true;
  42. while(beginChunk->isHeaderFileSizeLoadedSemaphore->twait() != 0);
  43. this->fileSize = beginChunk->headerFileSize;
  44. chunks.push_back(beginChunk);
  45. // File size is required for this packet to be downloaded
  46. this->fetchTraillingPacket();
  47. vorbisFile = { };
  48. vorbisCallbacks =
  49. {
  50. (decltype(ov_callbacks::read_func)) & vorbisReadCb,
  51. (decltype(ov_callbacks::seek_func)) & vorbisSeekCb,
  52. (decltype(ov_callbacks::close_func)) & vorbisCloseCb,
  53. (decltype(ov_callbacks::tell_func)) & vorbisTellCb,
  54. };
  55. }
  56. void ChunkedAudioStream::seekMs(uint32_t positionMs)
  57. {
  58. this->seekMutex.lock();
  59. loadingMeta = true;
  60. ov_time_seek(&vorbisFile, positionMs);
  61. loadingMeta = false;
  62. this->seekMutex.unlock();
  63. CSPOT_LOG(debug, "--- Finished seeking!");
  64. }
  65. void ChunkedAudioStream::startPlaybackLoop()
  66. {
  67. loadingMeta = true;
  68. isRunning = true;
  69. int32_t r = ov_open_callbacks(this, &vorbisFile, NULL, 0, vorbisCallbacks);
  70. CSPOT_LOG(debug, "--- Loaded file");
  71. if (this->startPositionMs != 0)
  72. {
  73. ov_time_seek(&vorbisFile, startPositionMs);
  74. }
  75. else
  76. {
  77. this->requestChunk(0);
  78. }
  79. loadingMeta = false;
  80. bool eof = false;
  81. while (!eof && isRunning)
  82. {
  83. if (!isPaused)
  84. {
  85. std::vector<uint8_t> pcmOut(4096 / 4);
  86. this->seekMutex.lock();
  87. long ret = ov_read(&vorbisFile, (char *)&pcmOut[0], 4096 / 4, &currentSection);
  88. this->seekMutex.unlock();
  89. if (ret == 0)
  90. {
  91. // and done :)
  92. eof = true;
  93. }
  94. else if (ret < 0)
  95. {
  96. CSPOT_LOG(error, "An error has occured in the stream");
  97. // Error in the stream
  98. }
  99. else
  100. {
  101. // Write the actual data
  102. auto data = std::vector<uint8_t>(pcmOut.begin(), pcmOut.begin() + ret);
  103. pcmCallback(data);
  104. // audioSink->feedPCMFrames(data);
  105. }
  106. }
  107. else
  108. {
  109. BELL_SLEEP_MS(100);
  110. }
  111. }
  112. ov_clear(&vorbisFile);
  113. vorbisCallbacks = {};
  114. CSPOT_LOG(debug, "Track finished");
  115. finished = true;
  116. if (eof)
  117. {
  118. this->streamFinishedCallback();
  119. }
  120. }
  121. void ChunkedAudioStream::fetchTraillingPacket()
  122. {
  123. auto startPosition = (this->fileSize / 4) - 0x1000;
  124. // AES block size is 16, so the index must be divisible by it
  125. while ((startPosition * 4) % 16 != 0)
  126. startPosition++; // ik, ugly lol
  127. auto endChunk = manager->fetchAudioChunk(fileId, audioKey, startPosition, fileSize / 4);
  128. endChunk->keepInMemory = true;
  129. chunks.push_back(endChunk);
  130. while (endChunk->isLoadedSemaphore->twait() != 0);
  131. }
  132. std::vector<uint8_t> ChunkedAudioStream::read(size_t bytes)
  133. {
  134. auto toRead = bytes;
  135. auto res = std::vector<uint8_t>();
  136. READ:
  137. while (res.size() < bytes)
  138. {
  139. auto position = pos;
  140. auto isLoadingMeta = loadingMeta;
  141. // Erase all chunks not close to current position
  142. chunks.erase(std::remove_if(
  143. chunks.begin(), chunks.end(),
  144. [position, &isLoadingMeta](const std::shared_ptr<AudioChunk> &chunk) {
  145. if (isLoadingMeta) {
  146. return false;
  147. }
  148. if (chunk->keepInMemory)
  149. {
  150. return false;
  151. }
  152. if (chunk->isFailed)
  153. {
  154. return true;
  155. }
  156. if (chunk->endPosition < position || chunk->startPosition > position + BUFFER_SIZE)
  157. {
  158. return true;
  159. }
  160. return false;
  161. }),
  162. chunks.end());
  163. int16_t chunkIndex = this->pos / AUDIO_CHUNK_SIZE;
  164. int32_t offset = this->pos % AUDIO_CHUNK_SIZE;
  165. if (pos >= fileSize)
  166. {
  167. CSPOT_LOG(debug, "EOL!");
  168. return res;
  169. }
  170. auto chunk = findChunkForPosition(pos);
  171. if (chunk != nullptr)
  172. {
  173. auto offset = pos - chunk->startPosition;
  174. if (chunk->isLoaded)
  175. {
  176. if (chunk->decryptedData.size() - offset >= toRead)
  177. {
  178. if((chunk->decryptedData.begin() + offset) < chunk->decryptedData.end()) {
  179. res.insert(res.end(), chunk->decryptedData.begin() + offset,
  180. chunk->decryptedData.begin() + offset + toRead);
  181. this->pos += toRead;
  182. } else {
  183. chunk->decrypt();
  184. }
  185. }
  186. else
  187. {
  188. res.insert(res.end(), chunk->decryptedData.begin() + offset, chunk->decryptedData.end());
  189. this->pos += chunk->decryptedData.size() - offset;
  190. toRead -= chunk->decryptedData.size() - offset;
  191. }
  192. }
  193. else
  194. {
  195. CSPOT_LOG(debug, "Waiting for chunk to load");
  196. while (chunk->isLoadedSemaphore->twait() != 0);
  197. if (chunk->isFailed)
  198. {
  199. auto requestChunk = this->requestChunk(chunkIndex);
  200. while (requestChunk->isLoadedSemaphore->twait() != 0);
  201. goto READ;
  202. }
  203. }
  204. }
  205. else
  206. {
  207. CSPOT_LOG(debug, "Actual request %d", chunkIndex);
  208. this->requestChunk(chunkIndex);
  209. }
  210. }
  211. if (!loadingMeta)
  212. {
  213. auto requestedOffset = 0;
  214. while (requestedOffset < BUFFER_SIZE)
  215. {
  216. auto chunk = findChunkForPosition(pos + requestedOffset);
  217. if (chunk != nullptr)
  218. {
  219. requestedOffset = chunk->endPosition - pos;
  220. // Don not buffer over EOL - unnecessary "failed chunks"
  221. if ((pos + requestedOffset) >= fileSize)
  222. {
  223. break;
  224. }
  225. }
  226. else
  227. {
  228. auto chunkReq = manager->fetchAudioChunk(fileId, audioKey, (pos + requestedOffset) / 4, (pos + requestedOffset + AUDIO_CHUNK_SIZE) / 4);
  229. CSPOT_LOG(debug, "Chunk req end pos %d", chunkReq->endPosition);
  230. this->chunks.push_back(chunkReq);
  231. }
  232. }
  233. }
  234. return res;
  235. }
  236. std::shared_ptr<AudioChunk> ChunkedAudioStream::findChunkForPosition(size_t position)
  237. {
  238. for (int i = 0; i < this->chunks.size(); i++)
  239. {
  240. auto chunk = this->chunks[i];
  241. if (chunk->startPosition <= position && chunk->endPosition > position)
  242. {
  243. return chunk;
  244. }
  245. }
  246. return nullptr;
  247. }
  248. void ChunkedAudioStream::seek(size_t dpos, Whence whence)
  249. {
  250. switch (whence)
  251. {
  252. case Whence::START:
  253. this->pos = dpos;
  254. break;
  255. case Whence::CURRENT:
  256. this->pos += dpos;
  257. break;
  258. case Whence::END:
  259. this->pos = fileSize + dpos;
  260. break;
  261. }
  262. auto currentChunk = this->pos / AUDIO_CHUNK_SIZE;
  263. if (findChunkForPosition(this->pos) == nullptr)
  264. {
  265. // Seeking might look back - therefore we preload some past data
  266. auto startPosition = (this->pos / 4) - (AUDIO_CHUNK_SIZE / 4);
  267. // AES block size is 16, so the index must be divisible by it
  268. while ((startPosition * 4) % 16 != 0)
  269. startPosition++; // ik, ugly lol
  270. this->chunks.push_back(manager->fetchAudioChunk(fileId, audioKey, startPosition, startPosition + (AUDIO_CHUNK_SIZE / 4)));
  271. }
  272. CSPOT_LOG(debug, "Change in current chunk %d", currentChunk);
  273. }
  274. std::shared_ptr<AudioChunk> ChunkedAudioStream::requestChunk(size_t chunkIndex)
  275. {
  276. CSPOT_LOG(debug, "Chunk Req %d", chunkIndex);
  277. auto chunk = manager->fetchAudioChunk(fileId, audioKey, chunkIndex);
  278. this->chunks.push_back(chunk);
  279. return chunk;
  280. }