Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion entry/src/main/ets/model/StreamConfig.ets
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export function getDefaultStreamConfig(): StreamConfig {
enableSyncDecode: false, // 默认禁用同步解码(需要 API 20+;启用后低延迟优先)
enableVrr: false, // 默认禁用 VRR(可能丢帧)
enableVsync: false, // 默认关闭(低延迟优先;开启后更平滑但可能增加 1 帧左右延迟)
enableHostPacedPresentation: false, // 默认关闭,约增加一帧呈现余量
enableHostPacedPresentation: false, // 默认关闭,启用后使用有界低延迟 PTS 呈现
enablePostProcess: false, // 默认禁用后处理滤镜
upscaleMode: 0, // 默认不超分
upscaleSharpness: 0.5, // 默认锐度 50%
Expand Down
2 changes: 1 addition & 1 deletion entry/src/main/ets/pages/SettingsPageV2.ets
Original file line number Diff line number Diff line change
Expand Up @@ -2115,7 +2115,7 @@ struct SettingsPageV2 {
},
{
title: '主机 PTS 平滑呈现 (实验性)',
subtitle: '按主机时间线匀速送显,约增加一帧延迟',
subtitle: '按主机时间线送显;积压时跳到最新帧,不固定增加一帧延迟',
type: 'toggle',
value: this.enableHostPacedPresentation,
action: () => {
Expand Down
16 changes: 14 additions & 2 deletions nativelib/src/main/cpp/native_render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/

#include "native_render.h"
#include <algorithm>
#include <cstring>
#include <dlfcn.h>
#include <time.h>
Expand Down Expand Up @@ -371,10 +372,12 @@ void NativeRender::ResetPresentationStatsLocked() {
preciseScheduledCount_ = 0;
preciseDroppedCount_ = 0;
preciseLateCount_ = 0;
preciseCatchUpCount_ = 0;
precisePhaseShiftCount_ = 0;
preciseRebufferCount_ = 0;
preciseResyncCount_ = 0;
preciseApiFailureCount_ = 0;
preciseMaxTargetLeadNs_ = 0;
}

void NativeRender::ResetPresentationClock() {
Expand Down Expand Up @@ -494,26 +497,35 @@ NativeRender::FrameSubmitResult NativeRender::SubmitFrame(const DecodedFrame& fr
preciseDroppedCount_++;
}
if (plan.latenessNs > 0) preciseLateCount_++;
if (plan.event == PresentationEvent::CATCH_UP) {
preciseCatchUpCount_++;
}
if (plan.event == PresentationEvent::PHASE_SHIFT) precisePhaseShiftCount_++;
if (plan.event == PresentationEvent::REBUFFER) preciseRebufferCount_++;
if (plan.event == PresentationEvent::DISCONTINUITY ||
plan.event == PresentationEvent::DUPLICATE_PTS) {
preciseResyncCount_++;
}
if (plan.action == PresentationAction::SCHEDULE) {
preciseMaxTargetLeadNs_ = std::max(
preciseMaxTargetLeadNs_, plan.targetTimeNs - decodedAtNs);
}

const int64_t totalFrames = preciseScheduledCount_ + preciseDroppedCount_;
if (totalFrames % 6000 == 0) {
OH_LOG_INFO(LOG_APP,
"Host-paced stats: frames=%{public}lld, scheduled=%{public}lld, dropped=%{public}lld, late=%{public}lld, phaseShift=%{public}lld, rebuffer=%{public}lld, resync=%{public}lld, apiFailure=%{public}lld, lead=%{public}lldus",
"Host-paced stats: frames=%{public}lld, scheduled=%{public}lld, dropped=%{public}lld, late=%{public}lld, catchUp=%{public}lld, phaseShift=%{public}lld, rebuffer=%{public}lld, resync=%{public}lld, apiFailure=%{public}lld, lead=%{public}lldus, maxLead=%{public}lldus",
static_cast<long long>(totalFrames),
static_cast<long long>(preciseScheduledCount_),
static_cast<long long>(preciseDroppedCount_),
static_cast<long long>(preciseLateCount_),
static_cast<long long>(preciseCatchUpCount_),
static_cast<long long>(precisePhaseShiftCount_),
static_cast<long long>(preciseRebufferCount_),
static_cast<long long>(preciseResyncCount_),
static_cast<long long>(preciseApiFailureCount_),
static_cast<long long>(ptsScheduler_.GetInitialLeadNs() / 1000));
static_cast<long long>(ptsScheduler_.GetInitialLeadNs() / 1000),
static_cast<long long>(preciseMaxTargetLeadNs_ / 1000));
}
}

Expand Down
2 changes: 2 additions & 0 deletions nativelib/src/main/cpp/native_render.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,12 @@ class NativeRender {
int64_t preciseScheduledCount_ = 0;
int64_t preciseDroppedCount_ = 0;
int64_t preciseLateCount_ = 0;
int64_t preciseCatchUpCount_ = 0;
int64_t precisePhaseShiftCount_ = 0;
int64_t preciseRebufferCount_ = 0;
int64_t preciseResyncCount_ = 0;
int64_t preciseApiFailureCount_ = 0;
int64_t preciseMaxTargetLeadNs_ = 0;

// NativeVSync(用于设置期望帧率范围,API 20+)
std::mutex nativeVsyncMutex_;
Expand Down
29 changes: 21 additions & 8 deletions nativelib/src/main/cpp/presentation_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
namespace {
constexpr int64_t kNanosecondsPerSecond = 1000000000LL;
constexpr int64_t kNanosecondsPerMicrosecond = 1000LL;
constexpr int64_t kExtraPresentationLeadNs = 2000000LL;
constexpr int64_t kMinSubmitLeadNs = 1000000LL;
constexpr int64_t kMaxSubmitLeadNs = 2000000LL;
constexpr int64_t kDriftDeadbandNs = 2000000LL;
Expand All @@ -29,9 +28,13 @@ void PtsPresentationScheduler::Configure(double fps) {
frameIntervalNs_ = static_cast<int64_t>(
std::llround(static_cast<double>(kNanosecondsPerSecond) / safeFps));
frameIntervalNs_ = std::max<int64_t>(frameIntervalNs_, 1000000LL);
initialLeadNs_ = frameIntervalNs_ + kExtraPresentationLeadNs;
submitLeadNs_ = std::clamp(frameIntervalNs_ / 8,
kMinSubmitLeadNs, kMaxSubmitLeadNs);
// RenderOutputBufferAtTime only needs a small submission margin. A full
// frame of fixed lead makes the next VSync become the following VSync on
// part of every refresh cycle, adding latency even in steady state.
initialLeadNs_ = submitLeadNs_;
maxFutureLeadNs_ = initialLeadNs_ + frameIntervalNs_ / 2;
discontinuityNs_ = std::max<int64_t>(250000000LL, frameIntervalNs_ * 12);
Reset();
}
Expand Down Expand Up @@ -126,12 +129,22 @@ PresentationPlan PtsPresentationScheduler::PlanFrame(
driftErrorEmaNs_ = 0;
consecutiveSevereLateFrames_ = 0;

PresentationPlan plan;
plan.action = PresentationAction::SCHEDULE;
plan.event = PresentationEvent::PHASE_SHIFT;
plan.targetTimeNs = targetTimeNs;
plan.latenessNs = -repaymentNs;
return plan;
if (targetTimeNs - decodedAtNs <= maxFutureLeadNs_) {
PresentationPlan plan;
plan.action = PresentationAction::SCHEDULE;
plan.event = PresentationEvent::PHASE_SHIFT;
plan.targetTimeNs = targetTimeNs;
plan.latenessNs = -repaymentNs;
return plan;
}
}

if (targetTimeNs - decodedAtNs > maxFutureLeadNs_) {
// This is not jitter absorbed by our own phase shift; it is decoded
// output arriving in a burst ahead of the presentation timeline. A
// fresh anchor gives all queued burst frames an immediate timestamp,
// so the Surface can keep the newest frame instead of preserving lag.
return AnchorFrame(ptsUs, decodedAtNs, PresentationEvent::CATCH_UP);
}

ApplySlowDriftCorrection(decodedAtNs, targetTimeNs);
Expand Down
5 changes: 4 additions & 1 deletion nativelib/src/main/cpp/presentation_scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum class PresentationEvent {
NONE,
INITIAL_ANCHOR,
DISCONTINUITY,
CATCH_UP,
PHASE_SHIFT,
REBUFFER,
DUPLICATE_PTS,
Expand All @@ -46,15 +47,17 @@ class PtsPresentationScheduler {
PresentationPlan PlanFrame(int64_t ptsUs, int64_t decodedAtNs);

int64_t GetInitialLeadNs() const { return initialLeadNs_; }
int64_t GetMaxFutureLeadNs() const { return maxFutureLeadNs_; }

private:
PresentationPlan AnchorFrame(int64_t ptsUs, int64_t decodedAtNs,
PresentationEvent event);
void ApplySlowDriftCorrection(int64_t decodedAtNs, int64_t& targetTimeNs);

int64_t frameIntervalNs_ = 16666667LL;
int64_t initialLeadNs_ = 18666667LL;
int64_t initialLeadNs_ = 2000000LL;
int64_t submitLeadNs_ = 2000000LL;
int64_t maxFutureLeadNs_ = 10333333LL;
int64_t discontinuityNs_ = 250000000LL;

bool initialized_ = false;
Expand Down
44 changes: 32 additions & 12 deletions nativelib/src/test/cpp/presentation_scheduler_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace {
constexpr int64_t kMs = 1000000LL;

void TestBurstKeepsPtsCadence() {
void TestBurstCollapsesToLatestWithinLatencyBudget() {
PtsPresentationScheduler scheduler;
scheduler.Configure(60.0);

Expand All @@ -20,23 +20,26 @@ void TestBurstKeepsPtsCadence() {
assert(first.action == PresentationAction::SCHEDULE);
assert(second.action == PresentationAction::SCHEDULE);
assert(third.action == PresentationAction::SCHEDULE);
assert(second.targetTimeNs - first.targetTimeNs == 16667000LL);
assert(third.targetTimeNs - second.targetTimeNs == 16666000LL);
assert(second.event == PresentationEvent::CATCH_UP);
assert(third.event == PresentationEvent::CATCH_UP);
assert(first.targetTimeNs - 1000 * kMs == scheduler.GetInitialLeadNs());
assert(second.targetTimeNs - 1000 * kMs == scheduler.GetInitialLeadNs());
assert(third.targetTimeNs - 1001 * kMs == scheduler.GetInitialLeadNs());
}

void TestSmallLateFrameShiftsWholeTimeline() {
PtsPresentationScheduler scheduler;
scheduler.Configure(60.0);

const PresentationPlan first = scheduler.PlanFrame(0, 1000 * kMs);
const PresentationPlan shifted = scheduler.PlanFrame(16667, 1034 * kMs);
const PresentationPlan next = scheduler.PlanFrame(33333, 1035 * kMs);
const PresentationPlan shifted = scheduler.PlanFrame(16667, 1018 * kMs);
const PresentationPlan next = scheduler.PlanFrame(33333, 1034 * kMs);

assert(shifted.action == PresentationAction::SCHEDULE);
assert(shifted.event == PresentationEvent::PHASE_SHIFT);
assert(next.action == PresentationAction::SCHEDULE);
assert(next.targetTimeNs - shifted.targetTimeNs == 16666000LL);
assert(shifted.targetTimeNs >= 1035 * kMs);
assert(shifted.targetTimeNs >= 1020 * kMs);
assert(first.targetTimeNs < shifted.targetTimeNs);
}

Expand Down Expand Up @@ -67,7 +70,7 @@ void TestAccumulatedPhaseShiftRecoversQuickly() {
for (int i = 1; i <= 12; ++i) {
const int64_t ptsUs = i * kFrameUs;
const int64_t nominalDecodeNs = kStartNs + ptsUs * 1000;
const int64_t rampDelayNs = (20 + (i - 1) * 6) * kMs;
const int64_t rampDelayNs = (3 + (i - 1) * 6) * kMs;
const PresentationPlan delayed = scheduler.PlanFrame(
ptsUs, nominalDecodeNs + rampDelayNs);
assert(delayed.action == PresentationAction::SCHEDULE);
Expand Down Expand Up @@ -96,6 +99,20 @@ void TestAccumulatedPhaseShiftRecoversQuickly() {
kFrameUs * 1000) < kMs);
}

void TestBurstCatchesUpAfterPartialPhaseDebtRepayment() {
PtsPresentationScheduler scheduler;
scheduler.Configure(60.0);

scheduler.PlanFrame(0, 1000 * kMs);
const PresentationPlan shifted = scheduler.PlanFrame(16667, 1022 * kMs);
const PresentationPlan burst = scheduler.PlanFrame(50000, 1023 * kMs);

assert(shifted.event == PresentationEvent::PHASE_SHIFT);
assert(burst.action == PresentationAction::SCHEDULE);
assert(burst.event == PresentationEvent::CATCH_UP);
assert(burst.targetTimeNs - 1023 * kMs == scheduler.GetInitialLeadNs());
}

void TestDuplicatePtsReanchorsInsteadOfFreezing() {
PtsPresentationScheduler scheduler;
scheduler.Configure(60.0);
Expand All @@ -122,17 +139,19 @@ void TestDiscontinuityReanchors() {
assert(discontinuity.event == PresentationEvent::DISCONTINUITY);
}

void TestFractionalFpsLead() {
void TestFractionalFpsLatencyBudget() {
PtsPresentationScheduler scheduler;
scheduler.Configure(59.94);

const int64_t decodedAtNs = 2000 * kMs;
const PresentationPlan first = scheduler.PlanFrame(0, decodedAtNs);
const int64_t expectedLeadNs = static_cast<int64_t>(
std::llround(1000000000.0 / 59.94)) + 2 * kMs;
const int64_t frameIntervalNs = static_cast<int64_t>(
std::llround(1000000000.0 / 59.94));
const int64_t expectedLeadNs = 2 * kMs;

assert(first.action == PresentationAction::SCHEDULE);
assert(first.targetTimeNs - decodedAtNs == expectedLeadNs);
assert(scheduler.GetMaxFutureLeadNs() == expectedLeadNs + frameIntervalNs / 2);
}

void TestSlowClockDriftStaysBounded() {
Expand Down Expand Up @@ -160,13 +179,14 @@ void TestSlowClockDriftStaysBounded() {
} // namespace

int main() {
TestBurstKeepsPtsCadence();
TestBurstCollapsesToLatestWithinLatencyBudget();
TestSmallLateFrameShiftsWholeTimeline();
TestSevereLateDropThenRebuffer();
TestAccumulatedPhaseShiftRecoversQuickly();
TestBurstCatchesUpAfterPartialPhaseDebtRepayment();
TestDuplicatePtsReanchorsInsteadOfFreezing();
TestDiscontinuityReanchors();
TestFractionalFpsLead();
TestFractionalFpsLatencyBudget();
TestSlowClockDriftStaysBounded();
std::cout << "presentation scheduler tests passed\n";
return 0;
Expand Down
Loading