浏览代码

add missing files

Philippe G 3 年之前
父节点
当前提交
052600a45a
共有 2 个文件被更改,包括 268 次插入0 次删除
  1. 115 0
      components/spotify/cspot/include/ChunkedByteStream.h
  2. 153 0
      components/spotify/cspot/src/ChunkedByteStream.cpp

+ 115 - 0
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 <memory>
+#include "MercuryManager.h"
+#include "stdint.h"
+
+class ChunkedByteStream : public bell::ByteStream {
+private:
+    // AES key used for data decryption
+    std::vector<uint8_t> audioKey;
+
+    // Spotify internal fileId
+    std::vector<uint8_t> fileId;
+
+    // Buffer for storing currently read chunks
+    std::vector<std::shared_ptr<AudioChunk>> chunks;
+
+    // Current position of the read pointer
+    size_t pos = 0;
+
+    size_t fileSize = -1;
+
+    std::mutex readMutex;
+
+    std::atomic<bool> loadAheadEnabled = false;
+
+    std::shared_ptr<MercuryManager> 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<AudioChunk> 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<AudioChunk> 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<AudioChunk> chunk);
+public:
+    ChunkedByteStream(std::shared_ptr<MercuryManager> 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<uint8_t>& fileId, std::vector<uint8_t>& 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

+ 153 - 0
components/spotify/cspot/src/ChunkedByteStream.cpp

@@ -0,0 +1,153 @@
+#include "ChunkedByteStream.h"
+
+ChunkedByteStream::ChunkedByteStream(std::shared_ptr<MercuryManager> manager) {
+    this->mercuryManager = manager;
+    this->pos = 167; // spotify header size
+}
+
+void ChunkedByteStream::setFileInfo(std::vector<uint8_t> &fileId, std::vector<uint8_t> &audioKey) {
+    this->audioKey = audioKey;
+    this->fileId = fileId;
+}
+
+void ChunkedByteStream::setEnableLoadAhead(bool loadAhead) {
+    this->loadAheadEnabled = loadAhead;
+}
+
+void ChunkedByteStream::fetchFileInformation() {
+    std::shared_ptr<AudioChunk> 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<AudioChunk> 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<AudioChunk> 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)->endPosition<pos || (*it)->startPosition>(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<AudioChunk> 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() {
+
+}