1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cast/streaming/sender.h"
6
7 #include <algorithm>
8 #include <chrono>
9 #include <ratio>
10
11 #include "cast/streaming/session_config.h"
12 #include "util/chrono_helpers.h"
13 #include "util/osp_logging.h"
14 #include "util/std_util.h"
15 #include "util/trace_logging.h"
16
17 namespace openscreen {
18 namespace cast {
19
20 using openscreen::operator<<; // For std::chrono::duration logging.
21
Sender(Environment * environment,SenderPacketRouter * packet_router,SessionConfig config,RtpPayloadType rtp_payload_type)22 Sender::Sender(Environment* environment,
23 SenderPacketRouter* packet_router,
24 SessionConfig config,
25 RtpPayloadType rtp_payload_type)
26 : config_(config),
27 packet_router_(packet_router),
28 rtcp_session_(config.sender_ssrc,
29 config.receiver_ssrc,
30 environment->now()),
31 rtcp_parser_(&rtcp_session_, this),
32 sender_report_builder_(&rtcp_session_),
33 rtp_packetizer_(rtp_payload_type,
34 config.sender_ssrc,
35 packet_router_->max_packet_size()),
36 rtp_timebase_(config.rtp_timebase),
37 crypto_(config.aes_secret_key, config.aes_iv_mask),
38 target_playout_delay_(config.target_playout_delay) {
39 OSP_DCHECK(packet_router_);
40 OSP_DCHECK_NE(rtcp_session_.sender_ssrc(), rtcp_session_.receiver_ssrc());
41 OSP_DCHECK_GT(rtp_timebase_, 0);
42 OSP_DCHECK(target_playout_delay_ > milliseconds::zero());
43
44 pending_sender_report_.reference_time = SenderPacketRouter::kNever;
45
46 packet_router_->OnSenderCreated(rtcp_session_.receiver_ssrc(), this);
47 }
48
~Sender()49 Sender::~Sender() {
50 packet_router_->OnSenderDestroyed(rtcp_session_.receiver_ssrc());
51 }
52
SetObserver(Sender::Observer * observer)53 void Sender::SetObserver(Sender::Observer* observer) {
54 observer_ = observer;
55 }
56
GetInFlightFrameCount() const57 int Sender::GetInFlightFrameCount() const {
58 return num_frames_in_flight_;
59 }
60
GetInFlightMediaDuration(RtpTimeTicks next_frame_rtp_timestamp) const61 Clock::duration Sender::GetInFlightMediaDuration(
62 RtpTimeTicks next_frame_rtp_timestamp) const {
63 if (num_frames_in_flight_ == 0) {
64 return Clock::duration::zero(); // No frames are currently in-flight.
65 }
66
67 const PendingFrameSlot& oldest_slot = *get_slot_for(checkpoint_frame_id_ + 1);
68 // Note: The oldest slot's frame cannot have been canceled because the
69 // protocol does not allow ACK'ing this particular frame without also moving
70 // the checkpoint forward. See "CST2 feedback" discussion in rtp_defines.h.
71 OSP_DCHECK(oldest_slot.is_active_for_frame(checkpoint_frame_id_ + 1));
72
73 return (next_frame_rtp_timestamp - oldest_slot.frame->rtp_timestamp)
74 .ToDuration<Clock::duration>(rtp_timebase_);
75 }
76
GetMaxInFlightMediaDuration() const77 Clock::duration Sender::GetMaxInFlightMediaDuration() const {
78 // Assumption: The total amount of allowed in-flight media should equal the
79 // half of the playout delay window, plus the amount of time it takes to
80 // receive an ACK from the Receiver.
81 //
82 // Why half of the playout delay window? It's assumed here that capture and
83 // media encoding, which occur before EnqueueFrame() is called, are executing
84 // within the first half of the playout delay window. This leaves the second
85 // half for executing all network transmits/re-transmits, plus decoding and
86 // play-out at the Receiver.
87 return (target_playout_delay_ / 2) + (round_trip_time_ / 2);
88 }
89
NeedsKeyFrame() const90 bool Sender::NeedsKeyFrame() const {
91 return last_enqueued_key_frame_id_ <= picture_lost_at_frame_id_;
92 }
93
GetNextFrameId() const94 FrameId Sender::GetNextFrameId() const {
95 return last_enqueued_frame_id_ + 1;
96 }
97
EnqueueFrame(const EncodedFrame & frame)98 Sender::EnqueueFrameResult Sender::EnqueueFrame(const EncodedFrame& frame) {
99 // Assume the fields of the |frame| have all been set correctly, with
100 // monotonically increasing timestamps and a valid pointer to the data.
101 OSP_DCHECK_EQ(frame.frame_id, GetNextFrameId());
102 OSP_DCHECK_GE(frame.referenced_frame_id, FrameId::first());
103 if (frame.frame_id != FrameId::first()) {
104 OSP_DCHECK_GT(frame.rtp_timestamp, pending_sender_report_.rtp_timestamp);
105 OSP_DCHECK_GT(frame.reference_time, pending_sender_report_.reference_time);
106 }
107 OSP_DCHECK(frame.data.data());
108
109 // Check whether enqueuing the frame would exceed the design limit for the
110 // span of FrameIds. Even if |num_frames_in_flight_| is less than
111 // kMaxUnackedFrames, it's the span of FrameIds that is restricted.
112 if ((frame.frame_id - checkpoint_frame_id_) > kMaxUnackedFrames) {
113 return REACHED_ID_SPAN_LIMIT;
114 }
115
116 // Check whether enqueuing the frame would exceed the current maximum media
117 // duration limit.
118 if (GetInFlightMediaDuration(frame.rtp_timestamp) >
119 GetMaxInFlightMediaDuration()) {
120 return MAX_DURATION_IN_FLIGHT;
121 }
122
123 // Encrypt the frame and initialize the slot tracking its sending.
124 PendingFrameSlot* const slot = get_slot_for(frame.frame_id);
125 OSP_DCHECK(!slot->frame);
126 slot->frame = crypto_.Encrypt(frame);
127 const int packet_count = rtp_packetizer_.ComputeNumberOfPackets(*slot->frame);
128 if (packet_count <= 0) {
129 slot->frame.reset();
130 return PAYLOAD_TOO_LARGE;
131 }
132 slot->send_flags.Resize(packet_count, YetAnotherBitVector::SET);
133 slot->packet_sent_times.assign(packet_count, SenderPacketRouter::kNever);
134
135 // Officially record the "enqueue."
136 ++num_frames_in_flight_;
137 last_enqueued_frame_id_ = slot->frame->frame_id;
138 OSP_DCHECK_LE(num_frames_in_flight_,
139 last_enqueued_frame_id_ - checkpoint_frame_id_);
140 if (slot->frame->dependency == EncodedFrame::KEY_FRAME) {
141 last_enqueued_key_frame_id_ = slot->frame->frame_id;
142 }
143
144 // Update the target playout delay, if necessary.
145 if (slot->frame->new_playout_delay > milliseconds::zero()) {
146 target_playout_delay_ = slot->frame->new_playout_delay;
147 playout_delay_change_at_frame_id_ = slot->frame->frame_id;
148 }
149
150 // Update the lip-sync information for the next Sender Report.
151 pending_sender_report_.reference_time = slot->frame->reference_time;
152 pending_sender_report_.rtp_timestamp = slot->frame->rtp_timestamp;
153
154 // If the round trip time hasn't been computed yet, immediately send a RTCP
155 // packet (i.e., before the RTP packets are sent). The RTCP packet will
156 // provide a Sender Report which contains the required lip-sync information
157 // the Receiver needs for timing the media playout.
158 //
159 // Detail: Working backwards, if the round trip time is not known, then this
160 // Sender has never processed a Receiver Report. Thus, the Receiver has never
161 // provided a Receiver Report, which it can only do after having processed a
162 // Sender Report from this Sender. Thus, this Sender really needs to send
163 // that, right now!
164 if (round_trip_time_ == Clock::duration::zero()) {
165 packet_router_->RequestRtcpSend(rtcp_session_.receiver_ssrc());
166 }
167
168 // Re-activate RTP sending if it was suspended.
169 packet_router_->RequestRtpSend(rtcp_session_.receiver_ssrc());
170
171 return OK;
172 }
173
CancelInFlightData()174 void Sender::CancelInFlightData() {
175 while (checkpoint_frame_id_ <= last_enqueued_frame_id_) {
176 ++checkpoint_frame_id_;
177 CancelPendingFrame(checkpoint_frame_id_);
178 }
179 }
180
OnReceivedRtcpPacket(Clock::time_point arrival_time,absl::Span<const uint8_t> packet)181 void Sender::OnReceivedRtcpPacket(Clock::time_point arrival_time,
182 absl::Span<const uint8_t> packet) {
183 rtcp_packet_arrival_time_ = arrival_time;
184 // This call to Parse() invoke zero or more of the OnReceiverXYZ() methods in
185 // the current call stack:
186 if (rtcp_parser_.Parse(packet, last_enqueued_frame_id_)) {
187 packet_router_->OnRtcpReceived(arrival_time, round_trip_time_);
188 }
189 }
190
GetRtcpPacketForImmediateSend(Clock::time_point send_time,absl::Span<uint8_t> buffer)191 absl::Span<uint8_t> Sender::GetRtcpPacketForImmediateSend(
192 Clock::time_point send_time,
193 absl::Span<uint8_t> buffer) {
194 if (pending_sender_report_.reference_time == SenderPacketRouter::kNever) {
195 // Cannot send a report if one is not available (i.e., a frame has never
196 // been enqueued).
197 return buffer.subspan(0, 0);
198 }
199
200 // The Sender Report to be sent is a snapshot of the "pending Sender Report,"
201 // but with its timestamp fields modified. First, the reference time is set to
202 // the RTCP packet's send time. Then, the corresponding RTP timestamp is
203 // translated to match (for lip-sync).
204 RtcpSenderReport sender_report = pending_sender_report_;
205 sender_report.reference_time = send_time;
206 sender_report.rtp_timestamp += RtpTimeDelta::FromDuration(
207 sender_report.reference_time - pending_sender_report_.reference_time,
208 rtp_timebase_);
209
210 return sender_report_builder_.BuildPacket(sender_report, buffer).first;
211 }
212
GetRtpPacketForImmediateSend(Clock::time_point send_time,absl::Span<uint8_t> buffer)213 absl::Span<uint8_t> Sender::GetRtpPacketForImmediateSend(
214 Clock::time_point send_time,
215 absl::Span<uint8_t> buffer) {
216 ChosenPacket chosen = ChooseNextRtpPacketNeedingSend();
217
218 // If no packets need sending (i.e., all packets have been sent at least once
219 // and do not need to be re-sent yet), check whether a Kickstart packet should
220 // be sent. It's possible that there has been complete packet loss of some
221 // frames, and the Receiver may not be aware of the existence of the latest
222 // frame(s). Kickstarting is the only way the Receiver can discover the newer
223 // frames it doesn't know about.
224 if (!chosen) {
225 const ChosenPacketAndWhen kickstart = ChooseKickstartPacket();
226 if (kickstart.when > send_time) {
227 // Nothing to send, so return "empty" signal to the packet router. The
228 // packet router will suspend RTP sending until this Sender explicitly
229 // resumes it.
230 return buffer.subspan(0, 0);
231 }
232 chosen = kickstart;
233 OSP_DCHECK(chosen);
234 }
235
236 const absl::Span<uint8_t> result = rtp_packetizer_.GeneratePacket(
237 *chosen.slot->frame, chosen.packet_id, buffer);
238 chosen.slot->send_flags.Clear(chosen.packet_id);
239 chosen.slot->packet_sent_times[chosen.packet_id] = send_time;
240
241 ++pending_sender_report_.send_packet_count;
242 // According to RFC3550, the octet count does not include the RTP header. The
243 // following is just a good approximation, however, because the header size
244 // will very infrequently be 4 bytes greater (see
245 // RtpPacketizer::kAdaptiveLatencyHeaderSize). No known Cast Streaming
246 // Receiver implementations use this for anything, and so this should be fine.
247 const int approximate_octet_count =
248 static_cast<int>(result.size()) - RtpPacketizer::kBaseRtpHeaderSize;
249 OSP_DCHECK_GE(approximate_octet_count, 0);
250 pending_sender_report_.send_octet_count += approximate_octet_count;
251
252 return result;
253 }
254
GetRtpResumeTime()255 Clock::time_point Sender::GetRtpResumeTime() {
256 if (ChooseNextRtpPacketNeedingSend()) {
257 return Alarm::kImmediately;
258 }
259 return ChooseKickstartPacket().when;
260 }
261
OnReceiverReferenceTimeAdvanced(Clock::time_point reference_time)262 void Sender::OnReceiverReferenceTimeAdvanced(Clock::time_point reference_time) {
263 // Not used.
264 }
265
OnReceiverReport(const RtcpReportBlock & receiver_report)266 void Sender::OnReceiverReport(const RtcpReportBlock& receiver_report) {
267 OSP_DCHECK_NE(rtcp_packet_arrival_time_, SenderPacketRouter::kNever);
268
269 const Clock::duration total_delay =
270 rtcp_packet_arrival_time_ -
271 sender_report_builder_.GetRecentReportTime(
272 receiver_report.last_status_report_id, rtcp_packet_arrival_time_);
273 const auto non_network_delay =
274 Clock::to_duration(receiver_report.delay_since_last_report);
275
276 // Round trip time measurement: This is the time elapsed since the Sender
277 // Report was sent, minus the time the Receiver did other stuff before sending
278 // the Receiver Report back.
279 //
280 // If the round trip time seems to be less than or equal to zero, assume clock
281 // imprecision by one or both peers caused a bad value to be calculated. The
282 // true value is likely very close to zero (i.e., this is ideal network
283 // behavior); and so just represent this as 75 µs, an optimistic
284 // wired-Ethernet LAN ping time.
285 constexpr auto kNearZeroRoundTripTime = Clock::to_duration(microseconds(75));
286 static_assert(kNearZeroRoundTripTime > Clock::duration::zero(),
287 "More precision in Clock::duration needed!");
288 const Clock::duration measurement =
289 std::max(total_delay - non_network_delay, kNearZeroRoundTripTime);
290
291 // Validate the measurement by using the current target playout delay as a
292 // "reasonable upper-bound." It's certainly possible that the actual network
293 // round-trip time could exceed the target playout delay, but that would mean
294 // the current network performance is totally inadequate for streaming anyway.
295 if (measurement > target_playout_delay_) {
296 OSP_LOG_WARN << "Invalidating a round-trip time measurement ("
297 << measurement
298 << ") since it exceeds the current target playout delay ("
299 << target_playout_delay_ << ").";
300 return;
301 }
302
303 // Measurements will typically have high variance. Use a simple smoothing
304 // filter to track a short-term average that changes less drastically.
305 if (round_trip_time_ == Clock::duration::zero()) {
306 round_trip_time_ = measurement;
307 } else {
308 // Arbitrary constant, to provide 1/8 weight to the new measurement, and 7/8
309 // weight to the old estimate, which seems to work well for de-noising the
310 // estimate.
311 constexpr int kInertia = 7;
312 round_trip_time_ =
313 (kInertia * round_trip_time_ + measurement) / (kInertia + 1);
314 }
315 TRACE_SCOPED(TraceCategory::kSender, "UpdatedRTT");
316 }
317
OnReceiverIndicatesPictureLoss()318 void Sender::OnReceiverIndicatesPictureLoss() {
319 TRACE_DEFAULT_SCOPED(TraceCategory::kSender);
320 // The Receiver will continue the PLI notifications until it has received a
321 // key frame. Thus, if a key frame is already in-flight, don't make a state
322 // change that would cause this Sender to force another expensive key frame.
323 if (checkpoint_frame_id_ < last_enqueued_key_frame_id_) {
324 return;
325 }
326
327 picture_lost_at_frame_id_ = checkpoint_frame_id_;
328
329 if (observer_) {
330 observer_->OnPictureLost();
331 }
332
333 // Note: It may seem that all pending frames should be canceled until
334 // EnqueueFrame() is called with a key frame. However:
335 //
336 // 1. The Receiver should still be the main authority on what frames/packets
337 // are being ACK'ed and NACK'ed.
338 //
339 // 2. It may be desirable for the Receiver to be "limping along" in the
340 // meantime. For example, video may be corrupted but mostly watchable,
341 // and so it's best for the Sender to continue sending the non-key frames
342 // until the Receiver indicates otherwise.
343 }
344
OnReceiverCheckpoint(FrameId frame_id,milliseconds playout_delay)345 void Sender::OnReceiverCheckpoint(FrameId frame_id,
346 milliseconds playout_delay) {
347 TRACE_DEFAULT_SCOPED(TraceCategory::kSender);
348 if (frame_id > last_enqueued_frame_id_) {
349 OSP_LOG_ERROR
350 << "Ignoring checkpoint for " << latest_expected_frame_id_
351 << " because this Sender could not have sent any frames after "
352 << last_enqueued_frame_id_ << '.';
353 return;
354 }
355 // CompoundRtcpParser should guarantee this:
356 OSP_DCHECK(playout_delay >= milliseconds::zero());
357
358 while (checkpoint_frame_id_ < frame_id) {
359 ++checkpoint_frame_id_;
360 CancelPendingFrame(checkpoint_frame_id_);
361 }
362 latest_expected_frame_id_ = std::max(latest_expected_frame_id_, frame_id);
363
364 if (playout_delay != target_playout_delay_ &&
365 frame_id >= playout_delay_change_at_frame_id_) {
366 OSP_LOG_WARN << "Sender's target playout delay (" << target_playout_delay_
367 << ") disagrees with the Receiver's (" << playout_delay << ")";
368 }
369 }
370
OnReceiverHasFrames(std::vector<FrameId> acks)371 void Sender::OnReceiverHasFrames(std::vector<FrameId> acks) {
372 OSP_DCHECK(!acks.empty() && AreElementsSortedAndUnique(acks));
373
374 if (acks.back() > last_enqueued_frame_id_) {
375 OSP_LOG_ERROR << "Ignoring individual frame ACKs: ACKing frame "
376 << latest_expected_frame_id_
377 << " is invalid because this Sender could not have sent any "
378 "frames after "
379 << last_enqueued_frame_id_ << '.';
380 return;
381 }
382
383 for (FrameId id : acks) {
384 CancelPendingFrame(id);
385 }
386 latest_expected_frame_id_ = std::max(latest_expected_frame_id_, acks.back());
387 }
388
OnReceiverIsMissingPackets(std::vector<PacketNack> nacks)389 void Sender::OnReceiverIsMissingPackets(std::vector<PacketNack> nacks) {
390 OSP_DCHECK(!nacks.empty() && AreElementsSortedAndUnique(nacks));
391 OSP_DCHECK_NE(rtcp_packet_arrival_time_, SenderPacketRouter::kNever);
392
393 // This is a point-in-time threshold that indicates whether each NACK will
394 // trigger a packet retransmit. The threshold is based on the network round
395 // trip time because a Receiver's NACK may have been issued while the needed
396 // packet was in-flight from the Sender. In such cases, the Receiver's NACK is
397 // likely stale and this Sender should not redundantly re-transmit the packet
398 // again.
399 const Clock::time_point too_recent_a_send_time =
400 rtcp_packet_arrival_time_ - round_trip_time_;
401
402 // Iterate over all the NACKs...
403 bool need_to_send = false;
404 for (auto nack_it = nacks.begin(); nack_it != nacks.end();) {
405 // Find the slot associated with the NACK's frame ID.
406 const FrameId frame_id = nack_it->frame_id;
407 PendingFrameSlot* slot = nullptr;
408 if (frame_id <= last_enqueued_frame_id_) {
409 PendingFrameSlot* const candidate_slot = get_slot_for(frame_id);
410 if (candidate_slot->is_active_for_frame(frame_id)) {
411 slot = candidate_slot;
412 }
413 }
414
415 // If no slot was found (i.e., the NACK is invalid) for the frame, skip-over
416 // all other NACKs for the same frame. While it seems to be a bug that the
417 // Receiver would attempt to NACK a frame that does not yet exist, this can
418 // happen in rare cases where RTCP packets arrive out-of-order (i.e., the
419 // network shuffled them).
420 if (!slot) {
421 TRACE_SCOPED(TraceCategory::kSender, "MissingNackSlot");
422 for (++nack_it; nack_it != nacks.end() && nack_it->frame_id == frame_id;
423 ++nack_it) {
424 }
425 continue;
426 }
427
428 latest_expected_frame_id_ = std::max(latest_expected_frame_id_, frame_id);
429
430 const auto HandleIndividualNack = [&](FramePacketId packet_id) {
431 if (slot->packet_sent_times[packet_id] <= too_recent_a_send_time) {
432 slot->send_flags.Set(packet_id);
433 need_to_send = true;
434 }
435 };
436 const FramePacketId range_end = slot->packet_sent_times.size();
437 if (nack_it->packet_id == kAllPacketsLost) {
438 for (FramePacketId packet_id = 0; packet_id < range_end; ++packet_id) {
439 HandleIndividualNack(packet_id);
440 }
441 ++nack_it;
442 } else {
443 do {
444 if (nack_it->packet_id < range_end) {
445 HandleIndividualNack(nack_it->packet_id);
446 } else {
447 OSP_LOG_WARN
448 << "Ignoring NACK for packet that doesn't exist in frame "
449 << frame_id << ": " << static_cast<int>(nack_it->packet_id);
450 }
451 ++nack_it;
452 } while (nack_it != nacks.end() && nack_it->frame_id == frame_id);
453 }
454 }
455
456 if (need_to_send) {
457 packet_router_->RequestRtpSend(rtcp_session_.receiver_ssrc());
458 }
459 }
460
ChooseNextRtpPacketNeedingSend()461 Sender::ChosenPacket Sender::ChooseNextRtpPacketNeedingSend() {
462 // Find the oldest packet needing to be sent (or re-sent).
463 for (FrameId frame_id = checkpoint_frame_id_ + 1;
464 frame_id <= last_enqueued_frame_id_; ++frame_id) {
465 PendingFrameSlot* const slot = get_slot_for(frame_id);
466 if (!slot->is_active_for_frame(frame_id)) {
467 continue; // Frame was canceled. None of its packets need to be sent.
468 }
469 const FramePacketId packet_id = slot->send_flags.FindFirstSet();
470 if (packet_id < slot->send_flags.size()) {
471 return {slot, packet_id};
472 }
473 }
474
475 return {}; // Nothing needs to be sent.
476 }
477
ChooseKickstartPacket()478 Sender::ChosenPacketAndWhen Sender::ChooseKickstartPacket() {
479 if (latest_expected_frame_id_ >= last_enqueued_frame_id_) {
480 // Since the Receiver must know about all of the frames currently queued, no
481 // Kickstart packet is necessary.
482 return {};
483 }
484
485 // The Kickstart packet is always in the last-enqueued frame, so that the
486 // Receiver will know about every frame the Sender has. However, which packet
487 // should be chosen? Any would do, since all packets contain the frame's total
488 // packet count. For historical reasons, all sender implementations have
489 // always just sent the last packet; and so that tradition is continued here.
490 ChosenPacketAndWhen chosen;
491 chosen.slot = get_slot_for(last_enqueued_frame_id_);
492 // Note: This frame cannot have been canceled since
493 // |latest_expected_frame_id_| hasn't yet reached this point.
494 OSP_DCHECK(chosen.slot->is_active_for_frame(last_enqueued_frame_id_));
495 chosen.packet_id = chosen.slot->send_flags.size() - 1;
496
497 const Clock::time_point time_last_sent =
498 chosen.slot->packet_sent_times[chosen.packet_id];
499 // Sanity-check: This method should not be called to choose a packet while
500 // there are still unsent packets.
501 OSP_DCHECK_NE(time_last_sent, SenderPacketRouter::kNever);
502
503 // The desired Kickstart interval is a fraction of the total
504 // |target_playout_delay_|. The reason for the specific ratio here is based on
505 // lost knowledge (from legacy implementations); but it makes sense (i.e., to
506 // be a good "network citizen") to be less aggressive for larger playout delay
507 // windows, and more aggressive for shorter ones to avoid too-late packet
508 // arrivals.
509 using kWaitFraction = std::ratio<1, 20>;
510 const Clock::duration desired_kickstart_interval =
511 Clock::to_duration(target_playout_delay_) * kWaitFraction::num /
512 kWaitFraction::den;
513 // The actual interval used is increased, if current network performance
514 // warrants waiting longer. Don't send a Kickstart packet until no NACKs
515 // have been received for two network round-trip periods.
516 constexpr int kLowerBoundRoundTrips = 2;
517 const Clock::duration kickstart_interval = std::max(
518 desired_kickstart_interval, round_trip_time_ * kLowerBoundRoundTrips);
519 chosen.when = time_last_sent + kickstart_interval;
520
521 return chosen;
522 }
523
CancelPendingFrame(FrameId frame_id)524 void Sender::CancelPendingFrame(FrameId frame_id) {
525 PendingFrameSlot* const slot = get_slot_for(frame_id);
526 if (!slot->is_active_for_frame(frame_id)) {
527 return; // Frame was already canceled.
528 }
529
530 packet_router_->OnPayloadReceived(
531 slot->frame->data.size(), rtcp_packet_arrival_time_, round_trip_time_);
532
533 slot->frame.reset();
534 OSP_DCHECK_GT(num_frames_in_flight_, 0);
535 --num_frames_in_flight_;
536 if (observer_) {
537 observer_->OnFrameCanceled(frame_id);
538 }
539 }
540
OnFrameCanceled(FrameId frame_id)541 void Sender::Observer::OnFrameCanceled(FrameId frame_id) {}
OnPictureLost()542 void Sender::Observer::OnPictureLost() {}
543 Sender::Observer::~Observer() = default;
544
545 Sender::PendingFrameSlot::PendingFrameSlot() = default;
546 Sender::PendingFrameSlot::~PendingFrameSlot() = default;
547
548 } // namespace cast
549 } // namespace openscreen
550