mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 05:09:23 +02:00
Adding clip serialization implementation, tests
This commit is contained in:
parent
8eea7ff67c
commit
4513b638db
17 changed files with 427 additions and 58 deletions
|
@ -16,33 +16,34 @@
|
||||||
using namespace recording;
|
using namespace recording;
|
||||||
|
|
||||||
Clip::Pointer Clip::fromFile(const QString& filePath) {
|
Clip::Pointer Clip::fromFile(const QString& filePath) {
|
||||||
return std::make_shared<FileClip>(filePath);
|
auto result = std::make_shared<FileClip>(filePath);
|
||||||
|
if (result->frameCount() == 0) {
|
||||||
|
return Clip::Pointer();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Clip::toFile(Clip::Pointer clip, const QString& filePath) {
|
void Clip::toFile(const QString& filePath, Clip::Pointer clip) {
|
||||||
// FIXME
|
FileClip::write(filePath, clip->duplicate());
|
||||||
|
}
|
||||||
|
|
||||||
|
Clip::Pointer Clip::newClip() {
|
||||||
|
return std::make_shared<BufferClip>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Clip::Pointer Clip::duplicate() {
|
Clip::Pointer Clip::duplicate() {
|
||||||
Clip::Pointer result = std::make_shared<BufferClip>();
|
Clip::Pointer result = std::make_shared<BufferClip>();
|
||||||
|
|
||||||
|
Locker lock(_mutex);
|
||||||
float currentPosition = position();
|
float currentPosition = position();
|
||||||
seek(0);
|
seek(0);
|
||||||
|
|
||||||
Frame::Pointer frame = nextFrame();
|
Frame::Pointer frame = nextFrame();
|
||||||
while (frame) {
|
while (frame) {
|
||||||
result->appendFrame(frame);
|
result->addFrame(frame);
|
||||||
|
frame = nextFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
seek(currentPosition);
|
seek(currentPosition);
|
||||||
return result;
|
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
|
|
|
@ -12,35 +12,44 @@
|
||||||
|
|
||||||
#include "Forward.h"
|
#include "Forward.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
|
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
|
|
||||||
namespace recording {
|
namespace recording {
|
||||||
|
|
||||||
class Clip : public QObject {
|
class Clip {
|
||||||
public:
|
public:
|
||||||
using Pointer = std::shared_ptr<Clip>;
|
using Pointer = std::shared_ptr<Clip>;
|
||||||
|
|
||||||
Clip(QObject* parent = nullptr) : QObject(parent) {}
|
|
||||||
virtual ~Clip() {}
|
virtual ~Clip() {}
|
||||||
|
|
||||||
Pointer duplicate();
|
Pointer duplicate();
|
||||||
|
|
||||||
|
virtual float duration() const = 0;
|
||||||
|
virtual size_t frameCount() const = 0;
|
||||||
|
|
||||||
virtual void seek(float offset) = 0;
|
virtual void seek(float offset) = 0;
|
||||||
virtual float position() const = 0;
|
virtual float position() const = 0;
|
||||||
|
|
||||||
virtual FramePointer peekFrame() const = 0;
|
virtual FramePointer peekFrame() const = 0;
|
||||||
virtual FramePointer nextFrame() = 0;
|
virtual FramePointer nextFrame() = 0;
|
||||||
virtual void skipFrame() = 0;
|
virtual void skipFrame() = 0;
|
||||||
virtual void appendFrame(FramePointer) = 0;
|
virtual void addFrame(FramePointer) = 0;
|
||||||
|
|
||||||
|
|
||||||
static Pointer fromFile(const QString& filePath);
|
static Pointer fromFile(const QString& filePath);
|
||||||
static void toFile(Pointer clip, const QString& filePath);
|
static void toFile(const QString& filePath, Pointer clip);
|
||||||
|
static Pointer newClip();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
using Mutex = std::recursive_mutex;
|
||||||
|
using Locker = std::unique_lock<Mutex>;
|
||||||
|
|
||||||
virtual void reset() = 0;
|
virtual void reset() = 0;
|
||||||
|
|
||||||
|
mutable Mutex _mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ public:
|
||||||
float timeOffset { 0 };
|
float timeOffset { 0 };
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
|
|
||||||
|
Frame() {}
|
||||||
|
Frame(FrameType type, float timeOffset, const QByteArray& data)
|
||||||
|
: type(type), timeOffset(timeOffset), data(data) {}
|
||||||
|
|
||||||
static FrameType registerFrameType(const QString& frameTypeName);
|
static FrameType registerFrameType(const QString& frameTypeName);
|
||||||
static QMap<QString, FrameType> getFrameTypes();
|
static QMap<QString, FrameType> getFrameTypes();
|
||||||
static QMap<FrameType, QString> getFrameTypeNames();
|
static QMap<FrameType, QString> getFrameTypeNames();
|
||||||
|
|
11
libraries/recording/src/recording/Logging.cpp
Normal file
11
libraries/recording/src/recording/Logging.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis 2015/10/11
|
||||||
|
// 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 "Logging.h"
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(recordingLog, "hifi.recording")
|
16
libraries/recording/src/recording/Logging.h
Normal file
16
libraries/recording/src/recording/Logging.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis 2015/10/11
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_Controllers_Logging_h
|
||||||
|
#define hifi_Controllers_Logging_h
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(recordingLog)
|
||||||
|
|
||||||
|
#endif
|
|
@ -51,7 +51,7 @@ void Recorder::recordFrame(FrameType type, QByteArray frameData) {
|
||||||
frame->type = type;
|
frame->type = type;
|
||||||
frame->data = frameData;
|
frame->data = frameData;
|
||||||
frame->timeOffset = (float)(_elapsed + _timer.elapsed()) / MSECS_PER_SECOND;
|
frame->timeOffset = (float)(_elapsed + _timer.elapsed()) / MSECS_PER_SECOND;
|
||||||
_clip->appendFrame(frame);
|
_clip->addFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipPointer Recorder::getClip() {
|
ClipPointer Recorder::getClip() {
|
||||||
|
|
|
@ -51,11 +51,15 @@ FramePointer BufferClip::nextFrame() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferClip::appendFrame(FramePointer newFrame) {
|
void BufferClip::addFrame(FramePointer newFrame) {
|
||||||
|
if (newFrame->timeOffset < 0.0f) {
|
||||||
|
throw std::runtime_error("Frames may not have negative time offsets");
|
||||||
|
}
|
||||||
auto currentPosition = position();
|
auto currentPosition = position();
|
||||||
seek(newFrame->timeOffset);
|
seek(newFrame->timeOffset);
|
||||||
{
|
{
|
||||||
Locker lock(_mutex);
|
Locker lock(_mutex);
|
||||||
|
|
||||||
_frames.insert(_frames.begin() + _frameIndex, newFrame);
|
_frames.insert(_frames.begin() + _frameIndex, newFrame);
|
||||||
}
|
}
|
||||||
seek(currentPosition);
|
seek(currentPosition);
|
||||||
|
@ -72,3 +76,15 @@ void BufferClip::reset() {
|
||||||
Locker lock(_mutex);
|
Locker lock(_mutex);
|
||||||
_frameIndex = 0;
|
_frameIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float BufferClip::duration() const {
|
||||||
|
if (_frames.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (*_frames.rbegin())->timeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BufferClip::frameCount() const {
|
||||||
|
return _frames.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,25 +20,23 @@ class BufferClip : public Clip {
|
||||||
public:
|
public:
|
||||||
using Pointer = std::shared_ptr<BufferClip>;
|
using Pointer = std::shared_ptr<BufferClip>;
|
||||||
|
|
||||||
BufferClip(QObject* parent = nullptr) : Clip(parent) {}
|
|
||||||
virtual ~BufferClip() {}
|
virtual ~BufferClip() {}
|
||||||
|
|
||||||
|
virtual float duration() const override;
|
||||||
|
virtual size_t frameCount() const override;
|
||||||
|
|
||||||
virtual void seek(float offset) override;
|
virtual void seek(float offset) override;
|
||||||
virtual float position() const override;
|
virtual float position() const override;
|
||||||
|
|
||||||
virtual FramePointer peekFrame() const override;
|
virtual FramePointer peekFrame() const override;
|
||||||
virtual FramePointer nextFrame() override;
|
virtual FramePointer nextFrame() override;
|
||||||
virtual void skipFrame() override;
|
virtual void skipFrame() override;
|
||||||
virtual void appendFrame(FramePointer) override;
|
virtual void addFrame(FramePointer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Mutex = std::mutex;
|
|
||||||
using Locker = std::unique_lock<Mutex>;
|
|
||||||
|
|
||||||
virtual void reset() override;
|
virtual void reset() override;
|
||||||
|
|
||||||
std::vector<FramePointer> _frames;
|
std::vector<FramePointer> _frames;
|
||||||
mutable Mutex _mutex;
|
|
||||||
mutable size_t _frameIndex { 0 };
|
mutable size_t _frameIndex { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,42 +8,197 @@
|
||||||
|
|
||||||
#include "FileClip.h"
|
#include "FileClip.h"
|
||||||
|
|
||||||
#include "../Frame.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
|
||||||
|
#include <Finally.h>
|
||||||
|
|
||||||
|
#include "../Frame.h"
|
||||||
|
#include "../Logging.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace recording;
|
using namespace recording;
|
||||||
|
|
||||||
static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(float) + sizeof(uint16_t) + 1;
|
static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(float) + sizeof(uint16_t);
|
||||||
|
|
||||||
FileClip::FileClip(const QString& fileName, QObject* parent) : Clip(parent), _file(fileName) {
|
static const QString FRAME_TYPE_MAP = QStringLiteral("frameTypes");
|
||||||
auto size = _file.size();
|
|
||||||
_map = _file.map(0, size, QFile::MapPrivateOption);
|
|
||||||
|
|
||||||
auto current = _map;
|
using FrameHeaderList = std::list<FileClip::FrameHeader>;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FrameHeaderList parseFrameHeaders(uchar* const start, const qint64& size) {
|
||||||
|
using FrameHeader = FileClip::FrameHeader;
|
||||||
|
FrameHeaderList results;
|
||||||
|
auto current = start;
|
||||||
auto end = current + size;
|
auto end = current + size;
|
||||||
// Read all the frame headers
|
// Read all the frame headers
|
||||||
while (end - current < MINIMUM_FRAME_SIZE) {
|
// FIXME move to Frame::readHeader?
|
||||||
|
while (end - current >= MINIMUM_FRAME_SIZE) {
|
||||||
FrameHeader header;
|
FrameHeader header;
|
||||||
memcpy(&(header.type), current, sizeof(FrameType));
|
memcpy(&(header.type), current, sizeof(FrameType));
|
||||||
current += sizeof(FrameType);
|
current += sizeof(FrameType);
|
||||||
memcpy(&(header.timeOffset), current, sizeof(FrameType));
|
memcpy(&(header.timeOffset), current, sizeof(float));
|
||||||
current += sizeof(float);
|
current += sizeof(float);
|
||||||
memcpy(&(header.size), current, sizeof(uint16_t));
|
memcpy(&(header.size), current, sizeof(uint16_t));
|
||||||
current += sizeof(uint16_t);
|
current += sizeof(uint16_t);
|
||||||
header.fileOffset = current - _map;
|
header.fileOffset = current - start;
|
||||||
if (end - current < header.size) {
|
if (end - current < header.size) {
|
||||||
|
current = end;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
current += header.size;
|
||||||
_frameHeaders.push_back(header);
|
results.push_back(header);
|
||||||
}
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FileClip::FileClip(const QString& fileName) : _file(fileName) {
|
||||||
|
auto size = _file.size();
|
||||||
|
bool opened = _file.open(QIODevice::ReadOnly);
|
||||||
|
if (!opened) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameHeaderList 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the loaded headers with the frame data
|
||||||
|
_frameHeaders.reserve(parsedFrameHeaders.size());
|
||||||
|
for (auto& frameHeader : parsedFrameHeaders) {
|
||||||
|
if (!translationMap.contains(frameHeader.type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
frameHeader.type = translationMap[frameHeader.type];
|
||||||
|
_frameHeaders.push_back(frameHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME move to frame?
|
||||||
|
bool writeFrame(QIODevice& output, const Frame& frame) {
|
||||||
|
auto written = output.write((char*)&(frame.type), sizeof(FrameType));
|
||||||
|
if (written != sizeof(FrameType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
written = output.write((char*)&(frame.timeOffset), sizeof(float));
|
||||||
|
if (written != sizeof(float)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint16_t dataSize = frame.data.size();
|
||||||
|
written = output.write((char*)&dataSize, sizeof(uint16_t));
|
||||||
|
if (written != sizeof(uint16_t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dataSize != 0) {
|
||||||
|
written = output.write(frame.data);
|
||||||
|
if (written != dataSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileClip::write(const QString& fileName, Clip::Pointer clip) {
|
||||||
|
qCDebug(recordingLog) << "Writing clip to file " << fileName;
|
||||||
|
|
||||||
|
if (0 == clip->frameCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile outputFile(fileName);
|
||||||
|
if (!outputFile.open(QFile::Truncate | QFile::WriteOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Finally closer([&] { outputFile.close(); });
|
||||||
|
{
|
||||||
|
auto frameTypes = Frame::getFrameTypes();
|
||||||
|
QJsonObject frameTypeObj;
|
||||||
|
for (const auto& frameTypeName : frameTypes.keys()) {
|
||||||
|
frameTypeObj[frameTypeName] = frameTypes[frameTypeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject rootObject;
|
||||||
|
rootObject.insert(FRAME_TYPE_MAP, frameTypeObj);
|
||||||
|
QByteArray headerFrameData = QJsonDocument(rootObject).toBinaryData();
|
||||||
|
if (!writeFrame(outputFile, Frame({ Frame::TYPE_HEADER, 0, headerFrameData }))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clip->seek(0);
|
||||||
|
for (auto frame = clip->nextFrame(); frame; frame = clip->nextFrame()) {
|
||||||
|
if (!writeFrame(outputFile, *frame)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputFile.close();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileClip::~FileClip() {
|
FileClip::~FileClip() {
|
||||||
Locker lock(_mutex);
|
Locker lock(_mutex);
|
||||||
_file.unmap(_map);
|
_file.unmap(_map);
|
||||||
_map = nullptr;
|
_map = nullptr;
|
||||||
|
if (_file.isOpen()) {
|
||||||
|
_file.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileClip::seek(float offset) {
|
void FileClip::seek(float offset) {
|
||||||
|
@ -72,7 +227,9 @@ FramePointer FileClip::readFrame(uint32_t frameIndex) const {
|
||||||
const FrameHeader& header = _frameHeaders[frameIndex];
|
const FrameHeader& header = _frameHeaders[frameIndex];
|
||||||
result->type = header.type;
|
result->type = header.type;
|
||||||
result->timeOffset = header.timeOffset;
|
result->timeOffset = header.timeOffset;
|
||||||
result->data.insert(0, reinterpret_cast<char*>(_map)+header.fileOffset, header.size);
|
if (header.size) {
|
||||||
|
result->data.insert(0, reinterpret_cast<char*>(_map)+header.fileOffset, header.size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -99,7 +256,18 @@ void FileClip::reset() {
|
||||||
_frameIndex = 0;
|
_frameIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileClip::appendFrame(FramePointer) {
|
void FileClip::addFrame(FramePointer) {
|
||||||
throw std::runtime_error("File clips are read only");
|
throw std::runtime_error("File clips are read only");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float FileClip::duration() const {
|
||||||
|
if (_frameHeaders.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return _frameHeaders.rbegin()->timeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FileClip::frameCount() const {
|
||||||
|
return _frameHeaders.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "../Clip.h"
|
#include "../Clip.h"
|
||||||
|
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
@ -22,22 +23,25 @@ class FileClip : public Clip {
|
||||||
public:
|
public:
|
||||||
using Pointer = std::shared_ptr<FileClip>;
|
using Pointer = std::shared_ptr<FileClip>;
|
||||||
|
|
||||||
FileClip(const QString& file, QObject* parent = nullptr);
|
FileClip(const QString& file);
|
||||||
virtual ~FileClip();
|
virtual ~FileClip();
|
||||||
|
|
||||||
|
virtual float duration() const override;
|
||||||
|
virtual size_t frameCount() const override;
|
||||||
|
|
||||||
virtual void seek(float offset) override;
|
virtual void seek(float offset) override;
|
||||||
virtual float position() const override;
|
virtual float position() const override;
|
||||||
|
|
||||||
virtual FramePointer peekFrame() const override;
|
virtual FramePointer peekFrame() const override;
|
||||||
virtual FramePointer nextFrame() override;
|
virtual FramePointer nextFrame() override;
|
||||||
virtual void appendFrame(FramePointer) override;
|
|
||||||
virtual void skipFrame() override;
|
virtual void skipFrame() override;
|
||||||
|
virtual void addFrame(FramePointer) override;
|
||||||
|
|
||||||
private:
|
const QJsonDocument& getHeader() {
|
||||||
using Mutex = std::mutex;
|
return _fileHeader;
|
||||||
using Locker = std::unique_lock<Mutex>;
|
}
|
||||||
|
|
||||||
virtual void reset() override;
|
static bool write(const QString& filePath, Clip::Pointer clip);
|
||||||
|
|
||||||
struct FrameHeader {
|
struct FrameHeader {
|
||||||
FrameType type;
|
FrameType type;
|
||||||
|
@ -46,15 +50,20 @@ private:
|
||||||
quint64 fileOffset;
|
quint64 fileOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
using FrameHeaders = std::vector<FrameHeader>;
|
private:
|
||||||
|
|
||||||
|
virtual void reset() override;
|
||||||
|
|
||||||
|
|
||||||
|
using FrameHeaderVector = std::vector<FrameHeader>;
|
||||||
|
|
||||||
FramePointer readFrame(uint32_t frameIndex) const;
|
FramePointer readFrame(uint32_t frameIndex) const;
|
||||||
|
|
||||||
mutable Mutex _mutex;
|
QJsonDocument _fileHeader;
|
||||||
QFile _file;
|
QFile _file;
|
||||||
uint32_t _frameIndex { 0 };
|
uint32_t _frameIndex { 0 };
|
||||||
uchar* _map;
|
uchar* _map { nullptr };
|
||||||
FrameHeaders _frameHeaders;
|
FrameHeaderVector _frameHeaders;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
|
set(TARGET_NAME recording-test)
|
||||||
|
# This is not a testcase -- just set it up as a regular hifi project
|
||||||
|
setup_hifi_project(Test)
|
||||||
|
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
||||||
|
link_hifi_libraries(shared recording)
|
||||||
|
copy_dlls_beside_windows_executable()
|
||||||
|
|
||||||
|
# FIXME convert to unit tests
|
||||||
# Declare dependencies
|
# Declare dependencies
|
||||||
macro (setup_testcase_dependencies)
|
#macro (setup_testcase_dependencies)
|
||||||
# link in the shared libraries
|
# # link in the shared libraries
|
||||||
link_hifi_libraries(shared recording)
|
# link_hifi_libraries(shared recording)
|
||||||
|
#
|
||||||
copy_dlls_beside_windows_executable()
|
# copy_dlls_beside_windows_executable()
|
||||||
endmacro ()
|
#endmacro ()
|
||||||
|
#setup_hifi_testcase()
|
||||||
setup_hifi_testcase()
|
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#ifndef hifi_Constants_h
|
#ifndef hifi_Constants_h
|
||||||
#define hifi_Constants_h
|
#define hifi_Constants_h
|
||||||
|
|
||||||
|
#include <QtCore/QString>
|
||||||
|
|
||||||
static const QString HEADER_NAME = "com.highfidelity.recording.Header";
|
static const QString HEADER_NAME = "com.highfidelity.recording.Header";
|
||||||
static const QString TEST_NAME = "com.highfidelity.recording.Test";
|
static const QString TEST_NAME = "com.highfidelity.recording.Test";
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
#include "FrameTests.h"
|
#include "FrameTests.h"
|
||||||
#include "Constants.h"
|
#include "Constants.h"
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
#include "../QTestExtensions.h"
|
#include "../QTestExtensions.h"
|
||||||
|
|
||||||
#include <recording/Frame.h>
|
#include <recording/Frame.h>
|
||||||
|
@ -27,3 +30,4 @@ void FrameTests::registerFrameTypeTest() {
|
||||||
QCOMPARE(backMap[recording::Frame::TYPE_HEADER], HEADER_NAME);
|
QCOMPARE(backMap[recording::Frame::TYPE_HEADER], HEADER_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#ifndef hifi_FrameTests_h
|
#ifndef hifi_FrameTests_h
|
||||||
#define hifi_FrameTests_h
|
#define hifi_FrameTests_h
|
||||||
|
|
||||||
|
#if 0
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
class FrameTests : public QObject {
|
class FrameTests : public QObject {
|
||||||
|
@ -18,4 +19,6 @@ private slots:
|
||||||
void registerFrameTypeTest();
|
void registerFrameTypeTest();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // hifi_FrameTests_h
|
#endif // hifi_FrameTests_h
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
#include "RecorderTests.h"
|
#include "RecorderTests.h"
|
||||||
#include "Constants.h"
|
#include "Constants.h"
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
#include "../QTestExtensions.h"
|
#include "../QTestExtensions.h"
|
||||||
|
|
||||||
#include <recording/Recorder.h>
|
#include <recording/Recorder.h>
|
||||||
|
@ -23,3 +26,4 @@ void RecorderTests::recorderTest() {
|
||||||
//QCOMPARE(recoreder.isRecording(), false);
|
//QCOMPARE(recoreder.isRecording(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#ifndef hifi_RecorderTests_h
|
#ifndef hifi_RecorderTests_h
|
||||||
#define hifi_RecorderTests_h
|
#define hifi_RecorderTests_h
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
class RecorderTests : public QObject {
|
class RecorderTests : public QObject {
|
||||||
|
@ -19,3 +21,5 @@ private slots:
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
114
tests/recording/src/main.cpp
Normal file
114
tests/recording/src/main.cpp
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QtTest/QtTest>
|
||||||
|
#include <QtCore/QTemporaryFile>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <recording/Clip.h>
|
||||||
|
#include <recording/Frame.h>
|
||||||
|
|
||||||
|
#include "Constants.h"
|
||||||
|
|
||||||
|
#define QVERIFY Q_ASSERT
|
||||||
|
|
||||||
|
using namespace recording;
|
||||||
|
FrameType TEST_FRAME_TYPE { Frame::TYPE_INVALID };
|
||||||
|
|
||||||
|
void testFrameTypeRegistration() {
|
||||||
|
TEST_FRAME_TYPE = Frame::registerFrameType(TEST_NAME);
|
||||||
|
QVERIFY(TEST_FRAME_TYPE != Frame::TYPE_INVALID);
|
||||||
|
QVERIFY(TEST_FRAME_TYPE != Frame::TYPE_HEADER);
|
||||||
|
|
||||||
|
auto forwardMap = recording::Frame::getFrameTypes();
|
||||||
|
QVERIFY(forwardMap.count(TEST_NAME) == 1);
|
||||||
|
QVERIFY(forwardMap[TEST_NAME] == TEST_FRAME_TYPE);
|
||||||
|
QVERIFY(forwardMap[HEADER_NAME] == recording::Frame::TYPE_HEADER);
|
||||||
|
|
||||||
|
auto backMap = recording::Frame::getFrameTypeNames();
|
||||||
|
QVERIFY(backMap.count(TEST_FRAME_TYPE) == 1);
|
||||||
|
QVERIFY(backMap[TEST_FRAME_TYPE] == TEST_NAME);
|
||||||
|
QVERIFY(backMap[recording::Frame::TYPE_HEADER] == HEADER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testFilePersist() {
|
||||||
|
QTemporaryFile file;
|
||||||
|
QString fileName;
|
||||||
|
if (file.open()) {
|
||||||
|
fileName = file.fileName();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
auto readClip = Clip::fromFile(fileName);
|
||||||
|
QVERIFY(Clip::Pointer() == readClip);
|
||||||
|
auto writeClip = Clip::newClip();
|
||||||
|
writeClip->addFrame(std::make_shared<Frame>(TEST_FRAME_TYPE, 5.0f, QByteArray()));
|
||||||
|
QVERIFY(writeClip->frameCount() == 1);
|
||||||
|
QVERIFY(writeClip->duration() == 5.0f);
|
||||||
|
|
||||||
|
Clip::toFile(fileName, writeClip);
|
||||||
|
readClip = Clip::fromFile(fileName);
|
||||||
|
QVERIFY(readClip != Clip::Pointer());
|
||||||
|
QVERIFY(readClip->frameCount() == 1);
|
||||||
|
QVERIFY(readClip->duration() == 5.0f);
|
||||||
|
readClip->seek(0);
|
||||||
|
writeClip->seek(0);
|
||||||
|
|
||||||
|
size_t count = 0;
|
||||||
|
for (auto readFrame = readClip->nextFrame(), writeFrame = writeClip->nextFrame(); readFrame && writeFrame;
|
||||||
|
readFrame = readClip->nextFrame(), writeFrame = writeClip->nextFrame(), ++count) {
|
||||||
|
QVERIFY(readFrame->type == writeFrame->type);
|
||||||
|
QVERIFY(readFrame->timeOffset == writeFrame->timeOffset);
|
||||||
|
QVERIFY(readFrame->data == writeFrame->data);
|
||||||
|
}
|
||||||
|
QVERIFY(readClip->frameCount() == count);
|
||||||
|
|
||||||
|
|
||||||
|
writeClip = Clip::newClip();
|
||||||
|
writeClip->addFrame(std::make_shared<Frame>(TEST_FRAME_TYPE, 5.0f, QByteArray()));
|
||||||
|
// Simulate an unknown frametype
|
||||||
|
writeClip->addFrame(std::make_shared<Frame>(Frame::TYPE_INVALID - 1, 10.0f, QByteArray()));
|
||||||
|
QVERIFY(writeClip->frameCount() == 2);
|
||||||
|
QVERIFY(writeClip->duration() == 10.0f);
|
||||||
|
Clip::toFile(fileName, writeClip);
|
||||||
|
|
||||||
|
// Verify that the read version of the clip ignores the unknown frame type
|
||||||
|
readClip = Clip::fromFile(fileName);
|
||||||
|
QVERIFY(readClip != Clip::Pointer());
|
||||||
|
QVERIFY(readClip->frameCount() == 1);
|
||||||
|
QVERIFY(readClip->duration() == 5.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testClipOrdering() {
|
||||||
|
auto writeClip = Clip::newClip();
|
||||||
|
// simulate our of order addition of frames
|
||||||
|
writeClip->addFrame(std::make_shared<Frame>(TEST_FRAME_TYPE, 10.0f, QByteArray()));
|
||||||
|
writeClip->addFrame(std::make_shared<Frame>(TEST_FRAME_TYPE, 5.0f, QByteArray()));
|
||||||
|
QVERIFY(writeClip->frameCount() == 2);
|
||||||
|
QVERIFY(writeClip->duration() == 10.0f);
|
||||||
|
|
||||||
|
QVERIFY(std::numeric_limits<float>::max() == writeClip->position());
|
||||||
|
writeClip->seek(0);
|
||||||
|
QVERIFY(5.0f == writeClip->position());
|
||||||
|
float lastFrameTimeOffset { 0 };
|
||||||
|
for (auto writeFrame = writeClip->nextFrame(); writeFrame; writeFrame = writeClip->nextFrame()) {
|
||||||
|
QVERIFY(writeClip->position() >= lastFrameTimeOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
void myMessageHandler(QtMsgType type, const QMessageLogContext & context, const QString & msg) {
|
||||||
|
OutputDebugStringA(msg.toLocal8Bit().toStdString().c_str());
|
||||||
|
OutputDebugStringA("\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int, const char**) {
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
qInstallMessageHandler(myMessageHandler);
|
||||||
|
#endif
|
||||||
|
testFrameTypeRegistration();
|
||||||
|
testFilePersist();
|
||||||
|
testClipOrdering();
|
||||||
|
}
|
Loading…
Reference in a new issue