Add recording classes

This commit is contained in:
Brad Davis 2015-11-04 18:41:54 -08:00
parent 5713f19fa4
commit 0bf29a441f
20 changed files with 832 additions and 3 deletions

View file

@ -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 )

View file

@ -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<FileClip>(filePath);
}
void Clip::toFile(Clip::Pointer clip, const QString& filePath) {
// FIXME
}
Clip::Pointer Clip::duplicate() {
Clip::Pointer result = std::make_shared<BufferClip>();
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<IOClip>(device);
}
void Clip::fromIODevice(Clip::Pointer clip, QIODevice * device) {
}
#endif

View file

@ -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 <QtCore/QObject>
class QIODevice;
namespace recording {
class Clip : public QObject {
public:
using Pointer = std::shared_ptr<Clip>;
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

View file

@ -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"

View file

@ -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 <QtCore/QObject>
class QIODevice;
namespace recording {
class Deck : public QObject {
public:
using Pointer = std::shared_ptr<Deck>;
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

View file

@ -15,14 +15,28 @@
namespace recording {
using FrameType = uint16_t;
struct Frame;
using FramePointer = std::shared_ptr<Frame>;
// 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<Clip>;
// An interface for playing back clips
class Deck;
using DeckPointer = std::shared_ptr<Deck>;
// An interface for recording a single clip
class Recorder;
using RecorderPointer = std::shared_ptr<Recorder>;
}
#endif

View file

@ -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 <mutex>
#include <QtCore/QMap>
using namespace recording;
// FIXME move to shared
template <typename Key, typename Value>
class Registry {
public:
using ForwardMap = QMap<Value, Key>;
using BackMap = QMap<Key, Value>;
static const Key INVALID_KEY = static_cast<Key>(-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 _mutex;
ForwardMap _forwardMap;
BackMap _backMap;
Key _nextKey { 0 };
};
static Registry<FrameType, QString> frameTypes;
static QMap<FrameType, Frame::Handler> handlerMap;
using Mutex = std::mutex;
using Locker = std::unique_lock<Mutex>;
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<QString, FrameType> Frame::getFrameTypes() {
return frameTypes.getKeysByValue();
}
QMap<FrameType, QString> 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;
}

View file

@ -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 <functional>
#include <QtCore/QObject>
namespace recording {
struct Frame {
public:
using Pointer = std::shared_ptr<Frame>;
using Handler = std::function<void(Frame::Pointer frame)>;
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<QString, FrameType> getFrameTypes();
static QMap<FrameType, QString> getFrameTypeNames();
static Handler registerFrameHandler(FrameType type, Handler handler);
};
}
#endif

View file

@ -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 <NumericalConstants.h>
#include "impl/BufferClip.h"
#include "Frame.h"
using namespace recording;
void Recorder::start() {
if (!_recording) {
_recording = true;
if (!_clip) {
_clip = std::make_shared<BufferClip>();
}
_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>();
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;
}

View file

@ -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 <QtCore/QObject>
#include <QtCore/QElapsedTimer>
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>;
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

View file

@ -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<float>::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;
}

View file

@ -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 <mutex>
namespace recording {
class BufferClip : public Clip {
public:
using Pointer = std::shared_ptr<BufferClip>;
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<Mutex>;
virtual void reset() override;
std::vector<FramePointer> _frames;
mutable Mutex _mutex;
mutable size_t _frameIndex { 0 };
};
}
#endif

View file

@ -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 <algorithm>
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<float>::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<Frame>();
const FrameHeader& header = _frameHeaders[frameIndex];
result->type = header.type;
result->timeOffset = header.timeOffset;
result->data.insert(0, reinterpret_cast<char*>(_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");
}

View file

@ -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 <QtCore/QFile>
#include <mutex>
namespace recording {
class FileClip : public Clip {
public:
using Pointer = std::shared_ptr<FileClip>;
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<Mutex>;
virtual void reset() override;
struct FrameHeader {
FrameType type;
float timeOffset;
uint16_t size;
quint64 fileOffset;
};
using FrameHeaders = std::vector<FrameHeader>;
FramePointer readFrame(uint32_t frameIndex) const;
mutable Mutex _mutex;
QFile _file;
uint32_t _frameIndex { 0 };
uchar* _map;
FrameHeaders _frameHeaders;
};
}
#endif

View file

@ -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()

View file

@ -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

View file

@ -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 <recording/Frame.h>
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);
}

View file

@ -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 <QtTest/QtTest>
class FrameTests : public QObject {
Q_OBJECT
private slots:
void registerFrameTypeTest();
};
#endif // hifi_FrameTests_h

View file

@ -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 <recording/Recorder.h>
QTEST_MAIN(RecorderTests)
void RecorderTests::recorderTest() {
//auto recorder = std::make_shared<recording::Recorder>();
//QCOMPARE(recoreder.isRecording(), false);
//recorder.start();
//QCOMPARE(recoreder.isRecording(), true);
//recorder.stop();
//QCOMPARE(recoreder.isRecording(), false);
}

View file

@ -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 <QtTest/QtTest>
class RecorderTests : public QObject {
Q_OBJECT
private slots:
void recorderTest();
};
#endif