Some checks failed
dev release / build (push) Failing after 12s
Builds out the full v1 scope: a vendored+patched openscreen subset for low-latency Cast Streaming (Mirroring receiver 0F5096E8) alongside the existing Cast V2/HLS and new DLNA/AVTransport casting paths, breadcastd's Idle/Casting state machine with a private IPC socket, the breadcast GTK4 popup as a thin IPC client, and bread.cast.*/bread.command.cast.* breadd integration (device discovery, start/stop, mirroring lifecycle events). Also adds bakery/systemd/Forgejo CI packaging. Validated end-to-end against a real Chromecast/Google TV: negotiated Cast Streaming session, live pipeline playback, and daemon+GUI click-to-cast/ stop through the actual popup.
252 lines
11 KiB
C++
252 lines
11 KiB
C++
// Copyright 2026 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#ifndef CAST_STREAMING_IMPL_SENDER_IMPL_H_
|
|
#define CAST_STREAMING_IMPL_SENDER_IMPL_H_
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
#include "cast/streaming/impl/compound_rtcp_parser.h"
|
|
#include "cast/streaming/impl/frame_crypto.h"
|
|
#include "cast/streaming/impl/rtcp_common.h"
|
|
#include "cast/streaming/impl/rtp_defines.h"
|
|
#include "cast/streaming/impl/rtp_packetizer.h"
|
|
#include "cast/streaming/impl/sender_report_builder.h"
|
|
#include "cast/streaming/impl/statistics_dispatcher.h"
|
|
#include "cast/streaming/public/constants.h"
|
|
#include "cast/streaming/public/frame_id.h"
|
|
#include "cast/streaming/public/sender.h"
|
|
#include "cast/streaming/public/session_config.h"
|
|
#include "cast/streaming/rtp_time.h"
|
|
#include "cast/streaming/sender_packet_router.h"
|
|
#include "platform/api/time.h"
|
|
#include "platform/base/span.h"
|
|
#include "util/bit_vector.h"
|
|
#include "util/raw_ptr.h"
|
|
#include "util/raw_ref.h"
|
|
|
|
namespace openscreen::cast {
|
|
|
|
class Environment;
|
|
|
|
// The Cast Streaming Sender, a peer corresponding to some Cast Streaming
|
|
// Receiver at the other end of a network link. See class level comments for
|
|
// Receiver for a high-level overview.
|
|
class SenderImpl final : public Sender,
|
|
public SenderPacketRouter::Sender,
|
|
public CompoundRtcpParser::Client {
|
|
public:
|
|
// Constructs a Sender that attaches to the given `environment`-provided
|
|
// resources and `packet_router`. The `config` contains the settings that were
|
|
// agreed-upon by both sides from the OFFER/ANSWER exchange (i.e., the part of
|
|
// the overall end-to-end connection process that occurs before Cast Streaming
|
|
// is started). The `rtp_payload_type` does not affect the behavior of this
|
|
// Sender. It is simply passed along to a Receiver in the RTP packet stream.
|
|
SenderImpl(Environment& environment,
|
|
SenderPacketRouter& packet_router,
|
|
SessionConfig config,
|
|
RtpPayloadType rtp_payload_type);
|
|
|
|
~SenderImpl() final;
|
|
|
|
// Sender overrides.
|
|
const SessionConfig& config() const override { return config_; }
|
|
void SetObserver(Observer* observer) override;
|
|
size_t GetInFlightFrameCount() const override;
|
|
Clock::duration GetInFlightMediaDuration(
|
|
RtpTimeTicks next_frame_rtp_timestamp) const override;
|
|
Clock::duration GetMaxInFlightMediaDuration() const override;
|
|
bool NeedsKeyFrame() const override;
|
|
FrameId GetNextFrameId() const override;
|
|
Clock::duration GetCurrentRoundTripTime() const override;
|
|
[[nodiscard]] EnqueueFrameResult EnqueueFrame(
|
|
const EncodedFrame& frame) override;
|
|
void CancelInFlightData() override;
|
|
void ReportFrameDropEvent(FrameId frame_id,
|
|
RtpTimeTicks rtp_timestamp,
|
|
Clock::time_point drop_time) override;
|
|
|
|
// Smooths a new round-trip-time `measurement` into the running `estimate`
|
|
// using an asymmetric "fast attack, slow decay" filter: the estimate climbs
|
|
// quickly on an upward spike (so the Sender notices congestion onset) but
|
|
// decays slowly (so a single low sample does not collapse it). A zero
|
|
// `estimate` adopts the measurement directly. Static and exposed for testing.
|
|
static Clock::duration SmoothRoundTripTime(Clock::duration estimate,
|
|
Clock::duration measurement);
|
|
|
|
private:
|
|
// Tracking/Storage for frames that are ready-to-send, and until they are
|
|
// fully received at the other end.
|
|
struct PendingFrameSlot {
|
|
// The frame to send, or nullopt if this slot is not in use.
|
|
std::optional<EncryptedFrame> frame;
|
|
|
|
// Represents which packets need to be sent. Elements are indexed by
|
|
// FramePacketId. A set bit means a packet needs to be sent (or re-sent).
|
|
BitVector send_flags;
|
|
|
|
// The time when each of the packets was last sent, or
|
|
// `SenderPacketRouter::kNever` if the packet has not been sent yet.
|
|
// Elements are indexed by FramePacketId. This is used to avoid
|
|
// re-transmitting any given packet too frequently.
|
|
std::vector<Clock::time_point> packet_sent_times;
|
|
|
|
PendingFrameSlot();
|
|
~PendingFrameSlot();
|
|
|
|
bool is_active_for_frame(FrameId frame_id) const {
|
|
return frame && frame->frame_id == frame_id;
|
|
}
|
|
};
|
|
|
|
// Return value from the ChooseXYZ() helper methods.
|
|
struct ChosenPacket {
|
|
raw_ptr<PendingFrameSlot> slot = nullptr;
|
|
FramePacketId packet_id{};
|
|
|
|
explicit operator bool() const { return !!slot; }
|
|
};
|
|
|
|
// An extension of ChosenPacket that also includes the point-in-time when the
|
|
// packet should be sent.
|
|
struct ChosenPacketAndWhen : public ChosenPacket {
|
|
Clock::time_point when = SenderPacketRouter::kNever;
|
|
};
|
|
|
|
// SenderPacketRouter::Sender implementation.
|
|
void OnReceivedRtcpPacket(Clock::time_point arrival_time,
|
|
ByteView packet) final;
|
|
ByteBuffer GetRtcpPacketForImmediateSend(Clock::time_point send_time,
|
|
ByteBuffer buffer) final;
|
|
ByteBuffer GetRtpPacketForImmediateSend(Clock::time_point send_time,
|
|
ByteBuffer buffer) final;
|
|
Clock::time_point GetRtpResumeTime() final;
|
|
RtpTimeTicks GetLastRtpTimestamp() const final;
|
|
StreamType GetStreamType() const final;
|
|
|
|
// CompoundRtcpParser::Client implementation.
|
|
void OnReceiverReferenceTimeAdvanced(Clock::time_point reference_time) final;
|
|
void OnReceiverReport(const RtcpReportBlock& receiver_report) final;
|
|
void OnCastReceiverFrameLogMessages(
|
|
std::vector<RtcpReceiverFrameLogMessage> messages) final;
|
|
void OnReceiverIndicatesPictureLoss() final;
|
|
void OnReceiverCheckpoint(FrameId frame_id,
|
|
std::chrono::milliseconds playout_delay) final;
|
|
void OnReceiverHasFrames(std::vector<FrameId> acks) final;
|
|
void OnReceiverIsMissingPackets(std::vector<PacketNack> nacks) final;
|
|
|
|
// Helper to choose which packet to send, from those that have been flagged as
|
|
// "need to send." Returns a "false" result if nothing needs to be sent.
|
|
ChosenPacket ChooseNextRtpPacketNeedingSend();
|
|
|
|
// Helper that returns the packet that should be used to kick-start the
|
|
// Receiver, and the time at which the packet should be sent. Returns a kNever
|
|
// result if kick-starting is not needed.
|
|
ChosenPacketAndWhen ChooseKickstartPacket();
|
|
|
|
// Cancels sending (or resending) the given frame once it is known to have
|
|
// been either:
|
|
// 1. Cancelled by the sender (was_acked must be false);
|
|
// 2. Fully received based on the ACK feedback in a receiver RTCP report
|
|
// (was_acked must be true);
|
|
// 3. The receiver sent a checkpoint frame ID (was_acked must be true).
|
|
//
|
|
// This clears the corresponding entry in `pending_frames_` and
|
|
// adds `frame_id` to the list of pending cancellations to be dispatched as
|
|
// part of DispatchCancellations().
|
|
//
|
|
// NOTE: Every frame_id ends up being "cancelled" at least once.
|
|
void CancelPendingFrame(FrameId frame_id, bool was_acked);
|
|
|
|
// Must be called after one or a series of CancelPendingFrame() calls in order
|
|
// to notify the observer, if any, about cancellations.
|
|
void DispatchCancellations();
|
|
|
|
// Inline helper to return the slot that would contain the tracking info for
|
|
// the given `frame_id`.
|
|
const PendingFrameSlot& get_slot_for(FrameId frame_id) const {
|
|
return pending_frames_[(frame_id - FrameId::first()) %
|
|
pending_frames_.size()];
|
|
}
|
|
PendingFrameSlot& get_slot_for(FrameId frame_id) {
|
|
return pending_frames_[(frame_id - FrameId::first()) %
|
|
pending_frames_.size()];
|
|
}
|
|
|
|
const SessionConfig config_;
|
|
const raw_ref<SenderPacketRouter> packet_router_;
|
|
RtcpSession rtcp_session_;
|
|
CompoundRtcpParser rtcp_parser_;
|
|
SenderReportBuilder sender_report_builder_;
|
|
RtpPacketizer rtp_packetizer_;
|
|
const int rtp_timebase_;
|
|
FrameCrypto crypto_;
|
|
StatisticsDispatcher statistics_dispatcher_;
|
|
|
|
// Ring buffer of PendingFrameSlots. The frame having FrameId x will always
|
|
// be slotted at position x % pending_frames_.size(). Use get_slot_for() to
|
|
// access the correct slot for a given FrameId.
|
|
std::array<PendingFrameSlot, kMaxUnackedFrames> pending_frames_ = {};
|
|
|
|
// A count of the number of frames in-flight (i.e., the number of active
|
|
// entries in `pending_frames_`).
|
|
size_t num_frames_in_flight_ = 0;
|
|
|
|
// The ID of the last frame enqueued.
|
|
FrameId last_enqueued_frame_id_ = FrameId::leader();
|
|
|
|
// Indicates that all of the packets for all frames up to and including this
|
|
// FrameId have been successfully received (or otherwise do not need to be
|
|
// re-transmitted).
|
|
FrameId checkpoint_frame_id_ = FrameId::leader();
|
|
|
|
// The ID of the latest frame the Receiver seems to be aware of.
|
|
FrameId latest_expected_frame_id_ = FrameId::leader();
|
|
|
|
// The target playout delay for the last-enqueued frame. This is auto-updated
|
|
// when a frame is enqueued that changes the delay.
|
|
std::chrono::milliseconds target_playout_delay_;
|
|
FrameId playout_delay_change_at_frame_id_ = FrameId::first();
|
|
|
|
// The exact arrival time of the last RTCP packet.
|
|
Clock::time_point rtcp_packet_arrival_time_ = SenderPacketRouter::kNever;
|
|
|
|
// The near-term average round trip time. This is updated with each Sender
|
|
// Report → Receiver Report round trip. This is initially zero, indicating the
|
|
// round trip time has not been measured yet.
|
|
Clock::duration round_trip_time_ = {};
|
|
|
|
// Maintain current stats in a Sender Report that is ready for sending at any
|
|
// time. This includes up-to-date lip-sync information, and packet and byte
|
|
// count stats.
|
|
RtcpSenderReport pending_sender_report_;
|
|
|
|
// These are used to determine whether a key frame needs to be sent to the
|
|
// Receiver. When the Receiver provides a picture loss notification, the
|
|
// current checkpoint frame ID is stored in `picture_lost_at_frame_id_`. Then,
|
|
// while `last_enqueued_key_frame_id_` is less than or equal to
|
|
// `picture_lost_at_frame_id_`, the Sender knows it still needs to send a key
|
|
// frame to resolve the picture loss condition. In all other cases, the
|
|
// Receiver is either in a good state or is in the process of receiving the
|
|
// key frame that will make that happen.
|
|
FrameId picture_lost_at_frame_id_ = FrameId::leader();
|
|
FrameId last_enqueued_key_frame_id_ = FrameId::leader();
|
|
|
|
// The current observer (optional).
|
|
raw_ptr<Observer> observer_ = nullptr;
|
|
|
|
// Because the observer may take action when frames are cancelled, such as
|
|
// calling APIs like EnqueueFrame(), `this` must be in a good state before
|
|
// the observer is notified of any pending frame cancellations.
|
|
std::vector<FrameId> pending_cancellations_;
|
|
};
|
|
|
|
} // namespace openscreen::cast
|
|
|
|
#endif // CAST_STREAMING_IMPL_SENDER_IMPL_H_
|