Implement Cast Streaming mirroring, DLNA casting, daemon+GUI, and breadd integration
Some checks failed
dev release / build (push) Failing after 12s
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.
This commit is contained in:
parent
887c29002f
commit
8c745d18e0
283 changed files with 36788 additions and 0 deletions
252
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/sender_impl.h
vendored
Normal file
252
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/sender_impl.h
vendored
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
// 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_
|
||||
Loading…
Add table
Add a link
Reference in a new issue