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