diff --git a/entry/src/main/cpp/CMakeLists.txt b/entry/src/main/cpp/CMakeLists.txt index aca856cf3..09bbe5f30 100644 --- a/entry/src/main/cpp/CMakeLists.txt +++ b/entry/src/main/cpp/CMakeLists.txt @@ -149,11 +149,13 @@ if(RDP_BUILD_TESTS) test/rdp_certificate_policy_test.cpp test/rdp_auth_identity_policy_test.cpp test/rdp_performance_policy_test.cpp + test/rdp_h264_capability_gate_test.cpp test/transfer_runtime_status_test.cpp test/rdp_input_queue_test.cpp test/gl_surface_lifecycle_policy_test.cpp test/native_image_context_policy_test.cpp test/decoder_recovery_policy_test.cpp + test/decoder_lifecycle_gate_test.cpp test/safe_log_test.cpp common/safe_log.cpp rdp/rdp_audio_policy.cpp diff --git a/entry/src/main/cpp/extensions/extension_loader_napi.cpp b/entry/src/main/cpp/extensions/extension_loader_napi.cpp index 9f19b1247..cf8640564 100644 --- a/entry/src/main/cpp/extensions/extension_loader_napi.cpp +++ b/entry/src/main/cpp/extensions/extension_loader_napi.cpp @@ -487,6 +487,13 @@ napi_value NapiGetRdpRenderStats(napi_env env, napi_callback_info info) { SetObjectInt64(env, result, "inputDroppedMouseMoves", stats.inputDroppedMouseMoves); SetObjectInt64(env, result, "inputNonDisposableOverflow", stats.inputNonDisposableOverflow); SetObjectString(env, result, "graphicsMode", stats.graphicsMode); + SetObjectBool(env, result, "h264Avc420Enabled", stats.h264Avc420Enabled); + SetObjectBool(env, result, "h264Avc444Enabled", stats.h264Avc444Enabled); + SetObjectBool(env, result, "h264DirectTextureEnabled", stats.h264DirectTextureEnabled); + SetObjectInt32(env, result, "h264PerformanceGainPermille", stats.h264PerformanceGainPermille); + SetObjectString(env, result, "h264Avc420Reason", stats.h264Avc420Reason); + SetObjectString(env, result, "h264Avc444Reason", stats.h264Avc444Reason); + SetObjectString(env, result, "h264DirectTextureReason", stats.h264DirectTextureReason); return result; } diff --git a/entry/src/main/cpp/extensions/protocol_adapter.h b/entry/src/main/cpp/extensions/protocol_adapter.h index fbeca6343..45b75e2f6 100644 --- a/entry/src/main/cpp/extensions/protocol_adapter.h +++ b/entry/src/main/cpp/extensions/protocol_adapter.h @@ -206,6 +206,13 @@ struct RdpRenderStats { int64_t inputDroppedMouseMoves = 0; int64_t inputNonDisposableOverflow = 0; std::string graphicsMode; + bool h264Avc420Enabled = false; + bool h264Avc444Enabled = false; + bool h264DirectTextureEnabled = false; + int h264PerformanceGainPermille = 0; + std::string h264Avc420Reason; + std::string h264Avc444Reason; + std::string h264DirectTextureReason; }; /** SFTP 远端文件条目 */ diff --git a/entry/src/main/cpp/rdp/freerdp_adapter.cpp b/entry/src/main/cpp/rdp/freerdp_adapter.cpp index 63c6f9a00..03c9c0a52 100644 --- a/entry/src/main/cpp/rdp/freerdp_adapter.cpp +++ b/entry/src/main/cpp/rdp/freerdp_adapter.cpp @@ -18,6 +18,7 @@ #include "rdp_certificate_policy.h" #include "rdp_frame_pump.h" #include "rdp_graphics_lifecycle.h" +#include "rdp_h264_capability_gate.h" #include "rdp_keymap.h" #include "rdp_performance_policy.h" #include "rdp_input_queue.h" @@ -569,6 +570,7 @@ struct FreeRdpAdapter::Impl { int lastFrameHeight = 0; bool forceNextFullFrame = false; std::string graphicsMode = "gdi"; + RdpH264CapabilitySnapshot h264Capabilities; void beginShutdownTrace() { shutdownStartedUs.store(steadyNowUs(), std::memory_order_release); @@ -798,11 +800,13 @@ static bool rdpGfxResetPathSafe() { } static bool rdpGfxH264PathSafe() { - // Keep H.264 disabled until decoder lifecycle, visual, and stress gates pass. - return false; + RdpH264CapabilityEvidence evidence; + evidence.gfxResetMatrixPassed = rdpGfxResetPathSafe(); + return EvaluateRdpH264Capabilities(evidence).avc420Enabled; } -static RdpPerformancePolicy::GraphicsMode applyRdpPerformanceSettings(rdpSettings* settings) { +static RdpPerformancePolicy::GraphicsMode applyRdpPerformanceSettings( + rdpSettings* settings, RdpH264CapabilitySnapshot* h264Capabilities) { const bool compiledGfx = compiledWithRdpGfx(); const bool compiledH264 = compiledWithGfxH264(); const bool fallbackForThisConnection = g_nextConnectionGfxFallback.consume(); @@ -810,7 +814,13 @@ static RdpPerformancePolicy::GraphicsMode applyRdpPerformanceSettings(rdpSetting const bool h264Available = compiledH264; const bool gfxConsumerAvailable = rdpGfxPipelineConsumerAvailable(); const bool gfxResetSafe = rdpGfxResetPathSafe(); - const bool h264PathSafe = rdpGfxH264PathSafe(); + RdpH264CapabilityEvidence h264Evidence; + h264Evidence.gfxResetMatrixPassed = gfxResetSafe; + const RdpH264CapabilitySnapshot h264Gate = EvaluateRdpH264Capabilities(h264Evidence); + const bool h264PathSafe = h264Gate.avc420Enabled && rdpGfxH264PathSafe(); + if (h264Capabilities) { + *h264Capabilities = h264Gate; + } const RdpPerformancePolicy::Settings perf = RdpPerformancePolicy::RecommendedLanSettings(gfxAvailable, h264Available, @@ -846,13 +856,18 @@ static RdpPerformancePolicy::GraphicsMode applyRdpPerformanceSettings(rdpSetting freerdp_settings_set_uint32(settings, FreeRDP_FrameAcknowledge, perf.frameAcknowledge); OH_LOG_INFO(LOG_APP, - "[RDP] performance settings: mode=%{public}s compiledGfx=%{public}s compiledH264=%{public}s gfxConsumer=%{public}s gfxResetSafe=%{public}s h264PathSafe=%{public}s nextFallback=%{public}s networkAuto=%{public}s connectionType=%{public}u gfx=%{public}s h264=%{public}s rfx=%{public}s frameAck=%{public}u", + "[RDP] performance settings: mode=%{public}s compiledGfx=%{public}s compiledH264=%{public}s gfxConsumer=%{public}s gfxResetSafe=%{public}s h264PathSafe=%{public}s h264Reason=%{public}s avc444=%{public}s avc444Reason=%{public}s directTexture=%{public}s directTextureReason=%{public}s nextFallback=%{public}s networkAuto=%{public}s connectionType=%{public}u gfx=%{public}s h264=%{public}s rfx=%{public}s frameAck=%{public}u", RdpPerformancePolicy::GraphicsModeName(mode), compiledGfx ? "true" : "false", compiledH264 ? "true" : "false", gfxConsumerAvailable ? "true" : "false", gfxResetSafe ? "true" : "false", h264PathSafe ? "true" : "false", + RdpH264GateReasonName(h264Gate.avc420Reason), + h264Gate.avc444Enabled ? "true" : "false", + RdpH264GateReasonName(h264Gate.avc444Reason), + h264Gate.directTextureEnabled ? "true" : "false", + RdpH264GateReasonName(h264Gate.directTextureReason), fallbackForThisConnection ? "true" : "false", freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) ? "true" : "false", freerdp_settings_get_uint32(settings, FreeRDP_ConnectionType), @@ -1535,7 +1550,14 @@ BOOL FreeRdpAdapter::cbDesktopResize(rdpContext* context) { } const bool success = resized && pumpStarted; - adapter->impl_->graphicsLifecycle.completeResize(ticket.epoch, success); + const bool committed = + adapter->impl_->graphicsLifecycle.completeResize(ticket.epoch, success); + if (!committed) { + OH_LOG_WARN(LOG_APP, + "[RDP-RESIZE] stale completion ignored epoch=%{public}llu [E-RDP-RESIZE-STALE]", + static_cast(ticket.epoch)); + return FALSE; + } adapter->impl_->presentationEnabled.store(success, std::memory_order_release); if (!success) { const RdpGraphicsLifecycleSnapshot lifecycle = @@ -1936,10 +1958,13 @@ void FreeRdpAdapter::connectThreadFunc() { freerdp_settings_set_bool(s, FreeRDP_RestrictedAdminModeRequired, FALSE); freerdp_settings_set_bool(s, FreeRDP_RestrictedAdminModeSupported, FALSE); freerdp_settings_set_bool(s, FreeRDP_SupportErrorInfoPdu, TRUE); - const RdpPerformancePolicy::GraphicsMode graphicsMode = applyRdpPerformanceSettings(s); + RdpH264CapabilitySnapshot h264Capabilities; + const RdpPerformancePolicy::GraphicsMode graphicsMode = + applyRdpPerformanceSettings(s, &h264Capabilities); { std::lock_guard renderLock(impl_->renderMutex); impl_->graphicsMode = RdpPerformancePolicy::GraphicsModeName(graphicsMode); + impl_->h264Capabilities = h264Capabilities; } impl_->graphicsLifecycle.reset( static_cast(freerdp_settings_get_uint32(s, FreeRDP_DesktopWidth)), @@ -2307,6 +2332,14 @@ RdpRenderStats FreeRdpAdapter::getRdpRenderStats() { stats.desktopResizeFailures = graphics.resizeFailures; stats.gfxChannelConnected = graphics.gfxInitialized; stats.graphicsMode = impl_->graphicsMode; + stats.h264Avc420Enabled = impl_->h264Capabilities.avc420Enabled; + stats.h264Avc444Enabled = impl_->h264Capabilities.avc444Enabled; + stats.h264DirectTextureEnabled = impl_->h264Capabilities.directTextureEnabled; + stats.h264PerformanceGainPermille = impl_->h264Capabilities.performanceGainPermille; + stats.h264Avc420Reason = RdpH264GateReasonName(impl_->h264Capabilities.avc420Reason); + stats.h264Avc444Reason = RdpH264GateReasonName(impl_->h264Capabilities.avc444Reason); + stats.h264DirectTextureReason = + RdpH264GateReasonName(impl_->h264Capabilities.directTextureReason); { std::lock_guard inputLock(impl_->inputQueueMutex); stats.inputQueueDepth = static_cast(impl_->inputQueue.depth()); diff --git a/entry/src/main/cpp/rdp/rdp_graphics_lifecycle.cpp b/entry/src/main/cpp/rdp/rdp_graphics_lifecycle.cpp index 9675e217c..e36677be5 100644 --- a/entry/src/main/cpp/rdp/rdp_graphics_lifecycle.cpp +++ b/entry/src/main/cpp/rdp/rdp_graphics_lifecycle.cpp @@ -27,7 +27,11 @@ RdpResizeTicket RdpGraphicsLifecycle::beginResize(int width, int height) { } snapshot_.resizeInProgress = true; snapshot_.presentationAllowed = false; - ++snapshot_.epoch; + ++nextResizeEpoch_; + if (nextResizeEpoch_ == 0) { + ++nextResizeEpoch_; + } + snapshot_.epoch = nextResizeEpoch_; pendingWidth_ = width; pendingHeight_ = height; ticket.accepted = true; @@ -37,10 +41,10 @@ RdpResizeTicket RdpGraphicsLifecycle::beginResize(int width, int height) { return ticket; } -void RdpGraphicsLifecycle::completeResize(uint64_t epoch, bool success) { +bool RdpGraphicsLifecycle::completeResize(uint64_t epoch, bool success) { std::lock_guard lock(mutex_); if (!snapshot_.resizeInProgress || epoch == 0 || epoch != snapshot_.epoch) { - return; + return false; } snapshot_.resizeInProgress = false; if (success) { @@ -54,6 +58,7 @@ void RdpGraphicsLifecycle::completeResize(uint64_t epoch, bool success) { } pendingWidth_ = 0; pendingHeight_ = 0; + return true; } RdpGfxChannelAction RdpGraphicsLifecycle::onChannelConnected(uintptr_t context) { diff --git a/entry/src/main/cpp/rdp/rdp_graphics_lifecycle.h b/entry/src/main/cpp/rdp/rdp_graphics_lifecycle.h index 9e6042ef5..fdd5bf94b 100644 --- a/entry/src/main/cpp/rdp/rdp_graphics_lifecycle.h +++ b/entry/src/main/cpp/rdp/rdp_graphics_lifecycle.h @@ -38,7 +38,7 @@ class RdpGraphicsLifecycle { void reset(int width, int height, bool gfxRequested); RdpResizeTicket beginResize(int width, int height); - void completeResize(uint64_t epoch, bool success); + bool completeResize(uint64_t epoch, bool success); RdpGfxChannelAction onChannelConnected(uintptr_t context); void completeChannelInitialization(uintptr_t context, bool success); @@ -53,6 +53,7 @@ class RdpGraphicsLifecycle { RdpGraphicsLifecycleSnapshot snapshot_; int pendingWidth_ = 0; int pendingHeight_ = 0; + uint64_t nextResizeEpoch_ = 0; bool channelInitializing_ = false; }; diff --git a/entry/src/main/cpp/rdp/rdp_h264_capability_gate.h b/entry/src/main/cpp/rdp/rdp_h264_capability_gate.h new file mode 100644 index 000000000..f73d2ca79 --- /dev/null +++ b/entry/src/main/cpp/rdp/rdp_h264_capability_gate.h @@ -0,0 +1,117 @@ +#ifndef RDP_H264_CAPABILITY_GATE_H +#define RDP_H264_CAPABILITY_GATE_H + +#include + +enum class RdpH264GateReason { + Ready = 0, + GfxResetMatrixPending, + Avc420LifecyclePending, + Avc420CompositionPending, + DeviceProfileUnsupported, + PerformanceEvidenceMissing, + PerformanceGainInsufficient, + StabilityMatrixPending, + Avc444LifecyclePending, + Avc444CompositionPending, + DirectTextureLifecyclePending, + CrossSurfaceZeroCopyPending, +}; + +struct RdpH264CapabilityEvidence { + bool gfxResetMatrixPassed = false; + bool avc420LifecyclePassed = false; + bool avc420CompositionSupported = false; + bool deviceProfileSupported = false; + int64_t gdiDamageP95Us = 0; + int64_t hardwareP95Us = 0; + bool stabilityMatrixPassed = false; + bool avc444LifecyclePassed = false; + bool avc444CompositionSupported = false; + bool directTextureLifecyclePassed = false; + bool crossSurfaceZeroCopyPassed = false; +}; + +struct RdpH264CapabilitySnapshot { + bool avc420Enabled = false; + bool avc444Enabled = false; + bool directTextureEnabled = false; + int performanceGainPermille = 0; + RdpH264GateReason avc420Reason = RdpH264GateReason::GfxResetMatrixPending; + RdpH264GateReason avc444Reason = RdpH264GateReason::Avc420LifecyclePending; + RdpH264GateReason directTextureReason = RdpH264GateReason::Avc420LifecyclePending; +}; + +inline int RdpH264PerformanceGainPermille(int64_t baselineP95Us, int64_t candidateP95Us) { + if (baselineP95Us <= 0 || candidateP95Us < 0 || candidateP95Us >= baselineP95Us) { + return 0; + } + return static_cast(((baselineP95Us - candidateP95Us) * 1000) / baselineP95Us); +} + +inline RdpH264GateReason EvaluateAvc420Reason(const RdpH264CapabilityEvidence& evidence) { + if (!evidence.gfxResetMatrixPassed) return RdpH264GateReason::GfxResetMatrixPending; + if (!evidence.avc420LifecyclePassed) return RdpH264GateReason::Avc420LifecyclePending; + if (!evidence.avc420CompositionSupported) return RdpH264GateReason::Avc420CompositionPending; + if (!evidence.deviceProfileSupported) return RdpH264GateReason::DeviceProfileUnsupported; + if (evidence.gdiDamageP95Us <= 0 || evidence.hardwareP95Us <= 0) { + return RdpH264GateReason::PerformanceEvidenceMissing; + } + if (RdpH264PerformanceGainPermille(evidence.gdiDamageP95Us, evidence.hardwareP95Us) < 300) { + return RdpH264GateReason::PerformanceGainInsufficient; + } + if (!evidence.stabilityMatrixPassed) return RdpH264GateReason::StabilityMatrixPending; + return RdpH264GateReason::Ready; +} + +inline RdpH264CapabilitySnapshot EvaluateRdpH264Capabilities( + const RdpH264CapabilityEvidence& evidence) { + RdpH264CapabilitySnapshot snapshot; + snapshot.performanceGainPermille = + RdpH264PerformanceGainPermille(evidence.gdiDamageP95Us, evidence.hardwareP95Us); + snapshot.avc420Reason = EvaluateAvc420Reason(evidence); + snapshot.avc420Enabled = snapshot.avc420Reason == RdpH264GateReason::Ready; + + if (!snapshot.avc420Enabled) { + snapshot.avc444Reason = snapshot.avc420Reason; + snapshot.directTextureReason = snapshot.avc420Reason; + return snapshot; + } + if (!evidence.avc444LifecyclePassed) { + snapshot.avc444Reason = RdpH264GateReason::Avc444LifecyclePending; + } else if (!evidence.avc444CompositionSupported) { + snapshot.avc444Reason = RdpH264GateReason::Avc444CompositionPending; + } else { + snapshot.avc444Reason = RdpH264GateReason::Ready; + snapshot.avc444Enabled = true; + } + if (!evidence.directTextureLifecyclePassed) { + snapshot.directTextureReason = RdpH264GateReason::DirectTextureLifecyclePending; + } else if (!evidence.crossSurfaceZeroCopyPassed) { + snapshot.directTextureReason = RdpH264GateReason::CrossSurfaceZeroCopyPending; + } else { + snapshot.directTextureReason = RdpH264GateReason::Ready; + snapshot.directTextureEnabled = true; + } + return snapshot; +} + +inline const char* RdpH264GateReasonName(RdpH264GateReason reason) { + switch (reason) { + case RdpH264GateReason::Ready: return "ready"; + case RdpH264GateReason::GfxResetMatrixPending: return "gfx-reset-matrix-pending"; + case RdpH264GateReason::Avc420LifecyclePending: return "avc420-lifecycle-pending"; + case RdpH264GateReason::Avc420CompositionPending: return "avc420-composition-pending"; + case RdpH264GateReason::DeviceProfileUnsupported: return "device-profile-unsupported"; + case RdpH264GateReason::PerformanceEvidenceMissing: return "performance-evidence-missing"; + case RdpH264GateReason::PerformanceGainInsufficient: return "performance-gain-insufficient"; + case RdpH264GateReason::StabilityMatrixPending: return "stability-matrix-pending"; + case RdpH264GateReason::Avc444LifecyclePending: return "avc444-lifecycle-pending"; + case RdpH264GateReason::Avc444CompositionPending: return "avc444-composition-pending"; + case RdpH264GateReason::DirectTextureLifecyclePending: return "direct-texture-lifecycle-pending"; + case RdpH264GateReason::CrossSurfaceZeroCopyPending: return "cross-surface-zero-copy-pending"; + default: return "unknown"; + } +} + +#endif diff --git a/entry/src/main/cpp/render/decoder_lifecycle_gate.h b/entry/src/main/cpp/render/decoder_lifecycle_gate.h new file mode 100644 index 000000000..8a04297e7 --- /dev/null +++ b/entry/src/main/cpp/render/decoder_lifecycle_gate.h @@ -0,0 +1,128 @@ +#ifndef DECODER_LIFECYCLE_GATE_H +#define DECODER_LIFECYCLE_GATE_H + +#include +#include +#include +#include + +enum class DecoderLifecycleState { + Uninitialized = 0, + Initializing, + Running, + Flushing, + Stopping, + Stopped, + Destroying, + Destroyed, +}; + +struct DecoderCallbackLease { + bool accepted = false; + uint64_t generation = 0; +}; + +class DecoderLifecycleGate { +public: + bool beginInitialization() { + std::lock_guard lock(mutex_); + if (state_ != DecoderLifecycleState::Uninitialized && + state_ != DecoderLifecycleState::Destroyed) { + return false; + } + state_ = DecoderLifecycleState::Initializing; + ++generation_; + return true; + } + + void markRunning() { + std::lock_guard lock(mutex_); + if (state_ == DecoderLifecycleState::Initializing) { + state_ = DecoderLifecycleState::Running; + } + } + + DecoderCallbackLease acquireCallback() { + std::lock_guard lock(mutex_); + if (state_ != DecoderLifecycleState::Running) { + return {}; + } + ++inFlightCallbacks_; + return {true, generation_}; + } + + void releaseCallback(const DecoderCallbackLease& lease) { + if (!lease.accepted) return; + std::lock_guard lock(mutex_); + if (inFlightCallbacks_ > 0) { + --inFlightCallbacks_; + } + if (inFlightCallbacks_ == 0) { + cv_.notify_all(); + } + } + + bool beginFlush() { + std::unique_lock lock(mutex_); + if (state_ != DecoderLifecycleState::Running) return false; + state_ = DecoderLifecycleState::Flushing; + ++generation_; + cv_.wait(lock, [this]() { return inFlightCallbacks_ == 0; }); + return true; + } + + void finishFlush(bool decoderReusable) { + std::lock_guard lock(mutex_); + if (state_ == DecoderLifecycleState::Flushing) { + state_ = decoderReusable ? DecoderLifecycleState::Running : DecoderLifecycleState::Stopped; + cv_.notify_all(); + } + } + + bool beginDestroy() { + std::unique_lock lock(mutex_); + cv_.wait(lock, [this]() { return state_ != DecoderLifecycleState::Flushing; }); + if (state_ == DecoderLifecycleState::Destroying || state_ == DecoderLifecycleState::Destroyed) { + return false; + } + state_ = DecoderLifecycleState::Destroying; + ++generation_; + cv_.wait(lock, [this]() { return inFlightCallbacks_ == 0; }); + return true; + } + + void markDestroyed() { + std::lock_guard lock(mutex_); + state_ = DecoderLifecycleState::Destroyed; + cv_.notify_all(); + } + + bool isCurrent(uint64_t generation) const { + std::lock_guard lock(mutex_); + return state_ == DecoderLifecycleState::Running && generation == generation_; + } + + DecoderLifecycleState state() const { + std::lock_guard lock(mutex_); + return state_; + } + + uint64_t generation() const { + std::lock_guard lock(mutex_); + return generation_; + } + + std::size_t inFlightCallbacks() const { + std::lock_guard lock(mutex_); + return inFlightCallbacks_; + } + +private: + mutable std::mutex mutex_; + std::condition_variable cv_; + DecoderLifecycleState state_ = DecoderLifecycleState::Uninitialized; + uint64_t generation_ = 0; + std::size_t inFlightCallbacks_ = 0; +}; + +#endif diff --git a/entry/src/main/cpp/render/gl_renderer.cpp b/entry/src/main/cpp/render/gl_renderer.cpp index 39bb6c606..7ee498585 100644 --- a/entry/src/main/cpp/render/gl_renderer.cpp +++ b/entry/src/main/cpp/render/gl_renderer.cpp @@ -1548,11 +1548,12 @@ int RendererNapi::RenderRawBgraRectActive( dirtyWidth, dirtyHeight, target.generation).result); } -void RendererNapi::MakeCurrent(int64_t handle) { +bool RendererNapi::MakeCurrent(int64_t handle) { auto* ctx = reinterpret_cast(handle); if (ctx && ctx->renderer && ctx->renderer->IsInitialized()) { - ctx->renderer->MakeCurrent(); + return ctx->renderer->MakeCurrent(); } + return false; } void RendererNapi::ReleaseCurrent(int64_t handle) { diff --git a/entry/src/main/cpp/render/gl_renderer.h b/entry/src/main/cpp/render/gl_renderer.h index 222aa1a15..30e947514 100644 --- a/entry/src/main/cpp/render/gl_renderer.h +++ b/entry/src/main/cpp/render/gl_renderer.h @@ -158,7 +158,7 @@ class GLRenderer { namespace RendererNapi { napi_value Init(napi_env env, napi_value exports); - void MakeCurrent(int64_t handle); + bool MakeCurrent(int64_t handle); void ReleaseCurrent(int64_t handle); void RenderNative(int64_t handle, GLuint textureId); void SetActiveSourceSize(int width, int height); diff --git a/entry/src/main/cpp/render/hw_decoder.cpp b/entry/src/main/cpp/render/hw_decoder.cpp index 27ae871c4..fd7527d19 100644 --- a/entry/src/main/cpp/render/hw_decoder.cpp +++ b/entry/src/main/cpp/render/hw_decoder.cpp @@ -41,44 +41,83 @@ constexpr size_t kMaxQueuedFrames = 12; // was 4 — too small for 45+fps w/ sa void HardwareDecoder::OnError(OH_AVCodec* /*codec*/, int32_t errorCode, void* userData) { auto* cb = static_cast(userData); + auto* self = cb ? cb->self.load(std::memory_order_acquire) : nullptr; + if (!self) return; + const DecoderCallbackLease lease = self->lifecycle_.acquireCallback(); + if (!lease.accepted) return; OH_LOG_ERROR(LOG_APP, "[Decoder] 解码器错误: code=%{public}d", errorCode); - if (cb && cb->self && cb->self->errorCallback_) { - cb->self->errorCallback_(DecoderError::OUTPUT_FAILED, - "OH_AVCodec error " + std::to_string(errorCode)); - } + self->notifyError(DecoderError::OUTPUT_FAILED, + "OH_AVCodec error " + std::to_string(errorCode)); + self->lifecycle_.releaseCallback(lease); } -void HardwareDecoder::OnStreamChanged(OH_AVCodec* /*codec*/, OH_AVFormat* /*format*/, void* userData) { - (void)userData; - OH_LOG_INFO(LOG_APP, "[Decoder] 码流格式变更"); +void HardwareDecoder::OnStreamChanged(OH_AVCodec* /*codec*/, OH_AVFormat* format, void* userData) { + auto* cb = static_cast(userData); + auto* self = cb ? cb->self.load(std::memory_order_acquire) : nullptr; + if (!self) return; + const DecoderCallbackLease lease = self->lifecycle_.acquireCallback(); + if (!lease.accepted) return; + int32_t width = 0; + int32_t height = 0; + int32_t pixelFormat = -1; + if (format) { + if (!OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &width)) { + OH_AVFormat_GetIntValue(format, OH_MD_KEY_WIDTH, &width); + } + if (!OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &height)) { + OH_AVFormat_GetIntValue(format, OH_MD_KEY_HEIGHT, &height); + } + OH_AVFormat_GetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, &pixelFormat); + } + if (width > 0 && height > 0 && width <= 16384 && height <= 16384) { + self->width_.store(width, std::memory_order_release); + self->height_.store(height, std::memory_order_release); + } + self->outputPixelFormat_.store(pixelFormat, std::memory_order_release); + OH_LOG_INFO(LOG_APP, "[Decoder] stream changed size=%{public}dx%{public}d format=%{public}d", + width, height, pixelFormat); + self->lifecycle_.releaseCallback(lease); } void HardwareDecoder::OnNeedInputBuffer(OH_AVCodec* /*codec*/, uint32_t index, OH_AVBuffer* buffer, void* userData) { - auto* self = static_cast(userData)->self; - self->handleInputBuffer(index, buffer); + auto* cb = static_cast(userData); + auto* self = cb ? cb->self.load(std::memory_order_acquire) : nullptr; + if (!self) return; + const DecoderCallbackLease lease = self->lifecycle_.acquireCallback(); + if (!lease.accepted) return; + self->handleInputBuffer(index, buffer, lease.generation); + self->lifecycle_.releaseCallback(lease); } void HardwareDecoder::OnNewOutputBuffer(OH_AVCodec* codec, uint32_t index, - OH_AVBuffer* /*buffer*/, void* userData) { - auto* self = static_cast(userData)->self; - OH_AVErrCode ret = OH_VideoDecoder_RenderOutputBuffer(codec, index); + OH_AVBuffer* buffer, void* userData) { + auto* cb = static_cast(userData); + auto* self = cb ? cb->self.load(std::memory_order_acquire) : nullptr; + if (!self) return; + const DecoderCallbackLease lease = self->lifecycle_.acquireCallback(); + if (!lease.accepted) return; + OH_AVCodecBufferAttr attr {}; + const bool eos = buffer && OH_AVBuffer_GetBufferAttr(buffer, &attr) == AV_ERR_OK && + (attr.flags & AVCODEC_BUFFER_FLAGS_EOS) != 0; + OH_AVErrCode ret = eos ? OH_VideoDecoder_FreeOutputBuffer(codec, index) : + OH_VideoDecoder_RenderOutputBuffer(codec, index); if (ret != AV_ERR_OK) { - if (self) { - ++self->renderOutputFailureCount_; - } - OH_LOG_WARN(LOG_APP, "[Decoder] RenderOutputBuffer failed: %{public}d index=%{public}u", - ret, index); - return; + ++self->renderOutputFailureCount_; + OH_LOG_WARN(LOG_APP, "[Decoder] output buffer release failed: %{public}d index=%{public}u eos=%{public}s", + ret, index, eos ? "true" : "false"); } - // NativeImage/GL is consumed by the dedicated render thread after OnFrameAvailable. + self->lifecycle_.releaseCallback(lease); } void HardwareDecoder::OnFrameAvailable(void* context) { auto* cb = static_cast(context); - if (cb && cb->self) { - cb->self->noteFrameAvailable(); - } + auto* self = cb ? cb->self.load(std::memory_order_acquire) : nullptr; + if (!self) return; + const DecoderCallbackLease lease = self->lifecycle_.acquireCallback(); + if (!lease.accepted) return; + self->noteFrameAvailable(); + self->lifecycle_.releaseCallback(lease); } // ============================================================ @@ -86,7 +125,7 @@ void HardwareDecoder::OnFrameAvailable(void* context) { // ============================================================ HardwareDecoder::HardwareDecoder() { - cbUserData_.self = this; + cbUserData_.self.store(this, std::memory_order_release); } HardwareDecoder::~HardwareDecoder() { @@ -110,24 +149,30 @@ const char* HardwareDecoder::GetMimeType(CodecType codec) { } int HardwareDecoder::Init(int width, int height, CodecType codec) { + if (width <= 0 || height <= 0 || width > 16384 || height > 16384 || + !lifecycle_.beginInitialization()) { + return -1; + } + cbUserData_.self.store(this, std::memory_order_release); + initialized_.store(false, std::memory_order_release); + decoderStarted_ = false; OH_LOG_INFO(LOG_APP, "[Decoder] Init: %{public}dx%{public}d codec=%{public}s", width, height, GetMimeType(codec)); - width_ = width; - height_ = height; + width_.store(width, std::memory_order_release); + height_.store(height, std::memory_order_release); + outputPixelFormat_.store(-1, std::memory_order_release); codecType_ = codec; - auto releaseTexture = [this]() { - if (textureId_ != 0) { - glDeleteTextures(1, &textureId_); - textureId_ = 0; - } + auto fail = [this](int code) { + Destroy(); + return code; }; // 1. 创建解码器 decoder_ = OH_VideoDecoder_CreateByMime(GetMimeType(codec)); if (!decoder_) { OH_LOG_ERROR(LOG_APP, "[Decoder] OH_VideoDecoder_CreateByMime 失败"); - return -1; + return fail(-1); } // 2. 注册回调 (必须在 Configure 之前) @@ -139,9 +184,7 @@ int HardwareDecoder::Init(int width, int height, CodecType codec) { OH_AVErrCode ret = OH_VideoDecoder_RegisterCallback(decoder_, cb, &cbUserData_); if (ret != AV_ERR_OK) { OH_LOG_ERROR(LOG_APP, "[Decoder] RegisterCallback 失败: %{public}d", ret); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - return -2; + return fail(-2); } // 3. 创建 NativeImage 并获取 surface (零拷贝纹理) @@ -156,19 +199,13 @@ int HardwareDecoder::Init(int width, int height, CodecType codec) { if (textureId_ == 0 || glErr != GL_NO_ERROR) { OH_LOG_ERROR(LOG_APP, "[Decoder] 创建 GL 外部纹理失败: texture=%{public}u err=%{public}x", textureId_, glErr); - releaseTexture(); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - return -3; + return fail(-3); } nativeImage_ = OH_NativeImage_Create(textureId_, GL_TEXTURE_EXTERNAL_OES); if (!nativeImage_) { OH_LOG_ERROR(LOG_APP, "[Decoder] OH_NativeImage_Create 失败"); - releaseTexture(); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - return -3; + return fail(-3); } OH_OnFrameAvailableListener listener; listener.context = &cbUserData_; @@ -184,15 +221,14 @@ int HardwareDecoder::Init(int width, int height, CodecType codec) { nativeWindow_ = OH_NativeImage_AcquireNativeWindow(nativeImage_); if (!nativeWindow_) { OH_LOG_ERROR(LOG_APP, "[Decoder] AcquireNativeWindow 失败"); - OH_NativeImage_Destroy(&nativeImage_); - releaseTexture(); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - return -4; + return fail(-4); } // 4. 配置解码器参数 OH_AVFormat* format = OH_AVFormat_Create(); + if (!format) { + return fail(-6); + } OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, width); OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, height); // Surface 模式不需要 OH_MD_KEY_PIXEL_FORMAT @@ -201,47 +237,34 @@ int HardwareDecoder::Init(int width, int height, CodecType codec) { OH_AVFormat_Destroy(format); if (ret != AV_ERR_OK) { OH_LOG_ERROR(LOG_APP, "[Decoder] Configure 失败: %{public}d", ret); - OH_NativeImage_Destroy(&nativeImage_); - releaseTexture(); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - return -6; + return fail(-6); } // 5. 设置解码输出 surface。必须在 Prepare 前,且部分设备要求 Configure 后调用。 ret = OH_VideoDecoder_SetSurface(decoder_, static_cast(nativeWindow_)); if (ret != AV_ERR_OK) { OH_LOG_ERROR(LOG_APP, "[Decoder] SetSurface 失败: %{public}d", ret); - OH_NativeImage_Destroy(&nativeImage_); - releaseTexture(); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - return -5; + return fail(-5); } // 6. Prepare ret = OH_VideoDecoder_Prepare(decoder_); if (ret != AV_ERR_OK) { OH_LOG_ERROR(LOG_APP, "[Decoder] Prepare 失败: %{public}d", ret); - OH_NativeImage_Destroy(&nativeImage_); - releaseTexture(); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - return -7; + return fail(-7); } // 7. Start + initialized_.store(true, std::memory_order_release); + lifecycle_.markRunning(); ret = OH_VideoDecoder_Start(decoder_); if (ret != AV_ERR_OK) { OH_LOG_ERROR(LOG_APP, "[Decoder] Start 失败: %{public}d", ret); - OH_NativeImage_Destroy(&nativeImage_); - releaseTexture(); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - return -8; + initialized_.store(false, std::memory_order_release); + return fail(-8); } - initialized_ = true; + decoderStarted_ = true; OH_LOG_INFO(LOG_APP, "[Decoder] ✓ 解码器启动成功 (Surface模式, %{public}dx%{public}d texture=%{public}u)", width, height, textureId_); return 0; @@ -268,10 +291,16 @@ size_t HardwareDecoder::dropOldestInputFramesLocked(size_t count) { } int HardwareDecoder::Decode(const uint8_t* data, size_t size, uint64_t timestamp, bool isKeyFrame) { - if (!initialized_) { + const DecoderCallbackLease operationLease = lifecycle_.acquireCallback(); + if (!operationLease.accepted || !initialized_.load(std::memory_order_acquire)) { OH_LOG_WARN(LOG_APP, "[Decoder] 解码器未初始化"); return -1; } + struct LeaseGuard { + DecoderLifecycleGate& gate; + DecoderCallbackLease lease; + ~LeaseGuard() { gate.releaseCallback(lease); } + } operationGuard {lifecycle_, operationLease}; if (!data || size == 0) { return 0; } @@ -353,15 +382,25 @@ int HardwareDecoder::Decode(const uint8_t* data, size_t size, uint64_t timestamp return 0; } -void HardwareDecoder::handleInputBuffer(uint32_t index, OH_AVBuffer* buffer) { +void HardwareDecoder::handleInputBuffer(uint32_t index, OH_AVBuffer* buffer, uint64_t generation) { + if (!buffer || !lifecycle_.isCurrent(generation)) { + return; + } { std::lock_guard lk(mutex_); - pendingInputBuffers_.push_back({index, buffer}); + pendingInputBuffers_.push_back({index, buffer, generation}); } drainInputBuffers(); } void HardwareDecoder::drainInputBuffers() { + const DecoderCallbackLease operationLease = lifecycle_.acquireCallback(); + if (!operationLease.accepted) return; + struct LeaseGuard { + DecoderLifecycleGate& gate; + DecoderCallbackLease lease; + ~LeaseGuard() { gate.releaseCallback(lease); } + } operationGuard {lifecycle_, operationLease}; while (true) { PendingInputBuffer input {}; EncodedFrame frame {}; @@ -371,7 +410,8 @@ void HardwareDecoder::drainInputBuffers() { { std::lock_guard lk(mutex_); - if (!initialized_ || !decoder_ || pendingInputBuffers_.empty() || inputQueue_.empty()) { + if (!initialized_.load(std::memory_order_acquire) || !decoder_ || + pendingInputBuffers_.empty() || inputQueue_.empty()) { return; } input = pendingInputBuffers_.front(); @@ -383,6 +423,12 @@ void HardwareDecoder::drainInputBuffers() { pendingBuffers = pendingInputBuffers_.size(); } + if (input.generation != operationLease.generation || + !lifecycle_.isCurrent(input.generation)) { + delete[] frame.data; + continue; + } + if (!input.buffer) { OH_LOG_WARN(LOG_APP, "[Decoder] input buffer null index=%{public}u", input.index); delete[] frame.data; @@ -463,9 +509,11 @@ void HardwareDecoder::noteFrameAvailable() { bool HardwareDecoder::waitForFrameAvailable() { std::unique_lock lk(mutex_); bool ok = frameAvailableCv_.wait_for(lk, std::chrono::milliseconds(50), [this]() { - return renderThreadStop_.load() || !initialized_ || frameAvailableCount_ > frameConsumeCount_; + return renderThreadStop_.load() || !initialized_.load(std::memory_order_acquire) || + frameAvailableCount_ > frameConsumeCount_; }); - if (ok && !renderThreadStop_.load() && initialized_ && frameAvailableCount_ > frameConsumeCount_) { + if (ok && !renderThreadStop_.load() && initialized_.load(std::memory_order_acquire) && + frameAvailableCount_ > frameConsumeCount_) { ++frameConsumeCount_; return true; } @@ -473,14 +521,28 @@ bool HardwareDecoder::waitForFrameAvailable() { } void HardwareDecoder::handleOutputBuffer(uint32_t /*index*/) { + const DecoderCallbackLease operationLease = lifecycle_.acquireCallback(); + if (!operationLease.accepted) return; + struct LeaseGuard { + DecoderLifecycleGate& gate; + DecoderCallbackLease lease; + ~LeaseGuard() { gate.releaseCallback(lease); } + } operationGuard {lifecycle_, operationLease}; if (!nativeImage_) { return; } if (!waitForFrameAvailable()) { return; } - if (makeCurrentCallback_) { - makeCurrentCallback_(); + DecoderMakeCurrentCallback makeCurrent; + DecoderFrameCallback frameCallback; + { + std::lock_guard lock(callbackMutex_); + makeCurrent = makeCurrentCallback_; + frameCallback = frameCallback_; + } + if (makeCurrent) { + makeCurrent(); } if (!nativeImageContextAttached_) { @@ -513,8 +575,10 @@ void HardwareDecoder::handleOutputBuffer(uint32_t /*index*/) { } // 通知渲染器: 纹理就绪 - if (frameCallback_) { - frameCallback_(textureId_, width_, height_); + const int outputWidth = width_.load(std::memory_order_acquire); + const int outputHeight = height_.load(std::memory_order_acquire); + if (frameCallback) { + frameCallback(textureId_, outputWidth, outputHeight); } uint64_t count = ++outputFrameCount_; if (count <= 3 || count % 300 == 0) { @@ -522,14 +586,15 @@ void HardwareDecoder::handleOutputBuffer(uint32_t /*index*/) { "[Decoder] output frame #%{public}llu texture=%{public}u size=%{public}dx%{public}d drops=%{public}llu waitDrops=%{public}llu trunc=%{public}llu renderFail=%{public}llu updateFail=%{public}llu", static_cast(count), textureId_, - width_, - height_, + outputWidth, + outputHeight, static_cast(inputDropCount_.load()), static_cast(waitKeyframeDropCount_.load()), static_cast(inputTruncatedCount_.load()), static_cast(renderOutputFailureCount_.load()), static_cast(updateSurfaceFailureCount_.load())); } + outputFrameCv_.notify_all(); } void HardwareDecoder::StartRenderThread() { @@ -559,8 +624,13 @@ void HardwareDecoder::renderLoop() { detachRet, textureId_); } - if (releaseCurrentCallback_) { - releaseCurrentCallback_(); + DecoderReleaseCurrentCallback releaseCurrent; + { + std::lock_guard lock(callbackMutex_); + releaseCurrent = releaseCurrentCallback_; + } + if (releaseCurrent) { + releaseCurrent(); } nativeImageContextAttached_ = false; OH_LOG_INFO(LOG_APP, "[Decoder] render thread stopped"); @@ -579,16 +649,41 @@ GLuint HardwareDecoder::GetTextureId() const { } void HardwareDecoder::Flush() { - if (initialized_ && decoder_) { - OH_LOG_INFO(LOG_APP, "[Decoder] Flush"); - OH_VideoDecoder_Flush(decoder_); + if (!initialized_.load(std::memory_order_acquire) || !decoder_ || !lifecycle_.beginFlush()) { + return; + } + OH_LOG_INFO(LOG_APP, "[Decoder] Flush"); + { std::lock_guard lk(mutex_); clearInputQueueLocked(); pendingInputBuffers_.clear(); backpressure_.reset(); + frameConsumeCount_ = frameAvailableCount_; + } + const OH_AVErrCode ret = OH_VideoDecoder_Flush(decoder_); + { + std::lock_guard lk(mutex_); + clearInputQueueLocked(); + pendingInputBuffers_.clear(); + backpressure_.reset(); + frameConsumeCount_ = frameAvailableCount_; + } + lifecycle_.finishFlush(ret == AV_ERR_OK); + if (ret != AV_ERR_OK) { + initialized_.store(false, std::memory_order_release); + notifyError(DecoderError::FLUSH_FAILED, + "OH_VideoDecoder_Flush failed " + std::to_string(ret)); } } +bool HardwareDecoder::WaitForOutputFrame(uint64_t afterCount, int timeoutMs) { + std::unique_lock lock(mutex_); + return outputFrameCv_.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this, afterCount]() { + return outputFrameCount_.load(std::memory_order_acquire) > afterCount || + !initialized_.load(std::memory_order_acquire); + }) && outputFrameCount_.load(std::memory_order_acquire) > afterCount; +} + size_t HardwareDecoder::QueuedFrameCount() const { std::lock_guard lock(mutex_); return inputQueue_.size(); @@ -599,55 +694,82 @@ uint64_t HardwareDecoder::DroppedFrameCount() const { } void HardwareDecoder::Destroy() { + if (!lifecycle_.beginDestroy()) { + return; + } + initialized_.store(false, std::memory_order_release); + frameAvailableCv_.notify_all(); + outputFrameCv_.notify_all(); stopRenderThread(); - if (initialized_) { - OH_LOG_INFO(LOG_APP, "[Decoder] Destroy"); - if (decoder_) { + OH_LOG_INFO(LOG_APP, "[Decoder] Destroy"); + if (decoder_) { + if (decoderStarted_) { OH_VideoDecoder_Stop(decoder_); - OH_VideoDecoder_Destroy(decoder_); - decoder_ = nullptr; - } - if (nativeImage_) { - OH_NativeImage_Destroy(&nativeImage_); - nativeImage_ = nullptr; + decoderStarted_ = false; } - if (textureId_ != 0) { - if (makeCurrentCallback_) { - makeCurrentCallback_(); - } - glDeleteTextures(1, &textureId_); - textureId_ = 0; - if (releaseCurrentCallback_) { - releaseCurrentCallback_(); - } + OH_VideoDecoder_Destroy(decoder_); + decoder_ = nullptr; + } + cbUserData_.self.store(nullptr, std::memory_order_release); + if (nativeImage_) { + OH_NativeImage_Destroy(&nativeImage_); + nativeImage_ = nullptr; + } + if (textureId_ != 0) { + DecoderMakeCurrentCallback makeCurrent; + DecoderReleaseCurrentCallback releaseCurrent; + { + std::lock_guard lock(callbackMutex_); + makeCurrent = makeCurrentCallback_; + releaseCurrent = releaseCurrentCallback_; } - nativeWindow_ = nullptr; - initialized_ = false; - - // 清空未处理的输入队列 + if (makeCurrent) makeCurrent(); + glDeleteTextures(1, &textureId_); + textureId_ = 0; + if (releaseCurrent) releaseCurrent(); + } + nativeWindow_ = nullptr; + nativeImageContextAttached_ = false; + { std::lock_guard lk(mutex_); clearInputQueueLocked(); pendingInputBuffers_.clear(); backpressure_.reset(); + frameAvailableCount_ = 0; + frameConsumeCount_ = 0; } + lifecycle_.markDestroyed(); } void HardwareDecoder::SetFrameCallback(DecoderFrameCallback callback) { + std::lock_guard lock(callbackMutex_); frameCallback_ = std::move(callback); } void HardwareDecoder::SetMakeCurrentCallback(DecoderMakeCurrentCallback callback) { + std::lock_guard lock(callbackMutex_); makeCurrentCallback_ = std::move(callback); } void HardwareDecoder::SetReleaseCurrentCallback(DecoderReleaseCurrentCallback callback) { + std::lock_guard lock(callbackMutex_); releaseCurrentCallback_ = std::move(callback); } void HardwareDecoder::SetErrorCallback(DecoderErrorCallback callback) { + std::lock_guard lock(callbackMutex_); errorCallback_ = std::move(callback); } +void HardwareDecoder::notifyError(DecoderError error, const std::string& message) { + DecoderErrorCallback callback; + { + std::lock_guard lock(callbackMutex_); + callback = errorCallback_; + } + if (callback) callback(error, message); +} + // ============================================================ // H.264 最小 IDR 帧 — 64×64 蓝色测试画面 // 编码: SPS + PPS + IDR slice (YUV all-blue) @@ -899,11 +1021,10 @@ bool RecreateDecoderForFrame(DecoderContext* ctx, const VideoFrame& frame) { ctx->useSoftware = false; auto decoder = std::shared_ptr(new HardwareDecoder()); - if (ctx->rendererHandle > 0) { + const bool contextCurrent = ctx->rendererHandle > 0 && RendererNapi::MakeCurrent(ctx->rendererHandle); - } - int result = decoder->Init(frame.width, frame.height, frame.codec); - if (ctx->rendererHandle > 0) { + const int result = contextCurrent ? decoder->Init(frame.width, frame.height, frame.codec) : -9; + if (contextCurrent) { RendererNapi::ReleaseCurrent(ctx->rendererHandle); } if (result == 0) { @@ -953,26 +1074,41 @@ bool RecreateDecoderForFrame(DecoderContext* ctx, const VideoFrame& frame) { } /** - * NAPI: initDecoder(width: number, height: number, codec: number): number + * NAPI: initDecoder(width: number, height: number, codec: number, rendererHandle: number): number */ napi_value NapiInitDecoder(napi_env env, napi_callback_info info) { - size_t argc = 3; - napi_value args[3]; + size_t argc = 4; + napi_value args[4]; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); - int32_t width, height, codecInt; + int32_t width = 0; + int32_t height = 0; + int32_t codecInt = 0; + int64_t rendererHandle = 0; napi_get_value_int32(env, args[0], &width); napi_get_value_int32(env, args[1], &height); napi_get_value_int32(env, args[2], &codecInt); + if (argc > 3) { + napi_get_value_int64(env, args[3], &rendererHandle); + } CodecType codec = static_cast(codecInt); auto decoder = std::shared_ptr(new HardwareDecoder()); - int result = decoder->Init(width, height, codec); + const bool contextCurrent = rendererHandle > 0 && RendererNapi::MakeCurrent(rendererHandle); + const int result = contextCurrent ? decoder->Init(width, height, codec) : -9; + if (contextCurrent) { + RendererNapi::ReleaseCurrent(rendererHandle); + } else { + OH_LOG_WARN(LOG_APP, + "[Decoder] hardware init skipped: renderer context unavailable handle=%{public}lld", + static_cast(rendererHandle)); + } if (result == 0) { auto* ctx = new DecoderContext(); ctx->decoder = decoder; ctx->useSoftware = false; + ctx->rendererHandle = rendererHandle; ctx->width = width; ctx->height = height; napi_value handle; @@ -987,6 +1123,7 @@ napi_value NapiInitDecoder(napi_env env, napi_callback_info info) { auto* ctx = new DecoderContext(); ctx->softwareDecoder = softwareDecoder; ctx->useSoftware = true; + ctx->rendererHandle = rendererHandle; ctx->width = width; ctx->height = height; napi_value handle; @@ -1106,6 +1243,91 @@ napi_value NapiTestDecoderH264(napi_env env, napi_callback_info info) { napi_value r; napi_create_int32(env, ret, &r); return r; } +/** + * Diagnostic-only isolated AVC420 lifecycle probe. It owns a temporary decoder + * and never binds a frame callback to the active user session. + */ +napi_value NapiProbeAvc420Decoder(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + int64_t rendererHandle = 0; + if (argc > 0) { + napi_get_value_int64(env, args[0], &rendererHandle); + } + + bool contextReady = rendererHandle > 0 && RendererNapi::MakeCurrent(rendererHandle); + int initCode = -9; + int decodeCode = -1; + bool outputObserved = false; + int outputWidth = 0; + int outputHeight = 0; + int outputPixelFormat = -1; + std::string status = "renderer-context-unavailable"; + + auto decoder = std::shared_ptr(new HardwareDecoder()); + if (contextReady) { + initCode = decoder->Init(64, 64, CodecType::H264); + RendererNapi::ReleaseCurrent(rendererHandle); + if (initCode == 0) { + decoder->SetMakeCurrentCallback([rendererHandle]() { + RendererNapi::MakeCurrent(rendererHandle); + }); + decoder->SetReleaseCurrentCallback([rendererHandle]() { + RendererNapi::ReleaseCurrent(rendererHandle); + }); + decoder->StartRenderThread(); + const uint64_t before = decoder->OutputFrameCount(); + decodeCode = decoder->Decode(H264_BLUE_IDR_64x64, H264_BLUE_IDR_SIZE, 0, true); + if (decodeCode == 0) { + outputObserved = decoder->WaitForOutputFrame(before, 1500); + } + outputWidth = decoder->GetOutputWidth(); + outputHeight = decoder->GetOutputHeight(); + outputPixelFormat = decoder->GetOutputPixelFormat(); + status = outputObserved ? "passed" : + (decodeCode == 0 ? "output-timeout" : "decode-failed"); + decoder->StopRenderThreadForDetach(); + if (RendererNapi::MakeCurrent(rendererHandle)) { + decoder->Destroy(); + RendererNapi::ReleaseCurrent(rendererHandle); + } else { + decoder->Destroy(); + } + } else { + status = "init-failed"; + } + } + + napi_value result; + napi_create_object(env, &result); + auto setBool = [env, result](const char* name, bool value) { + napi_value item; + napi_get_boolean(env, value, &item); + napi_set_named_property(env, result, name, item); + }; + auto setInt = [env, result](const char* name, int32_t value) { + napi_value item; + napi_create_int32(env, value, &item); + napi_set_named_property(env, result, name, item); + }; + auto setString = [env, result](const char* name, const std::string& value) { + napi_value item; + napi_create_string_utf8(env, value.c_str(), value.size(), &item); + napi_set_named_property(env, result, name, item); + }; + setString("status", status); + setBool("contextReady", contextReady); + setBool("outputObserved", outputObserved); + setBool("lifecyclePassed", outputObserved && initCode == 0 && decodeCode == 0); + setInt("initCode", initCode); + setInt("decodeCode", decodeCode); + setInt("outputWidth", outputWidth); + setInt("outputHeight", outputHeight); + setInt("outputPixelFormat", outputPixelFormat); + return result; +} + /** * NAPI: bindVideoPipeline(decoderHandle: number, rendererHandle: number): boolean */ @@ -1395,6 +1617,10 @@ napi_value DecoderNapi::Init(napi_env env, napi_value exports) { NapiTestDecoderH264, nullptr, &fn); napi_set_named_property(env, exports, "testDecoderH264", fn); + napi_create_function(env, "probeAvc420Decoder", NAPI_AUTO_LENGTH, + NapiProbeAvc420Decoder, nullptr, &fn); + napi_set_named_property(env, exports, "probeAvc420Decoder", fn); + napi_create_function(env, "bindVideoPipeline", NAPI_AUTO_LENGTH, NapiBindVideoPipeline, nullptr, &fn); napi_set_named_property(env, exports, "bindVideoPipeline", fn); diff --git a/entry/src/main/cpp/render/hw_decoder.h b/entry/src/main/cpp/render/hw_decoder.h index c2e87b0e6..efc37e493 100644 --- a/entry/src/main/cpp/render/hw_decoder.h +++ b/entry/src/main/cpp/render/hw_decoder.h @@ -12,6 +12,7 @@ #define HW_DECODER_H #include "extensions/protocol_adapter.h" +#include "decoder_lifecycle_gate.h" #include "video_backpressure_controller.h" #include #include @@ -63,6 +64,7 @@ struct EncodedFrame { struct PendingInputBuffer { uint32_t index; OH_AVBuffer* buffer; + uint64_t generation; }; /** @@ -108,10 +110,15 @@ class HardwareDecoder { void Destroy(); /** 是否已初始化 */ - bool IsInitialized() const { return initialized_; } + bool IsInitialized() const { return initialized_.load(std::memory_order_acquire); } /** 当前解码器编码类型 */ CodecType GetCodecType() const { return codecType_; } + int GetOutputWidth() const { return width_.load(std::memory_order_acquire); } + int GetOutputHeight() const { return height_.load(std::memory_order_acquire); } + int GetOutputPixelFormat() const { return outputPixelFormat_.load(std::memory_order_acquire); } + uint64_t OutputFrameCount() const { return outputFrameCount_.load(std::memory_order_acquire); } + bool WaitForOutputFrame(uint64_t afterCount, int timeoutMs); size_t QueuedFrameCount() const; uint64_t DroppedFrameCount() const; @@ -153,15 +160,19 @@ class HardwareDecoder { OH_NativeImage* nativeImage_ = nullptr; // NativeImage (零拷贝纹理) void* nativeWindow_ = nullptr; // OHNativeWindow* (从 NativeImage 获取, 存为 void* 避免头文件冲突) GLuint textureId_ = 0; // NativeImage 关联的 GL 纹理 ID - int width_ = 0; - int height_ = 0; + std::atomic width_ {0}; + std::atomic height_ {0}; + std::atomic outputPixelFormat_ {-1}; CodecType codecType_ = CodecType::H264; - bool initialized_ = false; + std::atomic initialized_ {false}; + bool decoderStarted_ = false; + DecoderLifecycleGate lifecycle_; DecoderFrameCallback frameCallback_; DecoderMakeCurrentCallback makeCurrentCallback_; DecoderReleaseCurrentCallback releaseCurrentCallback_; DecoderErrorCallback errorCallback_; + mutable std::mutex callbackMutex_; // 输入队列 + 线程安全 mutable std::mutex mutex_; @@ -176,6 +187,7 @@ class HardwareDecoder { std::atomic updateSurfaceFailureCount_ {0}; std::atomic outputFrameCount_ {0}; std::condition_variable frameAvailableCv_; + std::condition_variable outputFrameCv_; uint64_t frameAvailableCount_ = 0; uint64_t frameConsumeCount_ = 0; Render::VideoBackpressureController backpressure_; @@ -185,7 +197,7 @@ class HardwareDecoder { // 回调上下文 (静态函数 + userData) struct CallbackUserData { - HardwareDecoder* self; + std::atomic self {nullptr}; }; CallbackUserData cbUserData_; @@ -201,13 +213,14 @@ class HardwareDecoder { size_t clearInputQueueLocked(); size_t dropOldestInputFramesLocked(size_t count); - void handleInputBuffer(uint32_t index, OH_AVBuffer* buffer); + void handleInputBuffer(uint32_t index, OH_AVBuffer* buffer, uint64_t generation); void drainInputBuffers(); bool waitForFrameAvailable(); void handleOutputBuffer(uint32_t index); void noteFrameAvailable(); void stopRenderThread(); void renderLoop(); + void notifyError(DecoderError error, const std::string& message); }; // ============================================================ diff --git a/entry/src/main/cpp/test/decoder_lifecycle_gate_test.cpp b/entry/src/main/cpp/test/decoder_lifecycle_gate_test.cpp new file mode 100644 index 000000000..62139cbe3 --- /dev/null +++ b/entry/src/main/cpp/test/decoder_lifecycle_gate_test.cpp @@ -0,0 +1,77 @@ +#include "test_runner.h" +#include "render/decoder_lifecycle_gate.h" + +#include +#include +#include + +RDP_TEST_CASE(decoder_lifecycle_gate_rejects_stale_generation_after_flush) { + DecoderLifecycleGate gate; + RDP_ASSERT(gate.beginInitialization()); + gate.markRunning(); + const uint64_t beforeFlush = gate.generation(); + RDP_ASSERT(gate.isCurrent(beforeFlush)); + RDP_ASSERT(gate.beginFlush()); + RDP_ASSERT(!gate.isCurrent(beforeFlush)); + gate.finishFlush(true); + RDP_ASSERT(gate.generation() > beforeFlush); +} + +RDP_TEST_CASE(decoder_lifecycle_gate_waits_for_callback_before_flush) { + DecoderLifecycleGate gate; + RDP_ASSERT(gate.beginInitialization()); + gate.markRunning(); + const DecoderCallbackLease lease = gate.acquireCallback(); + RDP_ASSERT(lease.accepted); + + std::atomic flushStarted {false}; + std::atomic flushEntered {false}; + std::thread flushThread([&]() { + flushStarted.store(true); + flushEntered.store(gate.beginFlush()); + }); + while (!flushStarted.load()) { + std::this_thread::yield(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + RDP_ASSERT(!flushEntered.load()); + gate.releaseCallback(lease); + flushThread.join(); + RDP_ASSERT(flushEntered.load()); + gate.finishFlush(true); +} + +RDP_TEST_CASE(decoder_lifecycle_gate_makes_destroy_idempotent) { + DecoderLifecycleGate gate; + RDP_ASSERT(gate.beginInitialization()); + gate.markRunning(); + RDP_ASSERT(gate.beginDestroy()); + gate.markDestroyed(); + RDP_ASSERT(!gate.beginDestroy()); + RDP_ASSERT_EQ(gate.state(), DecoderLifecycleState::Destroyed); +} + +RDP_TEST_CASE(decoder_lifecycle_gate_serializes_destroy_after_flush) { + DecoderLifecycleGate gate; + RDP_ASSERT(gate.beginInitialization()); + gate.markRunning(); + RDP_ASSERT(gate.beginFlush()); + + std::atomic destroyStarted {false}; + std::atomic destroyEntered {false}; + std::thread destroyThread([&]() { + destroyStarted.store(true); + destroyEntered.store(gate.beginDestroy()); + }); + while (!destroyStarted.load()) { + std::this_thread::yield(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + RDP_ASSERT(!destroyEntered.load()); + + gate.finishFlush(true); + destroyThread.join(); + RDP_ASSERT(destroyEntered.load()); + RDP_ASSERT_EQ(gate.state(), DecoderLifecycleState::Destroying); + gate.markDestroyed(); +} diff --git a/entry/src/main/cpp/test/rdp_graphics_lifecycle_test.cpp b/entry/src/main/cpp/test/rdp_graphics_lifecycle_test.cpp index e2d861161..81523c2b8 100644 --- a/entry/src/main/cpp/test/rdp_graphics_lifecycle_test.cpp +++ b/entry/src/main/cpp/test/rdp_graphics_lifecycle_test.cpp @@ -36,6 +36,33 @@ RDP_TEST_CASE(rdp_graphics_lifecycle_rejects_invalid_or_overlapping_resize) { RDP_ASSERT_EQ(snapshot.resizeFailures, 1ULL); } +RDP_TEST_CASE(rdp_graphics_lifecycle_reset_invalidates_stale_resize_ticket) { + RdpGraphicsLifecycle lifecycle; + lifecycle.reset(1920, 1080, true); + + const RdpResizeTicket stale = lifecycle.beginResize(2560, 1440); + RDP_ASSERT(stale.accepted); + + lifecycle.reset(1280, 720, false); + const RdpResizeTicket current = lifecycle.beginResize(1600, 900); + RDP_ASSERT(current.accepted); + RDP_ASSERT(stale.epoch != current.epoch); + RDP_ASSERT(!lifecycle.completeResize(stale.epoch, true)); + + const RdpGraphicsLifecycleSnapshot pending = lifecycle.snapshot(); + RDP_ASSERT(pending.resizeInProgress); + RDP_ASSERT(!pending.presentationAllowed); + RDP_ASSERT_EQ(pending.desktopWidth, 1280); + RDP_ASSERT_EQ(pending.desktopHeight, 720); + + RDP_ASSERT(lifecycle.completeResize(current.epoch, true)); + const RdpGraphicsLifecycleSnapshot completed = lifecycle.snapshot(); + RDP_ASSERT(completed.presentationAllowed); + RDP_ASSERT_EQ(completed.desktopWidth, 1600); + RDP_ASSERT_EQ(completed.desktopHeight, 900); + RDP_ASSERT_EQ(completed.resizeCount, 1ULL); +} + RDP_TEST_CASE(rdp_graphics_lifecycle_ignores_duplicate_and_stale_channel_events) { RdpGraphicsLifecycle lifecycle; lifecycle.reset(1920, 1080, true); diff --git a/entry/src/main/cpp/test/rdp_h264_capability_gate_test.cpp b/entry/src/main/cpp/test/rdp_h264_capability_gate_test.cpp new file mode 100644 index 000000000..df554c2a9 --- /dev/null +++ b/entry/src/main/cpp/test/rdp_h264_capability_gate_test.cpp @@ -0,0 +1,63 @@ +#include "test_runner.h" +#include "rdp/rdp_h264_capability_gate.h" + +RDP_TEST_CASE(rdp_h264_gate_requires_every_avc420_production_requirement) { + RdpH264CapabilityEvidence evidence; + evidence.gfxResetMatrixPassed = true; + evidence.avc420LifecyclePassed = true; + evidence.avc420CompositionSupported = true; + evidence.deviceProfileSupported = true; + evidence.gdiDamageP95Us = 10000; + evidence.hardwareP95Us = 7000; + + RdpH264CapabilitySnapshot snapshot = EvaluateRdpH264Capabilities(evidence); + RDP_ASSERT(!snapshot.avc420Enabled); + RDP_ASSERT_EQ(snapshot.avc420Reason, RdpH264GateReason::StabilityMatrixPending); + + evidence.stabilityMatrixPassed = true; + snapshot = EvaluateRdpH264Capabilities(evidence); + RDP_ASSERT(snapshot.avc420Enabled); + RDP_ASSERT_EQ(snapshot.performanceGainPermille, 300); +} + +RDP_TEST_CASE(rdp_h264_gate_rejects_less_than_thirty_percent_p95_gain) { + RdpH264CapabilityEvidence evidence; + evidence.gfxResetMatrixPassed = true; + evidence.avc420LifecyclePassed = true; + evidence.avc420CompositionSupported = true; + evidence.deviceProfileSupported = true; + evidence.gdiDamageP95Us = 10000; + evidence.hardwareP95Us = 7001; + evidence.stabilityMatrixPassed = true; + + const RdpH264CapabilitySnapshot snapshot = EvaluateRdpH264Capabilities(evidence); + RDP_ASSERT(!snapshot.avc420Enabled); + RDP_ASSERT_EQ(snapshot.avc420Reason, RdpH264GateReason::PerformanceGainInsufficient); +} + +RDP_TEST_CASE(rdp_h264_gate_keeps_avc444_and_direct_texture_independent) { + RdpH264CapabilityEvidence evidence; + evidence.gfxResetMatrixPassed = true; + evidence.avc420LifecyclePassed = true; + evidence.avc420CompositionSupported = true; + evidence.deviceProfileSupported = true; + evidence.gdiDamageP95Us = 10000; + evidence.hardwareP95Us = 6000; + evidence.stabilityMatrixPassed = true; + + RdpH264CapabilitySnapshot snapshot = EvaluateRdpH264Capabilities(evidence); + RDP_ASSERT(snapshot.avc420Enabled); + RDP_ASSERT(!snapshot.avc444Enabled); + RDP_ASSERT(!snapshot.directTextureEnabled); + + evidence.avc444LifecyclePassed = true; + evidence.avc444CompositionSupported = true; + snapshot = EvaluateRdpH264Capabilities(evidence); + RDP_ASSERT(snapshot.avc444Enabled); + RDP_ASSERT(!snapshot.directTextureEnabled); + + evidence.directTextureLifecyclePassed = true; + evidence.crossSurfaceZeroCopyPassed = true; + snapshot = EvaluateRdpH264Capabilities(evidence); + RDP_ASSERT(snapshot.directTextureEnabled); +} diff --git a/entry/src/main/ets/pages/RemoteDesktop.ets b/entry/src/main/ets/pages/RemoteDesktop.ets index 026aff32b..d8d430eeb 100644 --- a/entry/src/main/ets/pages/RemoteDesktop.ets +++ b/entry/src/main/ets/pages/RemoteDesktop.ets @@ -95,6 +95,7 @@ import { } from '../services/RemoteImeCommandQueue'; import { hashForLog, maskHost, maskUser, safeError } from '../utils/SafeLogger'; import { RdpRenderStats, SessionConfig, SessionTransferStatus } from '../types/rdpnapi'; +import { shouldInitializeRemoteVideoDecoder } from '../services/RemoteVideoDecoderPolicy'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { picker } from '@kit.CoreFileKit'; import { fileIo } from '@kit.CoreFileKit'; @@ -2284,7 +2285,11 @@ struct RemoteDesktop { ' inputTextUnits=' + stats.inputTextUnits.toString() + ' inputMouseDrops=' + stats.inputDroppedMouseMoves.toString() + ' inputPriorityOverflow=' + stats.inputNonDisposableOverflow.toString() + - ' gfx=' + stats.graphicsMode); + ' gfx=' + stats.graphicsMode + + ' avc420=' + (stats.h264Avc420Enabled ? 'enabled' : stats.h264Avc420Reason) + + ' avc444=' + (stats.h264Avc444Enabled ? 'enabled' : stats.h264Avc444Reason) + + ' directTexture=' + (stats.h264DirectTextureEnabled ? 'enabled' : stats.h264DirectTextureReason) + + ' h264GainPermille=' + stats.h264PerformanceGainPermille.toString()); if (stats.renderedPaintCount <= 0 || stats.lastRenderResult < 0) { const model: RdpSessionErrorDialogModel = rdpNoFrameWatchdogError(); this.connectionError = model.title; @@ -2374,10 +2379,8 @@ struct RemoteDesktop { ' remote=' + w.toString() + 'x' + h.toString()); } - // 初始化解码器 (非阻塞 — 解码器未就绪不影响连接建立) const rdCodecVal: number = AppStorage.get('rustdeskCodec') ?? 0; - // RustDesk 自动模式不强制 H264;本地解码器按 RustDesk 常见 VP9 起步。 - let decoderCodec: number = host.protocol === 'rustdesk' ? 3 : 0; + let decoderCodec: number = 3; if (rdCodecVal === 1) { decoderCodec = 2; } else if (rdCodecVal === 2) { @@ -2389,14 +2392,20 @@ struct RemoteDesktop { } else if (rdCodecVal === 5) { decoderCodec = 1; } - let dHandle: number = this.loader.initDecoder(w, h, decoderCodec); - hilog.info(RD_DOMAIN, RD_TAG, 'initDecoder returned: ' + dHandle); - if (dHandle > 0) { - this.decoderHandle = dHandle; - const bindOk: boolean = this.loader.bindVideoPipeline(this.decoderHandle, this.rendererHandle); - hilog.info(RD_DOMAIN, RD_TAG, 'bindVideoPipeline returned: ' + bindOk); + if (shouldInitializeRemoteVideoDecoder(host.protocol)) { + // RustDesk delivers encoded VideoFrame packets through the shared decoder pipeline. + const dHandle: number = this.loader.initDecoder(w, h, decoderCodec, this.rendererHandle); + hilog.info(RD_DOMAIN, RD_TAG, 'initDecoder returned: ' + dHandle); + if (dHandle > 0) { + this.decoderHandle = dHandle; + const bindOk: boolean = this.loader.bindVideoPipeline(this.decoderHandle, this.rendererHandle); + hilog.info(RD_DOMAIN, RD_TAG, 'bindVideoPipeline returned: ' + bindOk); + } else { + hilog.warn(RD_DOMAIN, RD_TAG, '解码器未就绪 (rc=' + dHandle + '), 连接将继续, 待后续初始化'); + } } else { - hilog.warn(RD_DOMAIN, RD_TAG, '解码器未就绪 (rc=' + dHandle + '), 连接将继续, 待后续初始化'); + this.decoderHandle = -1; + hilog.info(RD_DOMAIN, RD_TAG, 'shared video decoder skipped for protocol=' + host.protocol); } if (host.protocol === 'rustdesk') { diff --git a/entry/src/main/ets/services/ExtensionLoader.ets b/entry/src/main/ets/services/ExtensionLoader.ets index 7c443fa45..c282c2cdf 100644 --- a/entry/src/main/ets/services/ExtensionLoader.ets +++ b/entry/src/main/ets/services/ExtensionLoader.ets @@ -5,6 +5,7 @@ */ import rdpnapi from 'librdpnapi.so'; import { + Avc420DecoderProbeResult, SessionConfig, SftpFileEntry, GeneratedSshKeyPair, @@ -243,7 +244,14 @@ export class ExtensionLoader { inputTextUnits: 0, inputDroppedMouseMoves: 0, inputNonDisposableOverflow: 0, - graphicsMode: 'unknown' + graphicsMode: 'unknown', + h264Avc420Enabled: false, + h264Avc444Enabled: false, + h264DirectTextureEnabled: false, + h264PerformanceGainPermille: 0, + h264Avc420Reason: 'stats-unavailable', + h264Avc444Reason: 'stats-unavailable', + h264DirectTextureReason: 'stats-unavailable' }; } } @@ -267,9 +275,9 @@ export class ExtensionLoader { this.rendererHandle = -1; } - initDecoder(w: number, h: number, c: number): number { + initDecoder(w: number, h: number, c: number, rendererHandle: number): number { try { - this.decoderHandle = rdpnapi.initDecoder(w, h, c) as number; + this.decoderHandle = rdpnapi.initDecoder(w, h, c, rendererHandle) as number; } catch (err) { hilog.error(DOMAIN, TAG, '[ExtensionLoader] initDecoder: ' + JSON.stringify(err)); this.decoderHandle = -1; @@ -302,6 +310,10 @@ export class ExtensionLoader { } } + probeAvc420Decoder(rendererHandle: number): Avc420DecoderProbeResult { + return rdpnapi.probeAvc420Decoder(rendererHandle) as Avc420DecoderProbeResult; + } + getRendererHandle(): number { return this.rendererHandle; } getDecoderHandle(): number { return this.decoderHandle; } diff --git a/entry/src/main/ets/services/RemoteVideoDecoderPolicy.ets b/entry/src/main/ets/services/RemoteVideoDecoderPolicy.ets new file mode 100644 index 000000000..2cd843abd --- /dev/null +++ b/entry/src/main/ets/services/RemoteVideoDecoderPolicy.ets @@ -0,0 +1,3 @@ +export function shouldInitializeRemoteVideoDecoder(protocol: string): boolean { + return protocol === 'rustdesk'; +} diff --git a/entry/src/main/ets/types/rdpnapi.d.ts b/entry/src/main/ets/types/rdpnapi.d.ts index cd0b162ef..f5df8ae79 100644 --- a/entry/src/main/ets/types/rdpnapi.d.ts +++ b/entry/src/main/ets/types/rdpnapi.d.ts @@ -67,11 +67,12 @@ declare module 'librdpnapi.so' { export function requestFrameRefresh(): void; export function getRendererViewport(handle: number): RendererViewport | null; - export function initDecoder(width: number, height: number, codecType: number): number; + export function initDecoder(width: number, height: number, codecType: number, rendererHandle: number): number; export function destroyDecoder(handle: number): void; export function decodeFrame(handle: number, data: ArrayBuffer, size: number, timestamp: number): number; export function getTextureId(handle: number): number; export function testDecoderH264(handle: number): number; + export function probeAvc420Decoder(rendererHandle: number): Avc420DecoderProbeResult; export function bindVideoPipeline(decoderHandle: number, rendererHandle: number): boolean; export function detachVideoPipeline(decoderHandle: number): boolean; export function requestDecoderRecovery(decoderHandle: number): boolean; @@ -199,6 +200,25 @@ export interface RdpRenderStats { inputDroppedMouseMoves: number; inputNonDisposableOverflow: number; graphicsMode: string; + h264Avc420Enabled: boolean; + h264Avc444Enabled: boolean; + h264DirectTextureEnabled: boolean; + h264PerformanceGainPermille: number; + h264Avc420Reason: string; + h264Avc444Reason: string; + h264DirectTextureReason: string; +} + +export interface Avc420DecoderProbeResult { + status: string; + contextReady: boolean; + outputObserved: boolean; + lifecyclePassed: boolean; + initCode: number; + decodeCode: number; + outputWidth: number; + outputHeight: number; + outputPixelFormat: number; } export interface SessionTransferStatus { diff --git a/entry/src/test/List.test.ets b/entry/src/test/List.test.ets index eb00de77a..43e14ffc9 100644 --- a/entry/src/test/List.test.ets +++ b/entry/src/test/List.test.ets @@ -35,6 +35,7 @@ import settingsLeafSheetLifecyclePolicyTest from './SettingsLeafSheetLifecyclePo import feedbackCommunityPolicyTest from './FeedbackCommunityPolicy.test'; import guideContentRegistryTest from './GuideContentRegistry.test'; import releaseNotesRegistryTest from './ReleaseNotesRegistry.test'; +import remoteVideoDecoderPolicyTest from './RemoteVideoDecoderPolicy.test'; export default function testsuite() { localUnitTest(); @@ -74,4 +75,5 @@ export default function testsuite() { feedbackCommunityPolicyTest(); guideContentRegistryTest(); releaseNotesRegistryTest(); + remoteVideoDecoderPolicyTest(); } diff --git a/entry/src/test/RemoteVideoDecoderPolicy.test.ets b/entry/src/test/RemoteVideoDecoderPolicy.test.ets new file mode 100644 index 000000000..0335c8766 --- /dev/null +++ b/entry/src/test/RemoteVideoDecoderPolicy.test.ets @@ -0,0 +1,19 @@ +import { describe, it, expect } from '@ohos/hypium'; +import { shouldInitializeRemoteVideoDecoder } from '../main/ets/services/RemoteVideoDecoderPolicy'; + +export default function remoteVideoDecoderPolicyTest(): void { + describe('RemoteVideoDecoderPolicy', (): void => { + it('rustdesk_should_create_the_shared_video_decoder', 0, (): void => { + expect(shouldInitializeRemoteVideoDecoder('rustdesk')).assertTrue(); + }); + + it('rdp_should_use_its_own_gdi_or_rdpgfx_pipeline', 0, (): void => { + expect(shouldInitializeRemoteVideoDecoder('rdp')).assertFalse(); + }); + + it('non_video_protocols_should_not_create_a_decoder', 0, (): void => { + expect(shouldInitializeRemoteVideoDecoder('ssh')).assertFalse(); + expect(shouldInitializeRemoteVideoDecoder('vnc')).assertFalse(); + }); + }); +}