OLD | NEW |
| (Empty) |
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 // This program benchmarks the theoretical throughput of the cast library. | |
6 // It runs using a fake clock, simulated network and fake codecs. This allows | |
7 // tests to run much faster than real time. | |
8 // To run the program, run: | |
9 // $ ./out/Release/cast_benchmarks | tee benchmarkoutput.asc | |
10 // This may take a while, when it is done, you can view the data with | |
11 // meshlab by running: | |
12 // $ meshlab benchmarkoutput.asc | |
13 // After starting meshlab, turn on Render->Show Axis. The red axis will | |
14 // represent bandwidth (in megabits) the blue axis will be packet drop | |
15 // (in percent) and the green axis will be latency (in milliseconds). | |
16 // | |
17 // This program can also be used for profiling. On linux it has | |
18 // built-in support for this. Simply set the environment variable | |
19 // PROFILE_FILE before running it, like so: | |
20 // $ export PROFILE_FILE=cast_benchmark.profile | |
21 // Then after running the program, you can view the profile with: | |
22 // $ pprof ./out/Release/cast_benchmarks $PROFILE_FILE --gv | |
23 | |
24 #include <math.h> | |
25 #include <stdint.h> | |
26 | |
27 #include <map> | |
28 #include <vector> | |
29 | |
30 #include "base/at_exit.h" | |
31 #include "base/bind.h" | |
32 #include "base/bind_helpers.h" | |
33 #include "base/command_line.h" | |
34 #include "base/debug/profiler.h" | |
35 #include "base/stl_util.h" | |
36 #include "base/strings/string_number_conversions.h" | |
37 #include "base/strings/stringprintf.h" | |
38 #include "base/test/simple_test_tick_clock.h" | |
39 #include "base/threading/thread.h" | |
40 #include "base/time/tick_clock.h" | |
41 #include "media/base/audio_bus.h" | |
42 #include "media/base/video_frame.h" | |
43 #include "media/cast/cast_config.h" | |
44 #include "media/cast/cast_environment.h" | |
45 #include "media/cast/cast_receiver.h" | |
46 #include "media/cast/cast_sender.h" | |
47 #include "media/cast/logging/simple_event_subscriber.h" | |
48 #include "media/cast/test/fake_single_thread_task_runner.h" | |
49 #include "media/cast/test/skewed_single_thread_task_runner.h" | |
50 #include "media/cast/test/skewed_tick_clock.h" | |
51 #include "media/cast/test/utility/audio_utility.h" | |
52 #include "media/cast/test/utility/default_config.h" | |
53 #include "media/cast/test/utility/test_util.h" | |
54 #include "media/cast/test/utility/udp_proxy.h" | |
55 #include "media/cast/test/utility/video_utility.h" | |
56 #include "media/cast/transport/cast_transport_config.h" | |
57 #include "media/cast/transport/cast_transport_defines.h" | |
58 #include "media/cast/transport/cast_transport_sender.h" | |
59 #include "media/cast/transport/cast_transport_sender_impl.h" | |
60 #include "testing/gtest/include/gtest/gtest.h" | |
61 | |
62 namespace media { | |
63 namespace cast { | |
64 | |
65 namespace { | |
66 | |
67 static const int64 kStartMillisecond = INT64_C(1245); | |
68 static const int kAudioChannels = 2; | |
69 static const int kVideoHdWidth = 1280; | |
70 static const int kVideoHdHeight = 720; | |
71 static const int kTargetDelay = 300; | |
72 | |
73 // The tests are commonly implemented with |kFrameTimerMs| RunTask function; | |
74 // a normal video is 30 fps hence the 33 ms between frames. | |
75 static const int kFrameTimerMs = 33; | |
76 | |
77 void UpdateCastTransportStatus(transport::CastTransportStatus status) { | |
78 bool result = (status == transport::TRANSPORT_AUDIO_INITIALIZED || | |
79 status == transport::TRANSPORT_VIDEO_INITIALIZED); | |
80 EXPECT_TRUE(result); | |
81 } | |
82 | |
83 void AudioInitializationStatus(CastInitializationStatus status) { | |
84 EXPECT_EQ(STATUS_AUDIO_INITIALIZED, status); | |
85 } | |
86 | |
87 void VideoInitializationStatus(CastInitializationStatus status) { | |
88 EXPECT_EQ(STATUS_VIDEO_INITIALIZED, status); | |
89 } | |
90 | |
91 void IgnoreRawEvents(const std::vector<PacketEvent>& packet_events) { | |
92 } | |
93 | |
94 } // namespace | |
95 | |
96 // Shim that turns forwards packets from a test::PacketPipe to a | |
97 // PacketReceiverCallback. | |
98 class LoopBackPacketPipe : public test::PacketPipe { | |
99 public: | |
100 LoopBackPacketPipe(const transport::PacketReceiverCallback& packet_receiver) | |
101 : packet_receiver_(packet_receiver) {} | |
102 | |
103 virtual ~LoopBackPacketPipe() {} | |
104 | |
105 // PacketPipe implementations. | |
106 virtual void Send(scoped_ptr<transport::Packet> packet) OVERRIDE { | |
107 packet_receiver_.Run(packet.Pass()); | |
108 } | |
109 | |
110 private: | |
111 transport::PacketReceiverCallback packet_receiver_; | |
112 }; | |
113 | |
114 // Class that sends the packet direct from sender into the receiver with the | |
115 // ability to drop packets between the two. | |
116 // TODO(hubbe): Break this out and share code with end2end_unittest.cc | |
117 class LoopBackTransport : public transport::PacketSender { | |
118 public: | |
119 explicit LoopBackTransport(scoped_refptr<CastEnvironment> cast_environment) | |
120 : cast_environment_(cast_environment) {} | |
121 | |
122 void SetPacketReceiver( | |
123 const transport::PacketReceiverCallback& packet_receiver, | |
124 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, | |
125 base::TickClock* clock) { | |
126 scoped_ptr<test::PacketPipe> loopback_pipe( | |
127 new LoopBackPacketPipe(packet_receiver)); | |
128 if (packet_pipe_) { | |
129 packet_pipe_->AppendToPipe(loopback_pipe.Pass()); | |
130 } else { | |
131 packet_pipe_ = loopback_pipe.Pass(); | |
132 } | |
133 packet_pipe_->InitOnIOThread(task_runner, clock); | |
134 } | |
135 | |
136 virtual bool SendPacket(transport::PacketRef packet, | |
137 const base::Closure& cb) OVERRIDE { | |
138 DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); | |
139 scoped_ptr<Packet> packet_copy(new Packet(packet->data)); | |
140 packet_pipe_->Send(packet_copy.Pass()); | |
141 return true; | |
142 } | |
143 | |
144 void SetPacketPipe(scoped_ptr<test::PacketPipe> pipe) { | |
145 // Append the loopback pipe to the end. | |
146 pipe->AppendToPipe(packet_pipe_.Pass()); | |
147 packet_pipe_ = pipe.Pass(); | |
148 } | |
149 | |
150 private: | |
151 scoped_refptr<CastEnvironment> cast_environment_; | |
152 scoped_ptr<test::PacketPipe> packet_pipe_; | |
153 }; | |
154 | |
155 // Wraps a CastTransportSender and records some statistics about | |
156 // the data that goes through it. | |
157 class CastTransportSenderWrapper : public transport::CastTransportSender { | |
158 public: | |
159 // Takes ownership of |transport|. | |
160 void Init(CastTransportSender* transport, | |
161 uint64* encoded_video_bytes, | |
162 uint64* encoded_audio_bytes) { | |
163 transport_.reset(transport); | |
164 encoded_video_bytes_ = encoded_video_bytes; | |
165 encoded_audio_bytes_ = encoded_audio_bytes; | |
166 } | |
167 | |
168 virtual void InitializeAudio( | |
169 const transport::CastTransportAudioConfig& config) OVERRIDE { | |
170 transport_->InitializeAudio(config); | |
171 } | |
172 | |
173 virtual void InitializeVideo( | |
174 const transport::CastTransportVideoConfig& config) OVERRIDE { | |
175 transport_->InitializeVideo(config); | |
176 } | |
177 | |
178 virtual void SetPacketReceiver( | |
179 const transport::PacketReceiverCallback& packet_receiver) OVERRIDE { | |
180 transport_->SetPacketReceiver(packet_receiver); | |
181 } | |
182 | |
183 virtual void InsertCodedAudioFrame( | |
184 const transport::EncodedFrame& audio_frame) OVERRIDE { | |
185 *encoded_audio_bytes_ += audio_frame.data.size(); | |
186 transport_->InsertCodedAudioFrame(audio_frame); | |
187 } | |
188 | |
189 virtual void InsertCodedVideoFrame( | |
190 const transport::EncodedFrame& video_frame) OVERRIDE { | |
191 *encoded_video_bytes_ += video_frame.data.size(); | |
192 transport_->InsertCodedVideoFrame(video_frame); | |
193 } | |
194 | |
195 virtual void SendRtcpFromRtpSender(uint32 packet_type_flags, | |
196 uint32 ntp_seconds, | |
197 uint32 ntp_fraction, | |
198 uint32 rtp_timestamp, | |
199 const transport::RtcpDlrrReportBlock& dlrr, | |
200 uint32 sending_ssrc, | |
201 const std::string& c_name) OVERRIDE { | |
202 transport_->SendRtcpFromRtpSender(packet_type_flags, | |
203 ntp_seconds, | |
204 ntp_fraction, | |
205 rtp_timestamp, | |
206 dlrr, | |
207 sending_ssrc, | |
208 c_name); | |
209 } | |
210 | |
211 // Retransmission request. | |
212 virtual void ResendPackets( | |
213 bool is_audio, | |
214 const MissingFramesAndPacketsMap& missing_packets) OVERRIDE { | |
215 transport_->ResendPackets(is_audio, missing_packets); | |
216 } | |
217 | |
218 private: | |
219 scoped_ptr<transport::CastTransportSender> transport_; | |
220 uint64* encoded_video_bytes_; | |
221 uint64* encoded_audio_bytes_; | |
222 }; | |
223 | |
224 struct MeasuringPoint { | |
225 MeasuringPoint(double bitrate_, double latency_, double percent_packet_drop_) | |
226 : bitrate(bitrate_), | |
227 latency(latency_), | |
228 percent_packet_drop(percent_packet_drop_) {} | |
229 bool operator<=(const MeasuringPoint& other) const { | |
230 return bitrate >= other.bitrate && latency <= other.latency && | |
231 percent_packet_drop <= other.percent_packet_drop; | |
232 } | |
233 bool operator>=(const MeasuringPoint& other) const { | |
234 return bitrate <= other.bitrate && latency >= other.latency && | |
235 percent_packet_drop >= other.percent_packet_drop; | |
236 } | |
237 | |
238 std::string AsString() const { | |
239 return base::StringPrintf( | |
240 "%f Mbit/s %f ms %f %% ", bitrate, latency, percent_packet_drop); | |
241 } | |
242 | |
243 double bitrate; | |
244 double latency; | |
245 double percent_packet_drop; | |
246 }; | |
247 | |
248 class RunOneBenchmark { | |
249 public: | |
250 RunOneBenchmark() | |
251 : start_time_(), | |
252 task_runner_(new test::FakeSingleThreadTaskRunner(&testing_clock_)), | |
253 testing_clock_sender_(new test::SkewedTickClock(&testing_clock_)), | |
254 task_runner_sender_( | |
255 new test::SkewedSingleThreadTaskRunner(task_runner_)), | |
256 testing_clock_receiver_(new test::SkewedTickClock(&testing_clock_)), | |
257 task_runner_receiver_( | |
258 new test::SkewedSingleThreadTaskRunner(task_runner_)), | |
259 cast_environment_sender_(new CastEnvironment( | |
260 scoped_ptr<base::TickClock>(testing_clock_sender_).Pass(), | |
261 task_runner_sender_, | |
262 task_runner_sender_, | |
263 task_runner_sender_)), | |
264 cast_environment_receiver_(new CastEnvironment( | |
265 scoped_ptr<base::TickClock>(testing_clock_receiver_).Pass(), | |
266 task_runner_receiver_, | |
267 task_runner_receiver_, | |
268 task_runner_receiver_)), | |
269 receiver_to_sender_(cast_environment_receiver_), | |
270 sender_to_receiver_(cast_environment_sender_), | |
271 video_bytes_encoded_(0), | |
272 audio_bytes_encoded_(0), | |
273 frames_sent_(0) { | |
274 testing_clock_.Advance( | |
275 base::TimeDelta::FromMilliseconds(kStartMillisecond)); | |
276 } | |
277 | |
278 void Configure(transport::VideoCodec video_codec, | |
279 transport::AudioCodec audio_codec, | |
280 int audio_sampling_frequency, | |
281 int max_number_of_video_buffers_used) { | |
282 audio_sender_config_.rtp_config.ssrc = 1; | |
283 audio_sender_config_.incoming_feedback_ssrc = 2; | |
284 audio_sender_config_.rtp_config.payload_type = 96; | |
285 audio_sender_config_.use_external_encoder = false; | |
286 audio_sender_config_.frequency = audio_sampling_frequency; | |
287 audio_sender_config_.channels = kAudioChannels; | |
288 audio_sender_config_.bitrate = kDefaultAudioEncoderBitrate; | |
289 audio_sender_config_.codec = audio_codec; | |
290 audio_sender_config_.rtp_config.max_delay_ms = kTargetDelay; | |
291 | |
292 audio_receiver_config_.feedback_ssrc = | |
293 audio_sender_config_.incoming_feedback_ssrc; | |
294 audio_receiver_config_.incoming_ssrc = audio_sender_config_.rtp_config.ssrc; | |
295 audio_receiver_config_.rtp_payload_type = | |
296 audio_sender_config_.rtp_config.payload_type; | |
297 audio_receiver_config_.frequency = audio_sender_config_.frequency; | |
298 audio_receiver_config_.channels = kAudioChannels; | |
299 audio_receiver_config_.max_frame_rate = 100; | |
300 audio_receiver_config_.codec.audio = audio_sender_config_.codec; | |
301 audio_receiver_config_.rtp_max_delay_ms = kTargetDelay; | |
302 | |
303 video_sender_config_.rtp_config.ssrc = 3; | |
304 video_sender_config_.incoming_feedback_ssrc = 4; | |
305 video_sender_config_.rtp_config.payload_type = 97; | |
306 video_sender_config_.use_external_encoder = false; | |
307 video_sender_config_.width = kVideoHdWidth; | |
308 video_sender_config_.height = kVideoHdHeight; | |
309 #if 0 | |
310 video_sender_config_.max_bitrate = 10000000; // 10Mbit max | |
311 video_sender_config_.min_bitrate = 1000000; // 1Mbit min | |
312 video_sender_config_.start_bitrate = 1000000; // 1Mbit start | |
313 #else | |
314 video_sender_config_.max_bitrate = 4000000; // 4Mbit all the time | |
315 video_sender_config_.min_bitrate = 4000000; | |
316 video_sender_config_.start_bitrate = 4000000; | |
317 #endif | |
318 video_sender_config_.max_qp = 56; | |
319 video_sender_config_.min_qp = 4; | |
320 video_sender_config_.max_frame_rate = 30; | |
321 video_sender_config_.max_number_of_video_buffers_used = | |
322 max_number_of_video_buffers_used; | |
323 video_sender_config_.codec = video_codec; | |
324 video_sender_config_.rtp_config.max_delay_ms = kTargetDelay; | |
325 | |
326 video_receiver_config_.feedback_ssrc = | |
327 video_sender_config_.incoming_feedback_ssrc; | |
328 video_receiver_config_.incoming_ssrc = video_sender_config_.rtp_config.ssrc; | |
329 video_receiver_config_.rtp_payload_type = | |
330 video_sender_config_.rtp_config.payload_type; | |
331 video_receiver_config_.codec.video = video_sender_config_.codec; | |
332 video_receiver_config_.frequency = kVideoFrequency; | |
333 video_receiver_config_.channels = 1; | |
334 video_receiver_config_.max_frame_rate = 100; | |
335 video_receiver_config_.rtp_max_delay_ms = kTargetDelay; | |
336 } | |
337 | |
338 void SetSenderClockSkew(double skew, base::TimeDelta offset) { | |
339 testing_clock_sender_->SetSkew(skew, offset); | |
340 task_runner_sender_->SetSkew(1.0 / skew); | |
341 } | |
342 | |
343 void SetReceiverClockSkew(double skew, base::TimeDelta offset) { | |
344 testing_clock_receiver_->SetSkew(skew, offset); | |
345 task_runner_receiver_->SetSkew(1.0 / skew); | |
346 } | |
347 | |
348 void Create() { | |
349 cast_receiver_ = CastReceiver::Create(cast_environment_receiver_, | |
350 audio_receiver_config_, | |
351 video_receiver_config_, | |
352 &receiver_to_sender_); | |
353 net::IPEndPoint dummy_endpoint; | |
354 transport_sender_.Init(new transport::CastTransportSenderImpl( | |
355 NULL, | |
356 testing_clock_sender_, | |
357 dummy_endpoint, | |
358 base::Bind(&UpdateCastTransportStatus), | |
359 base::Bind(&IgnoreRawEvents), | |
360 base::TimeDelta::FromSeconds(1), | |
361 task_runner_sender_, | |
362 &sender_to_receiver_), | |
363 &video_bytes_encoded_, | |
364 &audio_bytes_encoded_); | |
365 | |
366 cast_sender_ = | |
367 CastSender::Create(cast_environment_sender_, &transport_sender_); | |
368 | |
369 // Initializing audio and video senders. | |
370 cast_sender_->InitializeAudio(audio_sender_config_, | |
371 base::Bind(&AudioInitializationStatus)); | |
372 cast_sender_->InitializeVideo(video_sender_config_, | |
373 base::Bind(&VideoInitializationStatus), | |
374 CreateDefaultVideoEncodeAcceleratorCallback(), | |
375 CreateDefaultVideoEncodeMemoryCallback()); | |
376 | |
377 receiver_to_sender_.SetPacketReceiver( | |
378 cast_sender_->packet_receiver(), task_runner_, &testing_clock_); | |
379 sender_to_receiver_.SetPacketReceiver( | |
380 cast_receiver_->packet_receiver(), task_runner_, &testing_clock_); | |
381 } | |
382 | |
383 virtual ~RunOneBenchmark() { | |
384 cast_sender_.reset(); | |
385 cast_receiver_.reset(); | |
386 task_runner_->RunTasks(); | |
387 } | |
388 | |
389 void SendFakeVideoFrame() { | |
390 frames_sent_++; | |
391 cast_sender_->video_frame_input()->InsertRawVideoFrame( | |
392 media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2)), | |
393 testing_clock_sender_->NowTicks()); | |
394 } | |
395 | |
396 void RunTasks(int ms) { | |
397 task_runner_->Sleep(base::TimeDelta::FromMilliseconds(ms)); | |
398 } | |
399 | |
400 void BasicPlayerGotVideoFrame( | |
401 const scoped_refptr<media::VideoFrame>& video_frame, | |
402 const base::TimeTicks& render_time, | |
403 bool continuous) { | |
404 video_ticks_.push_back( | |
405 std::make_pair(testing_clock_receiver_->NowTicks(), render_time)); | |
406 cast_receiver_->RequestDecodedVideoFrame(base::Bind( | |
407 &RunOneBenchmark::BasicPlayerGotVideoFrame, base::Unretained(this))); | |
408 } | |
409 | |
410 void BasicPlayerGotAudioFrame(scoped_ptr<AudioBus> audio_bus, | |
411 const base::TimeTicks& playout_time, | |
412 bool is_continuous) { | |
413 audio_ticks_.push_back( | |
414 std::make_pair(testing_clock_receiver_->NowTicks(), playout_time)); | |
415 cast_receiver_->RequestDecodedAudioFrame(base::Bind( | |
416 &RunOneBenchmark::BasicPlayerGotAudioFrame, base::Unretained(this))); | |
417 } | |
418 | |
419 void StartBasicPlayer() { | |
420 cast_receiver_->RequestDecodedVideoFrame(base::Bind( | |
421 &RunOneBenchmark::BasicPlayerGotVideoFrame, base::Unretained(this))); | |
422 cast_receiver_->RequestDecodedAudioFrame(base::Bind( | |
423 &RunOneBenchmark::BasicPlayerGotAudioFrame, base::Unretained(this))); | |
424 } | |
425 | |
426 scoped_ptr<test::PacketPipe> CreateSimplePipe(const MeasuringPoint& p) { | |
427 scoped_ptr<test::PacketPipe> pipe = test::NewBuffer(65536, p.bitrate); | |
428 pipe->AppendToPipe( | |
429 test::NewRandomDrop(p.percent_packet_drop / 100.0).Pass()); | |
430 pipe->AppendToPipe(test::NewConstantDelay(p.latency / 1000.0)); | |
431 return pipe.Pass(); | |
432 } | |
433 | |
434 void Run(const MeasuringPoint& p) { | |
435 available_bitrate_ = p.bitrate; | |
436 Configure(transport::kFakeSoftwareVideo, transport::kPcm16, 32000, 1); | |
437 receiver_to_sender_.SetPacketPipe(CreateSimplePipe(p).Pass()); | |
438 sender_to_receiver_.SetPacketPipe(CreateSimplePipe(p).Pass()); | |
439 Create(); | |
440 StartBasicPlayer(); | |
441 | |
442 for (int frame = 0; frame < 1000; frame++) { | |
443 SendFakeVideoFrame(); | |
444 RunTasks(kFrameTimerMs); | |
445 } | |
446 RunTasks(100 * kFrameTimerMs); // Empty the pipeline. | |
447 VLOG(1) << "=============INPUTS============"; | |
448 VLOG(1) << "Bitrate: " << p.bitrate << " mbit/s"; | |
449 VLOG(1) << "Latency: " << p.latency << " ms"; | |
450 VLOG(1) << "Packet drop drop: " << p.percent_packet_drop << "%"; | |
451 VLOG(1) << "=============OUTPUTS============"; | |
452 VLOG(1) << "Frames lost: " << frames_lost(); | |
453 VLOG(1) << "Late frames: " << late_frames(); | |
454 VLOG(1) << "Playout margin: " << frame_playout_buffer().AsString(); | |
455 VLOG(1) << "Video bandwidth used: " << video_bandwidth() << " mbit/s (" | |
456 << (video_bandwidth() * 100 / desired_video_bitrate()) << "%)"; | |
457 VLOG(1) << "Good run: " << SimpleGood(); | |
458 } | |
459 | |
460 // Metrics | |
461 int frames_lost() const { return frames_sent_ - video_ticks_.size(); } | |
462 | |
463 int late_frames() const { | |
464 int frames = 0; | |
465 // Ignore the first two seconds of video or so. | |
466 for (size_t i = 60; i < video_ticks_.size(); i++) { | |
467 if (video_ticks_[i].first > video_ticks_[i].second) { | |
468 frames++; | |
469 } | |
470 } | |
471 return frames; | |
472 } | |
473 | |
474 test::MeanAndError frame_playout_buffer() const { | |
475 std::vector<double> values; | |
476 for (size_t i = 0; i < video_ticks_.size(); i++) { | |
477 values.push_back( | |
478 (video_ticks_[i].second - video_ticks_[i].first).InMillisecondsF()); | |
479 } | |
480 return test::MeanAndError(values); | |
481 } | |
482 | |
483 // Mbits per second | |
484 double video_bandwidth() const { | |
485 double seconds = (kFrameTimerMs * frames_sent_ / 1000.0); | |
486 double megabits = video_bytes_encoded_ * 8 / 1000000.0; | |
487 return megabits / seconds; | |
488 } | |
489 | |
490 // Mbits per second | |
491 double audio_bandwidth() const { | |
492 double seconds = (kFrameTimerMs * frames_sent_ / 1000.0); | |
493 double megabits = audio_bytes_encoded_ * 8 / 1000000.0; | |
494 return megabits / seconds; | |
495 } | |
496 | |
497 double desired_video_bitrate() { | |
498 return std::min<double>(available_bitrate_, | |
499 video_sender_config_.max_bitrate / 1000000.0); | |
500 } | |
501 | |
502 bool SimpleGood() { | |
503 return frames_lost() <= 1 && late_frames() <= 1 && | |
504 video_bandwidth() > desired_video_bitrate() * 0.8 && | |
505 video_bandwidth() < desired_video_bitrate() * 1.2; | |
506 } | |
507 | |
508 private: | |
509 FrameReceiverConfig audio_receiver_config_; | |
510 FrameReceiverConfig video_receiver_config_; | |
511 AudioSenderConfig audio_sender_config_; | |
512 VideoSenderConfig video_sender_config_; | |
513 | |
514 base::TimeTicks start_time_; | |
515 | |
516 // These run in "test time" | |
517 base::SimpleTestTickClock testing_clock_; | |
518 scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_; | |
519 | |
520 // These run on the sender timeline. | |
521 test::SkewedTickClock* testing_clock_sender_; | |
522 scoped_refptr<test::SkewedSingleThreadTaskRunner> task_runner_sender_; | |
523 | |
524 // These run on the receiver timeline. | |
525 test::SkewedTickClock* testing_clock_receiver_; | |
526 scoped_refptr<test::SkewedSingleThreadTaskRunner> task_runner_receiver_; | |
527 | |
528 scoped_refptr<CastEnvironment> cast_environment_sender_; | |
529 scoped_refptr<CastEnvironment> cast_environment_receiver_; | |
530 | |
531 LoopBackTransport receiver_to_sender_; | |
532 LoopBackTransport sender_to_receiver_; | |
533 CastTransportSenderWrapper transport_sender_; | |
534 uint64 video_bytes_encoded_; | |
535 uint64 audio_bytes_encoded_; | |
536 | |
537 scoped_ptr<CastReceiver> cast_receiver_; | |
538 scoped_ptr<CastSender> cast_sender_; | |
539 | |
540 int frames_sent_; | |
541 double available_bitrate_; | |
542 std::vector<std::pair<base::TimeTicks, base::TimeTicks> > audio_ticks_; | |
543 std::vector<std::pair<base::TimeTicks, base::TimeTicks> > video_ticks_; | |
544 }; | |
545 | |
546 enum CacheResult { FOUND_TRUE, FOUND_FALSE, NOT_FOUND }; | |
547 | |
548 template <class T> | |
549 class BenchmarkCache { | |
550 public: | |
551 CacheResult Lookup(const T& x) { | |
552 base::AutoLock key(lock_); | |
553 for (size_t i = 0; i < results_.size(); i++) { | |
554 if (results_[i].second) { | |
555 if (x <= results_[i].first) { | |
556 VLOG(2) << "TRUE because: " << x.AsString() | |
557 << " <= " << results_[i].first.AsString(); | |
558 return FOUND_TRUE; | |
559 } | |
560 } else { | |
561 if (x >= results_[i].first) { | |
562 VLOG(2) << "FALSE because: " << x.AsString() | |
563 << " >= " << results_[i].first.AsString(); | |
564 return FOUND_FALSE; | |
565 } | |
566 } | |
567 } | |
568 return NOT_FOUND; | |
569 } | |
570 | |
571 void Add(const T& x, bool result) { | |
572 base::AutoLock key(lock_); | |
573 VLOG(2) << "Cache Insert: " << x.AsString() << " = " << result; | |
574 results_.push_back(std::make_pair(x, result)); | |
575 } | |
576 | |
577 private: | |
578 base::Lock lock_; | |
579 std::vector<std::pair<T, bool> > results_; | |
580 }; | |
581 | |
582 struct SearchVariable { | |
583 SearchVariable() : base(0.0), grade(0.0) {} | |
584 SearchVariable(double b, double g) : base(b), grade(g) {} | |
585 SearchVariable blend(const SearchVariable& other, double factor) { | |
586 CHECK_GE(factor, 0); | |
587 CHECK_LE(factor, 1.0); | |
588 return SearchVariable(base * (1 - factor) + other.base * factor, | |
589 grade * (1 - factor) + other.grade * factor); | |
590 } | |
591 double value(double x) const { return base + grade * x; } | |
592 double base; | |
593 double grade; | |
594 }; | |
595 | |
596 struct SearchVector { | |
597 SearchVector blend(const SearchVector& other, double factor) { | |
598 SearchVector ret; | |
599 ret.bitrate = bitrate.blend(other.bitrate, factor); | |
600 ret.latency = latency.blend(other.latency, factor); | |
601 ret.packet_drop = packet_drop.blend(other.packet_drop, factor); | |
602 return ret; | |
603 } | |
604 | |
605 SearchVector average(const SearchVector& other) { | |
606 return blend(other, 0.5); | |
607 } | |
608 | |
609 MeasuringPoint GetMeasuringPoint(double v) const { | |
610 return MeasuringPoint( | |
611 bitrate.value(-v), latency.value(v), packet_drop.value(v)); | |
612 } | |
613 std::string AsString(double v) { return GetMeasuringPoint(v).AsString(); } | |
614 | |
615 SearchVariable bitrate; | |
616 SearchVariable latency; | |
617 SearchVariable packet_drop; | |
618 }; | |
619 | |
620 class CastBenchmark { | |
621 public: | |
622 bool RunOnePoint(const SearchVector& v, double multiplier) { | |
623 MeasuringPoint p = v.GetMeasuringPoint(multiplier); | |
624 VLOG(1) << "RUN: v = " << multiplier << " p = " << p.AsString(); | |
625 if (p.bitrate <= 0) { | |
626 return false; | |
627 } | |
628 switch (cache_.Lookup(p)) { | |
629 case FOUND_TRUE: | |
630 return true; | |
631 case FOUND_FALSE: | |
632 return false; | |
633 case NOT_FOUND: | |
634 // Keep going | |
635 break; | |
636 } | |
637 bool result = true; | |
638 for (int tries = 0; tries < 3 && result; tries++) { | |
639 RunOneBenchmark benchmark; | |
640 benchmark.Run(p); | |
641 result &= benchmark.SimpleGood(); | |
642 } | |
643 cache_.Add(p, result); | |
644 return result; | |
645 } | |
646 | |
647 void BinarySearch(SearchVector v, double accuracy) { | |
648 double min = 0.0; | |
649 double max = 1.0; | |
650 while (RunOnePoint(v, max)) { | |
651 min = max; | |
652 max *= 2; | |
653 } | |
654 | |
655 while (max - min > accuracy) { | |
656 double avg = (min + max) / 2; | |
657 if (RunOnePoint(v, avg)) { | |
658 min = avg; | |
659 } else { | |
660 max = avg; | |
661 } | |
662 } | |
663 | |
664 // Print a data point to stdout. | |
665 base::AutoLock key(lock_); | |
666 MeasuringPoint p = v.GetMeasuringPoint(min); | |
667 fprintf(stdout, "%f %f %f\n", p.bitrate, p.latency, p.percent_packet_drop); | |
668 fflush(stdout); | |
669 } | |
670 | |
671 void SpanningSearch(int max, | |
672 int x, | |
673 int y, | |
674 int skip, | |
675 SearchVector a, | |
676 SearchVector b, | |
677 SearchVector c, | |
678 double accuracy, | |
679 std::vector<linked_ptr<base::Thread> >* threads) { | |
680 static int thread_num = 0; | |
681 if (x > max) return; | |
682 if (skip > max) { | |
683 if (y > x) return; | |
684 SearchVector ab = a.blend(b, static_cast<double>(x) / max); | |
685 SearchVector ac = a.blend(c, static_cast<double>(x) / max); | |
686 SearchVector v = ab.blend(ac, x == y ? 1.0 : static_cast<double>(y) / x); | |
687 thread_num++; | |
688 (*threads)[thread_num % threads->size()]->message_loop()->PostTask( | |
689 FROM_HERE, | |
690 base::Bind(&CastBenchmark::BinarySearch, | |
691 base::Unretained(this), | |
692 v, | |
693 accuracy)); | |
694 } else { | |
695 skip *= 2; | |
696 SpanningSearch(max, x, y, skip, a, b, c, accuracy, threads); | |
697 SpanningSearch(max, x + skip, y + skip, skip, a, b, c, accuracy, threads); | |
698 SpanningSearch(max, x + skip, y, skip, a, b, c, accuracy, threads); | |
699 SpanningSearch(max, x, y + skip, skip, a, b, c, accuracy, threads); | |
700 } | |
701 } | |
702 | |
703 void Run() { | |
704 // Spanning search. | |
705 | |
706 std::vector<linked_ptr<base::Thread> > threads; | |
707 for (size_t i = 0; i < 16; i++) { | |
708 threads.push_back(make_linked_ptr(new base::Thread( | |
709 base::StringPrintf("cast_bench_thread_%lu", i)))); | |
710 threads[i]->Start(); | |
711 } | |
712 | |
713 if (CommandLine::ForCurrentProcess()->HasSwitch("single-run")) { | |
714 SearchVector a; | |
715 a.bitrate.base = 100.0; | |
716 a.bitrate.grade = 1.0; | |
717 a.latency.grade = 1.0; | |
718 a.packet_drop.grade = 1.0; | |
719 threads[0]->message_loop()->PostTask( | |
720 FROM_HERE, | |
721 base::Bind(base::IgnoreResult(&CastBenchmark::RunOnePoint), | |
722 base::Unretained(this), | |
723 a, | |
724 1.0)); | |
725 } else { | |
726 SearchVector a, b, c; | |
727 a.bitrate.base = b.bitrate.base = c.bitrate.base = 100.0; | |
728 a.bitrate.grade = 1.0; | |
729 b.latency.grade = 1.0; | |
730 c.packet_drop.grade = 1.0; | |
731 | |
732 SpanningSearch(512, | |
733 0, | |
734 0, | |
735 1, | |
736 a, | |
737 b, | |
738 c, | |
739 0.01, | |
740 &threads); | |
741 } | |
742 | |
743 for (size_t i = 0; i < threads.size(); i++) { | |
744 threads[i]->Stop(); | |
745 } | |
746 } | |
747 | |
748 private: | |
749 BenchmarkCache<MeasuringPoint> cache_; | |
750 base::Lock lock_; | |
751 }; | |
752 | |
753 } // namespace cast | |
754 } // namespace media | |
755 | |
756 int main(int argc, char** argv) { | |
757 base::AtExitManager at_exit; | |
758 CommandLine::Init(argc, argv); | |
759 media::cast::CastBenchmark benchmark; | |
760 if (getenv("PROFILE_FILE")) { | |
761 std::string profile_file(getenv("PROFILE_FILE")); | |
762 base::debug::StartProfiling(profile_file); | |
763 benchmark.Run(); | |
764 base::debug::StopProfiling(); | |
765 } else { | |
766 benchmark.Run(); | |
767 } | |
768 } | |
OLD | NEW |