From e187aaedcbf60fb72f5d475da2e373d63db6ccf6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 19 Nov 2015 15:56:37 -0800 Subject: [PATCH] Load recorded clips from URLs, not file paths --- libraries/recording/CMakeLists.txt | 2 +- .../recording/src/recording/ClipCache.cpp | 40 +++++ libraries/recording/src/recording/ClipCache.h | 57 ++++++ .../recording/src/recording/impl/FileClip.cpp | 137 +-------------- .../recording/src/recording/impl/FileClip.h | 27 +-- .../src/recording/impl/PointerClip.cpp | 163 ++++++++++++++++++ .../src/recording/impl/PointerClip.h | 61 +++++++ .../src/RecordingScriptingInterface.cpp | 18 +- .../src/RecordingScriptingInterface.h | 4 +- 9 files changed, 338 insertions(+), 171 deletions(-) create mode 100644 libraries/recording/src/recording/ClipCache.cpp create mode 100644 libraries/recording/src/recording/ClipCache.h create mode 100644 libraries/recording/src/recording/impl/PointerClip.cpp create mode 100644 libraries/recording/src/recording/impl/PointerClip.h diff --git a/libraries/recording/CMakeLists.txt b/libraries/recording/CMakeLists.txt index a0beae4496..b42a4018f8 100644 --- a/libraries/recording/CMakeLists.txt +++ b/libraries/recording/CMakeLists.txt @@ -4,6 +4,6 @@ set(TARGET_NAME recording) setup_hifi_library(Script) # use setup_hifi_library macro to setup our project and link appropriate Qt modules -link_hifi_libraries(shared) +link_hifi_libraries(shared networking) GroupSources("src/recording") diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp new file mode 100644 index 0000000000..fb09245bf9 --- /dev/null +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis on 2015/11/19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "ClipCache.h" +#include "impl/PointerClip.h" + +using namespace recording; +NetworkClipLoader::NetworkClipLoader(const QUrl& url, bool delayLoad) + : Resource(url, delayLoad), _clip(std::make_shared(url)) +{ + +} + + +void NetworkClip::init(const QByteArray& clipData) { + _clipData = clipData; + PointerClip::init((uchar*)_clipData.data(), _clipData.size()); +} + +void NetworkClipLoader::downloadFinished(const QByteArray& data) { + _clip->init(data); +} + +ClipCache& ClipCache::instance() { + static ClipCache _instance; + return _instance; +} + +NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { + return ResourceCache::getResource(url, QUrl(), false, nullptr).staticCast(); +} + +QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { + return QSharedPointer(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared); +} + diff --git a/libraries/recording/src/recording/ClipCache.h b/libraries/recording/src/recording/ClipCache.h new file mode 100644 index 0000000000..c72d45648d --- /dev/null +++ b/libraries/recording/src/recording/ClipCache.h @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis on 2015/11/19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#ifndef hifi_Recording_ClipCache_h +#define hifi_Recording_ClipCache_h + +#include + +#include "Forward.h" +#include "impl/PointerClip.h" + +namespace recording { + +class NetworkClip : public PointerClip { +public: + using Pointer = std::shared_ptr; + + NetworkClip(const QUrl& url) : _url(url) {} + virtual void init(const QByteArray& clipData); + virtual QString getName() const override { return _url.toString(); } + +private: + QByteArray _clipData; + QUrl _url; +}; + +class NetworkClipLoader : public Resource { +public: + NetworkClipLoader(const QUrl& url, bool delayLoad); + virtual void downloadFinished(const QByteArray& data) override; + ClipPointer getClip() { return _clip; } + bool completed() { return _failedToLoad || isLoaded(); } + +private: + const NetworkClip::Pointer _clip; +}; + +using NetworkClipLoaderPointer = QSharedPointer; + +class ClipCache : public ResourceCache { +public: + static ClipCache& instance(); + + NetworkClipLoaderPointer getClipLoader(const QUrl& url); + +protected: + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) override; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/impl/FileClip.cpp b/libraries/recording/src/recording/impl/FileClip.cpp index fcc22452e0..ce2705a76c 100644 --- a/libraries/recording/src/recording/impl/FileClip.cpp +++ b/libraries/recording/src/recording/impl/FileClip.cpp @@ -23,63 +23,6 @@ using namespace recording; -static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize); -static const QString FRAME_TYPE_MAP = QStringLiteral("frameTypes"); -static const QString FRAME_COMREPSSION_FLAG = QStringLiteral("compressed"); - -using FrameTranslationMap = QMap; - -FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) { - FrameTranslationMap results; - auto headerObj = doc.object(); - if (headerObj.contains(FRAME_TYPE_MAP)) { - auto frameTypeObj = headerObj[FRAME_TYPE_MAP].toObject(); - auto currentFrameTypes = Frame::getFrameTypes(); - for (auto frameTypeName : frameTypeObj.keys()) { - qDebug() << frameTypeName; - if (!currentFrameTypes.contains(frameTypeName)) { - continue; - } - FrameType currentTypeEnum = currentFrameTypes[frameTypeName]; - FrameType storedTypeEnum = static_cast(frameTypeObj[frameTypeName].toInt()); - results[storedTypeEnum] = currentTypeEnum; - } - } - return results; -} - - -FileFrameHeaderList parseFrameHeaders(uchar* const start, const qint64& size) { - FileFrameHeaderList results; - auto current = start; - auto end = current + size; - // Read all the frame headers - // FIXME move to Frame::readHeader? - while (end - current >= MINIMUM_FRAME_SIZE) { - FileFrameHeader header; - memcpy(&(header.type), current, sizeof(FrameType)); - current += sizeof(FrameType); - memcpy(&(header.timeOffset), current, sizeof(Frame::Time)); - current += sizeof(Frame::Time); - memcpy(&(header.size), current, sizeof(FrameSize)); - current += sizeof(FrameSize); - header.fileOffset = current - start; - if (end - current < header.size) { - current = end; - break; - } - current += header.size; - results.push_back(header); - } - qDebug() << "Parsed source data into " << results.size() << " frames"; -// int i = 0; -// for (const auto& frameHeader : results) { -// qDebug() << "Frame " << i++ << " time " << frameHeader.timeOffset << " Type " << frameHeader.type; -// } - return results; -} - - FileClip::FileClip(const QString& fileName) : _file(fileName) { auto size = _file.size(); qDebug() << "Opening file of size: " << size; @@ -88,58 +31,8 @@ FileClip::FileClip(const QString& fileName) : _file(fileName) { qCWarning(recordingLog) << "Unable to open file " << fileName; return; } - _map = _file.map(0, size, QFile::MapPrivateOption); - if (!_map) { - qCWarning(recordingLog) << "Unable to map file " << fileName; - return; - } - - auto parsedFrameHeaders = parseFrameHeaders(_map, size); - - // Verify that at least one frame exists and that the first frame is a header - if (0 == parsedFrameHeaders.size()) { - qWarning() << "No frames found, invalid file"; - return; - } - - // Grab the file header - { - auto fileHeaderFrameHeader = *parsedFrameHeaders.begin(); - parsedFrameHeaders.pop_front(); - if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) { - qWarning() << "Missing header frame, invalid file"; - return; - } - - QByteArray fileHeaderData((char*)_map + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size); - _fileHeader = QJsonDocument::fromBinaryData(fileHeaderData); - } - - // Check for compression - { - _compressed = _fileHeader.object()[FRAME_COMREPSSION_FLAG].toBool(); - } - - // Find the type enum translation map and fix up the frame headers - { - FrameTranslationMap translationMap = parseTranslationMap(_fileHeader); - if (translationMap.empty()) { - qWarning() << "Header missing frame type map, invalid file"; - return; - } - qDebug() << translationMap; - - // Update the loaded headers with the frame data - _frames.reserve(parsedFrameHeaders.size()); - for (auto& frameHeader : parsedFrameHeaders) { - if (!translationMap.contains(frameHeader.type)) { - continue; - } - frameHeader.type = translationMap[frameHeader.type]; - _frames.push_back(frameHeader); - } - } - + auto mappedFile = _file.map(0, size, QFile::MapPrivateOption); + init(mappedFile, size); } @@ -228,31 +121,9 @@ bool FileClip::write(const QString& fileName, Clip::Pointer clip) { FileClip::~FileClip() { Locker lock(_mutex); - _file.unmap(_map); - _map = nullptr; + _file.unmap(_data); if (_file.isOpen()) { _file.close(); } -} - -// Internal only function, needs no locking -FrameConstPointer FileClip::readFrame(size_t frameIndex) const { - FramePointer result; - if (frameIndex < _frames.size()) { - result = std::make_shared(); - const auto& header = _frames[frameIndex]; - result->type = header.type; - result->timeOffset = header.timeOffset; - if (header.size) { - result->data.insert(0, reinterpret_cast(_map)+header.fileOffset, header.size); - if (_compressed) { - result->data = qUncompress(result->data); - } - } - } - return result; -} - -void FileClip::addFrame(FrameConstPointer) { - throw std::runtime_error("File clips are read only"); + reset(); } diff --git a/libraries/recording/src/recording/impl/FileClip.h b/libraries/recording/src/recording/impl/FileClip.h index f103a9aca6..71ae414721 100644 --- a/libraries/recording/src/recording/impl/FileClip.h +++ b/libraries/recording/src/recording/impl/FileClip.h @@ -10,27 +10,13 @@ #ifndef hifi_Recording_Impl_FileClip_h #define hifi_Recording_Impl_FileClip_h -#include "ArrayClip.h" - -#include +#include "PointerClip.h" #include -#include - -#include "../Frame.h" namespace recording { -struct FileFrameHeader : public FrameHeader { - FrameType type; - Frame::Time timeOffset; - uint16_t size; - quint64 fileOffset; -}; - -using FileFrameHeaderList = std::list; - -class FileClip : public ArrayClip { +class FileClip : public PointerClip { public: using Pointer = std::shared_ptr; @@ -38,20 +24,11 @@ public: virtual ~FileClip(); virtual QString getName() const override; - virtual void addFrame(FrameConstPointer) override; - - const QJsonDocument& getHeader() { - return _fileHeader; - } static bool write(const QString& filePath, Clip::Pointer clip); private: - virtual FrameConstPointer readFrame(size_t index) const override; - QJsonDocument _fileHeader; QFile _file; - uchar* _map { nullptr }; - bool _compressed { true }; }; } diff --git a/libraries/recording/src/recording/impl/PointerClip.cpp b/libraries/recording/src/recording/impl/PointerClip.cpp new file mode 100644 index 0000000000..48132c066d --- /dev/null +++ b/libraries/recording/src/recording/impl/PointerClip.cpp @@ -0,0 +1,163 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "PointerClip.h" + +#include + +#include +#include +#include + +#include + +#include "../Frame.h" +#include "../Logging.h" +#include "BufferClip.h" + + +using namespace recording; + +const QString PointerClip::FRAME_TYPE_MAP = QStringLiteral("frameTypes"); +const QString PointerClip::FRAME_COMREPSSION_FLAG = QStringLiteral("compressed"); + +using FrameTranslationMap = QMap; + +FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) { + FrameTranslationMap results; + auto headerObj = doc.object(); + if (headerObj.contains(PointerClip::FRAME_TYPE_MAP)) { + auto frameTypeObj = headerObj[PointerClip::FRAME_TYPE_MAP].toObject(); + auto currentFrameTypes = Frame::getFrameTypes(); + for (auto frameTypeName : frameTypeObj.keys()) { + qDebug() << frameTypeName; + if (!currentFrameTypes.contains(frameTypeName)) { + continue; + } + FrameType currentTypeEnum = currentFrameTypes[frameTypeName]; + FrameType storedTypeEnum = static_cast(frameTypeObj[frameTypeName].toInt()); + results[storedTypeEnum] = currentTypeEnum; + } + } + return results; +} + + +PointerFrameHeaderList parseFrameHeaders(uchar* const start, const size_t& size) { + PointerFrameHeaderList results; + auto current = start; + auto end = current + size; + // Read all the frame headers + // FIXME move to Frame::readHeader? + while (end - current >= PointerClip::MINIMUM_FRAME_SIZE) { + PointerFrameHeader header; + memcpy(&(header.type), current, sizeof(FrameType)); + current += sizeof(FrameType); + memcpy(&(header.timeOffset), current, sizeof(Frame::Time)); + current += sizeof(Frame::Time); + memcpy(&(header.size), current, sizeof(FrameSize)); + current += sizeof(FrameSize); + header.fileOffset = current - start; + if (end - current < header.size) { + current = end; + break; + } + current += header.size; + results.push_back(header); + } + qDebug() << "Parsed source data into " << results.size() << " frames"; +// int i = 0; +// for (const auto& frameHeader : results) { +// qDebug() << "Frame " << i++ << " time " << frameHeader.timeOffset << " Type " << frameHeader.type; +// } + return results; +} + +void PointerClip::reset() { + _frames.clear(); + _data = nullptr; + _size = 0; + _header = QJsonDocument(); +} + +void PointerClip::init(uchar* data, size_t size) { + reset(); + + _data = data; + _size = size; + + auto parsedFrameHeaders = parseFrameHeaders(data, size); + // Verify that at least one frame exists and that the first frame is a header + if (0 == parsedFrameHeaders.size()) { + qWarning() << "No frames found, invalid file"; + reset(); + return; + } + + // Grab the file header + { + auto fileHeaderFrameHeader = *parsedFrameHeaders.begin(); + parsedFrameHeaders.pop_front(); + if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) { + qWarning() << "Missing header frame, invalid file"; + reset(); + return; + } + + QByteArray fileHeaderData((char*)_data + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size); + _header = QJsonDocument::fromBinaryData(fileHeaderData); + } + + // Check for compression + { + _compressed = _header.object()[FRAME_COMREPSSION_FLAG].toBool(); + } + + // Find the type enum translation map and fix up the frame headers + { + FrameTranslationMap translationMap = parseTranslationMap(_header); + if (translationMap.empty()) { + qWarning() << "Header missing frame type map, invalid file"; + reset(); + return; + } + + // Update the loaded headers with the frame data + _frames.reserve(parsedFrameHeaders.size()); + for (auto& frameHeader : parsedFrameHeaders) { + if (!translationMap.contains(frameHeader.type)) { + continue; + } + frameHeader.type = translationMap[frameHeader.type]; + _frames.push_back(frameHeader); + } + } + +} + +// Internal only function, needs no locking +FrameConstPointer PointerClip::readFrame(size_t frameIndex) const { + FramePointer result; + if (frameIndex < _frames.size()) { + result = std::make_shared(); + const auto& header = _frames[frameIndex]; + result->type = header.type; + result->timeOffset = header.timeOffset; + if (header.size) { + result->data.insert(0, reinterpret_cast(_data)+header.fileOffset, header.size); + if (_compressed) { + result->data = qUncompress(result->data); + } + } + } + return result; +} + +void PointerClip::addFrame(FrameConstPointer) { + throw std::runtime_error("Pointer clips are read only, use duplicate to create a read/write clip"); +} diff --git a/libraries/recording/src/recording/impl/PointerClip.h b/libraries/recording/src/recording/impl/PointerClip.h new file mode 100644 index 0000000000..5a7a3499fe --- /dev/null +++ b/libraries/recording/src/recording/impl/PointerClip.h @@ -0,0 +1,61 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_Recording_Impl_PointerClip_h +#define hifi_Recording_Impl_PointerClip_h + +#include "ArrayClip.h" + +#include + +#include + +#include "../Frame.h" + +namespace recording { + +struct PointerFrameHeader : public FrameHeader { + FrameType type; + Frame::Time timeOffset; + uint16_t size; + quint64 fileOffset; +}; + +using PointerFrameHeaderList = std::list; + +class PointerClip : public ArrayClip { +public: + using Pointer = std::shared_ptr; + + PointerClip() {}; + PointerClip(uchar* data, size_t size) { init(data, size); } + + void init(uchar* data, size_t size); + virtual void addFrame(FrameConstPointer) override; + const QJsonDocument& getHeader() const { + return _header; + } + + // FIXME move to frame? + static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize); + static const QString FRAME_TYPE_MAP; + static const QString FRAME_COMREPSSION_FLAG; + +protected: + void reset(); + virtual FrameConstPointer readFrame(size_t index) const override; + QJsonDocument _header; + uchar* _data { nullptr }; + size_t _size { 0 }; + bool _compressed { true }; +}; + +} + +#endif diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 33e67e1b43..d82d471d79 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -8,14 +8,15 @@ #include "RecordingScriptingInterface.h" -#include +#include +#include +#include #include #include #include #include -#include -#include +#include #include "ScriptEngineLogging.h" @@ -43,20 +44,17 @@ float RecordingScriptingInterface::playerLength() const { return _player->length(); } -void RecordingScriptingInterface::loadRecording(const QString& filename) { +void RecordingScriptingInterface::loadRecording(const QString& url) { using namespace recording; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection, - Q_ARG(QString, filename)); + Q_ARG(QString, url)); return; } - ClipPointer clip = Clip::fromFile(filename); - if (!clip) { - qWarning() << "Unable to load clip data from " << filename; - } - _player->queueClip(clip); + // FIXME make blocking and force off main thread? + _player->queueClip(ClipCache::instance().getClipLoader(url)->getClip()); } void RecordingScriptingInterface::startPlaying() { diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 483ead3ca3..3834089177 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -25,7 +25,7 @@ public: RecordingScriptingInterface(); public slots: - void loadRecording(const QString& filename); + void loadRecording(const QString& url); void startPlaying(); void pausePlayer();