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/standalone_sender/looping_file_sender.h"
6
7 #include "util/trace_logging.h"
8
9 namespace openscreen {
10 namespace cast {
11
LoopingFileSender(Environment * environment,const char * path,const SenderSession * session,SenderSession::ConfiguredSenders senders,int max_bitrate)12 LoopingFileSender::LoopingFileSender(Environment* environment,
13 const char* path,
14 const SenderSession* session,
15 SenderSession::ConfiguredSenders senders,
16 int max_bitrate)
17 : env_(environment),
18 path_(path),
19 session_(session),
20 max_bitrate_(max_bitrate),
21 audio_encoder_(senders.audio_sender->config().channels,
22 StreamingOpusEncoder::kDefaultCastAudioFramesPerSecond,
23 senders.audio_sender),
24 video_encoder_(StreamingVp8Encoder::Parameters{},
25 env_->task_runner(),
26 senders.video_sender),
27 next_task_(env_->now_function(), env_->task_runner()),
28 console_update_task_(env_->now_function(), env_->task_runner()) {
29 // Opus and Vp8 are the default values for the config, and if these are set
30 // to a different value that means we offered a codec that we do not
31 // support, which is a developer error.
32 OSP_CHECK(senders.audio_config.codec == AudioCodec::kOpus);
33 OSP_CHECK(senders.video_config.codec == VideoCodec::kVp8);
34 OSP_LOG_INFO << "Max allowed media bitrate (audio + video) will be "
35 << max_bitrate_;
36 bandwidth_being_utilized_ = max_bitrate_ / 2;
37 UpdateEncoderBitrates();
38
__anonf624c3560102null39 next_task_.Schedule([this] { SendFileAgain(); }, Alarm::kImmediately);
40 }
41
42 LoopingFileSender::~LoopingFileSender() = default;
43
UpdateEncoderBitrates()44 void LoopingFileSender::UpdateEncoderBitrates() {
45 if (bandwidth_being_utilized_ >= kHighBandwidthThreshold) {
46 audio_encoder_.UseHighQuality();
47 } else {
48 audio_encoder_.UseStandardQuality();
49 }
50 video_encoder_.SetTargetBitrate(bandwidth_being_utilized_ -
51 audio_encoder_.GetBitrate());
52 }
53
ControlForNetworkCongestion()54 void LoopingFileSender::ControlForNetworkCongestion() {
55 bandwidth_estimate_ = session_->GetEstimatedNetworkBandwidth();
56 if (bandwidth_estimate_ > 0) {
57 // Don't ever try to use *all* of the network bandwidth! However, don't go
58 // below the absolute minimum requirement either.
59 constexpr double kGoodNetworkCitizenFactor = 0.8;
60 const int usable_bandwidth = std::max<int>(
61 kGoodNetworkCitizenFactor * bandwidth_estimate_, kMinRequiredBitrate);
62
63 // See "congestion control" discussion in the class header comments for
64 // BandwidthEstimator.
65 if (usable_bandwidth > bandwidth_being_utilized_) {
66 constexpr double kConservativeIncrease = 1.1;
67 bandwidth_being_utilized_ = std::min<int>(
68 bandwidth_being_utilized_ * kConservativeIncrease, usable_bandwidth);
69 } else {
70 bandwidth_being_utilized_ = usable_bandwidth;
71 }
72
73 // Repsect the user's maximum bitrate setting.
74 bandwidth_being_utilized_ =
75 std::min(bandwidth_being_utilized_, max_bitrate_);
76
77 UpdateEncoderBitrates();
78 } else {
79 // There is no current bandwidth estimate. So, nothing should be adjusted.
80 }
81
82 next_task_.ScheduleFromNow([this] { ControlForNetworkCongestion(); },
83 kCongestionCheckInterval);
84 }
85
SendFileAgain()86 void LoopingFileSender::SendFileAgain() {
87 OSP_LOG_INFO << "Sending " << path_ << " (starts in one second)...";
88 TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender);
89
90 OSP_DCHECK_EQ(num_capturers_running_, 0);
91 num_capturers_running_ = 2;
92 capture_start_time_ = latest_frame_time_ = env_->now() + seconds(1);
93 audio_capturer_.emplace(env_, path_, audio_encoder_.num_channels(),
94 audio_encoder_.sample_rate(), capture_start_time_,
95 this);
96 video_capturer_.emplace(env_, path_, capture_start_time_, this);
97
98 next_task_.ScheduleFromNow([this] { ControlForNetworkCongestion(); },
99 kCongestionCheckInterval);
100 console_update_task_.Schedule([this] { UpdateStatusOnConsole(); },
101 capture_start_time_);
102 }
103
OnAudioData(const float * interleaved_samples,int num_samples,Clock::time_point capture_time)104 void LoopingFileSender::OnAudioData(const float* interleaved_samples,
105 int num_samples,
106 Clock::time_point capture_time) {
107 TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender);
108 latest_frame_time_ = std::max(capture_time, latest_frame_time_);
109 audio_encoder_.EncodeAndSend(interleaved_samples, num_samples, capture_time);
110 }
111
OnVideoFrame(const AVFrame & av_frame,Clock::time_point capture_time)112 void LoopingFileSender::OnVideoFrame(const AVFrame& av_frame,
113 Clock::time_point capture_time) {
114 TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender);
115 latest_frame_time_ = std::max(capture_time, latest_frame_time_);
116 StreamingVp8Encoder::VideoFrame frame{};
117 frame.width = av_frame.width - av_frame.crop_left - av_frame.crop_right;
118 frame.height = av_frame.height - av_frame.crop_top - av_frame.crop_bottom;
119 frame.yuv_planes[0] = av_frame.data[0] + av_frame.crop_left +
120 av_frame.linesize[0] * av_frame.crop_top;
121 frame.yuv_planes[1] = av_frame.data[1] + av_frame.crop_left / 2 +
122 av_frame.linesize[1] * av_frame.crop_top / 2;
123 frame.yuv_planes[2] = av_frame.data[2] + av_frame.crop_left / 2 +
124 av_frame.linesize[2] * av_frame.crop_top / 2;
125 for (int i = 0; i < 3; ++i) {
126 frame.yuv_strides[i] = av_frame.linesize[i];
127 }
128 // TODO(miu): Add performance metrics visual overlay (based on Stats
129 // callback).
130 video_encoder_.EncodeAndSend(frame, capture_time, {});
131 }
132
UpdateStatusOnConsole()133 void LoopingFileSender::UpdateStatusOnConsole() {
134 const Clock::duration elapsed = latest_frame_time_ - capture_start_time_;
135 const auto seconds_part = to_seconds(elapsed);
136 const auto millis_part = to_milliseconds(elapsed - seconds_part);
137 // The control codes here attempt to erase the current line the cursor is
138 // on, and then print out the updated status text. If the terminal does not
139 // support simple ANSI escape codes, the following will still work, but
140 // there might sometimes be old status lines not getting erased (i.e., just
141 // partially overwritten).
142 fprintf(stdout,
143 "\r\x1b[2K\rLoopingFileSender: At %01" PRId64
144 ".%03ds in file (est. network bandwidth: %d kbps). \n",
145 static_cast<int64_t>(seconds_part.count()),
146 static_cast<int>(millis_part.count()), bandwidth_estimate_ / 1024);
147 fflush(stdout);
148
149 console_update_task_.ScheduleFromNow([this] { UpdateStatusOnConsole(); },
150 kConsoleUpdateInterval);
151 }
152
OnEndOfFile(SimulatedCapturer * capturer)153 void LoopingFileSender::OnEndOfFile(SimulatedCapturer* capturer) {
154 OSP_LOG_INFO << "The " << ToTrackName(capturer)
155 << " capturer has reached the end of the media stream.";
156 --num_capturers_running_;
157 if (num_capturers_running_ == 0) {
158 console_update_task_.Cancel();
159 next_task_.Schedule([this] { SendFileAgain(); }, Alarm::kImmediately);
160 }
161 }
162
OnError(SimulatedCapturer * capturer,std::string message)163 void LoopingFileSender::OnError(SimulatedCapturer* capturer,
164 std::string message) {
165 OSP_LOG_ERROR << "The " << ToTrackName(capturer)
166 << " has failed: " << message;
167 --num_capturers_running_;
168 // If both fail, the application just pauses. This accounts for things like
169 // "file not found" errors. However, if only one track fails, then keep
170 // going.
171 }
172
ToTrackName(SimulatedCapturer * capturer) const173 const char* LoopingFileSender::ToTrackName(SimulatedCapturer* capturer) const {
174 const char* which;
175 if (capturer == &*audio_capturer_) {
176 which = "audio";
177 } else if (capturer == &*video_capturer_) {
178 which = "video";
179 } else {
180 OSP_NOTREACHED();
181 which = "";
182 }
183 return which;
184 }
185
186 } // namespace cast
187 } // namespace openscreen
188