summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Stichnoth <jstichnoth@mythtv.org>2013-01-01 03:23:42 (GMT)
committer Jim Stichnoth <jstichnoth@mythtv.org>2013-01-01 03:23:42 (GMT)
commit49dbed5be0729b04a5f0fd0426a32fceb2dd7935 (patch)
tree427ade39514218b1c55d1a3b8bd7d2360bafaad7
parent1eaecea6dd38db0c20b94f8ecb549b4c29491297 (diff)
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.
-rw-r--r--mythtv/bindings/perl/MythTV.pm4
-rw-r--r--mythtv/bindings/php/MythBackend.php4
-rw-r--r--mythtv/bindings/python/MythTV/static.py6
-rw-r--r--mythtv/libs/libmythbase/mythversion.h10
-rw-r--r--mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp11
-rw-r--r--mythtv/libs/libmythtv/DVD/mythdvdplayer.h1
-rw-r--r--mythtv/libs/libmythtv/avformatdecoder.cpp11
-rw-r--r--mythtv/libs/libmythtv/decoderbase.cpp238
-rw-r--r--mythtv/libs/libmythtv/decoderbase.h22
-rw-r--r--mythtv/libs/libmythtv/deletemap.cpp243
-rw-r--r--mythtv/libs/libmythtv/deletemap.h76
-rw-r--r--mythtv/libs/libmythtv/mythcommflagplayer.cpp1
-rw-r--r--mythtv/libs/libmythtv/mythplayer.cpp273
-rw-r--r--mythtv/libs/libmythtv/mythplayer.h36
-rw-r--r--mythtv/libs/libmythtv/nuppeldecoder.cpp7
-rw-r--r--mythtv/libs/libmythtv/recorders/dtvrecorder.cpp42
-rw-r--r--mythtv/libs/libmythtv/recorders/dtvrecorder.h4
-rw-r--r--mythtv/libs/libmythtv/recorders/recorderbase.cpp29
-rw-r--r--mythtv/libs/libmythtv/recorders/recorderbase.h4
-rw-r--r--mythtv/libs/libmythtv/remoteencoder.cpp35
-rw-r--r--mythtv/libs/libmythtv/remoteencoder.h7
-rw-r--r--mythtv/libs/libmythtv/tv_play.cpp107
-rw-r--r--mythtv/libs/libmythtv/tv_play.h1
-rw-r--r--mythtv/libs/libmythtv/tv_rec.cpp11
-rw-r--r--mythtv/libs/libmythtv/tv_rec.h1
-rw-r--r--mythtv/programs/mythbackend/encoderlink.cpp13
-rw-r--r--mythtv/programs/mythbackend/encoderlink.h1
-rw-r--r--mythtv/programs/mythbackend/mainserver.cpp26
-rw-r--r--mythtv/programs/mythtranscode/cutter.cpp10
-rw-r--r--mythtv/programs/mythtranscode/cutter.h2
-rw-r--r--mythtv/programs/mythtranscode/main.cpp9
-rw-r--r--mythtv/programs/mythtranscode/mpeg2fix.cpp11
-rw-r--r--[-rwxr-xr-x]mythtv/programs/mythtranscode/transcode.cpp4
33 files changed, 881 insertions, 379 deletions
diff --git a/mythtv/bindings/perl/MythTV.pm b/mythtv/bindings/perl/MythTV.pm
index 6063d67..82ce26d 100644
--- a/mythtv/bindings/perl/MythTV.pm
+++ b/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
diff --git a/mythtv/bindings/php/MythBackend.php b/mythtv/bindings/php/MythBackend.php
index 8c9df46..c5a116e 100644
--- a/mythtv/bindings/php/MythBackend.php
+++ b/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 = '[]:[]';
diff --git a/mythtv/bindings/python/MythTV/static.py b/mythtv/bindings/python/MythTV/static.py
index 98c0448..662a673 100644
--- a/mythtv/bindings/python/MythTV/static.py
+++ b/mythtv/bindings/python/MythTV/static.py
@@ -8,8 +8,8 @@ OWN_VERSION = (0,27,-1,0)
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
diff --git a/mythtv/libs/libmythbase/mythversion.h b/mythtv/libs/libmythbase/mythversion.h
index ce18790..84dd460 100644
--- a/mythtv/libs/libmythbase/mythversion.h
+++ b/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.
*
diff --git a/mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp b/mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp
index 463b384..804aaf1 100644
--- a/mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp
+++ b/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;
diff --git a/mythtv/libs/libmythtv/DVD/mythdvdplayer.h b/mythtv/libs/libmythtv/DVD/mythdvdplayer.h
index 7e4c6c4..1a7a868 100644
--- a/mythtv/libs/libmythtv/DVD/mythdvdplayer.h
+++ b/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
diff --git a/mythtv/libs/libmythtv/avformatdecoder.cpp b/mythtv/libs/libmythtv/avformatdecoder.cpp
index 3b02595..5505a22 100644
--- a/mythtv/libs/libmythtv/avformatdecoder.cpp
+++ b/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
diff --git a/mythtv/libs/libmythtv/decoderbase.cpp b/mythtv/libs/libmythtv/decoderbase.cpp
index 0f5c2e6..b5daa57 100644
--- a/mythtv/libs/libmythtv/decoderbase.cpp
+++ b/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: */
diff --git a/mythtv/libs/libmythtv/decoderbase.h b/mythtv/libs/libmythtv/decoderbase.h
index 7fb5f43..4ac2c63 100644
--- a/mythtv/libs/libmythtv/decoderbase.h
+++ b/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;
diff --git a/mythtv/libs/libmythtv/deletemap.cpp b/mythtv/libs/libmythtv/deletemap.cpp
index 7bb0b66..5ef0dbd 100644
--- a/mythtv/libs/libmythtv/deletemap.cpp
+++ b/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,31 +122,34 @@ 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;
}
@@ -155,12 +157,11 @@ static QString createTimeString(uint64_t frame, uint64_t total,
* \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,23 +491,23 @@ 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);
@@ -514,10 +515,10 @@ void DeleteMap::Move(uint64_t frame, uint64_t to, uint64_t total)
{
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,9 +585,8 @@ 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)
@@ -594,7 +594,7 @@ uint64_t DeleteMap::GetNearestMark(
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);
}
diff --git a/mythtv/libs/libmythtv/deletemap.h b/mythtv/libs/libmythtv/deletemap.h
index 731fac8..b68628c 100644
--- a/mythtv/libs/libmythtv/deletemap.h
+++ b/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,6 +96,9 @@ 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;
@@ -104,7 +106,7 @@ class MTV_PUBLIC DeleteMap
QString m_seekText;
bool m_changed;
int m_seekamountpos;
- int m_seekamount;
+ float m_seekamount;
PlayerContext *m_ctx;
uint64_t m_cachedTotalForOSD;
diff --git a/mythtv/libs/libmythtv/mythcommflagplayer.cpp b/mythtv/libs/libmythtv/mythcommflagplayer.cpp
index 80af370..9a0fed4 100644
--- a/mythtv/libs/libmythtv/mythcommflagplayer.cpp
+++ b/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__);
diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
index 9f972fe..ca2a784 100644
--- a/mythtv/libs/libmythtv/mythplayer.cpp
+++ b/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,20 +3583,22 @@ 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;
@@ -3580,7 +3606,7 @@ 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 || 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,19 +3959,19 @@ 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);
@@ -3941,41 +3979,40 @@ bool MythPlayer::HandleProgramEditorActions(QStringList &actions,
#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,11 +4690,16 @@ 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)
{
@@ -4656,12 +4707,11 @@ void MythPlayer::calcSliderPos(osdInfo &info, bool paddedFields)
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;
}
diff --git a/mythtv/libs/libmythtv/mythplayer.h b/mythtv/libs/libmythtv/mythplayer.h
index 695ff18..5f3cb3c 100644
--- a/mythtv/libs/libmythtv/mythplayer.h
+++ b/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
diff --git a/mythtv/libs/libmythtv/nuppeldecoder.cpp b/mythtv/libs/libmythtv/nuppeldecoder.cpp
index efe21f7..a3da830 100644
--- a/mythtv/libs/libmythtv/nuppeldecoder.cpp
+++ b/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;
}
}
}
diff --git a/mythtv/libs/libmythtv/recorders/dtvrecorder.cpp b/mythtv/libs/libmythtv/recorders/dtvrecorder.cpp
index 66fb570..8299422 100644
--- a/mythtv/libs/libmythtv/recorders/dtvrecorder.cpp
+++ b/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;
@@ -676,7 +702,7 @@ bool DTVRecorder::FindOtherKeyframes(const TSPacket *tspacket)
"generating initial key-frame");
_frames_seen_count++;
- _frames_written_count++;
+ UpdateFramesWritten();
_last_keyframe_seen = _frames_seen_count;
HandleKeyframe(_frames_written_count);
@@ -716,6 +742,8 @@ void DTVRecorder::HandleKeyframe(uint64_t frameNum, int64_t extra)
{
positionMapDelta[frameNum] = startpos;
positionMap[frameNum] = startpos;
+ durationMap[frameNum] = _total_duration / 1000;
+ durationMapDelta[frameNum] = _total_duration / 1000;
}
}
positionMapLock.unlock();
@@ -860,7 +888,7 @@ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket)
{
_frames_seen_count++;
if (!_wait_for_keyframe_option || _first_keyframe >= 0)
- _frames_written_count++;
+ UpdateFramesWritten();
}
if ((aspectRatio > 0) && (aspectRatio != m_videoAspect))
@@ -907,6 +935,8 @@ void DTVRecorder::HandleH264Keyframe(void)
{
positionMapDelta[frameNum] = m_h264_parser.keyframeAUstreamOffset();
positionMap[frameNum] = m_h264_parser.keyframeAUstreamOffset();
+ durationMap[frameNum] = _total_duration / 1000;
+ durationMapDelta[frameNum] = _total_duration / 1000;
}
positionMapLock.unlock();
@@ -1026,7 +1056,7 @@ void DTVRecorder::FindPSKeyFrames(const uint8_t *buffer, uint len)
{
_frames_seen_count++;
if (!_wait_for_keyframe_option || _first_keyframe >= 0)
- _frames_written_count++;
+ UpdateFramesWritten();
}
if (hasKeyFrame)
diff --git a/mythtv/libs/libmythtv/recorders/dtvrecorder.h b/mythtv/libs/libmythtv/recorders/dtvrecorder.h
index 853cfca..6617366 100644
--- a/mythtv/libs/libmythtv/recorders/dtvrecorder.h
+++ b/mythtv/libs/libmythtv/recorders/dtvrecorder.h
@@ -95,6 +95,7 @@ class DTVRecorder :
void HandleKeyframe(uint64_t frameNum, int64_t extra = 0);
void HandleTimestamps(int stream_id, int64_t pts, int64_t dts);
+ void UpdateFramesWritten(void);
void BufferedWrite(const TSPacket &tspacket);
@@ -182,6 +183,9 @@ class DTVRecorder :
mutable QAtomicInt _continuity_error_count;
unsigned long long _frames_seen_count;
unsigned long long _frames_written_count;
+ double _frame_interval; // usec
+ double _frame_duration; // usec
+ double _total_duration; // usec
// constants
/// If the number of regular frames detected since the last
diff --git a/mythtv/libs/libmythtv/recorders/recorderbase.cpp b/mythtv/libs/libmythtv/recorders/recorderbase.cpp
index 17dda69..3bf94f1 100644
--- a/mythtv/libs/libmythtv/recorders/recorderbase.cpp
+++ b/mythtv/libs/libmythtv/recorders/recorderbase.cpp
@@ -437,6 +437,28 @@ bool RecorderBase::GetKeyframePositions(
return true;
}
+bool RecorderBase::GetKeyframeDurations(
+ int64_t start, int64_t end, frm_pos_map_t &map) const
+{
+ map.clear();
+
+ QMutexLocker locker(&positionMapLock);
+ if (durationMap.empty())
+ return true;
+
+ frm_pos_map_t::const_iterator it = durationMap.lowerBound(start);
+ end = (end < 0) ? INT64_MAX : end;
+ for (; (it != durationMap.end()) &&
+ (it.key() <= (uint64_t)end); ++it)
+ map[it.key()] = *it;
+
+ LOG(VB_GENERAL, LOG_INFO, LOC +
+ QString("GetKeyframeDurations(%1,%2,#%3) out of %4")
+ .arg(start).arg(end).arg(map.size()).arg(durationMap.size()));
+
+ return true;
+}
+
/** \fn RecorderBase::SavePositionMap(bool)
* \brief This saves the postition map delta to the database if force
* is true or there are 30 frames in the map or there are five
@@ -456,6 +478,9 @@ void RecorderBase::SavePositionMap(bool force)
(delta_size >= 1) && (pm_elapsed >= 1500);
// save every 10 seconds later on
needToSave |= (delta_size >= 1) && (pm_elapsed >= 10000);
+ // Assume that durationMapDelta is the same size as
+ // positionMapDelta and implicitly use the same logic about when
+ // to same durationMapDelta.
if (curRecording && needToSave)
{
@@ -467,9 +492,13 @@ void RecorderBase::SavePositionMap(bool force)
// which is populating the delta map
frm_pos_map_t deltaCopy(positionMapDelta);
positionMapDelta.clear();
+ frm_pos_map_t durationDeltaCopy(durationMapDelta);
+ durationMapDelta.clear();
positionMapLock.unlock();
curRecording->SavePositionMapDelta(deltaCopy, positionMapType);
+ curRecording->SavePositionMapDelta(durationDeltaCopy,
+ MARK_DURATION_MS);
}
else
{
diff --git a/mythtv/libs/libmythtv/recorders/recorderbase.h b/mythtv/libs/libmythtv/recorders/recorderbase.h
index 0ee3855..972eced 100644
--- a/mythtv/libs/libmythtv/recorders/recorderbase.h
+++ b/mythtv/libs/libmythtv/recorders/recorderbase.h
@@ -173,6 +173,8 @@ class MTV_PUBLIC RecorderBase : public QRunnable
int64_t GetKeyframePosition(uint64_t desired) const;
bool GetKeyframePositions(
int64_t start, int64_t end, frm_pos_map_t&) const;
+ bool GetKeyframeDurations(
+ int64_t start, int64_t end, frm_pos_map_t&) const;
virtual void StopRecording(void);
virtual bool IsRecording(void);
@@ -301,6 +303,8 @@ class MTV_PUBLIC RecorderBase : public QRunnable
mutable QMutex positionMapLock;
frm_pos_map_t positionMap;
frm_pos_map_t positionMapDelta;
+ frm_pos_map_t durationMap;
+ frm_pos_map_t durationMapDelta;
MythTimer positionMapTimer;
// Statistics
diff --git a/mythtv/libs/libmythtv/remoteencoder.cpp b/mythtv/libs/libmythtv/remoteencoder.cpp
index edcf90e..ed009fc 100644
--- a/mythtv/libs/libmythtv/remoteencoder.cpp
+++ b/mythtv/libs/libmythtv/remoteencoder.cpp
@@ -264,8 +264,8 @@ int64_t RemoteEncoder::GetKeyframePosition(uint64_t desired)
return -1;
}
-void RemoteEncoder::FillPositionMap(long long start, long long end,
- QMap<long long, long long> &positionMap)
+void RemoteEncoder::FillPositionMap(int64_t start, int64_t end,
+ frm_pos_map_t &positionMap)
{
QStringList strlist( QString("QUERY_RECORDER %1").arg(recordernum));
strlist << "FILL_POSITION_MAP";
@@ -279,11 +279,11 @@ void RemoteEncoder::FillPositionMap(long long start, long long end,
for (; it != strlist.end(); ++it)
{
bool ok;
- long long index = (*it).toLongLong(&ok);
+ uint64_t index = (*it).toLongLong(&ok);
if (++it == strlist.end() || !ok)
break;
- long long pos = (*it).toLongLong(&ok);
+ uint64_t pos = (*it).toLongLong(&ok);
if (!ok)
break;
@@ -291,6 +291,33 @@ void RemoteEncoder::FillPositionMap(long long start, long long end,
}
}
+void RemoteEncoder::FillDurationMap(int64_t start, int64_t end,
+ frm_pos_map_t &durationMap)
+{
+ QStringList strlist( QString("QUERY_RECORDER %1").arg(recordernum));
+ strlist << "FILL_DURATION_MAP";
+ strlist << QString::number(start);
+ strlist << QString::number(end);
+
+ if (!SendReceiveStringList(strlist))
+ return;
+
+ QStringList::const_iterator it = strlist.begin();
+ for (; it != strlist.end(); ++it)
+ {
+ bool ok;
+ uint64_t index = (*it).toLongLong(&ok);
+ if (++it == strlist.end() || !ok)
+ break;
+
+ uint64_t pos = (*it).toLongLong(&ok);
+ if (!ok)
+ break;
+
+ durationMap[index] = pos;
+ }
+}
+
void RemoteEncoder::CancelNextRecording(bool cancel)
{
QStringList strlist( QString("QUERY_RECORDER %1").arg(recordernum));
diff --git a/mythtv/libs/libmythtv/remoteencoder.h b/mythtv/libs/libmythtv/remoteencoder.h
index 231a94f..4100ba8 100644
--- a/mythtv/libs/libmythtv/remoteencoder.h
+++ b/mythtv/libs/libmythtv/remoteencoder.h
@@ -11,6 +11,7 @@
#include "mythtvexp.h"
#include "videoouttypes.h"
#include "tv.h"
+#include "programtypes.h"
class QStringList;
class ProgramInfo;
@@ -36,8 +37,10 @@ class MTV_PUBLIC RemoteEncoder
long long GetFreeDiskSpace();
long long GetMaxBitrate();
int64_t GetKeyframePosition(uint64_t desired);
- void FillPositionMap(long long start, long long end,
- QMap<long long, long long> &positionMap);
+ void FillPositionMap(int64_t start, int64_t end,
+ frm_pos_map_t &positionMap);
+ void FillDurationMap(int64_t start, int64_t end,
+ frm_pos_map_t &durationMap);
void StopPlaying(void);
void SpawnLiveTV(QString chainid, bool pip, QString startchan);
void StopLiveTV(void);
diff --git a/mythtv/libs/libmythtv/tv_play.cpp b/mythtv/libs/libmythtv/tv_play.cpp
index 47a2cef..5d5a145 100644
--- a/mythtv/libs/libmythtv/tv_play.cpp
+++ b/mythtv/libs/libmythtv/tv_play.cpp
@@ -4214,14 +4214,15 @@ bool TV::ActiveHandleAction(PlayerContext *ctx,
{
ctx->LockDeletePlayer(__FILE__, __LINE__);
uint64_t bookmark = ctx->player->GetBookmark();
- float rate = ctx->player->GetFrameRate();
- float seekloc = ctx->player->TranslatePositionAbsToRel(bookmark) / rate;
ctx->UnlockDeletePlayer(__FILE__, __LINE__);
- if (bookmark > rate)
- DoSeek(ctx, seekloc, tr("Jump to Bookmark"),
- /*timeIsOffset*/false,
- /*honorCutlist*/true);
+ if (bookmark)
+ {
+ DoPlayerSeekToFrame(ctx, bookmark);
+ ctx->LockDeletePlayer(__FILE__, __LINE__);
+ UpdateOSDSeekMessage(ctx, tr("Jump to Bookmark"), kOSDTimeout_Med);
+ ctx->UnlockDeletePlayer(__FILE__, __LINE__);
+ }
}
else if (has_action(ACTION_JUMPSTART,actions))
{
@@ -5046,7 +5047,7 @@ void TV::ProcessNetworkControlCommand(PlayerContext *ctx,
if (ctx->player)
{
fplay = ctx->player->GetFramesPlayed();
- rate = ctx->player->GetFrameRate();
+ rate = ctx->player->GetFrameRate(); // for display only
}
ctx->UnlockDeletePlayer(__FILE__, __LINE__);
@@ -6130,6 +6131,37 @@ bool TV::DoPlayerSeek(PlayerContext *ctx, float time)
return res;
}
+bool TV::DoPlayerSeekToFrame(PlayerContext *ctx, uint64_t target)
+{
+ if (!ctx || !ctx->buffer)
+ return false;
+
+ LOG(VB_PLAYBACK, LOG_INFO, LOC +
+ QString("DoPlayerSeekToFrame %1").arg(target));
+
+ ctx->LockDeletePlayer(__FILE__, __LINE__);
+ if (!ctx->player)
+ {
+ ctx->UnlockDeletePlayer(__FILE__, __LINE__);
+ return false;
+ }
+
+ if (!ctx->buffer->IsSeekingAllowed())
+ {
+ ctx->UnlockDeletePlayer(__FILE__, __LINE__);
+ return false;
+ }
+
+ if (ctx == GetPlayer(ctx, 0))
+ PauseAudioUntilBuffered(ctx);
+
+ bool res = ctx->player->JumpToFrame(target);
+
+ ctx->UnlockDeletePlayer(__FILE__, __LINE__);
+
+ return res;
+}
+
bool TV::SeekHandleAction(PlayerContext *actx, const QStringList &actions,
const bool isDVD)
{
@@ -6162,18 +6194,24 @@ bool TV::SeekHandleAction(PlayerContext *actx, const QStringList &actions,
{
if (!isDVD)
{
- float rate = 30.0f;
- actx->LockDeletePlayer(__FILE__, __LINE__);
- if (actx->player)
- rate = actx->player->GetFrameRate();
- actx->UnlockDeletePlayer(__FILE__, __LINE__);
- float time = (flags & kAbsolute) ? direction :
- direction * (1.001 / rate);
QString message = (flags & kRewind) ? tr("Rewind") :
tr("Forward");
- DoSeek(actx, time, message,
- /*timeIsOffset*/true,
- /*honorCutlist*/!(flags & kIgnoreCutlist));
+ actx->LockDeletePlayer(__FILE__, __LINE__);
+ uint64_t frameAbs = actx->player->GetFramesPlayed();
+ uint64_t frameRel =
+ actx->player->TranslatePositionAbsToRel(frameAbs);
+ uint64_t targetRel = frameRel + direction;
+ if (frameRel == 0 && direction < 0)
+ targetRel = 0;
+ uint64_t maxAbs = actx->player->GetCurrentFrameCount();
+ uint64_t maxRel = actx->player->TranslatePositionAbsToRel(maxAbs);
+ if (targetRel > maxRel)
+ targetRel = maxRel;
+ uint64_t targetAbs =
+ actx->player->TranslatePositionRelToAbs(targetRel);
+ actx->UnlockDeletePlayer(__FILE__, __LINE__);
+ DoPlayerSeekToFrame(actx, targetAbs);
+ UpdateOSDSeekMessage(actx, message, kOSDTimeout_Med);
}
}
else if (flags & kSticky)
@@ -6213,30 +6251,27 @@ void TV::DoSeek(PlayerContext *ctx, float time, const QString &mesg,
ctx->LockDeletePlayer(__FILE__, __LINE__);
if (ctx->player->GetLimitKeyRepeat())
limitkeys = true;
- ctx->UnlockDeletePlayer(__FILE__, __LINE__);
if (!limitkeys || (keyRepeatTimer.elapsed() > (int)kKeyRepeatTimeout))
{
keyRepeatTimer.start();
NormalSpeed(ctx);
time += StopFFRew(ctx);
- float framerate = ctx->player->GetFrameRate();
uint64_t currentFrameAbs = ctx->player->GetFramesPlayed();
- uint64_t currentFrameRel = honorCutlist ?
- ctx->player->TranslatePositionAbsToRel(currentFrameAbs) :
- currentFrameAbs;
- int64_t desiredFrameRel = (timeIsOffset ? currentFrameRel : 0) +
- time * framerate + 0.5;
- if (desiredFrameRel < 0)
- desiredFrameRel = 0;
- uint64_t desiredFrameAbs = honorCutlist ?
- ctx->player->TranslatePositionRelToAbs(desiredFrameRel) :
- desiredFrameRel;
- time = ((int64_t)desiredFrameAbs - (int64_t)currentFrameAbs) /
- framerate;
- DoPlayerSeek(ctx, time);
+ if (timeIsOffset)
+ time +=
+ ctx->player->TranslatePositionFrameToMs(currentFrameAbs,
+ honorCutlist) / 1000.0;
+ if (time < 0)
+ time = 0;
+ uint64_t desiredFrameRel =
+ ctx->player->TranslatePositionMsToFrame(time * 1000, honorCutlist);
+ ctx->UnlockDeletePlayer(__FILE__, __LINE__);
+ DoPlayerSeekToFrame(ctx, desiredFrameRel);
UpdateOSDSeekMessage(ctx, mesg, kOSDTimeout_Med);
}
+ else
+ ctx->UnlockDeletePlayer(__FILE__, __LINE__);
}
void TV::DoSeekAbsolute(PlayerContext *ctx, long long seconds,
@@ -6281,11 +6316,11 @@ void TV::DoArbSeek(PlayerContext *ctx, ArbSeekWhence whence,
ctx->UnlockDeletePlayer(__FILE__, __LINE__);
return;
}
- time = (ctx->player->CalcMaxFFTime(LONG_MAX, false) /
- ctx->player->GetFrameRate()) - time;
+ uint64_t total_frames = ctx->player->GetCurrentFrameCount();
+ float dur = ctx->player->ComputeSecs(total_frames, honorCutlist);
+ time = max(0.0f, dur - time);
ctx->UnlockDeletePlayer(__FILE__, __LINE__);
- DoSeek(ctx, time, tr("Jump To"),
- /*timeIsOffset*/(whence != ARBSEEK_SET), honorCutlist);
+ DoSeek(ctx, time, tr("Jump To"), /*timeIsOffset*/false, honorCutlist);
}
else
DoSeekAbsolute(ctx, time, honorCutlist);
diff --git a/mythtv/libs/libmythtv/tv_play.h b/mythtv/libs/libmythtv/tv_play.h
index 9584593..6f408ef 100644
--- a/mythtv/libs/libmythtv/tv_play.h
+++ b/mythtv/libs/libmythtv/tv_play.h
@@ -426,6 +426,7 @@ class MTV_PUBLIC TV : public QObject
void DoSeek(PlayerContext*, float time, const QString &mesg,
bool timeIsOffset, bool honorCutlist);
bool DoPlayerSeek(PlayerContext*, float time);
+ bool DoPlayerSeekToFrame(PlayerContext *ctx, uint64_t target);
enum ArbSeekWhence {
ARBSEEK_SET = 0,
ARBSEEK_REWIND,
diff --git a/mythtv/libs/libmythtv/tv_rec.cpp b/mythtv/libs/libmythtv/tv_rec.cpp
index 73f6acf..6fc5c7f 100644
--- a/mythtv/libs/libmythtv/tv_rec.cpp
+++ b/mythtv/libs/libmythtv/tv_rec.cpp
@@ -2557,6 +2557,17 @@ bool TVRec::GetKeyframePositions(
return false;
}
+bool TVRec::GetKeyframeDurations(
+ int64_t start, int64_t end, frm_pos_map_t &map) const
+{
+ QMutexLocker lock(&stateChangeLock);
+
+ if (recorder)
+ return recorder->GetKeyframeDurations(start, end, map);
+
+ return false;
+}
+
/** \fn TVRec::GetMaxBitrate(void) const
* \brief Returns the maximum bits per second this recorder can produce.
*
diff --git a/mythtv/libs/libmythtv/tv_rec.h b/mythtv/libs/libmythtv/tv_rec.h
index 77954af..cea48a6 100644
--- a/mythtv/libs/libmythtv/tv_rec.h
+++ b/mythtv/libs/libmythtv/tv_rec.h
@@ -182,6 +182,7 @@ class MTV_PUBLIC TVRec : public SignalMonitorListener, public QRunnable
long long GetMaxBitrate(void) const;
int64_t GetKeyframePosition(uint64_t desired) const;
bool GetKeyframePositions(int64_t start, int64_t end, frm_pos_map_t&) const;
+ bool GetKeyframeDurations(int64_t start, int64_t end, frm_pos_map_t&) const;
void SpawnLiveTV(LiveTVChain *newchain, bool pip, QString startchan);
QString GetChainID(void);
void StopLiveTV(void);
diff --git a/mythtv/programs/mythbackend/encoderlink.cpp b/mythtv/programs/mythbackend/encoderlink.cpp
index 479235e..c00ba77 100644
--- a/mythtv/programs/mythbackend/encoderlink.cpp
+++ b/mythtv/programs/mythbackend/encoderlink.cpp
@@ -572,6 +572,19 @@ bool EncoderLink::GetKeyframePositions(
return tv->GetKeyframePositions(start, end, map);
}
+bool EncoderLink::GetKeyframeDurations(
+ int64_t start, int64_t end, frm_pos_map_t &map)
+{
+ if (!local)
+ {
+ LOG(VB_GENERAL, LOG_ERR,
+ "Should be local only query: GetKeyframeDurations");
+ return false;
+ }
+
+ return tv->GetKeyframeDurations(start, end, map);
+}
+
/** \fn EncoderLink::FrontendReady()
* \brief Tells TVRec that the frontend is ready for data.
* <b>This only works on local recorders.</b>
diff --git a/mythtv/programs/mythbackend/encoderlink.h b/mythtv/programs/mythbackend/encoderlink.h
index 09eaae1..8de94d9 100644
--- a/mythtv/programs/mythbackend/encoderlink.h
+++ b/mythtv/programs/mythbackend/encoderlink.h
@@ -102,6 +102,7 @@ class EncoderLink
long long GetFilePosition(void);
int64_t GetKeyframePosition(uint64_t desired);
bool GetKeyframePositions(int64_t start, int64_t end, frm_pos_map_t&);
+ bool GetKeyframeDurations(int64_t start, int64_t end, frm_pos_map_t&);
void SpawnLiveTV(LiveTVChain *chain, bool pip, QString startchan);
QString GetChainID(void);
void StopLiveTV(void);
diff --git a/mythtv/programs/mythbackend/mainserver.cpp b/mythtv/programs/mythbackend/mainserver.cpp
index 9fe988c..f38ba4a 100644
--- a/mythtv/programs/mythbackend/mainserver.cpp
+++ b/mythtv/programs/mythbackend/mainserver.cpp
@@ -3865,8 +3865,8 @@ void MainServer::HandleRecorderQuery(QStringList &slist, QStringList &commands,
}
else if (command == "FILL_POSITION_MAP")
{
- long long start = slist[2].toLongLong();
- long long end = slist[3].toLongLong();
+ int64_t start = slist[2].toLongLong();
+ int64_t end = slist[3].toLongLong();
frm_pos_map_t map;
if (!enc->GetKeyframePositions(start, end, map))
@@ -3885,6 +3885,28 @@ void MainServer::HandleRecorderQuery(QStringList &slist, QStringList &commands,
retlist << "OK";
}
}
+ else if (command == "FILL_DURATION_MAP")
+ {
+ int64_t start = slist[2].toLongLong();
+ int64_t end = slist[3].toLongLong();
+ frm_pos_map_t map;
+
+ if (!enc->GetKeyframeDurations(start, end, map))
+ {
+ retlist << "error";
+ }
+ else
+ {
+ frm_pos_map_t::const_iterator it = map.begin();
+ for (; it != map.end(); ++it)
+ {
+ retlist += QString::number(it.key());
+ retlist += QString::number(*it);
+ }
+ if (retlist.empty())
+ retlist << "OK";
+ }
+ }
else if (command == "GET_RECORDING")
{
ProgramInfo *pginfo = enc->GetRecording();
diff --git a/mythtv/programs/mythtranscode/cutter.cpp b/mythtv/programs/mythtranscode/cutter.cpp
index 281aa89..17840f5 100644
--- a/mythtv/programs/mythtranscode/cutter.cpp
+++ b/mythtv/programs/mythtranscode/cutter.cpp
@@ -2,7 +2,7 @@
#include "cutter.h"
-void Cutter::SetCutList(frm_dir_map_t &deleteMap)
+void Cutter::SetCutList(frm_dir_map_t &deleteMap, PlayerContext *ctx)
{
// Break each cut into two parts, the first for
// the player and the second for the transcode loop.
@@ -11,6 +11,7 @@ void Cutter::SetCutList(frm_dir_map_t &deleteMap)
int64_t start = 0;
int64_t leadinLength;
+ tracker.SetPlayerContext(ctx);
foreshortenedCutList.clear();
for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
@@ -82,7 +83,7 @@ void Cutter::Activate(float v2a, int64_t total)
totalFrames = total;
videoFramesToCut = 0;
audioFramesToCut = 0;
- tracker.TrackerReset(0, totalFrames);
+ tracker.TrackerReset(0);
}
void Cutter::NewFrame(int64_t currentFrame)
@@ -93,12 +94,11 @@ void Cutter::NewFrame(int64_t currentFrame)
{
uint64_t jumpTo = 0;
- if (tracker.TrackerWantsToJump(currentFrame, totalFrames,
- jumpTo))
+ if (tracker.TrackerWantsToJump(currentFrame, jumpTo))
{
// Reset the tracker and work out how much video and audio
// to drop
- tracker.TrackerReset(jumpTo, totalFrames);
+ tracker.TrackerReset(jumpTo);
videoFramesToCut = jumpTo - currentFrame;
audioFramesToCut += (int64_t)(videoFramesToCut *
audioFramesPerVideoFrame + 0.5);
diff --git a/mythtv/programs/mythtranscode/cutter.h b/mythtv/programs/mythtranscode/cutter.h
index 29f7dd5..1712172 100644
--- a/mythtv/programs/mythtranscode/cutter.h
+++ b/mythtv/programs/mythtranscode/cutter.h
@@ -15,7 +15,7 @@ class Cutter
Cutter() : active(false), videoFramesToCut(0), audioFramesToCut(0),
audioFramesPerVideoFrame(0.0) {};
- void SetCutList(frm_dir_map_t &deleteMap);
+ void SetCutList(frm_dir_map_t &deleteMap, PlayerContext *ctx);
frm_dir_map_t AdjustedCutList() const;
void Activate(float v2a, int64_t total);
void NewFrame(int64_t currentFrame);
diff --git a/mythtv/programs/mythtranscode/main.cpp b/mythtv/programs/mythtranscode/main.cpp
index 7109f69..19024e6 100644
--- a/mythtv/programs/mythtranscode/main.cpp
+++ b/mythtv/programs/mythtranscode/main.cpp
@@ -43,9 +43,11 @@ static void UpdatePositionMap(frm_pos_map_t &posMap, QString mapfile,
pginfo->ClearPositionMap(MARK_KEYFRAME);
pginfo->ClearPositionMap(MARK_GOP_START);
pginfo->SavePositionMap(posMap, MARK_GOP_BYFRAME);
+ pginfo->SavePositionMap(posMap, MARK_DURATION_MS);
}
else if (!mapfile.isEmpty())
{
+ MarkTypes keyType = MARK_GOP_BYFRAME;
FILE *mapfh = fopen(mapfile.toLocal8Bit().constData(), "w");
if (!mapfh)
{
@@ -54,10 +56,11 @@ static void UpdatePositionMap(frm_pos_map_t &posMap, QString mapfile,
return;
}
frm_pos_map_t::const_iterator it;
- fprintf (mapfh, "Type: %d\n", MARK_GOP_BYFRAME);
+ fprintf (mapfh, "Type: %d\n", keyType);
for (it = posMap.begin(); it != posMap.end(); ++it)
- fprintf(mapfh, "%lld %lld\n",
- (unsigned long long)it.key(), (unsigned long long)*it);
+ if (it.key() == keyType)
+ fprintf(mapfh, "%lld %lld\n",
+ it.key(), *it);
fclose(mapfh);
}
}
diff --git a/mythtv/programs/mythtranscode/mpeg2fix.cpp b/mythtv/programs/mythtranscode/mpeg2fix.cpp
index 58cbb8f..a659f7f 100644
--- a/mythtv/programs/mythtranscode/mpeg2fix.cpp
+++ b/mythtv/programs/mythtranscode/mpeg2fix.cpp
@@ -2660,12 +2660,23 @@ int MPEG2fixup::BuildKeyframeIndex(QString &file,
av_init_packet(&pkt);
+ uint64_t totalDuration = 0;
while (av_read_frame(inputFC, &pkt) >= 0)
{
if (pkt.stream_index == vid_id)
{
if (pkt.flags & AV_PKT_FLAG_KEY)
posMap[count] = pkt.pos;
+
+ // XXX totalDuration untested. Results should be the same
+ // as from mythcommflag --rebuild.
+
+ // totalDuration calculation based on
+ // AvFormatDecoder::PreProcessVideoPacket()
+ totalDuration +=
+ av_q2d(inputFC->streams[pkt.stream_index]->time_base) *
+ pkt.duration * 1000000; // usec
+ posMap.insertMulti(count, totalDuration);
count++;
}
av_free_packet(&pkt);
diff --git a/mythtv/programs/mythtranscode/transcode.cpp b/mythtv/programs/mythtranscode/transcode.cpp
index 5522042..7563971 100755..100644
--- a/mythtv/programs/mythtranscode/transcode.cpp
+++ b/mythtv/programs/mythtranscode/transcode.cpp
@@ -799,8 +799,7 @@ int Transcode::TranscodeFile(const QString &inputname,
// through a cut, and then use the cutter to
// discard the rest
cutter = new Cutter();
- cutter->SetCutList(deleteMap);
-
+ cutter->SetCutList(deleteMap, ctx);
GetPlayer()->SetCutList(cutter->AdjustedCutList());
}
else
@@ -1473,6 +1472,7 @@ int Transcode::TranscodeFile(const QString &inputname,
m_proginfo->ClearPositionMap(MARK_KEYFRAME);
m_proginfo->ClearPositionMap(MARK_GOP_START);
m_proginfo->ClearPositionMap(MARK_GOP_BYFRAME);
+ m_proginfo->ClearPositionMap(MARK_DURATION_MS);
}
if (nvr)