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
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_
|
||||
Loading…
Add table
Add a link
Reference in a new issue