Load recorded clips from URLs, not file paths

This commit is contained in:
Brad Davis 2015-11-19 15:56:37 -08:00
parent f1b5589813
commit e187aaedcb
9 changed files with 338 additions and 171 deletions

View file

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

View file

@ -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<NetworkClip>(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<NetworkClipLoader>();
}
QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared);
}

View file

@ -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 <ResourceCache.h>
#include "Forward.h"
#include "impl/PointerClip.h"
namespace recording {
class NetworkClip : public PointerClip {
public:
using Pointer = std::shared_ptr<NetworkClip>;
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<NetworkClipLoader>;
class ClipCache : public ResourceCache {
public:
static ClipCache& instance();
NetworkClipLoaderPointer getClipLoader(const QUrl& url);
protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) override;
};
}
#endif

View file

@ -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<FrameType, FrameType>;
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<FrameType>(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<Frame>();
const auto& header = _frames[frameIndex];
result->type = header.type;
result->timeOffset = header.timeOffset;
if (header.size) {
result->data.insert(0, reinterpret_cast<char*>(_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();
}

View file

@ -10,27 +10,13 @@
#ifndef hifi_Recording_Impl_FileClip_h
#define hifi_Recording_Impl_FileClip_h
#include "ArrayClip.h"
#include <mutex>
#include "PointerClip.h"
#include <QtCore/QFile>
#include <QtCore/QJsonDocument>
#include "../Frame.h"
namespace recording {
struct FileFrameHeader : public FrameHeader {
FrameType type;
Frame::Time timeOffset;
uint16_t size;
quint64 fileOffset;
};
using FileFrameHeaderList = std::list<FileFrameHeader>;
class FileClip : public ArrayClip<FileFrameHeader> {
class FileClip : public PointerClip {
public:
using Pointer = std::shared_ptr<FileClip>;
@ -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 };
};
}

View file

@ -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 <algorithm>
#include <QtCore/QDebug>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <Finally.h>
#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<FrameType, FrameType>;
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<FrameType>(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<Frame>();
const auto& header = _frames[frameIndex];
result->type = header.type;
result->timeOffset = header.timeOffset;
if (header.size) {
result->data.insert(0, reinterpret_cast<char*>(_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");
}

View file

@ -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 <mutex>
#include <QtCore/QJsonDocument>
#include "../Frame.h"
namespace recording {
struct PointerFrameHeader : public FrameHeader {
FrameType type;
Frame::Time timeOffset;
uint16_t size;
quint64 fileOffset;
};
using PointerFrameHeaderList = std::list<PointerFrameHeader>;
class PointerClip : public ArrayClip<PointerFrameHeader> {
public:
using Pointer = std::shared_ptr<PointerClip>;
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

View file

@ -8,14 +8,15 @@
#include "RecordingScriptingInterface.h"
#include <QThread>
#include <QtCore/QThread>
#include <NumericalConstants.h>
#include <Transform.h>
#include <recording/Deck.h>
#include <recording/Recorder.h>
#include <recording/Clip.h>
#include <recording/Frame.h>
#include <NumericalConstants.h>
#include <Transform.h>
#include <recording/ClipCache.h>
#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() {

View file

@ -12,7 +12,7 @@
#include <atomic>
#include <mutex>
#include <QObject>
#include <QtCore/QObject>
#include <DependencyManager.h>
#include <recording/Forward.h>
@ -25,7 +25,7 @@ public:
RecordingScriptingInterface();
public slots:
void loadRecording(const QString& filename);
void loadRecording(const QString& url);
void startPlaying();
void pausePlayer();