From 49dbed5be0729b04a5f0fd0426a32fceb2dd7935 Mon Sep 17 00:00:00 2001 From: Jim Stichnoth Date: Mon, 31 Dec 2012 19:23:42 -0800 Subject: [PATCH] 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. --- mythtv/bindings/perl/MythTV.pm | 4 +- mythtv/bindings/php/MythBackend.php | 4 +- mythtv/bindings/python/MythTV/static.py | 6 +- mythtv/libs/libmythbase/mythversion.h | 10 +- mythtv/libs/libmythtv/DVD/mythdvdplayer.cpp | 11 - mythtv/libs/libmythtv/DVD/mythdvdplayer.h | 1 - mythtv/libs/libmythtv/avformatdecoder.cpp | 11 + mythtv/libs/libmythtv/decoderbase.cpp | 238 ++++++++++++++- mythtv/libs/libmythtv/decoderbase.h | 22 ++ mythtv/libs/libmythtv/deletemap.cpp | 243 +++++++--------- mythtv/libs/libmythtv/deletemap.h | 76 ++--- mythtv/libs/libmythtv/mythcommflagplayer.cpp | 1 + mythtv/libs/libmythtv/mythplayer.cpp | 273 +++++++++++------- mythtv/libs/libmythtv/mythplayer.h | 36 ++- mythtv/libs/libmythtv/nuppeldecoder.cpp | 7 + .../libs/libmythtv/recorders/dtvrecorder.cpp | 42 ++- mythtv/libs/libmythtv/recorders/dtvrecorder.h | 4 + .../libs/libmythtv/recorders/recorderbase.cpp | 29 ++ .../libs/libmythtv/recorders/recorderbase.h | 4 + mythtv/libs/libmythtv/remoteencoder.cpp | 35 ++- mythtv/libs/libmythtv/remoteencoder.h | 7 +- mythtv/libs/libmythtv/tv_play.cpp | 107 ++++--- mythtv/libs/libmythtv/tv_play.h | 1 + mythtv/libs/libmythtv/tv_rec.cpp | 11 + mythtv/libs/libmythtv/tv_rec.h | 1 + mythtv/programs/mythbackend/encoderlink.cpp | 13 + mythtv/programs/mythbackend/encoderlink.h | 1 + mythtv/programs/mythbackend/mainserver.cpp | 26 +- mythtv/programs/mythtranscode/cutter.cpp | 10 +- mythtv/programs/mythtranscode/cutter.h | 2 +- mythtv/programs/mythtranscode/main.cpp | 9 +- mythtv/programs/mythtranscode/mpeg2fix.cpp | 11 + mythtv/programs/mythtranscode/transcode.cpp | 4 +- 33 files changed, 881 insertions(+), 379 deletions(-) mode change 100755 => 100644 mythtv/programs/mythtranscode/transcode.cpp diff --git a/mythtv/bindings/perl/MythTV.pm b/mythtv/bindings/perl/MythTV.pm index 6063d670209..82ce26d056b 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 8c9df461d06..c5a116e917c 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 98c0448d598..662a673c5eb 100644 --- a/mythtv/bindings/python/MythTV/static.py +++ b/mythtv/bindings/python/MythTV/static.py @@ -8,8 +8,8 @@ SCHEMA_VERSION = 1310 NVSCHEMA_VERSION = 1007 MUSICSCHEMA_VERSION = 1018 -PROTO_VERSION = '76' -PROTO_TOKEN = 'FireWilde' +PROTO_VERSION = '77' +PROTO_TOKEN = 'WindMark' BACKEND_SEP = '[]:[]' INSTALL_PREFIX = '/usr/local' @@ -34,6 +34,8 @@ class MARKUP( object ): MARK_ASPECT_CUSTOM = 14 MARK_VIDEO_WIDTH = 30 MARK_VIDEO_HEIGHT = 31 + MARK_VIDEO_RATE = 32 + MARK_DURATION_MS = 33 class RECTYPE( object ): kNotRecording = 0 diff --git a/mythtv/libs/libmythbase/mythversion.h b/mythtv/libs/libmythbase/mythversion.h index ce18790cf28..84dd4602ff9 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 463b3849dc9..804aaf1a945 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 7e4c6c4ca86..1a7a868b891 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 3b02595f304..5505a228c6f 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 0f5c2e6dbb9..b5daa57d071 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 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::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 7fb5f43bf7b..4ac2c63834a 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 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 7bb0b668af3..5ef0dbde3d8 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(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 731fac88137..b68628cb4c0 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 80af370bd0d..9a0fed4f626 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 9f972fedd93..ca2a7847a0f 100644 --- a/mythtv/libs/libmythtv/mythplayer.cpp +++ b/mythtv/libs/libmythtv/mythplayer.cpp @@ -15,6 +15,7 @@ #include #include #include +#include // C++ headers #include @@ -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 &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 695ff18ef7d..5f3cb3c9b50 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 &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 efe21f7773c..a3da830f172 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 66fb570497d..82994227e31 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 853cfca8cef..661736653e0 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 17dda69eff1..3bf94f17fdd 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 0ee38559c33..972eced58c2 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 edcf90eac27..ed009fcd8ce 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 &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 231a94fd912..4100ba8d693 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 &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 47a2cef448e..5d5a145b129 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 9584593a8e1..6f408ef3268 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 73f6acf6b3f..6fc5c7f25f8 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 77954afa282..cea48a66a62 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 479235ec163..c00ba7769af 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. * This only works on local recorders. diff --git a/mythtv/programs/mythbackend/encoderlink.h b/mythtv/programs/mythbackend/encoderlink.h index 09eaae17811..8de94d9a9b6 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 9fe988c6de1..f38ba4ac81d 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 281aa8970ad..17840f5848b 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 29f7dd5c5d8..1712172b388 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 7109f69223e..19024e6b754 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 58cbb8fc52b..a659f7f62e7 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 old mode 100755 new mode 100644 index 55220429491..7563971cfe1 --- 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)