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

View File

@ -57,14 +57,19 @@ public:
std::unique_ptr<Cache> cache, std::unique_ptr<Cache> cache,
const FrameRequest &request); 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]] Information information() const;
[[nodiscard]] bool initialized() const; [[nodiscard]] bool initialized() const;
[[nodiscard]] not_null<Frame*> frameForPaint(); [[nodiscard]] not_null<Frame*> frameForPaint();
[[nodiscard]] crl::time nextFrameDisplayTime() const; [[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(); crl::time markFrameShown();
void renderFrame(QImage &image, const FrameRequest &request, int index); void renderFrame(QImage &image, const FrameRequest &request, int index);
@ -88,10 +93,13 @@ private:
[[nodiscard]] not_null<Frame*> getFrame(int index); [[nodiscard]] not_null<Frame*> getFrame(int index);
[[nodiscard]] not_null<const Frame*> getFrame(int index) const; [[nodiscard]] not_null<const Frame*> getFrame(int index) const;
[[nodiscard]] int counter() const; [[nodiscard]] int counter() const;
[[nodiscard]] crl::time currentFramePosition() const;
QByteArray _content; QByteArray _content;
std::unique_ptr<rlottie::Animation> _animation; 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; static constexpr auto kCounterUninitialized = -1;
std::atomic<int> _counter = kCounterUninitialized; std::atomic<int> _counter = kCounterUninitialized;
@ -100,12 +108,16 @@ private:
base::weak_ptr<Player> _owner; base::weak_ptr<Player> _owner;
crl::time _started = kTimeUnknown; 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 _frameIndex = 0;
int _skippedFrames = 0;
int _framesCount = 0; int _framesCount = 0;
int _frameRate = 0; int _frameRate = 0;
QSize _size; QSize _size;
std::atomic<int> _accumulatedDelayMs = 0;
std::unique_ptr<Cache> _cache; std::unique_ptr<Cache> _cache;

View File

@ -22,6 +22,10 @@ std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> renderer) MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> renderer)
: _timer([=] { checkNextFrameRender(); }) : _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) { , _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) {
crl::on_main_update_requests(
) | rpl::start_with_next([=] {
checkStep();
}, _lifetime);
} }
MultiPlayer::~MultiPlayer() { MultiPlayer::~MultiPlayer() {
@ -54,26 +58,22 @@ not_null<Animation*> MultiPlayer::append(
return _animations.back().get(); return _animations.back().get();
} }
crl::time MultiPlayer::startAtRightTime(not_null<SharedState*> state) { void MultiPlayer::startAtRightTime(not_null<SharedState*> state) {
Expects(!_active.empty()); Expects(!_active.empty());
Expects((_active.size() == 1) == (_started == kTimeUnknown)); Expects((_active.size() == 1) == (_started == kTimeUnknown));
const auto now = crl::now();
if (_active.size() == 1) { if (_active.size() == 1) {
_started = crl::now(); _started = now;
state->start(this, _started);
return _started;
} }
const auto now = crl::now();
const auto rate = state->information().frameRate; const auto rate = state->information().frameRate;
Assert(rate != 0); Assert(rate != 0);
const auto started = _started + _accumulatedDelay; const auto started = _started + _delay;
const auto skipFrames = (now - started) * rate / 1000; const auto skipFrames = (now - started) * rate / 1000;
const auto startAt = started + (1000 * skipFrames / rate);
state->start(this, startAt); state->start(this, _started, _delay, skipFrames);
return startAt;
} }
void MultiPlayer::start( void MultiPlayer::start(
@ -81,21 +81,32 @@ void MultiPlayer::start(
std::unique_ptr<SharedState> state) { std::unique_ptr<SharedState> state) {
Expects(state != nullptr); 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()); _active.emplace(animation, state.get());
auto information = state->information(); auto information = state->information();
startAtRightTime(state.get()); startAtRightTime(state.get());
_renderer->append(std::move(state)); _renderer->append(std::move(state));
_updates.fire({}); _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) { void MultiPlayer::remove(not_null<Animation*> animation) {
@ -112,7 +123,7 @@ void MultiPlayer::remove(not_null<Animation*> animation) {
if (_active.empty()) { if (_active.empty()) {
_started = kTimeUnknown; _started = kTimeUnknown;
_accumulatedDelay = 0; _delay = 0;
_nextFrameTime = kTimeUnknown; _nextFrameTime = kTimeUnknown;
_timer.cancel(); _timer.cancel();
} }
@ -127,7 +138,7 @@ rpl::producer<MultiUpdate> MultiPlayer::updates() const {
} }
void MultiPlayer::checkStep() { void MultiPlayer::checkStep() {
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { if (_active.empty() || _nextFrameTime == kFrameDisplayTimeAlreadyDone) {
return; return;
} else if (_nextFrameTime != kTimeUnknown) { } else if (_nextFrameTime != kTimeUnknown) {
checkNextFrameRender(); checkNextFrameRender();
@ -139,9 +150,6 @@ void MultiPlayer::checkStep() {
void MultiPlayer::checkNextFrameAvailability() { void MultiPlayer::checkNextFrameAvailability() {
Expects(_nextFrameTime == kTimeUnknown); Expects(_nextFrameTime == kTimeUnknown);
if (_active.empty()) {
return;
}
auto next = kTimeUnknown; auto next = kTimeUnknown;
for (const auto &[animation, state] : _active) { for (const auto &[animation, state] : _active) {
const auto time = state->nextFrameDisplayTime(); const auto time = state->nextFrameDisplayTime();
@ -181,10 +189,10 @@ void MultiPlayer::checkNextFrameRender() {
} else { } else {
_timer.cancel(); _timer.cancel();
const auto exact = std::exchange( markFrameDisplayed(now);
_nextFrameTime, addTimelineDelay(now - _nextFrameTime);
kFrameDisplayTimeAlreadyDone);
markFrameDisplayed(now, now - exact); _nextFrameTime = kFrameDisplayTimeAlreadyDone;
_updates.fire({}); _updates.fire({});
} }
} }
@ -198,7 +206,7 @@ void MultiPlayer::updateFrameRequest(
_renderer->updateFrameRequest(i->second, request); _renderer->updateFrameRequest(i->second, request);
} }
void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) { void MultiPlayer::markFrameDisplayed(crl::time now) {
Expects(!_active.empty()); Expects(!_active.empty());
auto displayed = 0; auto displayed = 0;
@ -210,17 +218,27 @@ void MultiPlayer::markFrameDisplayed(crl::time now, crl::time delayed) {
continue; continue;
} else if (now >= time) { } else if (now >= time) {
++displayed; ++displayed;
state->markFrameDisplayed(now, delayed); state->markFrameDisplayed(now);
} else { } else {
++waiting; ++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() { void MultiPlayer::markFrameShown() {
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
_nextFrameTime = kTimeUnknown; _nextFrameTime = kTimeUnknown;
appendPendingToActive();
} }
auto count = 0; auto count = 0;
for (const auto &[animation, state] : _active) { for (const auto &[animation, state] : _active) {

View File

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

View File

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