From 6bf9a4518aecc97adbce448eee4ae3f573179bca Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 3 Nov 2015 10:36:25 -0800 Subject: [PATCH 1/4] Bail early from ScriptEngine::run if stopped evaluate() bails anyway, so this will avoid the cost of init(). If run() is invoked from runInThread(), this may avoid a race where _isRunning is set after it is checked because the check occured during init(). --- libraries/script-engine/src/ScriptEngine.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c6ac6495b1..381eef63db 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -646,13 +646,14 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi } void ScriptEngine::run() { - // TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if - // we're in the process of stopping? - + if (_stoppingAllScripts) { + return; // bail early - avoid setting state in init(), as evaluate() will bail too + } + if (!_isInitialized) { init(); } - + _isRunning = true; _isFinished = false; if (_wantSignals) { From c53c0ec53f92210393ababa7e247d7d897ebf8a7 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 6 Nov 2015 11:13:10 -0800 Subject: [PATCH 2/4] Fix double delete on shutdown --- interface/src/Application.cpp | 25 +++++++++---------- libraries/plugins/src/plugins/Forward.h | 13 +++++----- libraries/plugins/src/plugins/PluginManager.h | 2 ++ tests/controllers/src/main.cpp | 3 +-- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 718d06c6e5..6af4f0c2d1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1027,10 +1027,7 @@ void Application::initializeUi() { foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { QString name = inputPlugin->getName(); if (name == KeyboardMouseDevice::NAME) { - auto kbm = static_cast(inputPlugin.data()); - // FIXME incredibly evil.... _keyboardMouseDevice is now owned by - // both a QSharedPointer and a std::shared_ptr - _keyboardMouseDevice = std::shared_ptr(kbm); + _keyboardMouseDevice = std::dynamic_pointer_cast(inputPlugin); } } updateInputModes(); @@ -4645,7 +4642,7 @@ DisplayPlugin* Application::getActiveDisplayPlugin() { updateDisplayMode(); Q_ASSERT(_displayPlugin); } - return _displayPlugin.data(); + return _displayPlugin.get(); } const DisplayPlugin* Application::getActiveDisplayPlugin() const { @@ -4685,10 +4682,10 @@ void Application::updateDisplayMode() { bool first = true; foreach(auto displayPlugin, displayPlugins) { addDisplayPluginToMenu(displayPlugin, first); - QObject::connect(displayPlugin.data(), &DisplayPlugin::requestRender, [this] { + QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, [this] { paintGL(); }); - QObject::connect(displayPlugin.data(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { + QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { resizeGL(); }); @@ -4814,12 +4811,14 @@ void Application::updateInputModes() { foreach(auto inputPlugin, inputPlugins) { QString name = inputPlugin->getName(); QAction* action = menu->getActionForOption(name); - if (action->isChecked() && !_activeInputPlugins.contains(inputPlugin)) { - _activeInputPlugins.append(inputPlugin); - newInputPlugins.append(inputPlugin); - } else if (!action->isChecked() && _activeInputPlugins.contains(inputPlugin)) { - _activeInputPlugins.removeOne(inputPlugin); - removedInputPlugins.append(inputPlugin); + + auto it = std::find(std::begin(_activeInputPlugins), std::end(_activeInputPlugins), inputPlugin); + if (action->isChecked() && it == std::end(_activeInputPlugins)) { + _activeInputPlugins.push_back(inputPlugin); + newInputPlugins.push_back(inputPlugin); + } else if (!action->isChecked() && it != std::end(_activeInputPlugins)) { + _activeInputPlugins.erase(it); + removedInputPlugins.push_back(inputPlugin); } } diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h index 78ec8fdcb3..8d8259ba4f 100644 --- a/libraries/plugins/src/plugins/Forward.h +++ b/libraries/plugins/src/plugins/Forward.h @@ -7,9 +7,8 @@ // #pragma once -#include -#include -#include +#include +#include class DisplayPlugin; class InputPlugin; @@ -17,8 +16,8 @@ class Plugin; class PluginContainer; class PluginManager; -using DisplayPluginPointer = QSharedPointer; -using DisplayPluginList = QVector; -using InputPluginPointer = QSharedPointer; -using InputPluginList = QVector; +using DisplayPluginPointer = std::shared_ptr; +using DisplayPluginList = std::vector; +using InputPluginPointer = std::shared_ptr; +using InputPluginList = std::vector; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 17619a93c0..2e056414ec 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -7,6 +7,8 @@ // #pragma once +#include + #include "Forward.h" class PluginManager : public QObject { diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 139d9b282c..6e2732f1d8 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -132,8 +132,7 @@ int main(int argc, char** argv) { inputPlugin->activate(); auto userInputMapper = DependencyManager::get(); if (name == KeyboardMouseDevice::NAME) { - auto keyboardMouseDevice = static_cast(inputPlugin.data()); // TODO: this seems super hacky - userInputMapper->registerDevice(std::shared_ptr(keyboardMouseDevice)); + userInputMapper->registerDevice(std::dynamic_pointer_cast(inputPlugin)); } inputPlugin->pluginUpdate(0, false); } From e630f3072e6bdc4b4da6ffbf03014ffd4bebcc67 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 6 Nov 2015 11:14:10 -0800 Subject: [PATCH 3/4] Bit of cleanup --- libraries/controllers/src/controllers/ScriptingInterface.cpp | 2 +- libraries/controllers/src/controllers/UserInputMapper.h | 2 +- libraries/input-plugins/src/input-plugins/InputPlugin.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index a62172a730..bc4b0469f5 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -206,7 +206,7 @@ namespace controller { void ScriptingInterface::updateMaps() { QVariantMap newHardware; auto userInputMapper = DependencyManager::get(); - auto devices = userInputMapper->getDevices(); + const auto& devices = userInputMapper->getDevices(); for (const auto& deviceMapping : devices) { auto deviceID = deviceMapping.first; if (deviceID != userInputMapper->getStandardDeviceID()) { diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index c1dfcf5d33..d93a93016c 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -100,7 +100,7 @@ namespace controller { void setSensorToWorldMat(glm::mat4 sensorToWorldMat) { _sensorToWorldMat = sensorToWorldMat; } glm::mat4 getSensorToWorldMat() { return _sensorToWorldMat; } - DevicesMap getDevices() { return _registeredDevices; } + const DevicesMap& getDevices() { return _registeredDevices; } uint16 getStandardDeviceID() const { return STANDARD_DEVICE; } InputDevice::Pointer getStandardDevice() { return _registeredDevices[getStandardDeviceID()]; } diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 227bd12e1b..6db35572b5 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -29,7 +29,7 @@ InputPluginList getInputPlugins() { InputPluginList result; for (int i = 0; PLUGIN_POOL[i]; ++i) { - InputPlugin * plugin = PLUGIN_POOL[i]; + InputPlugin* plugin = PLUGIN_POOL[i]; if (plugin->isSupported()) { plugin->init(); result.push_back(InputPluginPointer(plugin)); From 0bf29a441f2bade95193f6d7bc2052e6091e66a6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 4 Nov 2015 18:41:54 -0800 Subject: [PATCH 4/4] Add recording classes --- interface/CMakeLists.txt | 2 +- libraries/recording/src/recording/Clip.cpp | 48 ++++++++ libraries/recording/src/recording/Clip.h | 48 ++++++++ libraries/recording/src/recording/Deck.cpp | 9 ++ libraries/recording/src/recording/Deck.h | 37 ++++++ libraries/recording/src/recording/Forward.h | 18 ++- libraries/recording/src/recording/Frame.cpp | 103 +++++++++++++++++ libraries/recording/src/recording/Frame.h | 40 +++++++ .../recording/src/recording/Recorder.cpp | 62 +++++++++++ libraries/recording/src/recording/Recorder.h | 55 +++++++++ .../src/recording/impl/BufferClip.cpp | 74 ++++++++++++ .../recording/src/recording/impl/BufferClip.h | 47 ++++++++ .../recording/src/recording/impl/FileClip.cpp | 105 ++++++++++++++++++ .../recording/src/recording/impl/FileClip.h | 62 +++++++++++ tests/recording/CMakeLists.txt | 10 ++ tests/recording/src/Constants.h | 19 ++++ tests/recording/src/FrameTests.cpp | 29 +++++ tests/recording/src/FrameTests.h | 21 ++++ tests/recording/src/RecorderTests.cpp | 25 +++++ tests/recording/src/RecorderTests.h | 21 ++++ 20 files changed, 832 insertions(+), 3 deletions(-) create mode 100644 libraries/recording/src/recording/Clip.cpp create mode 100644 libraries/recording/src/recording/Clip.h create mode 100644 libraries/recording/src/recording/Deck.cpp create mode 100644 libraries/recording/src/recording/Deck.h create mode 100644 libraries/recording/src/recording/Frame.cpp create mode 100644 libraries/recording/src/recording/Frame.h create mode 100644 libraries/recording/src/recording/Recorder.cpp create mode 100644 libraries/recording/src/recording/Recorder.h create mode 100644 libraries/recording/src/recording/impl/BufferClip.cpp create mode 100644 libraries/recording/src/recording/impl/BufferClip.h create mode 100644 libraries/recording/src/recording/impl/FileClip.cpp create mode 100644 libraries/recording/src/recording/impl/FileClip.h create mode 100644 tests/recording/CMakeLists.txt create mode 100644 tests/recording/src/Constants.h create mode 100644 tests/recording/src/FrameTests.cpp create mode 100644 tests/recording/src/FrameTests.h create mode 100644 tests/recording/src/RecorderTests.cpp create mode 100644 tests/recording/src/RecorderTests.h diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 930bdbd7ce..98a9dad909 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -102,7 +102,7 @@ endif() # link required hifi libraries link_hifi_libraries(shared octree environment gpu gl procedural model render - fbx networking model-networking entities avatars + recording fbx networking model-networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater controllers plugins display-plugins input-plugins ) diff --git a/libraries/recording/src/recording/Clip.cpp b/libraries/recording/src/recording/Clip.cpp new file mode 100644 index 0000000000..ef59532f09 --- /dev/null +++ b/libraries/recording/src/recording/Clip.cpp @@ -0,0 +1,48 @@ +// +// 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 "Clip.h" + +#include "Frame.h" + +#include "impl/FileClip.h" +#include "impl/BufferClip.h" + +using namespace recording; + +Clip::Pointer Clip::fromFile(const QString& filePath) { + return std::make_shared(filePath); +} + +void Clip::toFile(Clip::Pointer clip, const QString& filePath) { + // FIXME +} + +Clip::Pointer Clip::duplicate() { + Clip::Pointer result = std::make_shared(); + + float currentPosition = position(); + seek(0); + + Frame::Pointer frame = nextFrame(); + while (frame) { + result->appendFrame(frame); + } + + seek(currentPosition); + return result; +} + +#if 0 +Clip::Pointer Clip::fromIODevice(QIODevice * device) { + return std::make_shared(device); +} + +void Clip::fromIODevice(Clip::Pointer clip, QIODevice * device) { +} +#endif \ No newline at end of file diff --git a/libraries/recording/src/recording/Clip.h b/libraries/recording/src/recording/Clip.h new file mode 100644 index 0000000000..ca77ba8969 --- /dev/null +++ b/libraries/recording/src/recording/Clip.h @@ -0,0 +1,48 @@ +// +// 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 +// + +#pragma once +#ifndef hifi_Recording_Clip_h +#define hifi_Recording_Clip_h + +#include "Forward.h" + +#include + +class QIODevice; + +namespace recording { + +class Clip : public QObject { +public: + using Pointer = std::shared_ptr; + + Clip(QObject* parent = nullptr) : QObject(parent) {} + virtual ~Clip() {} + + Pointer duplicate(); + + virtual void seek(float offset) = 0; + virtual float position() const = 0; + + virtual FramePointer peekFrame() const = 0; + virtual FramePointer nextFrame() = 0; + virtual void skipFrame() = 0; + virtual void appendFrame(FramePointer) = 0; + + + static Pointer fromFile(const QString& filePath); + static void toFile(Pointer clip, const QString& filePath); + +protected: + virtual void reset() = 0; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/Deck.cpp b/libraries/recording/src/recording/Deck.cpp new file mode 100644 index 0000000000..a80fc43204 --- /dev/null +++ b/libraries/recording/src/recording/Deck.cpp @@ -0,0 +1,9 @@ +// +// 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 "Deck.h" diff --git a/libraries/recording/src/recording/Deck.h b/libraries/recording/src/recording/Deck.h new file mode 100644 index 0000000000..2ae8d6a7be --- /dev/null +++ b/libraries/recording/src/recording/Deck.h @@ -0,0 +1,37 @@ +// +// 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 +// + +#pragma once +#ifndef hifi_Recording_Deck_h +#define hifi_Recording_Deck_h + +#include "Forward.h" + +#include + +class QIODevice; + +namespace recording { + +class Deck : public QObject { +public: + using Pointer = std::shared_ptr; + + Deck(QObject* parent = nullptr) : QObject(parent) {} + virtual ~Deck(); + + // Place a clip on the deck for recording or playback + void queueClip(ClipPointer clip, float timeOffset = 0.0f); + void play(float timeOffset = 0.0f); + void reposition(float timeOffsetDelta); + void setPlaybackSpeed(float rate); +}; + +} + +#endif diff --git a/libraries/recording/src/recording/Forward.h b/libraries/recording/src/recording/Forward.h index 83a89da847..5bd6dd917f 100644 --- a/libraries/recording/src/recording/Forward.h +++ b/libraries/recording/src/recording/Forward.h @@ -15,14 +15,28 @@ namespace recording { +using FrameType = uint16_t; + +struct Frame; + +using FramePointer = std::shared_ptr; + // A recording of some set of state from the application, usually avatar // data + audio for a single person class Clip; -// An interface for interacting with clips, creating them by recording or -// playing them back. Also serialization to and from files / network sources +using ClipPointer = std::shared_ptr; + +// An interface for playing back clips class Deck; +using DeckPointer = std::shared_ptr; + +// An interface for recording a single clip +class Recorder; + +using RecorderPointer = std::shared_ptr; + } #endif diff --git a/libraries/recording/src/recording/Frame.cpp b/libraries/recording/src/recording/Frame.cpp new file mode 100644 index 0000000000..211f192c0a --- /dev/null +++ b/libraries/recording/src/recording/Frame.cpp @@ -0,0 +1,103 @@ +// +// 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 "Frame.h" + +#include + +#include + +using namespace recording; + +// FIXME move to shared +template +class Registry { +public: + using ForwardMap = QMap; + using BackMap = QMap; + static const Key INVALID_KEY = static_cast(-1); + + Key registerValue(const Value& value) { + Locker lock(_mutex); + Key result = INVALID_KEY; + if (_forwardMap.contains(value)) { + result = _forwardMap[value]; + } else { + _forwardMap[value] = result = _nextKey++; + _backMap[result] = value; + } + return result; + } + + Key getKey(const Value& value) { + Locker lock(_mutex); + Key result = INVALID_KEY; + if (_forwardMap.contains(value)) { + result = _forwardMap[value]; + } + return result; + } + + ForwardMap getKeysByValue() { + Locker lock(_mutex); + ForwardMap result = _forwardMap; + return result; + } + + BackMap getValuesByKey() { + Locker lock(_mutex); + BackMap result = _backMap; + return result; + } + +private: + using Mutex = std::mutex; + using Locker = std::unique_lock; + + Mutex _mutex; + + ForwardMap _forwardMap; + BackMap _backMap; + Key _nextKey { 0 }; +}; + +static Registry frameTypes; +static QMap handlerMap; +using Mutex = std::mutex; +using Locker = std::unique_lock; +static Mutex mutex; +static std::once_flag once; + + + +FrameType Frame::registerFrameType(const QString& frameTypeName) { + Locker lock(mutex); + std::call_once(once, [&] { + auto headerType = frameTypes.registerValue("com.highfidelity.recording.Header"); + Q_ASSERT(headerType == Frame::TYPE_HEADER); + }); + return frameTypes.registerValue(frameTypeName); +} + +QMap Frame::getFrameTypes() { + return frameTypes.getKeysByValue(); +} + +QMap Frame::getFrameTypeNames() { + return frameTypes.getValuesByKey(); +} + +Frame::Handler Frame::registerFrameHandler(FrameType type, Handler handler) { + Locker lock(mutex); + Handler result; + if (handlerMap.contains(type)) { + result = handlerMap[type]; + } + handlerMap[type] = handler; + return result; +} diff --git a/libraries/recording/src/recording/Frame.h b/libraries/recording/src/recording/Frame.h new file mode 100644 index 0000000000..2834637a6b --- /dev/null +++ b/libraries/recording/src/recording/Frame.h @@ -0,0 +1,40 @@ +// +// 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 +// + +#pragma once +#ifndef hifi_Recording_Frame_h +#define hifi_Recording_Frame_h + +#include "Forward.h" + +#include + +#include + +namespace recording { + +struct Frame { +public: + using Pointer = std::shared_ptr; + using Handler = std::function; + + static const FrameType TYPE_INVALID = 0xFFFF; + static const FrameType TYPE_HEADER = 0x0; + FrameType type { TYPE_INVALID }; + float timeOffset { 0 }; + QByteArray data; + + static FrameType registerFrameType(const QString& frameTypeName); + static QMap getFrameTypes(); + static QMap getFrameTypeNames(); + static Handler registerFrameHandler(FrameType type, Handler handler); +}; + +} + +#endif diff --git a/libraries/recording/src/recording/Recorder.cpp b/libraries/recording/src/recording/Recorder.cpp new file mode 100644 index 0000000000..a38e4252b9 --- /dev/null +++ b/libraries/recording/src/recording/Recorder.cpp @@ -0,0 +1,62 @@ +// +// 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 +// + +#include "Recorder.h" + +#include + +#include "impl/BufferClip.h" +#include "Frame.h" + +using namespace recording; + +void Recorder::start() { + if (!_recording) { + _recording = true; + if (!_clip) { + _clip = std::make_shared(); + } + _timer.start(); + emit recordingStateChanged(); + } +} + +void Recorder::stop() { + if (!_recording) { + _recording = false; + _elapsed = _timer.elapsed(); + emit recordingStateChanged(); + } +} + +bool Recorder::isRecording() { + return _recording; +} + +void Recorder::clear() { + _clip.reset(); +} + +void Recorder::recordFrame(FrameType type, QByteArray frameData) { + if (!_recording || !_clip) { + return; + } + + Frame::Pointer frame = std::make_shared(); + frame->type = type; + frame->data = frameData; + frame->timeOffset = (float)(_elapsed + _timer.elapsed()) / MSECS_PER_SECOND; + _clip->appendFrame(frame); +} + +ClipPointer Recorder::getClip() { + auto result = _clip; + _clip.reset(); + return result; +} + diff --git a/libraries/recording/src/recording/Recorder.h b/libraries/recording/src/recording/Recorder.h new file mode 100644 index 0000000000..deae543bb0 --- /dev/null +++ b/libraries/recording/src/recording/Recorder.h @@ -0,0 +1,55 @@ +// +// 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_Recorder_h +#define hifi_Recording_Recorder_h + +#include "Forward.h" + +#include +#include + +namespace recording { + +// An interface for interacting with clips, creating them by recording or +// playing them back. Also serialization to and from files / network sources +class Recorder : public QObject { +public: + using Pointer = std::shared_ptr; + + Recorder(QObject* parent = nullptr) : QObject(parent) {} + virtual ~Recorder(); + + // Start recording frames + void start(); + // Stop recording + void stop(); + // Test if recording is active + bool isRecording(); + // Erase the currently recorded content + void clear(); + + void recordFrame(FrameType type, QByteArray frameData); + + // Return the currently recorded content + ClipPointer getClip(); + +signals: + void recordingStateChanged(); + +private: + QElapsedTimer _timer; + ClipPointer _clip; + quint64 _elapsed; + bool _recording { false }; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/impl/BufferClip.cpp b/libraries/recording/src/recording/impl/BufferClip.cpp new file mode 100644 index 0000000000..378fee2558 --- /dev/null +++ b/libraries/recording/src/recording/impl/BufferClip.cpp @@ -0,0 +1,74 @@ +// +// 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 "BufferClip.h" + +#include "../Frame.h" + +using namespace recording; + + +void BufferClip::seek(float offset) { + Locker lock(_mutex); + auto itr = std::lower_bound(_frames.begin(), _frames.end(), offset, + [](Frame::Pointer a, float b)->bool{ + return a->timeOffset < b; + } + ); + _frameIndex = itr - _frames.begin(); +} + +float BufferClip::position() const { + Locker lock(_mutex); + float result = std::numeric_limits::max(); + if (_frameIndex < _frames.size()) { + result = _frames[_frameIndex]->timeOffset; + } + return result; +} + +FramePointer BufferClip::peekFrame() const { + Locker lock(_mutex); + FramePointer result; + if (_frameIndex < _frames.size()) { + result = _frames[_frameIndex]; + } + return result; +} + +FramePointer BufferClip::nextFrame() { + Locker lock(_mutex); + FramePointer result; + if (_frameIndex < _frames.size()) { + result = _frames[_frameIndex]; + ++_frameIndex; + } + return result; +} + +void BufferClip::appendFrame(FramePointer newFrame) { + auto currentPosition = position(); + seek(newFrame->timeOffset); + { + Locker lock(_mutex); + _frames.insert(_frames.begin() + _frameIndex, newFrame); + } + seek(currentPosition); +} + +void BufferClip::skipFrame() { + Locker lock(_mutex); + if (_frameIndex < _frames.size()) { + ++_frameIndex; + } +} + +void BufferClip::reset() { + Locker lock(_mutex); + _frameIndex = 0; +} diff --git a/libraries/recording/src/recording/impl/BufferClip.h b/libraries/recording/src/recording/impl/BufferClip.h new file mode 100644 index 0000000000..8fd78a9091 --- /dev/null +++ b/libraries/recording/src/recording/impl/BufferClip.h @@ -0,0 +1,47 @@ +// +// 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_BufferClip_h +#define hifi_Recording_Impl_BufferClip_h + +#include "../Clip.h" + +#include + +namespace recording { + +class BufferClip : public Clip { +public: + using Pointer = std::shared_ptr; + + BufferClip(QObject* parent = nullptr) : Clip(parent) {} + virtual ~BufferClip() {} + + virtual void seek(float offset) override; + virtual float position() const override; + + virtual FramePointer peekFrame() const override; + virtual FramePointer nextFrame() override; + virtual void skipFrame() override; + virtual void appendFrame(FramePointer) override; + +private: + using Mutex = std::mutex; + using Locker = std::unique_lock; + + virtual void reset() override; + + std::vector _frames; + mutable Mutex _mutex; + mutable size_t _frameIndex { 0 }; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/impl/FileClip.cpp b/libraries/recording/src/recording/impl/FileClip.cpp new file mode 100644 index 0000000000..354a97c5db --- /dev/null +++ b/libraries/recording/src/recording/impl/FileClip.cpp @@ -0,0 +1,105 @@ +// +// 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 "FileClip.h" + +#include "../Frame.h" + +#include + +using namespace recording; + +static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(float) + sizeof(uint16_t) + 1; + +FileClip::FileClip(const QString& fileName, QObject* parent) : Clip(parent), _file(fileName) { + auto size = _file.size(); + _map = _file.map(0, size, QFile::MapPrivateOption); + + auto current = _map; + auto end = current + size; + // Read all the frame headers + while (end - current < MINIMUM_FRAME_SIZE) { + FrameHeader header; + memcpy(&(header.type), current, sizeof(FrameType)); + current += sizeof(FrameType); + memcpy(&(header.timeOffset), current, sizeof(FrameType)); + current += sizeof(float); + memcpy(&(header.size), current, sizeof(uint16_t)); + current += sizeof(uint16_t); + header.fileOffset = current - _map; + if (end - current < header.size) { + break; + } + + _frameHeaders.push_back(header); + } +} + +FileClip::~FileClip() { + Locker lock(_mutex); + _file.unmap(_map); + _map = nullptr; +} + +void FileClip::seek(float offset) { + Locker lock(_mutex); + auto itr = std::lower_bound(_frameHeaders.begin(), _frameHeaders.end(), offset, + [](const FrameHeader& a, float b)->bool { + return a.timeOffset < b; + } + ); + _frameIndex = itr - _frameHeaders.begin(); +} + +float FileClip::position() const { + Locker lock(_mutex); + float result = std::numeric_limits::max(); + if (_frameIndex < _frameHeaders.size()) { + result = _frameHeaders[_frameIndex].timeOffset; + } + return result; +} + +FramePointer FileClip::readFrame(uint32_t frameIndex) const { + FramePointer result; + if (frameIndex < _frameHeaders.size()) { + result = std::make_shared(); + const FrameHeader& header = _frameHeaders[frameIndex]; + result->type = header.type; + result->timeOffset = header.timeOffset; + result->data.insert(0, reinterpret_cast(_map)+header.fileOffset, header.size); + } + return result; +} + +FramePointer FileClip::peekFrame() const { + Locker lock(_mutex); + return readFrame(_frameIndex); +} + +FramePointer FileClip::nextFrame() { + Locker lock(_mutex); + auto result = readFrame(_frameIndex); + if (_frameIndex < _frameHeaders.size()) { + ++_frameIndex; + } + return result; +} + +void FileClip::skipFrame() { + ++_frameIndex; +} + +void FileClip::reset() { + _frameIndex = 0; +} + +void FileClip::appendFrame(FramePointer) { + throw std::runtime_error("File clips are read only"); +} + diff --git a/libraries/recording/src/recording/impl/FileClip.h b/libraries/recording/src/recording/impl/FileClip.h new file mode 100644 index 0000000000..9b13adc9ef --- /dev/null +++ b/libraries/recording/src/recording/impl/FileClip.h @@ -0,0 +1,62 @@ +// +// 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_FileClip_h +#define hifi_Recording_Impl_FileClip_h + +#include "../Clip.h" + +#include + +#include + +namespace recording { + +class FileClip : public Clip { +public: + using Pointer = std::shared_ptr; + + FileClip(const QString& file, QObject* parent = nullptr); + virtual ~FileClip(); + + virtual void seek(float offset) override; + virtual float position() const override; + + virtual FramePointer peekFrame() const override; + virtual FramePointer nextFrame() override; + virtual void appendFrame(FramePointer) override; + virtual void skipFrame() override; + +private: + using Mutex = std::mutex; + using Locker = std::unique_lock; + + virtual void reset() override; + + struct FrameHeader { + FrameType type; + float timeOffset; + uint16_t size; + quint64 fileOffset; + }; + + using FrameHeaders = std::vector; + + FramePointer readFrame(uint32_t frameIndex) const; + + mutable Mutex _mutex; + QFile _file; + uint32_t _frameIndex { 0 }; + uchar* _map; + FrameHeaders _frameHeaders; +}; + +} + +#endif diff --git a/tests/recording/CMakeLists.txt b/tests/recording/CMakeLists.txt new file mode 100644 index 0000000000..f1e130956d --- /dev/null +++ b/tests/recording/CMakeLists.txt @@ -0,0 +1,10 @@ + +# Declare dependencies +macro (setup_testcase_dependencies) + # link in the shared libraries + link_hifi_libraries(shared recording) + + copy_dlls_beside_windows_executable() +endmacro () + +setup_hifi_testcase() diff --git a/tests/recording/src/Constants.h b/tests/recording/src/Constants.h new file mode 100644 index 0000000000..37758bfd68 --- /dev/null +++ b/tests/recording/src/Constants.h @@ -0,0 +1,19 @@ +// +// 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_Constants_h +#define hifi_Constants_h + +static const QString HEADER_NAME = "com.highfidelity.recording.Header"; +static const QString TEST_NAME = "com.highfidelity.recording.Test"; + +#endif // hifi_FrameTests_h + + diff --git a/tests/recording/src/FrameTests.cpp b/tests/recording/src/FrameTests.cpp new file mode 100644 index 0000000000..f61d45c197 --- /dev/null +++ b/tests/recording/src/FrameTests.cpp @@ -0,0 +1,29 @@ +// +// 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 +// + +#include "FrameTests.h" +#include "Constants.h" +#include "../QTestExtensions.h" + +#include + +QTEST_MAIN(FrameTests) + +void FrameTests::registerFrameTypeTest() { + auto result = recording::Frame::registerFrameType(TEST_NAME); + QCOMPARE(result, (recording::FrameType)1); + auto forwardMap = recording::Frame::getFrameTypes(); + QCOMPARE(forwardMap.count(TEST_NAME), 1); + QCOMPARE(forwardMap[TEST_NAME], result); + QCOMPARE(forwardMap[HEADER_NAME], recording::Frame::TYPE_HEADER); + auto backMap = recording::Frame::getFrameTypeNames(); + QCOMPARE(backMap.count(result), 1); + QCOMPARE(backMap[result], TEST_NAME); + QCOMPARE(backMap[recording::Frame::TYPE_HEADER], HEADER_NAME); +} + diff --git a/tests/recording/src/FrameTests.h b/tests/recording/src/FrameTests.h new file mode 100644 index 0000000000..04f73532a5 --- /dev/null +++ b/tests/recording/src/FrameTests.h @@ -0,0 +1,21 @@ +// +// 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_FrameTests_h +#define hifi_FrameTests_h + +#include + +class FrameTests : public QObject { + Q_OBJECT +private slots: + void registerFrameTypeTest(); +}; + +#endif // hifi_FrameTests_h diff --git a/tests/recording/src/RecorderTests.cpp b/tests/recording/src/RecorderTests.cpp new file mode 100644 index 0000000000..76b4b46577 --- /dev/null +++ b/tests/recording/src/RecorderTests.cpp @@ -0,0 +1,25 @@ +// +// 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 +// + +#include "RecorderTests.h" +#include "Constants.h" +#include "../QTestExtensions.h" + +#include + +QTEST_MAIN(RecorderTests) + +void RecorderTests::recorderTest() { + //auto recorder = std::make_shared(); + //QCOMPARE(recoreder.isRecording(), false); + //recorder.start(); + //QCOMPARE(recoreder.isRecording(), true); + //recorder.stop(); + //QCOMPARE(recoreder.isRecording(), false); +} + diff --git a/tests/recording/src/RecorderTests.h b/tests/recording/src/RecorderTests.h new file mode 100644 index 0000000000..8e97a828a2 --- /dev/null +++ b/tests/recording/src/RecorderTests.h @@ -0,0 +1,21 @@ +// +// 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_RecorderTests_h +#define hifi_RecorderTests_h + +#include + +class RecorderTests : public QObject { + Q_OBJECT +private slots: + void recorderTest(); +}; + +#endif