diff --git a/src/Train/ErgFile.cpp b/src/Train/ErgFile.cpp index c0e2867a3..0ab214349 100644 --- a/src/Train/ErgFile.cpp +++ b/src/Train/ErgFile.cpp @@ -401,6 +401,13 @@ void ErgFile::parseTacx() // if we got here and are still happy then it // must have been a valid file. if (happy) { + // Set the end point for the workout + ErgFilePoint end; + end.x = rdist; + end.y = Points.last().y; + end.val = Points.last().val; + Points.append(end); + valid = true; // set ErgFile duration diff --git a/src/Train/TrainSidebar.cpp b/src/Train/TrainSidebar.cpp index f60d8ee35..6493f03dc 100644 --- a/src/Train/TrainSidebar.cpp +++ b/src/Train/TrainSidebar.cpp @@ -1667,9 +1667,14 @@ void TrainSidebar::guiUpdate() // refreshes the telemetry displayLapDistance += distanceTick; displayLapDistanceRemaining -= distanceTick; - - if (!(status&RT_MODE_ERGO) && (context->currentVideoSyncFile())) { - displayWorkoutDistance = context->currentVideoSyncFile()->km + context->currentVideoSyncFile()->manualOffset; + if (!(status&RT_MODE_ERGO) && (context->currentVideoSyncFile())) + { + displayWorkoutDistance = context->currentVideoSyncFile()->km; + // If we reached the end of the RLV then stop + if (displayWorkoutDistance >= context->currentVideoSyncFile()->Distance) { + Stop(DEVICE_OK); + return; + } // TODO : graphs to be shown at seek position } else { displayWorkoutDistance += distanceTick; diff --git a/src/Train/VideoSyncFile.cpp b/src/Train/VideoSyncFile.cpp index bc3121ce8..085178fbc 100644 --- a/src/Train/VideoSyncFile.cpp +++ b/src/Train/VideoSyncFile.cpp @@ -72,6 +72,7 @@ void VideoSyncFile::parseRLV() // running totals double rdist = 0; // running total for distance + double rend = 0; // Length of entire course // open the file for binary reading and open a datastream QFile RLVFile(filename); @@ -164,6 +165,8 @@ void VideoSyncFile::parseRLV() { // read in the mapping records uint32_t PreviousFrameNbr=0; + double PreviousDistance=0; + for (unsigned int record=0; record < info.records; record++) { // get the next record if (sizeof(framemapping) != input.readRawData((char*)&framemapping, sizeof(framemapping))) { @@ -171,11 +174,16 @@ void VideoSyncFile::parseRLV() break; } + if (record == 0) PreviousDistance = framemapping.distance; VideoSyncFilePoint add; if (format == RLV) { - double distance = framemapping.distance; // in meters per frame - rdist += distance * (double) (framemapping.frameNbr - PreviousFrameNbr); + // Calculate average distance per video frame between this sync point and the last + // using the formula (2a+b)/3 where a is the point of lower speed and b is the higher. + double distance = framemapping.distance; + double avgDistance = (qMin(distance, PreviousDistance)*2 + qMax(distance, PreviousDistance)) / 3; + + rdist += avgDistance * (double) (framemapping.frameNbr - PreviousFrameNbr); add.km = rdist / 1000.0; // time @@ -186,6 +194,7 @@ void VideoSyncFile::parseRLV() // FIXME : do we have to consider video offset and weight ? PreviousFrameNbr = framemapping.frameNbr; + PreviousDistance = distance; } Points.append(add); @@ -220,6 +229,7 @@ void VideoSyncFile::parseRLV() happy = false; break; } + if (courseInfo.end > rend) rend = courseInfo.end; } // FIXME : add those data to training messages } @@ -236,6 +246,17 @@ void VideoSyncFile::parseRLV() } else happy = false; } + // Make sure we have a sync point to represent the end of the course + // Some earlier rlvs don't supply an end point frame reference, + // so we need to assume the speed from the last ref point continues to the end + if ((!Points.isEmpty()) && (rend > Points.last().km * 1000) && Points.last().kph > 0) { + VideoSyncFilePoint add; + add.km = rend / 1000; + add.kph = Points.last().kph; + add.secs = Points.last().secs + ((add.km - Points.last().km) * 3600) / add.kph; + Points.append(add); + } + // done RLVFile.close(); diff --git a/src/Train/VideoWindow.cpp b/src/Train/VideoWindow.cpp index 07c0375b0..f964ffe6b 100644 --- a/src/Train/VideoWindow.cpp +++ b/src/Train/VideoWindow.cpp @@ -206,8 +206,10 @@ void VideoWindow::resizeEvent(QResizeEvent * ) void VideoWindow::startPlayback() { - if (context->currentVideoSyncFile()) + if (context->currentVideoSyncFile()) { context->currentVideoSyncFile()->manualOffset = 0.0; + context->currentVideoSyncFile()->km = 0.0; + } #ifdef GC_VIDEO_VLC if (!m) return; // ignore if no media selected @@ -218,8 +220,12 @@ void VideoWindow::startPlayback() /* set the media to playback */ libvlc_media_player_set_media (mp, m); - /* set the playback rate to the media default - since there may be a different one set from RLV */ - libvlc_media_player_set_rate(mp, libvlc_media_player_get_fps(mp)); + /* Reset playback rate */ + /* If video speed will be controlled by a sync file, set almost stationary + until first telemetry update. Otherwise (re)set to normal rate */ + if (context->currentVideoSyncFile() && context->currentVideoSyncFile()->Points.count() > 1) + libvlc_media_player_set_rate(mp, 0.1f); + else libvlc_media_player_set_rate(mp, 1.0f); /* play the media_player */ libvlc_media_player_play (mp); @@ -402,7 +408,8 @@ void VideoWindow::telemetryUpdate(RealtimeData rtd) #endif #ifdef GC_VIDEO_VLC - if (!m) return; + if (!m || !context->isRunning || context->isPaused) + return; QList *ErgFilePoints = NULL; if (context->currentErgFile()) { @@ -431,11 +438,44 @@ void VideoWindow::telemetryUpdate(RealtimeData rtd) while ((VideoSyncFiledataPoints[curPosition].km <= CurrentDistance) && (curPosition < VideoSyncFiledataPoints.count()-1)) curPosition++; - // update the rfp - float weighted_average = (VideoSyncFiledataPoints[curPosition].km - VideoSyncFiledataPoints[curPosition-1].km != 0.0)?(CurrentDistance-VideoSyncFiledataPoints[curPosition-1].km) / (VideoSyncFiledataPoints[curPosition].km - VideoSyncFiledataPoints[curPosition-1].km):0.0; + /* Create an RFP to represent where we are */ + VideoSyncFilePoint syncPrevious = VideoSyncFiledataPoints[curPosition-1]; + VideoSyncFilePoint syncNext = VideoSyncFiledataPoints[curPosition]; + double syncKmDelta = syncNext.km - syncPrevious.km; + double syncKphDelta = syncNext.kph - syncPrevious.kph; + double syncTimeDelta = syncNext.secs - syncPrevious.secs; + double distanceFactor, speedFactor, timeFactor, timeExtra; + + // Calculate how far we are between points in terms of distance + if (syncKmDelta == 0) distanceFactor = 0.0; + else distanceFactor = (CurrentDistance - syncPrevious.km) / syncKmDelta; + + // Now create the appropriate factors and interpolate the + // video speed and time for the point we have reached. + // If there has been no acceleration we can just use use the distance factor + if (syncKphDelta == 0) { + // Constant filming speed + rfp.kph = syncPrevious.kph; + rfp.secs = syncPrevious.secs + syncTimeDelta * distanceFactor; + } + else { + // Calculate time difference because of change in speed + timeExtra = syncTimeDelta - ((syncKmDelta / syncPrevious.kph) * 3600); + if (syncKphDelta > 0) { + // The filming speed increased + speedFactor = qPow(distanceFactor, 0.66667); + timeFactor = qPow(distanceFactor, 0.33333); + rfp.kph = syncPrevious.kph + speedFactor * syncKphDelta; + } + else { + // The filming speed decreased + speedFactor = 1 - qPow(distanceFactor, 1.5); + timeFactor = qPow(distanceFactor, 3.0); + rfp.kph = syncNext.kph - speedFactor * syncKphDelta; + } + rfp.secs = syncPrevious.secs + (distanceFactor * (syncTimeDelta - timeExtra)) + (timeFactor * timeExtra); + } rfp.km = CurrentDistance; - rfp.secs = VideoSyncFiledataPoints[curPosition-1].secs + weighted_average * (VideoSyncFiledataPoints[curPosition].secs - VideoSyncFiledataPoints[curPosition-1].secs); - rfp.kph = VideoSyncFiledataPoints[curPosition-1].kph + weighted_average * (VideoSyncFiledataPoints[curPosition].kph - VideoSyncFiledataPoints[curPosition-1].kph); /* //TODO : GPX file format