From 052600a45a7fc99c4eecf1da67bac9a2e2d516ed Mon Sep 17 00:00:00 2001 From: Philippe G Date: Thu, 6 Jan 2022 18:56:27 -0800 Subject: [PATCH] add missing files --- .../spotify/cspot/include/ChunkedByteStream.h | 115 +++++++++++++ .../spotify/cspot/src/ChunkedByteStream.cpp | 153 ++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 components/spotify/cspot/include/ChunkedByteStream.h create mode 100644 components/spotify/cspot/src/ChunkedByteStream.cpp diff --git a/components/spotify/cspot/include/ChunkedByteStream.h b/components/spotify/cspot/include/ChunkedByteStream.h new file mode 100644 index 00000000..e03bd8a2 --- /dev/null +++ b/components/spotify/cspot/include/ChunkedByteStream.h @@ -0,0 +1,115 @@ +#ifndef CSPOT_CHUNKEDBYTESTREAM_H +#define CSPOT_CHUNKEDBYTESTREAM_H + +#include "ByteStream.h" +#include "BellLogger.h" +#include +#include "MercuryManager.h" +#include "stdint.h" + +class ChunkedByteStream : public bell::ByteStream { +private: + // AES key used for data decryption + std::vector audioKey; + + // Spotify internal fileId + std::vector fileId; + + // Buffer for storing currently read chunks + std::vector> chunks; + + // Current position of the read pointer + size_t pos = 0; + + size_t fileSize = -1; + + std::mutex readMutex; + + std::atomic loadAheadEnabled = false; + + std::shared_ptr mercuryManager; + + /** + * Returns an audio chunk for given position. + * @param position requested position + * @return matching audio chunk, or nullptr if no chunk is available + */ + std::shared_ptr getChunkForPosition(size_t position); + + /** + * Requests a new audio chunk from mercury manager. Returns its structure immediately. + * @param position index of a chunk to request + * @return requested chunk + */ + std::shared_ptr requestChunk(uint16_t position); + + /** + * Tries to read data from a given audio chunk. + * @param buffer destination buffer + * @param bytes number of bytes to read + * @param chunk `AudioChunk` to read from + * @return number of bytes read + */ + size_t attemptRead(uint8_t *buffer, size_t bytes, std::shared_ptr chunk); +public: + ChunkedByteStream(std::shared_ptr manager); + ~ChunkedByteStream() {}; + + /** + * Requests first chunk from the file, and then fills file information based on its header + */ + void fetchFileInformation(); + + /** + * Enables / disables load-ahead of chunks. + * @param loadAhead true to enable load ahead + */ + void setEnableLoadAhead(bool loadAhead); + + /** + * Sets information about given spotify file, necessary for chunk request + * @param fileId id of given audio file + * @param audioKey audio key used for decryption + */ + void setFileInfo(std::vector& fileId, std::vector& audioKey); + + // ---- ByteStream methods ---- + /** + * Reads given amount of bytes from stream. Data is OPUS encoded. + * @param buffer buffer to read into + * @param size amount of bytes to read + * @return amount of bytes read + */ + size_t read(uint8_t *buf, size_t nbytes); + + /** + * Seeks to given position in stream + * @param pos position to seek to + */ + void seek(size_t pos); + + /** + * skip given amount of bytes in stream. + * @param nbytes amount of bytes to skip + */ + size_t skip(size_t nbytes); + + /** + * Returns current position in stream. + * @return position + */ + size_t position(); + + /** + * Returns size of the file + * @return bytes in file + */ + size_t size(); + + /** + * Close the reader + */ + void close(); +}; + +#endif //CSPOT_CHUNKEDBYTESTREAM_H diff --git a/components/spotify/cspot/src/ChunkedByteStream.cpp b/components/spotify/cspot/src/ChunkedByteStream.cpp new file mode 100644 index 00000000..643544d3 --- /dev/null +++ b/components/spotify/cspot/src/ChunkedByteStream.cpp @@ -0,0 +1,153 @@ +#include "ChunkedByteStream.h" + +ChunkedByteStream::ChunkedByteStream(std::shared_ptr manager) { + this->mercuryManager = manager; + this->pos = 167; // spotify header size +} + +void ChunkedByteStream::setFileInfo(std::vector &fileId, std::vector &audioKey) { + this->audioKey = audioKey; + this->fileId = fileId; +} + +void ChunkedByteStream::setEnableLoadAhead(bool loadAhead) { + this->loadAheadEnabled = loadAhead; +} + +void ChunkedByteStream::fetchFileInformation() { + std::shared_ptr beginChunk = mercuryManager->fetchAudioChunk(fileId, audioKey, 0, 0x4000); + beginChunk->keepInMemory = true; + while (beginChunk->isHeaderFileSizeLoadedSemaphore->twait() != 0); + this->fileSize = beginChunk->headerFileSize; + chunks.push_back(beginChunk); + + auto startPosition = (this->fileSize / 4) - 0x1000; + + // AES block size is 16, so the index must be divisible by it + while ((startPosition * 4) % 16 != 0) + startPosition++; // ik, ugly lol + + auto endChunk = mercuryManager->fetchAudioChunk(fileId, audioKey, startPosition, fileSize / 4); + endChunk->keepInMemory = true; + + chunks.push_back(endChunk); +} + +std::shared_ptr ChunkedByteStream::getChunkForPosition(size_t position) { + // Find chunks that fit in requested position + for (auto chunk: chunks) { + if (chunk->startPosition <= position && chunk->endPosition > position) { + return chunk; + } + } + + return nullptr; +} + +std::shared_ptr ChunkedByteStream::requestChunk(uint16_t position) { + auto chunk = this->mercuryManager->fetchAudioChunk(this->fileId, this->audioKey, position); + + // Store a reference internally + chunks.push_back(chunk); + return chunk; +} + + +size_t ChunkedByteStream::read(uint8_t *buf, size_t nbytes) { + std::scoped_lock lock(this->readMutex); + auto chunk = getChunkForPosition(pos); + uint16_t chunkIndex = this->pos / AUDIO_CHUNK_SIZE; + for (auto it = chunks.begin(); it != chunks.end();) { + if (((*it)->endPositionstartPosition>(pos + 2 * AUDIO_CHUNK_SIZE)) && !(*it)->keepInMemory) { + it = chunks.erase(it); + } else { + it++; + } + } + + // Request chunk if does not exist + if (chunk == nullptr) { + BELL_LOG(info, "cspot", "Chunk not found, requesting %d", chunkIndex); + chunk = this->requestChunk(chunkIndex); + } + + + if (chunk != nullptr) { + // Wait for chunk if not loaded yet + if (!chunk->isLoaded && !chunk->isFailed) { + chunk->isLoadedSemaphore->wait(); + } + + if (chunk->isFailed) return 0; + + // Attempt to read from chunk + auto read = attemptRead(buf, nbytes, chunk); + pos += read; + + auto nextChunkPos = ((chunkIndex + 1) * AUDIO_CHUNK_SIZE) + 1; + if (loadAheadEnabled && nextChunkPos + AUDIO_CHUNK_SIZE < fileSize) { + auto nextChunk = getChunkForPosition(nextChunkPos); + + if (nextChunk == nullptr) { + // Request next chunk + this->requestChunk(chunkIndex + 1); + } + } + + return read; + } + return 0; +} + +size_t ChunkedByteStream::attemptRead(uint8_t *buffer, size_t bytes, std::shared_ptr chunk) { + //if (!chunk->isLoaded || chunk->isFailed || chunk->startPosition >= pos || chunk->endPosition < pos) return 0; + + // Calculate how many bytes we can read from chunk + auto offset = pos - chunk->startPosition; + auto toRead = bytes; + if (toRead > chunk->decryptedData.size() - offset) { + toRead = chunk->decryptedData.size() - offset; + } + + // Copy data + memcpy(buffer, chunk->decryptedData.data() + offset, toRead); + + return toRead; +} + +void ChunkedByteStream::seek(size_t nbytes) { + std::scoped_lock lock(this->readMutex); + BELL_LOG(info, "cspot", "seeking to %d", nbytes); + pos = nbytes; + + + if (getChunkForPosition(this->pos) == nullptr) { + // Seeking might look back - therefore we preload some past data + auto startPosition = (this->pos / 4) - (AUDIO_CHUNK_SIZE / 4); + + // AES block size is 16, so the index must be divisible by it + while ((startPosition * 4) % 16 != 0) + startPosition++; // ik, ugly lol + + this->chunks.push_back(mercuryManager->fetchAudioChunk(fileId, audioKey, startPosition, + startPosition + (AUDIO_CHUNK_SIZE / 4))); + } +} + +size_t ChunkedByteStream::skip(size_t nbytes) { + std::scoped_lock lock(this->readMutex); + pos += nbytes; + return pos; +} + +size_t ChunkedByteStream::position() { + return pos; +} + +size_t ChunkedByteStream::size() { + return fileSize; +} + +void ChunkedByteStream::close() { + +} \ No newline at end of file