1 // Copyright 2014 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 "media/cast/sender/video_sender.h"
6
7 #include <stdint.h>
8 #include <algorithm>
9 #include <cmath>
10 #include <cstring>
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/logging.h"
15 #include "base/trace_event/trace_event.h"
16 #include "media/cast/net/cast_transport_config.h"
17 #include "media/cast/sender/performance_metrics_overlay.h"
18 #include "media/cast/sender/video_encoder.h"
19
20 namespace media {
21 namespace cast {
22
23 namespace {
24
25 // The following two constants are used to adjust the target
26 // playout delay (when allowed). They were calculated using
27 // a combination of cast_benchmark runs and manual testing.
28 //
29 // This is how many round trips we think we need on the network.
30 const int kRoundTripsNeeded = 4;
31
32 // This is an estimate of all the the constant time needed independent of
33 // network quality (e.g., additional time that accounts for encode and decode
34 // time).
35 const int kConstantTimeMs = 75;
36
37 // The target maximum utilization of the encoder and network resources. This is
38 // used to attenuate the actual measured utilization values in order to provide
39 // "breathing room" (i.e., to ensure there will be sufficient CPU and bandwidth
40 // available to handle the occasional more-complex frames).
41 const int kTargetUtilizationPercentage = 75;
42
43 // This is the minimum duration in milliseconds that the sender sends key frame
44 // request to the encoder on receiving Pli messages. This is used to prevent
45 // sending multiple requests while the sender is waiting for an encoded key
46 // frame or receiving multiple Pli messages in a short period.
47 const int64_t kMinKeyFrameRequestOnPliIntervalMs = 500;
48
49 // Extract capture begin/end timestamps from |video_frame|'s metadata and log
50 // it.
LogVideoCaptureTimestamps(CastEnvironment * cast_environment,const media::VideoFrame & video_frame,RtpTimeTicks rtp_timestamp)51 void LogVideoCaptureTimestamps(CastEnvironment* cast_environment,
52 const media::VideoFrame& video_frame,
53 RtpTimeTicks rtp_timestamp) {
54 std::unique_ptr<FrameEvent> capture_begin_event(new FrameEvent());
55 capture_begin_event->type = FRAME_CAPTURE_BEGIN;
56 capture_begin_event->media_type = VIDEO_EVENT;
57 capture_begin_event->rtp_timestamp = rtp_timestamp;
58
59 std::unique_ptr<FrameEvent> capture_end_event(new FrameEvent());
60 capture_end_event->type = FRAME_CAPTURE_END;
61 capture_end_event->media_type = VIDEO_EVENT;
62 capture_end_event->rtp_timestamp = rtp_timestamp;
63 capture_end_event->width = video_frame.visible_rect().width();
64 capture_end_event->height = video_frame.visible_rect().height();
65
66 if (video_frame.metadata()->capture_begin_time.has_value() &&
67 video_frame.metadata()->capture_end_time.has_value()) {
68 capture_begin_event->timestamp =
69 *video_frame.metadata()->capture_begin_time;
70 capture_end_event->timestamp = *video_frame.metadata()->capture_end_time;
71 } else {
72 // The frame capture timestamps were not provided by the video capture
73 // source. Simply log the events as happening right now.
74 capture_begin_event->timestamp = capture_end_event->timestamp =
75 cast_environment->Clock()->NowTicks();
76 }
77
78 cast_environment->logger()->DispatchFrameEvent(
79 std::move(capture_begin_event));
80 cast_environment->logger()->DispatchFrameEvent(std::move(capture_end_event));
81 }
82
83 } // namespace
84
85 // Note, we use a fixed bitrate value when external video encoder is used.
86 // Some hardware encoder shows bad behavior if we set the bitrate too
87 // frequently, e.g. quality drop, not abiding by target bitrate, etc.
88 // See details: crbug.com/392086.
VideoSender(scoped_refptr<CastEnvironment> cast_environment,const FrameSenderConfig & video_config,StatusChangeCallback status_change_cb,const CreateVideoEncodeAcceleratorCallback & create_vea_cb,const CreateVideoEncodeMemoryCallback & create_video_encode_mem_cb,CastTransport * const transport_sender,PlayoutDelayChangeCB playout_delay_change_cb,media::VideoCaptureFeedbackCB feedback_callback)89 VideoSender::VideoSender(
90 scoped_refptr<CastEnvironment> cast_environment,
91 const FrameSenderConfig& video_config,
92 StatusChangeCallback status_change_cb,
93 const CreateVideoEncodeAcceleratorCallback& create_vea_cb,
94 const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb,
95 CastTransport* const transport_sender,
96 PlayoutDelayChangeCB playout_delay_change_cb,
97 media::VideoCaptureFeedbackCB feedback_callback)
98 : FrameSender(
99 cast_environment,
100 transport_sender,
101 video_config,
102 video_config.use_external_encoder
103 ? NewFixedCongestionControl(
104 (video_config.min_bitrate + video_config.max_bitrate) / 2)
105 : NewAdaptiveCongestionControl(cast_environment->Clock(),
106 video_config.max_bitrate,
107 video_config.min_bitrate,
108 video_config.max_frame_rate)),
109 frames_in_encoder_(0),
110 last_bitrate_(0),
111 playout_delay_change_cb_(std::move(playout_delay_change_cb)),
112 feedback_cb_(feedback_callback),
113 low_latency_mode_(false),
114 last_reported_encoder_utilization_(-1.0),
115 last_reported_lossy_utilization_(-1.0) {
116 video_encoder_ = VideoEncoder::Create(
117 cast_environment_,
118 video_config,
119 status_change_cb,
120 create_vea_cb,
121 create_video_encode_mem_cb);
122 if (!video_encoder_) {
123 cast_environment_->PostTask(
124 CastEnvironment::MAIN, FROM_HERE,
125 base::BindOnce(std::move(status_change_cb), STATUS_UNSUPPORTED_CODEC));
126 }
127 }
128
129 VideoSender::~VideoSender() = default;
130
InsertRawVideoFrame(scoped_refptr<media::VideoFrame> video_frame,const base::TimeTicks & reference_time)131 void VideoSender::InsertRawVideoFrame(
132 scoped_refptr<media::VideoFrame> video_frame,
133 const base::TimeTicks& reference_time) {
134 DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
135
136 if (!video_encoder_) {
137 NOTREACHED();
138 return;
139 }
140
141 const RtpTimeTicks rtp_timestamp =
142 RtpTimeTicks::FromTimeDelta(video_frame->timestamp(), kVideoFrequency);
143 LogVideoCaptureTimestamps(cast_environment_.get(), *video_frame,
144 rtp_timestamp);
145
146 // Used by chrome/browser/media/cast_mirroring_performance_browsertest.cc
147 TRACE_EVENT_INSTANT2("cast_perf_test", "InsertRawVideoFrame",
148 TRACE_EVENT_SCOPE_THREAD, "timestamp",
149 (reference_time - base::TimeTicks()).InMicroseconds(),
150 "rtp_timestamp", rtp_timestamp.lower_32_bits());
151
152 {
153 bool new_low_latency_mode = video_frame->metadata()->interactive_content;
154 if (new_low_latency_mode && !low_latency_mode_) {
155 VLOG(1) << "Interactive mode playout time " << min_playout_delay_;
156 playout_delay_change_cb_.Run(min_playout_delay_);
157 }
158 low_latency_mode_ = new_low_latency_mode;
159 }
160
161 // Drop the frame if either its RTP or reference timestamp is not an increase
162 // over the last frame's. This protects: 1) the duration calculations that
163 // assume timestamps are monotonically non-decreasing, and 2) assumptions made
164 // deeper in the implementation where each frame's RTP timestamp needs to be
165 // unique.
166 if (!last_enqueued_frame_reference_time_.is_null() &&
167 (rtp_timestamp <= last_enqueued_frame_rtp_timestamp_ ||
168 reference_time <= last_enqueued_frame_reference_time_)) {
169 VLOG(1) << "Dropping video frame: RTP or reference time did not increase.";
170 TRACE_EVENT_INSTANT2("cast.stream", "Video Frame Drop",
171 TRACE_EVENT_SCOPE_THREAD,
172 "rtp_timestamp", rtp_timestamp.lower_32_bits(),
173 "reason", "time did not increase");
174 return;
175 }
176
177 // Request a key frame when a Pli message was received, and it has been passed
178 // long enough from the last time sending key frame request on receiving a Pli
179 // message.
180 if (picture_lost_at_receiver_) {
181 const int64_t min_attemp_interval_ms =
182 std::max(kMinKeyFrameRequestOnPliIntervalMs,
183 6 * target_playout_delay_.InMilliseconds());
184 if (last_time_attempted_to_resolve_pli_.is_null() ||
185 ((reference_time - last_time_attempted_to_resolve_pli_)
186 .InMilliseconds() > min_attemp_interval_ms)) {
187 video_encoder_->GenerateKeyFrame();
188 last_time_attempted_to_resolve_pli_ = reference_time;
189 }
190 }
191
192 // Two video frames are needed to compute the exact media duration added by
193 // the next frame. If there are no frames in the encoder, compute a guess
194 // based on the configured |max_frame_rate_|. Any error introduced by this
195 // guess will be eliminated when |duration_in_encoder_| is updated in
196 // OnEncodedVideoFrame().
197 const base::TimeDelta duration_added_by_next_frame = frames_in_encoder_ > 0 ?
198 reference_time - last_enqueued_frame_reference_time_ :
199 base::TimeDelta::FromSecondsD(1.0 / max_frame_rate_);
200
201 if (ShouldDropNextFrame(duration_added_by_next_frame)) {
202 base::TimeDelta new_target_delay = std::min(
203 current_round_trip_time_ * kRoundTripsNeeded +
204 base::TimeDelta::FromMilliseconds(kConstantTimeMs),
205 max_playout_delay_);
206 // In case of low latency mode, we prefer frame drops over increasing
207 // playout time.
208 if (!low_latency_mode_ && new_target_delay > target_playout_delay_) {
209 // In case we detect user is no longer in a low latency mode and there is
210 // a need to drop a frame, we ensure the playout delay is at-least the
211 // the starting value for playing animated content.
212 // This is intended to minimize freeze when moving from an interactive
213 // session to watching animating content while being limited by end-to-end
214 // delay.
215 VLOG(1) << "Ensure playout time is at least " << animated_playout_delay_;
216 if (new_target_delay < animated_playout_delay_)
217 new_target_delay = animated_playout_delay_;
218 VLOG(1) << "New target delay: " << new_target_delay.InMilliseconds();
219 playout_delay_change_cb_.Run(new_target_delay);
220 }
221
222 // Some encoder implementations have a frame window for analysis. Since we
223 // are dropping this frame, unless we instruct the encoder to flush all the
224 // frames that have been enqueued for encoding, frames_in_encoder_ and
225 // last_enqueued_frame_reference_time_ will never be updated and we will
226 // drop every subsequent frame for the rest of the session.
227 video_encoder_->EmitFrames();
228
229 TRACE_EVENT_INSTANT2("cast.stream", "Video Frame Drop",
230 TRACE_EVENT_SCOPE_THREAD,
231 "rtp_timestamp", rtp_timestamp.lower_32_bits(),
232 "reason", "too much in flight");
233 return;
234 }
235
236 if (video_frame->visible_rect().IsEmpty()) {
237 VLOG(1) << "Rejecting empty video frame.";
238 return;
239 }
240
241 const int bitrate = congestion_control_->GetBitrate(
242 reference_time + target_playout_delay_, target_playout_delay_);
243 if (bitrate != last_bitrate_) {
244 video_encoder_->SetBitRate(bitrate);
245 last_bitrate_ = bitrate;
246 }
247
248 TRACE_COUNTER_ID1("cast.stream", "Video Target Bitrate", this, bitrate);
249
250 const scoped_refptr<VideoFrame> frame_to_encode =
251 MaybeRenderPerformanceMetricsOverlay(
252 GetTargetPlayoutDelay(), low_latency_mode_, bitrate,
253 frames_in_encoder_ + 1, last_reported_encoder_utilization_,
254 last_reported_lossy_utilization_, std::move(video_frame));
255 if (video_encoder_->EncodeVideoFrame(
256 frame_to_encode, reference_time,
257 base::BindOnce(&VideoSender::OnEncodedVideoFrame, AsWeakPtr(),
258 frame_to_encode, bitrate))) {
259 TRACE_EVENT_ASYNC_BEGIN1("cast.stream", "Video Encode",
260 frame_to_encode.get(), "rtp_timestamp",
261 rtp_timestamp.lower_32_bits());
262 frames_in_encoder_++;
263 duration_in_encoder_ += duration_added_by_next_frame;
264 last_enqueued_frame_rtp_timestamp_ = rtp_timestamp;
265 last_enqueued_frame_reference_time_ = reference_time;
266 } else {
267 VLOG(1) << "Encoder rejected a frame. Skipping...";
268 TRACE_EVENT_INSTANT1("cast.stream", "Video Encode Reject",
269 TRACE_EVENT_SCOPE_THREAD,
270 "rtp_timestamp", rtp_timestamp.lower_32_bits());
271 }
272 }
273
CreateVideoFrameFactory()274 std::unique_ptr<VideoFrameFactory> VideoSender::CreateVideoFrameFactory() {
275 return video_encoder_ ? video_encoder_->CreateVideoFrameFactory() : nullptr;
276 }
277
AsWeakPtr()278 base::WeakPtr<VideoSender> VideoSender::AsWeakPtr() {
279 return weak_factory_.GetWeakPtr();
280 }
281
GetNumberOfFramesInEncoder() const282 int VideoSender::GetNumberOfFramesInEncoder() const {
283 return frames_in_encoder_;
284 }
285
GetInFlightMediaDuration() const286 base::TimeDelta VideoSender::GetInFlightMediaDuration() const {
287 if (GetUnacknowledgedFrameCount() > 0) {
288 const FrameId oldest_unacked_frame_id = latest_acked_frame_id_ + 1;
289 return last_enqueued_frame_reference_time_ -
290 GetRecordedReferenceTime(oldest_unacked_frame_id);
291 } else {
292 return duration_in_encoder_;
293 }
294 }
295
OnEncodedVideoFrame(scoped_refptr<media::VideoFrame> video_frame,int encoder_bitrate,std::unique_ptr<SenderEncodedFrame> encoded_frame)296 void VideoSender::OnEncodedVideoFrame(
297 scoped_refptr<media::VideoFrame> video_frame,
298 int encoder_bitrate,
299 std::unique_ptr<SenderEncodedFrame> encoded_frame) {
300 DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
301
302 frames_in_encoder_--;
303 DCHECK_GE(frames_in_encoder_, 0);
304
305 // Encoding was exited with errors.
306 if (!encoded_frame)
307 return;
308
309 duration_in_encoder_ =
310 last_enqueued_frame_reference_time_ - encoded_frame->reference_time;
311
312 last_reported_encoder_utilization_ = encoded_frame->encoder_utilization;
313 last_reported_lossy_utilization_ = encoded_frame->lossy_utilization;
314
315 TRACE_EVENT_ASYNC_END2("cast.stream", "Video Encode", video_frame.get(),
316 "encoder_utilization",
317 last_reported_encoder_utilization_,
318 "lossy_utilization", last_reported_lossy_utilization_);
319
320 // Report the resource utilization for processing this frame. Take the
321 // greater of the two utilization values and attenuate them such that the
322 // target utilization is reported as the maximum sustainable amount.
323 const double attenuated_utilization =
324 std::max(last_reported_encoder_utilization_,
325 last_reported_lossy_utilization_) /
326 (kTargetUtilizationPercentage / 100.0);
327 if (attenuated_utilization >= 0.0) {
328 // Key frames are artificially capped to 1.0 because their actual
329 // utilization is atypical compared to the other frames in the stream, and
330 // this can misguide the producer of the input video frames.
331 VideoFrameFeedback feedback;
332 feedback.resource_utilization =
333 encoded_frame->dependency == EncodedFrame::KEY
334 ? std::min(1.0, attenuated_utilization)
335 : attenuated_utilization;
336 if (feedback_cb_)
337 feedback_cb_.Run(feedback);
338 }
339
340 SendEncodedFrame(encoder_bitrate, std::move(encoded_frame));
341 }
342
343 } // namespace cast
344 } // namespace media
345