mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-17 01:49:55 +00:00
Add rlv file parser for Video synchronisation feature - Work In Progress
This commit is contained in:
262
src/VideoSyncFile.cpp
Normal file
262
src/VideoSyncFile.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com)
|
||||
* Copyright (c) 2015 Vianney Boyer (vlcvboyer@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "VideoSyncFile.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "Units.h"
|
||||
|
||||
// Supported file types
|
||||
static QStringList supported;
|
||||
static bool setSupported()
|
||||
{
|
||||
::supported << ".rlv";
|
||||
//TODO ::supported << ".gpx";
|
||||
return true;
|
||||
}
|
||||
static bool isinit = setSupported();
|
||||
bool VideoSyncFile::isVideoSync(QString name)
|
||||
{
|
||||
foreach(QString extension, supported) {
|
||||
if (name.endsWith(extension, Qt::CaseInsensitive))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
VideoSyncFile::VideoSyncFile(QString filename, int &mode, Context *context) :
|
||||
filename(filename), context(context), mode(mode)
|
||||
{
|
||||
reload();
|
||||
}
|
||||
|
||||
VideoSyncFile::VideoSyncFile(Context *context) : context(context), mode(nomode)
|
||||
{
|
||||
filename ="";
|
||||
}
|
||||
|
||||
void VideoSyncFile::reload()
|
||||
{
|
||||
// which parser to call?
|
||||
if (filename.endsWith(".rlv", Qt::CaseInsensitive)) parseRLV();
|
||||
//TODO else if (filename.endsWith(".gpx", Qt::CaseInsensitive)) parseGPX();
|
||||
}
|
||||
|
||||
void VideoSyncFile::parseRLV()
|
||||
{
|
||||
// Initialise
|
||||
Version = "";
|
||||
Units = "";
|
||||
Filename = "";
|
||||
Name = "";
|
||||
Duration = -1;
|
||||
valid = false; // did it parse ok?
|
||||
rightPoint = leftPoint = 0;
|
||||
format = RLV; // default to rlv until we know
|
||||
Points.clear();
|
||||
|
||||
// running totals
|
||||
double rdist = 0; // running total for distance
|
||||
|
||||
// open the file for binary reading and open a datastream
|
||||
QFile RLVFile(filename);
|
||||
if (RLVFile.open(QIODevice::ReadOnly) == false) return;
|
||||
QDataStream input(&RLVFile);
|
||||
input.setByteOrder(QDataStream::LittleEndian);
|
||||
input.setVersion(QDataStream::Qt_4_0); // 32 bit floats not 64 bit.
|
||||
|
||||
bool happy = true; // are we ok to continue reading?
|
||||
|
||||
//
|
||||
// BASIC DATA STRUCTURES
|
||||
//
|
||||
struct {
|
||||
uint16_t fingerprint;
|
||||
uint16_t version;
|
||||
uint32_t blocks;
|
||||
} header; // file header
|
||||
|
||||
struct {
|
||||
uint16_t type;
|
||||
uint16_t version;
|
||||
uint32_t records;
|
||||
uint32_t recordSize;
|
||||
} info; // tells us what to read
|
||||
|
||||
struct {
|
||||
quint8 videoFileName[522];
|
||||
float frameRate;
|
||||
uint32_t orgrunWeight;
|
||||
uint32_t frameOffset;
|
||||
} videoInfo; // type 2010
|
||||
|
||||
struct {
|
||||
uint32_t frameNbr;
|
||||
float distance;
|
||||
} framemapping; // type 2020
|
||||
|
||||
struct {
|
||||
uint32_t frame;
|
||||
uint32_t cmd;
|
||||
} blockInfo; // type 2030
|
||||
|
||||
struct {
|
||||
float start;
|
||||
float end;
|
||||
quint8 segmentName[66];
|
||||
quint8 textFile[522];
|
||||
} courseInfo; // type 2040
|
||||
|
||||
|
||||
//
|
||||
// FILE HEADER
|
||||
//
|
||||
int rc = input.readRawData((char*)&header, sizeof(header));
|
||||
if (rc == sizeof(header)) {
|
||||
if (header.fingerprint == 2000) happy = true; //FIXME && header.version == ???
|
||||
else happy = false;
|
||||
} else happy = false;
|
||||
|
||||
unsigned int block = 0; // keep track of how many blocks we have read
|
||||
|
||||
//
|
||||
// READ THE BLOCKS INSIDE THE FILE
|
||||
//
|
||||
while (happy && block < header.blocks) {
|
||||
|
||||
// read the info for this block
|
||||
rc = input.readRawData((char*)&info, sizeof(info));
|
||||
if (rc == sizeof(info)) {
|
||||
|
||||
// okay now read that block
|
||||
switch (info.type) {
|
||||
|
||||
case 2010 : // videoInfo
|
||||
{
|
||||
// we read member by member to avoid struct word alignment problem caused
|
||||
// by the string length
|
||||
// FIXME ? input>>general.checksum;
|
||||
input.readRawData((char*)&videoInfo.videoFileName[0], 522);
|
||||
input>>videoInfo.frameRate;
|
||||
input>>videoInfo.orgrunWeight;
|
||||
input>>videoInfo.frameOffset;
|
||||
|
||||
VideoFrameRate = videoInfo.frameRate;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2020 : // frame mapping
|
||||
{
|
||||
// read in the mapping records
|
||||
uint32_t PreviousFrameNbr=0;
|
||||
for (unsigned int record=0; record < info.records; record++) {
|
||||
// get the next record
|
||||
if (sizeof(framemapping) != input.readRawData((char*)&framemapping, sizeof(framemapping))) {
|
||||
happy = false;
|
||||
break;
|
||||
}
|
||||
|
||||
VideoSyncFilePoint add;
|
||||
|
||||
if (format == RLV) {
|
||||
double distance = framemapping.distance; // in meters
|
||||
rdist += distance * (double) (framemapping.frameNbr - PreviousFrameNbr);
|
||||
add.x = rdist;
|
||||
|
||||
// time
|
||||
add.t = (float) framemapping.frameNbr / VideoFrameRate; //FIXME : are we sure that video frame rate will be given prior to mapping?
|
||||
// FIXME : do we have to consider video offset and weight ?
|
||||
PreviousFrameNbr = framemapping.frameNbr;
|
||||
}
|
||||
|
||||
Points.append(add);
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 2030 : // rlv info
|
||||
{
|
||||
// read in the mapping records
|
||||
for (unsigned int record=0; record < info.records; record++) {
|
||||
|
||||
// get the next record
|
||||
if (sizeof(blockInfo) != input.readRawData((char*)&blockInfo, sizeof(blockInfo))) {
|
||||
happy = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 2040 : // course info
|
||||
{
|
||||
// read in the mapping records
|
||||
for (unsigned int record=0; record < info.records; record++) {
|
||||
|
||||
// get the next record
|
||||
if (sizeof(courseInfo) != input.readRawData((char*)&courseInfo, sizeof(courseInfo))) {
|
||||
happy = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// FIXME : add those data to training messages
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default: // unexpected block type
|
||||
happy = false;
|
||||
break;
|
||||
}
|
||||
|
||||
block++;
|
||||
|
||||
} else happy = false;
|
||||
}
|
||||
|
||||
// done
|
||||
RLVFile.close();
|
||||
|
||||
// if we got here and are still happy then it
|
||||
// must have been a valid file.
|
||||
if (happy) {
|
||||
valid = true;
|
||||
|
||||
// set RLVFile duration
|
||||
Duration = Points.last().x; // last is the end point in msecs // FIXME : msec or sec ?
|
||||
leftPoint = 0;
|
||||
rightPoint = 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
VideoSyncFile::~VideoSyncFile()
|
||||
{
|
||||
Points.clear();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
VideoSyncFile::isValid()
|
||||
{
|
||||
return valid;
|
||||
}
|
||||
Reference in New Issue
Block a user