Ctrl K
ComunidadGuías…

server/src/rtsp.h

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * @file src/rtsp.h
 * @brief Declarations for RTSP streaming.
 */
#pragma once

// standard includes
#include "config.h"
#include "framegen_policy.h"
#include "hdr_stream_policy.h"
#include "remote_session.h"

#include <array>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <future>
#include <list>
#include <map>
#include <memory>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
// local includes
#include "crypto.h"
#include "thread_safe.h"

#ifdef _WIN32
  #include <winsock2.h>
  #include <windows.h>
#endif

// Resolve circular dependencies
namespace stream {
  struct session_t;
}

namespace rtsp_stream {
  constexpr auto RTSP_SETUP_PORT = 21;

  struct launch_session_t {
    // Pending Linux launches must keep the display awake before capture and
    // until the active capture has acquired its own reference. Deliberately
    // omitted from retained app/display-recovery snapshots.
    std::shared_ptr<void> display_power_guard;
    struct resolution_override_t {
      int width;
      int height;
    };

    uint32_t id;
    remote_session::role_e role {remote_session::role_e::game};
    std::uint64_t role_generation {};
    // The exact topology-owned capture target for Remote Monitor. Empty means
    // no target was verified and must never fall back to a physical display.
    std::optional<std::string> remote_capture_output;
    std::string rtsp_source_address;

    crypto::aes_t gcm_key;
    crypto::aes_t iv;

    std::string av_ping_payload;
    uint32_t control_connect_data;

    std::string device_name;
    std::string unique_id;
    std::string client_uuid;
    std::string client_name;
    std::optional<std::string> hdr_profile;
    crypto::PERM perm;
    int appid;

    bool input_only;
    bool host_audio;
    int width;
    int height;
    int fps;
    int gcmap;
    int playstation_gamepad_mask {};

    struct app_metadata_t {
      std::string id;
      std::string name;
      bool virtual_screen;
      bool has_command;
      bool has_playnite;
      bool playnite_fullscreen;
    };

    std::optional<app_metadata_t> app_metadata;
    int surround_info;
    std::string surround_params;
    bool continuous_audio;
    // The client's `hdrMode=1` launch flag. Every Moonlight client derives this bit from
    // `supportedVideoFormats & VIDEO_FORMAT_MASK_10BIT`, so it is also the only wire signal
    // telling us the client negotiated a 10-bit decoder profile.
    bool enable_hdr;
    // Per-client opt-in: stream an HDR-requesting client as 10-bit SDR instead. The display
    // stays out of HDR and the encode stays Main10, which the client can decode because it
    // asked for HDR in the first place.
    bool prefer_sdr_10bit = false;
    // Explicit HDR-off override. Unlike prefer_sdr_10bit, this does not request Main10.
    bool force_sdr = false;
    bool enable_sops;
    // Moonlight's VRR request passed its launch-time VSync, display-refresh,
    // and adaptive-headroom checks. The renderer may still fall back later.
    bool client_vrr_requested = false;
    bool client_display_mode_override;
    // Exact refresh requested by a per-client display-mode override, expressed
    // in millihertz. The existing fps field remains the legacy stream cadence.
    std::uint32_t client_display_refresh_millihz = 0;
    bool client_requests_virtual_display;
    // A transport joining another client's running game cannot change host-wide stream settings.
    bool secondary_game_client = false;
    // Present only when the client explicitly selected a virtual or physical display.
    std::optional<bool> client_virtual_display_override;
    bool virtual_display;
    uint32_t scale_factor = 100;
    bool normal_vdd_capacity_rejected = false;
    bool normal_vdd_identity_newly_reserved = false;
    std::uint64_t normal_vdd_identity_token = 0;
    // Linux resumes retain the running app's display owner across TLS clients.
    // client_uuid remains the authenticated transport identity.
    std::string normal_vdd_owner_uuid;
    // Host/display resolution derived from a launch-time client override. The RTSP
    // negotiated viewport remains in width/height.
    std::optional<resolution_override_t> resolution_override;
    bool virtual_display_failed;
    bool virtual_display_detach_with_app;
    // True only after the NVHTTP display-preparation path has resolved the
    // effective virtual/physical output request for this launch.
    bool virtual_display_request_resolved = false;
    std::optional<config::video_t::virtual_display_mode_e> virtual_display_mode_override;
    std::optional<config::video_t::virtual_display_layout_e> virtual_display_layout_override;
    std::optional<config::video_t::dd_t::config_option_e> dd_config_option_override;
    std::optional<std::string> output_name_override;
    bool display_config_preapplied = false;
    std::array<std::uint8_t, 16> virtual_display_guid_bytes {};
    std::string virtual_display_device_id;
    std::optional<std::chrono::steady_clock::time_point> virtual_display_ready_since;
    std::optional<bool> virtual_display_hdr_enabled;
    bool virtual_display_recreated_on_demand = false;
    bool virtual_display_needs_resume_apply = false;
    std::optional<std::vector<std::vector<std::string>>> virtual_display_topology_snapshot;

    /// @brief Pre-virtual-display device refresh rates captured before VD creation.
    /// Maps device_id to {numerator, denominator} of the original refresh rate.
    std::optional<std::map<std::string, std::pair<unsigned int, unsigned int>>> pre_virtual_display_refresh_rates;
    bool gen1_framegen_fix;
    bool gen2_framegen_fix;
    bool frame_generation_enabled = false;
    bool lossless_scaling_framegen;
    std::optional<int> framegen_refresh_rate;
    std::optional<std::uint32_t> framegen_refresh_millihz;
    int framegen_refresh_multiplier = 1;
    /// @brief framegen_refresh_millihz is the virtual display's own rate (VRR mode), not a stream multiple.
    bool framegen_fixed_refresh = false;
    std::string frame_generation_provider;
    std::optional<double> lossless_scaling_target_fps;
    std::optional<int> lossless_scaling_rtss_limit;

    std::optional<crypto::cipher::gcm_t> rtsp_cipher;
    std::string rtsp_url_scheme;
    uint32_t rtsp_iv_counter;

    std::list<crypto::command_entry_t> client_do_cmds;
    std::list<crypto::command_entry_t> client_undo_cmds;

    std::string client_cert;

#ifdef _WIN32
    enum class display_helper_gate_status_e : uint8_t {
      proceed,  ///< Verified/ready (or no-op)
      proceed_gaveup,  ///< Unknown/unavailable/timeout
      abort_failed  ///< Verified failure (capture proceeds anyway; logged)
    };

    /// Soft gate: capture start waits (bounded) for the display helper's apply
    /// verification so the first frames aren't grabbed mid-modeset.
    std::shared_future<display_helper_gate_status_e> display_helper_gate;
#endif

    /**
     * @brief Build an isolated copy for the background RTSP startup worker.
     *
     * stream::session::alloc()/start() run on the startup thread while the io_context
     * thread still owns and reuses the original launch session (reservation,
     * respond() cipher/IV, expiry). The worker must therefore operate on this clone
     * rather than the live original. launch_session_t is non-copyable (the move-only
     * rtsp_cipher), so the fields are copied explicitly.
     *
     * Contract: copy every field that stream::session::alloc() or the cmd_announce()
     * startup lambda reads. If you add such a field, copy it here too -- the
     * RtspStartupSnapshot tests guard this (a dropped `perm` caused ArtLight Server #280,
     * where streaming sessions ran at PERM::_no and silently discarded all input).
     */
    [[nodiscard]] std::shared_ptr<launch_session_t> clone_for_startup() const;
  };

  struct client_disconnect_result_t {
    bool disconnected {};
    std::vector<remote_session::role_e> pending_roles;
    std::vector<std::uint64_t> pending_generations;
  };

  /**
   * @brief Whether the session should put the display (and the stream) into HDR.
   *
   * `prefer_sdr_10bit` deliberately suppresses this: the whole point of that opt-in is to
   * take an HDR request and serve it as 10-bit SDR, so the display must never be switched
   * to HDR for such a session.
   */
  inline bool effective_hdr_requested(
    const bool client_hdr_requested,
    const bool prefer_sdr_10bit,
    const bool force_sdr
  ) {
    return client_hdr_requested && !prefer_sdr_10bit && !force_sdr;
  }

  inline bool effective_hdr_requested(const launch_session_t &session) {
    return effective_hdr_requested(session.enable_hdr, session.prefer_sdr_10bit, session.force_sdr);
  }

  /**
   * @brief Whether the current app/client runtime policy enables RTX HDR.
   */
  inline bool rtx_hdr_enabled(const config::video_t &video_config) {
    return config::runtime_config_override_enabled("rtx_hdr") &&
           video_config.rtx_hdr.enabled;
  }

  /**
   * @brief Whether RTX HDR conversion is active for this launch session.
   *
   * RTX HDR is app/client opt-in and requires an HDR stream, but its source content must
   * remain SDR so TrueHDR performs the only SDR-to-HDR conversion.
   */
  inline bool rtx_hdr_conversion_requested(const launch_session_t &session, const config::video_t &video_config) {
    return effective_hdr_requested(session) &&
           rtx_hdr_enabled(video_config);
  }

  /**
   * @brief Whether the *source* display must be HDR for this session.
   *
   * Different question from whether the stream is HDR. Synthesized HDR (RTX HDR) converts an
   * SDR source on the GPU, so asking the display coordinator for HDR in that case would hand
   * the capture path a 10-bit HDR scanout it cannot import. Native HDR still needs an HDR
   * source, because there is nothing to synthesize from.
   */
  inline bool hdr_capture_required(const launch_session_t &session, const config::video_t &video_config) {
    return video::hdr_stream_policy::requires_hdr_source(effective_hdr_requested(session), rtx_hdr_enabled(video_config));
  }

  /**
   * @brief Whether the session should be encoded as Main10 with an SDR colorspace.
   *
   * Requires the client to have requested HDR, because that flag is also the client's only
   * statement that it negotiated a 10-bit decoder. Promoting a client that asked for SDR
   * would hand a Main10 bitstream to a decoder configured for an 8-bit profile.
   */
  inline bool effective_10bit_sdr_requested(
    const bool prefer_sdr_10bit,
    const bool client_hdr_requested,
    const bool force_sdr
  ) {
    return prefer_sdr_10bit && client_hdr_requested && !force_sdr;
  }

  inline bool effective_10bit_sdr_requested(const launch_session_t &session) {
    return effective_10bit_sdr_requested(session.prefer_sdr_10bit, session.enable_hdr, session.force_sdr);
  }

  inline bool framegen_capture_fix_enabled(const launch_session_t &session) {
    return session.gen1_framegen_fix || session.gen2_framegen_fix;
  }

  inline int framegen_refresh_multiplier(const launch_session_t &session) {
    const bool has_integer_rate = session.framegen_refresh_rate && *session.framegen_refresh_rate > 0;
    const bool has_exact_rate = session.framegen_refresh_millihz && *session.framegen_refresh_millihz > 0;
    if (!has_integer_rate && !has_exact_rate) {
      return 1;
    }
    return session.framegen_refresh_multiplier > 1 ? session.framegen_refresh_multiplier : 1;
  }

  inline std::uint32_t effective_display_refresh_millihz(const launch_session_t &session) {
    if (session.framegen_refresh_millihz && *session.framegen_refresh_millihz > 0) {
      return *session.framegen_refresh_millihz;
    }
    if (session.client_display_refresh_millihz > 0) {
      return session.client_display_refresh_millihz;
    }
    if (session.framegen_refresh_rate && *session.framegen_refresh_rate > 0) {
      return framegen::normalize_refresh_millihz(*session.framegen_refresh_rate);
    }
    return framegen::normalize_refresh_millihz(session.fps);
  }

  inline int saturating_refresh_fps(int fps, int multiplier) {
    return framegen::saturating_refresh_fps(fps, multiplier);
  }

  inline framegen::stream_start_policy_t make_framegen_stream_start_policy(
    const launch_session_t &session,
    std::optional<int> lossless_rtss_limit,
    std::string_view capture_mode,
    bool auto_capture_uses_wgc,
    bool auto_virtual_framegen_limiter,
    int virtual_display_refresh_multiplier,
    std::uint32_t virtual_display_fixed_refresh_millihz = 0
  ) {
    return framegen::make_stream_start_policy({
      .fps = session.fps,
      .fps_scaled = session.fps,
      .display_refresh_millihz = session.client_display_refresh_millihz,
      .frame_generation_enabled = session.frame_generation_enabled,
      .gen1_framegen_fix = session.gen1_framegen_fix,
      .gen2_framegen_fix = session.gen2_framegen_fix,
      .lossless_scaling_framegen = session.lossless_scaling_framegen,
      .lossless_rtss_limit = lossless_rtss_limit,
      .frame_generation_provider = session.frame_generation_provider,
      .uses_virtual_display = session.virtual_display,
      .capture_mode = std::string(capture_mode),
      .auto_capture_uses_wgc = auto_capture_uses_wgc,
      .auto_virtual_framegen_limiter = auto_virtual_framegen_limiter,
      .virtual_display_refresh_multiplier = virtual_display_refresh_multiplier,
      .virtual_display_fixed_refresh_millihz = virtual_display_fixed_refresh_millihz,
    });
  }

  // Returns false when the per-launch admission registry rejects the request.
  // Encrypted launches are independent; plaintext remains one pending launch
  // per source address because it has no cryptographic routing identity.
  bool launch_session_raise(std::shared_ptr<launch_session_t> launch_session);

  // Surface the actual plaintext admission warning in the topology UI without
  // giving that UI a second, divergent notion of RTSP routing state.
  std::string plaintext_route_warning();
  bool disconnect_game_sessions(bool lifecycle_lock_held = false);
  bool disconnect_remote_role_session(std::string_view client_uuid, remote_session::role_e role, std::uint64_t generation, bool lifecycle_lock_held = false);

  /**
   * @brief Clear state for the specified launch session.
   * @param launch_session_id The ID of the session to clear.
   */
  void launch_session_clear(uint32_t launch_session_id);
  /**
   * @brief Whether an RTSP launch or asynchronous session startup is pending.
   */
  bool has_pending_launch_or_startup();

  /**
   * @brief Publish whether an HDR stream launch is pending before the RTSP session is active.
   *
   * Vulkan applications can query swapchain formats immediately at process startup. The Vulkan HDR
   * implicit layer therefore needs HDR stream intent before the launched app reaches the RTSP
   * ANNOUNCE path that creates the active streaming session.
   */
  void set_vulkan_hdr_layer_pending_stream(bool active);

  /**
   * @brief Get the number of active sessions.
   * @return Count of active sessions.
   */
  int session_count();

  /**
   * @brief Get the number of active sessions without reaping stopped sessions.
   *
   * This is intended for observers that must not enter session teardown while
   * they are making a scheduling decision of their own.
   */
  int session_count_no_cleanup();

  std::shared_ptr<stream::session_t>
    find_session(const std::string_view &uuid);

  std::list<std::string>
    get_all_session_uuids();

  std::vector<std::shared_ptr<stream::session_t>>
    get_sessions_snapshot();

  /**
   * @brief Terminates all running streaming sessions.
   */
  void terminate_sessions(bool preserve_pending_launch = false);
  void terminate_sessions_by_cert(std::string_view cert);

  /**
   * @brief Get the client UUIDs for all active sessions.
   */
  std::list<std::string> get_all_session_client_uuids();

  /**
   * @brief The client of every live session that is holding imported USB devices.
   *
   * The quit combo uses this to work out whose stream to end: the client whose keyboard was
   * taken away is the one somebody is sitting at. Empty means nothing is shared with this host,
   * which is the ordinary case when the combo is handled on the client.
   */
  std::list<std::string> clients_holding_usbip_devices();

  /**
   * @brief Stop any active sessions for a given client UUID.
   * @return True if one or more sessions were stopped.
   */
  bool disconnect_client_sessions(const std::string &client_uuid);
  client_disconnect_result_t disconnect_client_sessions_with_result(const std::string &client_uuid);

  /**
   * @brief Runs the RTSP server loop.
   */
  void start();
}  // namespace rtsp_stream