Improve video frame position checks.

This commit is contained in:
John Preston 2019-03-06 17:00:03 +04:00
parent 0f4ccce0e1
commit 67b9fe846b
3 changed files with 118 additions and 57 deletions

View File

@ -292,9 +292,11 @@ crl::time PacketPosition(const Packet &packet, AVRational timeBase) {
crl::time FramePosition(const Stream &stream) { crl::time FramePosition(const Stream &stream) {
const auto pts = !stream.frame const auto pts = !stream.frame
? AV_NOPTS_VALUE ? AV_NOPTS_VALUE
: (stream.frame->pts == AV_NOPTS_VALUE) : (stream.frame->best_effort_timestamp != AV_NOPTS_VALUE)
? stream.frame->pkt_dts ? stream.frame->best_effort_timestamp
: stream.frame->pts; : (stream.frame->pts != AV_NOPTS_VALUE)
? stream.frame->pts
: stream.frame->pkt_dts;
return PtsToTime(pts, stream.timeBase); return PtsToTime(pts, stream.timeBase);
} }

View File

@ -15,6 +15,7 @@ namespace Streaming {
namespace { namespace {
constexpr auto kDisplaySkipped = crl::time(-1); constexpr auto kDisplaySkipped = crl::time(-1);
constexpr auto kFinishedPosition = std::numeric_limits<crl::time>::max();
static_assert(kDisplaySkipped != kTimeUnknown); static_assert(kDisplaySkipped != kTimeUnknown);
} // namespace } // namespace
@ -53,12 +54,17 @@ private:
Looped, Looped,
Finished, Finished,
}; };
using ReadEnoughState = base::optional_variant<
FrameResult,
Shared::PrepareNextCheck>;
[[nodiscard]] bool interrupted() const; [[nodiscard]] bool interrupted() const;
[[nodiscard]] bool tryReadFirstFrame(Packet &&packet); [[nodiscard]] bool tryReadFirstFrame(Packet &&packet);
[[nodiscard]] bool fillStateFromFrame(); [[nodiscard]] bool fillStateFromFrame();
[[nodiscard]] bool processFirstFrame(); [[nodiscard]] bool processFirstFrame();
void queueReadFrames(crl::time delay = 0); void queueReadFrames(crl::time delay = 0);
void readFrames(); void readFrames();
[[nodiscard]] ReadEnoughState readEnoughFrames(crl::time trackTime);
[[nodiscard]] FrameResult readFrame(not_null<Frame*> frame); [[nodiscard]] FrameResult readFrame(not_null<Frame*> frame);
void presentFrameIfNeeded(); void presentFrameIfNeeded();
void callReady(); void callReady();
@ -162,31 +168,49 @@ void VideoTrackObject::readFrames() {
return; return;
} }
auto time = trackTime().trackTime; auto time = trackTime().trackTime;
const auto dropStaleFrames = _options.dropStaleFrames;
const auto state = _shared->prepareState(time, dropStaleFrames);
state.match([&](Shared::PrepareFrame frame) {
while (true) { while (true) {
const auto result = readFrame(frame); const auto result = readEnoughFrames(time);
if (result == FrameResult::Looped) { result.match([&](FrameResult result) {
time -= _stream.duration; if (result == FrameResult::Done
continue; || result == FrameResult::Finished) {
} else if (result != FrameResult::Done) {
break;
} else if (!dropStaleFrames
|| !VideoTrack::IsStale(frame, time)) {
LOG(("READ FRAMES, TRACK TIME: %1").arg(time));
presentFrameIfNeeded(); presentFrameIfNeeded();
break; } else if (result == FrameResult::Looped) {
} else { time -= _stream.duration;
LOG(("DROPPED FRAMES, TRACK TIME: %1").arg(time));
}
} }
}, [&](Shared::PrepareNextCheck delay) { }, [&](Shared::PrepareNextCheck delay) {
Expects(delay > 0); Expects(delay == kTimeUnknown || delay > 0);
if (delay != kTimeUnknown) {
queueReadFrames(delay); queueReadFrames(delay);
}, [&](std::nullopt_t) { }
presentFrameIfNeeded(); }, [](std::nullopt_t) {
});
if (result.has_value()) {
break;
}
}
}
auto VideoTrackObject::readEnoughFrames(crl::time trackTime)
-> ReadEnoughState {
const auto dropStaleFrames = _options.dropStaleFrames;
const auto state = _shared->prepareState(trackTime, dropStaleFrames);
return state.match([&](Shared::PrepareFrame frame) -> ReadEnoughState {
while (true) {
const auto result = readFrame(frame);
if (result != FrameResult::Done) {
return result;
} else if (!dropStaleFrames
|| !VideoTrack::IsStale(frame, trackTime)) {
LOG(("READ FRAMES, TRACK TIME: %1").arg(trackTime));
return std::nullopt;
} else {
LOG(("DROPPED FRAMES, TRACK TIME: %1").arg(trackTime));
}
}
}, [&](Shared::PrepareNextCheck delay) -> ReadEnoughState {
return delay;
}, [&](std::nullopt_t) -> ReadEnoughState {
return FrameResult::Done;
}); });
} }
@ -203,8 +227,8 @@ auto VideoTrackObject::readFrame(not_null<Frame*> frame) -> FrameResult {
loopAround(); loopAround();
return FrameResult::Looped; return FrameResult::Looped;
} else { } else {
interrupt(); frame->position = kFinishedPosition;
_nextFrameTimeUpdates = rpl::event_stream<crl::time>(); frame->displayed = kTimeUnknown;
return FrameResult::Finished; return FrameResult::Finished;
} }
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) { } else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
@ -234,7 +258,9 @@ void VideoTrackObject::presentFrameIfNeeded() {
return; return;
} }
const auto time = trackTime(); const auto time = trackTime();
const auto prepare = [&](not_null<Frame*> frame) { const auto rasterize = [&](not_null<Frame*> frame) {
Expects(frame->position != kFinishedPosition);
frame->request = _request; frame->request = _request;
frame->original = ConvertFrame( frame->original = ConvertFrame(
_stream, _stream,
@ -250,10 +276,17 @@ void VideoTrackObject::presentFrameIfNeeded() {
VideoTrack::PrepareFrameByRequest(frame); VideoTrack::PrepareFrameByRequest(frame);
Ensures(VideoTrack::IsPrepared(frame)); Ensures(VideoTrack::IsRasterized(frame));
}; };
const auto presented = _shared->presentFrame(time.trackTime, prepare); const auto presented = _shared->presentFrame(
if (presented.displayPosition != kTimeUnknown) { time.trackTime,
_options.dropStaleFrames,
rasterize);
if (presented.displayPosition == kFinishedPosition) {
interrupt();
_nextFrameTimeUpdates = rpl::event_stream<crl::time>();
return;
} else if (presented.displayPosition != kTimeUnknown) {
const auto trackLeft = presented.displayPosition - time.trackTime; const auto trackLeft = presented.displayPosition - time.trackTime;
// We don't use rpl::variable, because we want an event each time // We don't use rpl::variable, because we want an event each time
@ -263,8 +296,11 @@ void VideoTrackObject::presentFrameIfNeeded() {
LOG(("NOW: %1, FRAME POSITION: %2, TRACK TIME: %3, TRACK LEFT: %4, NEXT: %5").arg(time.worldTime).arg(presented.displayPosition).arg(time.trackTime).arg(trackLeft).arg(_nextFrameDisplayTime)); LOG(("NOW: %1, FRAME POSITION: %2, TRACK TIME: %3, TRACK LEFT: %4, NEXT: %5").arg(time.worldTime).arg(presented.displayPosition).arg(time.trackTime).arg(trackLeft).arg(_nextFrameDisplayTime));
_nextFrameTimeUpdates.fire_copy(_nextFrameDisplayTime); _nextFrameTimeUpdates.fire_copy(_nextFrameDisplayTime);
} }
if (presented.nextCheckDelay != kTimeUnknown) {
Assert(presented.nextCheckDelay >= 0);
queueReadFrames(presented.nextCheckDelay); queueReadFrames(presented.nextCheckDelay);
} }
}
void VideoTrackObject::pause(crl::time time) { void VideoTrackObject::pause(crl::time time) {
Expects(_syncTimePoint.valid()); Expects(_syncTimePoint.valid());
@ -367,15 +403,19 @@ bool VideoTrackObject::processFirstFrame() {
} }
crl::time VideoTrackObject::currentFramePosition() const { crl::time VideoTrackObject::currentFramePosition() const {
const auto position = _framePositionShift + std::min( const auto position = FramePosition(_stream);
FramePosition(_stream), LOG(("FRAME_POSITION: %1 (pts: %2, dts: %3, duration: %4)"
_stream.duration - 1); ).arg(position
if (_previousFramePosition != kTimeUnknown ).arg(PtsToTime(_stream.frame->pts, _stream.timeBase)
&& position <= _previousFramePosition) { ).arg(PtsToTime(_stream.frame->pkt_dts, _stream.timeBase)
).arg(PtsToTime(_stream.frame->pkt_duration, _stream.timeBase)));
if (position == kTimeUnknown || position == kFinishedPosition) {
return kTimeUnknown; return kTimeUnknown;
} }
_previousFramePosition = position; return _framePositionShift + std::clamp(
return position; position,
crl::time(0),
_stream.duration - 1);
} }
bool VideoTrackObject::fillStateFromFrame() { bool VideoTrackObject::fillStateFromFrame() {
@ -472,20 +512,28 @@ auto VideoTrack::Shared::prepareState(
const auto next = getFrame((index + 1) % kFramesCount); const auto next = getFrame((index + 1) % kFramesCount);
if (!IsDecoded(frame)) { if (!IsDecoded(frame)) {
return frame; return frame;
} else if (dropStaleFrames && IsStale(frame, trackTime)) { } else if (!IsDecoded(next)) {
return next;
} else if (next->position < frame->position) {
LOG(("INVALID ORDER, SWAPPING"));
std::swap(*frame, *next);
}
if (next->position == kFinishedPosition || !dropStaleFrames) {
return PrepareNextCheck(kTimeUnknown);
} else if (IsStale(frame, trackTime)) {
std::swap(*frame, *next); std::swap(*frame, *next);
next->displayed = kDisplaySkipped; next->displayed = kDisplaySkipped;
return IsDecoded(frame) ? next : frame; LOG(("DROPPED FRAMES, TRACK TIME: %1").arg(trackTime));
} else if (!IsDecoded(next)) {
return next; return next;
} else { } else {
return PrepareNextCheck(frame->position - trackTime + 1); return PrepareNextCheck(frame->position - trackTime + 1);
} }
}; };
const auto finishPrepare = [&](int index) { const auto finishPrepare = [&](int index) -> PrepareState {
const auto frame = getFrame(index);
// If player already awaits next frame - we ignore if it's stale. // If player already awaits next frame - we ignore if it's stale.
return IsDecoded(frame) ? std::nullopt : PrepareState(frame); dropStaleFrames = false;
const auto result = prepareNext(index);
return result.is<PrepareNextCheck>() ? PrepareState() : result;
}; };
switch (counter()) { switch (counter()) {
@ -501,17 +549,22 @@ auto VideoTrack::Shared::prepareState(
Unexpected("Counter value in VideoTrack::Shared::prepareState."); Unexpected("Counter value in VideoTrack::Shared::prepareState.");
} }
template <typename PrepareCallback> template <typename RasterizeCallback>
auto VideoTrack::Shared::presentFrame( auto VideoTrack::Shared::presentFrame(
crl::time trackTime, crl::time trackTime,
PrepareCallback &&prepare) bool dropStaleFrames,
RasterizeCallback &&rasterize)
-> PresentFrame { -> PresentFrame {
const auto present = [&](int counter, int index) -> PresentFrame { const auto present = [&](int counter, int index) -> PresentFrame {
const auto frame = getFrame(index); const auto frame = getFrame(index);
const auto position = frame->position; const auto position = frame->position;
prepare(frame); if (position == kFinishedPosition) {
if (!IsPrepared(frame)) { return { kFinishedPosition, kTimeUnknown };
return { kTimeUnknown, crl::time(0) }; }
rasterize(frame);
if (!IsRasterized(frame)) {
// Error happened during frame prepare.
return { kTimeUnknown, kTimeUnknown };
} }
// Release this frame to the main thread for rendering. // Release this frame to the main thread for rendering.
@ -522,13 +575,18 @@ auto VideoTrack::Shared::presentFrame(
}; };
const auto nextCheckDelay = [&](int index) -> PresentFrame { const auto nextCheckDelay = [&](int index) -> PresentFrame {
const auto frame = getFrame(index); const auto frame = getFrame(index);
const auto next = getFrame((index + 1) % kFramesCount); if (frame->position == kFinishedPosition) {
if (!IsDecoded(frame) return { kFinishedPosition, kTimeUnknown };
|| !IsDecoded(next)
|| IsStale(frame, trackTime)) {
return { kTimeUnknown, crl::time(0) };
} }
return { kTimeUnknown, (trackTime - frame->position + 1) }; const auto next = getFrame((index + 1) % kFramesCount);
if (!IsDecoded(frame) || !IsDecoded(next)) {
return { kTimeUnknown, crl::time(0) };
} else if (next->position == kFinishedPosition
|| !dropStaleFrames
|| IsStale(frame, trackTime)) {
return { kTimeUnknown, kTimeUnknown };
}
return { kTimeUnknown, (frame->position - trackTime + 1) };
}; };
LOG(("PRESENT COUNTER: %1").arg(counter())); LOG(("PRESENT COUNTER: %1").arg(counter()));
@ -681,7 +739,7 @@ bool VideoTrack::IsDecoded(not_null<Frame*> frame) {
&& (frame->displayed == kTimeUnknown); && (frame->displayed == kTimeUnknown);
} }
bool VideoTrack::IsPrepared(not_null<Frame*> frame) { bool VideoTrack::IsRasterized(not_null<Frame*> frame) {
return IsDecoded(frame) return IsDecoded(frame)
&& !frame->original.isNull(); && !frame->original.isNull();
} }

View File

@ -87,11 +87,12 @@ private:
crl::time trackTime, crl::time trackTime,
bool dropStaleFrames); bool dropStaleFrames);
// PrepareCallback(not_null<Frame*>). // RasterizeCallback(not_null<Frame*>).
template <typename PrepareCallback> template <typename RasterizeCallback>
[[nodiscard]] PresentFrame presentFrame( [[nodiscard]] PresentFrame presentFrame(
crl::time trackTime, crl::time trackTime,
PrepareCallback &&prepare); bool dropStaleFrames,
RasterizeCallback &&rasterize);
// Called from the main thread. // Called from the main thread.
// Returns the position of the displayed frame. // Returns the position of the displayed frame.
@ -114,7 +115,7 @@ private:
not_null<Frame*> frame, not_null<Frame*> frame,
bool useExistingPrepared = false); bool useExistingPrepared = false);
[[nodiscard]] static bool IsDecoded(not_null<Frame*> frame); [[nodiscard]] static bool IsDecoded(not_null<Frame*> frame);
[[nodiscard]] static bool IsPrepared(not_null<Frame*> frame); [[nodiscard]] static bool IsRasterized(not_null<Frame*> frame);
[[nodiscard]] static bool IsStale( [[nodiscard]] static bool IsStale(
not_null<Frame*> frame, not_null<Frame*> frame,
crl::time trackTime); crl::time trackTime);