1*3f982cf4SFabien Sanglard // Copyright 2019 The Chromium Authors. All rights reserved. 2*3f982cf4SFabien Sanglard // Use of this source code is governed by a BSD-style license that can be 3*3f982cf4SFabien Sanglard // found in the LICENSE file. 4*3f982cf4SFabien Sanglard 5*3f982cf4SFabien Sanglard #ifndef CAST_STREAMING_RECEIVER_H_ 6*3f982cf4SFabien Sanglard #define CAST_STREAMING_RECEIVER_H_ 7*3f982cf4SFabien Sanglard 8*3f982cf4SFabien Sanglard #include <stdint.h> 9*3f982cf4SFabien Sanglard 10*3f982cf4SFabien Sanglard #include <array> 11*3f982cf4SFabien Sanglard #include <chrono> 12*3f982cf4SFabien Sanglard #include <memory> 13*3f982cf4SFabien Sanglard #include <utility> 14*3f982cf4SFabien Sanglard #include <vector> 15*3f982cf4SFabien Sanglard 16*3f982cf4SFabien Sanglard #include "absl/types/optional.h" 17*3f982cf4SFabien Sanglard #include "absl/types/span.h" 18*3f982cf4SFabien Sanglard #include "cast/streaming/clock_drift_smoother.h" 19*3f982cf4SFabien Sanglard #include "cast/streaming/compound_rtcp_builder.h" 20*3f982cf4SFabien Sanglard #include "cast/streaming/environment.h" 21*3f982cf4SFabien Sanglard #include "cast/streaming/frame_collector.h" 22*3f982cf4SFabien Sanglard #include "cast/streaming/frame_id.h" 23*3f982cf4SFabien Sanglard #include "cast/streaming/packet_receive_stats_tracker.h" 24*3f982cf4SFabien Sanglard #include "cast/streaming/receiver_base.h" 25*3f982cf4SFabien Sanglard #include "cast/streaming/rtcp_common.h" 26*3f982cf4SFabien Sanglard #include "cast/streaming/rtcp_session.h" 27*3f982cf4SFabien Sanglard #include "cast/streaming/rtp_packet_parser.h" 28*3f982cf4SFabien Sanglard #include "cast/streaming/sender_report_parser.h" 29*3f982cf4SFabien Sanglard #include "cast/streaming/session_config.h" 30*3f982cf4SFabien Sanglard #include "cast/streaming/ssrc.h" 31*3f982cf4SFabien Sanglard #include "platform/api/time.h" 32*3f982cf4SFabien Sanglard #include "util/alarm.h" 33*3f982cf4SFabien Sanglard 34*3f982cf4SFabien Sanglard namespace openscreen { 35*3f982cf4SFabien Sanglard namespace cast { 36*3f982cf4SFabien Sanglard 37*3f982cf4SFabien Sanglard struct EncodedFrame; 38*3f982cf4SFabien Sanglard class ReceiverPacketRouter; 39*3f982cf4SFabien Sanglard 40*3f982cf4SFabien Sanglard // The Cast Streaming Receiver, a peer corresponding to some Cast Streaming 41*3f982cf4SFabien Sanglard // Sender at the other end of a network link. 42*3f982cf4SFabien Sanglard // 43*3f982cf4SFabien Sanglard // Cast Streaming is a transport protocol which divides up the frames for one 44*3f982cf4SFabien Sanglard // media stream (e.g., audio or video) into multiple RTP packets containing an 45*3f982cf4SFabien Sanglard // encrypted payload. The Receiver is the peer responsible for collecting the 46*3f982cf4SFabien Sanglard // RTP packets, decrypting the payload, and re-assembling a frame that can be 47*3f982cf4SFabien Sanglard // passed to a decoder and played out. 48*3f982cf4SFabien Sanglard // 49*3f982cf4SFabien Sanglard // A Sender ↔ Receiver pair is used to transport each media stream. Typically, 50*3f982cf4SFabien Sanglard // there are two pairs in a normal system, one for the audio stream and one for 51*3f982cf4SFabien Sanglard // video stream. A local player is responsible for synchronizing the playout of 52*3f982cf4SFabien Sanglard // the frames of each stream to achieve lip-sync. See the discussion in 53*3f982cf4SFabien Sanglard // encoded_frame.h for how the |reference_time| and |rtp_timestamp| of the 54*3f982cf4SFabien Sanglard // EncodedFrames are used to achieve this. 55*3f982cf4SFabien Sanglard // 56*3f982cf4SFabien Sanglard // See the Receiver Demo app for a reference implementation that both shows and 57*3f982cf4SFabien Sanglard // explains how Receivers are properly configured and started, integrated with a 58*3f982cf4SFabien Sanglard // decoder, and the resulting decoded media is played out. Also, here is a 59*3f982cf4SFabien Sanglard // general usage example: 60*3f982cf4SFabien Sanglard // 61*3f982cf4SFabien Sanglard // class MyPlayer : public openscreen::cast::Receiver::Consumer { 62*3f982cf4SFabien Sanglard // public: 63*3f982cf4SFabien Sanglard // explicit MyPlayer(Receiver* receiver) : receiver_(receiver) { 64*3f982cf4SFabien Sanglard // recevier_->SetPlayerProcessingTime(std::chrono::milliseconds(10)); 65*3f982cf4SFabien Sanglard // receiver_->SetConsumer(this); 66*3f982cf4SFabien Sanglard // } 67*3f982cf4SFabien Sanglard // 68*3f982cf4SFabien Sanglard // ~MyPlayer() override { 69*3f982cf4SFabien Sanglard // receiver_->SetConsumer(nullptr); 70*3f982cf4SFabien Sanglard // } 71*3f982cf4SFabien Sanglard // 72*3f982cf4SFabien Sanglard // private: 73*3f982cf4SFabien Sanglard // // Receiver::Consumer implementation. 74*3f982cf4SFabien Sanglard // void OnFramesReady(int next_frame_buffer_size) override { 75*3f982cf4SFabien Sanglard // std::vector<uint8_t> buffer; 76*3f982cf4SFabien Sanglard // buffer.resize(next_frame_buffer_size); 77*3f982cf4SFabien Sanglard // openscreen::cast::EncodedFrame encoded_frame = 78*3f982cf4SFabien Sanglard // receiver_->ConsumeNextFrame(absl::Span<uint8_t>(buffer)); 79*3f982cf4SFabien Sanglard // 80*3f982cf4SFabien Sanglard // display_.RenderFrame(decoder_.DecodeFrame(encoded_frame.data)); 81*3f982cf4SFabien Sanglard // 82*3f982cf4SFabien Sanglard // // Note: An implementation could call receiver_->AdvanceToNextFrame() 83*3f982cf4SFabien Sanglard // // and receiver_->ConsumeNextFrame() in a loop here, to consume all the 84*3f982cf4SFabien Sanglard // // remaining frames that are ready. 85*3f982cf4SFabien Sanglard // } 86*3f982cf4SFabien Sanglard // 87*3f982cf4SFabien Sanglard // Receiver* const receiver_; 88*3f982cf4SFabien Sanglard // MyDecoder decoder_; 89*3f982cf4SFabien Sanglard // MyDisplay display_; 90*3f982cf4SFabien Sanglard // }; 91*3f982cf4SFabien Sanglard // 92*3f982cf4SFabien Sanglard // Internally, a queue of complete and partially-received frames is maintained. 93*3f982cf4SFabien Sanglard // The queue is a circular queue of FrameCollectors that each maintain the 94*3f982cf4SFabien Sanglard // individual receive state of each in-flight frame. There are three conceptual 95*3f982cf4SFabien Sanglard // "pointers" that indicate what assumptions and operations are made on certain 96*3f982cf4SFabien Sanglard // ranges of frames in the queue: 97*3f982cf4SFabien Sanglard // 98*3f982cf4SFabien Sanglard // 1. Latest Frame Expected: The FrameId of the latest frame whose existence 99*3f982cf4SFabien Sanglard // is known to this Receiver. This is the highest FrameId seen in any 100*3f982cf4SFabien Sanglard // successfully-parsed RTP packet. 101*3f982cf4SFabien Sanglard // 2. Checkpoint Frame: Indicates that all of the RTP packets for all frames 102*3f982cf4SFabien Sanglard // up to and including the one having this FrameId have been successfully 103*3f982cf4SFabien Sanglard // received and processed. 104*3f982cf4SFabien Sanglard // 3. Last Frame Consumed: The FrameId of last frame consumed (see 105*3f982cf4SFabien Sanglard // ConsumeNextFrame()). Once a frame is consumed, all internal resources 106*3f982cf4SFabien Sanglard // related to the frame can be freed and/or re-used for later frames. 107*3f982cf4SFabien Sanglard class Receiver : public ReceiverBase { 108*3f982cf4SFabien Sanglard public: 109*3f982cf4SFabien Sanglard using ReceiverBase::Consumer; 110*3f982cf4SFabien Sanglard 111*3f982cf4SFabien Sanglard // Constructs a Receiver that attaches to the given |environment| and 112*3f982cf4SFabien Sanglard // |packet_router|. The config contains the settings that were 113*3f982cf4SFabien Sanglard // agreed-upon by both sides from the OFFER/ANSWER exchange (i.e., the part of 114*3f982cf4SFabien Sanglard // the overall end-to-end connection process that occurs before Cast Streaming 115*3f982cf4SFabien Sanglard // is started). 116*3f982cf4SFabien Sanglard Receiver(Environment* environment, 117*3f982cf4SFabien Sanglard ReceiverPacketRouter* packet_router, 118*3f982cf4SFabien Sanglard SessionConfig config); 119*3f982cf4SFabien Sanglard ~Receiver() override; 120*3f982cf4SFabien Sanglard 121*3f982cf4SFabien Sanglard // ReceiverBase overrides. 122*3f982cf4SFabien Sanglard const SessionConfig& config() const override; 123*3f982cf4SFabien Sanglard int rtp_timebase() const override; 124*3f982cf4SFabien Sanglard Ssrc ssrc() const override; 125*3f982cf4SFabien Sanglard void SetConsumer(Consumer* consumer) override; 126*3f982cf4SFabien Sanglard void SetPlayerProcessingTime(Clock::duration needed_time) override; 127*3f982cf4SFabien Sanglard void RequestKeyFrame() override; 128*3f982cf4SFabien Sanglard int AdvanceToNextFrame() override; 129*3f982cf4SFabien Sanglard EncodedFrame ConsumeNextFrame(absl::Span<uint8_t> buffer) override; 130*3f982cf4SFabien Sanglard 131*3f982cf4SFabien Sanglard // Allows setting picture loss indication for testing. In production, this 132*3f982cf4SFabien Sanglard // should be done using the config. SetPliEnabledForTesting(bool is_pli_enabled)133*3f982cf4SFabien Sanglard void SetPliEnabledForTesting(bool is_pli_enabled) { 134*3f982cf4SFabien Sanglard is_pli_enabled_ = is_pli_enabled; 135*3f982cf4SFabien Sanglard } 136*3f982cf4SFabien Sanglard 137*3f982cf4SFabien Sanglard // The default "player processing time" amount. See SetPlayerProcessingTime(). 138*3f982cf4SFabien Sanglard static constexpr std::chrono::milliseconds kDefaultPlayerProcessingTime = 139*3f982cf4SFabien Sanglard ReceiverBase::kDefaultPlayerProcessingTime; 140*3f982cf4SFabien Sanglard 141*3f982cf4SFabien Sanglard // Returned by AdvanceToNextFrame() when there are no frames currently ready 142*3f982cf4SFabien Sanglard // for consumption. 143*3f982cf4SFabien Sanglard static constexpr int kNoFramesReady = ReceiverBase::kNoFramesReady; 144*3f982cf4SFabien Sanglard 145*3f982cf4SFabien Sanglard protected: 146*3f982cf4SFabien Sanglard friend class ReceiverPacketRouter; 147*3f982cf4SFabien Sanglard 148*3f982cf4SFabien Sanglard // Called by ReceiverPacketRouter to provide this Receiver with what looks 149*3f982cf4SFabien Sanglard // like a RTP/RTCP packet meant for it specifically (among other Receivers). 150*3f982cf4SFabien Sanglard void OnReceivedRtpPacket(Clock::time_point arrival_time, 151*3f982cf4SFabien Sanglard std::vector<uint8_t> packet); 152*3f982cf4SFabien Sanglard void OnReceivedRtcpPacket(Clock::time_point arrival_time, 153*3f982cf4SFabien Sanglard std::vector<uint8_t> packet); 154*3f982cf4SFabien Sanglard 155*3f982cf4SFabien Sanglard private: 156*3f982cf4SFabien Sanglard // An entry in the circular queue (see |pending_frames_|). 157*3f982cf4SFabien Sanglard struct PendingFrame { 158*3f982cf4SFabien Sanglard FrameCollector collector; 159*3f982cf4SFabien Sanglard 160*3f982cf4SFabien Sanglard // The Receiver's [local] Clock time when this frame was originally captured 161*3f982cf4SFabien Sanglard // at the Sender. This is computed and assigned when the RTP packet with ID 162*3f982cf4SFabien Sanglard // 0 is processed. Add the target playout delay to this to get the target 163*3f982cf4SFabien Sanglard // playout time. 164*3f982cf4SFabien Sanglard absl::optional<Clock::time_point> estimated_capture_time; 165*3f982cf4SFabien Sanglard 166*3f982cf4SFabien Sanglard PendingFrame(); 167*3f982cf4SFabien Sanglard ~PendingFrame(); 168*3f982cf4SFabien Sanglard 169*3f982cf4SFabien Sanglard // Reset this entry to its initial state, freeing resources. 170*3f982cf4SFabien Sanglard void Reset(); 171*3f982cf4SFabien Sanglard }; 172*3f982cf4SFabien Sanglard 173*3f982cf4SFabien Sanglard // Get/Set the checkpoint FrameId. This indicates that all of the packets for 174*3f982cf4SFabien Sanglard // all frames up to and including this FrameId have been successfully received 175*3f982cf4SFabien Sanglard // (or otherwise do not need to be re-transmitted). checkpoint_frame()176*3f982cf4SFabien Sanglard FrameId checkpoint_frame() const { return rtcp_builder_.checkpoint_frame(); } set_checkpoint_frame(FrameId frame_id)177*3f982cf4SFabien Sanglard void set_checkpoint_frame(FrameId frame_id) { 178*3f982cf4SFabien Sanglard rtcp_builder_.SetCheckpointFrame(frame_id); 179*3f982cf4SFabien Sanglard } 180*3f982cf4SFabien Sanglard 181*3f982cf4SFabien Sanglard // Send an RTCP packet to the Sender immediately, to acknowledge the complete 182*3f982cf4SFabien Sanglard // reception of one or more additional frames, to reply to a Sender Report, or 183*3f982cf4SFabien Sanglard // to request re-transmits. Calling this also schedules additional RTCP 184*3f982cf4SFabien Sanglard // packets to be sent periodically for the life of this Receiver. 185*3f982cf4SFabien Sanglard void SendRtcp(); 186*3f982cf4SFabien Sanglard 187*3f982cf4SFabien Sanglard // Helpers to map the given |frame_id| to the element in the |pending_frames_| 188*3f982cf4SFabien Sanglard // circular queue. There are both const and non-const versions, but neither 189*3f982cf4SFabien Sanglard // mutate any state (i.e., they are just look-ups). 190*3f982cf4SFabien Sanglard const PendingFrame& GetQueueEntry(FrameId frame_id) const; 191*3f982cf4SFabien Sanglard PendingFrame& GetQueueEntry(FrameId frame_id); 192*3f982cf4SFabien Sanglard 193*3f982cf4SFabien Sanglard // Record that the target playout delay has changed starting with the given 194*3f982cf4SFabien Sanglard // FrameId. 195*3f982cf4SFabien Sanglard void RecordNewTargetPlayoutDelay(FrameId as_of_frame, 196*3f982cf4SFabien Sanglard std::chrono::milliseconds delay); 197*3f982cf4SFabien Sanglard 198*3f982cf4SFabien Sanglard // Examine the known target playout delay changes to determine what setting is 199*3f982cf4SFabien Sanglard // in-effect for the given frame. 200*3f982cf4SFabien Sanglard std::chrono::milliseconds ResolveTargetPlayoutDelay(FrameId frame_id) const; 201*3f982cf4SFabien Sanglard 202*3f982cf4SFabien Sanglard // Called to move the checkpoint forward. This scans the queue, starting from 203*3f982cf4SFabien Sanglard // |new_checkpoint|, to find the latest in a contiguous sequence of completed 204*3f982cf4SFabien Sanglard // frames. Then, it records that frame as the new checkpoint, and immediately 205*3f982cf4SFabien Sanglard // sends a feedback RTCP packet to the Sender. 206*3f982cf4SFabien Sanglard void AdvanceCheckpoint(FrameId new_checkpoint); 207*3f982cf4SFabien Sanglard 208*3f982cf4SFabien Sanglard // Helper to force-drop all frames before |first_kept_frame|, even if they 209*3f982cf4SFabien Sanglard // were never consumed. This will also auto-cancel frames that were never 210*3f982cf4SFabien Sanglard // completely received, artificially moving the checkpoint forward, and 211*3f982cf4SFabien Sanglard // notifying the Sender of that. The caller of this method is responsible for 212*3f982cf4SFabien Sanglard // making sure that frame data dependencies will not be broken by dropping the 213*3f982cf4SFabien Sanglard // frames. 214*3f982cf4SFabien Sanglard void DropAllFramesBefore(FrameId first_kept_frame); 215*3f982cf4SFabien Sanglard 216*3f982cf4SFabien Sanglard // Sets the |consumption_alarm_| to check whether any frames are ready, 217*3f982cf4SFabien Sanglard // including possibly skipping over late frames in order to make not-yet-late 218*3f982cf4SFabien Sanglard // frames become ready. The default argument value means "without delay." 219*3f982cf4SFabien Sanglard void ScheduleFrameReadyCheck(Clock::time_point when = Alarm::kImmediately); 220*3f982cf4SFabien Sanglard 221*3f982cf4SFabien Sanglard const ClockNowFunctionPtr now_; 222*3f982cf4SFabien Sanglard ReceiverPacketRouter* const packet_router_; 223*3f982cf4SFabien Sanglard const SessionConfig config_; 224*3f982cf4SFabien Sanglard RtcpSession rtcp_session_; 225*3f982cf4SFabien Sanglard SenderReportParser rtcp_parser_; 226*3f982cf4SFabien Sanglard CompoundRtcpBuilder rtcp_builder_; 227*3f982cf4SFabien Sanglard PacketReceiveStatsTracker stats_tracker_; // Tracks transmission stats. 228*3f982cf4SFabien Sanglard RtpPacketParser rtp_parser_; 229*3f982cf4SFabien Sanglard const int rtp_timebase_; // RTP timestamp ticks per second. 230*3f982cf4SFabien Sanglard const FrameCrypto crypto_; // Decrypts assembled frames. 231*3f982cf4SFabien Sanglard bool is_pli_enabled_; // Whether picture loss indication is enabled. 232*3f982cf4SFabien Sanglard 233*3f982cf4SFabien Sanglard // Buffer for serializing/sending RTCP packets. 234*3f982cf4SFabien Sanglard const int rtcp_buffer_capacity_; 235*3f982cf4SFabien Sanglard const std::unique_ptr<uint8_t[]> rtcp_buffer_; 236*3f982cf4SFabien Sanglard 237*3f982cf4SFabien Sanglard // Schedules tasks to ensure RTCP reports are sent within a bounded interval. 238*3f982cf4SFabien Sanglard // Not scheduled until after this Receiver has processed the first packet from 239*3f982cf4SFabien Sanglard // the Sender. 240*3f982cf4SFabien Sanglard Alarm rtcp_alarm_; 241*3f982cf4SFabien Sanglard Clock::time_point last_rtcp_send_time_ = Clock::time_point::min(); 242*3f982cf4SFabien Sanglard 243*3f982cf4SFabien Sanglard // The last Sender Report received and when the packet containing it had 244*3f982cf4SFabien Sanglard // arrived. This contains lip-sync timestamps used as part of the calculation 245*3f982cf4SFabien Sanglard // of playout times for the received frames, as well as ping-pong data bounced 246*3f982cf4SFabien Sanglard // back to the Sender in the Receiver Reports. It is nullopt until the first 247*3f982cf4SFabien Sanglard // parseable Sender Report is received. 248*3f982cf4SFabien Sanglard absl::optional<SenderReportParser::SenderReportWithId> last_sender_report_; 249*3f982cf4SFabien Sanglard Clock::time_point last_sender_report_arrival_time_; 250*3f982cf4SFabien Sanglard 251*3f982cf4SFabien Sanglard // Tracks the offset between the Receiver's [local] clock and the Sender's 252*3f982cf4SFabien Sanglard // clock. This is invalid until the first Sender Report has been successfully 253*3f982cf4SFabien Sanglard // processed (i.e., |last_sender_report_| is not nullopt). 254*3f982cf4SFabien Sanglard ClockDriftSmoother smoothed_clock_offset_; 255*3f982cf4SFabien Sanglard 256*3f982cf4SFabien Sanglard // The ID of the latest frame whose existence is known to this Receiver. This 257*3f982cf4SFabien Sanglard // value must always be greater than or equal to |checkpoint_frame()|. 258*3f982cf4SFabien Sanglard FrameId latest_frame_expected_ = FrameId::leader(); 259*3f982cf4SFabien Sanglard 260*3f982cf4SFabien Sanglard // The ID of the last frame consumed. This value must always be less than or 261*3f982cf4SFabien Sanglard // equal to |checkpoint_frame()|, since it's impossible to consume incomplete 262*3f982cf4SFabien Sanglard // frames! 263*3f982cf4SFabien Sanglard FrameId last_frame_consumed_ = FrameId::leader(); 264*3f982cf4SFabien Sanglard 265*3f982cf4SFabien Sanglard // The ID of the latest key frame known to be in-flight. This is used by 266*3f982cf4SFabien Sanglard // RequestKeyFrame() to ensure the PLI condition doesn't get set again until 267*3f982cf4SFabien Sanglard // after the consumer has seen a key frame that would clear the condition. 268*3f982cf4SFabien Sanglard FrameId last_key_frame_received_; 269*3f982cf4SFabien Sanglard 270*3f982cf4SFabien Sanglard // The frame queue (circular), which tracks which frames are in-flight, stores 271*3f982cf4SFabien Sanglard // data for partially-received frames, and holds onto completed frames until 272*3f982cf4SFabien Sanglard // the consumer consumes them. 273*3f982cf4SFabien Sanglard // 274*3f982cf4SFabien Sanglard // Use GetQueueEntry() to access a slot. The currently-active slots are those 275*3f982cf4SFabien Sanglard // for the frames after |last_frame_consumed_| and up-to/including 276*3f982cf4SFabien Sanglard // |latest_frame_expected_|. 277*3f982cf4SFabien Sanglard std::array<PendingFrame, kMaxUnackedFrames> pending_frames_{}; 278*3f982cf4SFabien Sanglard 279*3f982cf4SFabien Sanglard // Tracks the recent changes to the target playout delay, which is controlled 280*3f982cf4SFabien Sanglard // by the Sender. The FrameId indicates the first frame where a new delay 281*3f982cf4SFabien Sanglard // setting takes effect. This vector is never empty, is kept sorted, and is 282*3f982cf4SFabien Sanglard // pruned to remain as small as possible. 283*3f982cf4SFabien Sanglard // 284*3f982cf4SFabien Sanglard // The target playout delay is the amount of time between a frame's 285*3f982cf4SFabien Sanglard // capture/recording on the Sender and when it should be played-out at the 286*3f982cf4SFabien Sanglard // Receiver. 287*3f982cf4SFabien Sanglard std::vector<std::pair<FrameId, std::chrono::milliseconds>> 288*3f982cf4SFabien Sanglard playout_delay_changes_; 289*3f982cf4SFabien Sanglard 290*3f982cf4SFabien Sanglard // The consumer to notify when there are one or more frames completed and 291*3f982cf4SFabien Sanglard // ready to be consumed. 292*3f982cf4SFabien Sanglard Consumer* consumer_ = nullptr; 293*3f982cf4SFabien Sanglard 294*3f982cf4SFabien Sanglard // The additional time needed to decode/play-out each frame after being 295*3f982cf4SFabien Sanglard // consumed from this Receiver. 296*3f982cf4SFabien Sanglard Clock::duration player_processing_time_ = kDefaultPlayerProcessingTime; 297*3f982cf4SFabien Sanglard 298*3f982cf4SFabien Sanglard // Scheduled to check whether there are frames ready and, if there are, to 299*3f982cf4SFabien Sanglard // notify the Consumer via OnFramesReady(). 300*3f982cf4SFabien Sanglard Alarm consumption_alarm_; 301*3f982cf4SFabien Sanglard 302*3f982cf4SFabien Sanglard // The interval between sending ACK/NACK feedback RTCP messages while 303*3f982cf4SFabien Sanglard // incomplete frames exist in the queue. 304*3f982cf4SFabien Sanglard // 305*3f982cf4SFabien Sanglard // TODO(jophba): This should be a function of the current target playout 306*3f982cf4SFabien Sanglard // delay, similar to the Sender's kickstart interval logic. 307*3f982cf4SFabien Sanglard static constexpr std::chrono::milliseconds kNackFeedbackInterval{30}; 308*3f982cf4SFabien Sanglard }; 309*3f982cf4SFabien Sanglard 310*3f982cf4SFabien Sanglard } // namespace cast 311*3f982cf4SFabien Sanglard } // namespace openscreen 312*3f982cf4SFabien Sanglard 313*3f982cf4SFabien Sanglard #endif // CAST_STREAMING_RECEIVER_H_ 314