Fix multi player with same frame rates.

This commit is contained in:
John Preston 2019-06-30 15:19:57 +02:00
parent f6bfbbb805
commit 1da5d1c64f
5 changed files with 110 additions and 55 deletions

View File

@ -298,8 +298,6 @@ void SharedState::renderFrame(
void SharedState::init(QImage cover, const FrameRequest &request) {
Expects(!initialized());
_duration = crl::time(1000) * _framesCount / _frameRate;
_frames[0].request = request;
_frames[0].original = std::move(cover);
_frames[0].position = 0;
@ -307,13 +305,20 @@ void SharedState::init(QImage cover, const FrameRequest &request) {
// Usually main thread sets displayed time before _counter increment.
// But in this case we update _counter, so we set a fake displayed time.
_frames[0].displayed = kDisplaySkipped;
_counter.store(0, std::memory_order_release);
}
void SharedState::start(not_null<Player*> owner, crl::time now) {
void SharedState::start(
not_null<Player*> owner,
crl::time started,
crl::time delay,
int skippedFrames) {
_owner = owner;
_started = now;
_started = started;
_delay = delay;
_skippedFrames = skippedFrames;
_frames[0].position = currentFramePosition();
_counter.store(0, std::memory_order_release);
}
bool IsRendered(not_null<const Frame*> frame) {
@ -328,10 +333,14 @@ void SharedState::renderNextFrame(
renderFrame(frame->original, request, (++_frameIndex) % _framesCount);
PrepareFrameByRequest(frame);
frame->position = crl::time(1000) * _frameIndex / _frameRate;
frame->position = currentFramePosition();
frame->displayed = kTimeUnknown;
}
crl::time SharedState::currentFramePosition() const {
return crl::time(1000) * (_skippedFrames + _frameIndex) / _frameRate;
}
auto SharedState::renderNextFrame(const FrameRequest &request)
-> RenderResult {
const auto prerender = [&](int index) -> RenderResult {
@ -351,7 +360,7 @@ auto SharedState::renderNextFrame(const FrameRequest &request)
if (!IsRendered(frame)) {
renderNextFrame(frame, request);
}
frame->display = _started + _accumulatedDelayMs + frame->position;
frame->display = _started + _delay + frame->position;
// Release this frame to the main thread for rendering.
_counter.store(
@ -441,7 +450,16 @@ crl::time SharedState::nextFrameDisplayTime() const {
Unexpected("Counter value in VideoTrack::Shared::nextFrameDisplayTime.");
}
crl::time SharedState::markFrameDisplayed(crl::time now, crl::time delayed) {
void SharedState::addTimelineDelay(crl::time delayed) {
if (!delayed) {
return;
}
Assert(counter() % 2 == 1);
_delay += delayed;
}
crl::time SharedState::markFrameDisplayed(crl::time now) {
const auto mark = [&](int counter) {
const auto next = (counter + 1) % (2 * kFramesCount);
const auto index = next / 2;
@ -455,15 +473,14 @@ crl::time SharedState::markFrameDisplayed(crl::time now, crl::time delayed) {
return frame->position;
};
_accumulatedDelayMs += delayed;
switch (counter()) {
case 0: return kTimeUnknown;
case 0: Unexpected("Value 0 in SharedState::markFrameDisplayed.");
case 1: return mark(1);
case 2: return kTimeUnknown;
case 2: Unexpected("Value 2 in SharedState::markFrameDisplayed.");
case 3: return mark(3);
case 4: return kTimeUnknown;
case 4: Unexpected("Value 4 in SharedState::markFrameDisplayed.");
case 5: return mark(5);
case 6: return kTimeUnknown;
case 6: Unexpected("Value 6 in SharedState::markFrameDisplayed.");
case 7: return mark(7);
}
Unexpected("Counter value in Lottie::SharedState::markFrameDisplayed.");

View File

@ -57,14 +57,19 @@ public:
std::unique_ptr<Cache> cache,
const FrameRequest &request);
void start(not_null<Player*> owner, crl::time now);
void start(
not_null<Player*> owner,
crl::time now,
crl::time delay = 0,
int skippedFrames = 0);
[[nodiscard]] Information information() const;
[[nodiscard]] bool initialized() const;
[[nodiscard]] not_null<Frame*> frameForPaint();
[[nodiscard]] crl::time nextFrameDisplayTime() const;
crl::time markFrameDisplayed(crl::time now, crl::time delayed);
void addTimelineDelay(crl::time delayed);
crl::time markFrameDisplayed(crl::time now);
crl::time markFrameShown();
void renderFrame(QImage &image, const FrameRequest &request, int index);
@ -88,10 +93,13 @@ private:
[[nodiscard]] not_null<Frame*> getFrame(int index);
[[nodiscard]] not_null<const Frame*> getFrame(int index) const;
[[nodiscard]] int counter() const;
[[nodiscard]] crl::time currentFramePosition() const;
QByteArray _content;
std::unique_ptr<rlottie::Animation> _animation;
// crl::queue changes 0,2,4,6 to 1,3,5,7.
// main thread changes 1,3,5,7 to 2,4,6,0.
static constexpr auto kCounterUninitialized = -1;
std::atomic<int> _counter = kCounterUninitialized;
@ -100,12 +108,16 @@ private:
base::weak_ptr<Player> _owner;
crl::time _started = kTimeUnknown;
crl::time _duration = kTimeUnknown;
// (_counter % 2) == 1 main thread can write _delay.
// (_counter % 2) == 0 crl::queue can read _delay.
crl::time _delay = kTimeUnknown;
int _frameIndex = 0;
int _skippedFrames = 0;
int _framesCount = 0;
int _frameRate = 0;
QSize _size;
std::atomic<int> _accumulatedDelayMs = 0;
std::unique_ptr<Cache> _cache;

View File

@ -22,6 +22,10 @@ std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> renderer)
: _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) {
crl::on_main_update_requests(
) | rpl::start_with_next([=] {
checkStep();
}, _lifetime);
}
MultiPlayer::~MultiPlayer() {
@ -54,26 +58,22 @@ not_null<Animation*> MultiPlayer::append(
return _animations.back().get();
}
crl::time MultiPlayer::startAtRightTime(not_null<SharedState*> state) {
void MultiPlayer::startAtRightTime(not_null<SharedState*> state) {
Expects(!_active.empty());
Expects((_active.size() == 1) == (_started == kTimeUnknown));
const auto now = crl::now();
if (_active.size() == 1) {
_started = crl::now();
state->start(this, _started);
return _started;
_started = now;
}
const auto now = crl::now();
const auto rate = state->information().frameRate;
Assert(rate != 0);
const auto started = _started + _accumulatedDelay;
const auto started = _started + _delay;
const auto skipFrames = (now - started) * rate / 1000;
const auto startAt = started + (1000 * skipFrames / rate);
state->start(this, startAt);
return startAt;
state->start(this, _started, _delay, skipFrames);
}
void MultiPlayer::start(
@ -81,21 +81,32 @@ void MultiPlayer::start(
std::unique_ptr<SharedState> state) {
Expects(state != nullptr);
if (_nextFrameTime == kTimeUnknown) {
appendToActive(animation, std::move(state));
} else {
// We always try to mark as shown at the same time, so we start a new
// animation at the same time we mark all existing as shown.
_pendingToStart.emplace(animation, std::move(state));
}
}
void MultiPlayer::appendPendingToActive() {
for (auto &[animation, state] : base::take(_pendingToStart)) {
appendToActive(animation, std::move(state));
}
}
void MultiPlayer::appendToActive(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state) {
Expects(_nextFrameTime == kTimeUnknown);
_active.emplace(animation, state.get());
auto information = state->information();
startAtRightTime(state.get());
_renderer->append(std::move(state));
_updates.fire({});
crl::on_main_update_requests(
) | rpl::start_with_next([=] {
checkStep();
}, _lifetime);
_nextFrameTime = kTimeUnknown;
_timer.cancel();
checkStep();
}
void MultiPlayer::remove(not_null<Animation*> animation) {
@ -112,7 +123,7 @@ void MultiPlayer::remove(not_null<Animation*> animation) {
if (_active.empty()) {
_started = kTimeUnknown;
_accumulatedDelay = 0;
_delay = 0;
_nextFrameTime = kTimeUnknown;
_timer.cancel();
}
@ -127,7 +138,7 @@ rpl::producer<MultiUpdate> MultiPlayer::updates() const {
}
void MultiPlayer::checkStep() {
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
if (_active.empty() || _nextFrameTime == kFrameDisplayTimeAlreadyDone) {
return;
} else if (_nextFrameTime != kTimeUnknown) {
checkNextFrameRender();
@ -139,9 +150,6 @@ void MultiPlayer::checkStep() {
void MultiPlayer::checkNextFrameAvailability() {
Expects(_nextFrameTime == kTimeUnknown);
if (_active.empty()) {
return;
}
auto next = kTimeUnknown;
for (const auto &[animation, state] : _active) {
const auto time = state->nextFrameDisplayTime();
@ -181,10 +189,10 @@ void MultiPlayer::checkNextFrameRender() {
} else {
_timer.cancel();
const auto exact = std::exchange(
_nextFrameTime,
kFrameDisplayTimeAlreadyDone);
markFrameDisplayed(now, now - exact);
markFrameDisplayed(now);
addTimelineDelay(now - _nextFrameTime);
_nextFrameTime = kFrameDisplayTimeAlreadyDone;
_updates.fire({});
}
}
@ -198,7 +206,7 @@ void MultiPlayer::updateFrameRequest(
_renderer->updateFrameRequest(i->second, request);
}
void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) {
void MultiPlayer::markFrameDisplayed(crl::time now) {
Expects(!_active.empty());
auto displayed = 0;
@ -210,17 +218,27 @@ void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) {
continue;
} else if (now >= time) {
++displayed;
state->markFrameDisplayed(now, delayed);
state->markFrameDisplayed(now);
} else {
++waiting;
}
}
PROFILE_LOG(("PLAYER FRAME DISPLAYED AT: %1, DELAYED: %2, (MARKED %3, WAITING %4)").arg(now).arg(delayed).arg(displayed).arg(waiting));
PROFILE_LOG(("PLAYER FRAME DISPLAYED AT: %1 (MARKED %2, WAITING %3)").arg(now).arg(displayed).arg(waiting));
}
void MultiPlayer::addTimelineDelay(crl::time delayed) {
Expects(!_active.empty());
for (const auto &[animation, state] : _active) {
state->addTimelineDelay(delayed);
}
_delay += delayed;
}
void MultiPlayer::markFrameShown() {
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
_nextFrameTime = kTimeUnknown;
appendPendingToActive();
}
auto count = 0;
for (const auto &[animation, state] : _active) {

View File

@ -56,8 +56,13 @@ public:
void remove(not_null<Animation*> animation);
private:
crl::time startAtRightTime(not_null<SharedState*> state);
void markFrameDisplayed(crl::time now, crl::time delayed);
void appendToActive(
not_null<Animation*> animation,
std::unique_ptr<SharedState> state);
void startAtRightTime(not_null<SharedState*> state);
void appendPendingToActive();
void markFrameDisplayed(crl::time now);
void addTimelineDelay(crl::time delayed);
void checkNextFrameAvailability();
void checkNextFrameRender();
@ -65,9 +70,12 @@ private:
const std::shared_ptr<FrameRenderer> _renderer;
std::vector<std::unique_ptr<Animation>> _animations;
base::flat_map<not_null<Animation*>, not_null<SharedState*>> _active;
base::flat_map<
not_null<Animation*>,
std::unique_ptr<SharedState>> _pendingToStart;
//base::flat_map<not_null<Animation*>, not_null<SharedState*>> _paused;
crl::time _started = kTimeUnknown;
crl::time _accumulatedDelay = 0;
crl::time _delay = 0;
crl::time _nextFrameTime = kTimeUnknown;
rpl::event_stream<MultiUpdate> _updates;
rpl::lifetime _lifetime;

View File

@ -102,10 +102,10 @@ void SinglePlayer::checkNextFrameRender() {
} else {
_timer.cancel();
const auto exact = std::exchange(
_nextFrameTime,
kFrameDisplayTimeAlreadyDone);
const auto position = _state->markFrameDisplayed(now, now - exact);
const auto position = _state->markFrameDisplayed(now);
_state->addTimelineDelay(now - _nextFrameTime);
_nextFrameTime = kFrameDisplayTimeAlreadyDone;
_updates.fire({ DisplayFrameRequest{ position } });
}
}