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
573
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_analyzer.cc
vendored
Normal file
573
breadcast-caststream-sys/vendor/openscreen/cast/streaming/impl/statistics_analyzer.cc
vendored
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
// Copyright 2023 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cast/streaming/impl/statistics_analyzer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "cast/streaming/impl/statistics_common.h"
|
||||
#include "platform/base/trivial_clock_traits.h"
|
||||
#include "util/chrono_helpers.h"
|
||||
|
||||
namespace openscreen::cast {
|
||||
|
||||
using openscreen::clock_operators::operator<<;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr Clock::duration kAnalysisInterval = std::chrono::milliseconds(500);
|
||||
|
||||
constexpr size_t kMaxRecentPacketInfoMapSize = 1000;
|
||||
constexpr size_t kMaxRecentFrameInfoMapSize = 200;
|
||||
|
||||
constexpr int kDefaultMaxLatencyBucketMs = 800;
|
||||
constexpr int kDefaultBucketWidthMs = 20;
|
||||
|
||||
double InMilliseconds(Clock::duration duration) {
|
||||
return static_cast<double>(to_milliseconds(duration).count());
|
||||
}
|
||||
|
||||
bool IsReceiverEvent(StatisticsEvent::Type event) {
|
||||
return event == StatisticsEvent::Type::kFrameAckSent ||
|
||||
event == StatisticsEvent::Type::kFrameDecoded ||
|
||||
event == StatisticsEvent::Type::kFramePlayedOut ||
|
||||
event == StatisticsEvent::Type::kPacketReceived;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StatisticsAnalyzer::StatisticsAnalyzer(
|
||||
SenderStatsClient* stats_client,
|
||||
ClockNowFunctionPtr now,
|
||||
TaskRunner& task_runner,
|
||||
std::unique_ptr<ClockOffsetEstimator> offset_estimator)
|
||||
: stats_client_(stats_client),
|
||||
offset_estimator_(std::move(offset_estimator)),
|
||||
now_(now),
|
||||
alarm_(now, task_runner),
|
||||
start_time_(now()) {
|
||||
statistics_collector_ = std::make_unique<StatisticsCollector>(now_);
|
||||
InitHistograms();
|
||||
}
|
||||
|
||||
StatisticsAnalyzer::~StatisticsAnalyzer() = default;
|
||||
|
||||
void StatisticsAnalyzer::ScheduleAnalysis() {
|
||||
Clock::time_point next_analysis_time = now_() + kAnalysisInterval;
|
||||
alarm_.Schedule([this] { AnalyzeStatistics(); }, next_analysis_time);
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::InitHistograms() {
|
||||
for (auto& histogram : histograms_.audio) {
|
||||
histogram =
|
||||
SimpleHistogram(0, kDefaultMaxLatencyBucketMs, kDefaultBucketWidthMs);
|
||||
}
|
||||
for (auto& histogram : histograms_.video) {
|
||||
histogram =
|
||||
SimpleHistogram(0, kDefaultMaxLatencyBucketMs, kDefaultBucketWidthMs);
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::AnalyzeStatistics() {
|
||||
ProcessFrameEvents(statistics_collector_->TakeRecentFrameEvents());
|
||||
ProcessPacketEvents(statistics_collector_->TakeRecentPacketEvents());
|
||||
SendStatistics();
|
||||
ScheduleAnalysis();
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::SendStatistics() {
|
||||
if (!stats_client_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Clock::time_point end_time = now_();
|
||||
stats_client_->OnStatisticsUpdated(SenderStats{
|
||||
.audio_statistics =
|
||||
ConstructStatisticsList(end_time, StatisticsEvent::MediaType::kAudio),
|
||||
.audio_histograms = histograms_.audio,
|
||||
.video_statistics =
|
||||
ConstructStatisticsList(end_time, StatisticsEvent::MediaType::kVideo),
|
||||
.video_histograms = histograms_.video});
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::ProcessFrameEvents(
|
||||
const std::vector<FrameEvent>& frame_events) {
|
||||
for (FrameEvent frame_event : frame_events) {
|
||||
offset_estimator_->OnFrameEvent(frame_event);
|
||||
|
||||
FrameStatsMap& frame_stats_map = frame_stats_.Get(frame_event.media_type);
|
||||
auto it = frame_stats_map.find(frame_event.type);
|
||||
if (it == frame_stats_map.end()) {
|
||||
frame_stats_map.insert(std::make_pair(
|
||||
frame_event.type,
|
||||
FrameStatsAggregate{.event_counter = 1,
|
||||
.sum_size = frame_event.size,
|
||||
.sum_delay = frame_event.delay_delta}));
|
||||
} else {
|
||||
++(it->second.event_counter);
|
||||
it->second.sum_size += frame_event.size;
|
||||
it->second.sum_delay += frame_event.delay_delta;
|
||||
}
|
||||
|
||||
RecordEventTimes(frame_event);
|
||||
RecordFrameLatencies(frame_event);
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::ProcessPacketEvents(
|
||||
const std::vector<PacketEvent>& packet_events) {
|
||||
for (PacketEvent packet_event : packet_events) {
|
||||
offset_estimator_->OnPacketEvent(packet_event);
|
||||
|
||||
PacketStatsMap& packet_stats_map =
|
||||
packet_stats_.Get(packet_event.media_type);
|
||||
auto it = packet_stats_map.find(packet_event.type);
|
||||
if (it == packet_stats_map.end()) {
|
||||
packet_stats_map.insert(
|
||||
std::make_pair(packet_event.type,
|
||||
PacketStatsAggregate{.event_counter = 1,
|
||||
.sum_size = packet_event.size}));
|
||||
} else {
|
||||
++(it->second.event_counter);
|
||||
it->second.sum_size += packet_event.size;
|
||||
}
|
||||
|
||||
RecordEventTimes(packet_event);
|
||||
if (packet_event.type == StatisticsEvent::Type::kPacketSentToNetwork ||
|
||||
packet_event.type == StatisticsEvent::Type::kPacketReceived) {
|
||||
RecordPacketLatencies(packet_event);
|
||||
} else if (packet_event.type ==
|
||||
StatisticsEvent::Type::kPacketRetransmitted) {
|
||||
// We only measure network latency for packets that are not retransmitted.
|
||||
ErasePacketInfo(packet_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::RecordFrameLatencies(const FrameEvent& frame_event) {
|
||||
FrameInfoMap& frame_infos = recent_frame_infos_.Get(frame_event.media_type);
|
||||
|
||||
// Event is too old, don't bother.
|
||||
const bool map_is_full = frame_infos.size() == kMaxRecentFrameInfoMapSize;
|
||||
if (map_is_full && frame_event.rtp_timestamp <= frame_infos.begin()->first) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = frame_infos.find(frame_event.rtp_timestamp);
|
||||
if (it == frame_infos.end()) {
|
||||
if (map_is_full) {
|
||||
frame_infos.erase(frame_infos.begin());
|
||||
}
|
||||
|
||||
auto emplace_result =
|
||||
frame_infos.emplace(frame_event.rtp_timestamp, FrameInfo{});
|
||||
OSP_CHECK(emplace_result.second);
|
||||
it = emplace_result.first;
|
||||
}
|
||||
|
||||
switch (frame_event.type) {
|
||||
case StatisticsEvent::Type::kFrameCaptureBegin:
|
||||
it->second.capture_begin_time = frame_event.timestamp;
|
||||
break;
|
||||
|
||||
case StatisticsEvent::Type::kFrameCaptureEnd: {
|
||||
it->second.capture_end_time = frame_event.timestamp;
|
||||
if (it->second.capture_begin_time != Clock::time_point::min()) {
|
||||
const Clock::duration capture_latency =
|
||||
frame_event.timestamp - it->second.capture_begin_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgCaptureLatencyMs,
|
||||
capture_latency, frame_event.media_type);
|
||||
AddToHistogram(HistogramType::kCaptureLatencyMs, frame_event.media_type,
|
||||
InMilliseconds(capture_latency));
|
||||
}
|
||||
} break;
|
||||
|
||||
case StatisticsEvent::Type::kFrameEncoded: {
|
||||
it->second.encode_end_time = frame_event.timestamp;
|
||||
if (it->second.capture_end_time != Clock::time_point::min()) {
|
||||
const Clock::duration encode_latency =
|
||||
frame_event.timestamp - it->second.capture_end_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgEncodeTimeMs, encode_latency,
|
||||
frame_event.media_type);
|
||||
AddToHistogram(HistogramType::kEncodeTimeMs, frame_event.media_type,
|
||||
InMilliseconds(encode_latency));
|
||||
}
|
||||
} break;
|
||||
|
||||
// Frame latency is the time from when the frame is encoded until the
|
||||
// receiver ack for the frame is sent.
|
||||
case StatisticsEvent::Type::kFrameAckSent: {
|
||||
const auto adjusted_timestamp =
|
||||
ToSenderTimestamp(frame_event.timestamp, frame_event.media_type);
|
||||
if (!adjusted_timestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (it->second.encode_end_time != Clock::time_point::min()) {
|
||||
const Clock::duration frame_latency =
|
||||
*adjusted_timestamp - it->second.encode_end_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgFrameLatencyMs, frame_latency,
|
||||
frame_event.media_type);
|
||||
}
|
||||
} break;
|
||||
|
||||
case StatisticsEvent::Type::kFramePlayedOut: {
|
||||
const auto adjusted_timestamp =
|
||||
ToSenderTimestamp(frame_event.timestamp, frame_event.media_type);
|
||||
if (!adjusted_timestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (it->second.capture_begin_time != Clock::time_point::min()) {
|
||||
const Clock::duration e2e_latency =
|
||||
*adjusted_timestamp - it->second.capture_begin_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgEndToEndLatencyMs,
|
||||
e2e_latency, frame_event.media_type);
|
||||
AddToHistogram(HistogramType::kEndToEndLatencyMs,
|
||||
frame_event.media_type, InMilliseconds(e2e_latency));
|
||||
}
|
||||
|
||||
// Positive delay means the frame is late.
|
||||
if (frame_event.delay_delta > Clock::duration::zero()) {
|
||||
session_stats_.Get(frame_event.media_type).late_frame_counter += 1;
|
||||
AddToHistogram(HistogramType::kFrameLatenessMs, frame_event.media_type,
|
||||
InMilliseconds(frame_event.delay_delta));
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::RecordPacketLatencies(
|
||||
const PacketEvent& packet_event) {
|
||||
FrameInfoMap& frame_infos = recent_frame_infos_.Get(packet_event.media_type);
|
||||
|
||||
// Queueing latency is the time from when a frame is encoded to when the
|
||||
// packet is first sent.
|
||||
if (packet_event.type == StatisticsEvent::Type::kPacketSentToNetwork) {
|
||||
const auto it = frame_infos.find(packet_event.rtp_timestamp);
|
||||
|
||||
// We have an encode end time for a frame associated with this packet.
|
||||
if (it != frame_infos.end()) {
|
||||
const Clock::duration queueing_latency =
|
||||
packet_event.timestamp - it->second.encode_end_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgQueueingLatencyMs,
|
||||
queueing_latency, packet_event.media_type);
|
||||
AddToHistogram(HistogramType::kQueueingLatencyMs, packet_event.media_type,
|
||||
InMilliseconds(queueing_latency));
|
||||
}
|
||||
}
|
||||
|
||||
StatisticsAnalyzer::PacketKey key =
|
||||
std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id);
|
||||
PacketInfoMap& packet_infos =
|
||||
recent_packet_infos_.Get(packet_event.media_type);
|
||||
|
||||
const auto it = packet_infos.find(key);
|
||||
if (it == packet_infos.end()) {
|
||||
packet_infos.insert(
|
||||
std::make_pair(key, PacketInfo{.timestamp = packet_event.timestamp,
|
||||
.type = packet_event.type}));
|
||||
if (packet_infos.size() > kMaxRecentPacketInfoMapSize) {
|
||||
packet_infos.erase(packet_infos.begin());
|
||||
}
|
||||
} else { // We know when this packet was sent, and when it arrived.
|
||||
PacketInfo value = it->second;
|
||||
StatisticsEvent::Type recorded_type = value.type;
|
||||
Clock::time_point packet_sent_time;
|
||||
Clock::time_point packet_received_time;
|
||||
if (recorded_type == StatisticsEvent::Type::kPacketSentToNetwork &&
|
||||
packet_event.type == StatisticsEvent::Type::kPacketReceived) {
|
||||
packet_sent_time = value.timestamp;
|
||||
packet_received_time = packet_event.timestamp;
|
||||
} else if (recorded_type == StatisticsEvent::Type::kPacketReceived &&
|
||||
packet_event.type ==
|
||||
StatisticsEvent::Type::kPacketSentToNetwork) {
|
||||
packet_sent_time = packet_event.timestamp;
|
||||
packet_received_time = value.timestamp;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
packet_infos.erase(it);
|
||||
|
||||
// Use the offset estimator directly since we are trying to calculate the
|
||||
// average network latency.
|
||||
const std::optional<Clock::duration> receiver_offset =
|
||||
offset_estimator_->GetEstimatedOffset();
|
||||
if (!receiver_offset) {
|
||||
return;
|
||||
}
|
||||
packet_received_time -= *receiver_offset;
|
||||
|
||||
const auto latency = packet_received_time - packet_sent_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgNetworkLatencyMs, latency,
|
||||
packet_event.media_type);
|
||||
AddToHistogram(HistogramType::kNetworkLatencyMs, packet_event.media_type,
|
||||
InMilliseconds(latency));
|
||||
|
||||
// Packet latency is the time from when a frame is encoded until when the
|
||||
// packet is received.
|
||||
const auto frame_it = frame_infos.find(packet_event.rtp_timestamp);
|
||||
if (frame_it != frame_infos.end()) {
|
||||
const Clock::duration packet_latency =
|
||||
packet_received_time - frame_it->second.encode_end_time;
|
||||
AddToLatencyAggregrate(StatisticType::kAvgPacketLatencyMs, packet_latency,
|
||||
packet_event.media_type);
|
||||
AddToHistogram(HistogramType::kPacketLatencyMs, packet_event.media_type,
|
||||
InMilliseconds(packet_latency));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::RecordEventTimes(const StatisticsEvent& event) {
|
||||
SessionStats& session_stats = session_stats_.Get(event.media_type);
|
||||
|
||||
Clock::time_point sender_timestamp = event.timestamp;
|
||||
if (IsReceiverEvent(event.type)) {
|
||||
const auto latency = offset_estimator_->GetEstimatedLatency();
|
||||
if (latency) {
|
||||
const Clock::time_point estimated_sent_time =
|
||||
event.received_timestamp - *latency;
|
||||
session_stats.last_response_received_time = std::max(
|
||||
session_stats.last_response_received_time, estimated_sent_time);
|
||||
}
|
||||
|
||||
const auto result = ToSenderTimestamp(event.timestamp, event.media_type);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
sender_timestamp = *result;
|
||||
}
|
||||
|
||||
session_stats.first_event_time =
|
||||
std::min(session_stats.first_event_time, sender_timestamp);
|
||||
session_stats.last_event_time =
|
||||
std::max(session_stats.last_event_time, sender_timestamp);
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::ErasePacketInfo(const PacketEvent& packet_event) {
|
||||
const StatisticsAnalyzer::PacketKey key =
|
||||
std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id);
|
||||
PacketInfoMap& packet_infos =
|
||||
recent_packet_infos_.Get(packet_event.media_type);
|
||||
packet_infos.erase(key);
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::AddToLatencyAggregrate(
|
||||
StatisticType latency_stat,
|
||||
Clock::duration latency_delta,
|
||||
StatisticsEvent::MediaType media_type) {
|
||||
LatencyStatsMap& latency_stats = latency_stats_.Get(media_type);
|
||||
|
||||
auto it = latency_stats.find(latency_stat);
|
||||
if (it == latency_stats.end()) {
|
||||
latency_stats.insert(std::make_pair(
|
||||
latency_stat, LatencyStatsAggregate{.data_point_counter = 1,
|
||||
.sum_latency = latency_delta}));
|
||||
} else {
|
||||
++(it->second.data_point_counter);
|
||||
it->second.sum_latency += latency_delta;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::AddToHistogram(HistogramType histogram,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
int64_t sample) {
|
||||
histograms_.Get(media_type)[static_cast<int>(histogram)].Add(sample);
|
||||
}
|
||||
|
||||
SenderStats::StatisticsList StatisticsAnalyzer::ConstructStatisticsList(
|
||||
Clock::time_point end_time,
|
||||
StatisticsEvent::MediaType media_type) {
|
||||
SenderStats::StatisticsList stats_list;
|
||||
|
||||
PopulateFrameCountStat(StatisticsEvent::Type::kFrameDroppedByEncoder,
|
||||
StatisticType::kNumFramesDroppedByEncoder, media_type,
|
||||
stats_list);
|
||||
|
||||
PopulateFrameCountStat(StatisticsEvent::Type::kFrameCaptureEnd,
|
||||
StatisticType::kNumFramesCaptured, media_type,
|
||||
stats_list);
|
||||
|
||||
// kEnqueueFps
|
||||
PopulateFpsStat(StatisticsEvent::Type::kFrameEncoded,
|
||||
StatisticType::kEnqueueFps, media_type, end_time, stats_list);
|
||||
|
||||
constexpr StatisticType kSupportedLatencyStats[] = {
|
||||
StatisticType::kAvgEncodeTimeMs, StatisticType::kAvgCaptureLatencyMs,
|
||||
StatisticType::kAvgQueueingLatencyMs, StatisticType::kAvgNetworkLatencyMs,
|
||||
StatisticType::kAvgPacketLatencyMs, StatisticType::kAvgFrameLatencyMs,
|
||||
StatisticType::kAvgEndToEndLatencyMs,
|
||||
};
|
||||
for (StatisticType type : kSupportedLatencyStats) {
|
||||
PopulateAvgLatencyStat(type, media_type, stats_list);
|
||||
}
|
||||
|
||||
// kEncodeRateKbps
|
||||
PopulateFrameBitrateStat(StatisticsEvent::Type::kFrameEncoded,
|
||||
StatisticType::kEncodeRateKbps, media_type, end_time,
|
||||
stats_list);
|
||||
|
||||
// kPacketTransmissionRateKbps
|
||||
PopulatePacketBitrateStat(StatisticsEvent::Type::kPacketSentToNetwork,
|
||||
StatisticType::kPacketTransmissionRateKbps,
|
||||
media_type, end_time, stats_list);
|
||||
|
||||
// kNumPacketsSent
|
||||
PopulatePacketCountStat(StatisticsEvent::Type::kPacketSentToNetwork,
|
||||
StatisticType::kNumPacketsSent, media_type,
|
||||
stats_list);
|
||||
|
||||
// kNumPacketsReceived
|
||||
PopulatePacketCountStat(StatisticsEvent::Type::kPacketReceived,
|
||||
StatisticType::kNumPacketsReceived, media_type,
|
||||
stats_list);
|
||||
|
||||
// kTimeSinceLastReceiverResponseMs
|
||||
// kFirstEventTimeMs
|
||||
// kLastEventTimeMs
|
||||
// kNumLateFrames
|
||||
PopulateSessionStats(media_type, end_time, stats_list);
|
||||
|
||||
return stats_list;
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulatePacketCountStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
PacketStatsMap& stats_map = packet_stats_.Get(media_type);
|
||||
|
||||
auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
stats_list[static_cast<int>(stat)] = it->second.event_counter;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateFrameCountStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
FrameStatsMap& stats_map = frame_stats_.Get(media_type);
|
||||
|
||||
const auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
stats_list[static_cast<int>(stat)] = it->second.event_counter;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateFpsStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
FrameStatsMap& stats_map = frame_stats_.Get(media_type);
|
||||
|
||||
const auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
const Clock::duration duration = end_time - start_time_;
|
||||
if (duration != Clock::duration::zero()) {
|
||||
const int count = it->second.event_counter;
|
||||
const double fps = (count / InMilliseconds(duration)) * 1000;
|
||||
stats_list[static_cast<int>(stat)] = fps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateAvgLatencyStat(
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
SenderStats::StatisticsList& stats_list
|
||||
|
||||
) {
|
||||
LatencyStatsMap& latency_map = latency_stats_.Get(media_type);
|
||||
|
||||
const auto it = latency_map.find(stat);
|
||||
if (it != latency_map.end() && it->second.data_point_counter > 0) {
|
||||
const double avg_latency =
|
||||
InMilliseconds(it->second.sum_latency) / it->second.data_point_counter;
|
||||
stats_list[static_cast<int>(stat)] = avg_latency;
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateFrameBitrateStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
FrameStatsMap& stats_map = frame_stats_.Get(media_type);
|
||||
|
||||
const auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
const Clock::duration duration = end_time - start_time_;
|
||||
if (duration != Clock::duration::zero()) {
|
||||
const double kbps = it->second.sum_size / InMilliseconds(duration) * 8;
|
||||
stats_list[static_cast<int>(stat)] = kbps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulatePacketBitrateStat(
|
||||
StatisticsEvent::Type event,
|
||||
StatisticType stat,
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
PacketStatsMap& stats_map = packet_stats_.Get(media_type);
|
||||
|
||||
auto it = stats_map.find(event);
|
||||
if (it != stats_map.end()) {
|
||||
const Clock::duration duration = end_time - start_time_;
|
||||
if (duration != Clock::duration::zero()) {
|
||||
const double kbps = it->second.sum_size / InMilliseconds(duration) * 8;
|
||||
stats_list[static_cast<int>(stat)] = kbps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsAnalyzer::PopulateSessionStats(
|
||||
StatisticsEvent::MediaType media_type,
|
||||
Clock::time_point end_time,
|
||||
SenderStats::StatisticsList& stats_list) {
|
||||
SessionStats& session_stats = session_stats_.Get(media_type);
|
||||
|
||||
if (session_stats.first_event_time != Clock::time_point::min()) {
|
||||
stats_list[static_cast<int>(StatisticType::kFirstEventTimeMs)] =
|
||||
InMilliseconds(session_stats.first_event_time.time_since_epoch());
|
||||
}
|
||||
|
||||
if (session_stats.last_event_time != Clock::time_point::min()) {
|
||||
stats_list[static_cast<int>(StatisticType::kLastEventTimeMs)] =
|
||||
InMilliseconds(session_stats.last_event_time.time_since_epoch());
|
||||
}
|
||||
|
||||
if (session_stats.last_response_received_time != Clock::time_point::min()) {
|
||||
stats_list[static_cast<int>(
|
||||
StatisticType::kTimeSinceLastReceiverResponseMs)] =
|
||||
InMilliseconds(end_time - session_stats.last_response_received_time);
|
||||
}
|
||||
|
||||
stats_list[static_cast<int>(StatisticType::kNumLateFrames)] =
|
||||
session_stats.late_frame_counter;
|
||||
}
|
||||
|
||||
std::optional<Clock::time_point> StatisticsAnalyzer::ToSenderTimestamp(
|
||||
Clock::time_point receiver_timestamp,
|
||||
StatisticsEvent::MediaType media_type) const {
|
||||
const std::optional<Clock::duration> receiver_offset =
|
||||
offset_estimator_->GetEstimatedOffset();
|
||||
if (!receiver_offset) {
|
||||
return {};
|
||||
}
|
||||
return receiver_timestamp - *receiver_offset;
|
||||
}
|
||||
|
||||
} // namespace openscreen::cast
|
||||
Loading…
Add table
Add a link
Reference in a new issue