Skip to content
Browse files

Provide accurate position/duration/seeking with non-constant framerates.

The recordedseek and filemarkup tables are enhanced to hold timestamp
data in addition to the existing file offset data.  The millisecond
timestamps are produced by all recorders that subclass DTVRecorder, as
well as mythtranscode and mythcommflag --rebuild.  These timestamps
are relative to the start of the recording/video.

A new command is added to the myth protocol, "QUERY_RECORDER
FILL_DURATION_MAP", modeled after FILL_POSITION_MAP, to send updated
timestamp info for playback of an in-progress recording.

The timestamp markup is used during playback to give accurate position
and duration information in the OSD wherever possible, and to provide
accurate time-based seeking, such as "skip forward 30 seconds" or
"jump to the 5-minute mark".  Timestamps are linearly interpolated
from a frame's nearest neighbors in the map, and extrapolated based on
the current frame rate when the map is missing (e.g. legacy
recordings) or incomplete (e.g. in-progress recordings).  Other than
that, the frame rate is not used for seeking or duration calculations.
(With the exception of a handful of areas that still need some
attention, including seeking based on commskipmap, seeking across
program boundaries during Live TV, and the watched flag calculation.)

The cutlist continues to be taken into account for seeking and
displaying timestamps, for the most part making cutlists
indistinguishable from the result of lossless transcoding.

Note that to get the benefit of these changes for preexisting
recordings, it may be necessary to run "mythcommflag --rebuild" on
such recordings.

Bumps the ABI and protocol versions.  "make distclean" is recommended.

Fixes #10104.
  • Loading branch information...
1 parent 1eaecea commit 49dbed5be0729b04a5f0fd0426a32fceb2dd7935 @stichnot stichnot committed Dec 31, 2012
Showing with 881 additions and 379 deletions.
  1. +2 −2 mythtv/bindings/perl/MythTV.pm
  2. +2 −2 mythtv/bindings/php/MythBackend.php
  3. +4 −2 mythtv/bindings/python/MythTV/static.py
  4. +7 −3 mythtv/libs/libmythbase/mythversion.h
  5. +0 −11 mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp
  6. +0 −1 mythtv/libs/libmythtv/DVD/mythdvdplayer.h
  7. +11 −0 mythtv/libs/libmythtv/avformatdecoder.cpp
  8. +232 −6 mythtv/libs/libmythtv/decoderbase.cpp
  9. +22 −0 mythtv/libs/libmythtv/decoderbase.h
  10. +107 −136 mythtv/libs/libmythtv/deletemap.cpp
  11. +39 −37 mythtv/libs/libmythtv/deletemap.h
  12. +1 −0 mythtv/libs/libmythtv/mythcommflagplayer.cpp
  13. +162 −111 mythtv/libs/libmythtv/mythplayer.cpp
  14. +29 −7 mythtv/libs/libmythtv/mythplayer.h
  15. +7 −0 mythtv/libs/libmythtv/nuppeldecoder.cpp
  16. +36 −6 mythtv/libs/libmythtv/recorders/dtvrecorder.cpp
  17. +4 −0 mythtv/libs/libmythtv/recorders/dtvrecorder.h
  18. +29 −0 mythtv/libs/libmythtv/recorders/recorderbase.cpp
  19. +4 −0 mythtv/libs/libmythtv/recorders/recorderbase.h
  20. +31 −4 mythtv/libs/libmythtv/remoteencoder.cpp
  21. +5 −2 mythtv/libs/libmythtv/remoteencoder.h
  22. +71 −36 mythtv/libs/libmythtv/tv_play.cpp
  23. +1 −0 mythtv/libs/libmythtv/tv_play.h
  24. +11 −0 mythtv/libs/libmythtv/tv_rec.cpp
  25. +1 −0 mythtv/libs/libmythtv/tv_rec.h
  26. +13 −0 mythtv/programs/mythbackend/encoderlink.cpp
  27. +1 −0 mythtv/programs/mythbackend/encoderlink.h
  28. +24 −2 mythtv/programs/mythbackend/mainserver.cpp
  29. +5 −5 mythtv/programs/mythtranscode/cutter.cpp
  30. +1 −1 mythtv/programs/mythtranscode/cutter.h
  31. +6 −3 mythtv/programs/mythtranscode/main.cpp
  32. +11 −0 mythtv/programs/mythtranscode/mpeg2fix.cpp
  33. +2 −2 mythtv/programs/mythtranscode/transcode.cpp
View
4 mythtv/bindings/perl/MythTV.pm
@@ -107,8 +107,8 @@ package MythTV;
# Note: as of July 21, 2010, this is actually a string, to account for proto
# versions of the form "58a". This will get used if protocol versions are
# changed on a fixes branch ongoing.
- our $PROTO_VERSION = "76";
- our $PROTO_TOKEN = "FireWilde";
+ our $PROTO_VERSION = "77";
+ our $PROTO_TOKEN = "WindMark";
# currentDatabaseVersion is defined in libmythtv in
# mythtv/libs/libmythtv/dbcheck.cpp and should be the current MythTV core
View
4 mythtv/bindings/php/MythBackend.php
@@ -11,8 +11,8 @@ class MythBackend {
// MYTH_PROTO_VERSION is defined in libmyth in mythtv/libs/libmyth/mythcontext.h
// and should be the current MythTV protocol version.
- static $protocol_version = '76';
- static $protocol_token = 'FireWilde';
+ static $protocol_version = '77';
+ static $protocol_token = 'WindMark';
// The character string used by the backend to separate records
static $backend_separator = '[]:[]';
View
6 mythtv/bindings/python/MythTV/static.py
@@ -8,8 +8,8 @@
SCHEMA_VERSION = 1310
NVSCHEMA_VERSION = 1007
MUSICSCHEMA_VERSION = 1018
-PROTO_VERSION = '76'
-PROTO_TOKEN = 'FireWilde'
+PROTO_VERSION = '77'
+PROTO_TOKEN = 'WindMark'
BACKEND_SEP = '[]:[]'
INSTALL_PREFIX = '/usr/local'
@@ -34,6 +34,8 @@ class MARKUP( object ):
MARK_ASPECT_CUSTOM = 14
MARK_VIDEO_WIDTH = 30
MARK_VIDEO_HEIGHT = 31
+ MARK_VIDEO_RATE = 32
+ MARK_DURATION_MS = 33
class RECTYPE( object ):
kNotRecording = 0
View
10 mythtv/libs/libmythbase/mythversion.h
@@ -12,7 +12,7 @@
/// Update this whenever the plug-in API changes.
/// Including changes in the libmythbase, libmyth, libmythtv, libmythav* and
/// libmythui class methods used by plug-ins.
-#define MYTH_BINARY_VERSION "0.27.20121227-2"
+#define MYTH_BINARY_VERSION "0.27.20121231-1"
/** \brief Increment this whenever the MythTV network protocol changes.
*
@@ -34,9 +34,13 @@
* MythTV Python Bindings
* mythtv/bindings/python/MythTV/static.py (version number)
* mythtv/bindings/python/MythTV/mythproto.py (layout)
+ *
+ * Be kind and update the wiki as well.
+ * http://www.mythtv.org/wiki/Category:Myth_Protocol_Commands
+ * http://www.mythtv.org/wiki/Category:Myth_Protocol
*/
-#define MYTH_PROTO_VERSION "76"
-#define MYTH_PROTO_TOKEN "FireWilde"
+#define MYTH_PROTO_VERSION "77"
+#define MYTH_PROTO_TOKEN "WindMark"
/** \brief Increment this whenever the MythTV core database schema changes.
*
View
11 mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp
@@ -390,17 +390,6 @@ long long MythDVDPlayer::CalcMaxFFTime(long long ff, bool setjump) const
return MythPlayer::CalcMaxFFTime(ff, setjump);
}
-int64_t MythDVDPlayer::GetSecondsPlayed(void)
-{
- if (!player_ctx->buffer->IsDVD())
- return 0;
-
- return (m_stillFrameLength > 0) ?
- (m_stillFrameTimer.elapsed() / 1000) :
- (player_ctx->buffer->DVD()->GetCurrentTime());
-
-}
-
int64_t MythDVDPlayer::GetTotalSeconds(void) const
{
return (m_stillFrameLength > 0) ? m_stillFrameLength: totalLength;
View
1 mythtv/libs/libmythtv/DVD/mythdvdplayer.h
@@ -19,7 +19,6 @@ class MythDVDPlayer : public MythPlayer
// Gets
virtual uint64_t GetBookmark(void);
- virtual int64_t GetSecondsPlayed(void);
virtual int64_t GetTotalSeconds(void) const;
// DVD public stuff
View
11 mythtv/libs/libmythtv/avformatdecoder.cpp
@@ -2945,7 +2945,18 @@ void AvFormatDecoder::HandleGopStart(
PosMapEntry entry = {framesRead, framesRead, startpos};
QMutexLocker locker(&m_positionMapLock);
+ // Create a dummy positionmap entry for frame 0 so that
+ // seeking will work properly. (See
+ // DecoderBase::FindPosition() which subtracts
+ // DecoderBase::indexOffset from each frame number.)
+ if (m_positionMap.empty())
+ {
+ PosMapEntry dur = {0, 0, 0};
+ m_positionMap.push_back(dur);
+ }
m_positionMap.push_back(entry);
+ m_frameToDurMap[framesRead] = totalDuration / 1000;
+ m_durToFrameMap[m_frameToDurMap[framesRead]] = framesRead;
}
#if 0
View
238 mythtv/libs/libmythtv/decoderbase.cpp
@@ -119,7 +119,7 @@ bool DecoderBase::PosMapFromDb(void)
return false;
// Overwrites current positionmap with entire contents of database
- frm_pos_map_t posMap;
+ frm_pos_map_t posMap, durMap;
if (ringBuffer && ringBuffer->IsDVD())
{
@@ -191,6 +191,8 @@ bool DecoderBase::PosMapFromDb(void)
if (posMap.empty())
return false; // no position map in recording
+ m_playbackinfo->QueryPositionMap(durMap, MARK_DURATION_MS);
+
QMutexLocker locker(&m_positionMapLock);
m_positionMap.clear();
m_positionMap.reserve(posMap.size());
@@ -212,6 +214,21 @@ bool DecoderBase::PosMapFromDb(void)
.arg(m_positionMap.back().index));
}
+ uint64_t last = 0;
+ for (frm_pos_map_t::const_iterator it = durMap.begin();
+ it != durMap.end(); ++it)
+ {
+ m_frameToDurMap[it.key()] = it.value();
+ m_durToFrameMap[it.value()] = it.key();
+ last = it.key();
+ }
+
+ if (!m_durToFrameMap.empty())
+ {
+ LOG(VB_PLAYBACK, LOG_INFO, LOC +
+ QString("Duration map filled from DB to: %1").arg(last));
+ }
+
return true;
}
@@ -235,20 +252,20 @@ bool DecoderBase::PosMapFromEnc(void)
start = m_positionMap.back().index + 1;
}
- QMap<long long, long long> posMap;
- if (!m_parent->PosMapFromEnc(start, posMap))
+ frm_pos_map_t posMap, durMap;
+ if (!m_parent->PosMapFromEnc(start, posMap, durMap))
return false;
QMutexLocker locker(&m_positionMapLock);
// append this new position map to class's
m_positionMap.reserve(m_positionMap.size() + posMap.size());
- long long last_index = m_positionMap.back().index;
- for (QMap<long long,long long>::const_iterator it = posMap.begin();
+ uint64_t last_index = m_positionMap.back().index;
+ for (frm_pos_map_t::const_iterator it = posMap.begin();
it != posMap.end(); ++it)
{
if (it.key() <= last_index)
- continue; // we released the m_positionMapLock for a few ms...
+ continue;
PosMapEntry e = {it.key(), it.key() * keyframedist, *it};
m_positionMap.push_back(e);
@@ -264,6 +281,30 @@ bool DecoderBase::PosMapFromEnc(void)
.arg(m_positionMap.back().index));
}
+ bool isEmpty = m_frameToDurMap.empty();
+ if (!isEmpty)
+ {
+ frm_pos_map_t::const_iterator it = m_frameToDurMap.end();
+ --it;
+ last_index = it.key();
+ }
+ for (frm_pos_map_t::const_iterator it = durMap.begin();
+ it != durMap.end(); ++it)
+ {
+ if (!isEmpty && it.key() <= last_index)
+ continue; // we released the m_positionMapLock for a few ms...
+ m_frameToDurMap[it.key()] = it.value();
+ m_durToFrameMap[it.value()] = it.key();
+ }
+
+ if (!m_frameToDurMap.empty())
+ {
+ frm_pos_map_t::const_iterator it = m_frameToDurMap.end();
+ --it;
+ LOG(VB_PLAYBACK, LOG_INFO, LOC +
+ QString("Duration map filled from Encoder to: %1").arg(it.key()));
+ }
+
return true;
}
@@ -491,10 +532,22 @@ uint64_t DecoderBase::SavePositionMapDelta(uint64_t first, uint64_t last)
saved++;
}
+ frm_pos_map_t durMap;
+ for (frm_pos_map_t::const_iterator it = m_frameToDurMap.begin();
+ it != m_frameToDurMap.end(); ++it)
+ {
+ if (it.key() < first)
+ continue;
+ if (it.key() > last)
+ break;
+ durMap[it.key()] = it.value();
+ }
+
locker.unlock();
stm.start();
m_playbackinfo->SavePositionMapDelta(posMap, type);
+ m_playbackinfo->SavePositionMapDelta(durMap, MARK_DURATION_MS);
#if 0
LOG(VB_GENERAL, LOG_DEBUG, LOC +
@@ -1154,5 +1207,178 @@ void DecoderBase::SaveTotalFrames(void)
m_playbackinfo->SaveTotalFrames(framesRead);
}
+// Linearly interpolate the value for a given key in the map. If the
+// key is outside the range of keys in the map, linearly extrapolate
+// using the fallback ratio.
+uint64_t DecoderBase::TranslatePosition(const frm_pos_map_t &map,
+ uint64_t key,
+ float fallback_ratio)
+{
+ uint64_t key1, key2;
+ uint64_t val1, val2;
+
+ frm_pos_map_t::const_iterator lower = map.lowerBound(key);
+ // QMap::lowerBound() finds a key >= the given key. We want one
+ // <= the given key, so back up one element upon > condition.
+ if (lower != map.begin() && (lower == map.end() || lower.key() > key))
+ --lower;
+ if (lower == map.end())
+ {
+ key1 = 0;
+ val1 = 0;
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("TranslatePosition(key=%1): extrapolating to (0,0)")
+ .arg(key));
+ }
+ else
+ {
+ key1 = lower.key();
+ val1 = lower.value();
+ }
+ // Find the next key >= the given key. QMap::lowerBound() is
+ // precisely correct in this case.
+ frm_pos_map_t::const_iterator upper = map.lowerBound(key);
+ if (upper == map.end())
+ {
+ // Extrapolate from (key1,val1) based on fallback_ratio
+ key2 = key;
+ val2 = val1 + fallback_ratio * (key2 - key1) + 0.5;
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
+ QString("TranslatePosition(key=%1, ratio=%2): "
+ "extrapolating to (%3,%4)")
+ .arg(key).arg(fallback_ratio).arg(key2).arg(val2));
+ return val2;
+ }
+ else
+ {
+ key2 = upper.key();
+ val2 = upper.value();
+ }
+ if (key1 == key2) // this happens for an exact keyframe match
+ return val2; // can also set key2 = key1 + 1 avoid dividing by zero
+
+ return val1 + (double) (key - key1) * (val2 - val1) / (key2 - key1) + 0.5;
+}
+
+// Convert from an absolute frame number (not cutlist adjusted) to its
+// cutlist-adjusted position in milliseconds.
+uint64_t DecoderBase::TranslatePositionFrameToMs(uint64_t position,
+ float fallback_framerate,
+ const frm_dir_map_t &cutlist)
+ const
+{
+ QMutexLocker locker(&m_positionMapLock);
+ return TranslatePositionAbsToRel(cutlist, position, m_frameToDurMap,
+ 1000 / fallback_framerate);
+}
+
+// Convert from a cutlist-adjusted position in milliseconds to its
+// absolute frame number (not cutlist-adjusted).
+uint64_t DecoderBase::TranslatePositionMsToFrame(uint64_t dur_ms,
+ float fallback_framerate,
+ const frm_dir_map_t &cutlist)
+ const
+{
+ QMutexLocker locker(&m_positionMapLock);
+ // Convert relative position in milliseconds (cutlist-adjusted) to
+ // its absolute position in milliseconds (not cutlist-adjusted).
+ uint64_t ms = TranslatePositionRelToAbs(cutlist, dur_ms, m_frameToDurMap,
+ 1000 / fallback_framerate);
+ // Convert absolute position in milliseconds to its absolute frame
+ // number.
+ return TranslatePosition(m_durToFrameMap, ms, fallback_framerate / 1000);
+}
+
+// Convert from an "absolute" (not cutlist-adjusted) value to its
+// "relative" (cutlist-adjusted) mapped value. Usually the position
+// argument is a frame number, the map argument maps frames to
+// milliseconds, the fallback_ratio is 1000/framerate_fps, and the
+// return value is in milliseconds.
+//
+// If the map and fallback_ratio arguments are omitted, it simply
+// converts from an absolute frame number to a relative
+// (cutlist-adjusted) frame number.
+uint64_t
+DecoderBase::TranslatePositionAbsToRel(const frm_dir_map_t &deleteMap,
+ uint64_t absPosition, // frames
+ const frm_pos_map_t &map, // frame->ms
+ float fallback_ratio)
+{
+ uint64_t subtraction = 0;
+ uint64_t startOfCutRegion = 0;
+ bool withinCut = false;
+ bool first = true;
+ for (frm_dir_map_t::const_iterator i = deleteMap.begin();
+ i != deleteMap.end(); ++i)
+ {
+ if (first)
+ withinCut = (i.value() == MARK_CUT_END);
+ first = false;
+ if (i.key() > absPosition)
+ break;
+ uint64_t mappedKey = TranslatePosition(map, i.key(), fallback_ratio);
+ if (i.value() == MARK_CUT_START && !withinCut)
+ {
+ withinCut = true;
+ startOfCutRegion = mappedKey;
+ }
+ else if (i.value() == MARK_CUT_END && withinCut)
+ {
+ withinCut = false;
+ subtraction += (mappedKey - startOfCutRegion);
+ }
+ }
+ uint64_t mappedPos = TranslatePosition(map, absPosition, fallback_ratio);
+ if (withinCut)
+ subtraction += (mappedPos - startOfCutRegion);
+ return mappedPos - subtraction;
+}
+
+// Convert from a "relative" (cutlist-adjusted) value to its
+// "absolute" (not cutlist-adjusted) mapped value. Usually the
+// position argument is in milliseconds, the map argument maps frames
+// to milliseconds, the fallback_ratio is 1000/framerate_fps, and the
+// return value is also in milliseconds. Upon return, if necessary,
+// the result may need a separate, non-cutlist adjusted conversion
+// from milliseconds to frame number, using the inverse
+// millisecond-to-frame map and the inverse fallback_ratio; see for
+// example TranslatePositionMsToFrame().
+//
+// If the map and fallback_ratio arguments are omitted, it simply
+// converts from a relatve (cutlist-adjusted) frame number to an
+// absolute frame number.
+uint64_t
+DecoderBase::TranslatePositionRelToAbs(const frm_dir_map_t &deleteMap,
+ uint64_t relPosition, // ms
+ const frm_pos_map_t &map, // frame->ms
+ float fallback_ratio)
+{
+ uint64_t addition = 0;
+ uint64_t startOfCutRegion = 0;
+ bool withinCut = false;
+ bool first = true;
+ for (frm_dir_map_t::const_iterator i = deleteMap.begin();
+ i != deleteMap.end(); ++i)
+ {
+ if (first)
+ withinCut = (i.value() == MARK_CUT_END);
+ first = false;
+ uint64_t mappedKey = TranslatePosition(map, i.key(), fallback_ratio);
+ if (i.value() == MARK_CUT_START && !withinCut)
+ {
+ withinCut = true;
+ startOfCutRegion = mappedKey;
+ if (relPosition + addition <= startOfCutRegion)
+ break;
+ }
+ else if (i.value() == MARK_CUT_END && withinCut)
+ {
+ withinCut = false;
+ addition += (mappedKey - startOfCutRegion);
+ }
+ }
+ return relPosition + addition;
+}
+
/* vim: set expandtab tabstop=4 shiftwidth=4: */
View
22 mythtv/libs/libmythtv/decoderbase.h
@@ -141,6 +141,26 @@ class DecoderBase
virtual bool DoRewind(long long desiredFrame, bool doflush = true);
virtual bool DoFastForward(long long desiredFrame, bool doflush = true);
+ static uint64_t
+ TranslatePositionAbsToRel(const frm_dir_map_t &deleteMap,
+ uint64_t absPosition,
+ const frm_pos_map_t &map = frm_pos_map_t(),
+ float fallback_ratio = 1.0);
+ static uint64_t
+ TranslatePositionRelToAbs(const frm_dir_map_t &deleteMap,
+ uint64_t relPosition,
+ const frm_pos_map_t &map = frm_pos_map_t(),
+ float fallback_ratio = 1.0);
+ static uint64_t TranslatePosition(const frm_pos_map_t &map,
+ uint64_t key,
+ float fallback_ratio);
+ uint64_t TranslatePositionFrameToMs(uint64_t position,
+ float fallback_framerate,
+ const frm_dir_map_t &cutlist) const;
+ uint64_t TranslatePositionMsToFrame(uint64_t dur_ms,
+ float fallback_framerate,
+ const frm_dir_map_t &cutlist) const;
+
float GetVideoAspect(void) const { return current_aspect; }
virtual int64_t NormalizeVideoTimecode(int64_t timecode) { return timecode; }
@@ -274,6 +294,8 @@ class DecoderBase
mutable QMutex m_positionMapLock;
vector<PosMapEntry> m_positionMap;
+ frm_pos_map_t m_frameToDurMap; // guarded by m_positionMapLock
+ frm_pos_map_t m_durToFrameMap; // guarded by m_positionMapLock
bool dontSyncPositionMap;
uint64_t seeksnap;
View
243 mythtv/libs/libmythtv/deletemap.cpp
@@ -33,7 +33,7 @@ void DeleteMap::Push(QString undoMessage)
m_undoStack.pop_back();
m_undoStack.append(entry);
m_undoStackPointer ++;
- SaveMap(0, true);
+ SaveMap(true);
}
bool DeleteMap::Undo(void)
@@ -43,7 +43,7 @@ bool DeleteMap::Undo(void)
m_undoStackPointer --;
m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
m_changed = true;
- SaveMap(0, true);
+ SaveMap(true);
return true;
}
@@ -54,7 +54,7 @@ bool DeleteMap::Redo(void)
m_undoStackPointer ++;
m_deleteMap = m_undoStack[m_undoStackPointer].deleteMap;
m_changed = true;
- SaveMap(0, true);
+ SaveMap(true);
return true;
}
@@ -70,37 +70,36 @@ QString DeleteMap::GetRedoMessage(void) const
tr("(Nothing to redo)"));
}
-bool DeleteMap::HandleAction(QString &action, uint64_t frame,
- uint64_t played, uint64_t total, double rate)
+bool DeleteMap::HandleAction(QString &action, uint64_t frame, uint64_t played)
{
bool handled = true;
if (action == ACTION_UP)
- UpdateSeekAmount(1, rate);
+ UpdateSeekAmount(1);
else if (action == ACTION_DOWN)
- UpdateSeekAmount(-1, rate);
+ UpdateSeekAmount(-1);
else if (action == ACTION_CLEARMAP)
Clear(tr("Clear Cuts"));
else if (action == ACTION_INVERTMAP)
- ReverseAll(total);
+ ReverseAll();
else if (action == "MOVEPREV")
- MoveRelative(frame, total, false);
+ MoveRelative(frame, false);
else if (action == "MOVENEXT")
- MoveRelative(frame, total, true);
+ MoveRelative(frame, true);
else if (action == "CUTTOBEGINNING")
- Add(frame, total, MARK_CUT_END, tr("Cut to Beginning"));
+ Add(frame, MARK_CUT_END, tr("Cut to Beginning"));
else if (action == "CUTTOEND")
{
- Add(frame, total, MARK_CUT_START, tr("Cut to End"));
+ Add(frame, MARK_CUT_START, tr("Cut to End"));
// If the recording is still in progress, add an explicit end
// mark at the end.
if (m_ctx->player && m_ctx->player->IsWatchingInprogress())
- Add(total - 1, total, MARK_CUT_END, "");
+ Add(m_ctx->player->GetTotalFrameCount() - 1, MARK_CUT_END, "");
}
else if (action == "NEWCUT")
- NewCut(frame, total);
+ NewCut(frame);
else if (action == "DELETE")
//: Delete the current cut or preserved region
- Delete(frame, total, tr("Delete"));
+ Delete(frame, tr("Delete"));
else if (action == "UNDO")
Undo();
else if (action == "REDO")
@@ -110,7 +109,7 @@ bool DeleteMap::HandleAction(QString &action, uint64_t frame,
return handled;
}
-void DeleteMap::UpdateSeekAmount(int change, double framerate)
+void DeleteMap::UpdateSeekAmount(int change)
{
m_seekamountpos += change;
if (m_seekamountpos > 9)
@@ -123,44 +122,46 @@ void DeleteMap::UpdateSeekAmount(int change, double framerate)
{
case 0: m_seekText = tr("cut point"); m_seekamount = -2; break;
case 1: m_seekText = tr("keyframe"); m_seekamount = -1; break;
- case 2: m_seekText = tr("1 frame"); m_seekamount = 1; break;
- case 3: m_seekText = tr("0.5 seconds"); m_seekamount = (int)roundf(framerate / 2); break;
- case 4: m_seekText = tr("%n second(s)", "", 1); m_seekamount = (int)roundf(framerate); break;
- case 5: m_seekText = tr("%n second(s)", "", 5); m_seekamount = (int)roundf(framerate * 5); break;
- case 6: m_seekText = tr("%n second(s)", "", 20); m_seekamount = (int)roundf(framerate * 20); break;
- case 7: m_seekText = tr("%n minute(s)", "", 1); m_seekamount = (int)roundf(framerate * 60); break;
- case 8: m_seekText = tr("%n minute(s)", "", 5); m_seekamount = (int)roundf(framerate * 300); break;
- case 9: m_seekText = tr("%n minute(s)", "", 10); m_seekamount = (int)roundf(framerate * 600); break;
- default: m_seekText = tr("error"); m_seekamount = (int)roundf(framerate); break;
+ case 2: m_seekText = tr("1 frame"); m_seekamount = 0; break;
+ case 3: m_seekText = tr("0.5 seconds"); m_seekamount = 0.5; break;
+ case 4: m_seekText = tr("%n second(s)", "", 1); m_seekamount = 1; break;
+ case 5: m_seekText = tr("%n second(s)", "", 5); m_seekamount = 5; break;
+ case 6: m_seekText = tr("%n second(s)", "", 20); m_seekamount = 20; break;
+ case 7: m_seekText = tr("%n minute(s)", "", 1); m_seekamount = 60; break;
+ case 8: m_seekText = tr("%n minute(s)", "", 5); m_seekamount = 300; break;
+ case 9: m_seekText = tr("%n minute(s)", "", 10); m_seekamount = 600; break;
+ default: m_seekText = tr("error"); m_seekamount = 1; break;
}
}
-static QString createTimeString(uint64_t frame, uint64_t total,
- double frame_rate, bool full_resolution)
+QString DeleteMap::CreateTimeString(uint64_t frame, bool use_cutlist,
+ double frame_rate, bool full_resolution)
+ const
{
- int secs = (int)(frame / frame_rate);
- int frames = frame - (int)(secs * frame_rate);
- int totalSecs = (int)(total / frame_rate);
+ uint64_t ms = TranslatePositionFrameToMs(frame, frame_rate, use_cutlist);
+ int secs = (int)(ms / 1000);
+ int remainder = (int)(ms % 1000);
+ int totalSecs = (int)
+ (TranslatePositionFrameToMs(frame, frame_rate, use_cutlist) / 1000);
QString timestr;
if (totalSecs >= 3600)
timestr = QString::number(secs / 3600) + ":";
timestr += QString("%1").arg((secs / 60) % 60, 2, 10, QChar(48)) +
QString(":%1").arg(secs % 60, 2, 10, QChar(48));
if (full_resolution)
- timestr += QString(".%1").arg(frames, 2, 10, QChar(48));
+ timestr += QString(".%1").arg(remainder, 3, 10, QChar(48));
return timestr;
}
/**
* \brief Show and update the edit mode On Screen Display. The cut regions
* are only refreshed if the deleteMap has been updated.
*/
-void DeleteMap::UpdateOSD(uint64_t frame, uint64_t total, double frame_rate,
- OSD *osd)
+void DeleteMap::UpdateOSD(uint64_t frame, double frame_rate, OSD *osd)
{
if (!osd || !m_ctx)
return;
- CleanMap(total);
+ CleanMap();
InfoMap infoMap;
m_ctx->LockPlayingInfo(__FILE__, __LINE__);
@@ -173,12 +174,12 @@ void DeleteMap::UpdateOSD(uint64_t frame, uint64_t total, double frame_rate,
if (IsInDelete(frame))
cutmarker = tr("cut");
- QString timestr = createTimeString(frame, total, frame_rate, true);
- uint64_t relTotal = TranslatePositionAbsToRel(total);
- QString relTimeDisplay = createTimeString(TranslatePositionAbsToRel(frame),
- relTotal, frame_rate, false);
- QString relLengthDisplay = createTimeString(relTotal,
- relTotal, frame_rate, false);
+ uint64_t total = m_ctx->player->GetTotalFrameCount();
+ QString timestr = CreateTimeString(frame, false, frame_rate, true);
+ QString relTimeDisplay;
+ relTimeDisplay = CreateTimeString(frame, true, frame_rate, false);
+ QString relLengthDisplay;
+ relLengthDisplay = CreateTimeString(total, true, frame_rate, false);
infoMap["timedisplay"] = timestr;
infoMap["framedisplay"] = QString::number(frame);
infoMap["cutindicator"] = cutmarker;
@@ -249,14 +250,14 @@ void DeleteMap::Clear(QString undoMessage)
}
/// Reverses the direction of each mark in the map.
-void DeleteMap::ReverseAll(uint64_t total)
+void DeleteMap::ReverseAll(void)
{
EDIT_CHECK;
frm_dir_map_t::Iterator it = m_deleteMap.begin();
for ( ; it != m_deleteMap.end(); ++it)
Add(it.key(), it.value() == MARK_CUT_END ? MARK_CUT_START :
MARK_CUT_END);
- CleanMap(total);
+ CleanMap();
Push(tr("Reverse Cuts"));
}
@@ -265,8 +266,7 @@ void DeleteMap::ReverseAll(uint64_t total)
* existing redundant mark of that type is removed. This simplifies
* the cleanup code.
*/
-void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type,
- QString undoMessage)
+void DeleteMap::Add(uint64_t frame, MarkTypes type, QString undoMessage)
{
EDIT_CHECK;
if ((MARK_CUT_START != type) && (MARK_CUT_END != type) &&
@@ -280,7 +280,7 @@ void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type,
{
// Delete the temporary mark before putting a real mark at its
// location
- Delete(frame, total);
+ Delete(frame, "");
}
else // Don't add a mark on top of a mark
return;
@@ -334,20 +334,20 @@ void DeleteMap::Add(uint64_t frame, uint64_t total, MarkTypes type,
if (remove > -1)
Delete((uint64_t)remove);
Add(frame, type);
- CleanMap(total);
+ CleanMap();
if (!undoMessage.isEmpty())
Push(undoMessage);
}
/// Remove the mark at the given frame.
-void DeleteMap::Delete(uint64_t frame, uint64_t total, QString undoMessage)
+void DeleteMap::Delete(uint64_t frame, QString undoMessage)
{
EDIT_CHECK;
if (m_deleteMap.isEmpty())
return;
- uint64_t prev = GetNearestMark(frame, total, false);
- uint64_t next = GetNearestMark(frame, total, true);
+ uint64_t prev = GetNearestMark(frame, false);
+ uint64_t next = GetNearestMark(frame, true);
// If frame is a cut point, GetNearestMark() would return the previous/next
// mark (not this frame), so check to see if we need to use frame, instead
@@ -366,13 +366,13 @@ void DeleteMap::Delete(uint64_t frame, uint64_t total, QString undoMessage)
Delete(prev);
if (prev != next)
Delete(next);
- CleanMap(total);
+ CleanMap();
if (!undoMessage.isEmpty())
Push(undoMessage);
}
/// Add a new cut marker (to start or end a cut region)
-void DeleteMap::NewCut(uint64_t frame, uint64_t total)
+void DeleteMap::NewCut(uint64_t frame)
{
EDIT_CHECK;
@@ -390,6 +390,7 @@ void DeleteMap::NewCut(uint64_t frame, uint64_t total)
if (existing > -1)
{
+ uint64_t total = m_ctx->player->GetTotalFrameCount();
uint64_t otherframe = static_cast<uint64_t>(existing);
if (otherframe == frame)
Delete(otherframe);
@@ -402,8 +403,8 @@ void DeleteMap::NewCut(uint64_t frame, uint64_t total)
if (IsInDelete(frame))
{
MarkTypes type = MARK_UNSET;
- cut_start = GetNearestMark(frame, total, false);
- cut_end = GetNearestMark(frame, total, true);
+ cut_start = GetNearestMark(frame, false);
+ cut_end = GetNearestMark(frame, true);
frm_dir_map_t::Iterator it = m_deleteMap.find(frame);
if (it != m_deleteMap.end())
type = it.value();
@@ -466,12 +467,12 @@ void DeleteMap::NewCut(uint64_t frame, uint64_t total)
else
Add(frame, MARK_PLACEHOLDER);
- CleanMap(total);
+ CleanMap();
Push(tr("New Cut"));
}
/// Move the previous (!right) or next (right) cut to frame.
-void DeleteMap::MoveRelative(uint64_t frame, uint64_t total, bool right)
+void DeleteMap::MoveRelative(uint64_t frame, bool right)
{
frm_dir_map_t::Iterator it = m_deleteMap.find(frame);
if (it != m_deleteMap.end())
@@ -490,34 +491,34 @@ void DeleteMap::MoveRelative(uint64_t frame, uint64_t total, bool right)
// If on a mark, don't collapse a cut region to 0;
// instead, delete the region
//: Delete the current cut or preserved region
- Delete(frame, total, tr("Delete"));
+ Delete(frame, tr("Delete"));
return;
}
else if (MARK_PLACEHOLDER == type)
{
// Delete the temporary mark before putting a real mark at its
// location
- Delete(frame, total);
+ Delete(frame, "");
}
}
- uint64_t from = GetNearestMark(frame, total, right);
- Move(from, frame, total);
+ uint64_t from = GetNearestMark(frame, right);
+ Move(from, frame);
}
/// Move an existing mark to a new frame.
-void DeleteMap::Move(uint64_t frame, uint64_t to, uint64_t total)
+void DeleteMap::Move(uint64_t frame, uint64_t to)
{
EDIT_CHECK;
MarkTypes type = Delete(frame);
if (MARK_UNSET == type)
{
if (frame == 0)
type = MARK_CUT_START;
- else if (frame == total)
+ else if (frame == m_ctx->player->GetTotalFrameCount())
type = MARK_CUT_END;
}
- Add(to, total, type, tr("Move Mark"));
+ Add(to, type, tr("Move Mark"));
}
/// Private addition to the deleteMap.
@@ -584,17 +585,16 @@ bool DeleteMap::IsTemporaryMark(uint64_t frame) const
* frame). If hasMark is non-NULL, it is set to true if the
* next/previous mark exists, and false otherwise.
*/
-uint64_t DeleteMap::GetNearestMark(
- uint64_t frame, uint64_t total, bool right,
- bool *hasMark) const
+uint64_t DeleteMap::GetNearestMark(uint64_t frame, bool right, bool *hasMark)
+ const
{
uint64_t result;
if (hasMark)
*hasMark = true;
frm_dir_map_t::const_iterator it = m_deleteMap.begin();
if (right)
{
- result = total;
+ result = m_ctx->player->GetTotalFrameCount();
for (; it != m_deleteMap.end(); ++it)
if (it.key() > frame)
return it.key();
@@ -635,11 +635,12 @@ bool DeleteMap::HasTemporaryMark(void) const
* A valid sequence is 1 or more marks of alternating values and does
* not include the first or last frames.
*/
-void DeleteMap::CleanMap(uint64_t total)
+void DeleteMap::CleanMap(void)
{
if (IsEmpty())
return;
+ uint64_t total = m_ctx->player->GetTotalFrameCount();
Delete(0);
Delete(total);
@@ -693,19 +694,19 @@ void DeleteMap::SetMap(const frm_dir_map_t &map)
}
/// Loads the given commercial break map into the deleteMap.
-void DeleteMap::LoadCommBreakMap(uint64_t total, frm_dir_map_t &map)
+void DeleteMap::LoadCommBreakMap(frm_dir_map_t &map)
{
Clear();
frm_dir_map_t::Iterator it = map.begin();
for ( ; it != map.end(); ++it)
Add(it.key(), it.value() == MARK_COMM_START ?
MARK_CUT_START : MARK_CUT_END);
- CleanMap(total);
+ CleanMap();
Push(tr("Load Detected Commercials"));
}
/// Loads the delete map from the database.
-void DeleteMap::LoadMap(uint64_t total, QString undoMessage)
+void DeleteMap::LoadMap(QString undoMessage)
{
if (!m_ctx || !m_ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
return;
@@ -714,14 +715,14 @@ void DeleteMap::LoadMap(uint64_t total, QString undoMessage)
m_ctx->LockPlayingInfo(__FILE__, __LINE__);
m_ctx->playingInfo->QueryCutList(m_deleteMap);
m_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
- CleanMap(total);
+ CleanMap();
if (!undoMessage.isEmpty())
Push(undoMessage);
}
/// Returns true if an auto-save map was loaded.
/// Does nothing and returns false if not.
-bool DeleteMap::LoadAutoSaveMap(uint64_t total)
+bool DeleteMap::LoadAutoSaveMap(void)
{
if (!m_ctx || !m_ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
return false;
@@ -731,7 +732,7 @@ bool DeleteMap::LoadAutoSaveMap(uint64_t total)
m_ctx->LockPlayingInfo(__FILE__, __LINE__);
bool result = m_ctx->playingInfo->QueryCutList(m_deleteMap, true);
m_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
- CleanMap(total);
+ CleanMap();
if (result)
Push(tr("Load Auto-saved Cuts"));
else
@@ -741,7 +742,7 @@ bool DeleteMap::LoadAutoSaveMap(uint64_t total)
}
/// Saves the delete map to the database.
-void DeleteMap::SaveMap(uint64_t total, bool isAutoSave)
+void DeleteMap::SaveMap(bool isAutoSave)
{
if (!m_ctx || !m_ctx->playingInfo || gCoreContext->IsDatabaseIgnored())
return;
@@ -760,7 +761,7 @@ void DeleteMap::SaveMap(uint64_t total, bool isAutoSave)
}
}
- CleanMap(total);
+ CleanMap();
}
m_ctx->LockPlayingInfo(__FILE__, __LINE__);
m_ctx->playingInfo->SaveMarkupFlag(MARK_UPDATED_CUT);
@@ -774,7 +775,7 @@ void DeleteMap::SaveMap(uint64_t total, bool isAutoSave)
* This is used by the player to avoid iterating over the entire map
* many times per second.
*/
-void DeleteMap::TrackerReset(uint64_t frame, uint64_t total)
+void DeleteMap::TrackerReset(uint64_t frame)
{
m_nextCutStart = 0;
m_nextCutStartIsValid = false;
@@ -793,11 +794,12 @@ void DeleteMap::TrackerReset(uint64_t frame, uint64_t total)
{
++cutpoint;
m_nextCutStartIsValid = (cutpoint != m_deleteMap.end());
- m_nextCutStart = m_nextCutStartIsValid ? cutpoint.key() : total;
+ m_nextCutStart = m_nextCutStartIsValid ? cutpoint.key() :
+ m_ctx->player->GetTotalFrameCount();
}
}
else
- m_nextCutStart = GetNearestMark(frame, total, !IsInDelete(frame),
+ m_nextCutStart = GetNearestMark(frame, !IsInDelete(frame),
&m_nextCutStartIsValid);
LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Tracker next CUT_START: %1")
.arg(m_nextCutStart));
@@ -807,12 +809,12 @@ void DeleteMap::TrackerReset(uint64_t frame, uint64_t total)
* \brief Returns true if the given frame has passed the last cut point start
* and provides the frame number of the next jump.
*/
-bool DeleteMap::TrackerWantsToJump(uint64_t frame, uint64_t total, uint64_t &to)
+bool DeleteMap::TrackerWantsToJump(uint64_t frame, uint64_t &to)
{
if (IsEmpty() || !m_nextCutStartIsValid || frame < m_nextCutStart)
return false;
- to = GetNearestMark(m_nextCutStart, total, true);
+ to = GetNearestMark(m_nextCutStart, true);
LOG(VB_PLAYBACK, LOG_INFO, LOC +
QString("Tracker wants to jump to: %1").arg(to));
return true;
@@ -822,9 +824,9 @@ bool DeleteMap::TrackerWantsToJump(uint64_t frame, uint64_t total, uint64_t &to)
* \brief Returns the number of the last frame in the video that is not in a
* cut sequence.
*/
-uint64_t DeleteMap::GetLastFrame(uint64_t total) const
+uint64_t DeleteMap::GetLastFrame(void) const
{
- uint64_t result = total;
+ uint64_t result = m_ctx->player->GetTotalFrameCount();
if (IsEmpty())
return result;
@@ -862,62 +864,31 @@ bool DeleteMap::IsSaved(void) const
return currentMap == savedMap;
}
-uint64_t DeleteMap::TranslatePositionAbsToRel(const frm_dir_map_t &deleteMap,
- uint64_t absPosition)
+uint64_t DeleteMap::TranslatePositionFrameToMs(uint64_t position,
+ float fallback_framerate,
+ bool use_cutlist) const
{
- uint64_t subtraction = 0;
- uint64_t startOfCutRegion = 0;
- frm_dir_map_t::const_iterator i;
- bool withinCut = false;
- bool first = true;
- for (i = deleteMap.constBegin(); i != deleteMap.constEnd(); ++i)
- {
- if (first)
- withinCut = (i.value() == MARK_CUT_END);
- first = false;
- if (i.key() > absPosition)
- break;
- if (i.value() == MARK_CUT_START && !withinCut)
- {
- withinCut = true;
- startOfCutRegion = i.key();
- }
- else if (i.value() == MARK_CUT_END && withinCut)
- {
- withinCut = false;
- subtraction += (i.key() - startOfCutRegion);
- }
- }
- if (withinCut)
- subtraction += (absPosition - startOfCutRegion);
- return absPosition - subtraction;
+ return m_ctx->player->GetDecoder()
+ ->TranslatePositionFrameToMs(position, fallback_framerate,
+ use_cutlist ? m_deleteMap :
+ frm_dir_map_t());
+}
+uint64_t DeleteMap::TranslatePositionMsToFrame(uint64_t dur_ms,
+ float fallback_framerate,
+ bool use_cutlist) const
+{
+ return m_ctx->player->GetDecoder()
+ ->TranslatePositionMsToFrame(dur_ms, fallback_framerate,
+ use_cutlist ? m_deleteMap :
+ frm_dir_map_t());
}
-uint64_t DeleteMap::TranslatePositionRelToAbs(const frm_dir_map_t &deleteMap,
- uint64_t relPosition)
+uint64_t DeleteMap::TranslatePositionAbsToRel(uint64_t position) const
{
- uint64_t addition = 0;
- uint64_t startOfCutRegion = 0;
- frm_dir_map_t::const_iterator i;
- bool withinCut = false;
- bool first = true;
- for (i = deleteMap.constBegin(); i != deleteMap.constEnd(); ++i)
- {
- if (first)
- withinCut = (i.value() == MARK_CUT_END);
- first = false;
- if (i.value() == MARK_CUT_START && !withinCut)
- {
- withinCut = true;
- startOfCutRegion = i.key();
- if (relPosition + addition <= startOfCutRegion)
- break;
- }
- else if (i.value() == MARK_CUT_END && withinCut)
- {
- withinCut = false;
- addition += (i.key() - startOfCutRegion);
- }
- }
- return relPosition + addition;
+ return DecoderBase::TranslatePositionAbsToRel(m_deleteMap, position);
+}
+
+uint64_t DeleteMap::TranslatePositionRelToAbs(uint64_t position) const
+{
+ return DecoderBase::TranslatePositionRelToAbs(m_deleteMap, position);
}
View
76 mythtv/libs/libmythtv/deletemap.h
@@ -25,20 +25,19 @@ class MTV_PUBLIC DeleteMap
DeleteMap(): m_editing(false),
m_nextCutStartIsValid(false),
m_nextCutStart(0), m_changed(true),
- m_seekamountpos(4), m_seekamount(30),
- m_ctx(0), m_cachedTotalForOSD(0), m_undoStackPointer(-1)
+ m_seekamountpos(4), m_seekamount(1.0),
+ m_ctx(NULL), m_cachedTotalForOSD(0), m_undoStackPointer(-1)
{
Push("");
}
void SetPlayerContext(PlayerContext *ctx) { m_ctx = ctx; }
- bool HandleAction(QString &action, uint64_t frame, uint64_t played,
- uint64_t total, double rate);
- int GetSeekAmount(void) const { return m_seekamount; }
- void UpdateSeekAmount(int change, double framerate);
- void SetSeekAmount(int amount) { m_seekamount = amount; }
+ bool HandleAction(QString &action, uint64_t frame, uint64_t played);
+ float GetSeekAmount(void) const { return m_seekamount; }
+ void UpdateSeekAmount(int change);
+ void SetSeekAmount(float amount) { m_seekamount = amount; }
- void UpdateOSD(uint64_t frame, uint64_t total, double frame_rate, OSD *osd);
+ void UpdateOSD(uint64_t frame, double frame_rate, OSD *osd);
bool IsEditing(void) const { return m_editing; }
void SetEditing(bool edit, OSD *osd = NULL);
@@ -48,40 +47,40 @@ class MTV_PUBLIC DeleteMap
bool IsSaved(void) const;
void SetMap(const frm_dir_map_t &map);
- void LoadCommBreakMap(uint64_t total, frm_dir_map_t &map);
- void SaveMap(uint64_t total, bool isAutoSave = false);
- void LoadMap(uint64_t total, QString undoMessage = "");
- bool LoadAutoSaveMap(uint64_t total);
- void CleanMap(uint64_t total);
+ void LoadCommBreakMap(frm_dir_map_t &map);
+ void SaveMap(bool isAutoSave = false);
+ void LoadMap(QString undoMessage = "");
+ bool LoadAutoSaveMap(void);
+ void CleanMap(void);
void Clear(QString undoMessage = "");
- void ReverseAll(uint64_t total);
- void Add(uint64_t frame, uint64_t total, MarkTypes type,
- QString undoMessage);
- void NewCut(uint64_t frame, uint64_t total);
- void Delete(uint64_t frame, uint64_t total, QString undoMessage = "");
- void MoveRelative(uint64_t frame, uint64_t total, bool right);
- void Move(uint64_t frame, uint64_t to, uint64_t total);
+ void ReverseAll(void);
+ void Add(uint64_t frame, MarkTypes type, QString undoMessage);
+ void NewCut(uint64_t frame);
+ void Delete(uint64_t frame, QString undoMessage);
+ void MoveRelative(uint64_t frame, bool right);
+ void Move(uint64_t frame, uint64_t to);
bool IsInDelete(uint64_t frame) const;
- uint64_t GetNearestMark(uint64_t frame, uint64_t total, bool right,
- bool *hasMark = 0) const;
+ uint64_t GetNearestMark(uint64_t frame, bool right,
+ bool *hasMark = NULL) const;
bool IsTemporaryMark(uint64_t frame) const;
bool HasTemporaryMark(void) const;
- uint64_t GetLastFrame(uint64_t total) const;
- uint64_t TranslatePositionAbsToRel(uint64_t absPosition) const {
- return TranslatePositionAbsToRel(m_deleteMap, absPosition);
- }
- uint64_t TranslatePositionRelToAbs(uint64_t relPosition) const {
- return TranslatePositionRelToAbs(m_deleteMap, relPosition);
- }
- static uint64_t TranslatePositionAbsToRel(const frm_dir_map_t &deleteMap,
- uint64_t absPosition);
- static uint64_t TranslatePositionRelToAbs(const frm_dir_map_t &deleteMap,
- uint64_t relPosition);
-
- void TrackerReset(uint64_t frame, uint64_t total);
- bool TrackerWantsToJump(uint64_t frame, uint64_t total, uint64_t &to);
+ uint64_t GetLastFrame(void) const;
+
+ // Provide translations between frame numbers and millisecond
+ // durations, optionally taking the custlist into account.
+ uint64_t TranslatePositionFrameToMs(uint64_t position,
+ float fallback_framerate,
+ bool use_cutlist) const;
+ uint64_t TranslatePositionMsToFrame(uint64_t dur_ms,
+ float fallback_framerate,
+ bool use_cutlist) const;
+ uint64_t TranslatePositionAbsToRel(uint64_t position) const;
+ uint64_t TranslatePositionRelToAbs(uint64_t position) const;
+
+ void TrackerReset(uint64_t frame);
+ bool TrackerWantsToJump(uint64_t frame, uint64_t &to);
bool Undo(void);
bool Redo(void);
@@ -97,14 +96,17 @@ class MTV_PUBLIC DeleteMap
void Push(QString undoMessage);
+ QString CreateTimeString(uint64_t frame, bool use_cutlist,
+ double frame_rate, bool full_resolution) const;
+
bool m_editing;
bool m_nextCutStartIsValid;
uint64_t m_nextCutStart;
frm_dir_map_t m_deleteMap;
QString m_seekText;
bool m_changed;
int m_seekamountpos;
- int m_seekamount;
+ float m_seekamount;
PlayerContext *m_ctx;
uint64_t m_cachedTotalForOSD;
View
1 mythtv/libs/libmythtv/mythcommflagplayer.cpp
@@ -74,6 +74,7 @@ bool MythCommFlagPlayer::RebuildSeekTable(
player_ctx->playingInfo->ClearPositionMap(MARK_KEYFRAME);
player_ctx->playingInfo->ClearPositionMap(MARK_GOP_START);
player_ctx->playingInfo->ClearPositionMap(MARK_GOP_BYFRAME);
+ player_ctx->playingInfo->ClearPositionMap(MARK_DURATION_MS);
}
player_ctx->UnlockPlayingInfo(__FILE__, __LINE__);
View
273 mythtv/libs/libmythtv/mythplayer.cpp
@@ -15,6 +15,7 @@
#include <sched.h>
#include <sys/time.h>
#include <assert.h>
+#include <math.h>
// C++ headers
#include <algorithm>
@@ -991,14 +992,14 @@ int MythPlayer::OpenFile(uint retries)
if (ret > 0)
{
hasFullPositionMap = true;
- deleteMap.LoadMap(totalFrames);
- deleteMap.TrackerReset(0, totalFrames);
+ deleteMap.LoadMap();
+ deleteMap.TrackerReset(0);
}
// Determine the initial bookmark and update it for the cutlist
bookmarkseek = GetBookmark();
- deleteMap.TrackerReset(bookmarkseek, totalFrames);
- deleteMap.TrackerWantsToJump(bookmarkseek, totalFrames, bookmarkseek);
+ deleteMap.TrackerReset(bookmarkseek);
+ deleteMap.TrackerWantsToJump(bookmarkseek, bookmarkseek);
if (!gCoreContext->IsDatabaseIgnored() &&
player_ctx->playingInfo->QueryAutoExpire() == kLiveTVAutoExpire)
@@ -2395,7 +2396,12 @@ bool MythPlayer::FastForward(float seconds)
return false;
if (fftime <= 0)
- fftime = (long long)(seconds * video_frame_rate + 0.5);
+ {
+ float current = ComputeSecs(framesPlayed, true);
+ float dest = current + seconds;
+ uint64_t target = FindFrame(dest, true);
+ fftime = target - framesPlayed;
+ }
return fftime > CalcMaxFFTime(fftime, false);
}
@@ -2405,7 +2411,12 @@ bool MythPlayer::Rewind(float seconds)
return false;
if (rewindtime <= 0)
- rewindtime = (long long)(seconds * video_frame_rate + 0.5);
+ {
+ float current = ComputeSecs(framesPlayed, true);
+ float dest = current + seconds;
+ uint64_t target = FindFrame(dest, true);
+ rewindtime = target - framesPlayed;
+ }
return (uint64_t)rewindtime >= framesPlayed;
}
@@ -2766,6 +2777,7 @@ void MythPlayer::EventStart(void)
void MythPlayer::EventLoop(void)
{
+ uint64_t frameCount = GetCurrentFrameCount();
// recreate the osd if a reinit was triggered by another thread
if (reinit_osd)
ReinitOSD();
@@ -2794,8 +2806,7 @@ void MythPlayer::EventLoop(void)
// N.B. the positionmap update and osd refresh are asynchronous
forcePositionMapSync = true;
osdLock.lock();
- deleteMap.UpdateOSD(framesPlayed, totalFrames, video_frame_rate,
- osd);
+ deleteMap.UpdateOSD(framesPlayed, video_frame_rate, osd);
osdLock.unlock();
editUpdateTimer.start();
}
@@ -2942,8 +2953,10 @@ void MythPlayer::EventLoop(void)
{
QString msg;
uint64_t jumpto = 0;
+ // XXX CommBreakMap should use duration map not video_frame_rate
bool jump = commBreakMap.DoSkipCommercials(jumpto, framesPlayed,
- video_frame_rate, totalFrames, msg);
+ video_frame_rate,
+ frameCount, msg);
if (!msg.isEmpty())
SetOSDStatus(msg, kOSDTimeout_Med);
if (jump)
@@ -2959,9 +2972,10 @@ void MythPlayer::EventLoop(void)
(kCommSkipOff != commBreakMap.GetAutoCommercialSkip()))
{
QString msg;
+ // XXX CommBreakMap should use duration map not video_frame_rate
bool jump = commBreakMap.AutoCommercialSkip(jumpto, framesPlayed,
video_frame_rate,
- totalFrames, msg);
+ frameCount, msg);
if (!msg.isEmpty())
SetOSDStatus(msg, kOSDTimeout_Med);
if (jump)
@@ -2970,7 +2984,7 @@ void MythPlayer::EventLoop(void)
// Handle cutlist skipping
if (!allpaused && (ffrew_skip == 1) &&
- deleteMap.TrackerWantsToJump(framesPlayed, totalFrames, jumpto))
+ deleteMap.TrackerWantsToJump(framesPlayed, jumpto))
{
if (jumpto == totalFrames)
{
@@ -3369,7 +3383,7 @@ void MythPlayer::SetWatched(bool forceWatched)
return;
}
- long long numFrames = totalFrames;
+ uint64_t numFrames = GetCurrentFrameCount();
// For recordings we want to ignore the post-roll and account for
// in-progress recordings where totalFrames doesn't represent
@@ -3551,6 +3565,16 @@ bool MythPlayer::DoRewind(uint64_t frames, double inaccuracy)
return true;
}
+bool MythPlayer::DoRewindSecs(float secs, double inaccuracy, bool use_cutlist)
+{
+ float current = ComputeSecs(framesPlayed, use_cutlist);
+ float target = current - secs;
+ if (target < 0)
+ target = 0;
+ uint64_t targetFrame = FindFrame(target, use_cutlist);
+ return DoRewind(framesPlayed - targetFrame, inaccuracy);
+}
+
long long MythPlayer::CalcRWTime(long long rw) const
{
bool hasliveprev = (livetv && player_ctx->tvchain &&
@@ -3559,28 +3583,30 @@ long long MythPlayer::CalcRWTime(long long rw) const
if (!hasliveprev || ((int64_t)framesPlayed > (rw - 1)))
return rw;
- player_ctx->tvchain->JumpToNext(false, (int)(-15.0 * video_frame_rate));
+ player_ctx->tvchain->JumpToNext(false, (int)(-15.0 * video_frame_rate)); // XXX use seconds instead of assumed framerate
return -1;
}
-long long MythPlayer::CalcMaxFFTime(long long ff, bool setjump) const
+long long MythPlayer::CalcMaxFFTime(long long ffframes, bool setjump) const
{
- long long maxtime = (long long)(1.0 * video_frame_rate);
+ float maxtime = 1.0;
bool islivetvcur = (livetv && player_ctx->tvchain &&
!player_ctx->tvchain->HasNext());
if (livetv || IsWatchingInprogress())
- maxtime = (long long)(3.0 * video_frame_rate);
+ maxtime = 3.0;
- long long ret = ff;
+ long long ret = ffframes;
+ float ff = ComputeSecs(ffframes, true);
+ float secsPlayed = ComputeSecs(framesPlayed, true);
limitKeyRepeat = false;
if (livetv && !islivetvcur && player_ctx->tvchain)
{
if (totalFrames > 0)
{
- long long behind = totalFrames - framesPlayed;
+ float behind = ComputeSecs(totalFrames, true) - secsPlayed;
if (behind < maxtime || behind - ff <= maxtime * 2)
{
ret = -1;
@@ -3591,13 +3617,15 @@ long long MythPlayer::CalcMaxFFTime(long long ff, bool setjump) const
}
else if (islivetvcur || IsWatchingInprogress())
{
- long long behind = player_ctx->recorder->GetFramesWritten() -
- framesPlayed;
+ float secsWritten =
+ ComputeSecs(player_ctx->recorder->GetFramesWritten(), true);
+ float behind = secsWritten - secsPlayed;
if (behind < maxtime) // if we're close, do nothing
ret = 0;
else if (behind - ff <= maxtime)
- ret = behind - maxtime;
+ ret = TranslatePositionMsToFrame(1000 * (secsWritten - maxtime),
+ true) - framesPlayed;
if (behind < maxtime * 3)
limitKeyRepeat = true;
@@ -3606,11 +3634,15 @@ long long MythPlayer::CalcMaxFFTime(long long ff, bool setjump) const
{
if (totalFrames > 0)
{
- long long behind = totalFrames - framesPlayed;
+ float behind = ComputeSecs(totalFrames, true) - secsPlayed;
if (behind < maxtime)
ret = 0;
else if (behind - ff <= maxtime * 2)
- ret = behind - maxtime * 2;
+ {
+ uint64_t ms = 1000 *
+ (ComputeSecs(totalFrames, true) - maxtime * 2);
+ ret = TranslatePositionMsToFrame(ms, true) - framesPlayed;
+ }
}
}
@@ -3659,9 +3691,8 @@ bool MythPlayer::IsNearEnd(void)
if (!player_ctx->IsPIP() &&
player_ctx->GetState() == kState_WatchingPreRecorded)
{
- if (framesRead >= deleteMap.GetLastFrame(totalFrames))
- return true;
- framesLeft = (totalFrames > framesRead) ? totalFrames - framesRead : 0;
+ uint64_t frameCount = GetCurrentFrameCount();
+ framesLeft = (frameCount > framesRead) ? frameCount - framesRead : 0;
return (framesLeft < (uint64_t)margin);
}
@@ -3694,7 +3725,7 @@ bool MythPlayer::DoFastForward(uint64_t frames, double inaccuracy)
if (!deleteMap.IsEditing() && IsInDelete(desiredFrame))
{
- uint64_t endcheck = deleteMap.GetLastFrame(totalFrames);
+ uint64_t endcheck = deleteMap.GetLastFrame();
if (desiredFrame > endcheck)
desiredFrame = endcheck;
}
@@ -3708,6 +3739,15 @@ bool MythPlayer::DoFastForward(uint64_t frames, double inaccuracy)
return true;
}
+bool MythPlayer::DoFastForwardSecs(float secs, double inaccuracy,
+ bool use_cutlist)
+{
+ float current = ComputeSecs(framesPlayed, use_cutlist);
+ float target = current + secs;
+ uint64_t targetFrame = FindFrame(target, use_cutlist);
+ return DoFastForward(targetFrame - framesPlayed, inaccuracy);
+}
+
void MythPlayer::DoJumpToFrame(uint64_t frame, double inaccuracy)
{
if (frame > framesPlayed)
@@ -3726,7 +3766,7 @@ void MythPlayer::WaitForSeek(uint64_t frame, uint64_t seeksnap_wanted)
bool islivetvcur = (livetv && player_ctx->tvchain &&
!player_ctx->tvchain->HasNext());
- uint64_t max = totalFrames;
+ uint64_t max = GetCurrentFrameCount();
if (islivetvcur || IsWatchingInprogress())
{
max = (uint64_t)player_ctx->recorder->GetFramesWritten();
@@ -3793,7 +3833,7 @@ void MythPlayer::ClearAfterSeek(bool clearvideobuffers)
audio.Reset();
ResetCaptions();
- deleteMap.TrackerReset(framesPlayed, totalFrames);
+ deleteMap.TrackerReset(framesPlayed);
commBreakMap.SetTracker(framesPlayed);
commBreakMap.ResetLastSkip();
needNewPauseFrame = true;
@@ -3834,15 +3874,15 @@ bool MythPlayer::EnableEdit(void)
ResetCaptions();
osd->HideAll();
- bool loadedAutoSave = deleteMap.LoadAutoSaveMap(totalFrames);
+ bool loadedAutoSave = deleteMap.LoadAutoSaveMap();
if (loadedAutoSave)
{
SetOSDMessage(tr("Using previously auto-saved cuts"),
kOSDTimeout_Short);
}
- deleteMap.UpdateSeekAmount(0, video_frame_rate);
- deleteMap.UpdateOSD(framesPlayed, totalFrames, video_frame_rate, osd);
+ deleteMap.UpdateSeekAmount(0);
+ deleteMap.UpdateOSD(framesPlayed, video_frame_rate, osd);
deleteMap.SetFileEditing(true);
player_ctx->LockPlayingInfo(__FILE__, __LINE__);
if (player_ctx->playingInfo)
@@ -3868,11 +3908,11 @@ void MythPlayer::DisableEdit(int howToSave)
deleteMap.SetEditing(false, osd);
if (howToSave == 0)
- deleteMap.LoadMap(totalFrames);
+ deleteMap.LoadMap();
// Unconditionally save to remove temporary marks from the DB.
if (howToSave >= 0)
- deleteMap.SaveMap(totalFrames);
- deleteMap.TrackerReset(framesPlayed, totalFrames);
+ deleteMap.SaveMap();
+ deleteMap.TrackerReset(framesPlayed);
deleteMap.SetFileEditing(false);
player_ctx->LockPlayingInfo(__FILE__, __LINE__);
if (player_ctx->playingInfo)
@@ -3894,24 +3934,22 @@ bool MythPlayer::HandleProgramEditorActions(QStringList &actions,
{
QString action = actions[i];
handled = true;
- int seekamount = deleteMap.GetSeekAmount();
+ float seekamount = deleteMap.GetSeekAmount();
if (action == ACTION_LEFT)
{
- if (deleteMap.GetSeekAmount() > 0)
- {
- DoRewind(seekamount, seekamount > 1 ?
- kInaccuracyEditor : kInaccuracyNone);
- }
+ if (seekamount == 0) // 1 frame
+ DoRewind(1, kInaccuracyNone);
+ else if (seekamount > 0)
+ DoRewindSecs(seekamount, kInaccuracyEditor, false);
else
HandleArbSeek(false);
}
else if (action == ACTION_RIGHT)
{
- if (deleteMap.GetSeekAmount() > 0)
- {
- DoFastForward(seekamount, seekamount > 1 ?
- kInaccuracyEditor : kInaccuracyNone);
- }
+ if (seekamount == 0) // 1 frame
+ DoFastForward(1, kInaccuracyNone);
+ else if (seekamount > 0)
+ DoFastForwardSecs(seekamount, kInaccuracyEditor, false);
else
HandleArbSeek(true);
}
@@ -3921,61 +3959,60 @@ bool MythPlayer::HandleProgramEditorActions(QStringList &actions,
{
frm_dir_map_t map;
commBreakMap.GetMap(map);
- deleteMap.LoadCommBreakMap(totalFrames, map);
+ deleteMap.LoadCommBreakMap(map);
}
}
else if (action == ACTION_PREVCUT)
{
- int old_seekamount = deleteMap.GetSeekAmount();
+ float old_seekamount = deleteMap.GetSeekAmount();
deleteMap.SetSeekAmount(-2);
HandleArbSeek(false);
deleteMap.SetSeekAmount(old_seekamount);
}
else if (action == ACTION_NEXTCUT)
{
- int old_seekamount = deleteMap.GetSeekAmount();
+ float old_seekamount = deleteMap.GetSeekAmount();
deleteMap.SetSeekAmount(-2);
HandleArbSeek(true);
deleteMap.SetSeekAmount(old_seekamount);
}
#define FFREW_MULTICOUNT 10
else if (action == ACTION_BIGJUMPREW)
{
- if (seekamount > 0)
- DoRewind(seekamount * FFREW_MULTICOUNT, seekamount > 1 ?
- kInaccuracyEditor : kInaccuracyNone);
+ if (seekamount == 0)
+ DoRewind(FFREW_MULTICOUNT, kInaccuracyNone);
+ else if (seekamount > 0)
+ DoRewindSecs(seekamount * FFREW_MULTICOUNT,
+ kInaccuracyEditor, false);
else
- {
- int fps = (int)ceil(video_frame_rate);
- DoRewind(fps * FFREW_MULTICOUNT / 2, kInaccuracyNone);
- }
+ DoRewindSecs(FFREW_MULTICOUNT / 2,
+ kInaccuracyNone, false);
}
else if (action == ACTION_BIGJUMPFWD)
{
- if (seekamount > 0)
- DoFastForward(seekamount * FFREW_MULTICOUNT, seekamount > 1 ?
- kInaccuracyEditor : kInaccuracyNone);
+ if (seekamount == 0)
+ DoFastForward(FFREW_MULTICOUNT, kInaccuracyNone);
+ else if (seekamount > 0)
+ DoFastForwardSecs(seekamount * FFREW_MULTICOUNT,
+ kInaccuracyEditor, false);
else
- {
- int fps = (int)ceil(video_frame_rate);
- DoFastForward(fps * FFREW_MULTICOUNT / 2,
- kInaccuracyNone);
- }
+ DoFastForwardSecs(FFREW_MULTICOUNT / 2,
+ kInaccuracyNone, false);
}
else if (action == ACTION_SELECT)
{
- deleteMap.NewCut(frame, totalFrames);
+ deleteMap.NewCut(frame);
SetOSDMessage(tr("New cut added."), kOSDTimeout_Short);
refresh = true;
}
else if (action == "DELETE")
{
- deleteMap.Delete(frame, totalFrames, tr("Delete"));
+ deleteMap.Delete(frame, tr("Delete"));
refresh = true;
}
else if (action == "REVERT")
{
- deleteMap.LoadMap(totalFrames, tr("Undo Changes"));
+ deleteMap.LoadMap(tr("Undo Changes"));
refresh = true;
}
else if (action == "REVERTEXIT")
@@ -3985,7 +4022,7 @@ bool MythPlayer::HandleProgramEditorActions(QStringList &actions,
}
else if (action == ACTION_SAVEMAP)
{
- deleteMap.SaveMap(totalFrames);
+ deleteMap.SaveMap();
refresh = true;
}
else if (action == "EDIT" || action == "SAVEEXIT")
@@ -3997,8 +4034,7 @@ bool MythPlayer::HandleProgramEditorActions(QStringList &actions,
{
QString undoMessage = deleteMap.GetUndoMessage();
QString redoMessage = deleteMap.GetRedoMessage();
- handled = deleteMap.HandleAction(action, frame, framesPlayed,
- totalFrames, video_frame_rate);
+ handled = deleteMap.HandleAction(action, frame, framesPlayed);
if (handled && (action == "CUTTOBEGINNING" ||
action == "CUTTOEND" || action == "NEWCUT"))
{
@@ -4024,8 +4060,7 @@ bool MythPlayer::HandleProgramEditorActions(QStringList &actions,
osdLock.lock();
if (osd)
{
- deleteMap.UpdateOSD(framesPlayed, totalFrames, video_frame_rate,
- osd);
+ deleteMap.UpdateOSD(framesPlayed, video_frame_rate, osd);
}
osdLock.unlock();
}
@@ -4040,7 +4075,7 @@ bool MythPlayer::IsInDelete(uint64_t frame)
uint64_t MythPlayer::GetNearestMark(uint64_t frame, bool right)
{
- return deleteMap.GetNearestMark(frame, totalFrames, right);
+ return deleteMap.GetNearestMark(frame, right);
}
bool MythPlayer::IsTemporaryMark(uint64_t frame)
@@ -4057,8 +4092,7 @@ void MythPlayer::HandleArbSeek(bool right)
{
if (deleteMap.GetSeekAmount() == -2)
{
- long long framenum = deleteMap.GetNearestMark(framesPlayed,
- totalFrames, right);
+ uint64_t framenum = deleteMap.GetNearestMark(framesPlayed, right);
if (right && (framenum > (int64_t)framesPlayed))
DoFastForward(framenum - framesPlayed, kInaccuracyNone);
else if (!right && ((int64_t)framesPlayed > framenum))
@@ -4067,13 +4101,9 @@ void MythPlayer::HandleArbSeek(bool right)
else
{
if (right)
- {
DoFastForward(2, kInaccuracyFull);
- }
else
- {
DoRewind(2, kInaccuracyFull);
- }
}
}
@@ -4304,7 +4334,7 @@ void MythPlayer::SeekForScreenGrab(uint64_t &number, uint64_t frameNum,
else
{
uint64_t oldnumber = number;
- deleteMap.LoadMap(totalFrames);
+ deleteMap.LoadMap();
commBreakMap.LoadMap(player_ctx, framesPlayed);
bool started_in_break_map = false;
@@ -4464,7 +4494,7 @@ bool MythPlayer::TranscodeGetNextFrame(
videoOutput->GetLastDecodedFrame()->frameNumber;
if ((lastDecodedFrameNumber == 0) && honorCutList)
- deleteMap.TrackerReset(0, 0);
+ deleteMap.TrackerReset(0);
if (!decoderThread)
DecoderStart(true/*start paused*/);
@@ -4484,8 +4514,7 @@ bool MythPlayer::TranscodeGetNextFrame(
return false;
uint64_t jumpto = 0;
- if (deleteMap.TrackerWantsToJump(lastDecodedFrameNumber, totalFrames,
- jumpto))
+ if (deleteMap.TrackerWantsToJump(lastDecodedFrameNumber, jumpto))
{
LOG(VB_GENERAL, LOG_INFO, LOC +
QString("Fast-Forwarding from %1 to %2")
@@ -4612,20 +4641,37 @@ int MythPlayer::GetSecondsBehind(void) const
return (int)((float)(written - played) / video_frame_rate);
}
-int64_t MythPlayer::GetSecondsPlayed(void)
+int64_t MythPlayer::GetTotalSeconds(void) const
{
-#if 0
- return decoder->IsCodecMPEG() ?
- (disp_timecode / 1000.f) :
- (framesPlayed / video_frame_rate);
-#else
- return framesPlayed / video_frame_rate;
-#endif
+ return totalDuration;
}
-int64_t MythPlayer::GetTotalSeconds(void) const
+// Returns the total frame count, as totalFrames for a completed
+// recording, or the most recent frame count from the recorder for
+// live TV or an in-progress recording.
+uint64_t MythPlayer::GetCurrentFrameCount(void) const
{
- return totalDuration;
+ uint64_t result = totalFrames;
+ if (IsWatchingInprogress())
+ result = player_ctx->recorder->GetFramesWritten();
+ return result;
+}
+
+// Finds the frame number associated with the given time offset. A
+// positive offset or +0.0f indicate offset from the beginning. A
+// negative offset or -0.0f indicate offset from the end. Limit the
+// result to within bounds of the video.
+uint64_t MythPlayer::FindFrame(float offset, bool use_cutlist) const
+{
+ uint64_t length_ms = TranslatePositionFrameToMs(totalFrames, use_cutlist);
+ uint64_t offset_ms = offset * 1000 + 0.5;
+ if (signbit(offset))
+ offset_ms += length_ms;
+ if (offset_ms < 0)
+ offset_ms = 0;
+ if (offset_ms > length_ms)
+ offset_ms = length_ms;
+ return TranslatePositionMsToFrame(offset_ms, use_cutlist);
}
void MythPlayer::calcSliderPos(osdInfo &info, bool paddedFields)
@@ -4644,24 +4690,28 @@ void MythPlayer::calcSliderPos(osdInfo &info, bool paddedFields)
info.values.insert("progbefore", 0);
info.values.insert("progafter", 0);
- int playbackLen = GetTotalSeconds();
- float secsplayed = (float)GetSecondsPlayed();
+ uint64_t frames_played = framesPlayed;
+ uint64_t total_frames = totalFrames;
+ int playbackLen = 0;
+ bool fixed_playbacklen = false;
- if (totalDuration == 0 || decoder->GetCodecDecoderName() == "nuppel")
+ if (decoder->GetCodecDecoderName() == "nuppel")
+ {
playbackLen = totalLength;
+ fixed_playbacklen = true;
+ }
if (livetv && player_ctx->tvchain)
{
info.values["progbefore"] = (int)player_ctx->tvchain->HasPrev();
info.values["progafter"] = (int)player_ctx->tvchain->HasNext();
playbackLen = player_ctx->tvchain->GetLengthAtCurPos();
islive = true;
+ fixed_playbacklen = true;
}
else if (IsWatchingInprogress())
{
- playbackLen =
- (int)(((float)player_ctx->recorder->GetFramesWritten() /
- video_frame_rate));
+ total_frames = player_ctx->recorder->GetFramesWritten();
islive = true;
}
else
@@ -4691,20 +4741,19 @@ void MythPlayer::calcSliderPos(osdInfo &info, bool paddedFields)
}
}
- playbackLen = max(playbackLen, 1);
- secsplayed = min((float)playbackLen, max(secsplayed, 0.0f));
-
// Set the raw values, followed by the translated values.
for (int i = 0; i < 2 ; ++i)
{
QString relPrefix = (i == 0 ? "" : "rel");
- if (i > 0)
- {
- playbackLen = deleteMap.TranslatePositionAbsToRel(playbackLen * video_frame_rate) /
- video_frame_rate;
- secsplayed = deleteMap.TranslatePositionAbsToRel(secsplayed * video_frame_rate) /
- video_frame_rate;
- }
+ if (!fixed_playbacklen)
+ playbackLen =
+ TranslatePositionFrameToMs(total_frames, (i > 0))
+ / 1000;
+ playbackLen = max(playbackLen, 1);
+ float secsplayed =
+ TranslatePositionFrameToMs(frames_played, (i > 0))
+ / 1000;
+ secsplayed = min((float)playbackLen, max(secsplayed, 0.0f));
info.values.insert(relPrefix + "secondsplayed", (int)secsplayed);
info.values.insert(relPrefix + "totalseconds", playbackLen);
@@ -5057,8 +5106,9 @@ void MythPlayer::SetDecoder(DecoderBase *dec)
totalDecoderPause = false;
}
-bool MythPlayer::PosMapFromEnc(unsigned long long start,
- QMap<long long, long long> &posMap)
+bool MythPlayer::PosMapFromEnc(uint64_t start,
+ frm_pos_map_t &posMap,
+ frm_pos_map_t &durMap)
{
// Reads only new positionmap entries from encoder
if (!(livetv || (player_ctx->recorder &&
@@ -5073,6 +5123,7 @@ bool MythPlayer::PosMapFromEnc(unsigned long long start,
QString("Filling position map from %1 to %2") .arg(start).arg("end"));
player_ctx->recorder->FillPositionMap(start, -1, posMap);
+ player_ctx->recorder->FillDurationMap(start, -1, durMap);
return true;
}
View
36 mythtv/libs/libmythtv/mythplayer.h
@@ -180,8 +180,8 @@ class MTV_PUBLIC MythPlayer
float GetNextPlaySpeed(void) const { return next_play_speed; }
int GetLength(void) const { return totalLength; }
uint64_t GetTotalFrameCount(void) const { return totalFrames; }
+ uint64_t GetCurrentFrameCount(void) const;
uint64_t GetFramesPlayed(void) const { return framesPlayed; }
- virtual int64_t GetSecondsPlayed(void);
virtual int64_t GetTotalSeconds(void) const;
virtual uint64_t GetBookmark(void);
QString GetError(void) const;
@@ -321,8 +321,9 @@ class MTV_PUBLIC MythPlayer
virtual void GoToDVDProgram(bool direction) { (void) direction; }
// Position Map Stuff
- bool PosMapFromEnc(unsigned long long start,
- QMap<long long, long long> &posMap);
+ bool PosMapFromEnc(uint64_t start,
+ frm_pos_map_t &posMap,
+ frm_pos_map_t &durMap);
// OSD locking for TV class
bool TryLockOSD(void) { return osdLock.tryLock(50); }
@@ -391,12 +392,31 @@ class MTV_PUBLIC MythPlayer
virtual long long CalcMaxFFTime(long long ff, bool setjump = true) const;
long long CalcRWTime(long long rw) const;
virtual void calcSliderPos(osdInfo &info, bool paddedFields = false);
- uint64_t TranslatePositionAbsToRel(uint64_t absPosition) const {
- return deleteMap.TranslatePositionAbsToRel(absPosition);
+ uint64_t TranslatePositionFrameToMs(uint64_t position,
+ bool use_cutlist) const {
+ return deleteMap.TranslatePositionFrameToMs(position,
+ GetFrameRate(),
+ use_cutlist);
}
- uint64_t TranslatePositionRelToAbs(uint64_t relPosition) const {
- return deleteMap.TranslatePositionRelToAbs(relPosition);
+ uint64_t TranslatePositionMsToFrame(uint64_t position,
+ bool use_cutlist) const {
+ return deleteMap.TranslatePositionMsToFrame(position,
+ GetFrameRate(),
+ use_cutlist);
}
+ // TranslatePositionAbsToRel and TranslatePositionRelToAbs are
+ // used for frame calculations when seeking relative to a number
+ // of frames rather than by time.
+ uint64_t TranslatePositionAbsToRel(uint64_t position) const {
+ return deleteMap.TranslatePositionAbsToRel(position);
+ }
+ uint64_t TranslatePositionRelToAbs(uint64_t position) const {
+ return deleteMap.TranslatePositionRelToAbs(position);
+ }
+ float ComputeSecs(uint64_t position, bool use_cutlist) const {
+ return TranslatePositionFrameToMs(position, use_cutlist) / 1000.0;
+ }
+ uint64_t FindFrame(float offset, bool use_cutlist) const;
// Commercial stuff
void SetAutoCommercialSkip(CommSkipMode autoskip)
@@ -555,6 +575,8 @@ class MTV_PUBLIC MythPlayer
// The "inaccuracy" argument is generally one of the kInaccuracy* values.
bool DoFastForward(uint64_t frames, double inaccuracy);
bool DoRewind(uint64_t frames, double inaccuracy);
+ bool DoFastForwardSecs(float secs, double inaccuracy, bool use_cutlist);
+ bool DoRewindSecs(float secs, double inaccuracy, bool use_cutlist);
void DoJumpToFrame(uint64_t frame, double inaccuracy);
// Private seeking stuff
View
7 mythtv/libs/libmythtv/nuppeldecoder.cpp
@@ -378,6 +378,10 @@ int NuppelDecoder::OpenFile(RingBuffer *rbuffer, bool novideo,
ste.keyframe_number * keyframedist,
ste.file_offset};
m_positionMap.push_back(e);
+ uint64_t frame_num = ste.keyframe_number * keyframedist;
+ m_frameToDurMap[frame_num] =
+ frame_num * 1000 / video_frame_rate;
+ m_durToFrameMap[m_frameToDurMap[frame_num]] = frame_num;
}
hasFullPositionMap = true;
totalLength = (int)((ste.keyframe_number * keyframedist * 1.0) /
@@ -1152,6 +1156,9 @@ bool NuppelDecoder::GetFrame(DecodeType decodetype)
{
PosMapEntry e = {this_index, lastKey, currentposition};
m_positionMap.push_back(e);
+ m_frameToDurMap[lastKey] =
+ lastKey * 1000 / video_frame_rate;
+ m_durToFrameMap[m_frameToDurMap[lastKey]] = lastKey;
}
}
}
View
42 mythtv/libs/libmythtv/recorders/dtvrecorder.cpp
@@ -81,7 +81,9 @@ DTVRecorder::DTVRecorder(TVRec *rec) :
_use_pts(false),
_packet_count(0),
_continuity_error_count(0),
- _frames_seen_count(0), _frames_written_count(0)
+ _frames_seen_count(0), _frames_written_count(0),
+ _frame_interval(0), _frame_duration(0),
+ _total_duration(0)
{
SetPositionMapType(MARK_GOP_BYFRAME);
_payload_buffer.reserve(TSPacket::kSize * (50 + 1));
@@ -159,6 +161,8 @@ void DTVRecorder::FinishRecording(void)
if (ringBuffer)
curRecording->SaveFilesize(ringBuffer->GetRealFileSize());
SavePositionMap(true);
+ curRecording->SaveTotalDuration((int64_t)_total_duration);
+ curRecording->SaveTotalFrames(_frames_written_count);
}
}
@@ -229,7 +233,10 @@ void DTVRecorder::Reset(void)
_start_code = 0xffffffff;
if (curRecording)
+ {
curRecording->ClearPositionMap(MARK_GOP_BYFRAME);
+ curRecording->ClearPositionMap(MARK_DURATION_MS);
+ }
}
void DTVRecorder::SetStreamData(MPEGStreamData *data)
@@ -495,6 +502,11 @@ bool DTVRecorder::FindMPEG2Keyframes(const TSPacket* tspacket)
_repeat_pict = 2;
}
}
+ // The _repeat_pict code above matches
+ // mpegvideo_extract_headers(), but the
+ // code in mpeg_field_start() computes a
+ // value one less, which seems correct.
+ --_repeat_pict;
}
break;
}
@@ -533,7 +545,7 @@ bool DTVRecorder::FindMPEG2Keyframes(const TSPacket* tspacket)
{
_frames_seen_count++;
if (!_wait_for_keyframe_option || _first_keyframe>=0)
- _frames_written_count++;
+ UpdateFramesWritten();
}
if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
@@ -626,6 +638,20 @@ void DTVRecorder::HandleTimestamps(int stream_id, int64_t pts, int64_t dts)
_ts_count[stream_id]++;
}
+void DTVRecorder::UpdateFramesWritten(void)
+{
+ _frames_written_count++;
+ if (m_frameRate > 0)
+ {
+ // m_frameRate is frames per 1000 seconds, e.g. 29970 for
+ // 29.97 fps. Calculate usec values.
+ _frame_interval = 1000000000.0 / m_frameRate;
+ _frame_duration = _frame_interval +
+ (_repeat_pict * _frame_interval * 0.5f);
+ _total_duration += _frame_duration;
+ }
+}
+
bool DTVRecorder::FindAudioKeyframes(const TSPacket*)
{
bool hasKeyFrame = false;
@@ -656,7 +682,7 @@ bool DTVRecorder::FindAudioKeyframes(const TSPacket*)
}
if (!_wait_for_keyframe_option || _first_keyframe>=0)
- _frames_written_count++;
+ UpdateFramesWritten();
}
return hasKeyFrame;