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
52
breadcast-caststream-sys/vendor/openscreen/cast/common/public/message_port.h
vendored
Normal file
52
breadcast-caststream-sys/vendor/openscreen/cast/common/public/message_port.h
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2019 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_COMMON_PUBLIC_MESSAGE_PORT_H_
|
||||
#define CAST_COMMON_PUBLIC_MESSAGE_PORT_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform/base/error.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// This interface is intended to provide an abstraction for communicating
|
||||
// cast messages across a pipe with guaranteed delivery. This is used to
|
||||
// decouple the cast streaming receiver and sender sessions from the
|
||||
// network implementation.
|
||||
class MessagePort {
|
||||
public:
|
||||
class Client {
|
||||
public:
|
||||
// Called whenever a message arrives on the message port.
|
||||
virtual void OnMessage(const std::string& source_id,
|
||||
const std::string& message_namespace,
|
||||
const std::string& message) = 0;
|
||||
|
||||
// Called whenever an error occurs on the message port.
|
||||
virtual void OnError(const Error& error) = 0;
|
||||
|
||||
// Clients should expose a unique identifier used as the "source" of
|
||||
// all messages sent on this message port.
|
||||
virtual const std::string& source_id() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Client() = default;
|
||||
};
|
||||
|
||||
virtual ~MessagePort() = default;
|
||||
|
||||
// Set or reset the `MessagePort::Client` for this instance.
|
||||
virtual void SetClient(Client& client) = 0;
|
||||
virtual void ResetClient() = 0;
|
||||
|
||||
// Sends a message to a given `destination_id`.
|
||||
virtual void PostMessage(const std::string& destination_id,
|
||||
const std::string& message_namespace,
|
||||
const std::string& message) = 0;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_COMMON_PUBLIC_MESSAGE_PORT_H_
|
||||
81
breadcast-caststream-sys/vendor/openscreen/cast/streaming/capture_configs.h
vendored
Normal file
81
breadcast-caststream-sys/vendor/openscreen/cast/streaming/capture_configs.h
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2020 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_CAPTURE_CONFIGS_H_
|
||||
#define CAST_STREAMING_CAPTURE_CONFIGS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "cast/streaming/resolution.h"
|
||||
#include "util/simple_fraction.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// A configuration set that can be used by the sender to capture audio, and the
|
||||
// receiver to playback audio. Used by Cast Streaming to provide an offer to the
|
||||
// receiver.
|
||||
struct AudioCaptureConfig {
|
||||
// Audio codec represented by this configuration.
|
||||
AudioCodec codec = AudioCodec::kOpus;
|
||||
|
||||
// Number of channels used by this configuration.
|
||||
int channels = kDefaultAudioChannels;
|
||||
|
||||
// Average bit rate in bits per second used by this configuration. A value
|
||||
// of "zero" suggests that the bitrate should be automatically selected by
|
||||
// the sender.
|
||||
int bit_rate = 0;
|
||||
|
||||
// Sample rate for audio RTP timebase.
|
||||
int sample_rate = kDefaultAudioSampleRate;
|
||||
|
||||
// Target playout delay in milliseconds.
|
||||
std::chrono::milliseconds target_playout_delay = kDefaultTargetPlayoutDelay;
|
||||
|
||||
// The codec parameter for this configuration. Honors the format laid out
|
||||
// in RFC 6381: https://datatracker.ietf.org/doc/html/rfc6381
|
||||
// NOTE: the "profiles" parameter is not supported in our implementation.
|
||||
std::string codec_parameter;
|
||||
};
|
||||
|
||||
// A configuration set that can be used by the sender to capture video, as
|
||||
// well as the receiver to playback video. Used by Cast Streaming to provide an
|
||||
// offer to the receiver.
|
||||
struct VideoCaptureConfig {
|
||||
// Video codec represented by this configuration.
|
||||
VideoCodec codec = VideoCodec::kVp8;
|
||||
|
||||
// Maximum frame rate in frames per second.
|
||||
// For simple cases, the frame rate may be provided by simply setting the
|
||||
// number to the desired value, e.g. 30 or 60FPS. Some common frame rates like
|
||||
// 23.98 FPS (for NTSC compatibility) are represented as fractions, in this
|
||||
// case 24000/1001.
|
||||
SimpleFraction max_frame_rate{kDefaultFrameRate, 1};
|
||||
|
||||
// Number specifying the maximum bit rate for this stream. A value of
|
||||
// zero means that the maximum bit rate should be automatically selected by
|
||||
// the sender.
|
||||
int max_bit_rate = 0;
|
||||
|
||||
// Resolutions to be offered to the receiver. At least one resolution
|
||||
// must be provided.
|
||||
std::vector<Resolution> resolutions;
|
||||
|
||||
// Target playout delay in milliseconds.
|
||||
std::chrono::milliseconds target_playout_delay = kDefaultTargetPlayoutDelay;
|
||||
|
||||
// The codec parameter for this configuration. Honors the format laid out
|
||||
// in RFC 6381: https://datatracker.ietf.org/doc/html/rfc6381.
|
||||
// VP8 and VP9 codec parameter versions are defined here:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter#webm
|
||||
// https://www.webmproject.org/vp9/mp4/#codecs-parameter-string
|
||||
// NOTE: the "profiles" parameter is not supported in our implementation.
|
||||
std::string codec_parameter;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_CAPTURE_CONFIGS_H_
|
||||
155
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/bandwidth_estimator.cc
vendored
Normal file
155
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/bandwidth_estimator.cc
vendored
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/bandwidth_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/saturate_cast.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
using clock_operators::operator<<;
|
||||
|
||||
namespace {
|
||||
|
||||
// Converts units from `bytes` per `time_window` number of Clock ticks into
|
||||
// bits-per-second.
|
||||
int ToClampedBitsPerSecond(int32_t bytes, Clock::duration time_window) {
|
||||
OSP_CHECK_GT(time_window, Clock::duration::zero());
|
||||
|
||||
// Divide `bytes` by `time_window` and scale the units to bits per second.
|
||||
constexpr int64_t kBitsPerByte = 8;
|
||||
constexpr int64_t kClockTicksPerSecond =
|
||||
Clock::to_duration(std::chrono::seconds(1)).count();
|
||||
const int64_t bits = bytes * kBitsPerByte;
|
||||
const int64_t bits_per_second =
|
||||
(bits * kClockTicksPerSecond) / time_window.count();
|
||||
return saturate_cast<int>(bits_per_second);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BandwidthEstimator::BandwidthEstimator(int max_packets_per_timeslice,
|
||||
Clock::duration timeslice_duration,
|
||||
Clock::time_point start_time)
|
||||
: max_packets_per_history_window_(max_packets_per_timeslice *
|
||||
kNumTimeslices),
|
||||
history_window_(timeslice_duration * kNumTimeslices),
|
||||
burst_history_(timeslice_duration, start_time),
|
||||
feedback_history_(timeslice_duration, start_time) {
|
||||
OSP_CHECK_GT(max_packets_per_timeslice, 0);
|
||||
OSP_CHECK_GT(timeslice_duration, Clock::duration::zero());
|
||||
}
|
||||
|
||||
BandwidthEstimator::~BandwidthEstimator() = default;
|
||||
|
||||
void BandwidthEstimator::OnBurstComplete(int num_packets_sent,
|
||||
Clock::time_point when) {
|
||||
OSP_CHECK_GE(num_packets_sent, 0);
|
||||
burst_history_.Accumulate(num_packets_sent, when);
|
||||
}
|
||||
|
||||
void BandwidthEstimator::OnRtcpReceived(
|
||||
Clock::time_point arrival_time,
|
||||
Clock::duration estimated_round_trip_time) {
|
||||
OSP_CHECK_GE(estimated_round_trip_time, Clock::duration::zero());
|
||||
// Move forward the feedback history tracking timeline to include the latest
|
||||
// moment a packet could have left the Sender.
|
||||
feedback_history_.AdvanceToIncludeTime(arrival_time -
|
||||
estimated_round_trip_time);
|
||||
}
|
||||
|
||||
void BandwidthEstimator::OnPayloadReceived(
|
||||
int payload_bytes_acknowledged,
|
||||
Clock::time_point ack_arrival_time,
|
||||
Clock::duration estimated_round_trip_time) {
|
||||
OSP_CHECK_GE(payload_bytes_acknowledged, 0);
|
||||
OSP_CHECK_LT(ack_arrival_time, Clock::time_point::max());
|
||||
OSP_CHECK_GE(estimated_round_trip_time, Clock::duration::zero());
|
||||
// Track the bytes in terms of when the last packet was sent.
|
||||
feedback_history_.Accumulate(payload_bytes_acknowledged,
|
||||
ack_arrival_time - estimated_round_trip_time);
|
||||
}
|
||||
|
||||
int BandwidthEstimator::ComputeNetworkBandwidth() const {
|
||||
// Determine whether the `burst_history_` time window overlaps with the
|
||||
// `feedback_history_` time window by at least half. The time windows don't
|
||||
// have to overlap entirely because the calculations are averaging all the
|
||||
// measurements (i.e., recent typical behavior). Though, they should overlap
|
||||
// by "enough" so that the measurements correlate "enough."
|
||||
const Clock::time_point overlap_begin =
|
||||
std::max(burst_history_.begin_time(), feedback_history_.begin_time());
|
||||
const Clock::time_point overlap_end =
|
||||
std::min(burst_history_.end_time(), feedback_history_.end_time());
|
||||
if ((overlap_end - overlap_begin) < (history_window_ / 2)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int32_t num_packets_transmitted = burst_history_.Sum();
|
||||
if (num_packets_transmitted <= 0) {
|
||||
// Cannot estimate because there have been no transmissions recently.
|
||||
return 0;
|
||||
}
|
||||
const Clock::duration transmit_duration = history_window_ *
|
||||
num_packets_transmitted /
|
||||
max_packets_per_history_window_;
|
||||
const int32_t num_bytes_received = feedback_history_.Sum();
|
||||
return ToClampedBitsPerSecond(num_bytes_received, transmit_duration);
|
||||
}
|
||||
|
||||
// static
|
||||
constexpr int BandwidthEstimator::kNumTimeslices;
|
||||
|
||||
BandwidthEstimator::FlowTracker::FlowTracker(Clock::duration timeslice_duration,
|
||||
Clock::time_point begin_time)
|
||||
: timeslice_duration_(timeslice_duration), begin_time_(begin_time) {}
|
||||
|
||||
BandwidthEstimator::FlowTracker::~FlowTracker() = default;
|
||||
|
||||
void BandwidthEstimator::FlowTracker::AdvanceToIncludeTime(
|
||||
Clock::time_point until) {
|
||||
if (until < end_time()) {
|
||||
return; // Not advancing.
|
||||
}
|
||||
|
||||
// Step forward in time, at timeslice granularity.
|
||||
const int64_t num_periods = 1 + (until - end_time()) / timeslice_duration_;
|
||||
begin_time_ += num_periods * timeslice_duration_;
|
||||
|
||||
// Shift the ring elements, discarding N oldest timeslices, and creating N new
|
||||
// ones initialized to zero.
|
||||
const int shift_count = std::min<int64_t>(num_periods, kNumTimeslices);
|
||||
for (int i = 0; i < shift_count; ++i) {
|
||||
history_ring_[tail_++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BandwidthEstimator::FlowTracker::Accumulate(int32_t amount,
|
||||
Clock::time_point when) {
|
||||
if (when < begin_time_) {
|
||||
return; // Ignore a data point that is already too old.
|
||||
}
|
||||
|
||||
AdvanceToIncludeTime(when);
|
||||
|
||||
// Because of the AdvanceToIncludeTime() call just made, the offset/index
|
||||
// calculations here are guaranteed to point to a valid element in the
|
||||
// `history_ring_`.
|
||||
const int64_t offset_from_first = (when - begin_time_) / timeslice_duration_;
|
||||
const index_mod_256_t ring_index = tail_ + offset_from_first;
|
||||
int32_t& timeslice = history_ring_[ring_index];
|
||||
timeslice = saturate_cast<int32_t>(int64_t{timeslice} + amount);
|
||||
}
|
||||
|
||||
int32_t BandwidthEstimator::FlowTracker::Sum() const {
|
||||
int64_t result = 0;
|
||||
for (int32_t amount : history_ring_) {
|
||||
result += amount;
|
||||
}
|
||||
return saturate_cast<int32_t>(result);
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
168
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/bandwidth_estimator.h
vendored
Normal file
168
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/bandwidth_estimator.h
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// Copyright 2020 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_BANDWIDTH_ESTIMATOR_H_
|
||||
#define CAST_STREAMING_IMPL_BANDWIDTH_ESTIMATOR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "platform/api/time.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Tracks send attempts and successful receives, and then computes a total
|
||||
// network bandwith estimate.
|
||||
//
|
||||
// Two metrics are tracked by the BandwidthEstimator, over a "recent history"
|
||||
// time window:
|
||||
//
|
||||
// 1. The number of packets sent during bursts (see SenderPacketRouter for
|
||||
// explanation of what a "burst" is). These track when the network was
|
||||
// actually in-use for transmission and the magnitude of each burst. When
|
||||
// computing bandwidth, the estimator assumes the timeslices where the
|
||||
// network was not in-use could have been used to send even more bytes at
|
||||
// the same rate.
|
||||
//
|
||||
// 2. Successful receipt of payload bytes over time, or a lack thereof.
|
||||
// Packets that include acknowledgements from the Receivers are providing
|
||||
// proof of the successful receipt of payload bytes. All other packets
|
||||
// provide proof of network connectivity over time, and are used to
|
||||
// identify periods of time where nothing was received.
|
||||
//
|
||||
// The BandwidthEstimator assumes a simplified model for streaming over the
|
||||
// network. The model does not include any detailed knowledge about things like
|
||||
// protocol overhead, packet re-transmits, parasitic bufferring, network
|
||||
// reliability, etc. Instead, it automatically accounts for all such things by
|
||||
// looking at what's actually leaving the Senders and what's actually making it
|
||||
// to the Receivers.
|
||||
//
|
||||
// This simplified model does produce some known inaccuracies in the resulting
|
||||
// estimations. If no data has recently been transmitted (or been received),
|
||||
// estimations cannot be provided. If the transmission rate is near (or
|
||||
// exceeding) the network's capacity, the estimations will be very accurate. In
|
||||
// between those two extremes, the logic will tend to under-estimate the
|
||||
// network's capacity. However, those under-estimates will still be far larger
|
||||
// than the current transmission rate.
|
||||
//
|
||||
// Thus, these estimates can be used effectively as a control signal for
|
||||
// congestion control in upstream code modules. The logic computing the media's
|
||||
// encoding target bitrate should be adjusted in realtime using a TCP-like
|
||||
// congestion control algorithm:
|
||||
//
|
||||
// 1. When the estimated bitrate is less than the current encoding target
|
||||
// bitrate, aggressively and immediately decrease the encoding bitrate.
|
||||
//
|
||||
// 2. When the estimated bitrate is more than the current encoding target
|
||||
// bitrate, gradually increase the encoding bitrate (up to the maximum
|
||||
// that is reasonable for the application).
|
||||
class BandwidthEstimator {
|
||||
public:
|
||||
// `max_packets_per_timeslice` and `timeslice_duration` should match the burst
|
||||
// configuration in SenderPacketRouter. `start_time` should be a recent
|
||||
// point-in-time before the first packet is sent.
|
||||
BandwidthEstimator(int max_packets_per_timeslice,
|
||||
Clock::duration timeslice_duration,
|
||||
Clock::time_point start_time);
|
||||
|
||||
~BandwidthEstimator();
|
||||
|
||||
// Returns the duration of the fixed, recent-history time window over which
|
||||
// data flows are being tracked.
|
||||
Clock::duration history_window() const { return history_window_; }
|
||||
|
||||
// Records `when` burst-sending was active or inactive. For the active case,
|
||||
// `num_packets_sent` should include all network packets sent, including
|
||||
// non-payload packets (since both affect the modeled utilization/capacity).
|
||||
// For the inactive case, this method should be called with zero for
|
||||
// `num_packets_sent`.
|
||||
void OnBurstComplete(int num_packets_sent, Clock::time_point when);
|
||||
|
||||
// Records when a RTCP packet was received. It's important for Senders to call
|
||||
// this any time a packet comes in from the Receivers, even if no payload is
|
||||
// being acknowledged, since the time windows of "nothing successfully
|
||||
// received" is also important information to track.
|
||||
void OnRtcpReceived(Clock::time_point arrival_time,
|
||||
Clock::duration estimated_round_trip_time);
|
||||
|
||||
// Records that some number of payload bytes has been acknowledged (i.e.,
|
||||
// successfully received).
|
||||
void OnPayloadReceived(int payload_bytes_acknowledged,
|
||||
Clock::time_point ack_arrival_time,
|
||||
Clock::duration estimated_round_trip_time);
|
||||
|
||||
// Computes the current network bandwith estimate. Returns 0 if this cannot be
|
||||
// determined due to a lack of sufficiently-recent data.
|
||||
int ComputeNetworkBandwidth() const;
|
||||
|
||||
private:
|
||||
// FlowTracker (below) manages a ring buffer of size 256. It simplifies the
|
||||
// index calculations to use an integer data type where all arithmetic is mod
|
||||
// 256.
|
||||
using index_mod_256_t = uint8_t;
|
||||
static constexpr int kNumTimeslices =
|
||||
static_cast<int>(std::numeric_limits<index_mod_256_t>::max()) + 1;
|
||||
|
||||
// Tracks volume (e.g., the total number of payload bytes) over a fixed
|
||||
// recent-history time window. The time window is divided up into a number of
|
||||
// identical timeslices, each of which represents the total number of bytes
|
||||
// that flowed during a certain period of time. The data is accumulated in
|
||||
// ring buffer elements so that old data points drop-off as newer ones (that
|
||||
// move the history window forward) are added.
|
||||
class FlowTracker {
|
||||
public:
|
||||
FlowTracker(Clock::duration timeslice_duration,
|
||||
Clock::time_point begin_time);
|
||||
~FlowTracker();
|
||||
|
||||
Clock::time_point begin_time() const { return begin_time_; }
|
||||
Clock::time_point end_time() const {
|
||||
return begin_time_ + timeslice_duration_ * kNumTimeslices;
|
||||
}
|
||||
|
||||
// Advance the end of the time window being tracked such that the
|
||||
// most-recent timeslice includes `until`. Too-old timeslices are dropped
|
||||
// and new ones are initialized to a zero amount.
|
||||
void AdvanceToIncludeTime(Clock::time_point until);
|
||||
|
||||
// Accumulate the given `amount` into the timeslice that includes `when`.
|
||||
void Accumulate(int32_t amount, Clock::time_point when);
|
||||
|
||||
// Return the sum of all the amounts in recent history. This clamps to the
|
||||
// valid range of int32_t, if necessary.
|
||||
int32_t Sum() const;
|
||||
|
||||
private:
|
||||
const Clock::duration timeslice_duration_;
|
||||
|
||||
// The beginning of the oldest timeslice in the recent-history time window,
|
||||
// the one pointed to by `tail_`.
|
||||
Clock::time_point begin_time_;
|
||||
|
||||
// A ring buffer tracking the accumulated amount for each timeslice.
|
||||
int32_t history_ring_[kNumTimeslices]{};
|
||||
|
||||
// The index of the oldest timeslice in the `history_ring_`. This can also
|
||||
// be thought of, equivalently, as the index just after the most-recent
|
||||
// timeslice.
|
||||
index_mod_256_t tail_ = 0;
|
||||
};
|
||||
|
||||
// The maximum number of packet sends that could possibly be attempted during
|
||||
// the recent-history time window.
|
||||
const int max_packets_per_history_window_;
|
||||
|
||||
// The range of time being tracked.
|
||||
const Clock::duration history_window_;
|
||||
|
||||
// History tracking for send attempts, and success feeback. These timeseries
|
||||
// are in terms of when packets have left the Senders.
|
||||
FlowTracker burst_history_;
|
||||
FlowTracker feedback_history_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_BANDWIDTH_ESTIMATOR_H_
|
||||
79
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_drift_smoother.cc
vendored
Normal file
79
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_drift_smoother.cc
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2014 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/clock_drift_smoother.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "util/chrono_helpers.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/saturate_cast.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
namespace {
|
||||
|
||||
constexpr Clock::time_point kNullTime = Clock::time_point::min();
|
||||
}
|
||||
|
||||
using clock_operators::operator<<;
|
||||
|
||||
ClockDriftSmoother::ClockDriftSmoother(Clock::duration time_constant)
|
||||
: time_constant_(time_constant),
|
||||
last_update_time_(kNullTime),
|
||||
estimated_tick_offset_(0.0) {
|
||||
OSP_CHECK(time_constant_ > decltype(time_constant_)::zero());
|
||||
}
|
||||
|
||||
ClockDriftSmoother::~ClockDriftSmoother() = default;
|
||||
|
||||
std::optional<Clock::duration> ClockDriftSmoother::Current() const {
|
||||
if (last_update_time_ == kNullTime) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return Clock::duration(
|
||||
rounded_saturate_cast<Clock::duration::rep>(estimated_tick_offset_));
|
||||
}
|
||||
|
||||
void ClockDriftSmoother::Reset(Clock::time_point now,
|
||||
Clock::duration measured_offset) {
|
||||
OSP_CHECK_NE(now, kNullTime);
|
||||
last_update_time_ = now;
|
||||
estimated_tick_offset_ = static_cast<double>(measured_offset.count());
|
||||
}
|
||||
|
||||
void ClockDriftSmoother::Update(Clock::time_point now,
|
||||
Clock::duration measured_offset) {
|
||||
OSP_CHECK_NE(now, kNullTime);
|
||||
if (last_update_time_ == kNullTime) {
|
||||
Reset(now, measured_offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (now < last_update_time_) {
|
||||
// `now` is not monotonically non-decreasing.
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
|
||||
const double elapsed_ticks =
|
||||
static_cast<double>((now - last_update_time_).count());
|
||||
last_update_time_ = now;
|
||||
|
||||
// This is a standard exponential moving average (EMA) filter.
|
||||
// The alpha value is calculated such that the filter has the desired time
|
||||
// constant.
|
||||
const double alpha = 1.0 - std::exp(-elapsed_ticks / time_constant_.count());
|
||||
estimated_tick_offset_ =
|
||||
alpha * static_cast<double>(measured_offset.count()) +
|
||||
(1.0 - alpha) * estimated_tick_offset_;
|
||||
|
||||
const auto current = Current();
|
||||
OSP_VLOG << "Local clock is ahead of the remote clock by: measured = "
|
||||
<< measured_offset << ", "
|
||||
<< "filtered = " << (current ? ToString(*current) : "null") << ".";
|
||||
}
|
||||
|
||||
// static
|
||||
constexpr std::chrono::seconds ClockDriftSmoother::kDefaultTimeConstant;
|
||||
|
||||
} // namespace openscreen::cast
|
||||
58
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_drift_smoother.h
vendored
Normal file
58
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_drift_smoother.h
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2014 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_CLOCK_DRIFT_SMOOTHER_H_
|
||||
#define CAST_STREAMING_IMPL_CLOCK_DRIFT_SMOOTHER_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include "platform/api/time.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Tracks the jitter and drift between clocks, providing a smoothed offset.
|
||||
// Internally, a Simple IIR filter is used to maintain a running average that
|
||||
// moves at a rate based on the passage of time.
|
||||
class ClockDriftSmoother {
|
||||
public:
|
||||
// `time_constant` is the amount of time an impulse signal takes to decay by
|
||||
// ~62.6%. Interpretation: If the value passed to several Update() calls is
|
||||
// held constant for T seconds, then the running average will have moved
|
||||
// towards the value by ~62.6% from where it started.
|
||||
explicit ClockDriftSmoother(Clock::duration time_constant);
|
||||
~ClockDriftSmoother();
|
||||
|
||||
// Returns the current offset. Will be std::nullopt if no values have been
|
||||
// set yet (via Reset() or Update()).
|
||||
std::optional<Clock::duration> Current() const;
|
||||
|
||||
// Discard all history and reset to exactly `offset`, measured `now`.
|
||||
void Reset(Clock::time_point now, Clock::duration offset);
|
||||
|
||||
// Update the current offset, which was measured `now`. The weighting that
|
||||
// `measured_offset` will have on the running average is influenced by how
|
||||
// much time has passed since the last call to this method (or Reset()).
|
||||
// `now` should be monotonically non-decreasing over successive calls of this
|
||||
// method.
|
||||
void Update(Clock::time_point now, Clock::duration measured_offset);
|
||||
|
||||
// A time constant suitable for most use cases, where the clocks are expected
|
||||
// to drift very little with respect to each other, and the jitter caused by
|
||||
// clock imprecision is effectively canceled out.
|
||||
static constexpr std::chrono::seconds kDefaultTimeConstant{30};
|
||||
|
||||
private:
|
||||
const std::chrono::duration<double, Clock::duration::period> time_constant_;
|
||||
|
||||
// The time at which `estimated_tick_offset_` was last updated.
|
||||
Clock::time_point last_update_time_;
|
||||
|
||||
// The current estimated offset, as number of Clock::duration ticks.
|
||||
double estimated_tick_offset_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_CLOCK_DRIFT_SMOOTHER_H_
|
||||
71
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_offset_estimator.h
vendored
Normal file
71
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_offset_estimator.h
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2023 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_CLOCK_OFFSET_ESTIMATOR_H_
|
||||
#define CAST_STREAMING_IMPL_CLOCK_OFFSET_ESTIMATOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "cast/streaming/public/statistics.h"
|
||||
#include "platform/base/trivial_clock_traits.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Used to estimate the offset between the Sender and Receiver clocks.
|
||||
class ClockOffsetEstimator {
|
||||
public:
|
||||
static std::unique_ptr<ClockOffsetEstimator> Create();
|
||||
|
||||
virtual ~ClockOffsetEstimator() {}
|
||||
|
||||
// TODO(issuetracker.google.com/298085631): these should be in a separate
|
||||
// header, like Chrome's raw event subscriber pattern.
|
||||
// See: //media/cast/logging/raw_event_subscriber.h
|
||||
virtual void OnFrameEvent(const FrameEvent& frame_event) = 0;
|
||||
virtual void OnPacketEvent(const PacketEvent& packet_event) = 0;
|
||||
|
||||
// Estimates the clock offset between the sender and the receiver.
|
||||
//
|
||||
// This is calculated by solving a system of two linear equations with two
|
||||
// unknowns: the clock offset and the network latency. The two equations are
|
||||
// derived from two round-trip time measurements.
|
||||
//
|
||||
// Let's define:
|
||||
// - latency: the one-way network latency.
|
||||
// - offset: the clock offset, where Clock_Receiver(t) = Clock_Sender(t) +
|
||||
// offset.
|
||||
//
|
||||
// The estimator measures two bounds:
|
||||
//
|
||||
// 1. packet_bound (sender -> receiver):
|
||||
// delta = TS_receiver - TS_sender
|
||||
// = (TS_sender + latency + offset) - TS_sender
|
||||
// = latency + offset
|
||||
//
|
||||
// 2. frame_bound (receiver -> sender):
|
||||
// delta = TS_sender - TS_receiver
|
||||
// = (TS_receiver + latency - offset) - TS_receiver
|
||||
// = latency - offset
|
||||
//
|
||||
// The offset is then isolated by the formula:
|
||||
// (packet_bound - frame_bound) / 2 =
|
||||
// ( (latency + offset) - (latency - offset) ) / 2 =
|
||||
// (2 * offset) / 2 = offset
|
||||
virtual std::optional<Clock::duration> GetEstimatedOffset() const = 0;
|
||||
|
||||
// Estimates the one-way network latency.
|
||||
// This uses the same bounds as GetEstimatedOffset().
|
||||
//
|
||||
// The latency is isolated by the formula:
|
||||
// (packet_bound + frame_bound) / 2 =
|
||||
// ( (latency + offset) + (latency - offset) ) / 2 = (2 * latency) / 2 =
|
||||
// latency
|
||||
virtual std::optional<Clock::duration> GetEstimatedLatency() const = 0;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_CLOCK_OFFSET_ESTIMATOR_H_
|
||||
222
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_offset_estimator_impl.cc
vendored
Normal file
222
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_offset_estimator_impl.cc
vendored
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/clock_offset_estimator_impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "platform/base/trivial_clock_traits.h"
|
||||
#include "util/chrono_helpers.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
namespace {
|
||||
|
||||
// This should be large enough so that we can collect all 3 events before
|
||||
// the entry gets removed from the map.
|
||||
constexpr size_t kMaxEventTimesMapSize = 500;
|
||||
|
||||
// Bitwise merging of values to produce an ordered key for entries in the
|
||||
// BoundCalculator::events_ map. Since std::map is sorted by key value, we
|
||||
// ensure that the Packet ID is first (since the RTP timestamp may roll over
|
||||
// eventually).
|
||||
//
|
||||
// 0 1 2 3 4 5 6
|
||||
// 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Packet ID | RTP Timestamp |*| (is_audio)
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
uint64_t MakeEventKey(RtpTimeTicks rtp, uint16_t packet_id, bool audio) {
|
||||
return (static_cast<uint64_t>(packet_id) << 48) |
|
||||
(static_cast<uint64_t>(rtp.lower_32_bits()) << 1) |
|
||||
static_cast<uint64_t>(audio ? 1 : 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<ClockOffsetEstimator> ClockOffsetEstimator::Create() {
|
||||
return std::make_unique<ClockOffsetEstimatorImpl>();
|
||||
}
|
||||
|
||||
ClockOffsetEstimatorImpl::ClockOffsetEstimatorImpl() = default;
|
||||
ClockOffsetEstimatorImpl::ClockOffsetEstimatorImpl(
|
||||
ClockOffsetEstimatorImpl&&) noexcept = default;
|
||||
ClockOffsetEstimatorImpl& ClockOffsetEstimatorImpl::operator=(
|
||||
ClockOffsetEstimatorImpl&&) = default;
|
||||
ClockOffsetEstimatorImpl::~ClockOffsetEstimatorImpl() = default;
|
||||
|
||||
void ClockOffsetEstimatorImpl::OnFrameEvent(const FrameEvent& frame_event) {
|
||||
switch (frame_event.type) {
|
||||
case StatisticsEvent::Type::kFrameAckSent:
|
||||
frame_bound_.SetSent(
|
||||
frame_event.rtp_timestamp, 0,
|
||||
frame_event.media_type == StatisticsEvent::MediaType::kAudio,
|
||||
frame_event.timestamp);
|
||||
break;
|
||||
case StatisticsEvent::Type::kFrameAckReceived:
|
||||
frame_bound_.SetReceived(
|
||||
frame_event.rtp_timestamp, 0,
|
||||
frame_event.media_type == StatisticsEvent::MediaType::kAudio,
|
||||
frame_event.timestamp);
|
||||
break;
|
||||
default:
|
||||
// Ignored
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ClockOffsetEstimatorImpl::OnPacketEvent(const PacketEvent& packet_event) {
|
||||
switch (packet_event.type) {
|
||||
case StatisticsEvent::Type::kPacketSentToNetwork:
|
||||
packet_bound_.SetSent(
|
||||
packet_event.rtp_timestamp, packet_event.packet_id,
|
||||
packet_event.media_type == StatisticsEvent::MediaType::kAudio,
|
||||
packet_event.timestamp);
|
||||
break;
|
||||
case StatisticsEvent::Type::kPacketReceived:
|
||||
packet_bound_.SetReceived(
|
||||
packet_event.rtp_timestamp, packet_event.packet_id,
|
||||
packet_event.media_type == StatisticsEvent::MediaType::kAudio,
|
||||
packet_event.timestamp);
|
||||
break;
|
||||
default:
|
||||
// Ignored
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool ClockOffsetEstimatorImpl::GetReceiverOffsetBounds(
|
||||
Clock::duration& frame_bound,
|
||||
Clock::duration& packet_bound) const {
|
||||
if (!frame_bound_.has_bound() || !packet_bound_.has_bound()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
frame_bound = -frame_bound_.bound();
|
||||
packet_bound = packet_bound_.bound();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<Clock::duration> ClockOffsetEstimatorImpl::GetEstimatedOffset()
|
||||
const {
|
||||
Clock::duration frame_bound;
|
||||
Clock::duration packet_bound;
|
||||
if (!GetReceiverOffsetBounds(frame_bound, packet_bound)) {
|
||||
return {};
|
||||
}
|
||||
return (packet_bound + frame_bound) / 2;
|
||||
}
|
||||
|
||||
std::optional<Clock::duration> ClockOffsetEstimatorImpl::GetEstimatedLatency()
|
||||
const {
|
||||
Clock::duration frame_bound;
|
||||
Clock::duration packet_bound;
|
||||
if (!GetReceiverOffsetBounds(frame_bound, packet_bound)) {
|
||||
return {};
|
||||
}
|
||||
return (packet_bound - frame_bound) / 2;
|
||||
}
|
||||
|
||||
ClockOffsetEstimatorImpl::KalmanFilter::KalmanFilter(
|
||||
Clock::duration process_noise,
|
||||
Clock::duration measurement_noise)
|
||||
: q_nanos_squared_(
|
||||
static_cast<double>(std::chrono::nanoseconds(process_noise).count()) *
|
||||
std::chrono::nanoseconds(process_noise).count()),
|
||||
r_nanos_squared_(
|
||||
static_cast<double>(
|
||||
std::chrono::nanoseconds(measurement_noise).count()) *
|
||||
std::chrono::nanoseconds(measurement_noise).count()) {}
|
||||
|
||||
void ClockOffsetEstimatorImpl::KalmanFilter::Update(
|
||||
Clock::duration measurement) {
|
||||
if (!has_estimate_) {
|
||||
// First measurement, initialize the state.
|
||||
estimated_latency_ = measurement;
|
||||
error_covariance_nanos_squared_ = r_nanos_squared_;
|
||||
has_estimate_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 1. PREDICT ---
|
||||
// The predicted state is the same as the previous state.
|
||||
// The uncertainty (covariance) increases by the process noise.
|
||||
const double predicted_error_covariance =
|
||||
error_covariance_nanos_squared_ + q_nanos_squared_;
|
||||
|
||||
// --- 2. UPDATE ---
|
||||
// Calculate Kalman Gain.
|
||||
const double kalman_gain = predicted_error_covariance /
|
||||
(predicted_error_covariance + r_nanos_squared_);
|
||||
|
||||
// Update the estimate with the new measurement.
|
||||
const double measurement_nanos =
|
||||
static_cast<double>(std::chrono::nanoseconds(measurement).count());
|
||||
const double estimate_nanos =
|
||||
static_cast<double>(std::chrono::nanoseconds(estimated_latency_).count());
|
||||
const double new_estimate_nanos =
|
||||
estimate_nanos + kalman_gain * (measurement_nanos - estimate_nanos);
|
||||
estimated_latency_ =
|
||||
std::chrono::duration_cast<Clock::duration>(std::chrono::nanoseconds(
|
||||
static_cast<Clock::duration::rep>(new_estimate_nanos)));
|
||||
|
||||
// Update the error covariance.
|
||||
error_covariance_nanos_squared_ =
|
||||
(1.0 - kalman_gain) * predicted_error_covariance;
|
||||
}
|
||||
|
||||
ClockOffsetEstimatorImpl::BoundCalculator::BoundCalculator()
|
||||
: filter_(kProcessNoise, kMeasurementNoise) {}
|
||||
|
||||
ClockOffsetEstimatorImpl::BoundCalculator::BoundCalculator(
|
||||
BoundCalculator&&) noexcept = default;
|
||||
ClockOffsetEstimatorImpl::BoundCalculator&
|
||||
ClockOffsetEstimatorImpl::BoundCalculator::operator=(BoundCalculator&&) =
|
||||
default;
|
||||
ClockOffsetEstimatorImpl::BoundCalculator::~BoundCalculator() = default;
|
||||
|
||||
void ClockOffsetEstimatorImpl::BoundCalculator::SetSent(RtpTimeTicks rtp,
|
||||
uint16_t packet_id,
|
||||
bool audio,
|
||||
Clock::time_point t) {
|
||||
const uint64_t key = MakeEventKey(rtp, packet_id, audio);
|
||||
events_[key].first = t;
|
||||
CheckUpdate(key);
|
||||
}
|
||||
|
||||
void ClockOffsetEstimatorImpl::BoundCalculator::SetReceived(
|
||||
RtpTimeTicks rtp,
|
||||
uint16_t packet_id,
|
||||
bool audio,
|
||||
Clock::time_point t) {
|
||||
const uint64_t key = MakeEventKey(rtp, packet_id, audio);
|
||||
events_[key].second = t;
|
||||
CheckUpdate(key);
|
||||
}
|
||||
|
||||
void ClockOffsetEstimatorImpl::BoundCalculator::UpdateBound(
|
||||
Clock::time_point sent,
|
||||
Clock::time_point received) {
|
||||
filter_.Update(received - sent);
|
||||
}
|
||||
|
||||
void ClockOffsetEstimatorImpl::BoundCalculator::CheckUpdate(uint64_t key) {
|
||||
const TimeTickPair& ticks = events_[key];
|
||||
if (ticks.first && ticks.second) {
|
||||
UpdateBound(ticks.first.value(), ticks.second.value());
|
||||
events_.erase(key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (events_.size() > kMaxEventTimesMapSize) {
|
||||
// We can make use of the fact that std::map sorts by key and just erase
|
||||
// the first entry.
|
||||
events_.erase(events_.begin());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
136
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_offset_estimator_impl.h
vendored
Normal file
136
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/clock_offset_estimator_impl.h
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2023 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_CLOCK_OFFSET_ESTIMATOR_IMPL_H_
|
||||
#define CAST_STREAMING_IMPL_CLOCK_OFFSET_ESTIMATOR_IMPL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/impl/clock_offset_estimator.h"
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "cast/streaming/rtp_time.h"
|
||||
#include "platform/base/trivial_clock_traits.h"
|
||||
#include "util/chrono_helpers.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// This implementation listens to two pairs of events:
|
||||
// 1. FrameAckSent / FrameAckReceived (receiver->sender)
|
||||
// 2. PacketSentToNetwork / PacketReceived (sender->receiver)
|
||||
//
|
||||
// There is a causal relationship between these events in that these events
|
||||
// must happen in order. This class obtains the lower and upper bounds for
|
||||
// the offset by taking the difference of timestamps.
|
||||
class ClockOffsetEstimatorImpl final : public ClockOffsetEstimator {
|
||||
public:
|
||||
ClockOffsetEstimatorImpl();
|
||||
ClockOffsetEstimatorImpl(ClockOffsetEstimatorImpl&&) noexcept;
|
||||
ClockOffsetEstimatorImpl(const ClockOffsetEstimatorImpl&) = delete;
|
||||
ClockOffsetEstimatorImpl& operator=(ClockOffsetEstimatorImpl&&);
|
||||
ClockOffsetEstimatorImpl& operator=(const ClockOffsetEstimatorImpl&) = delete;
|
||||
~ClockOffsetEstimatorImpl() final;
|
||||
|
||||
void OnFrameEvent(const FrameEvent& frame_event) final;
|
||||
void OnPacketEvent(const PacketEvent& packet_event) final;
|
||||
|
||||
bool GetReceiverOffsetBounds(Clock::duration& frame_bound,
|
||||
Clock::duration& packet_bound) const;
|
||||
|
||||
// ClockOffsetEstimator overrides.
|
||||
std::optional<Clock::duration> GetEstimatedOffset() const final;
|
||||
std::optional<Clock::duration> GetEstimatedLatency() const final;
|
||||
|
||||
private:
|
||||
// These values are chosen based on common network conditions.
|
||||
//
|
||||
// Q (process_noise): We expect latency to drift by up to 5ms between
|
||||
// measurements.
|
||||
static constexpr Clock::duration kProcessNoise = milliseconds(5);
|
||||
//
|
||||
// R (measurement_noise): We expect jitter of up to 30ms.
|
||||
static constexpr Clock::duration kMeasurementNoise = milliseconds(30);
|
||||
|
||||
// Simplified 1D Kalman Filter for latency estimation.
|
||||
class KalmanFilter {
|
||||
public:
|
||||
// Q: process_noise - Represents the expected variance of the latency
|
||||
// itself between time steps. A higher value makes the filter adapt
|
||||
// more quickly to real changes in latency.
|
||||
// R: measurement_noise - Represents the variance of the measurement
|
||||
// noise (jitter). A higher value makes the filter trust its own
|
||||
// prediction more and smooth out noisy measurements.
|
||||
KalmanFilter(Clock::duration process_noise,
|
||||
Clock::duration measurement_noise);
|
||||
KalmanFilter(KalmanFilter&&) noexcept = default;
|
||||
KalmanFilter& operator=(KalmanFilter&&) = default;
|
||||
|
||||
Clock::duration GetEstimate() const { return estimated_latency_; }
|
||||
bool HasEstimate() const { return has_estimate_; }
|
||||
void Update(Clock::duration measurement);
|
||||
|
||||
private:
|
||||
double q_nanos_squared_;
|
||||
double r_nanos_squared_;
|
||||
|
||||
bool has_estimate_ = false;
|
||||
Clock::duration estimated_latency_{};
|
||||
double error_covariance_nanos_squared_ = 0.0;
|
||||
};
|
||||
|
||||
// This helper uses the difference between sent and received event
|
||||
// to calculate an upper bound on the difference between the clocks
|
||||
// on the sender and receiver. Note that this difference can take
|
||||
// very large positive or negative values, but the smaller value is
|
||||
// always the better estimate, since a receive event cannot possibly
|
||||
// happen before a send event. Note that we use this to calculate
|
||||
// both upper and lower bounds by reversing the sender/receiver
|
||||
// relationship.
|
||||
class BoundCalculator {
|
||||
public:
|
||||
typedef std::pair<std::optional<Clock::time_point>,
|
||||
std::optional<Clock::time_point>>
|
||||
TimeTickPair;
|
||||
typedef std::map<uint64_t, TimeTickPair> EventMap;
|
||||
|
||||
BoundCalculator();
|
||||
BoundCalculator(BoundCalculator&&) noexcept;
|
||||
BoundCalculator(const BoundCalculator&) = delete;
|
||||
BoundCalculator& operator=(BoundCalculator&&);
|
||||
BoundCalculator& operator=(const BoundCalculator&) = delete;
|
||||
~BoundCalculator();
|
||||
bool has_bound() const { return filter_.HasEstimate(); }
|
||||
Clock::duration bound() const { return filter_.GetEstimate(); }
|
||||
|
||||
void SetSent(RtpTimeTicks rtp,
|
||||
uint16_t packet_id,
|
||||
bool audio,
|
||||
Clock::time_point t);
|
||||
|
||||
void SetReceived(RtpTimeTicks rtp,
|
||||
uint16_t packet_id,
|
||||
bool audio,
|
||||
Clock::time_point t);
|
||||
|
||||
private:
|
||||
void UpdateBound(Clock::time_point a, Clock::time_point b);
|
||||
void CheckUpdate(uint64_t key);
|
||||
|
||||
private:
|
||||
EventMap events_;
|
||||
KalmanFilter filter_;
|
||||
};
|
||||
|
||||
// Fixed size storage to store event times for recent frames and packets.
|
||||
BoundCalculator packet_bound_;
|
||||
BoundCalculator frame_bound_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_CLOCK_OFFSET_ESTIMATOR_IMPL_H_
|
||||
479
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/compound_rtcp_parser.cc
vendored
Normal file
479
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/compound_rtcp_parser.cc
vendored
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/compound_rtcp_parser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/impl/packet_util.h"
|
||||
#include "cast/streaming/impl/rtcp_session.h"
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "util/chrono_helpers.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/std_util.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
// Use the Clock's minimum time value (an impossible value, waaaaay before epoch
|
||||
// time) to represent unset time_point values.
|
||||
constexpr auto kNullTimePoint = Clock::time_point::min();
|
||||
|
||||
// Some receivers send time sync requests (that we ignore).
|
||||
constexpr uint32_t kTimeSyncRequestName =
|
||||
('T' << 24) + ('I' << 16) + ('M' << 8) + 'E';
|
||||
|
||||
// Canonicalizes the just-parsed list of packet-specific NACKs so that the
|
||||
// CompoundRtcpParser::Client can make several simplifying assumptions when
|
||||
// processing the results.
|
||||
void CanonicalizePacketNackVector(std::vector<PacketNack>* packets) {
|
||||
// First, sort all elements. The sort order is the normal lexicographical
|
||||
// ordering, with one exception: The special kAllPacketsLost packet_id value
|
||||
// should be treated as coming before all others. This special sort order
|
||||
// allows the filtering algorithm below to be simpler, and only require one
|
||||
// pass; and the final result will be the normal lexicographically-sorted
|
||||
// output the CompoundRtcpParser::Client expects.
|
||||
std::sort(packets->begin(), packets->end(),
|
||||
[](const PacketNack& a, const PacketNack& b) {
|
||||
// Since the comparator is a hot code path, use a simple modular
|
||||
// arithmetic trick in lieu of extra branching: When comparing the
|
||||
// tuples, map all packet_id values to packet_id + 1, mod 0x10000.
|
||||
// This results in the desired sorting behavior since
|
||||
// kAllPacketsLost (0xffff) wraps-around to 0x0000, and all other
|
||||
// values become N + 1.
|
||||
static_assert(static_cast<FramePacketId>(kAllPacketsLost + 1) <
|
||||
FramePacketId{0x0000 + 1},
|
||||
"comparison requires integer wrap-around");
|
||||
return PacketNack{a.frame_id,
|
||||
static_cast<FramePacketId>(a.packet_id + 1)} <
|
||||
PacketNack{b.frame_id,
|
||||
static_cast<FramePacketId>(b.packet_id + 1)};
|
||||
});
|
||||
|
||||
// De-duplicate elements. Two possible cases:
|
||||
//
|
||||
// 1. Identical elements (same FrameId+FramePacketId).
|
||||
// 2. If there are any elements with kAllPacketsLost as the packet ID,
|
||||
// prune-out all other elements having the same frame ID, as they are
|
||||
// redundant.
|
||||
//
|
||||
// This is done by walking forwards over the sorted vector and deciding which
|
||||
// elements to keep. Those that are kept are stacked-up at the front of the
|
||||
// vector. After the "to-keep" pass, the vector is truncated to remove the
|
||||
// left-over garbage at the end.
|
||||
auto have_it = packets->begin();
|
||||
if (have_it != packets->end()) {
|
||||
auto kept_it = have_it; // Always keep the first element.
|
||||
for (++have_it; have_it != packets->end(); ++have_it) {
|
||||
if (have_it->frame_id != kept_it->frame_id ||
|
||||
(kept_it->packet_id != kAllPacketsLost &&
|
||||
have_it->packet_id != kept_it->packet_id)) { // Keep it.
|
||||
++kept_it;
|
||||
*kept_it = *have_it;
|
||||
}
|
||||
}
|
||||
packets->erase(++kept_it, packets->end());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CompoundRtcpParser::CompoundRtcpParser(RtcpSession& session,
|
||||
CompoundRtcpParser::Client& client)
|
||||
: session_(session),
|
||||
client_(client),
|
||||
latest_receiver_timestamp_(kNullTimePoint) {}
|
||||
|
||||
CompoundRtcpParser::~CompoundRtcpParser() = default;
|
||||
|
||||
bool CompoundRtcpParser::Parse(ByteView buffer, FrameId max_feedback_frame_id) {
|
||||
// These will contain the results from the various ParseXYZ() methods. None of
|
||||
// the results will be dispatched to the Client until the entire parse
|
||||
// succeeds.
|
||||
Clock::time_point receiver_reference_time = kNullTimePoint;
|
||||
std::optional<RtcpReportBlock> receiver_report;
|
||||
std::vector<RtcpReceiverFrameLogMessage> log_messages;
|
||||
FrameId checkpoint_frame_id;
|
||||
milliseconds target_playout_delay{};
|
||||
std::vector<FrameId> received_frames;
|
||||
std::vector<PacketNack> packet_nacks;
|
||||
bool picture_loss_indicator = false;
|
||||
|
||||
// The data contained in `buffer` can be a "compound packet," which means that
|
||||
// it can be the concatenation of multiple RTCP packets. The loop here
|
||||
// processes each one-by-one.
|
||||
while (!buffer.empty()) {
|
||||
const auto header = RtcpCommonHeader::Parse(buffer);
|
||||
if (!header) {
|
||||
return false;
|
||||
}
|
||||
buffer = buffer.subspan(kRtcpCommonHeaderSize);
|
||||
if (static_cast<int>(buffer.size()) < header->payload_size) {
|
||||
return false;
|
||||
}
|
||||
ByteView payload = buffer.subspan(0, header->payload_size);
|
||||
buffer = buffer.subspan(header->payload_size);
|
||||
|
||||
switch (header->packet_type) {
|
||||
case RtcpPacketType::kReceiverReport:
|
||||
if (!ParseReceiverReport(payload, header->with.report_count,
|
||||
receiver_report)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case RtcpPacketType::kApplicationDefined:
|
||||
if (!ParseApplicationDefined(header->with.subtype, payload,
|
||||
log_messages)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case RtcpPacketType::kPayloadSpecific:
|
||||
switch (header->with.subtype) {
|
||||
case RtcpSubtype::kPictureLossIndicator:
|
||||
if (!ParsePictureLossIndicator(payload, picture_loss_indicator)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case RtcpSubtype::kFeedback:
|
||||
if (!ParseFeedback(payload, max_feedback_frame_id,
|
||||
&checkpoint_frame_id, &target_playout_delay,
|
||||
&received_frames, packet_nacks)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Ignore: Unimplemented or not part of the Cast Streaming spec.
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RtcpPacketType::kExtendedReports:
|
||||
if (!ParseExtendedReports(payload, receiver_reference_time)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignored, unimplemented or not part of the Cast Streaming spec.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// A well-behaved Cast Streaming Receiver will always include a reference time
|
||||
// report. This essentially "timestamps" the RTCP packets just parsed.
|
||||
// However, the spec does not explicitly require this be included. When it is
|
||||
// present, improve the stability of the system by ignoring stale/out-of-order
|
||||
// RTCP packets.
|
||||
if (receiver_reference_time != kNullTimePoint) {
|
||||
// If the packet is out-of-order (e.g., it got delayed/shuffled when going
|
||||
// through the network), just ignore it. Since RTCP packets always include
|
||||
// all the necessary current state from the peer, dropping them does not
|
||||
// mean important signals will be lost. In fact, it can actually be harmful
|
||||
// to process compound RTCP packets out-of-order.
|
||||
if (latest_receiver_timestamp_ != kNullTimePoint &&
|
||||
receiver_reference_time < latest_receiver_timestamp_) {
|
||||
return true;
|
||||
}
|
||||
latest_receiver_timestamp_ = receiver_reference_time;
|
||||
client_->OnReceiverReferenceTimeAdvanced(latest_receiver_timestamp_);
|
||||
}
|
||||
|
||||
// At this point, the packet is known to be well-formed. Dispatch events of
|
||||
// interest to the Client.
|
||||
if (receiver_report) {
|
||||
client_->OnReceiverReport(*receiver_report);
|
||||
}
|
||||
if (!log_messages.empty()) {
|
||||
client_->OnCastReceiverFrameLogMessages(std::move(log_messages));
|
||||
}
|
||||
if (!checkpoint_frame_id.is_null()) {
|
||||
client_->OnReceiverCheckpoint(checkpoint_frame_id, target_playout_delay);
|
||||
}
|
||||
if (!received_frames.empty()) {
|
||||
OSP_DCHECK(AreElementsSortedAndUnique(received_frames));
|
||||
client_->OnReceiverHasFrames(std::move(received_frames));
|
||||
}
|
||||
CanonicalizePacketNackVector(&packet_nacks);
|
||||
if (!packet_nacks.empty()) {
|
||||
client_->OnReceiverIsMissingPackets(std::move(packet_nacks));
|
||||
}
|
||||
if (picture_loss_indicator) {
|
||||
client_->OnReceiverIndicatesPictureLoss();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompoundRtcpParser::ParseReceiverReport(
|
||||
ByteView in,
|
||||
int num_report_blocks,
|
||||
std::optional<RtcpReportBlock>& receiver_report) {
|
||||
if (in.size() < kRtcpReceiverReportSize) {
|
||||
return false;
|
||||
}
|
||||
if (ConsumeField<uint32_t>(in) == session_->receiver_ssrc()) {
|
||||
receiver_report = RtcpReportBlock::ParseOne(in, num_report_blocks,
|
||||
session_->sender_ssrc());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompoundRtcpParser::ParseApplicationDefined(
|
||||
RtcpSubtype subtype,
|
||||
ByteView in,
|
||||
std::vector<RtcpReceiverFrameLogMessage>& messages) {
|
||||
if (in.size() < 2 * sizeof(uint32_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t sender_ssrc = ConsumeField<uint32_t>(in);
|
||||
const uint32_t name = ConsumeField<uint32_t>(in);
|
||||
|
||||
// Just ignore events that aren't intended for us.
|
||||
if (sender_ssrc != session_->receiver_ssrc()) {
|
||||
return true;
|
||||
}
|
||||
if (name != kCastName) {
|
||||
// We ignore time sync requests but don't throw an error for them.
|
||||
return name == kTimeSyncRequestName;
|
||||
}
|
||||
if (subtype == RtcpSubtype::kReceiverLog) {
|
||||
return ParseFrameLogMessages(in, messages);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompoundRtcpParser::ParseFrameLogMessages(
|
||||
ByteView in,
|
||||
std::vector<RtcpReceiverFrameLogMessage>& messages) {
|
||||
while (!in.empty()) {
|
||||
if (in.size() < kRtcpReceiverFrameLogMessageHeaderSize) {
|
||||
messages.clear();
|
||||
return false;
|
||||
}
|
||||
const uint32_t truncated_rtp_timestamp = ConsumeField<uint32_t>(in);
|
||||
const uint32_t data = ConsumeField<uint32_t>(in);
|
||||
|
||||
// The 24 least significant bits contain the event timestamp, which is
|
||||
// offset from when the first packet was sent.
|
||||
const uint32_t raw_timestamp = data & 0xFFFFFF;
|
||||
const Clock::time_point event_timestamp_base =
|
||||
session_->start_time() + milliseconds(raw_timestamp);
|
||||
|
||||
// The 8 most significant bits contain the number of events.
|
||||
// NOTE: at least one event is required, so a value of "0" over the wire
|
||||
// actually means there is one event.
|
||||
const size_t num_events = 1u + static_cast<uint8_t>(data >> 24);
|
||||
|
||||
const RtpTimeTicks frame_log_rtp_timestamp =
|
||||
latest_frame_log_rtp_timestamp_.Expand(truncated_rtp_timestamp);
|
||||
RtcpReceiverFrameLogMessage frame_log_message{.rtp_timestamp =
|
||||
frame_log_rtp_timestamp};
|
||||
|
||||
for (size_t event = 0; event < num_events; ++event) {
|
||||
if (in.size() < kRtcpReceiverFrameLogMessageBlockSize) {
|
||||
messages.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t delay_delta_or_packet_id = ConsumeField<uint16_t>(in);
|
||||
const uint16_t event_type_and_timestamp_delta =
|
||||
ConsumeField<uint16_t>(in);
|
||||
|
||||
// Skip unknown event types, they are not useful.
|
||||
const auto event_type =
|
||||
StatisticsEvent::FromWireType(static_cast<StatisticsEvent::WireType>(
|
||||
event_type_and_timestamp_delta >> 12));
|
||||
if (event_type == StatisticsEvent::Type::kUnknown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RtcpReceiverEventLogMessage event_log{
|
||||
.type = event_type,
|
||||
.timestamp = event_timestamp_base +
|
||||
milliseconds(event_type_and_timestamp_delta & 0xFFF)};
|
||||
if (event_type == StatisticsEvent::Type::kPacketReceived) {
|
||||
event_log.packet_id = delay_delta_or_packet_id;
|
||||
} else {
|
||||
event_log.delay =
|
||||
milliseconds(static_cast<int16_t>(delay_delta_or_packet_id));
|
||||
}
|
||||
frame_log_message.messages.emplace_back(std::move(event_log));
|
||||
}
|
||||
latest_frame_log_rtp_timestamp_ = frame_log_rtp_timestamp;
|
||||
messages.emplace_back(std::move(frame_log_message));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompoundRtcpParser::ParseFeedback(ByteView in,
|
||||
FrameId max_feedback_frame_id,
|
||||
FrameId* checkpoint_frame_id,
|
||||
milliseconds* target_playout_delay,
|
||||
std::vector<FrameId>* received_frames,
|
||||
std::vector<PacketNack>& packet_nacks) {
|
||||
OSP_CHECK(!max_feedback_frame_id.is_null());
|
||||
|
||||
if (static_cast<int>(in.size()) < kRtcpFeedbackHeaderSize) {
|
||||
return false;
|
||||
}
|
||||
if (ConsumeField<uint32_t>(in) != session_->receiver_ssrc() ||
|
||||
ConsumeField<uint32_t>(in) != session_->sender_ssrc()) {
|
||||
return true; // Ignore report from mismatched SSRC(s).
|
||||
}
|
||||
if (ConsumeField<uint32_t>(in) != kRtcpCastIdentifierWord) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const FrameId feedback_frame_id =
|
||||
max_feedback_frame_id.ExpandLessThanOrEqual(ConsumeField<uint8_t>(in));
|
||||
const int loss_field_count = ConsumeField<uint8_t>(in);
|
||||
const auto playout_delay = milliseconds(ConsumeField<uint16_t>(in));
|
||||
// Don't process feedback that would move the checkpoint backwards. The Client
|
||||
// makes assumptions about what frame data and other tracking state can be
|
||||
// discarded based on a monotonically non-decreasing checkpoint FrameId.
|
||||
if (!checkpoint_frame_id->is_null() &&
|
||||
*checkpoint_frame_id > feedback_frame_id) {
|
||||
return true;
|
||||
}
|
||||
*checkpoint_frame_id = feedback_frame_id;
|
||||
*target_playout_delay = playout_delay;
|
||||
received_frames->clear();
|
||||
packet_nacks.clear();
|
||||
if (static_cast<int>(in.size()) <
|
||||
(kRtcpFeedbackLossFieldSize * loss_field_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the NACKs.
|
||||
for (int i = 0; i < loss_field_count; ++i) {
|
||||
const FrameId frame_id =
|
||||
feedback_frame_id.ExpandGreaterThan(ConsumeField<uint8_t>(in));
|
||||
FramePacketId packet_id = ConsumeField<uint16_t>(in);
|
||||
uint8_t bits = ConsumeField<uint8_t>(in);
|
||||
packet_nacks.push_back(PacketNack{frame_id, packet_id});
|
||||
|
||||
if (packet_id != kAllPacketsLost) {
|
||||
// Translate each set bit in the bit vector into another missing
|
||||
// FramePacketId.
|
||||
while (bits) {
|
||||
++packet_id;
|
||||
if (bits & 1) {
|
||||
packet_nacks.push_back(PacketNack{frame_id, packet_id});
|
||||
}
|
||||
bits >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the optional CST2 feedback (frame-level ACKs).
|
||||
if (static_cast<int>(in.size()) < kRtcpFeedbackAckHeaderSize ||
|
||||
ConsumeField<uint32_t>(in) != kRtcpCst2IdentifierWord) {
|
||||
// Optional CST2 extended feedback is not present. For backwards-
|
||||
// compatibility reasons, do not consider any extra "garbage" in the packet
|
||||
// that doesn't match 'CST2' as corrupted input.
|
||||
return true;
|
||||
}
|
||||
// Skip over the "Feedback Count" field. It's currently unused, though it
|
||||
// might be useful for event tracing later...
|
||||
in = in.subspan(sizeof(uint8_t));
|
||||
const int ack_bitvector_octet_count = ConsumeField<uint8_t>(in);
|
||||
if (static_cast<int>(in.size()) < ack_bitvector_octet_count) {
|
||||
return false;
|
||||
}
|
||||
// Translate each set bit in the bit vector into a FrameId. See the
|
||||
// explanation of this wire format in rtp_defines.h for where the "plus two"
|
||||
// comes from.
|
||||
FrameId starting_frame_id = feedback_frame_id + 2;
|
||||
for (int i = 0; i < ack_bitvector_octet_count; ++i) {
|
||||
uint8_t bits = ConsumeField<uint8_t>(in);
|
||||
FrameId frame_id = starting_frame_id;
|
||||
while (bits) {
|
||||
if (bits & 1) {
|
||||
received_frames->push_back(frame_id);
|
||||
}
|
||||
++frame_id;
|
||||
bits >>= 1;
|
||||
}
|
||||
constexpr int kBitsPerOctet = 8;
|
||||
starting_frame_id += kBitsPerOctet;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompoundRtcpParser::ParseExtendedReports(
|
||||
ByteView in,
|
||||
Clock::time_point& receiver_reference_time) {
|
||||
if (static_cast<int>(in.size()) < kRtcpExtendedReportHeaderSize) {
|
||||
return false;
|
||||
}
|
||||
if (ConsumeField<uint32_t>(in) != session_->receiver_ssrc()) {
|
||||
return true; // Ignore report from unknown receiver.
|
||||
}
|
||||
|
||||
while (!in.empty()) {
|
||||
// All extended report types have the same 4-byte subheader.
|
||||
if (static_cast<int>(in.size()) < kRtcpExtendedReportBlockHeaderSize) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t block_type = ConsumeField<uint8_t>(in);
|
||||
in = in.subspan(sizeof(uint8_t)); // Skip the "reserved" byte.
|
||||
const int block_data_size =
|
||||
static_cast<int>(ConsumeField<uint16_t>(in)) * 4;
|
||||
if (static_cast<int>(in.size()) < block_data_size) {
|
||||
return false;
|
||||
}
|
||||
if (block_type == kRtcpReceiverReferenceTimeReportBlockType) {
|
||||
if (block_data_size != sizeof(uint64_t)) {
|
||||
return false; // Length field must always be 2 words.
|
||||
}
|
||||
receiver_reference_time = session_->ntp_converter().ToLocalTime(
|
||||
ReadBigEndian<uint64_t>(in.data()));
|
||||
} else {
|
||||
// Ignore any other type of extended report.
|
||||
}
|
||||
in = in.subspan(block_data_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompoundRtcpParser::ParsePictureLossIndicator(
|
||||
ByteView in,
|
||||
bool& picture_loss_indicator) {
|
||||
if (static_cast<int>(in.size()) < kRtcpPictureLossIndicatorHeaderSize) {
|
||||
return false;
|
||||
}
|
||||
// Only set the flag if the PLI is from the Receiver and to this Sender.
|
||||
if (ConsumeField<uint32_t>(in) == session_->receiver_ssrc() &&
|
||||
ConsumeField<uint32_t>(in) == session_->sender_ssrc()) {
|
||||
picture_loss_indicator = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CompoundRtcpParser::Client::Client() = default;
|
||||
CompoundRtcpParser::Client::~Client() = default;
|
||||
void CompoundRtcpParser::Client::OnReceiverReferenceTimeAdvanced(
|
||||
Clock::time_point reference_time) {}
|
||||
void CompoundRtcpParser::Client::OnReceiverReport(
|
||||
const RtcpReportBlock& receiver_report) {}
|
||||
void CompoundRtcpParser::Client::OnCastReceiverFrameLogMessages(
|
||||
std::vector<RtcpReceiverFrameLogMessage> messages) {}
|
||||
void CompoundRtcpParser::Client::OnReceiverIndicatesPictureLoss() {}
|
||||
void CompoundRtcpParser::Client::OnReceiverCheckpoint(
|
||||
FrameId frame_id,
|
||||
milliseconds playout_delay) {}
|
||||
void CompoundRtcpParser::Client::OnReceiverHasFrames(
|
||||
std::vector<FrameId> acks) {}
|
||||
void CompoundRtcpParser::Client::OnReceiverIsMissingPackets(
|
||||
std::vector<PacketNack> nacks) {}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
135
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/compound_rtcp_parser.h
vendored
Normal file
135
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/compound_rtcp_parser.h
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2019 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_COMPOUND_RTCP_PARSER_H_
|
||||
#define CAST_STREAMING_IMPL_COMPOUND_RTCP_PARSER_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/impl/rtcp_common.h"
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
#include "cast/streaming/public/frame_id.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/raw_ref.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
class RtcpSession;
|
||||
|
||||
// Parses compound RTCP packets from a Receiver, invoking client callbacks when
|
||||
// information of interest to a Sender (in the current process) is encountered.
|
||||
class CompoundRtcpParser {
|
||||
public:
|
||||
// Callback interface used while parsing RTCP packets of interest to a Sender.
|
||||
// The implementation must take into account:
|
||||
//
|
||||
// 1. Some/All of the data could be stale, as it only reflects the state of
|
||||
// the Receiver at the time the packet was generated. A significant
|
||||
// amount of time may have passed, depending on how long it took the
|
||||
// packet to reach this local instance over the network.
|
||||
// 2. The data shouldn't necessarily be trusted blindly: Some may be
|
||||
// inconsistent (e.g., the same frame being ACKed and NACKed; or a frame
|
||||
// that has not been sent yet is being NACKed). While that would indicate
|
||||
// a badly-behaving Receiver, the Sender should be robust to such things.
|
||||
class Client {
|
||||
public:
|
||||
Client();
|
||||
|
||||
// Called when a Receiver Reference Time Report has been parsed.
|
||||
virtual void OnReceiverReferenceTimeAdvanced(
|
||||
Clock::time_point reference_time);
|
||||
|
||||
// Called when a Receiver Report with a Report Block has been parsed.
|
||||
virtual void OnReceiverReport(const RtcpReportBlock& receiver_report);
|
||||
|
||||
// Called when a group of Cast Receiver frame log messages has been parsed.
|
||||
virtual void OnCastReceiverFrameLogMessages(
|
||||
std::vector<RtcpReceiverFrameLogMessage> messages);
|
||||
|
||||
// Called when the Receiver has encountered an unrecoverable error in
|
||||
// decoding the data. The Sender should provide a key frame as soon as
|
||||
// possible.
|
||||
virtual void OnReceiverIndicatesPictureLoss();
|
||||
|
||||
// Called when the Receiver indicates that all of the packets for all frames
|
||||
// up to and including `frame_id` have been successfully received (or
|
||||
// otherwise do not need to be re-transmitted). The `playout_delay` is the
|
||||
// Receiver's current end-to-end target playout delay setting, which should
|
||||
// reflect any changes the Sender has made by using the "Cast Adaptive
|
||||
// Latency Extension" in RTP packets.
|
||||
virtual void OnReceiverCheckpoint(FrameId frame_id,
|
||||
std::chrono::milliseconds playout_delay);
|
||||
|
||||
// Called to indicate the Receiver has successfully received all of the
|
||||
// packets for each of the given `acks`. The argument's elements are in
|
||||
// monotonically increasing order.
|
||||
virtual void OnReceiverHasFrames(std::vector<FrameId> acks);
|
||||
|
||||
// Called to indicate the Receiver is missing certain specific packets for
|
||||
// certain specific frames. Any elements where the packet_id is
|
||||
// kAllPacketsLost indicates that all the packets are missing for a frame.
|
||||
// The argument's elements are in monotonically increasing order.
|
||||
virtual void OnReceiverIsMissingPackets(std::vector<PacketNack> nacks);
|
||||
|
||||
protected:
|
||||
virtual ~Client();
|
||||
};
|
||||
|
||||
// `session` and `client` must be non-null and must outlive the
|
||||
// CompoundRtcpParser instance.
|
||||
CompoundRtcpParser(RtcpSession& session, Client& client);
|
||||
~CompoundRtcpParser();
|
||||
|
||||
// Parses the packet, invoking the Client callback methods when appropriate.
|
||||
// Returns true if the `packet` was well-formed, or false if it was corrupt.
|
||||
// Note that none of the Client callback methods will be invoked until a
|
||||
// packet is known to be well-formed.
|
||||
//
|
||||
// `max_feedback_frame_id` is the maximum-valued FrameId that could possibly
|
||||
// be ACKnowledged by the Receiver, if there is Cast Feedback in the `packet`.
|
||||
// This is needed for expanding truncated frame IDs correctly.
|
||||
bool Parse(ByteView packet, FrameId max_feedback_frame_id);
|
||||
|
||||
private:
|
||||
// These return true if the input was well-formed, and false if it was
|
||||
// invalid/corrupt. The true/false value does NOT indicate whether the data
|
||||
// contained within was ignored. Output arguments are only modified if the
|
||||
// input contained the relevant field(s).
|
||||
bool ParseReceiverReport(ByteView in,
|
||||
int num_report_blocks,
|
||||
std::optional<RtcpReportBlock>& receiver_report);
|
||||
bool ParseApplicationDefined(
|
||||
RtcpSubtype subtype,
|
||||
ByteView in,
|
||||
std::vector<RtcpReceiverFrameLogMessage>& messages);
|
||||
bool ParseFrameLogMessages(
|
||||
ByteView in,
|
||||
std::vector<RtcpReceiverFrameLogMessage>& messages);
|
||||
bool ParseFeedback(ByteView in,
|
||||
FrameId max_feedback_frame_id,
|
||||
FrameId* checkpoint_frame_id,
|
||||
std::chrono::milliseconds* target_playout_delay,
|
||||
std::vector<FrameId>* received_frames,
|
||||
std::vector<PacketNack>& packet_nacks);
|
||||
bool ParseExtendedReports(ByteView in,
|
||||
Clock::time_point& receiver_reference_time);
|
||||
bool ParsePictureLossIndicator(ByteView in, bool& picture_loss_indicator);
|
||||
|
||||
const raw_ref<RtcpSession> session_;
|
||||
const raw_ref<Client> client_;
|
||||
|
||||
// Tracks the latest timestamp seen from any Receiver Reference Time Report,
|
||||
// and uses this to ignore stale RTCP packets that arrived out-of-order and/or
|
||||
// late from the network.
|
||||
Clock::time_point latest_receiver_timestamp_;
|
||||
|
||||
// Tracks the last parsed RTP timestamp seen from any Cast receiver frame log.
|
||||
RtpTimeTicks latest_frame_log_rtp_timestamp_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_COMPOUND_RTCP_PARSER_H_
|
||||
174
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/expanded_value_base.h
vendored
Normal file
174
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/expanded_value_base.h
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright 2015 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_EXPANDED_VALUE_BASE_H_
|
||||
#define CAST_STREAMING_IMPL_EXPANDED_VALUE_BASE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Abstract base template class for common "sequence value" data types such as
|
||||
// RtpTimeTicks, FrameId, or PacketId which generally increment/decrement in
|
||||
// predictable amounts as media is streamed, and which often need to be reliably
|
||||
// truncated and re-expanded for over-the-wire transmission.
|
||||
//
|
||||
// FullWidthInteger should be a signed integer POD type that is of sufficiently
|
||||
// high width (in bits) such that it is never expected to under/overflow during
|
||||
// the longest reasonable length of continuous system operation. Subclass is
|
||||
// the class inheriting the common functionality provided in this template, and
|
||||
// is used to provide operator overloads. The Subclass must friend this class
|
||||
// to enable these operator overloads.
|
||||
//
|
||||
// Please see RtpTimeTicks and unit test code for examples of how to define
|
||||
// Subclasses and add features specific to their concrete data type, and how to
|
||||
// use data types derived from ExpandedValueBase. For example, a RtpTimeTicks
|
||||
// adds math operators consisting of the meaningful and valid set of operations
|
||||
// allowed for doing "time math." On the other hand, FrameId only adds math
|
||||
// operators for incrementing/decrementing since multiplication and division are
|
||||
// meaningless.
|
||||
template <typename FullWidthInteger, class Subclass>
|
||||
class ExpandedValueBase {
|
||||
static_assert(std::numeric_limits<FullWidthInteger>::is_signed,
|
||||
"FullWidthInteger must be a signed integer.");
|
||||
static_assert(std::numeric_limits<FullWidthInteger>::is_integer,
|
||||
"FullWidthInteger must be a signed integer.");
|
||||
|
||||
public:
|
||||
// Methods that return the lower bits of this value. This should only be used
|
||||
// for serializing/wire-formatting, and not to subvert the restricted set of
|
||||
// operators allowed on this data type.
|
||||
constexpr uint8_t lower_8_bits() const {
|
||||
return static_cast<uint8_t>(value_);
|
||||
}
|
||||
constexpr uint16_t lower_16_bits() const {
|
||||
return static_cast<uint16_t>(value_);
|
||||
}
|
||||
constexpr uint32_t lower_32_bits() const {
|
||||
return static_cast<uint32_t>(value_);
|
||||
}
|
||||
|
||||
// Compute the greatest value less than or equal to `this` value whose lower
|
||||
// bits are those of `x`. The purpose of this method is to re-instantiate an
|
||||
// original value from its truncated form, usually when deserializing
|
||||
// off-the-wire, when `this` value is known to be the greatest possible valid
|
||||
// value.
|
||||
//
|
||||
// Use case example: Start with an original 32-bit value of 0x000001fe (510
|
||||
// decimal) and truncate, throwing away its upper 24 bits: 0xfe. Now, send
|
||||
// this truncated value over-the-wire to a peer who needs to expand it back to
|
||||
// the original 32-bit value. The peer knows that the greatest possible valid
|
||||
// value is 0x00000202 (514 decimal). This method will initially attempt to
|
||||
// just concatenate the upper 24 bits of |this->value_| with |x| (the 8-bit
|
||||
// value), and get a result of 0x000002fe (766 decimal). However, this is
|
||||
// greater than |this->value_|, so the upper 24 bits are subtracted by one to
|
||||
// get 0x000001fe, which is the original value.
|
||||
template <typename ShortUnsigned>
|
||||
Subclass ExpandLessThanOrEqual(ShortUnsigned x) const {
|
||||
static_assert(!std::numeric_limits<ShortUnsigned>::is_signed,
|
||||
"`x` must be an unsigned integer.");
|
||||
static_assert(std::numeric_limits<ShortUnsigned>::is_integer,
|
||||
"`x` must be an unsigned integer.");
|
||||
static_assert(sizeof(ShortUnsigned) <= sizeof(FullWidthInteger),
|
||||
"`x` must fit within the FullWidthInteger.");
|
||||
|
||||
if (sizeof(ShortUnsigned) < sizeof(FullWidthInteger)) {
|
||||
// Initially, the `result` is composed of upper bits from `value_` and
|
||||
// lower bits from `x`.
|
||||
const FullWidthInteger short_max =
|
||||
std::numeric_limits<ShortUnsigned>::max();
|
||||
FullWidthInteger result = (value_ & ~short_max) | x;
|
||||
|
||||
// If the `result` is larger than `value_`, decrement the upper bits by
|
||||
// one. In other words, `x` must always be interpreted as a truncated
|
||||
// version of a value less than or equal to `value_`.
|
||||
if (result > value_)
|
||||
result -= short_max + 1;
|
||||
|
||||
return Subclass(result);
|
||||
} else {
|
||||
// Debug builds: Ensure the highest bit is not set (which would cause
|
||||
// overflow when casting to the signed integer).
|
||||
OSP_CHECK_EQ(
|
||||
static_cast<ShortUnsigned>(0),
|
||||
x & (static_cast<ShortUnsigned>(1) << ((sizeof(x) * 8) - 1)));
|
||||
return Subclass(x);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the smallest value greater than `this` value whose lower bits are
|
||||
// those of `x`.
|
||||
template <typename ShortUnsigned>
|
||||
Subclass ExpandGreaterThan(ShortUnsigned x) const {
|
||||
const Subclass maximum_possible_result(
|
||||
value_ + std::numeric_limits<ShortUnsigned>::max() + 1);
|
||||
return maximum_possible_result.ExpandLessThanOrEqual(x);
|
||||
}
|
||||
|
||||
// Compute the value closest to `this` value whose lower bits are those of
|
||||
// `x`. The result is always within `max_distance_for_expansion()` of `this`
|
||||
// value. The purpose of this method is to re-instantiate an original value
|
||||
// from its truncated form, usually when deserializing off-the-wire. See
|
||||
// comments for ExpandLessThanOrEqual() above for further explanation.
|
||||
template <typename ShortUnsigned>
|
||||
Subclass Expand(ShortUnsigned x) const {
|
||||
const Subclass maximum_possible_result(
|
||||
value_ + max_distance_for_expansion<ShortUnsigned>());
|
||||
return maximum_possible_result.ExpandLessThanOrEqual(x);
|
||||
}
|
||||
|
||||
// Comparison operators.
|
||||
constexpr bool operator==(const ExpandedValueBase& rhs) const {
|
||||
return value_ == rhs.value_;
|
||||
}
|
||||
constexpr bool operator!=(const ExpandedValueBase& rhs) const {
|
||||
return value_ != rhs.value_;
|
||||
}
|
||||
constexpr bool operator<(const ExpandedValueBase& rhs) const {
|
||||
return value_ < rhs.value_;
|
||||
}
|
||||
constexpr bool operator>(const ExpandedValueBase& rhs) const {
|
||||
return value_ > rhs.value_;
|
||||
}
|
||||
constexpr bool operator<=(const ExpandedValueBase& rhs) const {
|
||||
return value_ <= rhs.value_;
|
||||
}
|
||||
constexpr bool operator>=(const ExpandedValueBase& rhs) const {
|
||||
return value_ >= rhs.value_;
|
||||
}
|
||||
|
||||
// (De)Serialize for transmission over IPC. Do not use these to subvert the
|
||||
// valid set of operators allowed by this class or its Subclass.
|
||||
uint64_t SerializeForIPC() const {
|
||||
static_assert(sizeof(uint64_t) >= sizeof(FullWidthInteger),
|
||||
"Cannot serialize FullWidthInteger into an uint64_t.");
|
||||
return static_cast<uint64_t>(value_);
|
||||
}
|
||||
static Subclass DeserializeForIPC(uint64_t serialized) {
|
||||
return Subclass(static_cast<FullWidthInteger>(serialized));
|
||||
}
|
||||
|
||||
// Design limit: Values that are truncated to the ShortUnsigned type must be
|
||||
// no more than this maximum distance from each other in order to ensure the
|
||||
// original value can be determined correctly.
|
||||
template <typename ShortUnsigned>
|
||||
static constexpr FullWidthInteger max_distance_for_expansion() {
|
||||
return std::numeric_limits<ShortUnsigned>::max() / 2;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Only subclasses are permitted to instantiate directly.
|
||||
constexpr explicit ExpandedValueBase(FullWidthInteger value)
|
||||
: value_(value) {}
|
||||
|
||||
FullWidthInteger value_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_EXPANDED_VALUE_BASE_H_
|
||||
106
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/frame_crypto.cc
vendored
Normal file
106
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/frame_crypto.cc
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/frame_crypto.h"
|
||||
|
||||
#include <random>
|
||||
#include <utility>
|
||||
|
||||
#include "openssl/crypto.h"
|
||||
#include "openssl/err.h"
|
||||
#include "openssl/rand.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/big_endian.h"
|
||||
#include "util/crypto/openssl_util.h"
|
||||
#include "util/crypto/random_bytes.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
EncryptedFrame::EncryptedFrame() {
|
||||
data = owned_data_;
|
||||
}
|
||||
|
||||
EncryptedFrame::~EncryptedFrame() = default;
|
||||
|
||||
EncryptedFrame::EncryptedFrame(EncryptedFrame&& other) noexcept
|
||||
: EncodedFrame(static_cast<EncodedFrame&&>(other)),
|
||||
owned_data_(std::move(other.owned_data_)) {
|
||||
data = owned_data_;
|
||||
other.data = ByteView();
|
||||
}
|
||||
|
||||
EncryptedFrame& EncryptedFrame::operator=(EncryptedFrame&& other) {
|
||||
this->EncodedFrame::operator=(static_cast<EncodedFrame&&>(other));
|
||||
owned_data_ = std::move(other.owned_data_);
|
||||
data = owned_data_;
|
||||
other.data = ByteView();
|
||||
return *this;
|
||||
}
|
||||
|
||||
FrameCrypto::FrameCrypto(const std::array<uint8_t, 16>& aes_key,
|
||||
const std::array<uint8_t, 16>& cast_iv_mask)
|
||||
: aes_key_{}, cast_iv_mask_(cast_iv_mask) {
|
||||
// Ensure that the library has been initialized. CRYPTO_library_init() may be
|
||||
// safely called multiple times during the life of a process.
|
||||
CRYPTO_library_init();
|
||||
|
||||
// Initialize the 244-byte AES_KEY struct once, here at construction time. The
|
||||
// const_cast<> is reasonable as this is a one-time-ctor-initialized value
|
||||
// that will remain constant from here onward.
|
||||
const int return_code = AES_set_encrypt_key(
|
||||
aes_key.data(), aes_key.size() * 8, const_cast<AES_KEY*>(&aes_key_));
|
||||
if (return_code != 0) {
|
||||
ClearOpenSSLERRStack(CURRENT_LOCATION);
|
||||
OSP_LOG_FATAL << "Failure when setting encryption key; unsafe to continue.";
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
FrameCrypto::~FrameCrypto() = default;
|
||||
|
||||
EncryptedFrame FrameCrypto::Encrypt(const EncodedFrame& encoded_frame) const {
|
||||
EncryptedFrame result;
|
||||
encoded_frame.CopyMetadataTo(&result);
|
||||
result.owned_data_.resize(encoded_frame.data.size());
|
||||
result.data = result.owned_data_;
|
||||
Crypt(encoded_frame.frame_id, {&encoded_frame.data, 1}, result.owned_data_);
|
||||
return result;
|
||||
}
|
||||
|
||||
void FrameCrypto::Decrypt(FrameId frame_id,
|
||||
ChunkList chunks,
|
||||
ByteBuffer out) const {
|
||||
Crypt(frame_id, chunks, out);
|
||||
}
|
||||
|
||||
void FrameCrypto::Crypt(FrameId frame_id,
|
||||
ChunkList chunks,
|
||||
ByteBuffer out) const {
|
||||
OSP_CHECK(!frame_id.is_null());
|
||||
|
||||
// Compute the AES nonce for Cast Streaming payload encryption, which is based
|
||||
// on the `frame_id`.
|
||||
std::array<uint8_t, 16> aes_nonce{};
|
||||
static_assert(AES_BLOCK_SIZE == sizeof(aes_nonce),
|
||||
"AES_BLOCK_SIZE is not 16 bytes.");
|
||||
WriteBigEndian<uint32_t>(frame_id.lower_32_bits(), aes_nonce.data() + 8);
|
||||
for (size_t i = 0; i < aes_nonce.size(); ++i) {
|
||||
aes_nonce[i] ^= cast_iv_mask_[i];
|
||||
}
|
||||
|
||||
std::array<uint8_t, 16> ecount_buf{};
|
||||
unsigned int block_offset = 0;
|
||||
size_t out_offset = 0;
|
||||
for (ByteView chunk : chunks) {
|
||||
OSP_CHECK_LE(out_offset + chunk.size(), out.size());
|
||||
AES_ctr128_encrypt(chunk.data(), out.data() + out_offset, chunk.size(),
|
||||
&aes_key_, aes_nonce.data(), ecount_buf.data(),
|
||||
&block_offset);
|
||||
out_offset += chunk.size();
|
||||
}
|
||||
OSP_CHECK_EQ(out_offset, out.size());
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
78
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/frame_crypto.h
vendored
Normal file
78
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/frame_crypto.h
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2019 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_FRAME_CRYPTO_H_
|
||||
#define CAST_STREAMING_IMPL_FRAME_CRYPTO_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/public/encoded_frame.h"
|
||||
#include "openssl/aes.h"
|
||||
#include "platform/base/span.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
class FrameCrypto;
|
||||
|
||||
// A subclass of EncodedFrame that represents an EncodedFrame with encrypted
|
||||
// payload data, and owns the buffer storing the encrypted payload data. Use
|
||||
// FrameCrypto (below) to explicitly convert between EncryptedFrames and
|
||||
// EncodedFrames.
|
||||
struct EncryptedFrame : public EncodedFrame {
|
||||
EncryptedFrame();
|
||||
~EncryptedFrame();
|
||||
EncryptedFrame(EncryptedFrame&&) noexcept;
|
||||
EncryptedFrame& operator=(EncryptedFrame&&);
|
||||
|
||||
protected:
|
||||
// Since only FrameCrypto is trusted to generate the
|
||||
// payload data, it is allowed direct access to the storage.
|
||||
friend class FrameCrypto;
|
||||
|
||||
// Note: EncodedFrame::data must be updated whenever any mutations are
|
||||
// performed on this member!
|
||||
std::vector<uint8_t> owned_data_;
|
||||
};
|
||||
|
||||
// Encrypts EncodedFrames before sending, or decrypts EncryptedFrames that have
|
||||
// been received.
|
||||
class FrameCrypto {
|
||||
public:
|
||||
using ChunkList = std::span<const ByteView>;
|
||||
|
||||
// Construct with the given 16-bytes AES key and IV mask. Both arguments
|
||||
// should be randomly-generated for each new streaming session.
|
||||
// GenerateRandomBytes() can be used to create them.
|
||||
FrameCrypto(const std::array<uint8_t, 16>& aes_key,
|
||||
const std::array<uint8_t, 16>& cast_iv_mask);
|
||||
|
||||
~FrameCrypto();
|
||||
|
||||
EncryptedFrame Encrypt(const EncodedFrame& encoded_frame) const;
|
||||
|
||||
// Decrypts `chunks` into `out`. `out` must have a sufficiently-sized
|
||||
// data buffer.
|
||||
void Decrypt(FrameId frame_id, ChunkList chunks, ByteBuffer out) const;
|
||||
|
||||
private:
|
||||
// The 244-byte AES_KEY struct, derived from the `aes_key` passed to the ctor,
|
||||
// and initialized by boringssl's AES_set_encrypt_key() function.
|
||||
const AES_KEY aes_key_;
|
||||
|
||||
// Random bytes used in the custom heuristic to generate a different
|
||||
// initialization vector for each frame.
|
||||
const std::array<uint8_t, 16> cast_iv_mask_;
|
||||
|
||||
// AES-CTR is symmetric. Thus, the "meat" of both Encrypt() and Decrypt() is
|
||||
// the same.
|
||||
void Crypt(FrameId frame_id, ChunkList chunks, ByteBuffer out) const;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_FRAME_CRYPTO_H_
|
||||
15
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/message_constants.h
vendored
Normal file
15
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/message_constants.h
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// 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_MESSAGE_CONSTANTS_H_
|
||||
#define CAST_STREAMING_IMPL_MESSAGE_CONSTANTS_H_
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// RTP extension strings.
|
||||
inline constexpr char kInputEventsRtpExtension[] = "input_events";
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_MESSAGE_CONSTANTS_H_
|
||||
54
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/ntp_time.cc
vendored
Normal file
54
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/ntp_time.cc
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/ntp_time.h"
|
||||
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
// The number of seconds between 1 January 1900 and 1 January 1970.
|
||||
constexpr NtpSeconds kTimeBetweenNtpEpochAndUnixEpoch(2208988800);
|
||||
|
||||
} // namespace
|
||||
|
||||
NtpTimeConverter::NtpTimeConverter(Clock::time_point now,
|
||||
std::chrono::seconds since_unix_epoch)
|
||||
: start_time_(now),
|
||||
since_ntp_epoch_(
|
||||
std::chrono::duration_cast<NtpSeconds>(since_unix_epoch) +
|
||||
kTimeBetweenNtpEpochAndUnixEpoch) {}
|
||||
|
||||
NtpTimeConverter::~NtpTimeConverter() = default;
|
||||
|
||||
NtpTimestamp NtpTimeConverter::ToNtpTimestamp(
|
||||
Clock::time_point time_point) const {
|
||||
const Clock::duration time_since_start = time_point - start_time_;
|
||||
const auto whole_seconds =
|
||||
std::chrono::duration_cast<NtpSeconds>(time_since_start);
|
||||
const auto remainder =
|
||||
std::chrono::duration_cast<NtpFraction>(time_since_start - whole_seconds);
|
||||
return AssembleNtpTimestamp(since_ntp_epoch_ + whole_seconds, remainder);
|
||||
}
|
||||
|
||||
Clock::time_point NtpTimeConverter::ToLocalTime(NtpTimestamp timestamp) const {
|
||||
auto ntp_seconds = NtpSecondsPart(timestamp);
|
||||
// Year 2036 wrap-around check: If the NTP timestamp appears to be a
|
||||
// point-in-time before 1970, assume the 2036 wrap-around has occurred, and
|
||||
// adjust to compensate.
|
||||
if (ntp_seconds <= kTimeBetweenNtpEpochAndUnixEpoch) {
|
||||
constexpr NtpSeconds kNtpSecondsPerEra{INT64_C(1) << 32};
|
||||
ntp_seconds += kNtpSecondsPerEra;
|
||||
}
|
||||
|
||||
const auto whole_seconds = ntp_seconds - since_ntp_epoch_;
|
||||
const auto seconds_since_start =
|
||||
Clock::to_duration(whole_seconds) + start_time_;
|
||||
const auto remainder = Clock::to_duration(NtpFractionPart(timestamp));
|
||||
return seconds_since_start + remainder;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
73
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/ntp_time.h
vendored
Normal file
73
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/ntp_time.h
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright 2019 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_NTP_TIME_H_
|
||||
#define CAST_STREAMING_IMPL_NTP_TIME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "platform/api/time.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// NTP timestamps are 64-bit timestamps that consist of two 32-bit parts: 1) The
|
||||
// number of seconds since 1 January 1900; and 2) The fraction of the second,
|
||||
// where 0 maps to 0x00000000 and each unit increment represents another 2^-32
|
||||
// seconds.
|
||||
//
|
||||
// Note that it is part of the design of NTP for the seconds part to roll around
|
||||
// on 7 February 2036.
|
||||
using NtpTimestamp = uint64_t;
|
||||
|
||||
// NTP fixed-point time math: Declare two std::chrono::duration types with the
|
||||
// bit-width necessary to reliably perform all conversions to/from NTP format.
|
||||
using NtpSeconds = std::chrono::duration<int64_t, std::chrono::seconds::period>;
|
||||
using NtpFraction =
|
||||
std::chrono::duration<int64_t, std::ratio<1, INT64_C(0x100000000)>>;
|
||||
|
||||
constexpr NtpSeconds NtpSecondsPart(NtpTimestamp timestamp) {
|
||||
return NtpSeconds(timestamp >> 32);
|
||||
}
|
||||
|
||||
constexpr NtpFraction NtpFractionPart(NtpTimestamp timestamp) {
|
||||
return NtpFraction(timestamp & 0xffffffff);
|
||||
}
|
||||
|
||||
constexpr NtpTimestamp AssembleNtpTimestamp(NtpSeconds seconds,
|
||||
NtpFraction fraction) {
|
||||
return (static_cast<uint64_t>(seconds.count()) << 32) |
|
||||
static_cast<uint32_t>(fraction.count());
|
||||
}
|
||||
|
||||
// Converts between Clock::time_points and NtpTimestamps. The class is
|
||||
// instantiated with the current Clock time and the current wall clock time, and
|
||||
// these are used to determine a fixed origin reference point for all
|
||||
// conversions. Thus, to avoid introducing unintended timing-related behaviors,
|
||||
// only one NtpTimeConverter instance should be used for converting all the NTP
|
||||
// timestamps in the same streaming session.
|
||||
class NtpTimeConverter {
|
||||
public:
|
||||
NtpTimeConverter(
|
||||
Clock::time_point now,
|
||||
std::chrono::seconds since_unix_epoch = GetWallTimeSinceUnixEpoch());
|
||||
~NtpTimeConverter();
|
||||
|
||||
NtpTimestamp ToNtpTimestamp(Clock::time_point time_point) const;
|
||||
Clock::time_point ToLocalTime(NtpTimestamp timestamp) const;
|
||||
|
||||
private:
|
||||
// The time point on the platform clock's timeline that corresponds to
|
||||
// approximately the same time point on the NTP timeline. Note that it is
|
||||
// acceptable for the granularity of the NTP seconds value to be whole seconds
|
||||
// here: Both a Cast Streaming Sender and Receiver will assume their clocks
|
||||
// can be off (with respect to each other) by even a large amount; and all
|
||||
// that matters is that time ticks forward at a reasonable pace from some
|
||||
// initial point.
|
||||
const Clock::time_point start_time_;
|
||||
const NtpSeconds since_ntp_epoch_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_NTP_TIME_H_
|
||||
39
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/packet_util.cc
vendored
Normal file
39
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/packet_util.cc
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/packet_util.h"
|
||||
|
||||
#include "cast/streaming/impl/rtcp_common.h"
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
std::pair<ApparentPacketType, Ssrc> InspectPacketForRouting(ByteView packet) {
|
||||
// Check for RTP packets first, since they are more frequent.
|
||||
if (packet.size() >= kRtpPacketMinValidSize &&
|
||||
packet[0] == kRtpRequiredFirstByte &&
|
||||
IsRtpPayloadType(packet[1] & kRtpPayloadTypeMask)) {
|
||||
constexpr int kOffsetToSsrcField = 8;
|
||||
return std::make_pair(
|
||||
ApparentPacketType::RTP,
|
||||
Ssrc{ReadBigEndian<uint32_t>(packet.data() + kOffsetToSsrcField)});
|
||||
}
|
||||
|
||||
// While RTCP packets are valid if they consist of just the RTCP Common
|
||||
// Header, all the RTCP packet types processed by this implementation will
|
||||
// also have a SSRC field immediately following the header. This is important
|
||||
// for routing the packet to the correct parser instance.
|
||||
constexpr int kRtcpPacketMinAcceptableSize =
|
||||
kRtcpCommonHeaderSize + sizeof(uint32_t);
|
||||
if (packet.size() >= kRtcpPacketMinAcceptableSize &&
|
||||
RtcpCommonHeader::Parse(packet).has_value()) {
|
||||
return std::make_pair(
|
||||
ApparentPacketType::RTCP,
|
||||
Ssrc{ReadBigEndian<uint32_t>(packet.data() + kRtcpCommonHeaderSize)});
|
||||
}
|
||||
|
||||
return std::make_pair(ApparentPacketType::UNKNOWN, Ssrc{0});
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
60
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/packet_util.h
vendored
Normal file
60
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/packet_util.h
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2019 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_PACKET_UTIL_H_
|
||||
#define CAST_STREAMING_IMPL_PACKET_UTIL_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/ssrc.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/big_endian.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Reads a field from the start of the given span and advances the span to point
|
||||
// just after the field.
|
||||
template <typename Integer>
|
||||
inline Integer ConsumeField(ByteView& in) {
|
||||
OSP_CHECK_GE(in.size(), sizeof(Integer));
|
||||
const Integer result = ReadBigEndian<Integer>(in.data());
|
||||
in = in.subspan(sizeof(Integer));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Writes a field at the start of the given span and advances the span to point
|
||||
// just after the field.
|
||||
template <typename Integer>
|
||||
inline void AppendField(Integer value, ByteBuffer& out) {
|
||||
WriteBigEndian<Integer>(value, out.data());
|
||||
out = out.subspan(sizeof(Integer));
|
||||
}
|
||||
|
||||
// Returns a bitmask for a field having the given number of bits. For example,
|
||||
// FieldBitmask<uint8_t>(5) returns 0b00011111.
|
||||
template <typename Integer>
|
||||
constexpr Integer FieldBitmask(unsigned field_size_in_bits) {
|
||||
return (Integer{1} << field_size_in_bits) - 1;
|
||||
}
|
||||
|
||||
// Reserves `num_bytes` from the beginning of the given span, returning the
|
||||
// reserved space.
|
||||
inline ByteBuffer ReserveSpace(int num_bytes, ByteBuffer& out) {
|
||||
const ByteBuffer reserved = out.subspan(0, num_bytes);
|
||||
out = out.subspan(num_bytes);
|
||||
return reserved;
|
||||
}
|
||||
|
||||
// Performs a quick-scan of the packet data for the purposes of routing it to an
|
||||
// appropriate parser. Identifies whether the packet is a RTP packet, RTCP
|
||||
// packet, or unknown; and provides the originator's SSRC. This only performs a
|
||||
// very quick scan of the packet data, and does not guarantee that a full parse
|
||||
// will later succeed.
|
||||
enum class ApparentPacketType { UNKNOWN, RTP, RTCP };
|
||||
std::pair<ApparentPacketType, Ssrc> InspectPacketForRouting(ByteView packet);
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_PACKET_UTIL_H_
|
||||
241
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtcp_common.cc
vendored
Normal file
241
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtcp_common.cc
vendored
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/rtcp_common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include "cast/streaming/impl/packet_util.h"
|
||||
#include "util/saturate_cast.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
RtcpCommonHeader::RtcpCommonHeader() = default;
|
||||
RtcpCommonHeader::~RtcpCommonHeader() = default;
|
||||
|
||||
void RtcpCommonHeader::AppendFields(ByteBuffer& buffer) const {
|
||||
OSP_CHECK_GE(buffer.size(), kRtcpCommonHeaderSize);
|
||||
|
||||
uint8_t byte0 = kRtcpRequiredVersionAndPaddingBits
|
||||
<< kRtcpReportCountFieldNumBits;
|
||||
switch (packet_type) {
|
||||
case RtcpPacketType::kSenderReport:
|
||||
case RtcpPacketType::kReceiverReport:
|
||||
OSP_CHECK_LE(with.report_count,
|
||||
FieldBitmask<int>(kRtcpReportCountFieldNumBits));
|
||||
byte0 |= with.report_count;
|
||||
break;
|
||||
case RtcpPacketType::kSourceDescription:
|
||||
OSP_UNIMPLEMENTED();
|
||||
break;
|
||||
case RtcpPacketType::kApplicationDefined:
|
||||
case RtcpPacketType::kPayloadSpecific:
|
||||
switch (with.subtype) {
|
||||
case RtcpSubtype::kPictureLossIndicator:
|
||||
case RtcpSubtype::kFeedback:
|
||||
case RtcpSubtype::kReceiverLog:
|
||||
byte0 |= static_cast<uint8_t>(with.subtype);
|
||||
break;
|
||||
|
||||
// We should not be creating application or payload specific packets
|
||||
// with an unknown or null subtype -- they will just be ignored.
|
||||
case RtcpSubtype::kNull:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
break;
|
||||
case RtcpPacketType::kExtendedReports:
|
||||
break;
|
||||
case RtcpPacketType::kNull:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
AppendField<uint8_t>(byte0, buffer);
|
||||
|
||||
AppendField<uint8_t>(static_cast<uint8_t>(packet_type), buffer);
|
||||
|
||||
// The size of the packet must be evenly divisible by the 32-bit word size.
|
||||
OSP_CHECK_EQ(0, payload_size % sizeof(uint32_t));
|
||||
AppendField<uint16_t>(payload_size / sizeof(uint32_t), buffer);
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<RtcpCommonHeader> RtcpCommonHeader::Parse(ByteView buffer) {
|
||||
if (buffer.size() < kRtcpCommonHeaderSize) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const uint8_t byte0 = ConsumeField<uint8_t>(buffer);
|
||||
if ((byte0 >> kRtcpReportCountFieldNumBits) !=
|
||||
kRtcpRequiredVersionAndPaddingBits) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint8_t report_count_or_subtype =
|
||||
byte0 & FieldBitmask<uint8_t>(kRtcpReportCountFieldNumBits);
|
||||
|
||||
const uint8_t byte1 = ConsumeField<uint8_t>(buffer);
|
||||
if (!IsRtcpPacketType(byte1)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Optionally set `header.with.report_count` or `header.with.subtype`,
|
||||
// depending on the packet type.
|
||||
RtcpCommonHeader header;
|
||||
header.packet_type = static_cast<RtcpPacketType>(byte1);
|
||||
switch (header.packet_type) {
|
||||
case RtcpPacketType::kSenderReport:
|
||||
case RtcpPacketType::kReceiverReport:
|
||||
header.with.report_count = report_count_or_subtype;
|
||||
break;
|
||||
case RtcpPacketType::kApplicationDefined:
|
||||
case RtcpPacketType::kPayloadSpecific:
|
||||
switch (static_cast<RtcpSubtype>(report_count_or_subtype)) {
|
||||
case RtcpSubtype::kPictureLossIndicator:
|
||||
case RtcpSubtype::kReceiverLog:
|
||||
case RtcpSubtype::kFeedback:
|
||||
header.with.subtype =
|
||||
static_cast<RtcpSubtype>(report_count_or_subtype);
|
||||
break;
|
||||
default: // Unknown subtype.
|
||||
header.with.subtype = RtcpSubtype::kNull;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Neither `header.with.report_count` nor `header.with.subtype` are used.
|
||||
break;
|
||||
}
|
||||
|
||||
header.payload_size =
|
||||
static_cast<int>(ConsumeField<uint16_t>(buffer)) * sizeof(uint32_t);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
RtcpReportBlock::RtcpReportBlock() = default;
|
||||
RtcpReportBlock::~RtcpReportBlock() = default;
|
||||
|
||||
void RtcpReportBlock::AppendFields(ByteBuffer& buffer) const {
|
||||
OSP_CHECK_GE(buffer.size(), kRtcpReportBlockSize);
|
||||
|
||||
AppendField<uint32_t>(ssrc, buffer);
|
||||
OSP_CHECK_GE(packet_fraction_lost_numerator,
|
||||
std::numeric_limits<uint8_t>::min());
|
||||
OSP_CHECK_LE(packet_fraction_lost_numerator,
|
||||
std::numeric_limits<uint8_t>::max());
|
||||
OSP_CHECK_GE(cumulative_packets_lost, 0);
|
||||
OSP_CHECK_LE(cumulative_packets_lost,
|
||||
FieldBitmask<int>(kRtcpCumulativePacketsFieldNumBits));
|
||||
AppendField<uint32_t>(
|
||||
(static_cast<int>(packet_fraction_lost_numerator)
|
||||
<< kRtcpCumulativePacketsFieldNumBits) |
|
||||
(static_cast<int>(cumulative_packets_lost) &
|
||||
FieldBitmask<uint32_t>(kRtcpCumulativePacketsFieldNumBits)),
|
||||
buffer);
|
||||
AppendField<uint32_t>(extended_high_sequence_number, buffer);
|
||||
const int64_t jitter_ticks = jitter / RtpTimeDelta::FromTicks(1);
|
||||
OSP_CHECK_GE(jitter_ticks, 0);
|
||||
OSP_CHECK_LE(jitter_ticks, int64_t{std::numeric_limits<uint32_t>::max()});
|
||||
AppendField<uint32_t>(jitter_ticks, buffer);
|
||||
AppendField<uint32_t>(last_status_report_id, buffer);
|
||||
const int64_t delay_ticks = delay_since_last_report.count();
|
||||
OSP_CHECK_GE(delay_ticks, 0);
|
||||
OSP_CHECK_LE(delay_ticks, int64_t{std::numeric_limits<uint32_t>::max()});
|
||||
AppendField<uint32_t>(delay_ticks, buffer);
|
||||
}
|
||||
|
||||
void RtcpReportBlock::SetPacketFractionLostNumerator(
|
||||
int64_t num_apparently_sent,
|
||||
int64_t num_received) {
|
||||
if (num_apparently_sent <= 0) {
|
||||
packet_fraction_lost_numerator = 0;
|
||||
return;
|
||||
}
|
||||
// The following computes the fraction of packets lost as "one minus
|
||||
// `num_received` divided by `num_apparently_sent`" and scales by 256 (the
|
||||
// kPacketFractionLostDenominator). It's valid for `num_received` to be
|
||||
// greater than `num_apparently_sent` in some cases (e.g., if duplicate
|
||||
// packets were received from the network).
|
||||
const int64_t numerator =
|
||||
((num_apparently_sent - num_received) * kPacketFractionLostDenominator) /
|
||||
num_apparently_sent;
|
||||
// Since the value must be in the range [0,255], just do a saturate_cast
|
||||
// to the uint8_t type to clamp.
|
||||
packet_fraction_lost_numerator = saturate_cast<uint8_t>(numerator);
|
||||
}
|
||||
|
||||
void RtcpReportBlock::SetCumulativePacketsLost(int64_t num_apparently_sent,
|
||||
int64_t num_received) {
|
||||
const int64_t num_lost = num_apparently_sent - num_received;
|
||||
// Clamp to valid range supported by the wire format (and RTP spec).
|
||||
//
|
||||
// Note that `num_lost` can be negative if duplicate packets were received.
|
||||
// The RFC spec (https://tools.ietf.org/html/rfc3550#section-6.4.1) states
|
||||
// this should result in a clamped, "zero loss" value.
|
||||
cumulative_packets_lost = static_cast<int>(
|
||||
std::min(std::max<int64_t>(num_lost, 0),
|
||||
FieldBitmask<int64_t>(kRtcpCumulativePacketsFieldNumBits)));
|
||||
}
|
||||
|
||||
void RtcpReportBlock::SetDelaySinceLastReport(
|
||||
Clock::duration local_clock_delay) {
|
||||
// Clamp to valid range supported by the wire format (and RTP spec). The
|
||||
// bounds checking is done in terms of Clock::duration, since doing the checks
|
||||
// after the duration_cast may allow overflow to occur in the duration_cast
|
||||
// math (well, only for unusually large inputs).
|
||||
constexpr Delay kMaxValidReportedDelay(std::numeric_limits<uint32_t>::max());
|
||||
constexpr auto kMaxValidLocalClockDelay =
|
||||
Clock::to_duration(kMaxValidReportedDelay);
|
||||
if (local_clock_delay > kMaxValidLocalClockDelay) {
|
||||
delay_since_last_report = kMaxValidReportedDelay;
|
||||
return;
|
||||
}
|
||||
if (local_clock_delay <= Clock::duration::zero()) {
|
||||
delay_since_last_report = Delay::zero();
|
||||
return;
|
||||
}
|
||||
|
||||
// If this point is reached, then the `local_clock_delay` is representable as
|
||||
// a Delay within the valid range.
|
||||
delay_since_last_report =
|
||||
std::chrono::duration_cast<Delay>(local_clock_delay);
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<RtcpReportBlock> RtcpReportBlock::ParseOne(ByteView buffer,
|
||||
int report_count,
|
||||
Ssrc ssrc) {
|
||||
if (static_cast<int>(buffer.size()) < (kRtcpReportBlockSize * report_count)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<RtcpReportBlock> result;
|
||||
for (int block = 0; block < report_count; ++block) {
|
||||
if (ConsumeField<uint32_t>(buffer) != ssrc) {
|
||||
// Skip-over report block meant for some other recipient.
|
||||
buffer = buffer.subspan(kRtcpReportBlockSize - sizeof(uint32_t));
|
||||
continue;
|
||||
}
|
||||
|
||||
RtcpReportBlock& report_block = result.emplace();
|
||||
report_block.ssrc = ssrc;
|
||||
const auto second_word = ConsumeField<uint32_t>(buffer);
|
||||
report_block.packet_fraction_lost_numerator =
|
||||
second_word >> kRtcpCumulativePacketsFieldNumBits;
|
||||
report_block.cumulative_packets_lost =
|
||||
second_word &
|
||||
FieldBitmask<uint32_t>(kRtcpCumulativePacketsFieldNumBits);
|
||||
report_block.extended_high_sequence_number = ConsumeField<uint32_t>(buffer);
|
||||
report_block.jitter =
|
||||
RtpTimeDelta::FromTicks(ConsumeField<uint32_t>(buffer));
|
||||
report_block.last_status_report_id = ConsumeField<uint32_t>(buffer);
|
||||
report_block.delay_since_last_report =
|
||||
RtcpReportBlock::Delay(ConsumeField<uint32_t>(buffer));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
RtcpSenderReport::RtcpSenderReport() = default;
|
||||
RtcpSenderReport::~RtcpSenderReport() = default;
|
||||
|
||||
} // namespace openscreen::cast
|
||||
203
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtcp_common.h
vendored
Normal file
203
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtcp_common.h
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright 2019 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_RTCP_COMMON_H_
|
||||
#define CAST_STREAMING_IMPL_RTCP_COMMON_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/impl/ntp_time.h"
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "cast/streaming/public/frame_id.h"
|
||||
#include "cast/streaming/rtp_time.h"
|
||||
#include "cast/streaming/ssrc.h"
|
||||
#include "platform/base/span.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
struct RtcpCommonHeader {
|
||||
RtcpCommonHeader();
|
||||
~RtcpCommonHeader();
|
||||
|
||||
RtcpPacketType packet_type = RtcpPacketType::kNull;
|
||||
|
||||
union {
|
||||
// The number of report blocks if `packet_type` is kSenderReport or
|
||||
// kReceiverReport.
|
||||
int report_count;
|
||||
|
||||
// Indicates the type of an application-defined message if `packet_type` is
|
||||
// kApplicationDefined or kPayloadSpecific.
|
||||
RtcpSubtype subtype;
|
||||
|
||||
// Otherwise, not used.
|
||||
} with{0};
|
||||
|
||||
// The size (in bytes) of the RTCP packet, not including the header.
|
||||
int payload_size = 0;
|
||||
|
||||
// Serializes this header into the first `kRtcpCommonHeaderSize` bytes of the
|
||||
// given `buffer` and adjusts `buffer` to point to the first byte after it.
|
||||
void AppendFields(ByteBuffer& buffer) const;
|
||||
|
||||
// Parse from the 4-byte wire format in `buffer`. Returns nullopt if the data
|
||||
// is corrupt.
|
||||
static std::optional<RtcpCommonHeader> Parse(ByteView buffer);
|
||||
};
|
||||
|
||||
// The middle 32-bits of the 64-bit NtpTimestamp field from the Sender Reports.
|
||||
// This is used as an opaque identifier that the Receiver will use in its
|
||||
// reports to refer to specific previous Sender Reports.
|
||||
using StatusReportId = uint32_t;
|
||||
constexpr StatusReportId ToStatusReportId(NtpTimestamp ntp_timestamp) {
|
||||
return static_cast<uint32_t>(ntp_timestamp >> 16);
|
||||
}
|
||||
|
||||
// One of these is optionally included with a Sender Report or a Receiver
|
||||
// Report. See: https://tools.ietf.org/html/rfc3550#section-6.4.1
|
||||
struct RtcpReportBlock {
|
||||
RtcpReportBlock();
|
||||
~RtcpReportBlock();
|
||||
|
||||
// The intended recipient of this report block.
|
||||
Ssrc ssrc = 0;
|
||||
|
||||
// The fraction of RTP packets lost since the last report, specified as a
|
||||
// variable numerator and fixed denominator. The numerator will always be in
|
||||
// the range [0,255] since, semantically:
|
||||
//
|
||||
// a. Negative values are impossible.
|
||||
// b. Values greater than 255 would indicate 100% packet loss, and so a
|
||||
// report block would not be generated in the first place.
|
||||
int packet_fraction_lost_numerator = 0;
|
||||
static constexpr int kPacketFractionLostDenominator = 256;
|
||||
|
||||
// The total number of RTP packets lost since the start of the session. This
|
||||
// value will always be in the range [0,2^24-1], as the wire format only
|
||||
// provides 24 bits; so, wrap-around is possible.
|
||||
int cumulative_packets_lost = 0;
|
||||
|
||||
// The highest sequence number received in any RTP packet. Wrap-around is
|
||||
// possible.
|
||||
uint32_t extended_high_sequence_number = 0;
|
||||
|
||||
// An estimate of the recent variance in RTP packet arrival times.
|
||||
RtpTimeDelta jitter;
|
||||
|
||||
// The last Status Report received.
|
||||
StatusReportId last_status_report_id{};
|
||||
|
||||
// The delay between when the peer received the most-recent Status Report and
|
||||
// when this report was sent. The timebase is 65536 ticks per second and,
|
||||
// because of the wire format, this value will always be in the range
|
||||
// [0,65536) seconds.
|
||||
using Delay = std::chrono::duration<int64_t, std::ratio<1, 65536>>;
|
||||
Delay delay_since_last_report{};
|
||||
|
||||
// Convenience helper to compute/assign the `packet_fraction_lost_numerator`,
|
||||
// based on the `num_apparently_sent` and `num_received` packet counts since
|
||||
// the last report was sent.
|
||||
void SetPacketFractionLostNumerator(int64_t num_apparently_sent,
|
||||
int64_t num_received);
|
||||
|
||||
// Convenience helper to compute/assign the `cumulative_packets_lost`, based
|
||||
// on the `num_apparently_sent` and `num_received` packet counts since the
|
||||
// start of the entire session.
|
||||
void SetCumulativePacketsLost(int64_t num_apparently_sent,
|
||||
int64_t num_received);
|
||||
|
||||
// Convenience helper to convert the given `local_clock_delay` to the
|
||||
// RtcpReportBlock::Delay timebase, then clamp and assign it to
|
||||
// `delay_since_last_report`.
|
||||
void SetDelaySinceLastReport(Clock::duration local_clock_delay);
|
||||
|
||||
// Serializes this report block in the first `kRtcpReportBlockSize` bytes of
|
||||
// the given `buffer` and adjusts `buffer` to point to the first byte after
|
||||
// it.
|
||||
void AppendFields(ByteBuffer& buffer) const;
|
||||
|
||||
// Scans the wire-format report blocks in `buffer`, searching for one with the
|
||||
// matching `ssrc` and, if found, returns the parse result. Returns nullopt if
|
||||
// the data is corrupt or no report block with the matching SSRC was found.
|
||||
static std::optional<RtcpReportBlock> ParseOne(ByteView buffer,
|
||||
int report_count,
|
||||
Ssrc ssrc);
|
||||
};
|
||||
|
||||
struct RtcpSenderReport {
|
||||
RtcpSenderReport();
|
||||
~RtcpSenderReport();
|
||||
|
||||
// The point-in-time at which this report was sent, according to both: 1) the
|
||||
// common reference clock shared by all RTP streams; 2) the RTP timestamp on
|
||||
// the media capture/playout timeline. Together, these are used by a Receiver
|
||||
// to achieve A/V synchronization across RTP streams for playout.
|
||||
Clock::time_point reference_time{};
|
||||
RtpTimeTicks rtp_timestamp;
|
||||
|
||||
// The total number of RTP packets transmitted since the start of the session
|
||||
// (wrap-around is possible).
|
||||
uint32_t send_packet_count = 0;
|
||||
|
||||
// The total number of payload bytes transmitted in RTP packets since the
|
||||
// start of the session (wrap-around is possible).
|
||||
uint32_t send_octet_count = 0;
|
||||
|
||||
// The report block, if present. While the RTCP spec allows for zero or
|
||||
// multiple reports, Cast Streaming only uses zero or one.
|
||||
std::optional<RtcpReportBlock> report_block;
|
||||
};
|
||||
|
||||
// A pair of IDs that refers to a specific missing packet within a frame. If
|
||||
// `packet_id` is kAllPacketsLost, then it represents all the packets of a
|
||||
// frame.
|
||||
struct PacketNack {
|
||||
FrameId frame_id;
|
||||
FramePacketId packet_id;
|
||||
|
||||
constexpr bool operator==(const PacketNack& other) const {
|
||||
return frame_id == other.frame_id && packet_id == other.packet_id;
|
||||
}
|
||||
constexpr bool operator!=(const PacketNack& other) const {
|
||||
return frame_id != other.frame_id || packet_id != other.packet_id;
|
||||
}
|
||||
constexpr bool operator<(const PacketNack& other) const {
|
||||
return (frame_id < other.frame_id) ||
|
||||
(frame_id == other.frame_id && packet_id < other.packet_id);
|
||||
}
|
||||
};
|
||||
|
||||
// Statistics events sent from the receiver over RTCP.
|
||||
struct RtcpReceiverEventLogMessage {
|
||||
// The statistics event type, may be either a receiver side frame event or
|
||||
// packet event.
|
||||
StatisticsEvent::Type type;
|
||||
|
||||
// The time at which this event occurred.
|
||||
Clock::time_point timestamp;
|
||||
|
||||
// Only set for frame played out events.
|
||||
// If this value is zero the frame is rendered on time.
|
||||
// If this value is positive it means the frame is rendered late.
|
||||
// If this value is negative it means the frame is rendered early.
|
||||
Clock::duration delay;
|
||||
|
||||
// Only set for packet events.
|
||||
// The ID of the packet associated with this event.
|
||||
FramePacketId packet_id;
|
||||
};
|
||||
|
||||
struct RtcpReceiverFrameLogMessage {
|
||||
RtpTimeTicks rtp_timestamp;
|
||||
std::vector<RtcpReceiverEventLogMessage> messages;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_RTCP_COMMON_H_
|
||||
25
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtcp_session.cc
vendored
Normal file
25
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtcp_session.cc
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/rtcp_session.h"
|
||||
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
RtcpSession::RtcpSession(Ssrc sender_ssrc,
|
||||
Ssrc receiver_ssrc,
|
||||
Clock::time_point start_time)
|
||||
: sender_ssrc_(sender_ssrc),
|
||||
receiver_ssrc_(receiver_ssrc),
|
||||
start_time_(start_time),
|
||||
ntp_converter_(start_time) {
|
||||
OSP_CHECK_NE(sender_ssrc_, kNullSsrc);
|
||||
OSP_CHECK_NE(receiver_ssrc_, kNullSsrc);
|
||||
OSP_CHECK_NE(sender_ssrc_, receiver_ssrc_);
|
||||
}
|
||||
|
||||
RtcpSession::~RtcpSession() = default;
|
||||
|
||||
} // namespace openscreen::cast
|
||||
43
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtcp_session.h
vendored
Normal file
43
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtcp_session.h
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2019 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_RTCP_SESSION_H_
|
||||
#define CAST_STREAMING_IMPL_RTCP_SESSION_H_
|
||||
|
||||
#include "cast/streaming/impl/ntp_time.h"
|
||||
#include "cast/streaming/ssrc.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Session-level configuration and shared components for the RTCP messaging
|
||||
// associated with a single Cast RTP stream. Multiple packet serialization and
|
||||
// parsing components share a single RtcpSession instance for data consistency.
|
||||
class RtcpSession {
|
||||
public:
|
||||
// `start_time` should be the current time, as it is used by NtpTimeConverter
|
||||
// to set a fixed reference point between the local Clock and current "real
|
||||
// world" wall time.
|
||||
RtcpSession(Ssrc sender_ssrc,
|
||||
Ssrc receiver_ssrc,
|
||||
Clock::time_point start_time);
|
||||
~RtcpSession();
|
||||
|
||||
Ssrc sender_ssrc() const { return sender_ssrc_; }
|
||||
Ssrc receiver_ssrc() const { return receiver_ssrc_; }
|
||||
const NtpTimeConverter& ntp_converter() const { return ntp_converter_; }
|
||||
Clock::time_point start_time() const { return start_time_; }
|
||||
|
||||
private:
|
||||
const Ssrc sender_ssrc_;
|
||||
const Ssrc receiver_ssrc_;
|
||||
|
||||
Clock::time_point start_time_;
|
||||
|
||||
// Translates between system time (internal format) and NTP (wire format).
|
||||
NtpTimeConverter ntp_converter_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_RTCP_SESSION_H_
|
||||
113
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtp_defines.cc
vendored
Normal file
113
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtp_defines.cc
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
RtpPayloadType GetPayloadType(AudioCodec codec, bool use_android_rtp_hack) {
|
||||
if (use_android_rtp_hack) {
|
||||
return RtpPayloadType::kAudioHackForAndroidTV;
|
||||
}
|
||||
|
||||
switch (codec) {
|
||||
case AudioCodec::kAac:
|
||||
return RtpPayloadType::kAudioAac;
|
||||
case AudioCodec::kOpus:
|
||||
return RtpPayloadType::kAudioOpus;
|
||||
case AudioCodec::kNotSpecified:
|
||||
return RtpPayloadType::kAudioVarious;
|
||||
default:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
RtpPayloadType GetPayloadType(VideoCodec codec, bool use_android_rtp_hack) {
|
||||
if (use_android_rtp_hack) {
|
||||
return RtpPayloadType::kVideoHackForAndroidTV;
|
||||
}
|
||||
switch (codec) {
|
||||
// VP8 and VP9 share the same payload type.
|
||||
case VideoCodec::kVp9:
|
||||
case VideoCodec::kVp8:
|
||||
return RtpPayloadType::kVideoVp8;
|
||||
|
||||
// H264 and HEVC/H265 share the same payload type.
|
||||
case VideoCodec::kHevc: // fallthrough
|
||||
case VideoCodec::kH264:
|
||||
return RtpPayloadType::kVideoH264;
|
||||
|
||||
case VideoCodec::kAv1:
|
||||
return RtpPayloadType::kVideoAv1;
|
||||
|
||||
case VideoCodec::kNotSpecified:
|
||||
return RtpPayloadType::kVideoVarious;
|
||||
|
||||
default:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
StreamType ToStreamType(RtpPayloadType type, bool use_android_rtp_hack) {
|
||||
if (use_android_rtp_hack) {
|
||||
if (type == RtpPayloadType::kAudioHackForAndroidTV) {
|
||||
return StreamType::kAudio;
|
||||
}
|
||||
if (type == RtpPayloadType::kVideoHackForAndroidTV) {
|
||||
return StreamType::kVideo;
|
||||
}
|
||||
}
|
||||
|
||||
if (RtpPayloadType::kAudioFirst <= type &&
|
||||
type <= RtpPayloadType::kAudioLast) {
|
||||
return StreamType::kAudio;
|
||||
}
|
||||
if (RtpPayloadType::kVideoFirst <= type &&
|
||||
type <= RtpPayloadType::kVideoLast) {
|
||||
return StreamType::kVideo;
|
||||
}
|
||||
return StreamType::kUnknown;
|
||||
}
|
||||
|
||||
bool IsRtpPayloadType(uint8_t raw_byte) {
|
||||
switch (static_cast<RtpPayloadType>(raw_byte)) {
|
||||
case RtpPayloadType::kAudioOpus:
|
||||
case RtpPayloadType::kAudioAac:
|
||||
case RtpPayloadType::kAudioPcm16:
|
||||
case RtpPayloadType::kAudioVarious:
|
||||
case RtpPayloadType::kVideoVp8:
|
||||
case RtpPayloadType::kVideoH264:
|
||||
case RtpPayloadType::kVideoVp9:
|
||||
case RtpPayloadType::kVideoAv1:
|
||||
case RtpPayloadType::kVideoVarious:
|
||||
case RtpPayloadType::kAudioHackForAndroidTV:
|
||||
// Note: RtpPayloadType::kVideoHackForAndroidTV has the same value as
|
||||
// kAudioOpus.
|
||||
return true;
|
||||
|
||||
case RtpPayloadType::kNull:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsRtcpPacketType(uint8_t raw_byte) {
|
||||
switch (static_cast<RtcpPacketType>(raw_byte)) {
|
||||
case RtcpPacketType::kSenderReport:
|
||||
case RtcpPacketType::kReceiverReport:
|
||||
case RtcpPacketType::kSourceDescription:
|
||||
case RtcpPacketType::kApplicationDefined:
|
||||
case RtcpPacketType::kPayloadSpecific:
|
||||
case RtcpPacketType::kExtendedReports:
|
||||
return true;
|
||||
|
||||
case RtcpPacketType::kNull:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
382
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtp_defines.h
vendored
Normal file
382
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtp_defines.h
vendored
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
// Copyright 2019 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_RTP_DEFINES_H_
|
||||
#define CAST_STREAMING_IMPL_RTP_DEFINES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Note: Cast Streaming uses a subset of the messages in the RTP/RTCP
|
||||
// specification, but also adds some of its own extensions. See:
|
||||
// https://tools.ietf.org/html/rfc3550
|
||||
|
||||
// Uniquely identifies one packet within a frame. These are sequence numbers,
|
||||
// starting at 0. Each Cast RTP packet also includes the "last ID" so that a
|
||||
// receiver always knows the range of valid FramePacketIds for a given frame.
|
||||
using FramePacketId = uint16_t;
|
||||
|
||||
// A special FramePacketId value meant to represent "all packets lost" in Cast
|
||||
// RTCP Feedback messages.
|
||||
inline constexpr FramePacketId kAllPacketsLost = 0xffff;
|
||||
inline constexpr FramePacketId kMaxAllowedFramePacketId = kAllPacketsLost - 1;
|
||||
|
||||
// The maximum size of any RTP or RTCP packet, in bytes. The calculation below
|
||||
// is: Standard Ethernet MTU bytes minus IP header bytes minus UDP header bytes.
|
||||
// The remainder is available for RTP/RTCP packet data (header + payload).
|
||||
//
|
||||
// A nice explanation of this: https://jvns.ca/blog/2017/02/07/mtu/
|
||||
//
|
||||
// Constants are provided here for UDP over IPv4 and IPv6 on Ethernet. Other
|
||||
// transports and network mediums will need additional consideration, alternate
|
||||
// calculations. Note that MTU is dynamic, depending on the path the packets
|
||||
// take between two endpoints (the 1500 here is just a commonly-used value for
|
||||
// LAN Ethernet).
|
||||
inline constexpr int kMaxRtpPacketSizeForIpv4UdpOnEthernet = 1500 - 20 - 8;
|
||||
inline constexpr int kMaxRtpPacketSizeForIpv6UdpOnEthernet = 1500 - 40 - 8;
|
||||
|
||||
// The Cast RTP packet header:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ^
|
||||
// |V=2|P|X| CC=0 |M| PT | sequence number | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+RTP
|
||||
// + RTP timestamp |Spec
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
|
||||
// + synchronization source (SSRC) identifier | v
|
||||
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
// |K|R| EXT count | FID | PID | ^
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Cast
|
||||
// | Max PID | optional fields, extensions, Spec
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ then payload... v
|
||||
//
|
||||
// Byte 0: Version 2, no padding, no RTP extensions, no CSRCs.
|
||||
// Byte 1: Marker bit indicates whether this is the last packet, followed by a
|
||||
// 7-bit payload type.
|
||||
// Byte 12: Key Frame bit, followed by "RFID will be provided" bit, followed by
|
||||
// 6 bits specifying the number of extensions that will be provided.
|
||||
|
||||
// The minimum-possible valid size of a Cast RTP packet (i.e., no optional
|
||||
// fields, extensions, nor payload).
|
||||
inline constexpr int kRtpPacketMinValidSize = 18;
|
||||
|
||||
// All Cast RTP packets must carry the version 2 flag, not use padding, not use
|
||||
// RTP extensions, and have zero CSRCs.
|
||||
inline constexpr uint8_t kRtpRequiredFirstByte = 0b10000000;
|
||||
|
||||
// Bitmasks to isolate fields within byte 2 of the Cast RTP header.
|
||||
inline constexpr uint8_t kRtpMarkerBitMask = 0b10000000;
|
||||
inline constexpr uint8_t kRtpPayloadTypeMask = 0b01111111;
|
||||
|
||||
// Describes the content being transported over RTP streams. These are Cast
|
||||
// Streaming specific assignments, within the "dynamic" range provided by
|
||||
// IANA. Note that this Cast Streaming implementation does not manipulate
|
||||
// already-encoded data, and so these payload types are only "informative" in
|
||||
// purpose and can be used to check for corruption while parsing packets.
|
||||
enum class RtpPayloadType : uint8_t {
|
||||
kNull = 0,
|
||||
|
||||
kAudioFirst = 96,
|
||||
kAudioOpus = 96,
|
||||
kAudioAac = 97,
|
||||
kAudioPcm16 = 98,
|
||||
kAudioVarious = 99, // Codec being used is not fixed.
|
||||
kAudioLast = kAudioVarious,
|
||||
|
||||
kVideoFirst = 100,
|
||||
kVideoVp8 = 100,
|
||||
kVideoH264 = 101,
|
||||
kVideoVarious = 102, // Codec being used is not fixed.
|
||||
kVideoVp9 = 103,
|
||||
kVideoAv1 = 104,
|
||||
kVideoLast = kVideoAv1,
|
||||
|
||||
// Some AndroidTV receivers require the payload type for audio to be 127, and
|
||||
// video to be 96; regardless of the codecs actually being used. This is
|
||||
// definitely out-of-spec, and inconsistent with the audio versus video range
|
||||
// of values, but must be taken into account for backwards-compatibility.
|
||||
kAudioHackForAndroidTV = 127,
|
||||
kVideoHackForAndroidTV = 96,
|
||||
};
|
||||
|
||||
// Returns the stream type associated with the RTP payload type.
|
||||
StreamType ToStreamType(RtpPayloadType type, bool use_android_rtp_hack);
|
||||
|
||||
// Setting `use_android_rtp_hack` to true means that we match the legacy Chrome
|
||||
// sender's behavior of always sending the audio and video hacks for AndroidTV,
|
||||
// as some legacy android receivers require these.
|
||||
// TODO(issuetracker.google.com/184438154): we need to figure out what receivers
|
||||
// need this still, if any. The hack should be removed when possible.
|
||||
RtpPayloadType GetPayloadType(AudioCodec codec, bool use_android_rtp_hack);
|
||||
RtpPayloadType GetPayloadType(VideoCodec codec, bool use_android_rtp_hack);
|
||||
|
||||
// Returns true if the `raw_byte` can be type-casted to a RtpPayloadType, and is
|
||||
// also not RtpPayloadType::kNull. The caller should mask the byte, to select
|
||||
// the lower 7 bits, if applicable.
|
||||
bool IsRtpPayloadType(uint8_t raw_byte);
|
||||
|
||||
// Bitmasks to isolate fields within byte 12 of the Cast RTP header.
|
||||
inline constexpr uint8_t kRtpKeyFrameBitMask = 0b10000000;
|
||||
inline constexpr uint8_t kRtpHasReferenceFrameIdBitMask = 0b01000000;
|
||||
inline constexpr uint8_t kRtpExtensionCountMask = 0b00111111;
|
||||
|
||||
// Cast extensions. This implementation supports only the Adaptive Latency
|
||||
// extension, and ignores all others:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | TYPE = 1 | Ext data SIZE = 2 |Playout Delay (unsigned millis)|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// The Adaptive Latency extension permits changing the fixed end-to-end playout
|
||||
// delay of a single RTP stream.
|
||||
inline constexpr uint8_t kAdaptiveLatencyRtpExtensionType = 1;
|
||||
inline constexpr int kNumExtensionDataSizeFieldBits = 10;
|
||||
|
||||
// RTCP Common Header:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |V=2|P|RC/Subtyp| Packet Type | Length |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
inline constexpr int kRtcpCommonHeaderSize = 4;
|
||||
// All RTCP packets must carry the version 2 flag and not use padding.
|
||||
inline constexpr uint8_t kRtcpRequiredVersionAndPaddingBits = 0b100;
|
||||
inline constexpr int kRtcpReportCountFieldNumBits = 5;
|
||||
|
||||
// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml
|
||||
enum class RtcpPacketType : uint8_t {
|
||||
kNull = 0,
|
||||
|
||||
kSenderReport = 200,
|
||||
kReceiverReport = 201,
|
||||
kSourceDescription = 202,
|
||||
kApplicationDefined = 204,
|
||||
kPayloadSpecific = 206,
|
||||
kExtendedReports = 207,
|
||||
};
|
||||
|
||||
// Returns true if the `raw_byte` can be type-casted to a RtcpPacketType, and is
|
||||
// also not RtcpPacketType::kNull.
|
||||
bool IsRtcpPacketType(uint8_t raw_byte);
|
||||
|
||||
// Supported subtype values in the RTCP Common Header when the packet type is
|
||||
// kApplicationDefined or kPayloadSpecific.
|
||||
enum class RtcpSubtype : uint8_t {
|
||||
kNull = 0,
|
||||
|
||||
kPictureLossIndicator = 1,
|
||||
kReceiverLog = 2,
|
||||
kFeedback = 15,
|
||||
};
|
||||
|
||||
// RTCP Sender Report:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of Sender |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// | NTP Timestamp |
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | RTP Timestamp |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Sender's Packet Count |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Sender's Octet Count |
|
||||
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
// ...Followed by zero or more "Report Blocks"...
|
||||
inline constexpr int kRtcpSenderReportSize = 24;
|
||||
|
||||
// RTCP Receiver Report:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of Receiver |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// ...Followed by zero or more "Report Blocks"...
|
||||
inline constexpr int kRtcpReceiverReportSize = 4;
|
||||
|
||||
// RTCP Report Block. For Cast Streaming, zero or one of these accompanies a
|
||||
// Sender or Receiver Report, which is different than the RTCP spec (which
|
||||
// allows zero or more).
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | "To" SSRC |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Fraction Lost | Cumulative Number of Packets Lost |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | [32-bit extended] Highest Sequence Number Received |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Interarrival Jitter Mean Absolute Deviation (in RTP Timebase) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Middle 32-bits of NTP Timestamp from last Sender Report |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Delay since last Sender Report (1/65536 sec timebase) |
|
||||
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
inline constexpr int kRtcpReportBlockSize = 24;
|
||||
inline constexpr int kRtcpCumulativePacketsFieldNumBits = 24;
|
||||
|
||||
// Cast Feedback Message:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of Receiver |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of Sender |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Unique identifier 'C' 'A' 'S' 'T' |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | CkPt Frame ID | # Loss Fields | Current Playout Delay (msec) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
inline constexpr int kRtcpFeedbackHeaderSize = 16;
|
||||
inline constexpr uint32_t kRtcpCastIdentifierWord =
|
||||
(uint32_t{'C'} << 24) | (uint32_t{'A'} << 16) | (uint32_t{'S'} << 8) |
|
||||
uint32_t{'T'};
|
||||
//
|
||||
// "Checkpoint Frame ID" indicates that all frames prior to and including this
|
||||
// one have been fully received. Unfortunately, the Frame ID is truncated to its
|
||||
// lower 8 bits in the packet, and 8 bits is not really enough: If a RTCP packet
|
||||
// is received very late (e.g., more than 1.2 seconds late for 100 FPS audio),
|
||||
// the Checkpoint Frame ID here will be mis-interpreted as representing a
|
||||
// higher-numbered frame than what was intended. This could make the sender's
|
||||
// tracking of "completely received" frames inconsistent, and Cast Streaming
|
||||
// would live-lock. However, this design issue has been baked into the spec and
|
||||
// millions of deployments over several years, and so there's no changing it
|
||||
// now. See kMaxUnackedFrames in constants.h.
|
||||
//
|
||||
// "# Loss fields" indicates the number of packet-level NACK words, 0 to 255:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | w/in Frame ID | Lost Frame Packet ID | PID BitVector |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
inline constexpr int kRtcpFeedbackLossFieldSize = 4;
|
||||
//
|
||||
// "Within Frame ID" is a truncated-to-8-bits frame ID field and, when
|
||||
// bit-expanded should always be interpreted to represent a value greater than
|
||||
// the Checkpoint Frame ID. "Lost Frame Packet ID" is either a specific packet
|
||||
// (within the frame) that has not been received, or kAllPacketsLost to indicate
|
||||
// none the packets for the frame have been received yet. In the former case,
|
||||
// "PID Bit Vector" then represents which of the next 8 packets are also
|
||||
// missing.
|
||||
//
|
||||
// Finally, all of the above is optionally followed by a frame-level ACK bit
|
||||
// vector:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Unique identifier 'C' 'S' 'T' '2' |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |Feedback Count | # BVectOctets | ACK BitVect (2 to 254 bytes)...
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ → zero-padded to word boundary
|
||||
inline constexpr int kRtcpFeedbackAckHeaderSize = 6;
|
||||
inline constexpr uint32_t kRtcpCst2IdentifierWord =
|
||||
(uint32_t{'C'} << 24) | (uint32_t{'S'} << 16) | (uint32_t{'T'} << 8) |
|
||||
uint32_t{'2'};
|
||||
inline constexpr int kRtcpMinAckBitVectorOctets = 2;
|
||||
inline constexpr int kRtcpMaxAckBitVectorOctets = 254;
|
||||
//
|
||||
// "Feedback Count" is a wrap-around counter indicating the number of Cast
|
||||
// Feedbacks that have been sent before this one. "# Bit Vector Octets"
|
||||
// indicates the number of bytes of ACK bit vector following. Cast RTCP
|
||||
// alignment/padding requirements (to 4-byte boundaries) dictates the following
|
||||
// rules for generating the ACK bit vector:
|
||||
//
|
||||
// 1. There must be at least 2 bytes of ACK bit vector, if only to pad the 6
|
||||
// byte header with two more bytes.
|
||||
// 2. If more than 2 bytes are needed, they must be added 4 at a time to
|
||||
// maintain the 4-byte alignment of the overall RTCP packet.
|
||||
// 3. The total number of octets may not exceed 255; but, because of #2, 254
|
||||
// is effectively the limit.
|
||||
// 4. The first bit in the first octet represents "Checkpoint Frame ID" plus
|
||||
// two. "Plus two" and not "plus one" because otherwise the "Checkpoint
|
||||
// Frame ID" should have been a greater value!
|
||||
|
||||
// RTCP Extended Report:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of Report Author |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
inline constexpr int kRtcpExtendedReportHeaderSize = 4;
|
||||
//
|
||||
// ...followed by zero or more Blocks:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Block Type | Reserved = 0 | Block Length |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ..."Block Length" words of report data... |
|
||||
// + +
|
||||
// + +
|
||||
inline constexpr int kRtcpExtendedReportBlockHeaderSize = 4;
|
||||
//
|
||||
// Cast Streaming only uses Receiver Reference Time Reports:
|
||||
// https://tools.ietf.org/html/rfc3611#section-4.4. So, the entire block would
|
||||
// be:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Block Type=4 | Reserved = 0 | Block Length = 2 |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | NTP Timestamp |
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
inline constexpr uint8_t kRtcpReceiverReferenceTimeReportBlockType = 4;
|
||||
inline constexpr int kRtcpReceiverReferenceTimeReportBlockSize = 8;
|
||||
|
||||
// Cast Picture Loss Indicator Message:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of Receiver |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of Sender |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
inline constexpr int kRtcpPictureLossIndicatorHeaderSize = 8;
|
||||
|
||||
// The Cast Receiver RTCP frame log message is an application specific
|
||||
// extension that contains receiver side statistics about the Receiver Session.
|
||||
// The message format is:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | RTP Timestamp |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Event Count | Event Timestamp |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
inline constexpr int kRtcpReceiverFrameLogMessageHeaderSize = 8;
|
||||
//
|
||||
// Followed by a list of zero or more event blocks:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Delay Delta or Packet ID | Type | Event Timestamp |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
inline constexpr int kRtcpReceiverFrameLogMessageBlockSize = 4;
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_RTP_DEFINES_H_
|
||||
133
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtp_packetizer.cc
vendored
Normal file
133
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtp_packetizer.cc
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/rtp_packetizer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
|
||||
#include "cast/streaming/impl/packet_util.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "util/big_endian.h"
|
||||
#include "util/integer_division.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns a random sequence number to start with. The reason for using a random
|
||||
// number instead of zero is unclear, but this has existed both in several
|
||||
// versions of the Cast Streaming spec and in other implementations for many
|
||||
// years.
|
||||
uint16_t GenerateRandomSequenceNumberStart() {
|
||||
// Use a statically-allocated generator, instantiated upon first use, and
|
||||
// seeded with the current time tick count. This generator was chosen because
|
||||
// it is light-weight and does not need to produce unguessable (nor
|
||||
// crypto-secure) values.
|
||||
static std::minstd_rand generator(static_cast<std::minstd_rand::result_type>(
|
||||
Clock::now().time_since_epoch().count()));
|
||||
|
||||
return std::uniform_int_distribution<uint16_t>()(generator);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RtpPacketizer::RtpPacketizer(RtpPayloadType payload_type,
|
||||
Ssrc sender_ssrc,
|
||||
int max_packet_size)
|
||||
: payload_type_7bits_(static_cast<uint8_t>(payload_type)),
|
||||
sender_ssrc_(sender_ssrc),
|
||||
max_packet_size_(max_packet_size),
|
||||
sequence_number_(GenerateRandomSequenceNumberStart()) {
|
||||
OSP_CHECK(IsRtpPayloadType(payload_type_7bits_));
|
||||
OSP_CHECK_GT(max_packet_size_, kMaxRtpHeaderSize);
|
||||
}
|
||||
|
||||
RtpPacketizer::~RtpPacketizer() = default;
|
||||
|
||||
ByteBuffer RtpPacketizer::GeneratePacket(const EncryptedFrame& frame,
|
||||
FramePacketId packet_id,
|
||||
ByteBuffer buffer) {
|
||||
OSP_CHECK_GE(static_cast<int>(buffer.size()), max_packet_size_);
|
||||
|
||||
const int num_packets = ComputeNumberOfPackets(frame);
|
||||
OSP_CHECK_GT(num_packets, 0);
|
||||
OSP_CHECK_LT(int{packet_id}, num_packets);
|
||||
const bool is_last_packet = int{packet_id} == (num_packets - 1);
|
||||
|
||||
// Compute the size of this packet, which is the number of bytes of header
|
||||
// plus the number of bytes of payload. Note that the optional Adaptive
|
||||
// Latency information is only added to the first packet.
|
||||
int packet_size = kBaseRtpHeaderSize;
|
||||
const bool include_adaptive_latency_change =
|
||||
(packet_id == 0 &&
|
||||
frame.new_playout_delay > std::chrono::milliseconds(0));
|
||||
if (include_adaptive_latency_change) {
|
||||
OSP_CHECK_LE(frame.new_playout_delay.count(),
|
||||
int{std::numeric_limits<uint16_t>::max()});
|
||||
packet_size += kAdaptiveLatencyHeaderSize;
|
||||
}
|
||||
int data_chunk_size = max_payload_size();
|
||||
const int data_chunk_start = data_chunk_size * int{packet_id};
|
||||
if (is_last_packet) {
|
||||
data_chunk_size = static_cast<int>(frame.data.size()) - data_chunk_start;
|
||||
}
|
||||
packet_size += data_chunk_size;
|
||||
OSP_CHECK_LE(packet_size, max_packet_size_);
|
||||
const ByteBuffer packet(buffer.data(), packet_size);
|
||||
|
||||
// RTP Header.
|
||||
AppendField<uint8_t>(kRtpRequiredFirstByte, buffer);
|
||||
AppendField<uint8_t>(
|
||||
(is_last_packet ? kRtpMarkerBitMask : 0) | payload_type_7bits_, buffer);
|
||||
AppendField<uint16_t>(sequence_number_++, buffer);
|
||||
AppendField<uint32_t>(frame.rtp_timestamp.lower_32_bits(), buffer);
|
||||
AppendField<uint32_t>(sender_ssrc_, buffer);
|
||||
|
||||
// Cast Header.
|
||||
AppendField<uint8_t>(
|
||||
((frame.dependency == EncodedFrame::Dependency::kKeyFrame)
|
||||
? kRtpKeyFrameBitMask
|
||||
: 0) |
|
||||
kRtpHasReferenceFrameIdBitMask |
|
||||
(include_adaptive_latency_change ? 1 : 0),
|
||||
buffer);
|
||||
AppendField<uint8_t>(frame.frame_id.lower_8_bits(), buffer);
|
||||
AppendField<uint16_t>(packet_id, buffer);
|
||||
AppendField<uint16_t>(num_packets - 1, buffer);
|
||||
AppendField<uint8_t>(frame.referenced_frame_id.lower_8_bits(), buffer);
|
||||
|
||||
// Extension of Cast Header for Adaptive Latency change.
|
||||
if (include_adaptive_latency_change) {
|
||||
AppendField<uint16_t>(
|
||||
(kAdaptiveLatencyRtpExtensionType << kNumExtensionDataSizeFieldBits) |
|
||||
sizeof(uint16_t),
|
||||
buffer);
|
||||
AppendField<uint16_t>(frame.new_playout_delay.count(), buffer);
|
||||
}
|
||||
|
||||
// Copy the encrypted payload data into the packet.
|
||||
auto data_chunk = frame.data.subspan(data_chunk_start, data_chunk_size);
|
||||
std::copy(data_chunk.begin(), data_chunk.end(), buffer.data());
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
int RtpPacketizer::ComputeNumberOfPackets(const EncryptedFrame& frame) const {
|
||||
// The total number of packets is computed by assuming the payload will be
|
||||
// split-up across as few packets as possible.
|
||||
int num_packets = DividePositivesRoundingUp(
|
||||
static_cast<int>(frame.data.size()), max_payload_size());
|
||||
// Edge case: There must always be at least one packet, even when there are no
|
||||
// payload bytes. Some audio codecs, for example, use zero bytes to represent
|
||||
// a period of silence.
|
||||
num_packets = std::max(1, num_packets);
|
||||
|
||||
// Ensure that the entire range of FramePacketIds can be represented.
|
||||
return num_packets <= int{kMaxAllowedFramePacketId} ? num_packets : -1;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
79
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtp_packetizer.h
vendored
Normal file
79
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/rtp_packetizer.h
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2019 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_RTP_PACKETIZER_H_
|
||||
#define CAST_STREAMING_IMPL_RTP_PACKETIZER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cast/streaming/impl/frame_crypto.h"
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
#include "cast/streaming/ssrc.h"
|
||||
#include "platform/base/span.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Transforms a logical sequence of EncryptedFrames into RTP packets for
|
||||
// transmission. A single instance of RtpPacketizer should be used for all the
|
||||
// frames in a Cast RTP stream having the same SSRC.
|
||||
class RtpPacketizer {
|
||||
public:
|
||||
// `payload_type` describes the type of the media content for the RTP stream
|
||||
// from the sender having the given `sender_ssrc`.
|
||||
//
|
||||
// The `max_packet_size` argument depends on the optimal over-the-wire size of
|
||||
// packets for the network medium being used. See discussion in rtp_defines.h
|
||||
// for further info.
|
||||
RtpPacketizer(RtpPayloadType payload_type,
|
||||
Ssrc sender_ssrc,
|
||||
int max_packet_size);
|
||||
|
||||
~RtpPacketizer();
|
||||
|
||||
// Wire-format one of the RTP packets for the given frame, which must only be
|
||||
// transmitted once. This method should be called in the same sequence that
|
||||
// packets will be transmitted. This also means that, if a packet needs to be
|
||||
// re-transmitted, this method should be called to generate it again. Returns
|
||||
// the subspan of `buffer` that contains the packet. `buffer` must be at least
|
||||
// as large as the `max_packet_size` passed to the constructor.
|
||||
ByteBuffer GeneratePacket(const EncryptedFrame& frame,
|
||||
FramePacketId packet_id,
|
||||
ByteBuffer buffer);
|
||||
|
||||
// Given `frame`, compute the total number of packets over which the whole
|
||||
// frame will be split-up. Returns -1 if the frame is too large and cannot be
|
||||
// packetized.
|
||||
int ComputeNumberOfPackets(const EncryptedFrame& frame) const;
|
||||
|
||||
// See rtp_defines.h for wire-format diagram.
|
||||
static constexpr int kBaseRtpHeaderSize =
|
||||
// Plus one byte, because this implementation always includes the 8-bit
|
||||
// Reference Frame ID field.
|
||||
kRtpPacketMinValidSize + 1;
|
||||
static constexpr int kAdaptiveLatencyHeaderSize = 4;
|
||||
static constexpr int kMaxRtpHeaderSize =
|
||||
kBaseRtpHeaderSize + kAdaptiveLatencyHeaderSize;
|
||||
|
||||
private:
|
||||
int max_payload_size() const {
|
||||
// Start with the configured max packet size, then subtract reserved space
|
||||
// for packet header fields. The rest can be allocated to the payload.
|
||||
return max_packet_size_ - kMaxRtpHeaderSize;
|
||||
}
|
||||
|
||||
// The validated ctor RtpPayloadType arg, in wire-format form.
|
||||
const uint8_t payload_type_7bits_;
|
||||
|
||||
const Ssrc sender_ssrc_;
|
||||
const int max_packet_size_;
|
||||
|
||||
// Incremented each time GeneratePacket() is called. Every packet, even those
|
||||
// re-transmitted, must have different sequence numbers (within wrap-around
|
||||
// concerns) per the RTP spec.
|
||||
uint16_t sequence_number_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_RTP_PACKETIZER_H_
|
||||
686
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/sender_impl.cc
vendored
Normal file
686
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/sender_impl.cc
vendored
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
// 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.
|
||||
|
||||
#include "cast/streaming/impl/sender_impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "cast/streaming/public/session_config.h"
|
||||
#include "platform/base/trivial_clock_traits.h"
|
||||
#include "util/chrono_helpers.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/std_util.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/trace_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
namespace {
|
||||
|
||||
// The minimum amount of media the Sender keeps in-flight, regardless of the
|
||||
// measured network round-trip time. This keeps the encoder pipeline flowing on
|
||||
// low-latency networks (roughly two video frames at 30 FPS). See
|
||||
// crbug.com/498035450.
|
||||
constexpr Clock::duration kMinSenderInFlight =
|
||||
Clock::to_duration(milliseconds(66));
|
||||
|
||||
} // namespace
|
||||
|
||||
using clock_operators::operator<<;
|
||||
|
||||
SenderImpl::SenderImpl(Environment& environment,
|
||||
SenderPacketRouter& packet_router,
|
||||
SessionConfig config,
|
||||
RtpPayloadType rtp_payload_type)
|
||||
: config_(config),
|
||||
packet_router_(packet_router),
|
||||
rtcp_session_(config.sender_ssrc,
|
||||
config.receiver_ssrc,
|
||||
environment.now()),
|
||||
rtcp_parser_(rtcp_session_, *this),
|
||||
sender_report_builder_(rtcp_session_),
|
||||
rtp_packetizer_(rtp_payload_type,
|
||||
config.sender_ssrc,
|
||||
packet_router_->max_packet_size()),
|
||||
rtp_timebase_(config.rtp_timebase),
|
||||
crypto_(config.aes_secret_key, config.aes_iv_mask),
|
||||
statistics_dispatcher_(environment),
|
||||
target_playout_delay_(config.target_playout_delay) {
|
||||
OSP_CHECK_NE(rtcp_session_.sender_ssrc(), rtcp_session_.receiver_ssrc());
|
||||
OSP_CHECK_GT(rtp_timebase_, 0);
|
||||
OSP_CHECK_GT(target_playout_delay_, milliseconds::zero());
|
||||
|
||||
pending_sender_report_.reference_time = SenderPacketRouter::kNever;
|
||||
|
||||
packet_router_->OnSenderCreated(rtcp_session_.receiver_ssrc(), this);
|
||||
}
|
||||
|
||||
SenderImpl::~SenderImpl() {
|
||||
packet_router_->OnSenderDestroyed(rtcp_session_.receiver_ssrc());
|
||||
}
|
||||
|
||||
void SenderImpl::SetObserver(openscreen::cast::Sender::Observer* observer) {
|
||||
OSP_CHECK_NE(observer_, observer);
|
||||
observer_ = observer;
|
||||
}
|
||||
|
||||
size_t SenderImpl::GetInFlightFrameCount() const {
|
||||
return num_frames_in_flight_;
|
||||
}
|
||||
|
||||
Clock::duration SenderImpl::GetInFlightMediaDuration(
|
||||
RtpTimeTicks next_frame_rtp_timestamp) const {
|
||||
if (num_frames_in_flight_ == 0) {
|
||||
return Clock::duration::zero(); // No frames are currently in-flight.
|
||||
}
|
||||
|
||||
const PendingFrameSlot& oldest_slot = get_slot_for(checkpoint_frame_id_ + 1);
|
||||
// Note: The oldest slot's frame cannot have been canceled because the
|
||||
// protocol does not allow ACK'ing this particular frame without also moving
|
||||
// the checkpoint forward. See "CST2 feedback" discussion in rtp_defines.h.
|
||||
OSP_CHECK(oldest_slot.is_active_for_frame(checkpoint_frame_id_ + 1));
|
||||
|
||||
return (next_frame_rtp_timestamp - oldest_slot.frame->rtp_timestamp)
|
||||
.ToDuration<Clock::duration>(rtp_timebase_);
|
||||
}
|
||||
|
||||
Clock::duration SenderImpl::GetMaxInFlightMediaDuration() const {
|
||||
// The Sender keeps only enough media in-flight to drive the loss-detection
|
||||
// and retransmit feedback loop, which takes on the order of two network
|
||||
// round-trips (one to detect a loss via NACK, one to retransmit). A small
|
||||
// floor (`kMinSenderInFlight`) keeps the encoder pipeline flowing on
|
||||
// low-latency networks where 2*RTT is negligible.
|
||||
//
|
||||
// The result is capped at a third of the playout delay window so that the
|
||||
// majority of the budget is reserved for the Receiver, which needs buffer to
|
||||
// absorb NACK retransmissions. Bounding the Sender this way also makes it
|
||||
// drop frames earlier during congestion (saving bandwidth and CPU) rather
|
||||
// than over-buffering. See crbug.com/498035450.
|
||||
//
|
||||
// Note: the upper bound is held at or above `kMinSenderInFlight` so the
|
||||
// std::clamp() bounds remain well-ordered even for very small playout delays.
|
||||
const Clock::duration max_in_flight = std::max(
|
||||
kMinSenderInFlight, Clock::to_duration(target_playout_delay_) / 3);
|
||||
return std::clamp(round_trip_time_ * 2, kMinSenderInFlight, max_in_flight);
|
||||
}
|
||||
|
||||
bool SenderImpl::NeedsKeyFrame() const {
|
||||
return last_enqueued_key_frame_id_ <= picture_lost_at_frame_id_;
|
||||
}
|
||||
|
||||
FrameId SenderImpl::GetNextFrameId() const {
|
||||
return last_enqueued_frame_id_ + 1;
|
||||
}
|
||||
|
||||
Clock::duration SenderImpl::GetCurrentRoundTripTime() const {
|
||||
return round_trip_time_;
|
||||
}
|
||||
|
||||
openscreen::cast::Sender::EnqueueFrameResult SenderImpl::EnqueueFrame(
|
||||
const EncodedFrame& frame) {
|
||||
// Assume the fields of the `frame` have all been set correctly, with
|
||||
// monotonically increasing timestamps and a valid pointer to the data.
|
||||
OSP_CHECK_EQ(frame.frame_id, GetNextFrameId());
|
||||
OSP_CHECK_GE(frame.referenced_frame_id, FrameId::first());
|
||||
if (frame.frame_id != FrameId::first()) {
|
||||
OSP_CHECK_GT(frame.rtp_timestamp, pending_sender_report_.rtp_timestamp);
|
||||
if (frame.reference_time <= pending_sender_report_.reference_time) {
|
||||
OSP_DLOG_WARN << "Frame " << frame.frame_id
|
||||
<< " has non-monotonic reference_time: "
|
||||
<< frame.reference_time
|
||||
<< " <= " << pending_sender_report_.reference_time;
|
||||
}
|
||||
}
|
||||
OSP_CHECK(frame.data.data());
|
||||
|
||||
const auto capture_begin_time =
|
||||
(frame.capture_begin_time > Clock::time_point::min())
|
||||
? frame.capture_begin_time
|
||||
: Clock::now();
|
||||
|
||||
TRACE_FLOW_BEGIN_WITH_TIME(TraceCategory::kSender, "Frame.Capture",
|
||||
frame.frame_id, capture_begin_time);
|
||||
|
||||
if (frame.capture_end_time > Clock::time_point::min()) {
|
||||
TRACE_FLOW_STEP_WITH_TIME(TraceCategory::kSender, "Frame.Capture.End",
|
||||
frame.frame_id, frame.capture_end_time);
|
||||
}
|
||||
|
||||
TRACE_FLOW_STEP(TraceCategory::kSender, "Frame.Encode.End", frame.frame_id);
|
||||
|
||||
// Check whether enqueuing the frame would exceed the design limit for the
|
||||
// span of FrameIds. Even if `num_frames_in_flight_` is less than
|
||||
// kMaxUnackedFrames, it's the span of FrameIds that is restricted.
|
||||
if ((frame.frame_id - checkpoint_frame_id_) > kMaxUnackedFrames) {
|
||||
return REACHED_ID_SPAN_LIMIT;
|
||||
}
|
||||
|
||||
// Check whether enqueuing the frame would exceed the current maximum media
|
||||
// duration limit.
|
||||
if (GetInFlightMediaDuration(frame.rtp_timestamp) >
|
||||
GetMaxInFlightMediaDuration()) {
|
||||
return MAX_DURATION_IN_FLIGHT;
|
||||
}
|
||||
|
||||
// Encrypt the frame and initialize the slot tracking its sending.
|
||||
PendingFrameSlot& slot = get_slot_for(frame.frame_id);
|
||||
OSP_CHECK(!slot.frame);
|
||||
slot.frame = crypto_.Encrypt(frame);
|
||||
const int packet_count = rtp_packetizer_.ComputeNumberOfPackets(*slot.frame);
|
||||
if (packet_count <= 0) {
|
||||
slot.frame.reset();
|
||||
return PAYLOAD_TOO_LARGE;
|
||||
}
|
||||
slot.send_flags.Resize(packet_count, BitVector::SET);
|
||||
slot.packet_sent_times.assign(packet_count, SenderPacketRouter::kNever);
|
||||
|
||||
// Officially record the "enqueue."
|
||||
++num_frames_in_flight_;
|
||||
last_enqueued_frame_id_ = slot.frame->frame_id;
|
||||
OSP_CHECK_LE(
|
||||
num_frames_in_flight_,
|
||||
static_cast<size_t>(last_enqueued_frame_id_ - checkpoint_frame_id_));
|
||||
if (slot.frame->dependency == EncodedFrame::Dependency::kKeyFrame) {
|
||||
last_enqueued_key_frame_id_ = slot.frame->frame_id;
|
||||
}
|
||||
TRACE_FLOW_STEP(TraceCategory::kSender, "Frame.Enqueued", frame.frame_id);
|
||||
|
||||
// Update the target playout delay, if necessary.
|
||||
if (slot.frame->new_playout_delay > milliseconds::zero()) {
|
||||
target_playout_delay_ = slot.frame->new_playout_delay;
|
||||
playout_delay_change_at_frame_id_ = slot.frame->frame_id;
|
||||
}
|
||||
|
||||
// Update the lip-sync information for the next Sender Report, ensuring that
|
||||
// the reference time is monotonically increasing.
|
||||
pending_sender_report_.reference_time =
|
||||
frame.frame_id == FrameId::first()
|
||||
? slot.frame->reference_time
|
||||
: std::max(slot.frame->reference_time,
|
||||
pending_sender_report_.reference_time);
|
||||
pending_sender_report_.rtp_timestamp = slot.frame->rtp_timestamp;
|
||||
|
||||
// If the round trip time hasn't been computed yet, immediately send a RTCP
|
||||
// packet (i.e., before the RTP packets are sent). The RTCP packet will
|
||||
// provide a Sender Report which contains the required lip-sync information
|
||||
// the Receiver needs for timing the media playout.
|
||||
//
|
||||
// Detail: Working backwards, if the round trip time is not known, then this
|
||||
// Sender has never processed a Receiver Report. Thus, the Receiver has never
|
||||
// provided a Receiver Report, which it can only do after having processed a
|
||||
// Sender Report from this Sender. Thus, this Sender really needs to send
|
||||
// that, right now!
|
||||
if (round_trip_time_ == Clock::duration::zero()) {
|
||||
packet_router_->RequestRtcpSend(rtcp_session_.receiver_ssrc());
|
||||
}
|
||||
|
||||
// Re-activate RTP sending if it was suspended.
|
||||
packet_router_->RequestRtpSend(rtcp_session_.receiver_ssrc());
|
||||
statistics_dispatcher_.DispatchEnqueueEvents(config_.stream_type, frame);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void SenderImpl::CancelInFlightData() {
|
||||
TRACE_DEFAULT_SCOPED1(
|
||||
TraceCategory::kSender, "frames_in_flight",
|
||||
std::to_string(last_enqueued_frame_id_ - checkpoint_frame_id_));
|
||||
|
||||
while (checkpoint_frame_id_ < last_enqueued_frame_id_) {
|
||||
++checkpoint_frame_id_;
|
||||
CancelPendingFrame(checkpoint_frame_id_, /*was_acked*/ false);
|
||||
}
|
||||
DispatchCancellations();
|
||||
}
|
||||
|
||||
void SenderImpl::ReportFrameDropEvent(FrameId frame_id,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
Clock::time_point drop_time) {
|
||||
statistics_dispatcher_.DispatchFrameDropEvent(config_.stream_type, frame_id,
|
||||
rtp_timestamp, drop_time);
|
||||
}
|
||||
|
||||
void SenderImpl::OnReceivedRtcpPacket(Clock::time_point arrival_time,
|
||||
ByteView packet) {
|
||||
rtcp_packet_arrival_time_ = arrival_time;
|
||||
// This call to Parse() invoke zero or more of the OnReceiverXYZ() methods in
|
||||
// the current call stack:
|
||||
if (rtcp_parser_.Parse(packet, last_enqueued_frame_id_)) {
|
||||
packet_router_->OnRtcpReceived(arrival_time, round_trip_time_);
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer SenderImpl::GetRtcpPacketForImmediateSend(
|
||||
Clock::time_point send_time,
|
||||
ByteBuffer buffer) {
|
||||
if (pending_sender_report_.reference_time == SenderPacketRouter::kNever) {
|
||||
// Cannot send a report if one is not available (i.e., a frame has never
|
||||
// been enqueued).
|
||||
return buffer.subspan(0, 0);
|
||||
}
|
||||
|
||||
// The Sender Report to be sent is a snapshot of the "pending Sender Report,"
|
||||
// but with its timestamp fields modified. First, the reference time is set to
|
||||
// the RTCP packet's send time. Then, the corresponding RTP timestamp is
|
||||
// translated to match (for lip-sync).
|
||||
RtcpSenderReport sender_report = pending_sender_report_;
|
||||
sender_report.reference_time = send_time;
|
||||
sender_report.rtp_timestamp += RtpTimeDelta::FromDuration(
|
||||
sender_report.reference_time - pending_sender_report_.reference_time,
|
||||
rtp_timebase_);
|
||||
|
||||
return sender_report_builder_.BuildPacket(sender_report, buffer).first;
|
||||
}
|
||||
|
||||
ByteBuffer SenderImpl::GetRtpPacketForImmediateSend(Clock::time_point send_time,
|
||||
ByteBuffer buffer) {
|
||||
ChosenPacket chosen = ChooseNextRtpPacketNeedingSend();
|
||||
|
||||
// If no packets need sending (i.e., all packets have been sent at least once
|
||||
// and do not need to be re-sent yet), check whether a Kickstart packet should
|
||||
// be sent. It's possible that there has been complete packet loss of some
|
||||
// frames, and the Receiver may not be aware of the existence of the latest
|
||||
// frame(s). Kickstarting is the only way the Receiver can discover the newer
|
||||
// frames it doesn't know about.
|
||||
if (!chosen) {
|
||||
const ChosenPacketAndWhen kickstart = ChooseKickstartPacket();
|
||||
if (kickstart.when > send_time) {
|
||||
// Nothing to send, so return "empty" signal to the packet router. The
|
||||
// packet router will suspend RTP sending until this Sender explicitly
|
||||
// resumes it.
|
||||
return buffer.subspan(0, 0);
|
||||
}
|
||||
chosen = kickstart;
|
||||
OSP_CHECK(chosen);
|
||||
}
|
||||
|
||||
const ByteBuffer result = rtp_packetizer_.GeneratePacket(
|
||||
*chosen.slot->frame, chosen.packet_id, buffer);
|
||||
chosen.slot->send_flags.Clear(chosen.packet_id);
|
||||
chosen.slot->packet_sent_times[chosen.packet_id] = send_time;
|
||||
|
||||
++pending_sender_report_.send_packet_count;
|
||||
// According to RFC3550, the octet count does not include the RTP header. The
|
||||
// following is just a good approximation, however, because the header size
|
||||
// will very infrequently be 4 bytes greater (see
|
||||
// RtpPacketizer::kAdaptiveLatencyHeaderSize). No known Cast Streaming
|
||||
// Receiver implementations use this for anything, and so this should be fine.
|
||||
const int approximate_octet_count =
|
||||
static_cast<int>(result.size()) - RtpPacketizer::kBaseRtpHeaderSize;
|
||||
OSP_CHECK_GE(approximate_octet_count, 0);
|
||||
pending_sender_report_.send_octet_count += approximate_octet_count;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Clock::time_point SenderImpl::GetRtpResumeTime() {
|
||||
if (ChooseNextRtpPacketNeedingSend()) {
|
||||
return Alarm::kImmediately;
|
||||
}
|
||||
return ChooseKickstartPacket().when;
|
||||
}
|
||||
|
||||
RtpTimeTicks SenderImpl::GetLastRtpTimestamp() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
StreamType SenderImpl::GetStreamType() const {
|
||||
return config_.stream_type;
|
||||
}
|
||||
|
||||
void SenderImpl::OnReceiverReferenceTimeAdvanced(
|
||||
Clock::time_point reference_time) {
|
||||
// Not used.
|
||||
}
|
||||
|
||||
// static
|
||||
Clock::duration SenderImpl::SmoothRoundTripTime(Clock::duration estimate,
|
||||
Clock::duration measurement) {
|
||||
// Measurements typically have high variance, so smooth them with an
|
||||
// exponentially-weighted moving average. The filter is asymmetric ("fast
|
||||
// attack, slow decay"): it reacts quickly to upward spikes so the Sender
|
||||
// notices congestion onset promptly and backs off, but decays slowly on the
|
||||
// way down so a single low sample doesn't collapse the estimate. See
|
||||
// crbug.com/498036656.
|
||||
if (estimate == Clock::duration::zero()) {
|
||||
return measurement;
|
||||
}
|
||||
if (measurement > estimate) {
|
||||
// Spike / congestion onset: give the new measurement half the weight so the
|
||||
// estimate climbs quickly.
|
||||
return (estimate + measurement) / 2;
|
||||
}
|
||||
// Recovery: give the new measurement 1/8 weight (and the old estimate 7/8) to
|
||||
// de-noise, since downward measurements are typically the network settling
|
||||
// rather than a sustained improvement.
|
||||
constexpr int kInertia = 7;
|
||||
return (kInertia * estimate + measurement) / (kInertia + 1);
|
||||
}
|
||||
|
||||
void SenderImpl::OnReceiverReport(const RtcpReportBlock& receiver_report) {
|
||||
OSP_CHECK_NE(rtcp_packet_arrival_time_, SenderPacketRouter::kNever);
|
||||
|
||||
const Clock::duration total_delay =
|
||||
rtcp_packet_arrival_time_ -
|
||||
sender_report_builder_.GetRecentReportTime(
|
||||
receiver_report.last_status_report_id, rtcp_packet_arrival_time_);
|
||||
const auto non_network_delay =
|
||||
Clock::to_duration(receiver_report.delay_since_last_report);
|
||||
|
||||
// Round trip time measurement: This is the time elapsed since the Sender
|
||||
// Report was sent, minus the time the Receiver did other stuff before sending
|
||||
// the Receiver Report back.
|
||||
//
|
||||
// If the round trip time seems to be less than or equal to zero, assume clock
|
||||
// imprecision by one or both peers caused a bad value to be calculated. The
|
||||
// true value is likely very close to zero (i.e., this is ideal network
|
||||
// behavior); and so just represent this as 75 µs, an optimistic
|
||||
// wired-Ethernet LAN ping time.
|
||||
constexpr auto kNearZeroRoundTripTime = Clock::to_duration(microseconds(75));
|
||||
static_assert(kNearZeroRoundTripTime > Clock::duration::zero(),
|
||||
"More precision in Clock::duration needed!");
|
||||
const Clock::duration measurement =
|
||||
std::max(total_delay - non_network_delay, kNearZeroRoundTripTime);
|
||||
|
||||
// Validate the measurement by using the current target playout delay as a
|
||||
// "reasonable upper-bound." It's certainly possible that the actual network
|
||||
// round-trip time could exceed the target playout delay, but that would mean
|
||||
// the current network performance is totally inadequate for streaming anyway.
|
||||
// We cap the measurement here instead of ignoring it so the Sender still
|
||||
// backs off its estimates during severe network congestion.
|
||||
Clock::duration clamped_measurement = measurement;
|
||||
if (clamped_measurement > target_playout_delay_) {
|
||||
OSP_LOG_WARN << "Capping round-trip time measurement (" << measurement
|
||||
<< ") to the current target playout delay ("
|
||||
<< target_playout_delay_ << ").";
|
||||
clamped_measurement = target_playout_delay_;
|
||||
}
|
||||
|
||||
round_trip_time_ = SmoothRoundTripTime(round_trip_time_, clamped_measurement);
|
||||
TRACE_SCOPED1(TraceCategory::kSender, "UpdatedRoundTripTime",
|
||||
"round_trip_time", ToString(round_trip_time_));
|
||||
}
|
||||
|
||||
void SenderImpl::OnCastReceiverFrameLogMessages(
|
||||
std::vector<RtcpReceiverFrameLogMessage> messages) {
|
||||
statistics_dispatcher_.DispatchFrameLogMessages(config_.stream_type,
|
||||
messages);
|
||||
}
|
||||
|
||||
void SenderImpl::OnReceiverIndicatesPictureLoss() {
|
||||
TRACE_DEFAULT_SCOPED1(TraceCategory::kSender, "last_received_frame_id",
|
||||
picture_lost_at_frame_id_.ToString());
|
||||
// The Receiver will continue the PLI notifications until it has received a
|
||||
// key frame. Thus, if a key frame is already in-flight, don't make a state
|
||||
// change that would cause this Sender to force another expensive key frame.
|
||||
if (checkpoint_frame_id_ < last_enqueued_key_frame_id_) {
|
||||
return;
|
||||
}
|
||||
|
||||
picture_lost_at_frame_id_ = checkpoint_frame_id_;
|
||||
|
||||
if (observer_) {
|
||||
observer_->OnPictureLost();
|
||||
}
|
||||
|
||||
// Note: It may seem that all pending frames should be canceled until
|
||||
// EnqueueFrame() is called with a key frame. However:
|
||||
//
|
||||
// 1. The Receiver should still be the main authority on what frames/packets
|
||||
// are being ACK'ed and NACK'ed.
|
||||
//
|
||||
// 2. It may be desirable for the Receiver to be "limping along" in the
|
||||
// meantime. For example, video may be corrupted but mostly watchable,
|
||||
// and so it's best for the Sender to continue sending the non-key frames
|
||||
// until the Receiver indicates otherwise.
|
||||
}
|
||||
|
||||
void SenderImpl::OnReceiverCheckpoint(FrameId frame_id,
|
||||
milliseconds playout_delay) {
|
||||
TRACE_DEFAULT_SCOPED2(TraceCategory::kSender, "frame_id", frame_id.ToString(),
|
||||
"playout_delay", ToString(playout_delay));
|
||||
if (frame_id > last_enqueued_frame_id_) {
|
||||
TRACE_SET_RESULT(Error::Code::kParameterOutOfRange);
|
||||
OSP_LOG_ERROR
|
||||
<< "Ignoring checkpoint for " << latest_expected_frame_id_
|
||||
<< " because this Sender could not have sent any frames after "
|
||||
<< last_enqueued_frame_id_ << '.';
|
||||
return;
|
||||
}
|
||||
// CompoundRtcpParser should guarantee this:
|
||||
OSP_CHECK_GE(playout_delay, milliseconds::zero());
|
||||
while (checkpoint_frame_id_ < frame_id) {
|
||||
++checkpoint_frame_id_;
|
||||
PendingFrameSlot& slot = get_slot_for(checkpoint_frame_id_);
|
||||
if (slot.is_active_for_frame(checkpoint_frame_id_)) {
|
||||
const RtpTimeTicks rtp_timestamp = slot.frame->rtp_timestamp;
|
||||
statistics_dispatcher_.DispatchAckEvent(
|
||||
config_.stream_type, rtp_timestamp, checkpoint_frame_id_);
|
||||
CancelPendingFrame(checkpoint_frame_id_, /*was_acked*/ true);
|
||||
|
||||
TRACE_FLOW_STEP(TraceCategory::kSender, "Frame.Acked",
|
||||
checkpoint_frame_id_);
|
||||
}
|
||||
}
|
||||
latest_expected_frame_id_ = std::max(latest_expected_frame_id_, frame_id);
|
||||
DispatchCancellations();
|
||||
|
||||
if (playout_delay != target_playout_delay_ &&
|
||||
frame_id >= playout_delay_change_at_frame_id_) {
|
||||
OSP_LOG_WARN << "Sender's target playout delay (" << target_playout_delay_
|
||||
<< ") disagrees with the Receiver's (" << playout_delay << ")";
|
||||
}
|
||||
}
|
||||
|
||||
void SenderImpl::OnReceiverHasFrames(std::vector<FrameId> acks) {
|
||||
OSP_DCHECK(!acks.empty() && AreElementsSortedAndUnique(acks));
|
||||
TRACE_DEFAULT_SCOPED1(TraceCategory::kSender, "frame_ids",
|
||||
string_util::Join(acks));
|
||||
|
||||
if (acks.back() > last_enqueued_frame_id_) {
|
||||
TRACE_SET_RESULT(Error::Code::kParameterOutOfRange);
|
||||
OSP_LOG_ERROR << "Ignoring individual frame ACKs: ACKing frame "
|
||||
<< latest_expected_frame_id_
|
||||
<< " is invalid because this Sender could not have sent any "
|
||||
"frames after "
|
||||
<< last_enqueued_frame_id_ << '.';
|
||||
return;
|
||||
}
|
||||
|
||||
for (FrameId id : acks) {
|
||||
TRACE_FLOW_STEP(TraceCategory::kSender, "Frame.Acked", id);
|
||||
PendingFrameSlot& slot = get_slot_for(id);
|
||||
if (slot.is_active_for_frame(id)) {
|
||||
const RtpTimeTicks rtp_timestamp = slot.frame->rtp_timestamp;
|
||||
statistics_dispatcher_.DispatchAckEvent(config_.stream_type,
|
||||
rtp_timestamp, id);
|
||||
}
|
||||
CancelPendingFrame(id, /*was_acked*/ true);
|
||||
}
|
||||
latest_expected_frame_id_ = std::max(latest_expected_frame_id_, acks.back());
|
||||
DispatchCancellations();
|
||||
}
|
||||
|
||||
void SenderImpl::OnReceiverIsMissingPackets(std::vector<PacketNack> nacks) {
|
||||
TRACE_DEFAULT_SCOPED1(TraceCategory::kSender, "number_of_packets",
|
||||
std::to_string(nacks.size()));
|
||||
OSP_DCHECK(!nacks.empty() && AreElementsSortedAndUnique(nacks));
|
||||
OSP_CHECK_NE(rtcp_packet_arrival_time_, SenderPacketRouter::kNever);
|
||||
|
||||
// This is a point-in-time threshold that indicates whether each NACK will
|
||||
// trigger a packet retransmit. The threshold is based on the network round
|
||||
// trip time because a Receiver's NACK may have been issued while the needed
|
||||
// packet was in-flight from the Sender. In such cases, the Receiver's NACK is
|
||||
// likely stale and this Sender should not redundantly re-transmit the packet
|
||||
// again.
|
||||
const Clock::time_point too_recent_a_send_time =
|
||||
rtcp_packet_arrival_time_ - round_trip_time_;
|
||||
|
||||
// Iterate over all the NACKs...
|
||||
bool need_to_send = false;
|
||||
for (auto nack_it = nacks.begin(); nack_it != nacks.end();) {
|
||||
// Find the slot associated with the NACK's frame ID.
|
||||
const FrameId frame_id = nack_it->frame_id;
|
||||
PendingFrameSlot* slot = nullptr;
|
||||
if (frame_id <= last_enqueued_frame_id_) {
|
||||
PendingFrameSlot& candidate_slot = get_slot_for(frame_id);
|
||||
if (candidate_slot.is_active_for_frame(frame_id)) {
|
||||
slot = &candidate_slot;
|
||||
}
|
||||
}
|
||||
|
||||
// If no slot was found (i.e., the NACK is invalid) for the frame, skip-over
|
||||
// all other NACKs for the same frame. While it seems to be a bug that the
|
||||
// Receiver would attempt to NACK a frame that does not yet exist, this can
|
||||
// happen in rare cases where RTCP packets arrive out-of-order (i.e., the
|
||||
// network shuffled them).
|
||||
if (!slot) {
|
||||
TRACE_SCOPED1(TraceCategory::kSender, "MissingNackSlot", "frame_id",
|
||||
frame_id.ToString());
|
||||
for (++nack_it; nack_it != nacks.end() && nack_it->frame_id == frame_id;
|
||||
++nack_it) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
latest_expected_frame_id_ = std::max(latest_expected_frame_id_, frame_id);
|
||||
|
||||
const auto HandleIndividualNack = [&](FramePacketId packet_id) {
|
||||
if (slot->packet_sent_times[packet_id] <= too_recent_a_send_time) {
|
||||
slot->send_flags.Set(packet_id);
|
||||
need_to_send = true;
|
||||
}
|
||||
};
|
||||
const FramePacketId range_end = slot->packet_sent_times.size();
|
||||
if (nack_it->packet_id == kAllPacketsLost) {
|
||||
for (FramePacketId packet_id = 0; packet_id < range_end; ++packet_id) {
|
||||
HandleIndividualNack(packet_id);
|
||||
}
|
||||
++nack_it;
|
||||
} else {
|
||||
do {
|
||||
if (nack_it->packet_id < range_end) {
|
||||
HandleIndividualNack(nack_it->packet_id);
|
||||
} else {
|
||||
OSP_LOG_WARN
|
||||
<< "Ignoring NACK for packet that doesn't exist in frame "
|
||||
<< frame_id << ": " << static_cast<int>(nack_it->packet_id);
|
||||
}
|
||||
++nack_it;
|
||||
} while (nack_it != nacks.end() && nack_it->frame_id == frame_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (need_to_send) {
|
||||
packet_router_->RequestRtpSend(rtcp_session_.receiver_ssrc());
|
||||
}
|
||||
}
|
||||
|
||||
SenderImpl::ChosenPacket SenderImpl::ChooseNextRtpPacketNeedingSend() {
|
||||
// Find the oldest packet needing to be sent (or re-sent).
|
||||
for (FrameId frame_id = checkpoint_frame_id_ + 1;
|
||||
frame_id <= last_enqueued_frame_id_; ++frame_id) {
|
||||
PendingFrameSlot& slot = get_slot_for(frame_id);
|
||||
if (!slot.is_active_for_frame(frame_id)) {
|
||||
continue; // Frame was canceled. None of its packets need to be sent.
|
||||
}
|
||||
const FramePacketId packet_id = slot.send_flags.FindFirstSet();
|
||||
if (packet_id < slot.send_flags.size()) {
|
||||
return {&slot, packet_id};
|
||||
}
|
||||
}
|
||||
|
||||
return {}; // Nothing needs to be sent.
|
||||
}
|
||||
|
||||
SenderImpl::ChosenPacketAndWhen SenderImpl::ChooseKickstartPacket() {
|
||||
if (latest_expected_frame_id_ >= last_enqueued_frame_id_) {
|
||||
// Since the Receiver must know about all of the frames currently queued, no
|
||||
// Kickstart packet is necessary.
|
||||
return {};
|
||||
}
|
||||
|
||||
// The Kickstart packet is always in the last-enqueued frame, so that the
|
||||
// Receiver will know about every frame the Sender has. However, which packet
|
||||
// should be chosen? Any would do, since all packets contain the frame's total
|
||||
// packet count. For historical reasons, all sender implementations have
|
||||
// always just sent the last packet; and so that tradition is continued here.
|
||||
ChosenPacketAndWhen chosen;
|
||||
chosen.slot = &get_slot_for(last_enqueued_frame_id_);
|
||||
// Note: This frame cannot have been canceled since
|
||||
// `latest_expected_frame_id_` hasn't yet reached this point.
|
||||
OSP_CHECK(chosen.slot->is_active_for_frame(last_enqueued_frame_id_));
|
||||
chosen.packet_id = chosen.slot->send_flags.size() - 1;
|
||||
|
||||
const Clock::time_point time_last_sent =
|
||||
chosen.slot->packet_sent_times[chosen.packet_id];
|
||||
// Sanity-check: This method should not be called to choose a packet while
|
||||
// there are still unsent packets.
|
||||
OSP_CHECK_NE(time_last_sent, SenderPacketRouter::kNever);
|
||||
|
||||
// The desired Kickstart interval is a fraction of the total
|
||||
// `target_playout_delay_`. The reason for the specific ratio here is based on
|
||||
// lost knowledge (from legacy implementations); but it makes sense (i.e., to
|
||||
// be a good "network citizen") to be less aggressive for larger playout delay
|
||||
// windows, and more aggressive for shorter ones to avoid too-late packet
|
||||
// arrivals.
|
||||
using kWaitFraction = std::ratio<1, 20>;
|
||||
const Clock::duration desired_kickstart_interval =
|
||||
Clock::to_duration(target_playout_delay_) * kWaitFraction::num /
|
||||
kWaitFraction::den;
|
||||
// The actual interval used is increased, if current network performance
|
||||
// warrants waiting longer. Don't send a Kickstart packet until no NACKs
|
||||
// have been received for two network round-trip periods.
|
||||
constexpr int kLowerBoundRoundTrips = 2;
|
||||
const Clock::duration kickstart_interval = std::max(
|
||||
desired_kickstart_interval, round_trip_time_ * kLowerBoundRoundTrips);
|
||||
chosen.when = time_last_sent + kickstart_interval;
|
||||
|
||||
return chosen;
|
||||
}
|
||||
|
||||
void SenderImpl::CancelPendingFrame(FrameId frame_id, bool was_acked) {
|
||||
TRACE_FLOW_END(TraceCategory::kSender, "Frame.Cancelled", frame_id);
|
||||
|
||||
PendingFrameSlot& slot = get_slot_for(frame_id);
|
||||
if (!slot.is_active_for_frame(frame_id)) {
|
||||
return; // Frame was already canceled.
|
||||
}
|
||||
|
||||
if (was_acked) {
|
||||
packet_router_->OnPayloadReceived(
|
||||
slot.frame->data.size(), rtcp_packet_arrival_time_, round_trip_time_);
|
||||
}
|
||||
|
||||
slot.frame.reset();
|
||||
OSP_CHECK_GT(num_frames_in_flight_, 0);
|
||||
--num_frames_in_flight_;
|
||||
if (observer_) {
|
||||
pending_cancellations_.emplace_back(frame_id);
|
||||
}
|
||||
}
|
||||
|
||||
void SenderImpl::DispatchCancellations() {
|
||||
if (observer_) {
|
||||
for (const FrameId id : pending_cancellations_) {
|
||||
observer_->OnFrameCanceled(id);
|
||||
}
|
||||
}
|
||||
pending_cancellations_.clear();
|
||||
|
||||
// At this point, there should either be no frames in flight, or the frame
|
||||
// immediately after `checkpoint_frame_id_` must be valid.
|
||||
OSP_DCHECK((num_frames_in_flight_ == 0) ||
|
||||
get_slot_for(checkpoint_frame_id_ + 1)
|
||||
.is_active_for_frame(checkpoint_frame_id_ + 1));
|
||||
}
|
||||
|
||||
SenderImpl::PendingFrameSlot::PendingFrameSlot() = default;
|
||||
SenderImpl::PendingFrameSlot::~PendingFrameSlot() = default;
|
||||
|
||||
} // namespace openscreen::cast
|
||||
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_
|
||||
79
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/sender_report_builder.cc
vendored
Normal file
79
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/sender_report_builder.cc
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/sender_report_builder.h"
|
||||
|
||||
#include "cast/streaming/impl/packet_util.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
SenderReportBuilder::SenderReportBuilder(RtcpSession& session)
|
||||
: session_(session) {}
|
||||
|
||||
SenderReportBuilder::~SenderReportBuilder() = default;
|
||||
|
||||
std::pair<ByteBuffer, StatusReportId> SenderReportBuilder::BuildPacket(
|
||||
const RtcpSenderReport& sender_report,
|
||||
ByteBuffer buffer) const {
|
||||
OSP_CHECK_GE(buffer.size(), kRequiredBufferSize);
|
||||
|
||||
uint8_t* const packet_begin = buffer.data();
|
||||
|
||||
RtcpCommonHeader header;
|
||||
header.packet_type = RtcpPacketType::kSenderReport;
|
||||
header.payload_size = kRtcpSenderReportSize;
|
||||
if (sender_report.report_block) {
|
||||
header.with.report_count = 1;
|
||||
header.payload_size += kRtcpReportBlockSize;
|
||||
} else {
|
||||
header.with.report_count = 0;
|
||||
}
|
||||
header.AppendFields(buffer);
|
||||
|
||||
AppendField<uint32_t>(session_->sender_ssrc(), buffer);
|
||||
const NtpTimestamp ntp_timestamp =
|
||||
session_->ntp_converter().ToNtpTimestamp(sender_report.reference_time);
|
||||
AppendField<uint64_t>(ntp_timestamp, buffer);
|
||||
AppendField<uint32_t>(sender_report.rtp_timestamp.lower_32_bits(), buffer);
|
||||
AppendField<uint32_t>(sender_report.send_packet_count, buffer);
|
||||
AppendField<uint32_t>(sender_report.send_octet_count, buffer);
|
||||
if (sender_report.report_block) {
|
||||
sender_report.report_block->AppendFields(buffer);
|
||||
}
|
||||
|
||||
uint8_t* const packet_end = buffer.data();
|
||||
return std::make_pair(ByteBuffer(packet_begin, packet_end - packet_begin),
|
||||
ToStatusReportId(ntp_timestamp));
|
||||
}
|
||||
|
||||
Clock::time_point SenderReportBuilder::GetRecentReportTime(
|
||||
StatusReportId report_id,
|
||||
Clock::time_point on_or_before) const {
|
||||
// Assumption: The `report_id` is the middle 32 bits of a 64-bit NtpTimestamp.
|
||||
static_assert(ToStatusReportId(NtpTimestamp{0x0192a3b4c5d6e7f8}) ==
|
||||
StatusReportId{0xa3b4c5d6},
|
||||
"FIXME: ToStatusReportId() implementation changed.");
|
||||
|
||||
// Compute the maximum possible NtpTimestamp. Then, use its uppermost 16 bits
|
||||
// and the 32 bits from the report_id to produce a reconstructed NtpTimestamp.
|
||||
const NtpTimestamp max_timestamp =
|
||||
session_->ntp_converter().ToNtpTimestamp(on_or_before);
|
||||
// max_timestamp: HH......
|
||||
// report_id: LLLL
|
||||
// ↓↓ ↙↙↙↙
|
||||
// reconstructed: HHLLLL00
|
||||
NtpTimestamp reconstructed = (max_timestamp & (uint64_t{0xffff} << 48)) |
|
||||
(static_cast<uint64_t>(report_id) << 16);
|
||||
// If the reconstructed timestamp is greater than the maximum one, rollover
|
||||
// of the lower 48 bits occurred. Subtract one from the upper 16 bits to
|
||||
// rectify that.
|
||||
if (reconstructed > max_timestamp) {
|
||||
reconstructed -= uint64_t{1} << 48;
|
||||
}
|
||||
|
||||
return session_->ntp_converter().ToLocalTime(reconstructed);
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
50
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/sender_report_builder.h
vendored
Normal file
50
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/sender_report_builder.h
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2019 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_REPORT_BUILDER_H_
|
||||
#define CAST_STREAMING_IMPL_SENDER_REPORT_BUILDER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/impl/rtcp_common.h"
|
||||
#include "cast/streaming/impl/rtcp_session.h"
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/raw_ref.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Builds RTCP packets containing one Sender Report.
|
||||
class SenderReportBuilder {
|
||||
public:
|
||||
explicit SenderReportBuilder(RtcpSession& session);
|
||||
~SenderReportBuilder();
|
||||
|
||||
// Serializes the given `sender_report` as a RTCP packet and writes it to
|
||||
// `buffer` (which must be kRequiredBufferSize in size). Returns the subspan
|
||||
// of `buffer` that contains the result and a StatusReportId the receiver
|
||||
// might use in its own reports to reference this specific report.
|
||||
std::pair<ByteBuffer, StatusReportId> BuildPacket(
|
||||
const RtcpSenderReport& sender_report,
|
||||
ByteBuffer buffer) const;
|
||||
|
||||
// Returns the approximate reference time from a recently-built Sender Report,
|
||||
// based on the given `report_id` and maximum possible reference time.
|
||||
Clock::time_point GetRecentReportTime(StatusReportId report_id,
|
||||
Clock::time_point on_or_before) const;
|
||||
|
||||
// The required size (in bytes) of the buffer passed to BuildPacket().
|
||||
static constexpr int kRequiredBufferSize =
|
||||
kRtcpCommonHeaderSize + kRtcpSenderReportSize + kRtcpReportBlockSize;
|
||||
|
||||
private:
|
||||
const raw_ref<RtcpSession> session_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_SENDER_REPORT_BUILDER_H_
|
||||
573
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_analyzer.cc
vendored
Normal file
573
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_analyzer.cc
vendored
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/statistics_analyzer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "platform/base/trivial_clock_traits.h"
|
||||
#include "util/chrono_helpers.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
using openscreen::clock_operators::operator<<;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr Clock::duration kAnalysisInterval = std::chrono::milliseconds(500);
|
||||
|
||||
constexpr size_t kMaxRecentPacketInfoMapSize = 1000;
|
||||
constexpr size_t kMaxRecentFrameInfoMapSize = 200;
|
||||
|
||||
constexpr int kDefaultMaxLatencyBucketMs = 800;
|
||||
constexpr int kDefaultBucketWidthMs = 20;
|
||||
|
||||
double InMilliseconds(Clock::duration duration) {
|
||||
return static_cast<double>(to_milliseconds(duration).count());
|
||||
}
|
||||
|
||||
bool IsReceiverEvent(StatisticsEvent::Type event) {
|
||||
return event == StatisticsEvent::Type::kFrameAckSent ||
|
||||
event == StatisticsEvent::Type::kFrameDecoded ||
|
||||
event == StatisticsEvent::Type::kFramePlayedOut ||
|
||||
event == StatisticsEvent::Type::kPacketReceived;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StatisticsAnalyzer::StatisticsAnalyzer(
|
||||
SenderStatsClient* stats_client,
|
||||
ClockNowFunctionPtr now,
|
||||
TaskRunner& task_runner,
|
||||
std::unique_ptr<ClockOffsetEstimator> offset_estimator)
|
||||
: stats_client_(stats_client),
|
||||
offset_estimator_(std::move(offset_estimator)),
|
||||
now_(now),
|
||||
alarm_(now, task_runner),
|
||||
start_time_(now()) {
|
||||
statistics_collector_ = std::make_unique<StatisticsCollector>(now_);
|
||||
InitHistograms();
|
||||
}
|
||||
|
||||
StatisticsAnalyzer::~StatisticsAnalyzer() = default;
|
||||
|
||||
void StatisticsAnalyzer::ScheduleAnalysis() {
|
||||
Clock::time_point next_analysis_time = now_() + kAnalysisInterval;
|
||||
alarm_.Schedule([this] { AnalyzeStatistics(); }, next_analysis_time);
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::InitHistograms() {
|
||||
for (auto& histogram : histograms_.audio) {
|
||||
histogram =
|
||||
SimpleHistogram(0, kDefaultMaxLatencyBucketMs, kDefaultBucketWidthMs);
|
||||
}
|
||||
for (auto& histogram : histograms_.video) {
|
||||
histogram =
|
||||
SimpleHistogram(0, kDefaultMaxLatencyBucketMs, kDefaultBucketWidthMs);
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::AnalyzeStatistics() {
|
||||
ProcessFrameEvents(statistics_collector_->TakeRecentFrameEvents());
|
||||
ProcessPacketEvents(statistics_collector_->TakeRecentPacketEvents());
|
||||
SendStatistics();
|
||||
ScheduleAnalysis();
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::SendStatistics() {
|
||||
if (!stats_client_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Clock::time_point end_time = now_();
|
||||
stats_client_->OnStatisticsUpdated(SenderStats{
|
||||
.audio_statistics =
|
||||
ConstructStatisticsList(end_time, StatisticsEvent::MediaType::kAudio),
|
||||
.audio_histograms = histograms_.audio,
|
||||
.video_statistics =
|
||||
ConstructStatisticsList(end_time, StatisticsEvent::MediaType::kVideo),
|
||||
.video_histograms = histograms_.video});
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::ProcessFrameEvents(
|
||||
const std::vector<FrameEvent>& frame_events) {
|
||||
for (FrameEvent frame_event : frame_events) {
|
||||
offset_estimator_->OnFrameEvent(frame_event);
|
||||
|
||||
FrameStatsMap& frame_stats_map = frame_stats_.Get(frame_event.media_type);
|
||||
auto it = frame_stats_map.find(frame_event.type);
|
||||
if (it == frame_stats_map.end()) {
|
||||
frame_stats_map.insert(std::make_pair(
|
||||
frame_event.type,
|
||||
FrameStatsAggregate{.event_counter = 1,
|
||||
.sum_size = frame_event.size,
|
||||
.sum_delay = frame_event.delay_delta}));
|
||||
} else {
|
||||
++(it->second.event_counter);
|
||||
it->second.sum_size += frame_event.size;
|
||||
it->second.sum_delay += frame_event.delay_delta;
|
||||
}
|
||||
|
||||
RecordEventTimes(frame_event);
|
||||
RecordFrameLatencies(frame_event);
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::ProcessPacketEvents(
|
||||
const std::vector<PacketEvent>& packet_events) {
|
||||
for (PacketEvent packet_event : packet_events) {
|
||||
offset_estimator_->OnPacketEvent(packet_event);
|
||||
|
||||
PacketStatsMap& packet_stats_map =
|
||||
packet_stats_.Get(packet_event.media_type);
|
||||
auto it = packet_stats_map.find(packet_event.type);
|
||||
if (it == packet_stats_map.end()) {
|
||||
packet_stats_map.insert(
|
||||
std::make_pair(packet_event.type,
|
||||
PacketStatsAggregate{.event_counter = 1,
|
||||
.sum_size = packet_event.size}));
|
||||
} else {
|
||||
++(it->second.event_counter);
|
||||
it->second.sum_size += packet_event.size;
|
||||
}
|
||||
|
||||
RecordEventTimes(packet_event);
|
||||
if (packet_event.type == StatisticsEvent::Type::kPacketSentToNetwork ||
|
||||
packet_event.type == StatisticsEvent::Type::kPacketReceived) {
|
||||
RecordPacketLatencies(packet_event);
|
||||
} else if (packet_event.type ==
|
||||
StatisticsEvent::Type::kPacketRetransmitted) {
|
||||
// We only measure network latency for packets that are not retransmitted.
|
||||
ErasePacketInfo(packet_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::RecordFrameLatencies(const FrameEvent& frame_event) {
|
||||
FrameInfoMap& frame_infos = recent_frame_infos_.Get(frame_event.media_type);
|
||||
|
||||
// Event is too old, don't bother.
|
||||
const bool map_is_full = frame_infos.size() == kMaxRecentFrameInfoMapSize;
|
||||
if (map_is_full && frame_event.rtp_timestamp <= frame_infos.begin()->first) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = frame_infos.find(frame_event.rtp_timestamp);
|
||||
if (it == frame_infos.end()) {
|
||||
if (map_is_full) {
|
||||
frame_infos.erase(frame_infos.begin());
|
||||
}
|
||||
|
||||
auto emplace_result =
|
||||
frame_infos.emplace(frame_event.rtp_timestamp, FrameInfo{});
|
||||
OSP_CHECK(emplace_result.second);
|
||||
it = emplace_result.first;
|
||||
}
|
||||
|
||||
switch (frame_event.type) {
|
||||
case StatisticsEvent::Type::kFrameCaptureBegin:
|
||||
it->second.capture_begin_time = frame_event.timestamp;
|
||||
break;
|
||||
|
||||
case StatisticsEvent::Type::kFrameCaptureEnd: {
|
||||
it->second.capture_end_time = frame_event.timestamp;
|
||||
if (it->second.capture_begin_time != Clock::time_point::min()) {
|
||||
const Clock::duration capture_latency =
|
||||
frame_event.timestamp - it->second.capture_begin_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgCaptureLatencyMs,
|
||||
capture_latency, frame_event.media_type);
|
||||
AddToHistogram(HistogramType::kCaptureLatencyMs, frame_event.media_type,
|
||||
InMilliseconds(capture_latency));
|
||||
}
|
||||
} break;
|
||||
|
||||
case StatisticsEvent::Type::kFrameEncoded: {
|
||||
it->second.encode_end_time = frame_event.timestamp;
|
||||
if (it->second.capture_end_time != Clock::time_point::min()) {
|
||||
const Clock::duration encode_latency =
|
||||
frame_event.timestamp - it->second.capture_end_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgEncodeTimeMs, encode_latency,
|
||||
frame_event.media_type);
|
||||
AddToHistogram(HistogramType::kEncodeTimeMs, frame_event.media_type,
|
||||
InMilliseconds(encode_latency));
|
||||
}
|
||||
} break;
|
||||
|
||||
// Frame latency is the time from when the frame is encoded until the
|
||||
// receiver ack for the frame is sent.
|
||||
case StatisticsEvent::Type::kFrameAckSent: {
|
||||
const auto adjusted_timestamp =
|
||||
ToSenderTimestamp(frame_event.timestamp, frame_event.media_type);
|
||||
if (!adjusted_timestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (it->second.encode_end_time != Clock::time_point::min()) {
|
||||
const Clock::duration frame_latency =
|
||||
*adjusted_timestamp - it->second.encode_end_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgFrameLatencyMs, frame_latency,
|
||||
frame_event.media_type);
|
||||
}
|
||||
} break;
|
||||
|
||||
case StatisticsEvent::Type::kFramePlayedOut: {
|
||||
const auto adjusted_timestamp =
|
||||
ToSenderTimestamp(frame_event.timestamp, frame_event.media_type);
|
||||
if (!adjusted_timestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (it->second.capture_begin_time != Clock::time_point::min()) {
|
||||
const Clock::duration e2e_latency =
|
||||
*adjusted_timestamp - it->second.capture_begin_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgEndToEndLatencyMs,
|
||||
e2e_latency, frame_event.media_type);
|
||||
AddToHistogram(HistogramType::kEndToEndLatencyMs,
|
||||
frame_event.media_type, InMilliseconds(e2e_latency));
|
||||
}
|
||||
|
||||
// Positive delay means the frame is late.
|
||||
if (frame_event.delay_delta > Clock::duration::zero()) {
|
||||
session_stats_.Get(frame_event.media_type).late_frame_counter += 1;
|
||||
AddToHistogram(HistogramType::kFrameLatenessMs, frame_event.media_type,
|
||||
InMilliseconds(frame_event.delay_delta));
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::RecordPacketLatencies(
|
||||
const PacketEvent& packet_event) {
|
||||
FrameInfoMap& frame_infos = recent_frame_infos_.Get(packet_event.media_type);
|
||||
|
||||
// Queueing latency is the time from when a frame is encoded to when the
|
||||
// packet is first sent.
|
||||
if (packet_event.type == StatisticsEvent::Type::kPacketSentToNetwork) {
|
||||
const auto it = frame_infos.find(packet_event.rtp_timestamp);
|
||||
|
||||
// We have an encode end time for a frame associated with this packet.
|
||||
if (it != frame_infos.end()) {
|
||||
const Clock::duration queueing_latency =
|
||||
packet_event.timestamp - it->second.encode_end_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgQueueingLatencyMs,
|
||||
queueing_latency, packet_event.media_type);
|
||||
AddToHistogram(HistogramType::kQueueingLatencyMs, packet_event.media_type,
|
||||
InMilliseconds(queueing_latency));
|
||||
}
|
||||
}
|
||||
|
||||
StatisticsAnalyzer::PacketKey key =
|
||||
std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id);
|
||||
PacketInfoMap& packet_infos =
|
||||
recent_packet_infos_.Get(packet_event.media_type);
|
||||
|
||||
const auto it = packet_infos.find(key);
|
||||
if (it == packet_infos.end()) {
|
||||
packet_infos.insert(
|
||||
std::make_pair(key, PacketInfo{.timestamp = packet_event.timestamp,
|
||||
.type = packet_event.type}));
|
||||
if (packet_infos.size() > kMaxRecentPacketInfoMapSize) {
|
||||
packet_infos.erase(packet_infos.begin());
|
||||
}
|
||||
} else { // We know when this packet was sent, and when it arrived.
|
||||
PacketInfo value = it->second;
|
||||
StatisticsEvent::Type recorded_type = value.type;
|
||||
Clock::time_point packet_sent_time;
|
||||
Clock::time_point packet_received_time;
|
||||
if (recorded_type == StatisticsEvent::Type::kPacketSentToNetwork &&
|
||||
packet_event.type == StatisticsEvent::Type::kPacketReceived) {
|
||||
packet_sent_time = value.timestamp;
|
||||
packet_received_time = packet_event.timestamp;
|
||||
} else if (recorded_type == StatisticsEvent::Type::kPacketReceived &&
|
||||
packet_event.type ==
|
||||
StatisticsEvent::Type::kPacketSentToNetwork) {
|
||||
packet_sent_time = packet_event.timestamp;
|
||||
packet_received_time = value.timestamp;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
packet_infos.erase(it);
|
||||
|
||||
// Use the offset estimator directly since we are trying to calculate the
|
||||
// average network latency.
|
||||
const std::optional<Clock::duration> receiver_offset =
|
||||
offset_estimator_->GetEstimatedOffset();
|
||||
if (!receiver_offset) {
|
||||
return;
|
||||
}
|
||||
packet_received_time -= *receiver_offset;
|
||||
|
||||
const auto latency = packet_received_time - packet_sent_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgNetworkLatencyMs, latency,
|
||||
packet_event.media_type);
|
||||
AddToHistogram(HistogramType::kNetworkLatencyMs, packet_event.media_type,
|
||||
InMilliseconds(latency));
|
||||
|
||||
// Packet latency is the time from when a frame is encoded until when the
|
||||
// packet is received.
|
||||
const auto frame_it = frame_infos.find(packet_event.rtp_timestamp);
|
||||
if (frame_it != frame_infos.end()) {
|
||||
const Clock::duration packet_latency =
|
||||
packet_received_time - frame_it->second.encode_end_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgPacketLatencyMs, packet_latency,
|
||||
packet_event.media_type);
|
||||
AddToHistogram(HistogramType::kPacketLatencyMs, packet_event.media_type,
|
||||
InMilliseconds(packet_latency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::RecordEventTimes(const StatisticsEvent& event) {
|
||||
SessionStats& session_stats = session_stats_.Get(event.media_type);
|
||||
|
||||
Clock::time_point sender_timestamp = event.timestamp;
|
||||
if (IsReceiverEvent(event.type)) {
|
||||
const auto latency = offset_estimator_->GetEstimatedLatency();
|
||||
if (latency) {
|
||||
const Clock::time_point estimated_sent_time =
|
||||
event.received_timestamp - *latency;
|
||||
session_stats.last_response_received_time = std::max(
|
||||
session_stats.last_response_received_time, estimated_sent_time);
|
||||
}
|
||||
|
||||
const auto result = ToSenderTimestamp(event.timestamp, event.media_type);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
sender_timestamp = *result;
|
||||
}
|
||||
|
||||
session_stats.first_event_time =
|
||||
std::min(session_stats.first_event_time, sender_timestamp);
|
||||
session_stats.last_event_time =
|
||||
std::max(session_stats.last_event_time, sender_timestamp);
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::ErasePacketInfo(const PacketEvent& packet_event) {
|
||||
const StatisticsAnalyzer::PacketKey key =
|
||||
std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id);
|
||||
PacketInfoMap& packet_infos =
|
||||
recent_packet_infos_.Get(packet_event.media_type);
|
||||
packet_infos.erase(key);
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::AddToLatencyAggregrate(
|
||||
StatisticType latency_stat,
|
||||
Clock::duration latency_delta,
|
||||
StatisticsEvent::MediaType media_type) {
|
||||
LatencyStatsMap& latency_stats = latency_stats_.Get(media_type);
|
||||
|
||||
auto it = latency_stats.find(latency_stat);
|
||||
if (it == latency_stats.end()) {
|
||||
latency_stats.insert(std::make_pair(
|
||||
latency_stat, LatencyStatsAggregate{.data_point_counter = 1,
|
||||
.sum_latency = latency_delta}));
|
||||
} else {
|
||||
++(it->second.data_point_counter);
|
||||
it->second.sum_latency += latency_delta;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::AddToHistogram(HistogramType histogram,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
int64_t sample) {
|
||||
histograms_.Get(media_type)[static_cast<int>(histogram)].Add(sample);
|
||||
}
|
||||
|
||||
SenderStats::StatisticsList StatisticsAnalyzer::ConstructStatisticsList(
|
||||
Clock::time_point end_time,
|
||||
StatisticsEvent::MediaType media_type) {
|
||||
SenderStats::StatisticsList stats_list;
|
||||
|
||||
PopulateFrameCountStat(StatisticsEvent::Type::kFrameDroppedByEncoder,
|
||||
StatisticType::kNumFramesDroppedByEncoder, media_type,
|
||||
stats_list);
|
||||
|
||||
PopulateFrameCountStat(StatisticsEvent::Type::kFrameCaptureEnd,
|
||||
StatisticType::kNumFramesCaptured, media_type,
|
||||
stats_list);
|
||||
|
||||
// kEnqueueFps
|
||||
PopulateFpsStat(StatisticsEvent::Type::kFrameEncoded,
|
||||
StatisticType::kEnqueueFps, media_type, end_time, stats_list);
|
||||
|
||||
constexpr StatisticType kSupportedLatencyStats[] = {
|
||||
StatisticType::kAvgEncodeTimeMs, StatisticType::kAvgCaptureLatencyMs,
|
||||
StatisticType::kAvgQueueingLatencyMs, StatisticType::kAvgNetworkLatencyMs,
|
||||
StatisticType::kAvgPacketLatencyMs, StatisticType::kAvgFrameLatencyMs,
|
||||
StatisticType::kAvgEndToEndLatencyMs,
|
||||
};
|
||||
for (StatisticType type : kSupportedLatencyStats) {
|
||||
PopulateAvgLatencyStat(type, media_type, stats_list);
|
||||
}
|
||||
|
||||
// kEncodeRateKbps
|
||||
PopulateFrameBitrateStat(StatisticsEvent::Type::kFrameEncoded,
|
||||
StatisticType::kEncodeRateKbps, media_type, end_time,
|
||||
stats_list);
|
||||
|
||||
// kPacketTransmissionRateKbps
|
||||
PopulatePacketBitrateStat(StatisticsEvent::Type::kPacketSentToNetwork,
|
||||
StatisticType::kPacketTransmissionRateKbps,
|
||||
media_type, end_time, stats_list);
|
||||
|
||||
// kNumPacketsSent
|
||||
PopulatePacketCountStat(StatisticsEvent::Type::kPacketSentToNetwork,
|
||||
StatisticType::kNumPacketsSent, media_type,
|
||||
stats_list);
|
||||
|
||||
// kNumPacketsReceived
|
||||
PopulatePacketCountStat(StatisticsEvent::Type::kPacketReceived,
|
||||
StatisticType::kNumPacketsReceived, media_type,
|
||||
stats_list);
|
||||
|
||||
// kTimeSinceLastReceiverResponseMs
|
||||
// kFirstEventTimeMs
|
||||
// kLastEventTimeMs
|
||||
// kNumLateFrames
|
||||
PopulateSessionStats(media_type, end_time, stats_list);
|
||||
|
||||
return stats_list;
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulatePacketCountStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
PacketStatsMap& stats_map = packet_stats_.Get(media_type);
|
||||
|
||||
auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
stats_list[static_cast<int>(stat)] = it->second.event_counter;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateFrameCountStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
FrameStatsMap& stats_map = frame_stats_.Get(media_type);
|
||||
|
||||
const auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
stats_list[static_cast<int>(stat)] = it->second.event_counter;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateFpsStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
FrameStatsMap& stats_map = frame_stats_.Get(media_type);
|
||||
|
||||
const auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
const Clock::duration duration = end_time - start_time_;
|
||||
if (duration != Clock::duration::zero()) {
|
||||
const int count = it->second.event_counter;
|
||||
const double fps = (count / InMilliseconds(duration)) * 1000;
|
||||
stats_list[static_cast<int>(stat)] = fps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateAvgLatencyStat(
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list
|
||||
|
||||
) {
|
||||
LatencyStatsMap& latency_map = latency_stats_.Get(media_type);
|
||||
|
||||
const auto it = latency_map.find(stat);
|
||||
if (it != latency_map.end() && it->second.data_point_counter > 0) {
|
||||
const double avg_latency =
|
||||
InMilliseconds(it->second.sum_latency) / it->second.data_point_counter;
|
||||
stats_list[static_cast<int>(stat)] = avg_latency;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateFrameBitrateStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
FrameStatsMap& stats_map = frame_stats_.Get(media_type);
|
||||
|
||||
const auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
const Clock::duration duration = end_time - start_time_;
|
||||
if (duration != Clock::duration::zero()) {
|
||||
const double kbps = it->second.sum_size / InMilliseconds(duration) * 8;
|
||||
stats_list[static_cast<int>(stat)] = kbps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulatePacketBitrateStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
PacketStatsMap& stats_map = packet_stats_.Get(media_type);
|
||||
|
||||
auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
const Clock::duration duration = end_time - start_time_;
|
||||
if (duration != Clock::duration::zero()) {
|
||||
const double kbps = it->second.sum_size / InMilliseconds(duration) * 8;
|
||||
stats_list[static_cast<int>(stat)] = kbps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateSessionStats(
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
SessionStats& session_stats = session_stats_.Get(media_type);
|
||||
|
||||
if (session_stats.first_event_time != Clock::time_point::min()) {
|
||||
stats_list[static_cast<int>(StatisticType::kFirstEventTimeMs)] =
|
||||
InMilliseconds(session_stats.first_event_time.time_since_epoch());
|
||||
}
|
||||
|
||||
if (session_stats.last_event_time != Clock::time_point::min()) {
|
||||
stats_list[static_cast<int>(StatisticType::kLastEventTimeMs)] =
|
||||
InMilliseconds(session_stats.last_event_time.time_since_epoch());
|
||||
}
|
||||
|
||||
if (session_stats.last_response_received_time != Clock::time_point::min()) {
|
||||
stats_list[static_cast<int>(
|
||||
StatisticType::kTimeSinceLastReceiverResponseMs)] =
|
||||
InMilliseconds(end_time - session_stats.last_response_received_time);
|
||||
}
|
||||
|
||||
stats_list[static_cast<int>(StatisticType::kNumLateFrames)] =
|
||||
session_stats.late_frame_counter;
|
||||
}
|
||||
|
||||
std::optional<Clock::time_point> StatisticsAnalyzer::ToSenderTimestamp(
|
||||
Clock::time_point receiver_timestamp,
|
||||
StatisticsEvent::MediaType media_type) const {
|
||||
const std::optional<Clock::duration> receiver_offset =
|
||||
offset_estimator_->GetEstimatedOffset();
|
||||
if (!receiver_offset) {
|
||||
return {};
|
||||
}
|
||||
return receiver_timestamp - *receiver_offset;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
208
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_analyzer.h
vendored
Normal file
208
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_analyzer.h
vendored
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
// Copyright 2023 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_STATISTICS_ANALYZER_H_
|
||||
#define CAST_STREAMING_IMPL_STATISTICS_ANALYZER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/impl/clock_offset_estimator.h"
|
||||
#include "cast/streaming/impl/statistics_collector.h"
|
||||
#include "cast/streaming/public/statistics.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "util/alarm.h"
|
||||
#include "util/raw_ptr.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
class StatisticsAnalyzer {
|
||||
public:
|
||||
StatisticsAnalyzer(SenderStatsClient* stats_client,
|
||||
ClockNowFunctionPtr now,
|
||||
TaskRunner& task_runner,
|
||||
std::unique_ptr<ClockOffsetEstimator> offset_estimator);
|
||||
~StatisticsAnalyzer();
|
||||
|
||||
void ScheduleAnalysis();
|
||||
|
||||
// Get the statistics collector managed by this analyzer.
|
||||
StatisticsCollector* statistics_collector() {
|
||||
return statistics_collector_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
struct FrameStatsAggregate {
|
||||
int event_counter;
|
||||
uint32_t sum_size;
|
||||
Clock::duration sum_delay;
|
||||
};
|
||||
|
||||
struct PacketStatsAggregate {
|
||||
int event_counter;
|
||||
uint32_t sum_size;
|
||||
};
|
||||
|
||||
struct LatencyStatsAggregate {
|
||||
int data_point_counter;
|
||||
Clock::duration sum_latency;
|
||||
};
|
||||
|
||||
struct FrameInfo {
|
||||
Clock::time_point capture_begin_time = Clock::time_point::min();
|
||||
Clock::time_point capture_end_time = Clock::time_point::min();
|
||||
Clock::time_point encode_end_time = Clock::time_point::min();
|
||||
};
|
||||
|
||||
struct PacketInfo {
|
||||
Clock::time_point timestamp;
|
||||
StatisticsEvent::Type type;
|
||||
};
|
||||
|
||||
struct SessionStats {
|
||||
Clock::time_point first_event_time = Clock::time_point::max();
|
||||
Clock::time_point last_event_time = Clock::time_point::min();
|
||||
Clock::time_point last_response_received_time = Clock::time_point::min();
|
||||
int late_frame_counter = 0;
|
||||
};
|
||||
|
||||
// Named std::pair equivalent for audio + video classes.
|
||||
template <typename T>
|
||||
struct AVPair {
|
||||
T audio;
|
||||
T video;
|
||||
|
||||
const T& Get(StatisticsEvent::MediaType media_type) const {
|
||||
if (media_type == StatisticsEvent::MediaType::kAudio) {
|
||||
return audio;
|
||||
}
|
||||
OSP_CHECK(media_type == StatisticsEvent::MediaType::kVideo);
|
||||
return video;
|
||||
}
|
||||
T& Get(StatisticsEvent::MediaType media_type) {
|
||||
return const_cast<T&>(const_cast<const AVPair*>(this)->Get(media_type));
|
||||
}
|
||||
};
|
||||
|
||||
using FrameStatsMap = std::map<StatisticsEvent::Type, FrameStatsAggregate>;
|
||||
using PacketStatsMap = std::map<StatisticsEvent::Type, PacketStatsAggregate>;
|
||||
using LatencyStatsMap = std::map<StatisticType, LatencyStatsAggregate>;
|
||||
|
||||
using FrameInfoMap = std::map<RtpTimeTicks, FrameInfo>;
|
||||
using PacketKey = std::pair<RtpTimeTicks, uint16_t>;
|
||||
using PacketInfoMap = std::map<PacketKey, PacketInfo>;
|
||||
|
||||
// Initialize the stats histograms with the preferred min, max, and width.
|
||||
void InitHistograms();
|
||||
|
||||
// Takes the Frame and Packet events from the `collector_`, and processes them
|
||||
// into a form expected by `stats_client_`. Then sends the stats, and
|
||||
// schedules a future analysis.
|
||||
void AnalyzeStatistics();
|
||||
|
||||
// Constructs a stats list, and sends it to `stats_client_`;
|
||||
void SendStatistics();
|
||||
|
||||
// Handles incoming stat events, and adds their infos to all of the proper
|
||||
// stats maps / aggregates.
|
||||
void ProcessFrameEvents(const std::vector<FrameEvent>& frame_events);
|
||||
void ProcessPacketEvents(const std::vector<PacketEvent>& packet_events);
|
||||
void RecordFrameLatencies(const FrameEvent& frame_event);
|
||||
void RecordPacketLatencies(const PacketEvent& packet_event);
|
||||
void RecordEventTimes(const StatisticsEvent& event);
|
||||
void ErasePacketInfo(const PacketEvent& packet_event);
|
||||
void AddToLatencyAggregrate(StatisticType latency_stat,
|
||||
Clock::duration latency_delta,
|
||||
StatisticsEvent::MediaType media_type);
|
||||
void AddToHistogram(HistogramType histogram,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
int64_t sample);
|
||||
|
||||
// Creates a stats list, and populates the entries based on stored stats info
|
||||
// / aggregates for each stat field.
|
||||
SenderStats::StatisticsList ConstructStatisticsList(
|
||||
Clock::time_point end_time,
|
||||
StatisticsEvent::MediaType media_type);
|
||||
|
||||
void PopulatePacketCountStat(StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list);
|
||||
|
||||
void PopulateFrameCountStat(StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list);
|
||||
|
||||
void PopulateFpsStat(StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list);
|
||||
|
||||
void PopulateAvgLatencyStat(StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list);
|
||||
|
||||
void PopulateFrameBitrateStat(StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list);
|
||||
|
||||
void PopulatePacketBitrateStat(StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list);
|
||||
|
||||
void PopulateSessionStats(StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list);
|
||||
|
||||
// Calculates the offset between the sender and receiver clocks and returns
|
||||
// the sender-side version of this receiver timestamp, if possible.
|
||||
std::optional<Clock::time_point> ToSenderTimestamp(
|
||||
Clock::time_point receiver_timestamp,
|
||||
StatisticsEvent::MediaType media_type) const;
|
||||
|
||||
// The statistics client to which we report analyzed statistics.
|
||||
const raw_ptr<SenderStatsClient> stats_client_;
|
||||
|
||||
// The statistics collector from which we take the un-analyzed stats packets.
|
||||
std::unique_ptr<StatisticsCollector> statistics_collector_;
|
||||
|
||||
// Keeps track of the best-guess clock offset between the sender and receiver.
|
||||
std::unique_ptr<ClockOffsetEstimator> offset_estimator_;
|
||||
|
||||
// Keep track of time and events for this analyzer.
|
||||
ClockNowFunctionPtr now_;
|
||||
Alarm alarm_;
|
||||
Clock::time_point start_time_;
|
||||
|
||||
// Maps of frame / packet infos used for stats that rely on seeing multiple
|
||||
// events. For example, network latency is the calculated time difference
|
||||
// between went a packet is sent, and when it is received.
|
||||
AVPair<FrameInfoMap> recent_frame_infos_;
|
||||
AVPair<PacketInfoMap> recent_packet_infos_;
|
||||
|
||||
// Aggregate statistics.
|
||||
AVPair<FrameStatsMap> frame_stats_;
|
||||
AVPair<PacketStatsMap> packet_stats_;
|
||||
AVPair<LatencyStatsMap> latency_stats_;
|
||||
|
||||
// Stats that relate to the entirety of the session. For example, total late
|
||||
// frames, or time of last event.
|
||||
AVPair<SessionStats> session_stats_;
|
||||
|
||||
// Histograms.
|
||||
AVPair<SenderStats::HistogramsList> histograms_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_STATISTICS_ANALYZER_H_
|
||||
74
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_collector.cc
vendored
Normal file
74
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_collector.cc
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/statistics_collector.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/public/environment.h"
|
||||
#include "util/big_endian.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
StatisticsCollector::StatisticsCollector(ClockNowFunctionPtr now) : now_(now) {}
|
||||
StatisticsCollector::~StatisticsCollector() = default;
|
||||
|
||||
void StatisticsCollector::CollectPacketSentEvent(ByteView packet,
|
||||
PacketMetadata metadata) {
|
||||
PacketEvent event;
|
||||
|
||||
// Populate the new PacketEvent by parsing the wire-format `packet`.
|
||||
event.timestamp = now_();
|
||||
event.type = StatisticsEvent::Type::kPacketSentToNetwork;
|
||||
|
||||
BigEndianReader reader(packet.data(), packet.size());
|
||||
bool success = reader.Skip(4);
|
||||
uint32_t truncated_rtp_timestamp = 0;
|
||||
success &= reader.Read<uint32_t>(&truncated_rtp_timestamp);
|
||||
success &= reader.Skip(4);
|
||||
|
||||
event.rtp_timestamp = metadata.rtp_timestamp.Expand(truncated_rtp_timestamp);
|
||||
event.media_type = StatisticsEvent::ToMediaType(metadata.stream_type);
|
||||
|
||||
success &= reader.Skip(2);
|
||||
success &= reader.Read<uint16_t>(&event.packet_id);
|
||||
success &= reader.Read<uint16_t>(&event.max_packet_id);
|
||||
|
||||
// Check that the cast is safe.
|
||||
// TODO(issuetracker.google.com/3576782): move to checked casts when ready.
|
||||
static_assert(static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) <=
|
||||
static_cast<uint64_t>(std::numeric_limits<size_t>::max()),
|
||||
"invalid type cast assumption");
|
||||
OSP_CHECK_LE(packet.size(),
|
||||
static_cast<size_t>(std::numeric_limits<uint32_t>::max()));
|
||||
event.size = static_cast<uint32_t>(packet.size());
|
||||
OSP_CHECK(success);
|
||||
|
||||
recent_packet_events_.emplace_back(event);
|
||||
}
|
||||
|
||||
void StatisticsCollector::CollectPacketEvent(PacketEvent event) {
|
||||
recent_packet_events_.emplace_back(event);
|
||||
}
|
||||
|
||||
void StatisticsCollector::CollectFrameEvent(FrameEvent event) {
|
||||
recent_frame_events_.emplace_back(event);
|
||||
}
|
||||
|
||||
std::vector<PacketEvent> StatisticsCollector::TakeRecentPacketEvents() {
|
||||
std::vector<PacketEvent> out;
|
||||
recent_packet_events_.swap(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<FrameEvent> StatisticsCollector::TakeRecentFrameEvents() {
|
||||
std::vector<FrameEvent> out;
|
||||
recent_frame_events_.swap(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
64
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_collector.h
vendored
Normal file
64
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_collector.h
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2023 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_STATISTICS_COLLECTOR_H_
|
||||
#define CAST_STREAMING_IMPL_STATISTICS_COLLECTOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "platform/base/span.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// This POD struct contains helpful information about a given packet that is
|
||||
// not stored directly on the packet itself.
|
||||
struct PacketMetadata {
|
||||
// The stream type (audio, video, unknown) of this packet.
|
||||
StreamType stream_type;
|
||||
|
||||
// The RTP timestamp associated with this packet.
|
||||
RtpTimeTicks rtp_timestamp;
|
||||
};
|
||||
|
||||
// This class is responsible for gathering packet and frame statistics using its
|
||||
// Collect*() methods, that can then be taken by consumers using the Take*()
|
||||
// methods.
|
||||
class StatisticsCollector {
|
||||
public:
|
||||
explicit StatisticsCollector(ClockNowFunctionPtr now);
|
||||
~StatisticsCollector();
|
||||
|
||||
// Informs the collector that a packet has been sent. The collector will then
|
||||
// generate a packet event that is then added to `recent_packet_events_`.
|
||||
void CollectPacketSentEvent(ByteView packet, PacketMetadata metadata);
|
||||
|
||||
// Informs the collector that a packet event has occurred. This event is then
|
||||
// added to `recent_packet_events_`.
|
||||
void CollectPacketEvent(PacketEvent event);
|
||||
|
||||
// Informs the collector that a frame event has occurred. This event is then
|
||||
// added to `recent_frame_events_`.
|
||||
void CollectFrameEvent(FrameEvent event);
|
||||
|
||||
// Returns the current collection of packet events stored in
|
||||
// `recent_packet_events_`. After calling this method, `recent_packet_events_`
|
||||
// is reset to an empty vector.
|
||||
std::vector<PacketEvent> TakeRecentPacketEvents();
|
||||
|
||||
// Returns the current collection of frame events stored in
|
||||
// `recent_frame_events_`. After calling this method, `recent_frame_events_`
|
||||
// is reset to an empty vector.
|
||||
std::vector<FrameEvent> TakeRecentFrameEvents();
|
||||
|
||||
private:
|
||||
ClockNowFunctionPtr now_;
|
||||
std::vector<PacketEvent> recent_packet_events_;
|
||||
std::vector<FrameEvent> recent_frame_events_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_STATISTICS_COLLECTOR_H_
|
||||
115
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_common.cc
vendored
Normal file
115
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_common.cc
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// static
|
||||
StatisticsEvent::Type StatisticsEvent::FromWireType(WireType wire_type) {
|
||||
switch (wire_type) {
|
||||
case WireType::kAudioAckSent:
|
||||
case WireType::kVideoAckSent:
|
||||
case WireType::kUnifiedAckSent:
|
||||
return Type::kFrameAckSent;
|
||||
|
||||
case WireType::kAudioPlayoutDelay:
|
||||
case WireType::kVideoRenderDelay:
|
||||
case WireType::kUnifiedRenderDelay:
|
||||
return Type::kFramePlayedOut;
|
||||
|
||||
case WireType::kAudioFrameDecoded:
|
||||
case WireType::kVideoFrameDecoded:
|
||||
case WireType::kUnifiedFrameDecoded:
|
||||
return Type::kFrameDecoded;
|
||||
|
||||
case WireType::kAudioPacketReceived:
|
||||
case WireType::kVideoPacketReceived:
|
||||
case WireType::kUnifiedPacketReceived:
|
||||
return Type::kPacketReceived;
|
||||
|
||||
default:
|
||||
OSP_VLOG << "Unexpected RTCP log message received: "
|
||||
<< static_cast<int>(wire_type);
|
||||
return Type::kUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
StatisticsEvent::WireType StatisticsEvent::ToWireType(Type type) {
|
||||
switch (type) {
|
||||
case Type::kUnknown:
|
||||
return WireType::kUnknown;
|
||||
|
||||
case Type::kFrameAckSent:
|
||||
return WireType::kUnifiedAckSent;
|
||||
|
||||
case Type::kFramePlayedOut:
|
||||
return WireType::kUnifiedRenderDelay;
|
||||
|
||||
case Type::kFrameDecoded:
|
||||
return WireType::kUnifiedFrameDecoded;
|
||||
|
||||
case Type::kPacketReceived:
|
||||
return WireType::kUnifiedPacketReceived;
|
||||
|
||||
default:
|
||||
OSP_VLOG << "Unknown RTCP log message event type: "
|
||||
<< static_cast<int>(type);
|
||||
return WireType::kUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
StatisticsEvent::MediaType StatisticsEvent::ToMediaType(StreamType type) {
|
||||
switch (type) {
|
||||
case StreamType::kUnknown:
|
||||
return MediaType::kUnknown;
|
||||
case StreamType::kAudio:
|
||||
return MediaType::kAudio;
|
||||
case StreamType::kVideo:
|
||||
return MediaType::kVideo;
|
||||
}
|
||||
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
|
||||
StatisticsEvent::StatisticsEvent(const StatisticsEvent& other) = default;
|
||||
StatisticsEvent::StatisticsEvent(StatisticsEvent&& other) noexcept = default;
|
||||
StatisticsEvent& StatisticsEvent::operator=(const StatisticsEvent& other) =
|
||||
default;
|
||||
StatisticsEvent& StatisticsEvent::operator=(StatisticsEvent&& other) = default;
|
||||
|
||||
bool StatisticsEvent::operator==(const StatisticsEvent& other) const {
|
||||
return frame_id == other.frame_id && type == other.type &&
|
||||
media_type == other.media_type &&
|
||||
rtp_timestamp == other.rtp_timestamp && size == other.size &&
|
||||
timestamp == other.timestamp &&
|
||||
received_timestamp == other.received_timestamp;
|
||||
}
|
||||
|
||||
FrameEvent::FrameEvent(const FrameEvent& other) = default;
|
||||
FrameEvent::FrameEvent(FrameEvent&& other) noexcept = default;
|
||||
FrameEvent& FrameEvent::operator=(const FrameEvent& other) = default;
|
||||
FrameEvent& FrameEvent::operator=(FrameEvent&& other) = default;
|
||||
|
||||
bool FrameEvent::operator==(const FrameEvent& other) const {
|
||||
return StatisticsEvent::operator==(other) && width == other.width &&
|
||||
height == other.height && delay_delta == other.delay_delta &&
|
||||
key_frame == other.key_frame && target_bitrate == other.target_bitrate;
|
||||
}
|
||||
|
||||
PacketEvent::PacketEvent(const PacketEvent& other) = default;
|
||||
PacketEvent::PacketEvent(PacketEvent&& other) noexcept = default;
|
||||
PacketEvent& PacketEvent::operator=(const PacketEvent& other) = default;
|
||||
PacketEvent& PacketEvent::operator=(PacketEvent&& other) = default;
|
||||
|
||||
bool PacketEvent::operator==(const PacketEvent& other) const {
|
||||
return StatisticsEvent::operator==(other) && packet_id == other.packet_id &&
|
||||
max_packet_id == other.max_packet_id;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
223
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_common.h
vendored
Normal file
223
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_common.h
vendored
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
// Copyright 2023 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_STATISTICS_COMMON_H_
|
||||
#define CAST_STREAMING_IMPL_STATISTICS_COMMON_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "cast/streaming/public/frame_id.h"
|
||||
#include "cast/streaming/rtp_time.h"
|
||||
#include "platform/api/time.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
struct StatisticsEvent {
|
||||
enum class Type : int {
|
||||
kUnknown = 0,
|
||||
|
||||
// Sender side frame events.
|
||||
kFrameCaptureBegin = 1,
|
||||
kFrameCaptureEnd = 2,
|
||||
kFrameEncoded = 3,
|
||||
kFrameAckReceived = 4,
|
||||
|
||||
// Receiver side frame events.
|
||||
kFrameAckSent = 5,
|
||||
kFrameDecoded = 6,
|
||||
kFramePlayedOut = 7,
|
||||
|
||||
// Sender side packet events.
|
||||
kPacketSentToNetwork = 8,
|
||||
kPacketRetransmitted = 9,
|
||||
kPacketRtxRejected = 10,
|
||||
|
||||
// Receiver side packet events.
|
||||
kPacketReceived = 11,
|
||||
kFrameDroppedByEncoder = 15,
|
||||
|
||||
kNumOfEvents = kFrameDroppedByEncoder + 1
|
||||
};
|
||||
|
||||
// Serialized values for the statistics events for use by the RTCP builder
|
||||
// and parser logic. *Do not modify existing values* since they are shared by
|
||||
// both libcast-based devices as well as a variety of legacy implementations.
|
||||
//
|
||||
// NOTE: Events 1 to 8 have been replaced with events 11 to 14 (e.g.
|
||||
// kAudioAckSent and kVideoAckSent merged into a single event kAckSent).
|
||||
// Events 9 and 10 (to log duplicated packets) have been fully removed. Future
|
||||
// events may reuse those values.
|
||||
enum class WireType : uint8_t {
|
||||
kUnknown = 0,
|
||||
|
||||
// Legacy audio event types.
|
||||
kAudioAckSent = 1,
|
||||
kAudioPlayoutDelay = 2,
|
||||
kAudioFrameDecoded = 3,
|
||||
kAudioPacketReceived = 4,
|
||||
|
||||
// Legacy video event types.
|
||||
kVideoAckSent = 5,
|
||||
kVideoRenderDelay = 6,
|
||||
kVideoFrameDecoded = 7,
|
||||
kVideoPacketReceived = 8,
|
||||
|
||||
// New unified event types.
|
||||
kUnifiedAckSent = 11,
|
||||
kUnifiedRenderDelay = 12,
|
||||
kUnifiedFrameDecoded = 13,
|
||||
kUnifiedPacketReceived = 14,
|
||||
|
||||
kNumOfEvents = kUnifiedPacketReceived + 1
|
||||
};
|
||||
|
||||
enum class MediaType : int { kUnknown = 0, kAudio = 1, kVideo = 2 };
|
||||
|
||||
static Type FromWireType(WireType wire_type);
|
||||
static WireType ToWireType(Type type);
|
||||
static MediaType ToMediaType(StreamType type);
|
||||
|
||||
constexpr StatisticsEvent(FrameId frame_id,
|
||||
Type type,
|
||||
MediaType media_type,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
uint32_t size,
|
||||
Clock::time_point timestamp,
|
||||
Clock::time_point received_timestamp)
|
||||
: frame_id(frame_id),
|
||||
type(type),
|
||||
media_type(media_type),
|
||||
rtp_timestamp(rtp_timestamp),
|
||||
size(size),
|
||||
timestamp(timestamp),
|
||||
received_timestamp(received_timestamp) {}
|
||||
|
||||
constexpr StatisticsEvent() = default;
|
||||
StatisticsEvent(const StatisticsEvent& other);
|
||||
StatisticsEvent(StatisticsEvent&& other) noexcept;
|
||||
StatisticsEvent& operator=(const StatisticsEvent& other);
|
||||
StatisticsEvent& operator=(StatisticsEvent&& other);
|
||||
~StatisticsEvent() = default;
|
||||
|
||||
bool operator==(const StatisticsEvent& other) const;
|
||||
|
||||
// The frame this event is associated with.
|
||||
FrameId frame_id;
|
||||
|
||||
// The type of this frame event.
|
||||
Type type = Type::kUnknown;
|
||||
|
||||
// Whether this was audio or video (or unknown).
|
||||
MediaType media_type = MediaType::kUnknown;
|
||||
|
||||
// The RTP timestamp of the frame this event is associated with.
|
||||
RtpTimeTicks rtp_timestamp;
|
||||
|
||||
// Size of this packet, or the frame it is associated with.
|
||||
// Note: we use uint32_t instead of size_t for byte count because this struct
|
||||
// is sent over IPC which could span 32 & 64 bit processes.
|
||||
uint32_t size = 0;
|
||||
|
||||
// Time of event logged.
|
||||
Clock::time_point timestamp;
|
||||
|
||||
// Time that the event was received by the sender. Only set for receiver-side
|
||||
// events.
|
||||
Clock::time_point received_timestamp;
|
||||
};
|
||||
|
||||
struct FrameEvent : public StatisticsEvent {
|
||||
constexpr FrameEvent(FrameId frame_id_in,
|
||||
Type type_in,
|
||||
MediaType media_type_in,
|
||||
RtpTimeTicks rtp_timestamp_in,
|
||||
uint32_t size_in,
|
||||
Clock::time_point timestamp_in,
|
||||
Clock::time_point received_timestamp_in,
|
||||
int width,
|
||||
int height,
|
||||
Clock::duration delay_delta,
|
||||
bool key_frame,
|
||||
int target_bitrate)
|
||||
: StatisticsEvent(frame_id_in,
|
||||
type_in,
|
||||
media_type_in,
|
||||
rtp_timestamp_in,
|
||||
size_in,
|
||||
timestamp_in,
|
||||
received_timestamp_in),
|
||||
width(width),
|
||||
height(height),
|
||||
delay_delta(delay_delta),
|
||||
key_frame(key_frame),
|
||||
target_bitrate(target_bitrate) {}
|
||||
|
||||
constexpr FrameEvent() = default;
|
||||
FrameEvent(const FrameEvent& other);
|
||||
FrameEvent(FrameEvent&& other) noexcept;
|
||||
FrameEvent& operator=(const FrameEvent& other);
|
||||
FrameEvent& operator=(FrameEvent&& other);
|
||||
~FrameEvent() = default;
|
||||
|
||||
bool operator==(const FrameEvent& other) const;
|
||||
|
||||
// Resolution of the frame. Only set for video FRAME_CAPTURE_END events.
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
// Only set for FRAME_PLAYOUT events.
|
||||
// If this value is zero the frame is rendered on time.
|
||||
// If this value is positive it means the frame is rendered late.
|
||||
// If this value is negative it means the frame is rendered early.
|
||||
Clock::duration delay_delta{};
|
||||
|
||||
// Whether the frame is a key frame. Only set for video FRAME_ENCODED event.
|
||||
bool key_frame = false;
|
||||
|
||||
// The requested target bitrate of the encoder at the time the frame is
|
||||
// encoded. Only set for video FRAME_ENCODED event.
|
||||
int target_bitrate = 0;
|
||||
};
|
||||
|
||||
struct PacketEvent : public StatisticsEvent {
|
||||
constexpr PacketEvent(FrameId frame_id_in,
|
||||
Type type_in,
|
||||
MediaType media_type_in,
|
||||
RtpTimeTicks rtp_timestamp_in,
|
||||
uint32_t size_in,
|
||||
Clock::time_point timestamp_in,
|
||||
Clock::time_point received_timestamp_in,
|
||||
uint16_t packet_id,
|
||||
uint16_t max_packet_id)
|
||||
: StatisticsEvent(frame_id_in,
|
||||
type_in,
|
||||
media_type_in,
|
||||
rtp_timestamp_in,
|
||||
size_in,
|
||||
timestamp_in,
|
||||
received_timestamp_in),
|
||||
packet_id(packet_id),
|
||||
max_packet_id(max_packet_id) {}
|
||||
|
||||
constexpr PacketEvent() = default;
|
||||
PacketEvent(const PacketEvent& other);
|
||||
PacketEvent(PacketEvent&& other) noexcept;
|
||||
PacketEvent& operator=(const PacketEvent& other);
|
||||
PacketEvent& operator=(PacketEvent&& other);
|
||||
~PacketEvent() = default;
|
||||
|
||||
bool operator==(const PacketEvent& other) const;
|
||||
|
||||
// The packet this event is associated with.
|
||||
uint16_t packet_id = 0;
|
||||
|
||||
// The highest packet ID seen so far at time of event.
|
||||
uint16_t max_packet_id = 0;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_STATISTICS_COMMON_H_
|
||||
163
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_dispatcher.cc
vendored
Normal file
163
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_dispatcher.cc
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/statistics_dispatcher.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/impl/rtcp_common.h"
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
#include "cast/streaming/impl/statistics_collector.h"
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "cast/streaming/public/encoded_frame.h"
|
||||
#include "cast/streaming/public/environment.h"
|
||||
#include "cast/streaming/public/session_config.h"
|
||||
#include "platform/base/trivial_clock_traits.h"
|
||||
#include "util/chrono_helpers.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/std_util.h"
|
||||
#include "util/trace_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
using clock_operators::operator<<;
|
||||
|
||||
StatisticsDispatcher::StatisticsDispatcher(Environment& environment)
|
||||
: environment_(environment) {}
|
||||
StatisticsDispatcher::~StatisticsDispatcher() = default;
|
||||
|
||||
void StatisticsDispatcher::DispatchEnqueueEvents(StreamType stream_type,
|
||||
const EncodedFrame& frame) {
|
||||
if (!environment_->statistics_collector()) {
|
||||
return;
|
||||
}
|
||||
const auto media_type = StatisticsEvent::ToMediaType(stream_type);
|
||||
|
||||
// Submit a capture begin event.
|
||||
FrameEvent capture_begin_event;
|
||||
capture_begin_event.type = StatisticsEvent::Type::kFrameCaptureBegin;
|
||||
capture_begin_event.media_type = media_type;
|
||||
capture_begin_event.rtp_timestamp = frame.rtp_timestamp;
|
||||
capture_begin_event.timestamp =
|
||||
(frame.capture_begin_time > Clock::time_point::min())
|
||||
? frame.capture_begin_time
|
||||
: environment_->now();
|
||||
environment_->statistics_collector()->CollectFrameEvent(
|
||||
std::move(capture_begin_event));
|
||||
|
||||
// Submit a capture end event.
|
||||
FrameEvent capture_end_event;
|
||||
capture_end_event.type = StatisticsEvent::Type::kFrameCaptureEnd;
|
||||
capture_end_event.media_type = media_type;
|
||||
capture_end_event.rtp_timestamp = frame.rtp_timestamp;
|
||||
capture_end_event.timestamp =
|
||||
(frame.capture_end_time > Clock::time_point::min())
|
||||
? frame.capture_end_time
|
||||
: environment_->now();
|
||||
environment_->statistics_collector()->CollectFrameEvent(
|
||||
std::move(capture_end_event));
|
||||
|
||||
// Submit an encoded event.
|
||||
FrameEvent encode_event;
|
||||
encode_event.timestamp = environment_->now();
|
||||
encode_event.type = StatisticsEvent::Type::kFrameEncoded;
|
||||
encode_event.media_type = media_type;
|
||||
encode_event.rtp_timestamp = frame.rtp_timestamp;
|
||||
encode_event.frame_id = frame.frame_id;
|
||||
encode_event.size = static_cast<uint32_t>(frame.data.size());
|
||||
encode_event.key_frame =
|
||||
frame.dependency == openscreen::cast::EncodedFrame::Dependency::kKeyFrame;
|
||||
|
||||
environment_->statistics_collector()->CollectFrameEvent(
|
||||
std::move(encode_event));
|
||||
}
|
||||
|
||||
void StatisticsDispatcher::DispatchAckEvent(StreamType stream_type,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
FrameId frame_id) {
|
||||
if (!environment_->statistics_collector()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FrameEvent ack_event;
|
||||
ack_event.timestamp = environment_->now();
|
||||
ack_event.type = StatisticsEvent::Type::kFrameAckReceived;
|
||||
ack_event.media_type = StatisticsEvent::ToMediaType(stream_type);
|
||||
ack_event.rtp_timestamp = rtp_timestamp;
|
||||
ack_event.frame_id = frame_id;
|
||||
|
||||
environment_->statistics_collector()->CollectFrameEvent(std::move(ack_event));
|
||||
}
|
||||
|
||||
void StatisticsDispatcher::DispatchFrameDropEvent(StreamType stream_type,
|
||||
FrameId frame_id,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
Clock::time_point drop_time) {
|
||||
if (!environment_->statistics_collector()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FrameEvent drop_event;
|
||||
drop_event.timestamp = drop_time;
|
||||
drop_event.type = StatisticsEvent::Type::kFrameDroppedByEncoder;
|
||||
drop_event.media_type = StatisticsEvent::ToMediaType(stream_type);
|
||||
drop_event.rtp_timestamp = rtp_timestamp;
|
||||
drop_event.frame_id = frame_id;
|
||||
|
||||
environment_->statistics_collector()->CollectFrameEvent(
|
||||
std::move(drop_event));
|
||||
}
|
||||
|
||||
void StatisticsDispatcher::DispatchFrameLogMessages(
|
||||
StreamType stream_type,
|
||||
const std::vector<RtcpReceiverFrameLogMessage>& messages) {
|
||||
if (!environment_->statistics_collector()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Clock::time_point now = environment_->now();
|
||||
const auto media_type = StatisticsEvent::ToMediaType(stream_type);
|
||||
for (const RtcpReceiverFrameLogMessage& log_message : messages) {
|
||||
for (const RtcpReceiverEventLogMessage& event_message :
|
||||
log_message.messages) {
|
||||
switch (event_message.type) {
|
||||
case StatisticsEvent::Type::kPacketReceived: {
|
||||
PacketEvent event;
|
||||
event.timestamp = event_message.timestamp;
|
||||
event.received_timestamp = now;
|
||||
event.type = event_message.type;
|
||||
event.media_type = media_type;
|
||||
event.rtp_timestamp = log_message.rtp_timestamp;
|
||||
event.packet_id = event_message.packet_id;
|
||||
environment_->statistics_collector()->CollectPacketEvent(
|
||||
std::move(event));
|
||||
} break;
|
||||
|
||||
case StatisticsEvent::Type::kFrameAckSent:
|
||||
case StatisticsEvent::Type::kFrameDecoded:
|
||||
case StatisticsEvent::Type::kFramePlayedOut: {
|
||||
FrameEvent event;
|
||||
event.timestamp = event_message.timestamp;
|
||||
event.received_timestamp = now;
|
||||
event.type = event_message.type;
|
||||
event.media_type = media_type;
|
||||
event.rtp_timestamp = log_message.rtp_timestamp;
|
||||
if (event.type == StatisticsEvent::Type::kFramePlayedOut) {
|
||||
event.delay_delta = event_message.delay;
|
||||
}
|
||||
environment_->statistics_collector()->CollectFrameEvent(
|
||||
std::move(event));
|
||||
} break;
|
||||
|
||||
default:
|
||||
OSP_VLOG << "Received log message via RTCP that we did not expect, "
|
||||
"StatisticsEvent::Type="
|
||||
<< static_cast<int>(event_message.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
58
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_dispatcher.h
vendored
Normal file
58
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_dispatcher.h
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2025 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_STATISTICS_DISPATCHER_H_
|
||||
#define CAST_STREAMING_IMPL_STATISTICS_DISPATCHER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/raw_ref.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
class StatisticsCollector;
|
||||
class Environment;
|
||||
struct EncodedFrame;
|
||||
struct RtcpReceiverFrameLogMessage;
|
||||
|
||||
// This class is responsible for dispatching statistics events.
|
||||
class StatisticsDispatcher {
|
||||
public:
|
||||
explicit StatisticsDispatcher(Environment& environment);
|
||||
|
||||
StatisticsDispatcher(const StatisticsDispatcher&) = delete;
|
||||
StatisticsDispatcher& operator=(const StatisticsDispatcher&) = delete;
|
||||
StatisticsDispatcher(StatisticsDispatcher&&) noexcept = delete;
|
||||
StatisticsDispatcher& operator=(StatisticsDispatcher&&) = delete;
|
||||
~StatisticsDispatcher();
|
||||
|
||||
// Dispatches enqueue events for a given frame.
|
||||
void DispatchEnqueueEvents(StreamType stream_type, const EncodedFrame& frame);
|
||||
|
||||
// Dispatches frame log messages.
|
||||
void DispatchFrameLogMessages(
|
||||
StreamType stream_type,
|
||||
const std::vector<RtcpReceiverFrameLogMessage>& messages);
|
||||
|
||||
// Dispatches an ack event.
|
||||
void DispatchAckEvent(StreamType stream_type,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
FrameId frame_id);
|
||||
|
||||
// Dispatches a frame dropped by encoder event.
|
||||
void DispatchFrameDropEvent(StreamType stream_type,
|
||||
FrameId frame_id,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
Clock::time_point drop_time);
|
||||
|
||||
private:
|
||||
const raw_ref<Environment> environment_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_IMPL_STATISTICS_DISPATCHER_H_
|
||||
47
breadcast-caststream-sys/vendor/openscreen/cast/streaming/message_fields.cc
vendored
Normal file
47
breadcast-caststream-sys/vendor/openscreen/cast/streaming/message_fields.cc
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/message_fields.h"
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
#include "util/enum_name_table.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
namespace {
|
||||
|
||||
constexpr EnumNameTable<AudioCodec, 3> kAudioCodecNames{
|
||||
{{"aac", AudioCodec::kAac},
|
||||
{"opus", AudioCodec::kOpus},
|
||||
{"REMOTE_AUDIO", AudioCodec::kNotSpecified}}};
|
||||
|
||||
constexpr EnumNameTable<VideoCodec, 6> kVideoCodecNames{
|
||||
{{"h264", VideoCodec::kH264},
|
||||
{"vp8", VideoCodec::kVp8},
|
||||
{"hevc", VideoCodec::kHevc},
|
||||
{"REMOTE_VIDEO", VideoCodec::kNotSpecified},
|
||||
{"vp9", VideoCodec::kVp9},
|
||||
{"av1", VideoCodec::kAv1}}};
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* CodecToString(AudioCodec codec) {
|
||||
return GetEnumName(kAudioCodecNames, codec).value();
|
||||
}
|
||||
|
||||
ErrorOr<AudioCodec> StringToAudioCodec(std::string_view name) {
|
||||
return GetEnum(kAudioCodecNames, name);
|
||||
}
|
||||
|
||||
const char* CodecToString(VideoCodec codec) {
|
||||
return GetEnumName(kVideoCodecNames, codec).value();
|
||||
}
|
||||
|
||||
ErrorOr<VideoCodec> StringToVideoCodec(std::string_view name) {
|
||||
return GetEnum(kVideoCodecNames, name);
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
59
breadcast-caststream-sys/vendor/openscreen/cast/streaming/message_fields.h
vendored
Normal file
59
breadcast-caststream-sys/vendor/openscreen/cast/streaming/message_fields.h
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2020 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_MESSAGE_FIELDS_H_
|
||||
#define CAST_STREAMING_MESSAGE_FIELDS_H_
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "platform/base/error.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
/// NOTE: Constants here are all taken from the Cast V2: Mirroring Control
|
||||
/// Protocol specification.
|
||||
|
||||
// Namespace for OFFER/ANSWER messages.
|
||||
inline constexpr char kCastWebrtcNamespace[] =
|
||||
"urn:x-cast:com.google.cast.webrtc";
|
||||
inline constexpr char kCastRemotingNamespace[] =
|
||||
"urn:x-cast:com.google.cast.remoting";
|
||||
|
||||
// JSON message field values specific to the Sender Session.
|
||||
inline constexpr char kMessageType[] = "type";
|
||||
|
||||
// List of OFFER message fields.
|
||||
inline constexpr char kMessageTypeOffer[] = "OFFER";
|
||||
inline constexpr char kOfferMessageBody[] = "offer";
|
||||
inline constexpr char kSequenceNumber[] = "seqNum";
|
||||
inline constexpr char kCodecName[] = "codecName";
|
||||
|
||||
/// ANSWER message fields.
|
||||
inline constexpr char kMessageTypeAnswer[] = "ANSWER";
|
||||
inline constexpr char kAnswerMessageBody[] = "answer";
|
||||
inline constexpr char kResult[] = "result";
|
||||
inline constexpr char kResultOk[] = "ok";
|
||||
inline constexpr char kResultError[] = "error";
|
||||
inline constexpr char kErrorMessageBody[] = "error";
|
||||
inline constexpr char kErrorCode[] = "code";
|
||||
inline constexpr char kErrorDescription[] = "description";
|
||||
|
||||
// Other message fields.
|
||||
inline constexpr char kRpcMessageBody[] = "rpc";
|
||||
inline constexpr char kInputMessageBody[] = "input";
|
||||
inline constexpr char kCapabilitiesMessageBody[] = "capabilities";
|
||||
inline constexpr char kStatusMessageBody[] = "status";
|
||||
|
||||
// Conversion methods for codec message fields.
|
||||
const char* CodecToString(AudioCodec codec);
|
||||
ErrorOr<AudioCodec> StringToAudioCodec(std::string_view name);
|
||||
|
||||
const char* CodecToString(VideoCodec codec);
|
||||
ErrorOr<VideoCodec> StringToVideoCodec(std::string_view name);
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_MESSAGE_FIELDS_H_
|
||||
498
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/answer_messages.cc
vendored
Normal file
498
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/answer_messages.cc
vendored
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/answer_messages.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "platform/base/error.h"
|
||||
#include "util/enum_name_table.h"
|
||||
#include "util/json/json_helpers.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/string_parse.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/stringprintf.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
/// Constraint properties.
|
||||
// Audio constraints. See properties below.
|
||||
constexpr char kAudio[] = "audio";
|
||||
// Video constraints. See properties below.
|
||||
constexpr char kVideo[] = "video";
|
||||
|
||||
// An optional field representing the minimum bits per second. If not specified
|
||||
// by the receiver, the sender will use kDefaultAudioMinBitRate and
|
||||
// kDefaultVideoMinBitRate, which represent the true operational minimum.
|
||||
constexpr char kMinBitRate[] = "minBitRate";
|
||||
|
||||
// Maximum encoded bits per second. This is the lower of (1) the max capability
|
||||
// of the decoder, or (2) the max data transfer rate.
|
||||
constexpr char kMaxBitRate[] = "maxBitRate";
|
||||
// Maximum supported end-to-end latency, in milliseconds. Proportional to the
|
||||
// size of the data buffers in the receiver.
|
||||
constexpr char kMaxDelay[] = "maxDelay";
|
||||
|
||||
/// Video constraint properties.
|
||||
// Maximum pixel rate (width * height * framerate). Is often less than
|
||||
// multiplying the fields in maxDimensions. This field is used to set the
|
||||
// maximum processing rate.
|
||||
constexpr char kMaxPixelsPerSecond[] = "maxPixelsPerSecond";
|
||||
// Minimum dimensions. If omitted, the sender will assume a reasonable minimum
|
||||
// with the same aspect ratio as maxDimensions, as close to 320*180 as possible.
|
||||
// Should reflect the true operational minimum.
|
||||
constexpr char kMinResolution[] = "minResolution";
|
||||
// Maximum dimensions, not necessarily ideal dimensions.
|
||||
constexpr char kMaxDimensions[] = "maxDimensions";
|
||||
|
||||
/// Audio constraint properties.
|
||||
// Maximum supported sampling frequency (not necessarily ideal).
|
||||
constexpr char kMaxSampleRate[] = "maxSampleRate";
|
||||
// Maximum number of audio channels (1 is mono, 2 is stereo, etc.).
|
||||
constexpr char kMaxChannels[] = "maxChannels";
|
||||
|
||||
/// Display description properties
|
||||
// If this optional field is included in the ANSWER message, the receiver is
|
||||
// attached to a fixed display that has the given dimensions and frame rate
|
||||
// configuration. These may exceed, be the same, or be less than the values in
|
||||
// constraints. If undefined, we assume the display is not fixed (e.g. a Google
|
||||
// Hangouts UI panel).
|
||||
constexpr char kDimensions[] = "dimensions";
|
||||
// An optional field. When missing and dimensions are specified, the sender
|
||||
// will assume square pixels and the dimensions imply the aspect ratio of the
|
||||
// fixed display. WHen present and dimensions are also specified, implies the
|
||||
// pixels are not square.
|
||||
constexpr char kAspectRatio[] = "aspectRatio";
|
||||
// The delimeter used for the aspect ratio format ("A:B").
|
||||
constexpr char kAspectRatioDelimiter = ':';
|
||||
// Sets the aspect ratio constraints. Value must be either "sender" or
|
||||
// "receiver", see kScalingSender and kScalingReceiver below.
|
||||
constexpr char kScaling[] = "scaling";
|
||||
// scaling = "sender" means that the sender must provide video frames of a fixed
|
||||
// aspect ratio. In this case, the dimensions object must be passed or an error
|
||||
// case will occur.
|
||||
constexpr char kScalingSender[] = "sender";
|
||||
// scaling = "receiver" means that the sender may send arbitrarily sized frames,
|
||||
// and the receiver will handle scaling and letterboxing as necessary.
|
||||
constexpr char kScalingReceiver[] = "receiver";
|
||||
|
||||
/// Answer properties.
|
||||
// A number specifying the UDP port used for all streams in this session.
|
||||
// Must have a value between kUdpPortMin and kUdpPortMax.
|
||||
constexpr char kUdpPort[] = "udpPort";
|
||||
constexpr int kUdpPortMin = 1;
|
||||
constexpr int kUdpPortMax = 65535;
|
||||
// Numbers specifying the indexes chosen from the offer message.
|
||||
constexpr char kSendIndexes[] = "sendIndexes";
|
||||
// uint32_t values specifying the RTP SSRC values used to send the RTCP feedback
|
||||
// of the stream indicated in kSendIndexes.
|
||||
constexpr char kSsrcs[] = "ssrcs";
|
||||
// Provides detailed maximum and minimum capabilities of the receiver for
|
||||
// processing the selected streams. The sender may alter video resolution and
|
||||
// frame rate throughout the session, and the constraints here determine how
|
||||
// much data volume is allowed.
|
||||
constexpr char kConstraints[] = "constraints";
|
||||
// Provides details about the display on the receiver.
|
||||
constexpr char kDisplay[] = "display";
|
||||
// std::optional array of numbers specifying the indexes of streams that will
|
||||
// send event logs through RTCP.
|
||||
constexpr char kReceiverRtcpEventLog[] = "receiverRtcpEventLog";
|
||||
// Optional array of numbers specifying the indexes of streams that will use
|
||||
// DSCP values specified in the OFFER message for RTCP packets.
|
||||
constexpr char kReceiverRtcpDscp[] = "receiverRtcpDscp";
|
||||
// If this optional field is present the receiver supports the specific
|
||||
// RTP extensions (such as adaptive playout delay).
|
||||
constexpr char kRtpExtensions[] = "rtpExtensions";
|
||||
|
||||
EnumNameTable<AspectRatioConstraint, 2> kAspectRatioConstraintNames{
|
||||
{{kScalingReceiver, AspectRatioConstraint::kVariable},
|
||||
{kScalingSender, AspectRatioConstraint::kFixed}}};
|
||||
|
||||
Json::Value AspectRatioConstraintToJson(AspectRatioConstraint aspect_ratio) {
|
||||
return Json::Value(GetEnumName(kAspectRatioConstraintNames, aspect_ratio)
|
||||
.value(kScalingSender));
|
||||
}
|
||||
|
||||
std::optional<AspectRatioConstraint> TryParseAspectRatioConstraint(
|
||||
const Json::Value& value) {
|
||||
std::string aspect_ratio;
|
||||
if (!json::TryParseString(value, &aspect_ratio)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ErrorOr<AspectRatioConstraint> constraint =
|
||||
GetEnum(kAspectRatioConstraintNames, aspect_ratio);
|
||||
if (constraint.is_error()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return constraint.value();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ErrorOr<std::optional<T>> ParseOptional(const Json::Value& value) {
|
||||
if (!value) {
|
||||
return std::optional<T>{};
|
||||
}
|
||||
auto out = T::TryParse(value);
|
||||
if (out.is_error()) {
|
||||
return out.error();
|
||||
}
|
||||
return std::optional<T>{std::move(out.value())};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
ErrorOr<AspectRatio> AspectRatio::TryParse(const Json::Value& value) {
|
||||
std::string parsed_value;
|
||||
if (!json::TryParseString(value, &parsed_value)) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid aspect ratio string");
|
||||
}
|
||||
|
||||
std::vector<std::string_view> fields =
|
||||
string_util::Split(parsed_value, kAspectRatioDelimiter);
|
||||
if (fields.size() != 2) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid aspect ratio format");
|
||||
}
|
||||
|
||||
AspectRatio out;
|
||||
if (!string_parse::ParseAsciiNumber(fields[0], out.width) ||
|
||||
!string_parse::ParseAsciiNumber(fields[1], out.height)) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid aspect ratio values");
|
||||
}
|
||||
if (!out.IsValid()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid aspect ratio");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool AspectRatio::IsValid() const {
|
||||
return width > 0 && height > 0;
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<AudioConstraints> AudioConstraints::TryParse(const Json::Value& root) {
|
||||
if (!root.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Audio constraints is not a JSON object");
|
||||
}
|
||||
|
||||
AudioConstraints out;
|
||||
if (!json::TryParseInt(root[kMaxSampleRate], &out.max_sample_rate) ||
|
||||
!json::TryParseInt(root[kMaxChannels], &out.max_channels) ||
|
||||
!json::TryParseInt(root[kMaxBitRate], &out.max_bit_rate)) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid audio constraints");
|
||||
}
|
||||
|
||||
std::chrono::milliseconds max_delay;
|
||||
if (json::TryParseMilliseconds(root[kMaxDelay], &max_delay)) {
|
||||
out.max_delay = max_delay;
|
||||
}
|
||||
|
||||
if (!json::TryParseInt(root[kMinBitRate], &out.min_bit_rate)) {
|
||||
out.min_bit_rate = kDefaultAudioMinBitRate;
|
||||
}
|
||||
if (!out.IsValid()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid audio constraints");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Json::Value AudioConstraints::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
Json::Value root;
|
||||
root[kMaxSampleRate] = max_sample_rate;
|
||||
root[kMaxChannels] = max_channels;
|
||||
root[kMinBitRate] = min_bit_rate;
|
||||
root[kMaxBitRate] = max_bit_rate;
|
||||
if (max_delay.has_value()) {
|
||||
root[kMaxDelay] = Json::Value::Int64(max_delay->count());
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
bool AudioConstraints::IsValid() const {
|
||||
return max_sample_rate > 0 && max_channels > 0 && min_bit_rate > 0 &&
|
||||
max_bit_rate >= min_bit_rate;
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<VideoConstraints> VideoConstraints::TryParse(const Json::Value& root) {
|
||||
if (!root.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Video constraints is not a JSON object");
|
||||
}
|
||||
|
||||
VideoConstraints out;
|
||||
|
||||
auto max_dimensions = Dimensions::TryParse(root[kMaxDimensions]);
|
||||
if (max_dimensions.is_error()) {
|
||||
return max_dimensions.error();
|
||||
}
|
||||
out.max_dimensions = std::move(max_dimensions.value());
|
||||
|
||||
if (!json::TryParseInt(root[kMaxBitRate], &out.max_bit_rate)) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Invalid video constraints: missing maxBitRate");
|
||||
}
|
||||
|
||||
auto min_resolution = ParseOptional<Dimensions>(root[kMinResolution]);
|
||||
if (min_resolution.is_error()) {
|
||||
return min_resolution.error();
|
||||
}
|
||||
out.min_resolution = std::move(min_resolution.value());
|
||||
|
||||
std::chrono::milliseconds max_delay;
|
||||
if (json::TryParseMilliseconds(root[kMaxDelay], &max_delay)) {
|
||||
out.max_delay = max_delay;
|
||||
}
|
||||
|
||||
double max_pixels_per_second;
|
||||
if (json::TryParseDouble(root[kMaxPixelsPerSecond], &max_pixels_per_second)) {
|
||||
out.max_pixels_per_second = max_pixels_per_second;
|
||||
}
|
||||
|
||||
if (!json::TryParseInt(root[kMinBitRate], &out.min_bit_rate)) {
|
||||
out.min_bit_rate = kDefaultVideoMinBitRate;
|
||||
}
|
||||
if (!out.IsValid()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid video constraints");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool VideoConstraints::IsValid() const {
|
||||
return max_pixels_per_second > 0 && min_bit_rate > 0 &&
|
||||
max_bit_rate > min_bit_rate &&
|
||||
(!max_delay.has_value() || max_delay->count() > 0) &&
|
||||
max_dimensions.IsValid() &&
|
||||
(!min_resolution.has_value() || min_resolution->IsValid()) &&
|
||||
max_dimensions.frame_rate.numerator() > 0;
|
||||
}
|
||||
|
||||
Json::Value VideoConstraints::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
Json::Value root;
|
||||
root[kMaxDimensions] = max_dimensions.ToJson();
|
||||
root[kMinBitRate] = min_bit_rate;
|
||||
root[kMaxBitRate] = max_bit_rate;
|
||||
if (max_pixels_per_second.has_value()) {
|
||||
root[kMaxPixelsPerSecond] = max_pixels_per_second.value();
|
||||
}
|
||||
|
||||
if (min_resolution.has_value()) {
|
||||
root[kMinResolution] = min_resolution->ToJson();
|
||||
}
|
||||
|
||||
if (max_delay.has_value()) {
|
||||
root[kMaxDelay] = Json::Value::Int64(max_delay->count());
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<Constraints> Constraints::TryParse(const Json::Value& root) {
|
||||
if (!root.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Constraints is not a JSON object");
|
||||
}
|
||||
|
||||
Constraints out;
|
||||
|
||||
auto audio = AudioConstraints::TryParse(root[kAudio]);
|
||||
if (audio.is_error()) {
|
||||
return audio.error();
|
||||
}
|
||||
out.audio = std::move(audio.value());
|
||||
|
||||
auto video = VideoConstraints::TryParse(root[kVideo]);
|
||||
if (video.is_error()) {
|
||||
return video.error();
|
||||
}
|
||||
out.video = std::move(video.value());
|
||||
|
||||
if (!out.IsValid()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid constraints");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Constraints::IsValid() const {
|
||||
return audio.IsValid() && video.IsValid();
|
||||
}
|
||||
|
||||
Json::Value Constraints::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
Json::Value root;
|
||||
root[kAudio] = audio.ToJson();
|
||||
root[kVideo] = video.ToJson();
|
||||
return root;
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<DisplayDescription> DisplayDescription::TryParse(
|
||||
const Json::Value& root) {
|
||||
if (!root.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Display description is not a JSON object");
|
||||
}
|
||||
|
||||
DisplayDescription out;
|
||||
|
||||
auto dimensions = ParseOptional<Dimensions>(root[kDimensions]);
|
||||
if (dimensions.is_error()) {
|
||||
return dimensions.error();
|
||||
}
|
||||
out.dimensions = std::move(dimensions.value());
|
||||
|
||||
auto aspect_ratio = ParseOptional<AspectRatio>(root[kAspectRatio]);
|
||||
if (aspect_ratio.is_error()) {
|
||||
return aspect_ratio.error();
|
||||
}
|
||||
out.aspect_ratio = std::move(aspect_ratio.value());
|
||||
|
||||
auto constraint = TryParseAspectRatioConstraint(root[kScaling]);
|
||||
if (constraint.has_value()) {
|
||||
out.aspect_ratio_constraint = constraint.value();
|
||||
} else {
|
||||
out.aspect_ratio_constraint = std::nullopt;
|
||||
}
|
||||
|
||||
if (!out.IsValid()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid display description");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool DisplayDescription::IsValid() const {
|
||||
// At least one of the properties must be set, and if a property is set
|
||||
// it must be valid.
|
||||
if (aspect_ratio.has_value() && !aspect_ratio->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dimensions.has_value() && !dimensions->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sender behavior is undefined if the aspect ratio is fixed but no
|
||||
// dimensions or aspect ratio are provided.
|
||||
if (aspect_ratio_constraint.has_value() &&
|
||||
(aspect_ratio_constraint.value() == AspectRatioConstraint::kFixed) &&
|
||||
!dimensions.has_value() && !aspect_ratio.has_value()) {
|
||||
return false;
|
||||
}
|
||||
return aspect_ratio.has_value() || dimensions.has_value() ||
|
||||
aspect_ratio_constraint.has_value();
|
||||
}
|
||||
|
||||
Json::Value DisplayDescription::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
Json::Value root;
|
||||
if (aspect_ratio.has_value()) {
|
||||
root[kAspectRatio] =
|
||||
StringFormat("{}{}{}", aspect_ratio->width, kAspectRatioDelimiter,
|
||||
aspect_ratio->height);
|
||||
}
|
||||
if (dimensions.has_value()) {
|
||||
root[kDimensions] = dimensions->ToJson();
|
||||
}
|
||||
if (aspect_ratio_constraint.has_value()) {
|
||||
root[kScaling] =
|
||||
AspectRatioConstraintToJson(aspect_ratio_constraint.value());
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
ErrorOr<Answer> Answer::TryParse(const Json::Value& root) {
|
||||
if (!root.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError, "Answer is not a JSON object");
|
||||
}
|
||||
|
||||
Answer out;
|
||||
if (!json::TryParseInt(root[kUdpPort], &out.udp_port) ||
|
||||
!json::TryParseIntArray(root[kSendIndexes], &out.send_indexes) ||
|
||||
!json::TryParseUintArray(root[kSsrcs], &out.ssrcs)) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Invalid answer: missing or invalid mandatory fields");
|
||||
}
|
||||
|
||||
auto constraints = ParseOptional<Constraints>(root[kConstraints]);
|
||||
if (constraints.is_error()) {
|
||||
return constraints.error();
|
||||
}
|
||||
out.constraints = std::move(constraints.value());
|
||||
|
||||
auto display = ParseOptional<DisplayDescription>(root[kDisplay]);
|
||||
if (display.is_error()) {
|
||||
return display.error();
|
||||
}
|
||||
out.display = std::move(display.value());
|
||||
|
||||
// These functions set to empty array if not present, so we can ignore
|
||||
// the return value for optional values.
|
||||
json::TryParseIntArray(root[kReceiverRtcpEventLog],
|
||||
&out.receiver_rtcp_event_log);
|
||||
json::TryParseIntArray(root[kReceiverRtcpDscp], &out.receiver_rtcp_dscp);
|
||||
json::TryParseNestedStringArray(root[kRtpExtensions], &out.rtp_extensions);
|
||||
|
||||
if (!out.IsValid()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid answer");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Answer::IsValid() const {
|
||||
if (ssrcs.empty() || send_indexes.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't know what the indexes used in the offer were here, so we just
|
||||
// sanity check.
|
||||
for (const int index : send_indexes) {
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (constraints.has_value() && !constraints->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
if (display.has_value() && !display->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
return kUdpPortMin <= udp_port && udp_port <= kUdpPortMax;
|
||||
}
|
||||
|
||||
Json::Value Answer::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
Json::Value root;
|
||||
if (constraints.has_value()) {
|
||||
root[kConstraints] = constraints->ToJson();
|
||||
}
|
||||
if (display.has_value()) {
|
||||
root[kDisplay] = display->ToJson();
|
||||
}
|
||||
root[kUdpPort] = udp_port;
|
||||
root[kSendIndexes] = json::PrimitiveVectorToJson(send_indexes);
|
||||
root[kSsrcs] = json::PrimitiveVectorToJson(ssrcs);
|
||||
// Some sender do not handle empty array properly, so we omit these fields
|
||||
// if they are empty.
|
||||
if (!receiver_rtcp_event_log.empty()) {
|
||||
root[kReceiverRtcpEventLog] =
|
||||
json::PrimitiveVectorToJson(receiver_rtcp_event_log);
|
||||
}
|
||||
if (!receiver_rtcp_dscp.empty()) {
|
||||
root[kReceiverRtcpDscp] = json::PrimitiveVectorToJson(receiver_rtcp_dscp);
|
||||
}
|
||||
if (!rtp_extensions.empty()) {
|
||||
root[kRtpExtensions] = json::NestedStringArrayToJson(rtp_extensions);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
122
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/answer_messages.h
vendored
Normal file
122
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/answer_messages.h
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2019 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_PUBLIC_ANSWER_MESSAGES_H_
|
||||
#define CAST_STREAMING_PUBLIC_ANSWER_MESSAGES_H_
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/resolution.h"
|
||||
#include "cast/streaming/ssrc.h"
|
||||
#include "json/value.h"
|
||||
#include "platform/base/error.h"
|
||||
#include "util/simple_fraction.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// For each of the below classes, though a number of methods are shared, the use
|
||||
// of a shared base class has intentionally been avoided. This is to improve
|
||||
// readability of the structs provided in this file by cutting down on the
|
||||
// amount of obscuring boilerplate code. For each of the following struct
|
||||
// definitions, the following method definitions are shared:
|
||||
// (1) TryParse. Shall return a boolean indicating whether the out
|
||||
// parameter is in a valid state after checking bounds and restrictions.
|
||||
// (2) ToJson. Should return a proper JSON object. Assumes that IsValid()
|
||||
// has been called already, OSP_CHECKs if not IsValid().
|
||||
// (3) IsValid. Used by both TryParse and ToJson to ensure that the
|
||||
// object is in a good state.
|
||||
struct AudioConstraints {
|
||||
static ErrorOr<AudioConstraints> TryParse(const Json::Value& value);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
int max_sample_rate = 0;
|
||||
int max_channels = 0;
|
||||
int min_bit_rate = 0; // optional
|
||||
int max_bit_rate = 0;
|
||||
std::optional<std::chrono::milliseconds> max_delay = {};
|
||||
};
|
||||
|
||||
struct VideoConstraints {
|
||||
static ErrorOr<VideoConstraints> TryParse(const Json::Value& value);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
std::optional<double> max_pixels_per_second = {};
|
||||
std::optional<Dimensions> min_resolution = {};
|
||||
Dimensions max_dimensions = {};
|
||||
int min_bit_rate = 0; // optional
|
||||
int max_bit_rate = 0;
|
||||
std::optional<std::chrono::milliseconds> max_delay = {};
|
||||
};
|
||||
|
||||
struct Constraints {
|
||||
static ErrorOr<Constraints> TryParse(const Json::Value& value);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
AudioConstraints audio;
|
||||
VideoConstraints video;
|
||||
};
|
||||
|
||||
// Decides whether the Sender scales and letterboxes content to 16:9, or if
|
||||
// it may send video frames of any arbitrary size and the Receiver must
|
||||
// handle the presentation details.
|
||||
enum class AspectRatioConstraint : uint8_t { kVariable = 0, kFixed };
|
||||
|
||||
struct AspectRatio {
|
||||
static ErrorOr<AspectRatio> TryParse(const Json::Value& value);
|
||||
bool IsValid() const;
|
||||
|
||||
bool operator==(const AspectRatio& other) const {
|
||||
return width == other.width && height == other.height;
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
struct DisplayDescription {
|
||||
static ErrorOr<DisplayDescription> TryParse(const Json::Value& value);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
// May exceed, be the same, or less than those mentioned in the
|
||||
// video constraints.
|
||||
std::optional<Dimensions> dimensions;
|
||||
std::optional<AspectRatio> aspect_ratio = {};
|
||||
std::optional<AspectRatioConstraint> aspect_ratio_constraint = {};
|
||||
};
|
||||
|
||||
struct Answer {
|
||||
static ErrorOr<Answer> TryParse(const Json::Value& value);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
int udp_port = 0;
|
||||
std::vector<int> send_indexes;
|
||||
std::vector<Ssrc> ssrcs;
|
||||
|
||||
// Constraints and display descriptions are optional fields, and maybe null in
|
||||
// the valid case.
|
||||
std::optional<Constraints> constraints;
|
||||
std::optional<DisplayDescription> display;
|
||||
std::vector<int> receiver_rtcp_event_log;
|
||||
std::vector<int> receiver_rtcp_dscp;
|
||||
|
||||
// RTP extensions should be empty, but not null.
|
||||
std::vector<std::vector<std::string>> rtp_extensions = {};
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_ANSWER_MESSAGES_H_
|
||||
157
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/capture_recommendations.cc
vendored
Normal file
157
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/capture_recommendations.cc
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/capture_recommendations.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/public/answer_messages.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
namespace capture_recommendations {
|
||||
namespace {
|
||||
|
||||
void ApplyDisplay(const DisplayDescription& description,
|
||||
Recommendations* recommendations) {
|
||||
recommendations->video.supports_scaling =
|
||||
(description.aspect_ratio_constraint &&
|
||||
(description.aspect_ratio_constraint.value() ==
|
||||
AspectRatioConstraint::kVariable));
|
||||
|
||||
// We should never exceed the display's resolution, since it will always
|
||||
// force scaling.
|
||||
if (description.dimensions) {
|
||||
recommendations->video.maximum = description.dimensions.value();
|
||||
recommendations->video.bit_rate_limits.maximum =
|
||||
recommendations->video.maximum.effective_bit_rate();
|
||||
|
||||
if (recommendations->video.maximum.width <
|
||||
recommendations->video.minimum.width) {
|
||||
recommendations->video.minimum =
|
||||
recommendations->video.maximum.ToResolution();
|
||||
}
|
||||
}
|
||||
|
||||
// If the receiver gives us an aspect ratio that doesn't match the display
|
||||
// resolution they give us, the behavior is undefined from the spec.
|
||||
// Here we prioritize the aspect ratio, and the receiver can scale the frame
|
||||
// as they wish.
|
||||
double aspect_ratio = 0.0;
|
||||
if (description.aspect_ratio) {
|
||||
aspect_ratio = static_cast<double>(description.aspect_ratio->width) /
|
||||
description.aspect_ratio->height;
|
||||
recommendations->video.maximum.width =
|
||||
recommendations->video.maximum.height * aspect_ratio;
|
||||
} else if (description.dimensions) {
|
||||
aspect_ratio = static_cast<double>(description.dimensions->width) /
|
||||
description.dimensions->height;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
recommendations->video.minimum.width =
|
||||
recommendations->video.minimum.height * aspect_ratio;
|
||||
}
|
||||
|
||||
void ApplyConstraints(const Constraints& constraints,
|
||||
Recommendations* recommendations) {
|
||||
// Audio has no fields in the display description, so we can safely
|
||||
// ignore the current recommendations when setting values here.
|
||||
if (constraints.audio.max_delay.has_value()) {
|
||||
recommendations->audio.max_delay = constraints.audio.max_delay.value();
|
||||
}
|
||||
recommendations->audio.max_channels = constraints.audio.max_channels;
|
||||
recommendations->audio.max_sample_rate = constraints.audio.max_sample_rate;
|
||||
|
||||
recommendations->audio.bit_rate_limits = BitRateLimits{
|
||||
std::max(constraints.audio.min_bit_rate, kDefaultAudioMinBitRate),
|
||||
std::max(constraints.audio.max_bit_rate, kDefaultAudioMinBitRate)};
|
||||
|
||||
// With video, we take the intersection of values of the constraints and
|
||||
// the display description.
|
||||
if (constraints.video.max_delay.has_value()) {
|
||||
recommendations->video.max_delay = constraints.video.max_delay.value();
|
||||
}
|
||||
|
||||
if (constraints.video.max_pixels_per_second.has_value()) {
|
||||
recommendations->video.max_pixels_per_second =
|
||||
constraints.video.max_pixels_per_second.value();
|
||||
}
|
||||
|
||||
recommendations->video.bit_rate_limits =
|
||||
BitRateLimits{std::max(constraints.video.min_bit_rate,
|
||||
recommendations->video.bit_rate_limits.minimum),
|
||||
std::min(constraints.video.max_bit_rate,
|
||||
recommendations->video.bit_rate_limits.maximum)};
|
||||
Dimensions dimensions = constraints.video.max_dimensions;
|
||||
if (dimensions.width <= kDefaultMinResolution.width) {
|
||||
recommendations->video.maximum = {kDefaultMinResolution.width,
|
||||
kDefaultMinResolution.height,
|
||||
kDefaultFrameRate};
|
||||
} else if (dimensions.width < recommendations->video.maximum.width) {
|
||||
recommendations->video.maximum = std::move(dimensions);
|
||||
}
|
||||
|
||||
if (constraints.video.min_resolution) {
|
||||
const Resolution& min = constraints.video.min_resolution->ToResolution();
|
||||
if (kDefaultMinResolution.width < min.width) {
|
||||
recommendations->video.minimum = std::move(min);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The receiver's video constraints, even when each is individually valid, can
|
||||
// intersect with the display description to produce an inverted range: a
|
||||
// minimum bit rate above the display-limited maximum, or a minimum resolution
|
||||
// larger than the display. (Audio cannot invert: AudioConstraints::IsValid()
|
||||
// already requires max_bit_rate >= min_bit_rate.) Resolve any such
|
||||
// contradiction in favor of the maximum, which reflects what the
|
||||
// receiver/display can actually handle.
|
||||
void ClampVideoToWellOrderedRanges(Video& video) {
|
||||
video.bit_rate_limits.minimum =
|
||||
std::min(video.bit_rate_limits.minimum, video.bit_rate_limits.maximum);
|
||||
video.minimum.width = std::min(video.minimum.width, video.maximum.width);
|
||||
video.minimum.height =
|
||||
std::min(video.minimum.height, video.maximum.height);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool BitRateLimits::operator==(const BitRateLimits& other) const {
|
||||
return std::tie(minimum, maximum) == std::tie(other.minimum, other.maximum);
|
||||
}
|
||||
|
||||
bool Audio::operator==(const Audio& other) const {
|
||||
return std::tie(bit_rate_limits, max_delay, max_channels, max_sample_rate) ==
|
||||
std::tie(other.bit_rate_limits, other.max_delay, other.max_channels,
|
||||
other.max_sample_rate);
|
||||
}
|
||||
|
||||
bool Video::operator==(const Video& other) const {
|
||||
return std::tie(bit_rate_limits, minimum, maximum, supports_scaling,
|
||||
max_delay, max_pixels_per_second) ==
|
||||
std::tie(other.bit_rate_limits, other.minimum, other.maximum,
|
||||
other.supports_scaling, other.max_delay,
|
||||
other.max_pixels_per_second);
|
||||
}
|
||||
|
||||
bool Recommendations::operator==(const Recommendations& other) const {
|
||||
return std::tie(audio, video) == std::tie(other.audio, other.video);
|
||||
}
|
||||
|
||||
Recommendations GetRecommendations(const Answer& answer) {
|
||||
Recommendations recommendations;
|
||||
if (answer.display.has_value() && answer.display->IsValid()) {
|
||||
ApplyDisplay(answer.display.value(), &recommendations);
|
||||
}
|
||||
if (answer.constraints.has_value() && answer.constraints->IsValid()) {
|
||||
ApplyConstraints(answer.constraints.value(), &recommendations);
|
||||
}
|
||||
ClampVideoToWellOrderedRanges(recommendations.video);
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
} // namespace capture_recommendations
|
||||
} // namespace openscreen::cast
|
||||
151
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/capture_recommendations.h
vendored
Normal file
151
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/capture_recommendations.h
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2020 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_PUBLIC_CAPTURE_RECOMMENDATIONS_H_
|
||||
#define CAST_STREAMING_PUBLIC_CAPTURE_RECOMMENDATIONS_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "cast/streaming/resolution.h"
|
||||
namespace openscreen::cast {
|
||||
|
||||
struct Answer;
|
||||
|
||||
// This namespace contains classes and functions to be used by senders for
|
||||
// determining what constraints are recommended for the capture device, based on
|
||||
// the limits reported by the receiver.
|
||||
//
|
||||
// A general note about recommendations: they are NOT maximum operational
|
||||
// limits, instead they are targeted to provide a delightful cast experience.
|
||||
// For example, if a receiver is connected to a 1080P display but cannot provide
|
||||
// 1080P at a stable FPS with a good experience, 1080P will not be recommended.
|
||||
namespace capture_recommendations {
|
||||
|
||||
// Default maximum delay for both audio and video. Used if the sender fails
|
||||
// to provide any constraints.
|
||||
inline constexpr std::chrono::milliseconds kDefaultMaxDelayMs(400);
|
||||
|
||||
// Bit rate limits, used for both audio and video streams.
|
||||
struct BitRateLimits {
|
||||
bool operator==(const BitRateLimits& other) const;
|
||||
|
||||
// Minimum bit rate, in bits per second.
|
||||
int minimum;
|
||||
|
||||
// Maximum bit rate, in bits per second.
|
||||
int maximum;
|
||||
};
|
||||
|
||||
// The mirroring control protocol specifies 32kbps as the absolute minimum
|
||||
// for audio. Depending on the type of audio content (narrowband, fullband,
|
||||
// etc.) Opus specifically can perform very well at this bitrate.
|
||||
// See: https://research.google/pubs/pub41650/
|
||||
inline constexpr int kDefaultAudioMinBitRate = 32 * 1000;
|
||||
|
||||
// Opus generally sees little improvement above 192kbps, but some older codecs
|
||||
// that we may consider supporting improve at up to 256kbps.
|
||||
inline constexpr int kDefaultAudioMaxBitRate = 256 * 1000;
|
||||
inline constexpr BitRateLimits kDefaultAudioBitRateLimits{
|
||||
kDefaultAudioMinBitRate, kDefaultAudioMaxBitRate};
|
||||
|
||||
// While generally audio should be captured at the maximum sample rate, 16kHz is
|
||||
// the recommended absolute minimum.
|
||||
inline constexpr int kDefaultAudioMinSampleRate = 16000;
|
||||
|
||||
// Audio capture recommendations. Maximum delay is determined by buffer
|
||||
// constraints, and capture bit rate may vary between limits as appropriate.
|
||||
struct Audio {
|
||||
bool operator==(const Audio& other) const;
|
||||
|
||||
// Represents the recommended bit rate range.
|
||||
BitRateLimits bit_rate_limits = kDefaultAudioBitRateLimits;
|
||||
|
||||
// Represents the maximum audio delay, in milliseconds.
|
||||
std::chrono::milliseconds max_delay = kDefaultMaxDelayMs;
|
||||
|
||||
// Represents the maximum number of audio channels.
|
||||
int max_channels = kDefaultAudioChannels;
|
||||
|
||||
// Represents the maximum samples per second.
|
||||
int max_sample_rate = kDefaultAudioSampleRate;
|
||||
|
||||
// Represents the absolute minimum samples per second. Generally speaking,
|
||||
// audio should be captured at the maximum samples per second rate.
|
||||
int min_sample_rate = kDefaultAudioMinSampleRate;
|
||||
};
|
||||
|
||||
// The minimum dimensions are as close as possible to low-definition
|
||||
// television, factoring in the receiver's aspect ratio if provided.
|
||||
inline constexpr Resolution kDefaultMinResolution{kMinVideoWidth,
|
||||
kMinVideoHeight};
|
||||
|
||||
// Currently mirroring only supports 1080P.
|
||||
inline constexpr Dimensions kDefaultMaxResolution{1920, 1080,
|
||||
kDefaultFrameRate};
|
||||
|
||||
// The mirroring spec suggests 300kbps as the absolute minimum bitrate.
|
||||
inline constexpr int kDefaultVideoMinBitRate = 300 * 1000;
|
||||
|
||||
// The theoretical maximum pixels per second is the maximum bit rate
|
||||
// divided by 8 (the max byte rate). In practice it should generally be
|
||||
// less.
|
||||
inline constexpr int kDefaultVideoMaxPixelsPerSecond =
|
||||
kDefaultMaxResolution.effective_bit_rate() / 8;
|
||||
|
||||
// Our default limits are merely the product of the minimum and maximum
|
||||
// dimensions, and are only used if the receiver fails to give better
|
||||
// constraint information.
|
||||
inline constexpr BitRateLimits kDefaultVideoBitRateLimits{
|
||||
kDefaultVideoMinBitRate, kDefaultMaxResolution.effective_bit_rate()};
|
||||
|
||||
// Video capture recommendations.
|
||||
struct Video {
|
||||
bool operator==(const Video& other) const;
|
||||
|
||||
// Represents the recommended bit rate range.
|
||||
BitRateLimits bit_rate_limits = kDefaultVideoBitRateLimits;
|
||||
|
||||
// Represents the recommended minimum resolution.
|
||||
Resolution minimum = kDefaultMinResolution;
|
||||
|
||||
// Represents the recommended maximum resolution.
|
||||
Dimensions maximum = kDefaultMaxResolution;
|
||||
|
||||
// Indicates whether the receiver can scale frames from a different aspect
|
||||
// ratio, or if it needs to be done by the sender. Default is false, meaning
|
||||
// that the sender is responsible for letterboxing.
|
||||
bool supports_scaling = false;
|
||||
|
||||
// Represents the maximum video delay, in milliseconds.
|
||||
std::chrono::milliseconds max_delay = kDefaultMaxDelayMs;
|
||||
|
||||
// Represents the maximum pixels per second, not necessarily correlated
|
||||
// to bit rate.
|
||||
int max_pixels_per_second = kDefaultVideoMaxPixelsPerSecond;
|
||||
};
|
||||
|
||||
// Outputted recommendations for usage by capture devices. Note that we always
|
||||
// return both audio and video (it is up to the sender to determine what
|
||||
// streams actually get created). If the receiver doesn't give us any
|
||||
// information for making recommendations, the defaults are used.
|
||||
struct Recommendations {
|
||||
bool operator==(const Recommendations& other) const;
|
||||
|
||||
// Audio specific recommendations.
|
||||
Audio audio;
|
||||
|
||||
// Video specific recommendations.
|
||||
Video video;
|
||||
};
|
||||
|
||||
Recommendations GetRecommendations(const Answer& answer);
|
||||
|
||||
} // namespace capture_recommendations
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_CAPTURE_RECOMMENDATIONS_H_
|
||||
57
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/constants.cc
vendored
Normal file
57
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/constants.cc
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, VideoCodec codec) {
|
||||
const char* str = nullptr;
|
||||
switch (codec) {
|
||||
case VideoCodec::kH264:
|
||||
str = "H264";
|
||||
break;
|
||||
case VideoCodec::kVp8:
|
||||
str = "VP8";
|
||||
break;
|
||||
case VideoCodec::kHevc:
|
||||
str = "HEVC";
|
||||
break;
|
||||
case VideoCodec::kNotSpecified:
|
||||
str = "NotSpecified";
|
||||
break;
|
||||
case VideoCodec::kVp9:
|
||||
str = "VP9";
|
||||
break;
|
||||
case VideoCodec::kAv1:
|
||||
str = "AV1";
|
||||
break;
|
||||
default:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
os << str;
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, CastMode mode) {
|
||||
const char* str = nullptr;
|
||||
switch (mode) {
|
||||
case CastMode::kMirroring:
|
||||
str = "mirroring";
|
||||
break;
|
||||
case CastMode::kRemoting:
|
||||
str = "remoting";
|
||||
break;
|
||||
default:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
os << str;
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
122
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/constants.h
vendored
Normal file
122
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/constants.h
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2015 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_PUBLIC_CONSTANTS_H_
|
||||
#define CAST_STREAMING_PUBLIC_CONSTANTS_H_
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE: This file should only contain constants that are reasonably globally
|
||||
// used (i.e., by many modules, and in all or nearly all subdirs). Do NOT add
|
||||
// non-POD constants, functions, interfaces, or any logic to this module,
|
||||
// except for std::ostream operators on an as-needed basis.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <chrono>
|
||||
#include <ostream>
|
||||
#include <ratio>
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Default target playout delay. The playout delay is the window of time between
|
||||
// capture from the source until presentation at the receiver.
|
||||
inline constexpr std::chrono::milliseconds kDefaultTargetPlayoutDelay(400);
|
||||
|
||||
// Default UDP port, bound at the Receiver, for Cast Streaming. An
|
||||
// implementation is required to use the port specified by the Receiver in its
|
||||
// ANSWER control message, which may or may not match this port number here.
|
||||
inline constexpr int kDefaultCastStreamingPort = 2344;
|
||||
|
||||
// Default TCP port, bound at the TLS server socket level, for Cast Streaming.
|
||||
// An implementation must use the port specified in the DNS-SD published record
|
||||
// for connecting over TLS, which may or may not match this port number here.
|
||||
inline constexpr int kDefaultCastPort = 8010;
|
||||
|
||||
// Target number of milliseconds between the sending of RTCP reports. Both
|
||||
// senders and receivers regularly send RTCP reports to their peer.
|
||||
inline constexpr std::chrono::milliseconds kRtcpReportInterval(500);
|
||||
|
||||
// This is an important system-wide constant. This limits how much history
|
||||
// the implementation must retain in order to process the acknowledgements of
|
||||
// past frames.
|
||||
//
|
||||
// This value is carefully choosen such that it fits in the 8-bits range for
|
||||
// frame IDs. It is also less than half of the full 8-bits range such that
|
||||
// logic can handle wrap around and compare two frame IDs meaningfully.
|
||||
inline constexpr int kMaxUnackedFrames = 120;
|
||||
|
||||
// The network must support a packet size of at least this many bytes.
|
||||
inline constexpr int kRequiredNetworkPacketSize = 256;
|
||||
|
||||
// The spec declares RTP timestamps must always have a timebase of 90000 ticks
|
||||
// per second for video.
|
||||
inline constexpr int kRtpVideoTimebase = 90000;
|
||||
|
||||
// Minimum resolution is 320x240.
|
||||
inline constexpr int kMinVideoHeight = 240;
|
||||
inline constexpr int kMinVideoWidth = 320;
|
||||
|
||||
// The default frame rate for capture options is 30FPS.
|
||||
inline constexpr int kDefaultFrameRate = 30;
|
||||
|
||||
// The mirroring spec suggests 300kbps as the absolute minimum bitrate.
|
||||
inline constexpr int kDefaultVideoMinBitRate = 300 * 1000;
|
||||
|
||||
// Default video max bitrate is based on 1080P @ 30FPS, which can be played back
|
||||
// at good quality around 10mbps.
|
||||
inline constexpr int kDefaultVideoMaxBitRate = 10 * 1000 * 1000;
|
||||
|
||||
// The mirroring control protocol specifies 32kbps as the absolute minimum
|
||||
// for audio. Depending on the type of audio content (narrowband, fullband,
|
||||
// etc.) Opus specifically can perform very well at this bitrate.
|
||||
// See: https://research.google/pubs/pub41650/
|
||||
inline constexpr int kDefaultAudioMinBitRate = 32 * 1000;
|
||||
|
||||
// Opus generally sees little improvement above 192kbps, but some older codecs
|
||||
// that we may consider supporting improve at up to 256kbps.
|
||||
inline constexpr int kDefaultAudioMaxBitRate = 256 * 1000;
|
||||
|
||||
// While generally audio should be captured at the maximum sample rate, 16kHz is
|
||||
// the recommended absolute minimum.
|
||||
inline constexpr int kDefaultAudioMinSampleRate = 16000;
|
||||
|
||||
// The default audio sample rate is 48kHz, slightly higher than standard
|
||||
// consumer audio.
|
||||
inline constexpr int kDefaultAudioSampleRate = 48000;
|
||||
|
||||
// The default audio number of channels is set to stereo.
|
||||
inline constexpr int kDefaultAudioChannels = 2;
|
||||
|
||||
// Default maximum delay for both audio and video. Used if the sender fails
|
||||
// to provide any constraints.
|
||||
inline constexpr std::chrono::milliseconds kDefaultMaxDelayMs(1500);
|
||||
|
||||
// TODO(issuetracker.google.com/184189100): As part of updating remoting
|
||||
// OFFER/ANSWER and capabilities exchange, remoting version should be updated
|
||||
// to 3.
|
||||
inline constexpr int kSupportedRemotingVersion = 2;
|
||||
|
||||
// Used for RTCP message support.
|
||||
constexpr uint32_t kCastName = ('C' << 24) + ('A' << 16) + ('S' << 8) + 'T';
|
||||
|
||||
// Codecs known and understood by cast senders and receivers. Note: receivers
|
||||
// are required to implement the following codecs to be Cast V2 compliant: H264,
|
||||
// VP8, AAC, Opus. Senders have to implement at least one codec from this
|
||||
// list for audio or video to start a session.
|
||||
// `kNotSpecified` is used in remoting to indicate that the stream is being
|
||||
// remoted and is not specified as part of the OFFER message (indicated as
|
||||
// "REMOTE_AUDIO" or "REMOTE_VIDEO").
|
||||
enum class AudioCodec { kAac, kOpus, kNotSpecified };
|
||||
|
||||
enum class VideoCodec { kH264, kVp8, kHevc, kNotSpecified, kVp9, kAv1 };
|
||||
std::ostream& operator<<(std::ostream& os, VideoCodec codec);
|
||||
|
||||
// The type (audio, video, or unknown) of the stream.
|
||||
enum class StreamType { kUnknown, kAudio, kVideo };
|
||||
|
||||
enum class CastMode : uint8_t { kMirroring, kRemoting };
|
||||
std::ostream& operator<<(std::ostream& os, CastMode mode);
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_CONSTANTS_H_
|
||||
60
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/encoded_frame.cc
vendored
Normal file
60
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/encoded_frame.cc
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2014 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/encoded_frame.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
EncodedFrame::EncodedFrame(Dependency dependency,
|
||||
FrameId frame_id,
|
||||
FrameId referenced_frame_id,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
Clock::time_point reference_time,
|
||||
std::chrono::milliseconds new_playout_delay,
|
||||
Clock::time_point capture_begin_time,
|
||||
Clock::time_point capture_end_time,
|
||||
ByteView data)
|
||||
: dependency(dependency),
|
||||
frame_id(frame_id),
|
||||
referenced_frame_id(referenced_frame_id),
|
||||
rtp_timestamp(rtp_timestamp),
|
||||
reference_time(reference_time),
|
||||
new_playout_delay(new_playout_delay),
|
||||
capture_begin_time(capture_begin_time),
|
||||
capture_end_time(capture_end_time),
|
||||
data(data) {}
|
||||
|
||||
EncodedFrame::EncodedFrame(Dependency dependency,
|
||||
FrameId frame_id,
|
||||
FrameId referenced_frame_id,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
Clock::time_point reference_time,
|
||||
std::chrono::milliseconds new_playout_delay,
|
||||
ByteView data)
|
||||
: dependency(dependency),
|
||||
frame_id(frame_id),
|
||||
referenced_frame_id(referenced_frame_id),
|
||||
rtp_timestamp(rtp_timestamp),
|
||||
reference_time(reference_time),
|
||||
new_playout_delay(new_playout_delay),
|
||||
data(data) {}
|
||||
|
||||
EncodedFrame::EncodedFrame() = default;
|
||||
EncodedFrame::~EncodedFrame() = default;
|
||||
|
||||
EncodedFrame::EncodedFrame(EncodedFrame&&) noexcept = default;
|
||||
EncodedFrame& EncodedFrame::operator=(EncodedFrame&&) = default;
|
||||
|
||||
void EncodedFrame::CopyMetadataTo(EncodedFrame* dest) const {
|
||||
dest->dependency = this->dependency;
|
||||
dest->frame_id = this->frame_id;
|
||||
dest->referenced_frame_id = this->referenced_frame_id;
|
||||
dest->rtp_timestamp = this->rtp_timestamp;
|
||||
dest->reference_time = this->reference_time;
|
||||
dest->new_playout_delay = this->new_playout_delay;
|
||||
dest->capture_begin_time = this->capture_begin_time;
|
||||
dest->capture_end_time = this->capture_end_time;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
119
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/encoded_frame.h
vendored
Normal file
119
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/encoded_frame.h
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2014 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_PUBLIC_ENCODED_FRAME_H_
|
||||
#define CAST_STREAMING_PUBLIC_ENCODED_FRAME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/public/frame_id.h"
|
||||
#include "cast/streaming/rtp_time.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "platform/base/span.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// A combination of metadata and data for one encoded frame. This can contain
|
||||
// audio data or video data or other.
|
||||
struct EncodedFrame {
|
||||
enum class Dependency : int8_t {
|
||||
// "null" value, used to indicate whether `dependency` has been set.
|
||||
kUnknown,
|
||||
|
||||
// Not decodable without the reference frame indicated by
|
||||
// `referenced_frame_id`.
|
||||
kDependent,
|
||||
|
||||
// Independently decodable.
|
||||
kIndependent,
|
||||
|
||||
// Independently decodable, and no future frames will depend on any frames
|
||||
// before this one.
|
||||
kKeyFrame,
|
||||
};
|
||||
|
||||
EncodedFrame(Dependency dependency,
|
||||
FrameId frame_id,
|
||||
FrameId referenced_frame_id,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
Clock::time_point reference_time,
|
||||
std::chrono::milliseconds new_playout_delay,
|
||||
Clock::time_point capture_begin_time,
|
||||
Clock::time_point capture_end_time,
|
||||
ByteView data);
|
||||
|
||||
// TODO(issuetracker.google.com/285905175): remove remaining optional fields
|
||||
// (new_playout_delay) once Chrome provides the capture begin and end
|
||||
// timestamps, so this constructor only provides the required fields.
|
||||
EncodedFrame(Dependency dependency,
|
||||
FrameId frame_id,
|
||||
FrameId referenced_frame_id,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
Clock::time_point reference_time,
|
||||
std::chrono::milliseconds new_playout_delay,
|
||||
ByteView data);
|
||||
EncodedFrame();
|
||||
EncodedFrame(const EncodedFrame&) = delete;
|
||||
EncodedFrame& operator=(const EncodedFrame&) = delete;
|
||||
EncodedFrame(EncodedFrame&&) noexcept;
|
||||
EncodedFrame& operator=(EncodedFrame&&);
|
||||
~EncodedFrame();
|
||||
|
||||
// Copies all members except `data` to `dest`. Does not modify |dest->data|.
|
||||
void CopyMetadataTo(EncodedFrame* dest) const;
|
||||
|
||||
// This frame's dependency relationship with respect to other frames.
|
||||
Dependency dependency = Dependency::kUnknown;
|
||||
|
||||
// The label associated with this frame. Implies an ordering relative to
|
||||
// other frames in the same stream.
|
||||
FrameId frame_id;
|
||||
|
||||
// The label associated with the frame upon which this frame depends. If
|
||||
// this frame does not require any other frame in order to become decodable
|
||||
// (e.g., key frames), `referenced_frame_id` must equal `frame_id`.
|
||||
FrameId referenced_frame_id;
|
||||
|
||||
// The stream timestamp, on the timeline of the signal data. For example, RTP
|
||||
// timestamps for audio are usually defined as the total number of audio
|
||||
// samples encoded in all prior frames. A playback system uses this value to
|
||||
// detect gaps in the stream, and otherwise stretch the signal to gradually
|
||||
// re-align towards playout targets when too much drift has occurred (see
|
||||
// `reference_time`, below).
|
||||
RtpTimeTicks rtp_timestamp;
|
||||
|
||||
// The common reference clock timestamp for this frame. Over a sequence of
|
||||
// frames, this time value is expected to drift with respect to the elapsed
|
||||
// time implied by the RTP timestamps; and this may not necessarily increment
|
||||
// with precise regularity.
|
||||
//
|
||||
// This value originates from a sender, and is the time at which the frame was
|
||||
// captured/recorded. In the receiver context, this value is the computed
|
||||
// target playout time, which is used for guiding the timing of presentation
|
||||
// (see `rtp_timestamp`, above). It is also meant to be used to synchronize
|
||||
// the presentation of multiple streams (e.g., audio and video), commonly
|
||||
// known as "lip-sync." It is NOT meant to be a mandatory/exact playout time.
|
||||
Clock::time_point reference_time;
|
||||
|
||||
// Playout delay for this and all future frames. Used by the Adaptive
|
||||
// Playout delay extension. Non-positive values means no change.
|
||||
std::chrono::milliseconds new_playout_delay{};
|
||||
|
||||
// Video capture begin/end timestamps. If set to a value other than
|
||||
// Clock::time_point::min(), used for improved statistics gathering.
|
||||
Clock::time_point capture_begin_time = Clock::time_point::min();
|
||||
Clock::time_point capture_end_time = Clock::time_point::min();
|
||||
|
||||
// A buffer containing the encoded signal data for the frame. In the sender
|
||||
// context, this points to the data to be sent. In the receiver context, this
|
||||
// is set to the region of a client-provided buffer that was populated.
|
||||
ByteView data;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_ENCODED_FRAME_H_
|
||||
171
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/environment.cc
vendored
Normal file
171
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/environment.cc
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/environment.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
#include "platform/api/task_runner.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
Environment::PacketConsumer::~PacketConsumer() = default;
|
||||
|
||||
Environment::SocketSubscriber::~SocketSubscriber() = default;
|
||||
|
||||
Environment::Environment(ClockNowFunctionPtr now_function,
|
||||
TaskRunner& task_runner,
|
||||
const IPEndpoint& local_endpoint)
|
||||
: now_function_(now_function), task_runner_(task_runner) {
|
||||
OSP_CHECK(now_function_);
|
||||
ErrorOr<std::unique_ptr<UdpSocket>> result =
|
||||
UdpSocket::Create(*task_runner_, this, local_endpoint);
|
||||
if (result.is_error()) {
|
||||
OSP_LOG_ERROR << "Unable to create a UDP socket bound to " << local_endpoint
|
||||
<< ": " << result.error();
|
||||
return;
|
||||
}
|
||||
const_cast<std::unique_ptr<UdpSocket>&>(socket_) = std::move(result.value());
|
||||
OSP_CHECK(socket_);
|
||||
socket_->Bind();
|
||||
}
|
||||
|
||||
Environment::~Environment() = default;
|
||||
|
||||
IPEndpoint Environment::GetBoundLocalEndpoint() const {
|
||||
if (socket_) {
|
||||
return socket_->GetLocalEndpoint();
|
||||
}
|
||||
return IPEndpoint{};
|
||||
}
|
||||
|
||||
void Environment::SetSocketStateForTesting(SocketState state) {
|
||||
state_ = state;
|
||||
if (socket_subscriber_) {
|
||||
switch (state_) {
|
||||
case SocketState::kReady:
|
||||
socket_subscriber_->OnSocketReady();
|
||||
break;
|
||||
case SocketState::kInvalid:
|
||||
socket_subscriber_->OnSocketInvalid(Error::Code::kSocketFailure);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Environment::SetSocketSubscriber(SocketSubscriber* subscriber) {
|
||||
socket_subscriber_ = subscriber;
|
||||
}
|
||||
|
||||
void Environment::SetStatisticsCollector(StatisticsCollector* collector) {
|
||||
statistics_collector_ = collector;
|
||||
}
|
||||
|
||||
void Environment::ConsumeIncomingPackets(PacketConsumer* packet_consumer) {
|
||||
OSP_CHECK(packet_consumer);
|
||||
OSP_CHECK(!packet_consumer_);
|
||||
packet_consumer_ = packet_consumer;
|
||||
}
|
||||
|
||||
void Environment::DropIncomingPackets() {
|
||||
packet_consumer_ = nullptr;
|
||||
}
|
||||
|
||||
int Environment::GetMaxPacketSize() const {
|
||||
// Return hard-coded values for UDP over wired Ethernet (which is a smaller
|
||||
// MTU than typical defaults for UDP over 802.11 wireless). Performance would
|
||||
// be more-optimized if the network were probed for the actual value. See
|
||||
// discussion in rtp_defines.h.
|
||||
switch (remote_endpoint_.address.version()) {
|
||||
case IPAddress::Version::kV4:
|
||||
return kMaxRtpPacketSizeForIpv4UdpOnEthernet;
|
||||
case IPAddress::Version::kV6:
|
||||
return kMaxRtpPacketSizeForIpv6UdpOnEthernet;
|
||||
default:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void Environment::SetDscp(UdpSocket::DscpMode mode) {
|
||||
if (socket_) {
|
||||
socket_->SetDscp(mode);
|
||||
}
|
||||
}
|
||||
|
||||
void Environment::SendPacket(ByteView packet, PacketMetadata metadata) {
|
||||
OSP_CHECK(remote_endpoint_.address);
|
||||
OSP_CHECK_NE(remote_endpoint_.port, 0);
|
||||
if (socket_) {
|
||||
socket_->SendMessage(packet, remote_endpoint_);
|
||||
}
|
||||
if (statistics_collector_) {
|
||||
statistics_collector_->CollectPacketSentEvent(packet, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void Environment::OnBound(UdpSocket* socket) {
|
||||
OSP_CHECK_EQ(socket, socket_.get());
|
||||
state_ = SocketState::kReady;
|
||||
|
||||
if (socket_subscriber_) {
|
||||
socket_subscriber_->OnSocketReady();
|
||||
}
|
||||
}
|
||||
|
||||
void Environment::OnError(UdpSocket* socket, const Error& error) {
|
||||
OSP_CHECK_EQ(socket, socket_.get());
|
||||
// Usually OnError() is only called for non-recoverable Errors. However,
|
||||
// OnSendError() and OnRead() delegate to this method, to handle their hard
|
||||
// error cases as well. So, return early here if `error` is recoverable.
|
||||
if (error.ok() || error.code() == Error::Code::kAgain) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_ = SocketState::kInvalid;
|
||||
if (socket_subscriber_) {
|
||||
socket_subscriber_->OnSocketInvalid(error);
|
||||
} else {
|
||||
// Default behavior when there are no subscribers.
|
||||
OSP_LOG_ERROR << "For UDP socket bound to " << socket_->GetLocalEndpoint()
|
||||
<< ": " << error;
|
||||
}
|
||||
}
|
||||
|
||||
void Environment::OnSendError(UdpSocket* socket, const Error& error) {
|
||||
OnError(socket, error);
|
||||
}
|
||||
|
||||
void Environment::OnRead(UdpSocket* socket,
|
||||
ErrorOr<UdpPacket> packet_or_error) {
|
||||
if (!packet_consumer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet_or_error.is_error()) {
|
||||
OnError(socket, packet_or_error.error());
|
||||
return;
|
||||
}
|
||||
|
||||
// Ideally, the arrival time would come from the operating system's network
|
||||
// stack (e.g., by using the SO_TIMESTAMP sockopt on POSIX systems). However,
|
||||
// there would still be the problem of mapping the timestamp to a value in
|
||||
// terms of Clock::time_point. So, just sample the Clock here and call that
|
||||
// the "arrival time." While this can add variance within the system, it
|
||||
// should be minimal, assuming not too much time has elapsed between the
|
||||
// actual packet receive event and the when this code here is executing.
|
||||
const Clock::time_point arrival_time = now_function_();
|
||||
|
||||
UdpPacket packet = std::move(packet_or_error.value());
|
||||
packet_consumer_->OnReceivedPacket(
|
||||
packet.source(), arrival_time,
|
||||
std::move(static_cast<std::vector<uint8_t>&>(packet)));
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
164
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/environment.h
vendored
Normal file
164
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/environment.h
vendored
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// Copyright 2019 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_PUBLIC_ENVIRONMENT_H_
|
||||
#define CAST_STREAMING_PUBLIC_ENVIRONMENT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/impl/statistics_collector.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "platform/api/udp_socket.h"
|
||||
#include "platform/base/ip_address.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/raw_ptr.h"
|
||||
#include "util/raw_ref.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Provides the common environment for operating system resources shared by
|
||||
// multiple components.
|
||||
class Environment : public UdpSocket::Client {
|
||||
public:
|
||||
class PacketConsumer {
|
||||
public:
|
||||
virtual void OnReceivedPacket(const IPEndpoint& source,
|
||||
Clock::time_point arrival_time,
|
||||
std::vector<uint8_t> packet) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~PacketConsumer();
|
||||
};
|
||||
|
||||
// Consumers of the environment's UDP socket should be careful to check the
|
||||
// socket's state before accessing its methods, especially
|
||||
// GetBoundLocalEndpoint(). If the environment is `kStarting`, the
|
||||
// local endpoint may not be set yet and will be zero initialized.
|
||||
enum class SocketState {
|
||||
// Socket is still initializing. Usually the UDP socket bind is
|
||||
// the last piece.
|
||||
kStarting,
|
||||
|
||||
// The socket is ready for use and has been bound.
|
||||
kReady,
|
||||
|
||||
// The socket is either closed (normally or due to an error) or in an
|
||||
// invalid state. Currently the environment does not create a new socket
|
||||
// in this case, so to be used again the environment itself needs to be
|
||||
// recreated.
|
||||
kInvalid
|
||||
};
|
||||
|
||||
// Classes concerned with the Environment's UDP socket state may inherit from
|
||||
// `Subscriber` and then `Subscribe`.
|
||||
class SocketSubscriber {
|
||||
public:
|
||||
// Event that occurs when the environment is ready for use.
|
||||
virtual void OnSocketReady() = 0;
|
||||
|
||||
// Event that occurs when the environment has experienced a fatal error.
|
||||
virtual void OnSocketInvalid(const Error& error) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~SocketSubscriber();
|
||||
};
|
||||
|
||||
// Construct with the given clock source and TaskRunner. Creates and
|
||||
// internally-owns a UdpSocket, and immediately binds it to the given
|
||||
// `local_endpoint`. Default behavior if `local_endpoint` is omitted is to
|
||||
// bind to all available interfaces using IPv4.
|
||||
Environment(ClockNowFunctionPtr now_function,
|
||||
TaskRunner& task_runner,
|
||||
const IPEndpoint& local_endpoint = IPEndpoint::kAnyV4());
|
||||
|
||||
~Environment() override;
|
||||
|
||||
ClockNowFunctionPtr now_function() const { return now_function_; }
|
||||
Clock::time_point now() const { return now_function_(); }
|
||||
TaskRunner& task_runner() const { return *task_runner_; }
|
||||
|
||||
// Returns the local endpoint the socket is bound to, or the zero IPEndpoint
|
||||
// if socket creation/binding failed.
|
||||
//
|
||||
// Note: This method is virtual to allow unit tests to fake that there really
|
||||
// is a bound socket.
|
||||
virtual IPEndpoint GetBoundLocalEndpoint() const;
|
||||
|
||||
// Get/Set the remote endpoint. This is separate from the constructor because
|
||||
// the remote endpoint is, in some cases, discovered only after receiving a
|
||||
// packet.
|
||||
const IPEndpoint& remote_endpoint() const { return remote_endpoint_; }
|
||||
void set_remote_endpoint(const IPEndpoint& endpoint) {
|
||||
remote_endpoint_ = endpoint;
|
||||
}
|
||||
|
||||
SocketState socket_state() const { return state_; }
|
||||
void SetSocketStateForTesting(SocketState state);
|
||||
|
||||
// Subscribe to socket changes. Callers can unsubscribe by passing
|
||||
// nullptr.
|
||||
void SetSocketSubscriber(SocketSubscriber* subscriber);
|
||||
|
||||
// Subscribe to frame and packet events. Callers can unsubscribe by passing
|
||||
// nullptr. Note that if the collector is destroyed before the environment,
|
||||
// callers MUST unsubscribe to avoid an access exception.
|
||||
void SetStatisticsCollector(StatisticsCollector* subscriber);
|
||||
StatisticsCollector* statistics_collector() {
|
||||
return statistics_collector_.get();
|
||||
}
|
||||
|
||||
// Start/Resume delivery of incoming packets to the given `packet_consumer`.
|
||||
// Delivery will continue until DropIncomingPackets() is called.
|
||||
void ConsumeIncomingPackets(PacketConsumer* packet_consumer);
|
||||
|
||||
// Stop delivery of incoming packets, dropping any that do come in. All
|
||||
// internal references to the PacketConsumer that was provided in the last
|
||||
// call to ConsumeIncomingPackets() are cleared.
|
||||
void DropIncomingPackets();
|
||||
|
||||
// Returns the maximum packet size for the network. This will always return a
|
||||
// value of at least kRequiredNetworkPacketSize.
|
||||
int GetMaxPacketSize() const;
|
||||
|
||||
// Sets the DSCP value for the underlying UDP socket.
|
||||
void SetDscp(UdpSocket::DscpMode mode);
|
||||
|
||||
// Sends the given `packet` to the remote endpoint, best-effort.
|
||||
// set_remote_endpoint() must be called beforehand with a valid IPEndpoint.
|
||||
//
|
||||
// Note: This method is virtual to allow unit tests to intercept packets
|
||||
// before they actually head-out through the socket.
|
||||
virtual void SendPacket(ByteView packet, PacketMetadata metadata);
|
||||
|
||||
private:
|
||||
// UdpSocket::Client implementation.
|
||||
void OnBound(UdpSocket* socket) final;
|
||||
void OnError(UdpSocket* socket, const Error& error) final;
|
||||
void OnSendError(UdpSocket* socket, const Error& error) final;
|
||||
void OnRead(UdpSocket* socket, ErrorOr<UdpPacket> packet_or_error) final;
|
||||
|
||||
ClockNowFunctionPtr now_function_;
|
||||
const raw_ref<TaskRunner> task_runner_;
|
||||
|
||||
// The UDP socket bound to the local endpoint that was passed into the
|
||||
// constructor, or null if socket creation failed.
|
||||
const std::unique_ptr<UdpSocket> socket_;
|
||||
|
||||
// These are externally set/cleared. Behaviors are described in getter/setter
|
||||
// method comments above.
|
||||
IPEndpoint local_endpoint_{};
|
||||
IPEndpoint remote_endpoint_{};
|
||||
raw_ptr<PacketConsumer> packet_consumer_ = nullptr;
|
||||
SocketState state_ = SocketState::kStarting;
|
||||
raw_ptr<SocketSubscriber> socket_subscriber_ = nullptr;
|
||||
raw_ptr<StatisticsCollector> statistics_collector_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_ENVIRONMENT_H_
|
||||
20
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/frame_id.cc
vendored
Normal file
20
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/frame_id.cc
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2016 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/frame_id.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const FrameId rhs) {
|
||||
return out << rhs.ToString();
|
||||
}
|
||||
|
||||
std::string FrameId::ToString() const {
|
||||
if (is_null())
|
||||
return "F<null>";
|
||||
|
||||
return "F" + std::to_string(value());
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
121
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/frame_id.h
vendored
Normal file
121
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/frame_id.h
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2016 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_PUBLIC_FRAME_ID_H_
|
||||
#define CAST_STREAMING_PUBLIC_FRAME_ID_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "cast/streaming/impl/expanded_value_base.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Forward declaration (see below).
|
||||
class FrameId;
|
||||
|
||||
// Convenience operator overloads for logging.
|
||||
std::ostream& operator<<(std::ostream& out, const FrameId rhs);
|
||||
|
||||
// Unique identifier for a frame in a RTP media stream. FrameIds are truncated
|
||||
// to 8-bit values in RTP and RTCP headers, and then expanded back by the other
|
||||
// endpoint when parsing the headers.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// // Distance/offset math.
|
||||
// FrameId first = FrameId::first();
|
||||
// FrameId second = first + 1;
|
||||
// FrameId third = second + 1;
|
||||
// int64_t offset = third - first;
|
||||
// FrameId fourth = second + offset;
|
||||
//
|
||||
// // Logging convenience.
|
||||
// OSP_DLOG_INFO << "The current frame is " << fourth;
|
||||
class FrameId : public ExpandedValueBase<int64_t, FrameId> {
|
||||
public:
|
||||
// The "null" FrameId constructor. Represents a FrameId field that has not
|
||||
// been set and/or a "not applicable" indicator.
|
||||
constexpr FrameId() : FrameId(std::numeric_limits<int64_t>::min()) {}
|
||||
|
||||
constexpr explicit FrameId(int64_t value) : ExpandedValueBase(value) {}
|
||||
|
||||
// Allow copy construction and assignment.
|
||||
constexpr FrameId(const FrameId&) = default;
|
||||
constexpr FrameId& operator=(const FrameId&) = default;
|
||||
|
||||
// Returns true if this is the special value representing null.
|
||||
constexpr bool is_null() const { return *this == FrameId(); }
|
||||
|
||||
// Distance operator.
|
||||
int64_t operator-(FrameId rhs) const {
|
||||
OSP_CHECK(!is_null());
|
||||
OSP_CHECK(!rhs.is_null());
|
||||
return value_ - rhs.value_;
|
||||
}
|
||||
|
||||
// Operators to compute advancement by incremental amounts.
|
||||
constexpr FrameId operator+(int64_t rhs) const {
|
||||
OSP_CHECK(!is_null());
|
||||
return FrameId(value_ + rhs);
|
||||
}
|
||||
constexpr FrameId operator-(int64_t rhs) const {
|
||||
OSP_CHECK(!is_null());
|
||||
return FrameId(value_ - rhs);
|
||||
}
|
||||
constexpr FrameId& operator+=(int64_t rhs) {
|
||||
OSP_CHECK(!is_null());
|
||||
return (*this = (*this + rhs));
|
||||
}
|
||||
constexpr FrameId& operator-=(int64_t rhs) {
|
||||
OSP_CHECK(!is_null());
|
||||
return (*this = (*this - rhs));
|
||||
}
|
||||
constexpr FrameId& operator++() {
|
||||
OSP_CHECK(!is_null());
|
||||
++value_;
|
||||
return *this;
|
||||
}
|
||||
constexpr FrameId& operator--() {
|
||||
OSP_CHECK(!is_null());
|
||||
--value_;
|
||||
return *this;
|
||||
}
|
||||
constexpr FrameId operator++(int) {
|
||||
OSP_CHECK(!is_null());
|
||||
return FrameId(value_++);
|
||||
}
|
||||
constexpr FrameId operator--(int) {
|
||||
OSP_CHECK(!is_null());
|
||||
return FrameId(value_--);
|
||||
}
|
||||
|
||||
// The identifier for the first frame in a stream.
|
||||
static constexpr FrameId first() { return FrameId(0); }
|
||||
|
||||
// A virtual identifier, representing the frame before the first. There should
|
||||
// never actually be a frame streamed with this identifier. Instead, this is
|
||||
// used in various components to represent a "not yet seen/processed the first
|
||||
// frame" state.
|
||||
//
|
||||
// The name "leader" comes from the terminology used in tape reels, which
|
||||
// refers to the non-data-carrying segment of tape before the recording
|
||||
// begins.
|
||||
static constexpr FrameId leader() { return FrameId(-1); }
|
||||
|
||||
constexpr int64_t value() const { return value_; }
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
friend class ExpandedValueBase<int64_t, FrameId>;
|
||||
friend std::ostream& operator<<(std::ostream& out, const FrameId rhs);
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_FRAME_ID_H_
|
||||
487
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/offer_messages.cc
vendored
Normal file
487
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/offer_messages.cc
vendored
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/offer_messages.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "platform/base/error.h"
|
||||
#include "util/big_endian.h"
|
||||
#include "util/enum_name_table.h"
|
||||
#include "util/json/json_helpers.h"
|
||||
#include "util/json/json_serialization.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/stringprintf.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kSupportedStreams[] = "supportedStreams";
|
||||
constexpr char kAudioSourceType[] = "audio_source";
|
||||
constexpr char kVideoSourceType[] = "video_source";
|
||||
constexpr char kStreamType[] = "type";
|
||||
|
||||
[[nodiscard]] constexpr bool CodecParameterIsValid(VideoCodec codec,
|
||||
std::string_view parameter) {
|
||||
if (parameter.empty()) {
|
||||
return true;
|
||||
}
|
||||
switch (codec) {
|
||||
using enum VideoCodec;
|
||||
case kVp8:
|
||||
return parameter.starts_with("vp08");
|
||||
case kVp9:
|
||||
return parameter.starts_with("vp09");
|
||||
case kAv1:
|
||||
return parameter.starts_with("av01");
|
||||
case kHevc:
|
||||
return parameter.starts_with("hev1");
|
||||
case kH264:
|
||||
return parameter.starts_with("avc1");
|
||||
case kNotSpecified:
|
||||
return false;
|
||||
}
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
|
||||
bool CodecParameterIsValid(AudioCodec codec,
|
||||
const std::string& codec_parameter) {
|
||||
if (codec_parameter.empty()) {
|
||||
return true;
|
||||
}
|
||||
switch (codec) {
|
||||
case AudioCodec::kAac:
|
||||
return codec_parameter.starts_with("mp4a.");
|
||||
|
||||
// Opus doesn't use codec parameters.
|
||||
case AudioCodec::kOpus: // fallthrough
|
||||
case AudioCodec::kNotSpecified:
|
||||
return false;
|
||||
}
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
|
||||
EnumNameTable<CastMode, 2> kCastModeNames{
|
||||
{{"mirroring", CastMode::kMirroring}, {"remoting", CastMode::kRemoting}}};
|
||||
|
||||
bool TryParseRtpPayloadType(const Json::Value& value, RtpPayloadType* out) {
|
||||
int t;
|
||||
if (!json::TryParseInt(value, &t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t t_small = t;
|
||||
if (t_small != t || !IsRtpPayloadType(t_small)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = static_cast<RtpPayloadType>(t_small);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryParseRtpTimebase(const Json::Value& value, int* out) {
|
||||
std::string raw_timebase;
|
||||
if (!json::TryParseString(value, &raw_timebase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The spec demands a leading 1, so this isn't really a fraction.
|
||||
const auto fraction = SimpleFraction::FromString(raw_timebase);
|
||||
if (fraction.is_error() || !fraction.value().is_positive() ||
|
||||
fraction.value().numerator() != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = fraction.value().denominator();
|
||||
return true;
|
||||
}
|
||||
|
||||
// For a hex byte, the conversion is 4 bits to 1 character, e.g.
|
||||
// 0b11110001 becomes F1, so 1 byte is two characters.
|
||||
constexpr int kHexDigitsPerByte = 2;
|
||||
constexpr int kAesBytesSize = 16;
|
||||
constexpr int kAesStringLength = kAesBytesSize * kHexDigitsPerByte;
|
||||
bool TryParseAesHexBytes(const Json::Value& value,
|
||||
std::array<uint8_t, kAesBytesSize>* out) {
|
||||
std::string hex_string;
|
||||
if (!json::TryParseString(value, &hex_string)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr int kHexDigitsPerScanField = 16;
|
||||
constexpr int kNumScanFields = kAesStringLength / kHexDigitsPerScanField;
|
||||
uint64_t quads[kNumScanFields];
|
||||
int chars_scanned;
|
||||
if (hex_string.size() == kAesStringLength &&
|
||||
sscanf(hex_string.c_str(), "%16" SCNx64 "%16" SCNx64 "%n", &quads[0],
|
||||
&quads[1], &chars_scanned) == kNumScanFields &&
|
||||
chars_scanned == kAesStringLength &&
|
||||
std::none_of(hex_string.begin(), hex_string.end(),
|
||||
[](char c) { return std::isspace(c); })) {
|
||||
WriteBigEndian(quads[0], out->data());
|
||||
WriteBigEndian(quads[1], out->data() + 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view ToString(Stream::Type type) {
|
||||
switch (type) {
|
||||
case Stream::Type::kAudioSource:
|
||||
return kAudioSourceType;
|
||||
case Stream::Type::kVideoSource:
|
||||
return kVideoSourceType;
|
||||
default: {
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TryParseResolutions(const Json::Value& value,
|
||||
std::vector<Resolution>* out) {
|
||||
out->clear();
|
||||
|
||||
// Some legacy senders don't provide resolutions, so just return empty.
|
||||
if (!value.isArray() || value.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
|
||||
auto resolution = Resolution::TryParse(value[i]);
|
||||
if (resolution.is_error()) {
|
||||
out->clear();
|
||||
return false;
|
||||
}
|
||||
out->push_back(std::move(resolution.value()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ErrorOr<Stream> Stream::TryParse(const Json::Value& value, Stream::Type type) {
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError, "Stream is not a JSON object");
|
||||
}
|
||||
|
||||
Stream out;
|
||||
out.type = type;
|
||||
|
||||
if (!json::TryParseInt(value["index"], &out.index) ||
|
||||
!json::TryParseUint(value["ssrc"], &out.ssrc) ||
|
||||
!TryParseRtpPayloadType(value["rtpPayloadType"], &out.rtp_payload_type) ||
|
||||
!TryParseRtpTimebase(value["timeBase"], &out.rtp_timebase)) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Offer stream has missing or invalid mandatory field");
|
||||
}
|
||||
|
||||
if (!json::TryParseInt(value["channels"], &out.channels)) {
|
||||
out.channels = out.type == Stream::Type::kAudioSource
|
||||
? kDefaultNumAudioChannels
|
||||
: kDefaultNumVideoChannels;
|
||||
} else if (out.channels <= 0) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid channel count");
|
||||
}
|
||||
|
||||
if (!TryParseAesHexBytes(value["aesKey"], &out.aes_key) ||
|
||||
!TryParseAesHexBytes(value["aesIvMask"], &out.aes_iv_mask)) {
|
||||
return Error(Error::Code::kUnencryptedOffer,
|
||||
"Offer stream must have both a valid aesKey and aesIvMask");
|
||||
}
|
||||
if (out.rtp_timebase <
|
||||
std::min(kDefaultAudioMinSampleRate, kRtpVideoTimebase) ||
|
||||
out.rtp_timebase > kRtpVideoTimebase) {
|
||||
return Error(Error::Code::kJsonParseError, "rtp_timebase (sample rate)");
|
||||
}
|
||||
|
||||
out.target_delay = kDefaultTargetPlayoutDelay;
|
||||
int target_delay;
|
||||
if (json::TryParseInt(value["targetDelay"], &target_delay)) {
|
||||
auto d = std::chrono::milliseconds(target_delay);
|
||||
if (kMinTargetPlayoutDelay <= d && d <= kMaxTargetPlayoutDelay) {
|
||||
out.target_delay = d;
|
||||
}
|
||||
}
|
||||
|
||||
json::TryParseBool(value["receiverRtcpEventLog"],
|
||||
&out.receiver_rtcp_event_log);
|
||||
int dscp_value;
|
||||
if (json::TryParseInt(value["receiverRtcpDscp"], &dscp_value)) {
|
||||
// DSCP values are clamped to [0, 63].
|
||||
if (dscp_value < 0 || dscp_value > 63) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"receiverRtcpDscp (invalid DSCP value)");
|
||||
}
|
||||
out.receiver_rtcp_dscp = dscp_value;
|
||||
}
|
||||
|
||||
json::TryParseStringArray(value["rtpExtensions"], &out.rtp_extensions);
|
||||
json::TryParseString(value["codecParameter"], &out.codec_parameter);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Json::Value Stream::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
|
||||
Json::Value root;
|
||||
root["index"] = index;
|
||||
root["type"] = std::string(ToString(type));
|
||||
root["channels"] = channels;
|
||||
root["rtpPayloadType"] = static_cast<int>(rtp_payload_type);
|
||||
// rtpProfile is technically required by the spec, although it is always set
|
||||
// to cast. We set it here to be compliant with all spec implementers.
|
||||
root["rtpProfile"] = "cast";
|
||||
static_assert(sizeof(ssrc) <= sizeof(Json::UInt),
|
||||
"this code assumes Ssrc fits in a Json::UInt");
|
||||
root["ssrc"] = static_cast<Json::UInt>(ssrc);
|
||||
root["targetDelay"] = static_cast<int>(target_delay.count());
|
||||
root["aesKey"] = HexEncode(aes_key.data(), aes_key.size());
|
||||
root["aesIvMask"] = HexEncode(aes_iv_mask.data(), aes_iv_mask.size());
|
||||
root["receiverRtcpEventLog"] = receiver_rtcp_event_log;
|
||||
if (receiver_rtcp_dscp.has_value()) {
|
||||
root["receiverRtcpDscp"] = receiver_rtcp_dscp.value();
|
||||
}
|
||||
root["timeBase"] = "1/" + std::to_string(rtp_timebase);
|
||||
root["codecParameter"] = codec_parameter;
|
||||
if (!rtp_extensions.empty()) {
|
||||
root["rtpExtensions"] = json::PrimitiveVectorToJson(rtp_extensions);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
bool Stream::IsValid() const {
|
||||
return channels >= 1 && index >= 0 && target_delay.count() > 0 &&
|
||||
target_delay.count() <= std::numeric_limits<int>::max() &&
|
||||
rtp_timebase >= 1;
|
||||
}
|
||||
|
||||
ErrorOr<AudioStream> AudioStream::TryParse(const Json::Value& value) {
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Audio stream is not a JSON object");
|
||||
}
|
||||
|
||||
auto stream_or_error = Stream::TryParse(value, Stream::Type::kAudioSource);
|
||||
if (stream_or_error.is_error()) {
|
||||
return stream_or_error.error();
|
||||
}
|
||||
|
||||
AudioStream out;
|
||||
out.stream = std::move(stream_or_error.value());
|
||||
|
||||
std::string codec_name;
|
||||
if (!json::TryParseInt(value["bitRate"], &out.bit_rate) || out.bit_rate < 0 ||
|
||||
!json::TryParseString(value[kCodecName], &codec_name)) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid audio stream field");
|
||||
}
|
||||
ErrorOr<AudioCodec> codec = StringToAudioCodec(codec_name);
|
||||
if (!codec) {
|
||||
return Error(Error::Code::kUnknownCodec,
|
||||
"Codec is not known, can't use stream");
|
||||
}
|
||||
out.codec = codec.value();
|
||||
if (!CodecParameterIsValid(codec.value(), out.stream.codec_parameter)) {
|
||||
return Error(Error::Code::kInvalidCodecParameter,
|
||||
StringFormat("Invalid audio codec parameter ({} for codec {})",
|
||||
out.stream.codec_parameter.c_str(),
|
||||
CodecToString(codec.value())));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Json::Value AudioStream::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
|
||||
Json::Value out = stream.ToJson();
|
||||
out[kCodecName] = CodecToString(codec);
|
||||
out["bitRate"] = bit_rate;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool AudioStream::IsValid() const {
|
||||
return bit_rate >= 0 && stream.IsValid();
|
||||
}
|
||||
|
||||
ErrorOr<VideoStream> VideoStream::TryParse(const Json::Value& value) {
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Video stream is not a JSON object");
|
||||
}
|
||||
|
||||
auto stream_or_error = Stream::TryParse(value, Stream::Type::kVideoSource);
|
||||
if (stream_or_error.is_error()) {
|
||||
return stream_or_error.error();
|
||||
}
|
||||
|
||||
VideoStream out;
|
||||
out.stream = std::move(stream_or_error.value());
|
||||
|
||||
std::string codec_name;
|
||||
if (!json::TryParseString(value[kCodecName], &codec_name)) {
|
||||
return Error(Error::Code::kJsonParseError, "Video stream missing codec");
|
||||
}
|
||||
ErrorOr<VideoCodec> codec = StringToVideoCodec(codec_name);
|
||||
if (!codec) {
|
||||
return Error(Error::Code::kUnknownCodec,
|
||||
"Codec is not known, can't use stream");
|
||||
}
|
||||
out.codec = codec.value();
|
||||
if (!CodecParameterIsValid(codec.value(), out.stream.codec_parameter)) {
|
||||
return Error(Error::Code::kInvalidCodecParameter,
|
||||
StringFormat("Invalid video codec parameter ({} for codec {})",
|
||||
out.stream.codec_parameter.c_str(),
|
||||
CodecToString(codec.value())));
|
||||
}
|
||||
|
||||
out.max_frame_rate = SimpleFraction{kDefaultMaxFrameRate, 1};
|
||||
std::string raw_max_frame_rate;
|
||||
if (json::TryParseString(value["maxFrameRate"], &raw_max_frame_rate)) {
|
||||
auto parsed = SimpleFraction::FromString(raw_max_frame_rate);
|
||||
if (parsed.is_value() && parsed.value().is_positive()) {
|
||||
out.max_frame_rate = parsed.value();
|
||||
}
|
||||
}
|
||||
|
||||
TryParseResolutions(value["resolutions"], &out.resolutions);
|
||||
json::TryParseString(value["profile"], &out.profile);
|
||||
json::TryParseString(value["protection"], &out.protection);
|
||||
json::TryParseString(value["level"], &out.level);
|
||||
json::TryParseString(value["errorRecoveryMode"], &out.error_recovery_mode);
|
||||
if (!json::TryParseInt(value["maxBitRate"], &out.max_bit_rate)) {
|
||||
out.max_bit_rate = 4 << 20;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Json::Value VideoStream::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
|
||||
Json::Value out = stream.ToJson();
|
||||
out["codecName"] = CodecToString(codec);
|
||||
out["maxFrameRate"] = max_frame_rate.ToString();
|
||||
out["maxBitRate"] = max_bit_rate;
|
||||
out["protection"] = protection;
|
||||
out["profile"] = profile;
|
||||
out["level"] = level;
|
||||
out["errorRecoveryMode"] = error_recovery_mode;
|
||||
|
||||
Json::Value rs;
|
||||
for (auto resolution : resolutions) {
|
||||
rs.append(resolution.ToJson());
|
||||
}
|
||||
out["resolutions"] = std::move(rs);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool VideoStream::IsValid() const {
|
||||
return max_bit_rate > 0 && max_frame_rate.is_positive();
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<Offer> Offer::TryParse(const Json::Value& root) {
|
||||
if (!root.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError, "null offer");
|
||||
}
|
||||
const ErrorOr<CastMode> cast_mode =
|
||||
GetEnum(kCastModeNames, root["castMode"].asString());
|
||||
Json::Value supported_streams = root[kSupportedStreams];
|
||||
if (!supported_streams.isArray()) {
|
||||
return Error(Error::Code::kJsonParseError, "supported streams in offer");
|
||||
}
|
||||
|
||||
std::vector<AudioStream> audio_streams;
|
||||
std::vector<VideoStream> video_streams;
|
||||
|
||||
using Dscp = std::optional<int>;
|
||||
std::optional<Dscp> receiver_rtcp_dscp;
|
||||
for (Json::ArrayIndex i = 0; i < supported_streams.size(); ++i) {
|
||||
const Json::Value& fields = supported_streams[i];
|
||||
std::string type;
|
||||
if (!json::TryParseString(fields[kStreamType], &type)) {
|
||||
return Error(Error::Code::kJsonParseError, "Missing stream type");
|
||||
}
|
||||
|
||||
Error error = Error::None();
|
||||
if (type == kAudioSourceType) {
|
||||
auto stream_or_error = AudioStream::TryParse(fields);
|
||||
if (stream_or_error.is_value()) {
|
||||
auto stream = std::move(stream_or_error.value());
|
||||
if (!receiver_rtcp_dscp) {
|
||||
receiver_rtcp_dscp.emplace(stream.stream.receiver_rtcp_dscp);
|
||||
} else if (stream.stream.receiver_rtcp_dscp != *receiver_rtcp_dscp) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Mixed DSCP values in offer");
|
||||
}
|
||||
audio_streams.push_back(std::move(stream));
|
||||
} else {
|
||||
error = stream_or_error.error();
|
||||
}
|
||||
} else if (type == kVideoSourceType) {
|
||||
auto stream_or_error = VideoStream::TryParse(fields);
|
||||
if (stream_or_error.is_value()) {
|
||||
auto stream = std::move(stream_or_error.value());
|
||||
if (!receiver_rtcp_dscp) {
|
||||
receiver_rtcp_dscp.emplace(stream.stream.receiver_rtcp_dscp);
|
||||
} else if (stream.stream.receiver_rtcp_dscp != *receiver_rtcp_dscp) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Mixed DSCP values in offer");
|
||||
}
|
||||
video_streams.push_back(std::move(stream));
|
||||
} else {
|
||||
error = stream_or_error.error();
|
||||
}
|
||||
}
|
||||
|
||||
if (!error.ok()) {
|
||||
if (error.code() == Error::Code::kUnknownCodec) {
|
||||
OSP_VLOG << "Dropping audio stream due to unknown codec: " << error;
|
||||
continue;
|
||||
} else {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Offer{cast_mode.value(CastMode::kMirroring), std::move(audio_streams),
|
||||
std::move(video_streams)};
|
||||
}
|
||||
|
||||
Json::Value Offer::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
Json::Value root;
|
||||
root["castMode"] = GetEnumName(kCastModeNames, cast_mode).value();
|
||||
Json::Value streams;
|
||||
for (auto& stream : audio_streams) {
|
||||
streams.append(stream.ToJson());
|
||||
}
|
||||
|
||||
for (auto& stream : video_streams) {
|
||||
streams.append(stream.ToJson());
|
||||
}
|
||||
|
||||
root[kSupportedStreams] = std::move(streams);
|
||||
return root;
|
||||
}
|
||||
|
||||
bool Offer::IsValid() const {
|
||||
return std::ranges::all_of(
|
||||
audio_streams, [](const AudioStream& a) { return a.IsValid(); }) &&
|
||||
std::ranges::all_of(video_streams,
|
||||
[](const VideoStream& v) { return v.IsValid(); });
|
||||
}
|
||||
} // namespace openscreen::cast
|
||||
115
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/offer_messages.h
vendored
Normal file
115
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/offer_messages.h
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright 2019 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_PUBLIC_OFFER_MESSAGES_H_
|
||||
#define CAST_STREAMING_PUBLIC_OFFER_MESSAGES_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/impl/rtp_defines.h"
|
||||
#include "cast/streaming/message_fields.h"
|
||||
#include "cast/streaming/public/session_config.h"
|
||||
#include "cast/streaming/resolution.h"
|
||||
#include "json/value.h"
|
||||
#include "platform/base/error.h"
|
||||
#include "util/simple_fraction.h"
|
||||
|
||||
// This file contains the implementation of the Cast V2 Mirroring Control
|
||||
// Protocol offer object definition.
|
||||
namespace openscreen::cast {
|
||||
|
||||
// If the target delay provided by the sender is not bounded by
|
||||
// [kMinTargetDelay, kMaxTargetDelay], it will be set to
|
||||
// kDefaultTargetPlayoutDelay.
|
||||
inline constexpr auto kMinTargetPlayoutDelay = std::chrono::milliseconds(0);
|
||||
inline constexpr auto kMaxTargetPlayoutDelay = std::chrono::milliseconds(5000);
|
||||
|
||||
// If the sender provides an invalid maximum frame rate, it ill
|
||||
// be set to kDefaultMaxFrameRate.
|
||||
inline constexpr int kDefaultMaxFrameRate = 30;
|
||||
|
||||
inline constexpr int kDefaultNumVideoChannels = 1;
|
||||
inline constexpr int kDefaultNumAudioChannels = 2;
|
||||
|
||||
// A stream, as detailed by the CastV2 protocol spec, is a segment of an
|
||||
// offer message specifically representing a configuration object for
|
||||
// a codec and its related fields, such as maximum bit rate, time base,
|
||||
// and other fields.
|
||||
// Composed classes include AudioStream and VideoStream, which contain
|
||||
// fields specific to audio and video respectively.
|
||||
struct Stream {
|
||||
enum class Type : uint8_t { kAudioSource, kVideoSource };
|
||||
|
||||
static ErrorOr<Stream> TryParse(const Json::Value& root, Stream::Type type);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
int index = 0;
|
||||
Type type = {};
|
||||
|
||||
// Default channel count is 1, e.g. for video.
|
||||
int channels = 0;
|
||||
RtpPayloadType rtp_payload_type = {};
|
||||
Ssrc ssrc = {};
|
||||
std::chrono::milliseconds target_delay = {};
|
||||
|
||||
// AES Key and IV mask format is very strict: a 32 digit hex string that
|
||||
// must be converted to a 16 digit byte array.
|
||||
std::array<uint8_t, 16> aes_key = {};
|
||||
std::array<uint8_t, 16> aes_iv_mask = {};
|
||||
|
||||
// The event logs are generally recommended for use in gathering statistics
|
||||
// for the sender session.
|
||||
bool receiver_rtcp_event_log = true;
|
||||
std::optional<int> receiver_rtcp_dscp;
|
||||
int rtp_timebase = 0;
|
||||
|
||||
// The codec parameter field honors the format laid out in RFC 6381:
|
||||
// https://datatracker.ietf.org/doc/html/rfc6381.
|
||||
std::string codec_parameter;
|
||||
|
||||
std::vector<std::string> rtp_extensions;
|
||||
};
|
||||
|
||||
struct AudioStream {
|
||||
static ErrorOr<AudioStream> TryParse(const Json::Value& root);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
Stream stream;
|
||||
AudioCodec codec = AudioCodec::kNotSpecified;
|
||||
int bit_rate = 0;
|
||||
};
|
||||
|
||||
struct VideoStream {
|
||||
static ErrorOr<VideoStream> TryParse(const Json::Value& root);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
Stream stream;
|
||||
VideoCodec codec = VideoCodec::kNotSpecified;
|
||||
SimpleFraction max_frame_rate;
|
||||
int max_bit_rate = 0;
|
||||
std::string protection;
|
||||
std::string profile;
|
||||
std::string level;
|
||||
std::vector<Resolution> resolutions;
|
||||
std::string error_recovery_mode;
|
||||
};
|
||||
|
||||
struct Offer {
|
||||
static ErrorOr<Offer> TryParse(const Json::Value& root);
|
||||
Json::Value ToJson() const;
|
||||
bool IsValid() const;
|
||||
|
||||
CastMode cast_mode = CastMode::kMirroring;
|
||||
std::vector<AudioStream> audio_streams;
|
||||
std::vector<VideoStream> video_streams;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_OFFER_MESSAGES_H_
|
||||
300
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/receiver_message.cc
vendored
Normal file
300
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/receiver_message.cc
vendored
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/receiver_message.h"
|
||||
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "cast/streaming/message_fields.h"
|
||||
#include "json/reader.h"
|
||||
#include "json/writer.h"
|
||||
#include "platform/base/error.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/enum_name_table.h"
|
||||
#include "util/json/json_helpers.h"
|
||||
#include "util/json/json_serialization.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/stringprintf.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
EnumNameTable<ReceiverMessage::Type, 4> kMessageTypeNames{
|
||||
{{kMessageTypeAnswer, ReceiverMessage::Type::kAnswer},
|
||||
{"CAPABILITIES_RESPONSE", ReceiverMessage::Type::kCapabilitiesResponse},
|
||||
{"RPC", ReceiverMessage::Type::kRpc},
|
||||
{"INPUT", ReceiverMessage::Type::kInput}}};
|
||||
|
||||
EnumNameTable<MediaCapability, 10> kMediaCapabilityNames{
|
||||
{{"audio", MediaCapability::kAudio},
|
||||
{"aac", MediaCapability::kAac},
|
||||
{"opus", MediaCapability::kOpus},
|
||||
{"video", MediaCapability::kVideo},
|
||||
{"4k", MediaCapability::k4k},
|
||||
{"h264", MediaCapability::kH264},
|
||||
{"vp8", MediaCapability::kVp8},
|
||||
{"vp9", MediaCapability::kVp9},
|
||||
{"hevc", MediaCapability::kHevc},
|
||||
{"av1", MediaCapability::kAv1}}};
|
||||
|
||||
ReceiverMessage::Type GetMessageType(const Json::Value& root) {
|
||||
std::string type;
|
||||
if (!json::TryParseString(root[kMessageType], &type)) {
|
||||
return ReceiverMessage::Type::kUnknown;
|
||||
}
|
||||
string_util::AsciiStrToUpper(type);
|
||||
|
||||
ErrorOr<ReceiverMessage::Type> parsed = GetEnum(kMessageTypeNames, type);
|
||||
return parsed.value(ReceiverMessage::Type::kUnknown);
|
||||
}
|
||||
|
||||
bool TryParseCapability(const Json::Value& value, MediaCapability* out) {
|
||||
std::string c;
|
||||
if (!json::TryParseString(value, &c)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ErrorOr<MediaCapability> capability = GetEnum(kMediaCapabilityNames, c);
|
||||
if (capability.is_error()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = capability.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ReceiverError::ReceiverError(int code, std::string_view description)
|
||||
: code(code), description(description) {
|
||||
if (code >= kOpenscreenErrorOffset) {
|
||||
openscreen_code = static_cast<Error::Code>(code - kOpenscreenErrorOffset);
|
||||
}
|
||||
}
|
||||
|
||||
ReceiverError::ReceiverError(Error::Code code, std::string_view description)
|
||||
: code(static_cast<int>(code) + kOpenscreenErrorOffset),
|
||||
openscreen_code(code),
|
||||
description(description) {}
|
||||
|
||||
ReceiverError::ReceiverError(const Error& error)
|
||||
: code(static_cast<int>(error.code()) + kOpenscreenErrorOffset),
|
||||
openscreen_code(error.code()),
|
||||
description(error.message()) {}
|
||||
|
||||
ReceiverError::ReceiverError(const ReceiverError&) = default;
|
||||
ReceiverError::ReceiverError(ReceiverError&&) noexcept = default;
|
||||
ReceiverError& ReceiverError::operator=(const ReceiverError&) = default;
|
||||
ReceiverError& ReceiverError::operator=(ReceiverError&&) = default;
|
||||
ReceiverError::~ReceiverError() = default;
|
||||
|
||||
// static
|
||||
ErrorOr<ReceiverError> ReceiverError::Parse(const Json::Value& value) {
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kParameterInvalid,
|
||||
"Empty JSON in receiver error parsing");
|
||||
}
|
||||
|
||||
int code;
|
||||
std::string description;
|
||||
if (!json::TryParseInt(value[kErrorCode], &code) ||
|
||||
!json::TryParseString(value[kErrorDescription], &description)) {
|
||||
return Error::Code::kJsonParseError;
|
||||
}
|
||||
|
||||
return ReceiverError(code, description);
|
||||
}
|
||||
|
||||
Json::Value ReceiverError::ToJson() const {
|
||||
Json::Value root;
|
||||
root[kErrorCode] = openscreen_code ? static_cast<int>(*openscreen_code) +
|
||||
kOpenscreenErrorOffset
|
||||
: code;
|
||||
root[kErrorDescription] = description;
|
||||
return root;
|
||||
}
|
||||
|
||||
Error ReceiverError::ToError() const {
|
||||
if (openscreen_code) {
|
||||
return Error(*openscreen_code, description);
|
||||
}
|
||||
|
||||
std::string full_description = StringFormat("Error code: {}, description: {}",
|
||||
code, description.c_str());
|
||||
return Error(Error::Code::kUnknownError, std::move(full_description));
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<ReceiverCapability> ReceiverCapability::Parse(
|
||||
const Json::Value& value) {
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kParameterInvalid,
|
||||
"Empty JSON in capabilities parsing");
|
||||
}
|
||||
|
||||
int remoting_version;
|
||||
if (!json::TryParseInt(value["remoting"], &remoting_version)) {
|
||||
remoting_version = ReceiverCapability::kRemotingVersionUnknown;
|
||||
}
|
||||
|
||||
std::vector<MediaCapability> capabilities;
|
||||
if (!json::TryParseArray<MediaCapability>(
|
||||
value["mediaCaps"], TryParseCapability, &capabilities)) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Failed to parse media capabilities");
|
||||
}
|
||||
|
||||
return ReceiverCapability{remoting_version, std::move(capabilities)};
|
||||
}
|
||||
|
||||
Json::Value ReceiverCapability::ToJson() const {
|
||||
Json::Value root;
|
||||
root["remoting"] = remoting_version;
|
||||
Json::Value capabilities(Json::ValueType::arrayValue);
|
||||
for (const auto& capability : media_capabilities) {
|
||||
capabilities.append(GetEnumName(kMediaCapabilityNames, capability).value());
|
||||
}
|
||||
root["mediaCaps"] = std::move(capabilities);
|
||||
return root;
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<ReceiverMessage> ReceiverMessage::Parse(const Json::Value& value) {
|
||||
ReceiverMessage message;
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid message body");
|
||||
}
|
||||
|
||||
std::string result;
|
||||
if (!json::TryParseString(value[kResult], &result)) {
|
||||
result = kResultError;
|
||||
}
|
||||
|
||||
message.type = GetMessageType(value);
|
||||
message.valid =
|
||||
(result == kResultOk || message.type == ReceiverMessage::Type::kRpc ||
|
||||
message.type == ReceiverMessage::Type::kInput);
|
||||
|
||||
if (message.type != ReceiverMessage::Type::kRpc &&
|
||||
message.type != ReceiverMessage::Type::kInput) {
|
||||
if (!json::TryParseInt(value[kSequenceNumber],
|
||||
&(message.sequence_number))) {
|
||||
message.sequence_number = -1;
|
||||
}
|
||||
|
||||
// Sequence numbers must be non-negative.
|
||||
if (message.sequence_number < 0) {
|
||||
message.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!message.valid) {
|
||||
ErrorOr<ReceiverError> error =
|
||||
ReceiverError::Parse(value[kErrorMessageBody]);
|
||||
if (error.is_value()) {
|
||||
message.body = std::move(error.value());
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case Type::kAnswer: {
|
||||
auto answer_or_error =
|
||||
openscreen::cast::Answer::TryParse(value[kAnswerMessageBody]);
|
||||
if (answer_or_error.is_value()) {
|
||||
message.body = std::move(answer_or_error.value());
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kCapabilitiesResponse: {
|
||||
ErrorOr<ReceiverCapability> capability =
|
||||
ReceiverCapability::Parse(value[kCapabilitiesMessageBody]);
|
||||
if (capability.is_value()) {
|
||||
message.body = std::move(capability.value());
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kRpc: {
|
||||
std::string encoded_rpc;
|
||||
std::vector<uint8_t> rpc;
|
||||
if (json::TryParseString(value[kRpcMessageBody], &encoded_rpc) &&
|
||||
base64::Decode(encoded_rpc, &rpc)) {
|
||||
message.body = std::move(rpc);
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kInput: {
|
||||
std::string encoded_input;
|
||||
std::vector<uint8_t> input;
|
||||
if (json::TryParseString(value[kInputMessageBody], &encoded_input) &&
|
||||
base64::Decode(encoded_input, &input)) {
|
||||
message.body = std::move(input);
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
ErrorOr<Json::Value> ReceiverMessage::ToJson() const {
|
||||
OSP_CHECK(type != ReceiverMessage::Type::kUnknown)
|
||||
<< "Trying to send an unknown message is a developer error";
|
||||
|
||||
Json::Value root;
|
||||
root[kMessageType] = GetEnumName(kMessageTypeNames, type).value();
|
||||
if (sequence_number >= 0) {
|
||||
root[kSequenceNumber] = sequence_number;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case ReceiverMessage::Type::kAnswer:
|
||||
if (valid) {
|
||||
root[kResult] = kResultOk;
|
||||
root[kAnswerMessageBody] = std::get<Answer>(body).ToJson();
|
||||
} else {
|
||||
root[kResult] = kResultError;
|
||||
root[kErrorMessageBody] = std::get<ReceiverError>(body).ToJson();
|
||||
}
|
||||
break;
|
||||
|
||||
case ReceiverMessage::Type::kCapabilitiesResponse:
|
||||
if (valid) {
|
||||
root[kResult] = kResultOk;
|
||||
root[kCapabilitiesMessageBody] =
|
||||
std::get<ReceiverCapability>(body).ToJson();
|
||||
} else {
|
||||
root[kResult] = kResultError;
|
||||
root[kErrorMessageBody] = std::get<ReceiverError>(body).ToJson();
|
||||
}
|
||||
break;
|
||||
|
||||
// NOTE: RPC messages do NOT have a result field.
|
||||
case ReceiverMessage::Type::kRpc:
|
||||
root[kRpcMessageBody] =
|
||||
base64::Encode(std::get<std::vector<uint8_t>>(body));
|
||||
break;
|
||||
|
||||
case ReceiverMessage::Type::kInput:
|
||||
root[kInputMessageBody] =
|
||||
base64::Encode(std::get<std::vector<uint8_t>>(body));
|
||||
break;
|
||||
|
||||
default:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
117
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/receiver_message.h
vendored
Normal file
117
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/receiver_message.h
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2020 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_PUBLIC_RECEIVER_MESSAGE_H_
|
||||
#define CAST_STREAMING_PUBLIC_RECEIVER_MESSAGE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/public/answer_messages.h"
|
||||
#include "json/value.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
enum class MediaCapability {
|
||||
kAudio,
|
||||
kAac,
|
||||
kOpus,
|
||||
kVideo,
|
||||
k4k,
|
||||
kH264,
|
||||
kVp8,
|
||||
kVp9,
|
||||
kHevc,
|
||||
kAv1
|
||||
};
|
||||
|
||||
struct ReceiverCapability {
|
||||
static constexpr int kRemotingVersionUnknown = -1;
|
||||
|
||||
Json::Value ToJson() const;
|
||||
static ErrorOr<ReceiverCapability> Parse(const Json::Value& value);
|
||||
|
||||
// The remoting version that the receiver uses.
|
||||
int remoting_version = kRemotingVersionUnknown;
|
||||
|
||||
// Set of capabilities (e.g., ac3, 4k, hevc, vp9, dolby_vision, etc.).
|
||||
std::vector<MediaCapability> media_capabilities;
|
||||
};
|
||||
|
||||
// To avoid collisions with legacy error values, all Open Screen receiver errors
|
||||
// are offset.
|
||||
struct ReceiverError {
|
||||
explicit ReceiverError(int code, std::string_view description = "");
|
||||
explicit ReceiverError(Error::Code code, std::string_view description = "");
|
||||
explicit ReceiverError(const Error& error);
|
||||
|
||||
ReceiverError(const ReceiverError&);
|
||||
ReceiverError(ReceiverError&&) noexcept;
|
||||
ReceiverError& operator=(const ReceiverError&);
|
||||
ReceiverError& operator=(ReceiverError&&);
|
||||
~ReceiverError();
|
||||
|
||||
Json::Value ToJson() const;
|
||||
static ErrorOr<ReceiverError> Parse(const Json::Value& value);
|
||||
Error ToError() const;
|
||||
|
||||
// All Open Screen errors are offset by a fixed value to avoid overlapping
|
||||
// with legacy values.
|
||||
static constexpr int kOpenscreenErrorOffset = 10000;
|
||||
|
||||
// Raw error code.
|
||||
int32_t code = -1;
|
||||
|
||||
// Parsed openscreen::Error code. May be nullopt if not a match.
|
||||
std::optional<Error::Code> openscreen_code;
|
||||
|
||||
// Error description.
|
||||
std::string description;
|
||||
};
|
||||
|
||||
struct ReceiverMessage {
|
||||
public:
|
||||
// Receiver response message type.
|
||||
enum class Type {
|
||||
// Unknown message type.
|
||||
kUnknown,
|
||||
|
||||
// Response to OFFER message.
|
||||
kAnswer,
|
||||
|
||||
// Response to GET_CAPABILITIES message.
|
||||
kCapabilitiesResponse,
|
||||
|
||||
// Rpc binary messages. The payload is base64-encoded.
|
||||
kRpc,
|
||||
|
||||
// Input-related binary messages. The payload is base64-encoded.
|
||||
kInput,
|
||||
};
|
||||
|
||||
static ErrorOr<ReceiverMessage> Parse(const Json::Value& value);
|
||||
ErrorOr<Json::Value> ToJson() const;
|
||||
|
||||
Type type = Type::kUnknown;
|
||||
|
||||
int32_t sequence_number = -1;
|
||||
|
||||
bool valid = false;
|
||||
|
||||
std::variant<std::monostate,
|
||||
Answer,
|
||||
std::vector<uint8_t>, // Binary-encoded protobuf message.
|
||||
ReceiverCapability,
|
||||
ReceiverError>
|
||||
body;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_RECEIVER_MESSAGE_H_
|
||||
12
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/sender.cc
vendored
Normal file
12
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/sender.cc
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/sender.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
Sender::Observer::~Observer() = default;
|
||||
Sender::~Sender() = default;
|
||||
|
||||
} // namespace openscreen::cast
|
||||
173
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/sender.h
vendored
Normal file
173
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/sender.h
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright 2020 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_PUBLIC_SENDER_H_
|
||||
#define CAST_STREAMING_PUBLIC_SENDER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "cast/streaming/public/encoded_frame.h"
|
||||
#include "cast/streaming/public/frame_id.h"
|
||||
#include "cast/streaming/public/session_config.h"
|
||||
#include "cast/streaming/rtp_time.h"
|
||||
#include "cast/streaming/ssrc.h"
|
||||
#include "platform/api/time.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// The Cast Streaming Sender, a peer corresponding to some Cast Streaming
|
||||
// Receiver at the other end of a network link.
|
||||
//
|
||||
// The Sender is the peer responsible for enqueuing EncodedFrames for streaming,
|
||||
// guaranteeing their delivery to a Receiver, and handling feedback events from
|
||||
// a Receiver. Some feedback events are used for managing the Sender's internal
|
||||
// queue of in-flight frames, requesting network packet re-transmits, etc.;
|
||||
// while others are exposed via the Sender's public interface. For example,
|
||||
// sometimes the Receiver signals that it needs a a key frame to resolve a
|
||||
// picture loss condition, and the modules upstream of the Sender (e.g., where
|
||||
// encoding happens) should call NeedsKeyFrame() to check for, and handle that.
|
||||
//
|
||||
// There are usually one or two Senders in a streaming session, one for audio
|
||||
// and one for video. Both senders work with the same SenderPacketRouter
|
||||
// instance to schedule their transmission of packets, and provide the necessary
|
||||
// metrics for estimating bandwidth utilization and availability.
|
||||
//
|
||||
// It is the responsibility of upstream code modules to handle congestion
|
||||
// control. With respect to this Sender, that means the media encoding bit rate
|
||||
// should be throttled based on network bandwidth availability. This Sender does
|
||||
// not do any throttling, only flow-control. In other words, this Sender can
|
||||
// only manage its in-flight queue of frames, and if that queue grows too large,
|
||||
// it will eventually reject further enqueuing.
|
||||
//
|
||||
// General usage: A client should check the in-flight media duration frequently
|
||||
// to decide when to pause encoding, to avoid wasting system resources on
|
||||
// encoding frames that will likely be rejected by the Sender. The client should
|
||||
// also frequently call NeedsKeyFrame() and, when this returns true, direct its
|
||||
// encoder to produce a key frame soon. Finally, when using EnqueueFrame(), an
|
||||
// EncodedFrame struct should be prepared with its frame_id field set to
|
||||
// whatever GetNextFrameId() returns. Please see method comments for
|
||||
// more-detailed usage info.
|
||||
class Sender {
|
||||
public:
|
||||
// Interface for receiving notifications about events of possible interest.
|
||||
class Observer {
|
||||
public:
|
||||
// Called when a frame was canceled, which may occur in the following cases:
|
||||
// - The Receiver acknowledged successful receipt of the frame.
|
||||
// - The Receiver decided to skip over the frame (e.g. it was too late).
|
||||
// - The Sender decided to skip the frame (e.g. OnFrameCanceled() called).
|
||||
//
|
||||
// Note: Frame cancellations may occur out-of-order.
|
||||
virtual void OnFrameCanceled(FrameId frame_id) = 0;
|
||||
|
||||
// Called when a Receiver begins reporting picture loss, and there is no key
|
||||
// frame currently enqueued in the Sender. The application should enqueue a
|
||||
// key frame as soon as possible.
|
||||
//
|
||||
// This acts as a "push" notification, which is useful for immediately
|
||||
// waking up an application that may be waiting for the next capture tick.
|
||||
// For "pull" state checking inside a continuous encoding loop, see
|
||||
// NeedsKeyFrame().
|
||||
virtual void OnPictureLost() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Observer();
|
||||
};
|
||||
|
||||
// Result codes for EnqueueFrame().
|
||||
enum EnqueueFrameResult {
|
||||
// The frame has been queued for sending.
|
||||
OK,
|
||||
|
||||
// The frame's payload was too large.
|
||||
PAYLOAD_TOO_LARGE,
|
||||
|
||||
// The span of FrameIds is too large.
|
||||
REACHED_ID_SPAN_LIMIT,
|
||||
|
||||
// Too-large a media duration is in-flight.
|
||||
MAX_DURATION_IN_FLIGHT,
|
||||
};
|
||||
|
||||
virtual ~Sender();
|
||||
|
||||
// The session configuration for this sender. The configuration is generated
|
||||
// from the offer/answer exchange, and includes critical information like the
|
||||
// RTP timebase, SSRCs for sending and receiving, and the AES configuration.
|
||||
virtual const SessionConfig& config() const = 0;
|
||||
|
||||
// Sets an observer for receiving notifications. Call with nullptr to stop
|
||||
// observing.
|
||||
virtual void SetObserver(Observer* observer) = 0;
|
||||
|
||||
// Returns the number of frames currently in-flight. This is only meant to be
|
||||
// informative. Clients should use GetInFlightMediaDuration() to make
|
||||
// throttling decisions.
|
||||
virtual size_t GetInFlightFrameCount() const = 0;
|
||||
|
||||
// Returns the total media duration of the frames currently in-flight,
|
||||
// assuming the next not-yet-enqueued frame will have the given RTP timestamp.
|
||||
// For a better user experience, the result should be compared to
|
||||
// GetMaxInFlightMediaDuration(), and media encoding should be throttled down
|
||||
// before additional EnqueueFrame() calls would cause this to reach the
|
||||
// current maximum limit.
|
||||
virtual Clock::duration GetInFlightMediaDuration(
|
||||
RtpTimeTicks next_frame_rtp_timestamp) const = 0;
|
||||
|
||||
// Return the maximum acceptable in-flight media duration, given the current
|
||||
// target playout delay setting and end-to-end network/system conditions.
|
||||
virtual Clock::duration GetMaxInFlightMediaDuration() const = 0;
|
||||
|
||||
// Returns true if the Receiver requires a key frame. Note that this will
|
||||
// return true until a key frame is accepted by EnqueueFrame(). Thus, when
|
||||
// encoding is pipelined, care should be taken to instruct the encoder to
|
||||
// produce just ONE forced key frame.
|
||||
//
|
||||
// This acts as a stateful "pull" check, which is useful for an encoder loop
|
||||
// to poll right before processing the next image. For "push" notifications
|
||||
// to wake up an idle application, see Observer::OnPictureLost().
|
||||
virtual bool NeedsKeyFrame() const = 0;
|
||||
|
||||
// Returns the next FrameId, the one after the frame enqueued by the last call
|
||||
// to EnqueueFrame(). Note that the next call to EnqueueFrame() assumes this
|
||||
// frame ID be used.
|
||||
virtual FrameId GetNextFrameId() const = 0;
|
||||
|
||||
// Get the current round trip time, defined as the total time between when the
|
||||
// sender report is sent and the receiver report is received. This value is
|
||||
// updated with each receiver report using a weighted moving average of 1/8
|
||||
// for the new value and 7/8 for the previous value. Will be set to
|
||||
// Clock::duration::zero() if no reports have been received yet.
|
||||
// TODO(crbug.com/498036656): move to a more modern approach for estimating
|
||||
// bandwidth.
|
||||
virtual Clock::duration GetCurrentRoundTripTime() const = 0;
|
||||
|
||||
// Enqueues the given `frame` for sending as soon as possible. Returns OK if
|
||||
// the frame is accepted, and some time later Observer::OnFrameCanceled() will
|
||||
// be called once it is no longer in-flight.
|
||||
//
|
||||
// All fields of the `frame` must be set to valid values: the `frame_id` must
|
||||
// be the same as GetNextFrameId(); both the `rtp_timestamp` and
|
||||
// `reference_time` fields must be monotonically increasing relative to the
|
||||
// prior frame; and the frame's `data` pointer must be set.
|
||||
[[nodiscard]] virtual EnqueueFrameResult EnqueueFrame(
|
||||
const EncodedFrame& frame) = 0;
|
||||
|
||||
// Causes all pending operations to discard data when they are processed
|
||||
// later. This will notify observers by invoking OnFrameCanceled() for each
|
||||
// canceled frame.
|
||||
virtual void CancelInFlightData() = 0;
|
||||
|
||||
// May be called by the consumer to report that a frame has been dropped. This
|
||||
// is used to report drop statistics to the sender's statistics collector.
|
||||
virtual void ReportFrameDropEvent(FrameId frame_id,
|
||||
RtpTimeTicks rtp_timestamp,
|
||||
Clock::time_point drop_time) = 0;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_SENDER_H_
|
||||
54
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/session_config.cc
vendored
Normal file
54
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/session_config.cc
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/session_config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsNonZero(uint8_t byte) {
|
||||
return byte > 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SessionConfig::SessionConfig(Ssrc sender_ssrc,
|
||||
Ssrc receiver_ssrc,
|
||||
int rtp_timebase,
|
||||
int channels,
|
||||
std::chrono::milliseconds target_playout_delay,
|
||||
std::array<uint8_t, 16> aes_secret_key,
|
||||
std::array<uint8_t, 16> aes_iv_mask,
|
||||
bool is_pli_enabled,
|
||||
StreamType stream_type,
|
||||
bool are_receiver_event_logs_enabled)
|
||||
: sender_ssrc(sender_ssrc),
|
||||
receiver_ssrc(receiver_ssrc),
|
||||
rtp_timebase(rtp_timebase),
|
||||
channels(channels),
|
||||
target_playout_delay(target_playout_delay),
|
||||
aes_secret_key(std::move(aes_secret_key)),
|
||||
aes_iv_mask(std::move(aes_iv_mask)),
|
||||
is_pli_enabled(is_pli_enabled),
|
||||
stream_type(stream_type),
|
||||
are_receiver_event_logs_enabled(are_receiver_event_logs_enabled) {}
|
||||
|
||||
SessionConfig::SessionConfig(const SessionConfig& other) = default;
|
||||
SessionConfig::SessionConfig(SessionConfig&& other) noexcept = default;
|
||||
SessionConfig& SessionConfig::operator=(const SessionConfig& other) = default;
|
||||
SessionConfig& SessionConfig::operator=(SessionConfig&& other) noexcept =
|
||||
default;
|
||||
SessionConfig::~SessionConfig() = default;
|
||||
|
||||
bool SessionConfig::IsValid() const {
|
||||
return sender_ssrc > 0 && receiver_ssrc > 0 && rtp_timebase > 0 &&
|
||||
channels > 0 &&
|
||||
std::any_of(aes_secret_key.begin(), aes_secret_key.end(), IsNonZero) &&
|
||||
std::any_of(aes_iv_mask.begin(), aes_iv_mask.end(), IsNonZero);
|
||||
}
|
||||
} // namespace openscreen::cast
|
||||
71
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/session_config.h
vendored
Normal file
71
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/session_config.h
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2019 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_PUBLIC_SESSION_CONFIG_H_
|
||||
#define CAST_STREAMING_PUBLIC_SESSION_CONFIG_H_
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "cast/streaming/ssrc.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Common streaming configuration, established from the OFFER/ANSWER exchange,
|
||||
// that the Sender and Receiver are both assuming.
|
||||
struct SessionConfig final {
|
||||
SessionConfig(Ssrc sender_ssrc,
|
||||
Ssrc receiver_ssrc,
|
||||
int rtp_timebase,
|
||||
int channels,
|
||||
std::chrono::milliseconds target_playout_delay,
|
||||
std::array<uint8_t, 16> aes_secret_key,
|
||||
std::array<uint8_t, 16> aes_iv_mask,
|
||||
bool is_pli_enabled = false,
|
||||
StreamType stream_type = StreamType::kUnknown,
|
||||
bool are_receiver_event_logs_enabled = true);
|
||||
SessionConfig(const SessionConfig& other);
|
||||
SessionConfig(SessionConfig&& other) noexcept;
|
||||
SessionConfig& operator=(const SessionConfig& other);
|
||||
SessionConfig& operator=(SessionConfig&& other) noexcept;
|
||||
~SessionConfig();
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
// The sender and receiver's SSRC identifiers. Note: SSRC identifiers
|
||||
// are defined as unsigned 32 bit integers here:
|
||||
// https://tools.ietf.org/html/rfc5576#page-5
|
||||
Ssrc sender_ssrc = 0;
|
||||
Ssrc receiver_ssrc = 0;
|
||||
|
||||
// RTP timebase: The number of RTP units advanced per second. For audio,
|
||||
// this is the sampling rate. For video, this is 90 kHz by convention.
|
||||
int rtp_timebase = 90000;
|
||||
|
||||
// Number of channels. Must be 1 for video, for audio typically 2.
|
||||
int channels = 1;
|
||||
|
||||
// Initial target playout delay.
|
||||
std::chrono::milliseconds target_playout_delay;
|
||||
|
||||
// The AES-128 crypto key and initialization vector.
|
||||
std::array<uint8_t, 16> aes_secret_key{};
|
||||
std::array<uint8_t, 16> aes_iv_mask{};
|
||||
|
||||
// Whether picture loss indication (PLI) should be used for this session.
|
||||
bool is_pli_enabled = false;
|
||||
|
||||
// The type (e.g. audio or video) of the stream.
|
||||
StreamType stream_type = StreamType::kUnknown;
|
||||
|
||||
// Whether RTCP event logs from the Receiver are enabled. These are used for
|
||||
// generating statistics. It is recommended that this generally be true.
|
||||
bool are_receiver_event_logs_enabled = true;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_SESSION_CONFIG_H_
|
||||
388
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/session_messenger.cc
vendored
Normal file
388
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/session_messenger.cc
vendored
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/session_messenger.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "cast/common/public/message_port.h"
|
||||
#include "cast/streaming/message_fields.h"
|
||||
#include "platform/base/trivial_clock_traits.h"
|
||||
#include "util/json/json_helpers.h"
|
||||
#include "util/json/json_serialization.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
// Default timeout to receive a reply message in response to a request message
|
||||
// sent by us.
|
||||
constexpr std::chrono::milliseconds kReplyTimeout(4000);
|
||||
|
||||
// Special character indicating message was sent to all receivers or senders.
|
||||
constexpr char kAnyDestination[] = "*";
|
||||
|
||||
void ReplyIfTimedOut(
|
||||
int sequence_number,
|
||||
std::vector<std::pair<int, SenderSessionMessenger::ReplyCallback>>*
|
||||
replies) {
|
||||
for (auto it = replies->begin(); it != replies->end(); ++it) {
|
||||
if (it->first == sequence_number) {
|
||||
OSP_VLOG << "Reply was an error with due to timeout for sequence number: "
|
||||
<< sequence_number;
|
||||
|
||||
// We erase before handling the callback, since it may invalidate the
|
||||
// replies vector.
|
||||
SenderSessionMessenger::ReplyCallback callback = std::move(it->second);
|
||||
replies->erase(it);
|
||||
callback(Error(Error::Code::kMessageTimeout,
|
||||
string_util::StrCat({"message timed out; max delay of ",
|
||||
ToString(kReplyTimeout)})));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SessionMessenger::SessionMessenger(MessagePort& message_port,
|
||||
std::string source_id,
|
||||
ErrorCallback cb)
|
||||
: message_port_(message_port),
|
||||
source_id_(source_id),
|
||||
error_callback_(std::move(cb)) {
|
||||
OSP_CHECK(!source_id_.empty());
|
||||
message_port_->SetClient(*this);
|
||||
}
|
||||
|
||||
SessionMessenger::~SessionMessenger() {
|
||||
message_port_->ResetClient();
|
||||
}
|
||||
|
||||
Error SessionMessenger::SendMessage(const std::string& destination_id,
|
||||
const std::string& namespace_,
|
||||
const Json::Value& message_root) {
|
||||
OSP_CHECK(namespace_ == kCastRemotingNamespace ||
|
||||
namespace_ == kCastWebrtcNamespace);
|
||||
auto body_or_error = json::Stringify(message_root);
|
||||
if (body_or_error.is_error()) {
|
||||
return std::move(body_or_error.error());
|
||||
}
|
||||
OSP_VLOG << "Sending message: DESTINATION[" << destination_id
|
||||
<< "], NAMESPACE[" << namespace_ << "], BODY:\n"
|
||||
<< body_or_error.value();
|
||||
message_port_->PostMessage(destination_id, namespace_, body_or_error.value());
|
||||
return Error::None();
|
||||
}
|
||||
|
||||
void SessionMessenger::ReportError(const Error& error) {
|
||||
error_callback_(error);
|
||||
}
|
||||
|
||||
SenderSessionMessenger::SenderSessionMessenger(MessagePort& message_port,
|
||||
std::string source_id,
|
||||
std::string receiver_id,
|
||||
ErrorCallback cb,
|
||||
TaskRunner& task_runner)
|
||||
: SessionMessenger(message_port, std::move(source_id), std::move(cb)),
|
||||
task_runner_(task_runner),
|
||||
receiver_id_(std::move(receiver_id)) {}
|
||||
|
||||
void SenderSessionMessenger::SetHandler(ReceiverMessage::Type type,
|
||||
ReplyCallback cb) {
|
||||
// Currently the only handlers allowed are for RPC and INPUT messages.
|
||||
if (type == ReceiverMessage::Type::kRpc) {
|
||||
rpc_callback_ = std::move(cb);
|
||||
} else if (type == ReceiverMessage::Type::kInput) {
|
||||
input_callback_ = std::move(cb);
|
||||
} else {
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void SenderSessionMessenger::ResetHandler(ReceiverMessage::Type type) {
|
||||
if (type == ReceiverMessage::Type::kRpc) {
|
||||
rpc_callback_ = {};
|
||||
} else if (type == ReceiverMessage::Type::kInput) {
|
||||
input_callback_ = {};
|
||||
} else {
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
Error SenderSessionMessenger::SendOutboundMessage(SenderMessage message) {
|
||||
const auto namespace_ = (message.type == SenderMessage::Type::kRpc ||
|
||||
message.type == SenderMessage::Type::kInput)
|
||||
? kCastRemotingNamespace
|
||||
: kCastWebrtcNamespace;
|
||||
|
||||
ErrorOr<Json::Value> jsonified = message.ToJson();
|
||||
OSP_CHECK(jsonified.is_value()) << "Tried to send an invalid message";
|
||||
return SessionMessenger::SendMessage(receiver_id_, namespace_,
|
||||
jsonified.value());
|
||||
}
|
||||
|
||||
Error SenderSessionMessenger::SendRpcMessage(ByteView message) {
|
||||
return SendOutboundMessage(SenderMessage{
|
||||
openscreen::cast::SenderMessage::Type::kRpc,
|
||||
-1 /* sequence_number, unused by RPC messages */, true /* valid */,
|
||||
std::vector<uint8_t>(message.begin(), message.end())});
|
||||
}
|
||||
|
||||
Error SenderSessionMessenger::SendInputMessage(ByteView message) {
|
||||
return SendOutboundMessage(SenderMessage{
|
||||
openscreen::cast::SenderMessage::Type::kInput,
|
||||
-1 /* sequence_number, unused by INPUT messages */, true /* valid */,
|
||||
std::vector<uint8_t>(message.begin(), message.end())});
|
||||
}
|
||||
|
||||
Error SenderSessionMessenger::SendRequest(SenderMessage message,
|
||||
ReceiverMessage::Type reply_type,
|
||||
ReplyCallback cb) {
|
||||
// RPC and INPUT messages are not meant to be request/reply.
|
||||
OSP_CHECK(reply_type != ReceiverMessage::Type::kRpc);
|
||||
OSP_CHECK(reply_type != ReceiverMessage::Type::kInput);
|
||||
|
||||
if (!cb) {
|
||||
return Error(Error::Code::kParameterInvalid,
|
||||
"Must provide a reply callback");
|
||||
}
|
||||
const Error error = SendOutboundMessage(message);
|
||||
if (!error.ok()) {
|
||||
return error;
|
||||
}
|
||||
|
||||
OSP_DCHECK(awaiting_replies_.find(message.sequence_number) ==
|
||||
awaiting_replies_.end());
|
||||
awaiting_replies_.emplace_back(message.sequence_number, std::move(cb));
|
||||
task_runner_->PostTaskWithDelay(
|
||||
[self = weak_factory_.GetWeakPtr(), seq_num = message.sequence_number] {
|
||||
if (self) {
|
||||
ReplyIfTimedOut(seq_num, &self->awaiting_replies_);
|
||||
}
|
||||
},
|
||||
kReplyTimeout);
|
||||
|
||||
return Error::None();
|
||||
}
|
||||
|
||||
void SenderSessionMessenger::OnMessage(const std::string& source_id,
|
||||
const std::string& message_namespace,
|
||||
const std::string& message) {
|
||||
if (source_id != receiver_id_ && source_id != kAnyDestination) {
|
||||
OSP_DLOG_WARN << "Received message from unknown/incorrect Cast Receiver "
|
||||
<< source_id << ". Currently connected to " << receiver_id_;
|
||||
return;
|
||||
}
|
||||
|
||||
if (message_namespace != kCastWebrtcNamespace &&
|
||||
message_namespace != kCastRemotingNamespace) {
|
||||
OSP_DLOG_WARN << "Received message from unknown namespace: "
|
||||
<< message_namespace << ". Message was " << message;
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorOr<Json::Value> message_body = json::Parse(message);
|
||||
if (!message_body || !message_body.value().isObject()) {
|
||||
ReportError(message_body.error());
|
||||
OSP_DLOG_WARN << "Received an invalid message: " << message;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the message is valid JSON and we don't understand it, there are two
|
||||
// options: (1) it's an unknown type, or (2) the receiver filled out the
|
||||
// message incorrectly. In the first case we can drop it, it's likely just
|
||||
// unsupported. In the second case we might need it, so worth warning the
|
||||
// client.
|
||||
ErrorOr<ReceiverMessage> receiver_message =
|
||||
ReceiverMessage::Parse(message_body.value());
|
||||
if (receiver_message.is_error()) {
|
||||
ReportError(receiver_message.error());
|
||||
OSP_DLOG_WARN << "Received an invalid receiver message: "
|
||||
<< receiver_message.error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (receiver_message.value().type == ReceiverMessage::Type::kRpc) {
|
||||
if (rpc_callback_) {
|
||||
rpc_callback_(receiver_message.value());
|
||||
} else {
|
||||
OSP_DLOG_INFO << "Received RPC message but no callback, dropping";
|
||||
}
|
||||
} else if (receiver_message.value().type == ReceiverMessage::Type::kInput) {
|
||||
if (input_callback_) {
|
||||
input_callback_(receiver_message.value());
|
||||
} else {
|
||||
OSP_DLOG_INFO << "Received INPUT message but no callback, dropping";
|
||||
}
|
||||
} else {
|
||||
const int sequence_number = receiver_message.value().sequence_number;
|
||||
auto it = awaiting_replies_.find(sequence_number);
|
||||
if (it == awaiting_replies_.end()) {
|
||||
OSP_DLOG_WARN << "Received a reply I wasn't waiting for: "
|
||||
<< sequence_number;
|
||||
return;
|
||||
}
|
||||
|
||||
ReplyCallback callback = std::move(it->second);
|
||||
awaiting_replies_.erase(it);
|
||||
callback(std::move(receiver_message.value()));
|
||||
}
|
||||
}
|
||||
|
||||
void SenderSessionMessenger::OnError(const Error& error) {
|
||||
OSP_DLOG_WARN << "Received an error in the session messenger: " << error;
|
||||
ReportError(error);
|
||||
}
|
||||
|
||||
ReceiverSessionMessenger::ReceiverSessionMessenger(MessagePort& message_port,
|
||||
std::string source_id,
|
||||
ErrorCallback cb)
|
||||
: SessionMessenger(message_port, std::move(source_id), std::move(cb)) {}
|
||||
|
||||
void ReceiverSessionMessenger::SetHandler(SenderMessage::Type type,
|
||||
RequestCallback cb) {
|
||||
OSP_DCHECK(callbacks_.find(type) == callbacks_.end());
|
||||
callbacks_.emplace_back(type, std::move(cb));
|
||||
}
|
||||
|
||||
void ReceiverSessionMessenger::ResetHandler(SenderMessage::Type type) {
|
||||
callbacks_.erase_key(type);
|
||||
}
|
||||
|
||||
Error ReceiverSessionMessenger::SendRpcMessage(const std::string& source_id,
|
||||
ByteView message) {
|
||||
return SendMessage(
|
||||
source_id,
|
||||
ReceiverMessage{ReceiverMessage::Type::kRpc, -1 /* sequence_number */,
|
||||
true /* valid */,
|
||||
std::vector<uint8_t>(message.begin(), message.end())});
|
||||
}
|
||||
|
||||
Error ReceiverSessionMessenger::SendInputMessage(const std::string& source_id,
|
||||
ByteView message) {
|
||||
return SendMessage(
|
||||
source_id,
|
||||
ReceiverMessage{ReceiverMessage::Type::kInput, -1 /* sequence_number */,
|
||||
true /* valid */,
|
||||
std::vector<uint8_t>(message.begin(), message.end())});
|
||||
}
|
||||
|
||||
Error ReceiverSessionMessenger::SendMessage(const std::string& source_id,
|
||||
ReceiverMessage message) {
|
||||
if (source_id.empty()) {
|
||||
return Error(Error::Code::kInitializationFailure,
|
||||
"Cannot send a message without a current source ID.");
|
||||
}
|
||||
|
||||
const auto namespace_ = (message.type == ReceiverMessage::Type::kRpc ||
|
||||
message.type == ReceiverMessage::Type::kInput)
|
||||
? kCastRemotingNamespace
|
||||
: kCastWebrtcNamespace;
|
||||
|
||||
ErrorOr<Json::Value> message_json = message.ToJson();
|
||||
OSP_CHECK(message_json.is_value()) << "Tried to send an invalid message";
|
||||
return SessionMessenger::SendMessage(source_id, namespace_,
|
||||
message_json.value());
|
||||
}
|
||||
|
||||
void ReceiverSessionMessenger::SetCustomMessageHandler(
|
||||
std::string_view message_namespace,
|
||||
CustomMessageCallback cb) {
|
||||
auto it = std::find_if(custom_message_handlers_.begin(),
|
||||
custom_message_handlers_.end(),
|
||||
[&message_namespace](const auto& pair) {
|
||||
return pair.first == message_namespace;
|
||||
});
|
||||
|
||||
if (!cb) {
|
||||
if (it != custom_message_handlers_.end()) {
|
||||
custom_message_handlers_.erase(it);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (it != custom_message_handlers_.end()) {
|
||||
OSP_LOG_ERROR << "Handler already exists for namespace: "
|
||||
<< message_namespace;
|
||||
return;
|
||||
} else {
|
||||
custom_message_handlers_.emplace_back(std::string(message_namespace),
|
||||
std::move(cb));
|
||||
}
|
||||
}
|
||||
|
||||
Error ReceiverSessionMessenger::SendMessage(std::string_view destination_id,
|
||||
std::string_view message_namespace,
|
||||
std::string_view message) {
|
||||
message_port().PostMessage(std::string(destination_id),
|
||||
std::string(message_namespace),
|
||||
std::string(message));
|
||||
return Error::None();
|
||||
}
|
||||
|
||||
void ReceiverSessionMessenger::OnMessage(const std::string& source_id,
|
||||
const std::string& message_namespace,
|
||||
const std::string& message) {
|
||||
if (message_namespace != kCastWebrtcNamespace &&
|
||||
message_namespace != kCastRemotingNamespace) {
|
||||
auto it = std::find_if(custom_message_handlers_.begin(),
|
||||
custom_message_handlers_.end(),
|
||||
[&message_namespace](const auto& pair) {
|
||||
return pair.first == message_namespace;
|
||||
});
|
||||
if (it != custom_message_handlers_.end()) {
|
||||
it->second(source_id, message_namespace, message);
|
||||
return;
|
||||
}
|
||||
OSP_DLOG_WARN << "Received message from unknown namespace: "
|
||||
<< message_namespace;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the message is bad JSON, the sender is in a funky state so we
|
||||
// report an error.
|
||||
ErrorOr<Json::Value> message_body = json::Parse(message);
|
||||
if (message_body.is_error() || !message_body.value().isObject()) {
|
||||
ReportError(message_body.error());
|
||||
return;
|
||||
}
|
||||
|
||||
// If the message is valid JSON and we don't understand it, there are two
|
||||
// options: (1) it's an unknown type, or (2) the sender filled out the message
|
||||
// incorrectly. In the first case we can drop it, it's likely just
|
||||
// unsupported. In the second case we might need it, so worth warning the
|
||||
// client.
|
||||
ErrorOr<SenderMessage> sender_message =
|
||||
SenderMessage::Parse(message_body.value());
|
||||
if (sender_message.is_error()) {
|
||||
ReportError(sender_message.error());
|
||||
OSP_DLOG_WARN << "Received an invalid sender message: "
|
||||
<< sender_message.error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender_message.value().type == SenderMessage::Type::kOffer ||
|
||||
sender_message.value().type == SenderMessage::Type::kGetCapabilities) {
|
||||
OSP_VLOG << "Received Message:\n" << message;
|
||||
}
|
||||
|
||||
auto it = callbacks_.find(sender_message.value().type);
|
||||
if (it == callbacks_.end()) {
|
||||
OSP_DLOG_INFO << "Received message without a callback, dropping";
|
||||
return;
|
||||
}
|
||||
it->second(source_id, sender_message.value());
|
||||
}
|
||||
|
||||
void ReceiverSessionMessenger::OnError(const Error& error) {
|
||||
OSP_DLOG_WARN << "Received an error in the session messenger: " << error;
|
||||
ReportError(error);
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
168
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/session_messenger.h
vendored
Normal file
168
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/session_messenger.h
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// Copyright 2020 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_PUBLIC_SESSION_MESSENGER_H_
|
||||
#define CAST_STREAMING_PUBLIC_SESSION_MESSENGER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/common/public/message_port.h"
|
||||
#include "cast/streaming/public/answer_messages.h"
|
||||
#include "cast/streaming/public/offer_messages.h"
|
||||
#include "cast/streaming/public/receiver_message.h"
|
||||
#include "cast/streaming/sender_message.h"
|
||||
#include "json/value.h"
|
||||
#include "platform/api/task_runner.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/flat_map.h"
|
||||
#include "util/raw_ref.h"
|
||||
#include "util/weak_ptr.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// A message port interface designed specifically for use by the Receiver
|
||||
// and Sender session classes.
|
||||
class SessionMessenger : public MessagePort::Client {
|
||||
public:
|
||||
using ErrorCallback = std::function<void(Error)>;
|
||||
|
||||
SessionMessenger(MessagePort& message_port,
|
||||
std::string source_id,
|
||||
ErrorCallback cb);
|
||||
~SessionMessenger() override;
|
||||
|
||||
MessagePort& message_port() { return *message_port_; }
|
||||
|
||||
protected:
|
||||
// Barebones message sending method shared by both children.
|
||||
[[nodiscard]] Error SendMessage(const std::string& destination_id,
|
||||
const std::string& namespace_,
|
||||
const Json::Value& message_root);
|
||||
|
||||
// Used to report errors in subclasses.
|
||||
void ReportError(const Error& error);
|
||||
|
||||
const std::string& source_id() override { return source_id_; }
|
||||
|
||||
private:
|
||||
const raw_ref<MessagePort> message_port_;
|
||||
const std::string source_id_;
|
||||
ErrorCallback error_callback_;
|
||||
};
|
||||
|
||||
// Message port interface designed to handle sending messages to and
|
||||
// from a receiver. When possible, errors receiving messages are reported
|
||||
// to the ReplyCallback passed to SendRequest(), otherwise errors are
|
||||
// reported to the ErrorCallback passed in the constructor.
|
||||
class SenderSessionMessenger final : public SessionMessenger {
|
||||
public:
|
||||
using ReplyCallback = std::function<void(ErrorOr<ReceiverMessage>)>;
|
||||
|
||||
SenderSessionMessenger(MessagePort& message_port,
|
||||
std::string source_id,
|
||||
std::string receiver_id,
|
||||
ErrorCallback cb,
|
||||
TaskRunner& task_runner);
|
||||
|
||||
// Set receiver message handler. Note that this should only be
|
||||
// applied for messages that don't have sequence numbers, like RPC
|
||||
// and status messages.
|
||||
void SetHandler(ReceiverMessage::Type type, ReplyCallback cb);
|
||||
void ResetHandler(ReceiverMessage::Type type);
|
||||
|
||||
// Send a message that doesn't require a reply.
|
||||
[[nodiscard]] Error SendOutboundMessage(SenderMessage message);
|
||||
|
||||
// Convenience method for sending a valid RPC message.
|
||||
[[nodiscard]] Error SendRpcMessage(ByteView message);
|
||||
|
||||
// Convenience method for sending a valid INPUT message.
|
||||
[[nodiscard]] Error SendInputMessage(ByteView message);
|
||||
|
||||
// Send a request (with optional reply callback).
|
||||
[[nodiscard]] Error SendRequest(SenderMessage message,
|
||||
ReceiverMessage::Type reply_type,
|
||||
ReplyCallback cb);
|
||||
|
||||
// MessagePort::Client overrides
|
||||
void OnMessage(const std::string& source_id,
|
||||
const std::string& message_namespace,
|
||||
const std::string& message) override;
|
||||
void OnError(const Error& error) override;
|
||||
|
||||
private:
|
||||
const raw_ref<TaskRunner> task_runner_;
|
||||
|
||||
// This messenger should only be connected to one receiver, so `receiver_id_`
|
||||
// should not change.
|
||||
const std::string receiver_id_;
|
||||
|
||||
// We keep a list here of replies we are expecting--if the reply is
|
||||
// received for this sequence number, we call its respective callback,
|
||||
// otherwise it is called after an internally specified timeout.
|
||||
FlatMap<int, ReplyCallback> awaiting_replies_;
|
||||
|
||||
// Currently we can only set a handler for RPC messages, so no need for
|
||||
// a flatmap here.
|
||||
ReplyCallback rpc_callback_;
|
||||
ReplyCallback input_callback_;
|
||||
|
||||
WeakPtrFactory<SenderSessionMessenger> weak_factory_{this};
|
||||
};
|
||||
|
||||
// Message port interface designed for messaging to and from a sender.
|
||||
class ReceiverSessionMessenger final : public SessionMessenger {
|
||||
public:
|
||||
using RequestCallback =
|
||||
std::function<void(const std::string&, SenderMessage)>;
|
||||
ReceiverSessionMessenger(MessagePort& message_port,
|
||||
std::string source_id,
|
||||
ErrorCallback cb);
|
||||
|
||||
// Set sender message handler.
|
||||
void SetHandler(SenderMessage::Type type, RequestCallback cb);
|
||||
void ResetHandler(SenderMessage::Type type);
|
||||
|
||||
// Convenience method for sending a valid RPC message.
|
||||
[[nodiscard]] Error SendRpcMessage(const std::string& source_id,
|
||||
ByteView message);
|
||||
|
||||
// Convenience method for sending a valid INPUT message.
|
||||
[[nodiscard]] Error SendInputMessage(const std::string& source_id,
|
||||
ByteView message);
|
||||
|
||||
// Send a JSON message.
|
||||
[[nodiscard]] Error SendMessage(const std::string& source_id,
|
||||
ReceiverMessage message);
|
||||
|
||||
// Send a raw string message to a custom namespace.
|
||||
[[nodiscard]] Error SendMessage(std::string_view destination_id,
|
||||
std::string_view message_namespace,
|
||||
std::string_view message);
|
||||
|
||||
using CustomMessageCallback =
|
||||
std::function<void(const std::string& /* source_id */,
|
||||
const std::string& /* message_namespace */,
|
||||
const std::string& /* message */)>;
|
||||
void SetCustomMessageHandler(std::string_view message_namespace,
|
||||
CustomMessageCallback cb);
|
||||
|
||||
// MessagePort::Client overrides
|
||||
void OnMessage(const std::string& source_id,
|
||||
const std::string& message_namespace,
|
||||
const std::string& message) override;
|
||||
void OnError(const Error& error) override;
|
||||
|
||||
private:
|
||||
FlatMap<SenderMessage::Type, RequestCallback> callbacks_;
|
||||
std::vector<std::pair<std::string, CustomMessageCallback>>
|
||||
custom_message_handlers_;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_SESSION_MESSENGER_H_
|
||||
183
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/statistics.cc
vendored
Normal file
183
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/statistics.cc
vendored
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/public/statistics.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include "util/enum_name_table.h"
|
||||
#include "util/json/json_helpers.h"
|
||||
#include "util/json/json_serialization.h"
|
||||
#include "util/stringprintf.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Type>
|
||||
Json::Value ToJson(const Type& t) {
|
||||
return t.ToJson();
|
||||
}
|
||||
|
||||
template <>
|
||||
Json::Value ToJson(const double& t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
template <typename T, typename Type>
|
||||
Json::Value ArrayToJson(
|
||||
const std::array<T, static_cast<size_t>(Type::kNumTypes)>& list,
|
||||
const EnumNameTable<Type, static_cast<size_t>(Type::kNumTypes)>& names) {
|
||||
Json::Value out;
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
ErrorOr<const char*> name = GetEnumName(names, static_cast<Type>(i));
|
||||
OSP_CHECK(name);
|
||||
out[name.value()] = ToJson(list[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// External linkage for unit test
|
||||
extern const EnumNameTable<StatisticType,
|
||||
static_cast<size_t>(StatisticType::kNumTypes)>
|
||||
kStatisticTypeNames = {
|
||||
{{"EnqueueFps", StatisticType::kEnqueueFps},
|
||||
{"AvgCaptureLatencyMs", StatisticType::kAvgCaptureLatencyMs},
|
||||
{"AvgEncodeTimeMs", StatisticType::kAvgEncodeTimeMs},
|
||||
{"AvgQueueingLatencyMs", StatisticType::kAvgQueueingLatencyMs},
|
||||
{"AvgNetworkLatencyMs", StatisticType::kAvgNetworkLatencyMs},
|
||||
{"AvgPacketLatencyMs", StatisticType::kAvgPacketLatencyMs},
|
||||
{"AvgFrameLatencyMs", StatisticType::kAvgFrameLatencyMs},
|
||||
{"AvgEndToEndLatencyMs", StatisticType::kAvgEndToEndLatencyMs},
|
||||
{"EncodeRateKbps", StatisticType::kEncodeRateKbps},
|
||||
{"PacketTransmissionRateKbps",
|
||||
StatisticType::kPacketTransmissionRateKbps},
|
||||
{"TimeSinceLastReceiverResponseMs",
|
||||
StatisticType::kTimeSinceLastReceiverResponseMs},
|
||||
{"NumFramesCaptured", StatisticType::kNumFramesCaptured},
|
||||
{"NumFramesDroppedByEncoder",
|
||||
StatisticType::kNumFramesDroppedByEncoder},
|
||||
{"NumLateFrames", StatisticType::kNumLateFrames},
|
||||
{"NumPacketsSent", StatisticType::kNumPacketsSent},
|
||||
{"NumPacketsReceived", StatisticType::kNumPacketsReceived},
|
||||
{"FirstEventTimeMs", StatisticType::kFirstEventTimeMs},
|
||||
{"LastEventTimeMs", StatisticType::kLastEventTimeMs}}};
|
||||
|
||||
// External linkage for unit test
|
||||
extern const EnumNameTable<HistogramType,
|
||||
static_cast<size_t>(HistogramType::kNumTypes)>
|
||||
kHistogramTypeNames = {
|
||||
{{"CaptureLatencyMs", HistogramType::kCaptureLatencyMs},
|
||||
{"EncodeTimeMs", HistogramType::kEncodeTimeMs},
|
||||
{"QueueingLatencyMs", HistogramType::kQueueingLatencyMs},
|
||||
{"NetworkLatencyMs", HistogramType::kNetworkLatencyMs},
|
||||
{"PacketLatencyMs", HistogramType::kPacketLatencyMs},
|
||||
{"EndToEndLatencyMs", HistogramType::kEndToEndLatencyMs},
|
||||
{"FrameLatenessMs", HistogramType::kFrameLatenessMs}}};
|
||||
|
||||
SimpleHistogram::SimpleHistogram() = default;
|
||||
SimpleHistogram::SimpleHistogram(int64_t min, int64_t max, int64_t width)
|
||||
|
||||
: min(min), max(max), width(width), buckets((max - min) / width + 2) {
|
||||
OSP_CHECK_GT(buckets.size(), 2u);
|
||||
OSP_CHECK_EQ(0, (max - min) % width);
|
||||
}
|
||||
|
||||
SimpleHistogram::SimpleHistogram(const SimpleHistogram&) = default;
|
||||
SimpleHistogram::SimpleHistogram(SimpleHistogram&&) noexcept = default;
|
||||
SimpleHistogram& SimpleHistogram::operator=(const SimpleHistogram&) = default;
|
||||
SimpleHistogram& SimpleHistogram::operator=(SimpleHistogram&&) = default;
|
||||
SimpleHistogram::~SimpleHistogram() = default;
|
||||
|
||||
bool SimpleHistogram::operator==(const SimpleHistogram& other) const {
|
||||
return min == other.min && max == other.max && width == other.width &&
|
||||
buckets == other.buckets;
|
||||
}
|
||||
|
||||
void SimpleHistogram::Add(int64_t sample) {
|
||||
if (sample < min) {
|
||||
++buckets.front();
|
||||
} else if (sample >= max) {
|
||||
++buckets.back();
|
||||
} else {
|
||||
size_t index = 1 + (sample - min) / width;
|
||||
OSP_CHECK_LT(index, buckets.size());
|
||||
++buckets[index];
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleHistogram::Reset() {
|
||||
buckets.assign(buckets.size(), 0);
|
||||
}
|
||||
|
||||
Json::Value SimpleHistogram::ToJson() const {
|
||||
// Nest the bucket values in an array instead of a dictionary, so we sort
|
||||
// numerically instead of alphabetically.
|
||||
Json::Value out(Json::ValueType::arrayValue);
|
||||
for (size_t i = 0; i < buckets.size(); ++i) {
|
||||
if (buckets[i] != 0) {
|
||||
Json::Value entry;
|
||||
entry[GetBucketName(i)] = buckets[i];
|
||||
out.append(entry);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string SimpleHistogram::ToString() const {
|
||||
return json::Stringify(ToJson()).value();
|
||||
}
|
||||
|
||||
SimpleHistogram::SimpleHistogram(int64_t min,
|
||||
int64_t max,
|
||||
int64_t width,
|
||||
std::vector<int> buckets)
|
||||
: SimpleHistogram(min, max, width) {
|
||||
this->buckets = std::move(buckets);
|
||||
}
|
||||
|
||||
std::string SimpleHistogram::GetBucketName(size_t index) const {
|
||||
if (index == 0) {
|
||||
return "<" + std::to_string(min);
|
||||
}
|
||||
|
||||
if (index == buckets.size() - 1) {
|
||||
return ">=" + std::to_string(max);
|
||||
}
|
||||
|
||||
// See the constructor comment for an example of how these bucket bounds
|
||||
// are calculated.
|
||||
const int bucket_min = min + width * (index - 1);
|
||||
const int bucket_max = min + index * width - 1;
|
||||
return StringFormat("{}-{}", bucket_min, bucket_max);
|
||||
}
|
||||
|
||||
Json::Value SenderStats::ToJson() const {
|
||||
Json::Value out;
|
||||
out["audio_statistics"] = ArrayToJson(audio_statistics, kStatisticTypeNames);
|
||||
out["audio_histograms"] = ArrayToJson(audio_histograms, kHistogramTypeNames);
|
||||
out["video_statistics"] = ArrayToJson(video_statistics, kStatisticTypeNames);
|
||||
out["video_histograms"] = ArrayToJson(video_histograms, kHistogramTypeNames);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string SenderStats::ToString() const {
|
||||
return json::Stringify(ToJson()).value();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const SenderStats& stats) {
|
||||
return out << stats.ToString();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const SimpleHistogram& histogram) {
|
||||
return out << histogram.ToString();
|
||||
}
|
||||
|
||||
SenderStatsClient::~SenderStatsClient() {}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
195
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/statistics.h
vendored
Normal file
195
breadcast-caststream-sys/vendor/openscreen/cast/streaming/public/statistics.h
vendored
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright 2024 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_PUBLIC_STATISTICS_H_
|
||||
#define CAST_STREAMING_PUBLIC_STATISTICS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/public/frame_id.h"
|
||||
#include "cast/streaming/rtp_time.h"
|
||||
#include "json/value.h"
|
||||
#include "platform/api/time.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// This file must be updated whenever sender_stats.proto is updated.
|
||||
enum class StatisticType {
|
||||
// Frame enqueuing rate.
|
||||
kEnqueueFps = 0,
|
||||
|
||||
// Average capture latency in milliseconds.
|
||||
kAvgCaptureLatencyMs,
|
||||
|
||||
// Average encode duration in milliseconds.
|
||||
kAvgEncodeTimeMs,
|
||||
|
||||
// Duration from when a frame is encoded to when the packet is first
|
||||
// sent.
|
||||
kAvgQueueingLatencyMs,
|
||||
|
||||
// Duration from when a packet is transmitted to when it is received.
|
||||
// This measures latency from sender to receiver.
|
||||
kAvgNetworkLatencyMs,
|
||||
|
||||
// Duration from when a frame is encoded to when the packet is first
|
||||
// received.
|
||||
kAvgPacketLatencyMs,
|
||||
|
||||
// Average latency between frame encoded and the moment when the frame
|
||||
// is fully received.
|
||||
kAvgFrameLatencyMs,
|
||||
|
||||
// Duration from when a frame is captured to when it should be played out.
|
||||
kAvgEndToEndLatencyMs,
|
||||
|
||||
// Encode bitrate in kbps.
|
||||
kEncodeRateKbps,
|
||||
|
||||
// Packet transmission bitrate in kbps.
|
||||
kPacketTransmissionRateKbps,
|
||||
|
||||
// Duration in milliseconds since the estimated last time the receiver sent
|
||||
// a response.
|
||||
kTimeSinceLastReceiverResponseMs,
|
||||
|
||||
// Number of frames captured.
|
||||
kNumFramesCaptured,
|
||||
|
||||
// Number of frames dropped by encoder.
|
||||
kNumFramesDroppedByEncoder,
|
||||
|
||||
// Number of late frames.
|
||||
kNumLateFrames,
|
||||
|
||||
// Number of packets that were sent.
|
||||
kNumPacketsSent,
|
||||
|
||||
// Number of packets that were received by receiver.
|
||||
kNumPacketsReceived,
|
||||
|
||||
// Unix time in milliseconds of first event since reset.
|
||||
kFirstEventTimeMs,
|
||||
|
||||
// Unix time in milliseconds of last event since reset.
|
||||
kLastEventTimeMs,
|
||||
|
||||
// The number of statistic types.
|
||||
kNumTypes = kLastEventTimeMs + 1
|
||||
};
|
||||
|
||||
enum class HistogramType {
|
||||
// Histogram representing the capture latency (in milliseconds).
|
||||
kCaptureLatencyMs,
|
||||
|
||||
// Histogram representing the encode time (in milliseconds).
|
||||
kEncodeTimeMs,
|
||||
|
||||
// Histogram representing the queueing latency (in milliseconds).
|
||||
kQueueingLatencyMs,
|
||||
|
||||
// Histogram representing the network latency (in milliseconds).
|
||||
kNetworkLatencyMs,
|
||||
|
||||
// Histogram representing the packet latency (in milliseconds).
|
||||
kPacketLatencyMs,
|
||||
|
||||
// Histogram representing the end to end latency (in milliseconds).
|
||||
kEndToEndLatencyMs,
|
||||
|
||||
// Histogram representing how late frames are (in milliseconds).
|
||||
kFrameLatenessMs,
|
||||
|
||||
// The number of histogram types.
|
||||
kNumTypes = kFrameLatenessMs + 1
|
||||
};
|
||||
|
||||
struct SimpleHistogram {
|
||||
// This will create N+2 buckets where N = (max - min) / width:
|
||||
// Underflow bucket: < min
|
||||
// Bucket 0: [min, min + width - 1]
|
||||
// Bucket 1: [min + width, min + 2 * width - 1]
|
||||
// ...
|
||||
// Bucket N-1: [max - width, max - 1]
|
||||
// Overflow bucket: >= max
|
||||
// `min` must be less than `max`.
|
||||
// `width` must divide `max - min` evenly.
|
||||
SimpleHistogram(int64_t min, int64_t max, int64_t width);
|
||||
|
||||
SimpleHistogram();
|
||||
SimpleHistogram(const SimpleHistogram&);
|
||||
SimpleHistogram(SimpleHistogram&&) noexcept;
|
||||
SimpleHistogram& operator=(const SimpleHistogram&);
|
||||
SimpleHistogram& operator=(SimpleHistogram&&);
|
||||
~SimpleHistogram();
|
||||
|
||||
bool operator==(const SimpleHistogram&) const;
|
||||
|
||||
void Add(int64_t sample);
|
||||
void Reset();
|
||||
|
||||
Json::Value ToJson() const;
|
||||
std::string ToString() const;
|
||||
|
||||
int64_t min = 1;
|
||||
int64_t max = 1;
|
||||
int64_t width = 1;
|
||||
std::vector<int> buckets;
|
||||
|
||||
private:
|
||||
SimpleHistogram(int64_t min,
|
||||
int64_t max,
|
||||
int64_t width,
|
||||
std::vector<int> buckets);
|
||||
|
||||
std::string GetBucketName(size_t index) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const SimpleHistogram& histogram);
|
||||
|
||||
struct SenderStats {
|
||||
using StatisticsList =
|
||||
std::array<double, static_cast<size_t>(StatisticType::kNumTypes)>;
|
||||
using HistogramsList =
|
||||
std::array<SimpleHistogram,
|
||||
static_cast<size_t>(HistogramType::kNumTypes)>;
|
||||
|
||||
// The current audio statistics.
|
||||
StatisticsList audio_statistics = {};
|
||||
|
||||
// The current audio histograms.
|
||||
HistogramsList audio_histograms = {};
|
||||
|
||||
// The current video statistics.
|
||||
StatisticsList video_statistics = {};
|
||||
|
||||
// The current video histograms.
|
||||
HistogramsList video_histograms = {};
|
||||
|
||||
Json::Value ToJson() const;
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const SenderStats& stats);
|
||||
|
||||
// The consumer may provide a statistics client if they are interested in
|
||||
// getting statistics about the ongoing session.
|
||||
class SenderStatsClient {
|
||||
public:
|
||||
// Gets called regularly with updated statistics while they are being
|
||||
// generated.
|
||||
virtual void OnStatisticsUpdated(const SenderStats& updated_stats) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~SenderStatsClient();
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_PUBLIC_STATISTICS_H_
|
||||
141
breadcast-caststream-sys/vendor/openscreen/cast/streaming/resolution.cc
vendored
Normal file
141
breadcast-caststream-sys/vendor/openscreen/cast/streaming/resolution.cc
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/resolution.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/message_fields.h"
|
||||
#include "platform/base/error.h"
|
||||
#include "util/json/json_helpers.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
/// Dimension properties.
|
||||
// Width in pixels.
|
||||
constexpr char kWidth[] = "width";
|
||||
|
||||
// Height in pixels.
|
||||
constexpr char kHeight[] = "height";
|
||||
|
||||
// Frame rate as a rational decimal number or fraction.
|
||||
// E.g. 30 and "3000/1001" are both valid representations.
|
||||
constexpr char kFrameRate[] = "frameRate";
|
||||
|
||||
// Choice of epsilon for double comparison allows for proper comparison
|
||||
// for both aspect ratios and frame rates. For frame rates, it is based on the
|
||||
// broadcast rate of 29.97fps, which is actually 29.976. For aspect ratios, it
|
||||
// allows for a one-pixel difference at a 4K resolution, we want it to be
|
||||
// relatively high to avoid false negative comparison results.
|
||||
bool FrameRateEquals(double a, double b) {
|
||||
const double kEpsilonForFrameRateComparisons = .0001;
|
||||
return std::abs(a - b) < kEpsilonForFrameRateComparisons;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ErrorOr<Resolution> Resolution::TryParse(const Json::Value& root) {
|
||||
if (!root.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Resolution is not a JSON object");
|
||||
}
|
||||
|
||||
Resolution out;
|
||||
if (!json::TryParseInt(root[kWidth], &out.width) ||
|
||||
!json::TryParseInt(root[kHeight], &out.height)) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid resolution");
|
||||
}
|
||||
if (!out.IsValid()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid resolution values");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Resolution::IsValid() const {
|
||||
return width > 0 && height > 0;
|
||||
}
|
||||
|
||||
Json::Value Resolution::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
Json::Value root;
|
||||
root[kWidth] = width;
|
||||
root[kHeight] = height;
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
bool Resolution::operator==(const Resolution& other) const {
|
||||
return std::tie(width, height) == std::tie(other.width, other.height);
|
||||
}
|
||||
|
||||
bool Resolution::operator!=(const Resolution& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool Resolution::IsSupersetOf(const Resolution& other) const {
|
||||
return width >= other.width && height >= other.height;
|
||||
}
|
||||
|
||||
ErrorOr<Dimensions> Dimensions::TryParse(const Json::Value& root) {
|
||||
if (!root.isObject()) {
|
||||
return Error(Error::Code::kJsonParseError,
|
||||
"Dimensions is not a JSON object");
|
||||
}
|
||||
|
||||
Dimensions out;
|
||||
if (!json::TryParseInt(root[kWidth], &out.width) ||
|
||||
!json::TryParseInt(root[kHeight], &out.height)) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid dimensions");
|
||||
}
|
||||
|
||||
if (!root[kFrameRate].isNull()) {
|
||||
if (!json::TryParseSimpleFraction(root[kFrameRate], &out.frame_rate)) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid frame rate");
|
||||
}
|
||||
}
|
||||
|
||||
if (!out.IsValid()) {
|
||||
return Error(Error::Code::kJsonParseError, "Invalid dimensions values");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Dimensions::IsValid() const {
|
||||
return width > 0 && height > 0 && frame_rate.is_positive();
|
||||
}
|
||||
|
||||
Json::Value Dimensions::ToJson() const {
|
||||
OSP_CHECK(IsValid());
|
||||
Json::Value root;
|
||||
root[kWidth] = width;
|
||||
root[kHeight] = height;
|
||||
root[kFrameRate] = frame_rate.ToString();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
bool Dimensions::operator==(const Dimensions& other) const {
|
||||
return (std::tie(width, height) == std::tie(other.width, other.height) &&
|
||||
FrameRateEquals(static_cast<double>(frame_rate),
|
||||
static_cast<double>(other.frame_rate)));
|
||||
}
|
||||
|
||||
bool Dimensions::operator!=(const Dimensions& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool Dimensions::IsSupersetOf(const Dimensions& other) const {
|
||||
if (static_cast<double>(frame_rate) !=
|
||||
static_cast<double>(other.frame_rate)) {
|
||||
return static_cast<double>(frame_rate) >=
|
||||
static_cast<double>(other.frame_rate);
|
||||
}
|
||||
|
||||
return ToResolution().IsSupersetOf(other.ToResolution());
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
67
breadcast-caststream-sys/vendor/openscreen/cast/streaming/resolution.h
vendored
Normal file
67
breadcast-caststream-sys/vendor/openscreen/cast/streaming/resolution.h
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2021 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// Resolutions and dimensions (resolutions with a frame rate) are used
|
||||
// extensively throughout cast streaming. Since their serialization to and
|
||||
// from JSON is stable and standard, we have a single place definition for
|
||||
// these for use both in our public APIs and private messages.
|
||||
|
||||
#ifndef CAST_STREAMING_RESOLUTION_H_
|
||||
#define CAST_STREAMING_RESOLUTION_H_
|
||||
|
||||
#include "json/value.h"
|
||||
#include "util/simple_fraction.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// A resolution in pixels.
|
||||
struct Resolution {
|
||||
static ErrorOr<Resolution> TryParse(const Json::Value& value);
|
||||
bool IsValid() const;
|
||||
Json::Value ToJson() const;
|
||||
|
||||
// Returns true if both `width` and `height` of this instance are greater than
|
||||
// or equal to that of `other`.
|
||||
bool IsSupersetOf(const Resolution& other) const;
|
||||
|
||||
bool operator==(const Resolution& other) const;
|
||||
bool operator!=(const Resolution& other) const;
|
||||
|
||||
// Width and height in pixels.
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
// A resolution in pixels and a frame rate.
|
||||
struct Dimensions {
|
||||
static ErrorOr<Dimensions> TryParse(const Json::Value& value);
|
||||
bool IsValid() const;
|
||||
Json::Value ToJson() const;
|
||||
|
||||
// Returns true if all properties of this instance are greater than or equal
|
||||
// to those of `other`.
|
||||
bool IsSupersetOf(const Dimensions& other) const;
|
||||
|
||||
bool operator==(const Dimensions& other) const;
|
||||
bool operator!=(const Dimensions& other) const;
|
||||
|
||||
// Get just the width and height fields (for comparisons).
|
||||
constexpr Resolution ToResolution() const { return {width, height}; }
|
||||
|
||||
// The effective bit rate is the width * height * frame rate.
|
||||
constexpr int effective_bit_rate() const {
|
||||
return width * height * static_cast<double>(frame_rate);
|
||||
}
|
||||
|
||||
// Width and height in pixels.
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
// `frame_rate` is the maximum maintainable frame rate.
|
||||
SimpleFraction frame_rate{0, 1};
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_RESOLUTION_H_
|
||||
23
breadcast-caststream-sys/vendor/openscreen/cast/streaming/rtp_time.cc
vendored
Normal file
23
breadcast-caststream-sys/vendor/openscreen/cast/streaming/rtp_time.cc
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2015 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/rtp_time.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const RtpTimeDelta rhs) {
|
||||
if (rhs.value_ >= 0)
|
||||
out << "RTP+";
|
||||
else
|
||||
out << "RTP";
|
||||
return out << rhs.value_;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const RtpTimeTicks rhs) {
|
||||
return out << "RTP@" << rhs.value_;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
257
breadcast-caststream-sys/vendor/openscreen/cast/streaming/rtp_time.h
vendored
Normal file
257
breadcast-caststream-sys/vendor/openscreen/cast/streaming/rtp_time.h
vendored
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
// Copyright 2015 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_RTP_TIME_H_
|
||||
#define CAST_STREAMING_RTP_TIME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "cast/streaming/impl/expanded_value_base.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "util/saturate_cast.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Forward declarations (see below).
|
||||
class RtpTimeDelta;
|
||||
class RtpTimeTicks;
|
||||
|
||||
// Convenience operator overloads for logging.
|
||||
std::ostream& operator<<(std::ostream& out, const RtpTimeDelta rhs);
|
||||
std::ostream& operator<<(std::ostream& out, const RtpTimeTicks rhs);
|
||||
|
||||
// The difference between two RtpTimeTicks values. This data type is modeled
|
||||
// off of Chromium's base::TimeDelta, and used for performing compiler-checked
|
||||
// arithmetic with RtpTimeTicks.
|
||||
//
|
||||
// This data type wraps a value, providing only the meaningful set of math
|
||||
// operations that may be performed on the value. RtpTimeDeltas may be
|
||||
// added/subtracted with other RtpTimeDeltas to produce a RtpTimeDelta holding
|
||||
// the sum/difference. RtpTimeDeltas may also be multiplied or divided by
|
||||
// integer amounts. Finally, RtpTimeDeltas may be divided by other
|
||||
// RtpTimeDeltas to compute a number of periods (trunc'ed to an integer), or
|
||||
// modulo each other to determine a time period remainder.
|
||||
//
|
||||
// The base class provides bit truncation/extension features for
|
||||
// wire-formatting, and also the comparison operators.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// // Time math.
|
||||
// RtpTimeDelta zero;
|
||||
// RtpTimeDelta one_second_later =
|
||||
// zero + RtpTimeDelta::FromTicks(kAudioSamplingRate);
|
||||
// RtpTimeDelta ten_seconds_later = one_second_later * 10;
|
||||
// int64_t ten_periods = ten_seconds_later / one_second_later;
|
||||
//
|
||||
// // Logging convenience.
|
||||
// OSP_DLOG_INFO << "The RTP time offset is " << ten_seconds_later;
|
||||
//
|
||||
// // Convert (approximately!) between RTP timebase and microsecond timebase:
|
||||
// RtpTimeDelta nine_seconds_in_rtp = ten_seconds_later - one_second_later;
|
||||
// using std::chrono::microseconds;
|
||||
// microseconds nine_seconds_duration =
|
||||
// nine_seconds_in_rtp.ToDuration<microseconds>(kAudioSamplingRate);
|
||||
// RtpTimeDelta two_seconds_in_rtp =
|
||||
// RtpTimeDelta::FromDuration(std::chrono::seconds(2),
|
||||
// kAudioSamplingRate);
|
||||
class RtpTimeDelta : public ExpandedValueBase<int64_t, RtpTimeDelta> {
|
||||
public:
|
||||
constexpr RtpTimeDelta() : ExpandedValueBase(0) {}
|
||||
|
||||
// Arithmetic operators (with other deltas).
|
||||
constexpr RtpTimeDelta operator+(RtpTimeDelta rhs) const {
|
||||
return RtpTimeDelta(value_ + rhs.value_);
|
||||
}
|
||||
constexpr RtpTimeDelta operator-(RtpTimeDelta rhs) const {
|
||||
return RtpTimeDelta(value_ - rhs.value_);
|
||||
}
|
||||
constexpr RtpTimeDelta& operator+=(RtpTimeDelta rhs) {
|
||||
return (*this = (*this + rhs));
|
||||
}
|
||||
constexpr RtpTimeDelta& operator-=(RtpTimeDelta rhs) {
|
||||
return (*this = (*this - rhs));
|
||||
}
|
||||
constexpr RtpTimeDelta operator-() const { return RtpTimeDelta(-value_); }
|
||||
|
||||
// Multiplicative operators (with other deltas).
|
||||
constexpr int64_t operator/(RtpTimeDelta rhs) const {
|
||||
return value_ / rhs.value_;
|
||||
}
|
||||
constexpr RtpTimeDelta operator%(RtpTimeDelta rhs) const {
|
||||
return RtpTimeDelta(value_ % rhs.value_);
|
||||
}
|
||||
constexpr RtpTimeDelta& operator%=(RtpTimeDelta rhs) {
|
||||
return (*this = (*this % rhs));
|
||||
}
|
||||
|
||||
// Multiplicative operators (with integer types).
|
||||
template <typename IntType>
|
||||
constexpr RtpTimeDelta operator*(IntType rhs) const {
|
||||
static_assert(std::numeric_limits<IntType>::is_integer,
|
||||
"|rhs| must be a POD integer type");
|
||||
return RtpTimeDelta(value_ * rhs);
|
||||
}
|
||||
template <typename IntType>
|
||||
constexpr RtpTimeDelta operator/(IntType rhs) const {
|
||||
static_assert(std::numeric_limits<IntType>::is_integer,
|
||||
"|rhs| must be a POD integer type");
|
||||
return RtpTimeDelta(value_ / rhs);
|
||||
}
|
||||
template <typename IntType>
|
||||
constexpr RtpTimeDelta& operator*=(IntType rhs) {
|
||||
return (*this = (*this * rhs));
|
||||
}
|
||||
template <typename IntType>
|
||||
constexpr RtpTimeDelta& operator/=(IntType rhs) {
|
||||
return (*this = (*this / rhs));
|
||||
}
|
||||
|
||||
// Maps this RtpTimeDelta to an approximate std::chrono::duration using the
|
||||
// given RTP timebase. Assumes a zero-valued Duration corresponds to a
|
||||
// zero-valued RtpTimeDelta.
|
||||
template <typename Duration>
|
||||
Duration ToDuration(int rtp_timebase) const {
|
||||
OSP_CHECK_GT(rtp_timebase, 0);
|
||||
constexpr Duration kOneSecond =
|
||||
std::chrono::duration_cast<Duration>(std::chrono::seconds(1));
|
||||
return Duration(ToNearestRepresentativeValue<typename Duration::rep>(
|
||||
static_cast<double>(value_) / rtp_timebase * kOneSecond.count()));
|
||||
}
|
||||
|
||||
// Maps the `duration` to an approximate RtpTimeDelta using the given RTP
|
||||
// timebase. Assumes a zero-valued Duration corresponds to a zero-valued
|
||||
// RtpTimeDelta.
|
||||
template <typename Duration>
|
||||
static constexpr RtpTimeDelta FromDuration(Duration duration,
|
||||
int rtp_timebase) {
|
||||
constexpr Duration kOneSecond =
|
||||
std::chrono::duration_cast<Duration>(std::chrono::seconds(1));
|
||||
static_assert(kOneSecond > Duration::zero(),
|
||||
"Duration is too coarse-grained to represent one second.");
|
||||
return RtpTimeDelta(ToNearestRepresentativeValue<int64_t>(
|
||||
static_cast<double>(duration.count()) / kOneSecond.count() *
|
||||
rtp_timebase));
|
||||
}
|
||||
|
||||
// Construct a RtpTimeDelta from an exact number of ticks.
|
||||
static constexpr RtpTimeDelta FromTicks(int64_t ticks) {
|
||||
return RtpTimeDelta(ticks);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ExpandedValueBase<int64_t, RtpTimeDelta>;
|
||||
friend class RtpTimeTicks;
|
||||
friend std::ostream& operator<<(std::ostream& out, const RtpTimeDelta rhs);
|
||||
|
||||
constexpr explicit RtpTimeDelta(int64_t ticks) : ExpandedValueBase(ticks) {}
|
||||
|
||||
constexpr int64_t value() const { return value_; }
|
||||
|
||||
template <typename Rep>
|
||||
static std::enable_if_t<std::is_floating_point<Rep>::value, Rep>
|
||||
ToNearestRepresentativeValue(double ticks) {
|
||||
return Rep(ticks);
|
||||
}
|
||||
|
||||
template <typename Rep>
|
||||
static std::enable_if_t<std::is_integral<Rep>::value, Rep>
|
||||
ToNearestRepresentativeValue(double ticks) {
|
||||
return rounded_saturate_cast<Rep>(ticks);
|
||||
}
|
||||
};
|
||||
|
||||
// A media timestamp whose timebase matches the periodicity of the content
|
||||
// (e.g., for audio, the timebase would be the sampling frequency). This data
|
||||
// type is modeled off of Chromium's base::TimeTicks.
|
||||
//
|
||||
// This data type wraps a value, providing only the meaningful set of math
|
||||
// operations that may be performed on the value. The difference between two
|
||||
// RtpTimeTicks is a RtpTimeDelta. Likewise, adding or subtracting a
|
||||
// RtpTimeTicks with a RtpTimeDelta produces an off-set RtpTimeTicks.
|
||||
//
|
||||
// The base class provides bit truncation/extension features for
|
||||
// wire-formatting, and also the comparison operators.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// // Time math.
|
||||
// RtpTimeTicks origin;
|
||||
// RtpTimeTicks at_one_second =
|
||||
// origin + RtpTimeDelta::FromTicks(kAudioSamplingRate);
|
||||
// RtpTimeTicks at_two_seconds =
|
||||
// at_one_second + RtpTimeDelta::FromTicks(kAudioSamplingRate);
|
||||
// RtpTimeDelta elasped_in_between = at_two_seconds - at_one_second;
|
||||
// RtpTimeDelta thrice_as_much_elasped = elasped_in_between * 3;
|
||||
// RtpTimeTicks at_four_seconds = at_one_second + thrice_as_much_elasped;
|
||||
//
|
||||
// // Logging convenience.
|
||||
// OSP_DLOG_INFO << "The RTP timestamp is " << at_four_seconds;
|
||||
//
|
||||
// // Convert (approximately!) between RTP timebase and stream time offsets in
|
||||
// // microsecond timebase:
|
||||
// using std::chrono::microseconds;
|
||||
// microseconds four_seconds_since_stream_start =
|
||||
// at_four_seconds.ToTimeSinceOrigin<microseconds>(kAudioSamplingRate);
|
||||
// RtpTimeTicks at_three_seconds = RtpTimeDelta::FromTimeSinceOrigin(
|
||||
// std::chrono::seconds(3), kAudioSamplingRate);
|
||||
class RtpTimeTicks : public ExpandedValueBase<int64_t, RtpTimeTicks> {
|
||||
public:
|
||||
constexpr explicit RtpTimeTicks(int64_t value) : ExpandedValueBase(value) {}
|
||||
constexpr RtpTimeTicks() : ExpandedValueBase(0) {}
|
||||
|
||||
constexpr int64_t value() const { return value_; }
|
||||
|
||||
// Compute the difference between two RtpTimeTickses.
|
||||
constexpr RtpTimeDelta operator-(RtpTimeTicks rhs) const {
|
||||
return RtpTimeDelta(value_ - rhs.value_);
|
||||
}
|
||||
|
||||
// Return a new RtpTimeTicks before or after this one.
|
||||
constexpr RtpTimeTicks operator+(RtpTimeDelta rhs) const {
|
||||
return RtpTimeTicks(value_ + rhs.value());
|
||||
}
|
||||
constexpr RtpTimeTicks operator-(RtpTimeDelta rhs) const {
|
||||
return RtpTimeTicks(value_ - rhs.value());
|
||||
}
|
||||
constexpr RtpTimeTicks& operator+=(RtpTimeDelta rhs) {
|
||||
return (*this = (*this + rhs));
|
||||
}
|
||||
constexpr RtpTimeTicks& operator-=(RtpTimeDelta rhs) {
|
||||
return (*this = (*this - rhs));
|
||||
}
|
||||
|
||||
// Maps this RtpTimeTicks to an approximate std::chrono::duration representing
|
||||
// the amount of time since the origin point (e.g., the start of a stream)
|
||||
// using the given `rtp_timebase`. Assumes a zero-valued Duration corresponds
|
||||
// to a zero-valued RtpTimeTicks.
|
||||
template <typename Duration>
|
||||
Duration ToTimeSinceOrigin(int rtp_timebase) const {
|
||||
return (*this - RtpTimeTicks()).ToDuration<Duration>(rtp_timebase);
|
||||
}
|
||||
|
||||
// Maps the `time_since_origin` to an approximate RtpTimeTicks using the given
|
||||
// RTP timebase. Assumes a zero-valued Duration corresponds to a zero-valued
|
||||
// RtpTimeTicks.
|
||||
template <typename Duration>
|
||||
static constexpr RtpTimeTicks FromTimeSinceOrigin(Duration time_since_origin,
|
||||
int rtp_timebase) {
|
||||
return RtpTimeTicks() +
|
||||
RtpTimeDelta::FromDuration(time_since_origin, rtp_timebase);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ExpandedValueBase<int64_t, RtpTimeTicks>;
|
||||
friend std::ostream& operator<<(std::ostream& out, const RtpTimeTicks rhs);
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_RTP_TIME_H_
|
||||
128
breadcast-caststream-sys/vendor/openscreen/cast/streaming/sender_message.cc
vendored
Normal file
128
breadcast-caststream-sys/vendor/openscreen/cast/streaming/sender_message.cc
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/sender_message.h"
|
||||
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "cast/streaming/message_fields.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/enum_name_table.h"
|
||||
#include "util/json/json_helpers.h"
|
||||
#include "util/json/json_serialization.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
EnumNameTable<SenderMessage::Type, 4> kMessageTypeNames{
|
||||
{{kMessageTypeOffer, SenderMessage::Type::kOffer},
|
||||
{"GET_CAPABILITIES", SenderMessage::Type::kGetCapabilities},
|
||||
{"RPC", SenderMessage::Type::kRpc},
|
||||
{"INPUT", SenderMessage::Type::kInput}}};
|
||||
|
||||
SenderMessage::Type GetMessageType(const Json::Value& root) {
|
||||
std::string type;
|
||||
if (!json::TryParseString(root[kMessageType], &type)) {
|
||||
return SenderMessage::Type::kUnknown;
|
||||
}
|
||||
string_util::AsciiStrToUpper(type);
|
||||
ErrorOr<SenderMessage::Type> parsed = GetEnum(kMessageTypeNames, type);
|
||||
|
||||
return parsed.value(SenderMessage::Type::kUnknown);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
ErrorOr<SenderMessage> SenderMessage::Parse(const Json::Value& value) {
|
||||
if (!value.isObject()) {
|
||||
return Error(Error::Code::kParameterInvalid,
|
||||
"SenderMessage body is not a JSON object");
|
||||
}
|
||||
|
||||
SenderMessage message;
|
||||
if (!json::TryParseInt(value[kSequenceNumber], &(message.sequence_number))) {
|
||||
message.sequence_number = -1;
|
||||
}
|
||||
|
||||
message.type = GetMessageType(value);
|
||||
switch (message.type) {
|
||||
case Type::kOffer: {
|
||||
auto offer_or_error = Offer::TryParse(value[kOfferMessageBody]);
|
||||
if (offer_or_error.is_value()) {
|
||||
message.body = std::move(offer_or_error.value());
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kRpc: {
|
||||
std::string rpc_body;
|
||||
std::vector<uint8_t> rpc;
|
||||
if (json::TryParseString(value[kRpcMessageBody], &rpc_body) &&
|
||||
base64::Decode(rpc_body, &rpc)) {
|
||||
message.body = rpc;
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kInput: {
|
||||
std::string input_body;
|
||||
std::vector<uint8_t> input;
|
||||
if (json::TryParseString(value[kInputMessageBody], &input_body) &&
|
||||
base64::Decode(input_body, &input)) {
|
||||
message.body = input;
|
||||
message.valid = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Type::kGetCapabilities:
|
||||
message.valid = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
ErrorOr<Json::Value> SenderMessage::ToJson() const {
|
||||
OSP_CHECK(type != SenderMessage::Type::kUnknown)
|
||||
<< "Trying to send an unknown message is a developer error";
|
||||
|
||||
Json::Value root;
|
||||
ErrorOr<const char*> message_type = GetEnumName(kMessageTypeNames, type);
|
||||
root[kMessageType] = message_type.value();
|
||||
if (sequence_number >= 0) {
|
||||
root[kSequenceNumber] = sequence_number;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case SenderMessage::Type::kOffer:
|
||||
root[kOfferMessageBody] = std::get<Offer>(body).ToJson();
|
||||
break;
|
||||
|
||||
case SenderMessage::Type::kRpc:
|
||||
root[kRpcMessageBody] =
|
||||
base64::Encode(std::get<std::vector<uint8_t>>(body));
|
||||
break;
|
||||
|
||||
case SenderMessage::Type::kInput:
|
||||
root[kInputMessageBody] =
|
||||
base64::Encode(std::get<std::vector<uint8_t>>(body));
|
||||
break;
|
||||
|
||||
case SenderMessage::Type::kGetCapabilities:
|
||||
break;
|
||||
|
||||
default:
|
||||
OSP_NOTREACHED();
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
55
breadcast-caststream-sys/vendor/openscreen/cast/streaming/sender_message.h
vendored
Normal file
55
breadcast-caststream-sys/vendor/openscreen/cast/streaming/sender_message.h
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2020 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_SENDER_MESSAGE_H_
|
||||
#define CAST_STREAMING_SENDER_MESSAGE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/public/offer_messages.h"
|
||||
#include "json/value.h"
|
||||
#include "platform/base/error.h"
|
||||
#include "util/osp_logging.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
struct SenderMessage {
|
||||
public:
|
||||
// Receiver response message type.
|
||||
enum class Type {
|
||||
// Unknown message type.
|
||||
kUnknown,
|
||||
|
||||
// OFFER request message.
|
||||
kOffer,
|
||||
|
||||
// GET_CAPABILITIES request message.
|
||||
kGetCapabilities,
|
||||
|
||||
// Rpc binary messages. The payload is base64-encoded.
|
||||
kRpc,
|
||||
|
||||
// Input-related binary messages. The payload is base64-encoded.
|
||||
kInput,
|
||||
};
|
||||
|
||||
static ErrorOr<SenderMessage> Parse(const Json::Value& value);
|
||||
ErrorOr<Json::Value> ToJson() const;
|
||||
|
||||
Type type = Type::kUnknown;
|
||||
int32_t sequence_number = -1;
|
||||
bool valid = false;
|
||||
std::variant<std::monostate,
|
||||
std::vector<uint8_t>, // Binary-encoded protobuf message.
|
||||
Offer,
|
||||
std::string>
|
||||
body;
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_SENDER_MESSAGE_H_
|
||||
273
breadcast-caststream-sys/vendor/openscreen/cast/streaming/sender_packet_router.cc
vendored
Normal file
273
breadcast-caststream-sys/vendor/openscreen/cast/streaming/sender_packet_router.cc
vendored
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/sender_packet_router.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "cast/streaming/impl/packet_util.h"
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/chrono_helpers.h"
|
||||
#include "util/osp_logging.h"
|
||||
#include "util/saturate_cast.h"
|
||||
#include "util/stringprintf.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
using clock_operators::operator<<;
|
||||
|
||||
SenderPacketRouter::SenderPacketRouter(Environment& environment,
|
||||
int max_burst_bitrate)
|
||||
: SenderPacketRouter(
|
||||
environment,
|
||||
ComputeMaxPacketsPerBurst(max_burst_bitrate,
|
||||
environment.GetMaxPacketSize(),
|
||||
kDefaultBurstInterval),
|
||||
kDefaultBurstInterval) {}
|
||||
|
||||
SenderPacketRouter::SenderPacketRouter(Environment& environment,
|
||||
int max_packets_per_burst,
|
||||
milliseconds burst_interval)
|
||||
: BandwidthEstimator(max_packets_per_burst,
|
||||
burst_interval,
|
||||
environment.now()),
|
||||
environment_(environment),
|
||||
packet_buffer_size_(environment.GetMaxPacketSize()),
|
||||
packet_buffer_(new uint8_t[packet_buffer_size_]),
|
||||
max_packets_per_burst_(max_packets_per_burst),
|
||||
burst_interval_(burst_interval),
|
||||
max_burst_bitrate_(ComputeMaxBurstBitrate(packet_buffer_size_,
|
||||
max_packets_per_burst_,
|
||||
burst_interval_)),
|
||||
alarm_(environment_->now_function(), environment_->task_runner()) {
|
||||
OSP_CHECK_GT(packet_buffer_size_, kRequiredNetworkPacketSize);
|
||||
}
|
||||
|
||||
SenderPacketRouter::~SenderPacketRouter() {
|
||||
OSP_CHECK(senders_.empty());
|
||||
}
|
||||
|
||||
void SenderPacketRouter::OnSenderCreated(Ssrc receiver_ssrc, Sender* sender) {
|
||||
OSP_CHECK(FindEntry(receiver_ssrc) == senders_.end());
|
||||
senders_.push_back(SenderEntry{receiver_ssrc, sender, kNever, kNever});
|
||||
|
||||
if (senders_.size() == 1) {
|
||||
environment_->ConsumeIncomingPackets(this);
|
||||
} else {
|
||||
// Sort the list of Senders so that they are iterated in priority order.
|
||||
std::sort(senders_.begin(), senders_.end());
|
||||
}
|
||||
}
|
||||
|
||||
void SenderPacketRouter::OnSenderDestroyed(Ssrc receiver_ssrc) {
|
||||
const auto it = FindEntry(receiver_ssrc);
|
||||
OSP_CHECK(it != senders_.end());
|
||||
senders_.erase(it);
|
||||
|
||||
// If there are no longer any Senders, suspend receiving RTCP packets.
|
||||
if (senders_.empty()) {
|
||||
environment_->DropIncomingPackets();
|
||||
}
|
||||
}
|
||||
|
||||
void SenderPacketRouter::RequestRtcpSend(Ssrc receiver_ssrc) {
|
||||
const auto it = FindEntry(receiver_ssrc);
|
||||
OSP_CHECK(it != senders_.end());
|
||||
it->next_rtcp_send_time = Alarm::kImmediately;
|
||||
ScheduleNextBurst();
|
||||
}
|
||||
|
||||
void SenderPacketRouter::RequestRtpSend(Ssrc receiver_ssrc) {
|
||||
const auto it = FindEntry(receiver_ssrc);
|
||||
OSP_CHECK(it != senders_.end());
|
||||
it->next_rtp_send_time = Alarm::kImmediately;
|
||||
ScheduleNextBurst();
|
||||
}
|
||||
|
||||
void SenderPacketRouter::OnReceivedPacket(const IPEndpoint& source,
|
||||
Clock::time_point arrival_time,
|
||||
std::vector<uint8_t> packet) {
|
||||
// If the packet did not come from the expected endpoint, ignore it.
|
||||
OSP_CHECK_NE(source.port, uint16_t{0});
|
||||
if (source != environment_->remote_endpoint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine which Sender to dispatch the packet to. Senders may only receive
|
||||
// RTCP packets from Receivers. Log a warning containing a pretty-printed dump
|
||||
// if the packet is not an RTCP packet.
|
||||
const std::pair<ApparentPacketType, Ssrc> seems_like =
|
||||
InspectPacketForRouting(packet);
|
||||
if (seems_like.first != ApparentPacketType::RTCP) {
|
||||
constexpr int kMaxPartiaHexDumpSize = 96;
|
||||
const std::size_t encode_size =
|
||||
std::min(packet.size(), static_cast<size_t>(kMaxPartiaHexDumpSize));
|
||||
OSP_LOG_WARN << "UNKNOWN packet of " << packet.size()
|
||||
<< " bytes. Partial hex dump: "
|
||||
<< HexEncode(packet.data(), encode_size);
|
||||
return;
|
||||
}
|
||||
const auto it = FindEntry(seems_like.second);
|
||||
if (it != senders_.end()) {
|
||||
it->sender->OnReceivedRtcpPacket(arrival_time, std::move(packet));
|
||||
}
|
||||
}
|
||||
|
||||
SenderPacketRouter::SenderEntries::iterator SenderPacketRouter::FindEntry(
|
||||
Ssrc receiver_ssrc) {
|
||||
return std::find_if(senders_.begin(), senders_.end(),
|
||||
[receiver_ssrc](const SenderEntry& entry) {
|
||||
return entry.receiver_ssrc == receiver_ssrc;
|
||||
});
|
||||
}
|
||||
|
||||
void SenderPacketRouter::ScheduleNextBurst() {
|
||||
// Determine the next burst time by scanning for the earliest of the
|
||||
// next-scheduled send times for each Sender.
|
||||
const Clock::time_point earliest_allowed_burst_time =
|
||||
last_burst_time_ + burst_interval_;
|
||||
Clock::time_point next_burst_time = kNever;
|
||||
for (const SenderEntry& entry : senders_) {
|
||||
const auto next_send_time =
|
||||
std::min(entry.next_rtcp_send_time, entry.next_rtp_send_time);
|
||||
if (next_send_time >= next_burst_time) {
|
||||
continue;
|
||||
}
|
||||
if (next_send_time <= earliest_allowed_burst_time) {
|
||||
next_burst_time = earliest_allowed_burst_time;
|
||||
// No need to continue, since `next_burst_time` cannot become any earlier.
|
||||
break;
|
||||
}
|
||||
next_burst_time = next_send_time;
|
||||
}
|
||||
|
||||
// Schedule the alarm for the next burst time unless none of the Senders has
|
||||
// anything to send.
|
||||
if (next_burst_time == kNever) {
|
||||
alarm_.Cancel();
|
||||
} else {
|
||||
alarm_.Schedule([this] { SendBurstOfPackets(); }, next_burst_time);
|
||||
}
|
||||
}
|
||||
|
||||
void SenderPacketRouter::SendBurstOfPackets() {
|
||||
// Treat RTCP packets as "critical priority," and so there is no upper limit
|
||||
// on the number to send. Practically, this will always be limited by the
|
||||
// number of Senders; so, this won't be a huge number of packets.
|
||||
const Clock::time_point burst_time = environment_->now();
|
||||
const int num_rtcp_packets_sent = SendJustTheRtcpPackets(burst_time);
|
||||
// Now send all the RTP packets, up to the maximum number allowed in a burst.
|
||||
// Higher priority Senders' RTP packets are sent first.
|
||||
const int num_rtp_packets_sent = SendJustTheRtpPackets(
|
||||
burst_time, max_packets_per_burst_ - num_rtcp_packets_sent);
|
||||
last_burst_time_ = burst_time;
|
||||
|
||||
BandwidthEstimator::OnBurstComplete(
|
||||
num_rtcp_packets_sent + num_rtp_packets_sent, burst_time);
|
||||
|
||||
ScheduleNextBurst();
|
||||
}
|
||||
|
||||
int SenderPacketRouter::SendJustTheRtcpPackets(Clock::time_point send_time) {
|
||||
int num_sent = 0;
|
||||
for (SenderEntry& entry : senders_) {
|
||||
if (entry.next_rtcp_send_time > send_time) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note: Only one RTCP packet is sent from the same Sender in the same
|
||||
// burst. This is because RTCP packets are supposed to always contain the
|
||||
// most up-to-date Sender state. Having multiple RTCP packets in the same
|
||||
// burst would mean that all but the last one are old/irrelevant snapshots
|
||||
// of Sender state, and this would just thrash/confuse the Receiver.
|
||||
const ByteBuffer packet = entry.sender->GetRtcpPacketForImmediateSend(
|
||||
send_time, ByteBuffer(packet_buffer_.get(), packet_buffer_size_));
|
||||
if (!packet.empty()) {
|
||||
environment_->SendPacket(
|
||||
ByteView(packet.data(), packet.size()),
|
||||
PacketMetadata{.stream_type = entry.sender->GetStreamType(),
|
||||
.rtp_timestamp = entry.sender->GetLastRtpTimestamp()});
|
||||
entry.next_rtcp_send_time = send_time + kRtcpReportInterval;
|
||||
++num_sent;
|
||||
}
|
||||
}
|
||||
|
||||
return num_sent;
|
||||
}
|
||||
|
||||
int SenderPacketRouter::SendJustTheRtpPackets(Clock::time_point send_time,
|
||||
int num_packets_to_send) {
|
||||
int num_sent = 0;
|
||||
for (SenderEntry& entry : senders_) {
|
||||
if (num_sent >= num_packets_to_send) {
|
||||
break;
|
||||
}
|
||||
if (entry.next_rtp_send_time > send_time) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (; num_sent < num_packets_to_send; ++num_sent) {
|
||||
const ByteBuffer packet = entry.sender->GetRtpPacketForImmediateSend(
|
||||
send_time, ByteBuffer(packet_buffer_.get(), packet_buffer_size_));
|
||||
if (packet.empty()) {
|
||||
break;
|
||||
}
|
||||
environment_->SendPacket(
|
||||
ByteView(packet.data(), packet.size()),
|
||||
PacketMetadata{.stream_type = entry.sender->GetStreamType(),
|
||||
.rtp_timestamp = entry.sender->GetLastRtpTimestamp()});
|
||||
}
|
||||
entry.next_rtp_send_time = entry.sender->GetRtpResumeTime();
|
||||
}
|
||||
|
||||
return num_sent;
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr int kBitsPerByte = 8;
|
||||
constexpr auto kOneSecondInMilliseconds = to_milliseconds(seconds(1));
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
int SenderPacketRouter::ComputeMaxPacketsPerBurst(int max_burst_bitrate,
|
||||
int packet_size,
|
||||
milliseconds burst_interval) {
|
||||
OSP_CHECK_GT(max_burst_bitrate, 0);
|
||||
OSP_CHECK_GT(packet_size, 0);
|
||||
OSP_CHECK_GT(burst_interval, milliseconds(0));
|
||||
OSP_CHECK_LE(burst_interval, kOneSecondInMilliseconds);
|
||||
|
||||
const int max_packets_per_second =
|
||||
max_burst_bitrate / kBitsPerByte / packet_size;
|
||||
const int bursts_per_second = kOneSecondInMilliseconds / burst_interval;
|
||||
return std::max(max_packets_per_second / bursts_per_second, 1);
|
||||
}
|
||||
|
||||
// static
|
||||
int SenderPacketRouter::ComputeMaxBurstBitrate(int packet_size,
|
||||
int max_packets_per_burst,
|
||||
milliseconds burst_interval) {
|
||||
OSP_CHECK_GT(packet_size, 0);
|
||||
OSP_CHECK_GT(max_packets_per_burst, 0);
|
||||
OSP_CHECK_GT(burst_interval, milliseconds(0));
|
||||
OSP_CHECK_LE(burst_interval, kOneSecondInMilliseconds);
|
||||
|
||||
const int64_t max_bits_per_burst =
|
||||
int64_t{packet_size} * kBitsPerByte * max_packets_per_burst;
|
||||
const int bursts_per_second = kOneSecondInMilliseconds / burst_interval;
|
||||
return saturate_cast<int>(max_bits_per_burst * bursts_per_second);
|
||||
}
|
||||
|
||||
SenderPacketRouter::Sender::~Sender() = default;
|
||||
|
||||
// static
|
||||
constexpr int SenderPacketRouter::kDefaultMaxBurstBitrate;
|
||||
// static
|
||||
constexpr milliseconds SenderPacketRouter::kDefaultBurstInterval;
|
||||
// static
|
||||
constexpr Clock::time_point SenderPacketRouter::kNever;
|
||||
|
||||
} // namespace openscreen::cast
|
||||
203
breadcast-caststream-sys/vendor/openscreen/cast/streaming/sender_packet_router.h
vendored
Normal file
203
breadcast-caststream-sys/vendor/openscreen/cast/streaming/sender_packet_router.h
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright 2020 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_SENDER_PACKET_ROUTER_H_
|
||||
#define CAST_STREAMING_SENDER_PACKET_ROUTER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cast/streaming/impl/bandwidth_estimator.h"
|
||||
#include "cast/streaming/public/constants.h"
|
||||
#include "cast/streaming/public/environment.h"
|
||||
#include "cast/streaming/ssrc.h"
|
||||
#include "platform/api/time.h"
|
||||
#include "platform/base/span.h"
|
||||
#include "util/alarm.h"
|
||||
#include "util/raw_ptr.h"
|
||||
#include "util/raw_ref.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// Manages network packet transmission for one or more Senders, directing each
|
||||
// inbound packet to a specific Sender instance, pacing the transmission of
|
||||
// outbound packets, and employing network bandwidth/availability monitoring and
|
||||
// congestion control.
|
||||
//
|
||||
// Instead of just sending packets whenever they want, Senders must request
|
||||
// transmission from the SenderPacketRouter. The router then calls-back to each
|
||||
// Sender, in the near future, when it has allocated an available time slice for
|
||||
// transmission. The Sender is allowed to decide, at that exact moment, which
|
||||
// packet most needs to be sent.
|
||||
//
|
||||
// Pacing strategy: Packets are sent in bursts. This allows the platform
|
||||
// (operating system) to collect many small packets into a short-term buffer,
|
||||
// which allows for optimizations at the link layer. For example, multiple
|
||||
// packets can be sent together as one larger transmission unit, and this can be
|
||||
// critical for good performance over shared-medium networks (such as 802.11
|
||||
// WiFi). https://en.wikipedia.org/wiki/Frame-bursting
|
||||
class SenderPacketRouter : public BandwidthEstimator,
|
||||
public Environment::PacketConsumer {
|
||||
public:
|
||||
class Sender {
|
||||
public:
|
||||
// Called to provide the Sender with what looks like a RTCP packet meant for
|
||||
// it specifically (among other Senders) to process. `arrival_time`
|
||||
// indicates when the packet arrived (i.e., when it was received from the
|
||||
// platform).
|
||||
virtual void OnReceivedRtcpPacket(Clock::time_point arrival_time,
|
||||
ByteView packet) = 0;
|
||||
|
||||
// Populates the given `buffer` with a RTCP/RTP packet that will be sent
|
||||
// immediately. Returns the portion of `buffer` contaning the packet, or an
|
||||
// empty Span if nothing is ready to send.
|
||||
virtual ByteBuffer GetRtcpPacketForImmediateSend(
|
||||
Clock::time_point send_time,
|
||||
ByteBuffer buffer) = 0;
|
||||
virtual ByteBuffer GetRtpPacketForImmediateSend(Clock::time_point send_time,
|
||||
ByteBuffer buffer) = 0;
|
||||
|
||||
// Returns the point-in-time at which RTP sending should resume, or kNever
|
||||
// if it should be suspended until an explicit call to RequestRtpSend(). The
|
||||
// implementation may return a value on or before "now" to indicate an
|
||||
// immediate resume is desired.
|
||||
virtual Clock::time_point GetRtpResumeTime() = 0;
|
||||
|
||||
// Returns the last logged RTP timestamp, for use in expanding truncated
|
||||
// packet RTP timestamps for metrics purposes.
|
||||
virtual RtpTimeTicks GetLastRtpTimestamp() const = 0;
|
||||
|
||||
// Returns the type of stream that this sender is providing.
|
||||
virtual StreamType GetStreamType() const = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Sender();
|
||||
};
|
||||
|
||||
// Constructs an instance with default burst parameters appropriate for the
|
||||
// given `max_burst_bitrate`.
|
||||
explicit SenderPacketRouter(Environment& environment,
|
||||
int max_burst_bitrate = kDefaultMaxBurstBitrate);
|
||||
|
||||
// Constructs an instance with specific burst parameters. The maximum bitrate
|
||||
// will be computed based on these (and Environment::GetMaxPacketSize()).
|
||||
SenderPacketRouter(Environment& environment,
|
||||
int max_packets_per_burst,
|
||||
std::chrono::milliseconds burst_interval);
|
||||
|
||||
~SenderPacketRouter();
|
||||
|
||||
int max_packet_size() const { return packet_buffer_size_; }
|
||||
int max_burst_bitrate() const { return max_burst_bitrate_; }
|
||||
|
||||
// Called from a Sender constructor/destructor to register/deregister a Sender
|
||||
// instance that processes RTP/RTCP packets from a Receiver having the given
|
||||
// SSRC.
|
||||
void OnSenderCreated(Ssrc receiver_ssrc, Sender* client);
|
||||
void OnSenderDestroyed(Ssrc receiver_ssrc);
|
||||
|
||||
// Requests an immediate send of a RTCP packet, and then RTCP sending will
|
||||
// repeat at regular intervals (see kRtcpSendInterval) until the Sender is
|
||||
// de-registered.
|
||||
void RequestRtcpSend(Ssrc receiver_ssrc);
|
||||
|
||||
// Requests an immediate send of a RTP packet. RTP sending will continue until
|
||||
// the Sender stops providing packet data.
|
||||
//
|
||||
// See also: Sender::GetRtpResumeTime().
|
||||
void RequestRtpSend(Ssrc receiver_ssrc);
|
||||
|
||||
// A reasonable default maximum bitrate for bursting. Congestion control
|
||||
// should always be employed to limit the Senders' sustained/average outbound
|
||||
// data volume for "fair" use of the network.
|
||||
static constexpr int kDefaultMaxBurstBitrate = 24 << 20; // 24 megabits/sec
|
||||
|
||||
// The minimum amount of time between burst-sends. The methodology by which
|
||||
// this value was determined is lost knowledge, but is likely the result of
|
||||
// experimentation with various network and operating system configurations.
|
||||
// This value came from the original Chrome Cast Streaming implementation.
|
||||
static constexpr std::chrono::milliseconds kDefaultBurstInterval{10};
|
||||
|
||||
// A special time_point value representing "never."
|
||||
static constexpr Clock::time_point kNever = Clock::time_point::max();
|
||||
|
||||
private:
|
||||
struct SenderEntry {
|
||||
Ssrc receiver_ssrc;
|
||||
raw_ptr<Sender> sender;
|
||||
Clock::time_point next_rtcp_send_time;
|
||||
Clock::time_point next_rtp_send_time;
|
||||
|
||||
// Entries are ordered by the transmission priority (high→low), as implied
|
||||
// by their SSRC. See ssrc.h for details.
|
||||
bool operator<(const SenderEntry& other) const {
|
||||
return ComparePriority(receiver_ssrc, other.receiver_ssrc) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
using SenderEntries = std::vector<SenderEntry>;
|
||||
|
||||
// Environment::PacketConsumer implementation.
|
||||
void OnReceivedPacket(const IPEndpoint& source,
|
||||
Clock::time_point arrival_time,
|
||||
std::vector<uint8_t> packet) final;
|
||||
|
||||
// Helper to return an iterator pointing to the entry corresponding to the
|
||||
// given `receiver_ssrc`, or "end" if not found.
|
||||
SenderEntries::iterator FindEntry(Ssrc receiver_ssrc);
|
||||
|
||||
// Examine the next send time for all Senders, and decide whether to schedule
|
||||
// a burst-send.
|
||||
void ScheduleNextBurst();
|
||||
|
||||
// Performs a burst-send of packets. This is called whenever the Alarm fires.
|
||||
void SendBurstOfPackets();
|
||||
|
||||
// Send an RTCP packet from each Sender that has one ready, and return the
|
||||
// number of packets sent.
|
||||
int SendJustTheRtcpPackets(Clock::time_point send_time);
|
||||
|
||||
// Send zero or more RTP packets from each Sender, up to a maximum of
|
||||
// `num_packets_to_send`, and return the number of packets sent.
|
||||
int SendJustTheRtpPackets(Clock::time_point send_time,
|
||||
int num_packets_to_send);
|
||||
|
||||
// Returns the maximum number of packets to send in one burst, based on the
|
||||
// given parameters.
|
||||
static int ComputeMaxPacketsPerBurst(
|
||||
int max_burst_bitrate,
|
||||
int packet_size,
|
||||
std::chrono::milliseconds burst_interval);
|
||||
|
||||
// Returns the maximum bitrate inferred by the given parameters.
|
||||
static int ComputeMaxBurstBitrate(int packet_size,
|
||||
int max_packets_per_burst,
|
||||
std::chrono::milliseconds burst_interval);
|
||||
|
||||
const raw_ref<Environment> environment_;
|
||||
const int packet_buffer_size_;
|
||||
const std::unique_ptr<uint8_t[]> packet_buffer_;
|
||||
const int max_packets_per_burst_;
|
||||
const std::chrono::milliseconds burst_interval_;
|
||||
const int max_burst_bitrate_;
|
||||
|
||||
// Schedules the task that calls back into this SenderPacketRouter at a later
|
||||
// time to send the next burst of packets.
|
||||
Alarm alarm_;
|
||||
|
||||
// The current list of Senders and their timing information. This is
|
||||
// maintained in order of the priority implied by the Sender SSRC's.
|
||||
SenderEntries senders_;
|
||||
|
||||
// The last time a burst of packets was sent. This is used to determine the
|
||||
// next burst time.
|
||||
Clock::time_point last_burst_time_ = Clock::time_point::min();
|
||||
};
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_SENDER_PACKET_ROUTER_H_
|
||||
42
breadcast-caststream-sys/vendor/openscreen/cast/streaming/ssrc.cc
vendored
Normal file
42
breadcast-caststream-sys/vendor/openscreen/cast/streaming/ssrc.cc
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2019 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/ssrc.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include "platform/api/time.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
namespace {
|
||||
|
||||
// These ranges are arbitrary, but have been used for several years (in prior
|
||||
// implementations of Cast Streaming).
|
||||
constexpr int kHigherPriorityMin = 1;
|
||||
constexpr int kHigherPriorityMax = 50000;
|
||||
constexpr int kNormalPriorityMin = 50001;
|
||||
constexpr int kNormalPriorityMax = 100000;
|
||||
|
||||
} // namespace
|
||||
|
||||
Ssrc GenerateSsrc(bool higher_priority) {
|
||||
// Use a statically-allocated generator, instantiated upon first use, and
|
||||
// seeded with the current time tick count. This generator was chosen because
|
||||
// it is light-weight and does not need to produce unguessable (nor
|
||||
// crypto-secure) values.
|
||||
static std::minstd_rand generator(static_cast<std::minstd_rand::result_type>(
|
||||
Clock::now().time_since_epoch().count()));
|
||||
|
||||
std::uniform_int_distribution<int> distribution(
|
||||
higher_priority ? kHigherPriorityMin : kNormalPriorityMin,
|
||||
higher_priority ? kHigherPriorityMax : kNormalPriorityMax);
|
||||
return static_cast<Ssrc>(distribution(generator));
|
||||
}
|
||||
|
||||
int ComparePriority(Ssrc ssrc_a, Ssrc ssrc_b) {
|
||||
return static_cast<int>(ssrc_a) - static_cast<int>(ssrc_b);
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
37
breadcast-caststream-sys/vendor/openscreen/cast/streaming/ssrc.h
vendored
Normal file
37
breadcast-caststream-sys/vendor/openscreen/cast/streaming/ssrc.h
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2019 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_SSRC_H_
|
||||
#define CAST_STREAMING_SSRC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
// A Synchronization Source is a 32-bit opaque identifier used in RTP packets
|
||||
// for identifying the source (or recipient) of a logical sequence of encoded
|
||||
// audio/video frames. In other words, an audio stream will have one sender SSRC
|
||||
// and a video stream will have a different sender SSRC.
|
||||
using Ssrc = uint32_t;
|
||||
|
||||
// The "not set" or "null" value for the Ssrc type.
|
||||
inline constexpr Ssrc kNullSsrc = 0;
|
||||
|
||||
// Computes a new SSRC that will be used to uniquely identify an RTP stream. The
|
||||
// `higher_priority` argument, if true, will generate an SSRC that causes the
|
||||
// system to use a higher priority when scheduling data transmission. Generally,
|
||||
// this is set to true for audio streams and false for video streams.
|
||||
Ssrc GenerateSsrc(bool higher_priority);
|
||||
|
||||
// Returns a value indicating how to prioritize data transmission for a stream
|
||||
// with `ssrc_a` versus a stream with `ssrc_b`:
|
||||
//
|
||||
// ret < 0: Stream `ssrc_a` has higher priority.
|
||||
// ret == 0: Equal priority.
|
||||
// ret > 0: Stream `ssrc_b` has higher priority.
|
||||
int ComparePriority(Ssrc ssrc_a, Ssrc ssrc_b);
|
||||
|
||||
} // namespace openscreen::cast
|
||||
|
||||
#endif // CAST_STREAMING_SSRC_H_
|
||||
Loading…
Add table
Add a link
Reference in a new issue